Document Request, Response, RequestHeader, ResponseHeader, URI and Args

This commit is contained in:
Aliaksandr Valialkin
2015-10-23 15:56:12 +03:00
parent 40e775dce5
commit e9d6d7f561
5 changed files with 175 additions and 27 deletions
+44 -9
View File
@@ -5,6 +5,9 @@ import (
"errors"
)
// Args represents query arguments.
//
// It is forbidden copying Args instances. Create new instances instead.
type Args struct {
args []argsKV
bufKV argsKV
@@ -16,19 +19,25 @@ type argsKV struct {
value []byte
}
// Clear clear query args.
func (a *Args) Clear() {
a.args = a.args[:0]
}
// Len returns the number of query args.
func (a *Args) Len() int {
return len(a.args)
}
// Parse parses the given string containing query args.
func (a *Args) Parse(s string) {
a.buf = AppendBytesStr(a.buf[:0], s)
a.ParseBytes(a.buf)
}
// ParseBytes parses the given b containing query args.
//
// It is safe modifying b buffer conntents after ParseBytes return.
func (a *Args) ParseBytes(b []byte) {
a.Clear()
@@ -56,11 +65,16 @@ func (a *Args) ParseBytes(b []byte) {
}
}
// String returns string representation of query args.
func (a *Args) String() string {
a.buf = a.AppendBytes(a.buf[:0])
return string(a.buf)
}
// AppendBytes appends query string to dst and returns dst
// (which may be newly allocated).
//
// It is safe modifying dst buffer after AppendBytes returns.
func (a *Args) AppendBytes(dst []byte) []byte {
for i, n := 0, len(a.args); i < n; i++ {
kv := &a.args[i]
@@ -76,6 +90,7 @@ func (a *Args) AppendBytes(dst []byte) []byte {
return dst
}
// Del deletes argument with the given key from query args.
func (a *Args) Del(key string) {
for i, n := 0, len(a.args); i < n; i++ {
kv := &a.args[i]
@@ -89,11 +104,15 @@ func (a *Args) Del(key string) {
}
}
// Set sets 'key=value' argument.
func (a *Args) Set(key, value string) {
a.bufKV.value = AppendBytesStr(a.bufKV.value[:0], value)
a.SetBytes(key, a.bufKV.value)
}
// SetBytes sets 'key=value' argument.
//
// It is safe modifying valye buffer after SetBytes() return.
func (a *Args) SetBytes(key string, value []byte) {
a.bufKV.key = AppendBytesStr(a.bufKV.key[:0], key)
a.args = setKV(a.args, a.bufKV.key, value)
@@ -133,35 +152,42 @@ func peekKV(h []argsKV, k []byte) []byte {
return nil
}
func (a *Args) GetBytes(dst []byte, key string) []byte {
value := a.Peek(key)
return append(dst[:0], value...)
}
// Warning: each call allocates memory for returned string.
// Get returns query arg value for the given key.
//
// Each Get call allocates memory for returned string,
// so consider using Peek() instead.
func (a *Args) Get(key string) string {
return string(a.Peek(key))
}
// Peek returns query arg value for the given key.
//
// Returned value is valid until the next Args call.
func (a *Args) Peek(key string) []byte {
a.bufKV.key = AppendBytesStr(a.bufKV.key[:0], key)
return peekKV(a.args, a.bufKV.key)
}
// Has returns true if the given key exists in Args.
func (a *Args) Has(key string) bool {
return a.Peek(key) != nil
}
var errNoArgValue = errors.New("No value for the given key")
// ErrNoArgValue is returned when value with the given key is missing.
var ErrNoArgValue = errors.New("No value for the given key")
// GetUint returns uint value for the given key.
func (a *Args) GetUint(key string) (int, error) {
value := a.Peek(key)
if len(value) == 0 {
return -1, errNoArgValue
return -1, ErrNoArgValue
}
return parseUint(value)
}
// GetUintOrZero returns uint value for the given key.
//
// Zero (0) is returned on error.
func (a *Args) GetUintOrZero(key string) int {
n, err := a.GetUint(key)
if err != nil {
@@ -170,14 +196,18 @@ func (a *Args) GetUintOrZero(key string) int {
return n
}
// GetUfloat returns ufloat value for the given key.
func (a *Args) GetUfloat(key string) (float64, error) {
value := a.Peek(key)
if len(value) == 0 {
return -1, errNoArgValue
return -1, ErrNoArgValue
}
return parseUfloat(value)
}
// GetUfloatOrZero returns ufloat value for the given key.
//
// Zero (0) is returned on error.
func (a *Args) GetUfloatOrZero(key string) float64 {
f, err := a.GetUfloat(key)
if err != nil {
@@ -186,6 +216,9 @@ func (a *Args) GetUfloatOrZero(key string) float64 {
return f
}
// EqualBytesStr returns true if string(b) == s.
//
// It doesn't allocate memory unlike string(b) do.
func EqualBytesStr(b []byte, s string) bool {
if len(s) != len(b) {
return false
@@ -198,6 +231,8 @@ func EqualBytesStr(b []byte, s string) bool {
return true
}
// AppendBytesStr appends src to dst and returns dst
// (which may be newly allocated).
func AppendBytesStr(dst []byte, src string) []byte {
for i, n := 0, len(src); i < n; i++ {
dst = append(dst, src[i])
+1 -2
View File
@@ -188,7 +188,6 @@ func testArgsHasNot(t *testing.T, a *Args, s string, unexpectedKeys ...string) {
}
func testArgsParse(t *testing.T, a *Args, s string, expectedLen int, expectedArgs ...string) {
var buf []byte
a.Parse(s)
if a.Len() != expectedLen {
t.Fatalf("Unexpected args len %d. Expected %d. s=%q", a.Len(), expectedLen, s)
@@ -197,7 +196,7 @@ func testArgsParse(t *testing.T, a *Args, s string, expectedLen int, expectedArg
tmp := strings.SplitN(xx, "=", 2)
k := tmp[0]
v := tmp[1]
buf = a.GetBytes(buf, k)
buf := a.Peek(k)
if string(buf) != v {
t.Fatalf("Unexpected value for key=%q: %q. Expected %q. s=%q", k, buf, v, s)
}
+51 -4
View File
@@ -45,9 +45,17 @@ var (
strPostArgsContentType = []byte("application/x-www-form-urlencoded")
)
// ResponseHeader represents HTTP response header.
type ResponseHeader struct {
StatusCode int
ContentLength int
// Response status code.
StatusCode int
// Response content length read from Content-Length header.
//
// It may be negative on chunked response.
ContentLength int
// Set to true if response contains 'Connection: close' header.
ConnectionClose bool
contentType []byte
@@ -57,9 +65,17 @@ type ResponseHeader struct {
bufKV argsKV
}
// RequestHeader represents HTTP request header.
type RequestHeader struct {
Method []byte
RequestURI []byte
// Request method (e.g. 'GET', 'POST', etc.).
Method []byte
// Request URI read from the first request line.
RequestURI []byte
// Request content length read from Content-Length header.
//
// It may be negative on chunked request.
ContentLength int
host []byte
@@ -69,18 +85,22 @@ type RequestHeader struct {
bufKV argsKV
}
// IsMethodGet returns true if request method is GET.
func (h *RequestHeader) IsMethodGet() bool {
return bytes.Equal(h.Method, strGet)
}
// IsMethodPost returns true if request methos is POST.
func (h *RequestHeader) IsMethodPost() bool {
return bytes.Equal(h.Method, strPost)
}
// IsMethodHead returns true if request method is HEAD.
func (h *RequestHeader) IsMethodHead() bool {
return bytes.Equal(h.Method, strHead)
}
// Clear clears response header.
func (h *ResponseHeader) Clear() {
h.StatusCode = 0
h.ContentLength = 0
@@ -92,6 +112,7 @@ func (h *ResponseHeader) Clear() {
h.h = h.h[:0]
}
// Clear clears request header.
func (h *RequestHeader) Clear() {
h.Method = h.Method[:0]
h.RequestURI = h.RequestURI[:0]
@@ -103,6 +124,7 @@ func (h *RequestHeader) Clear() {
h.h = h.h[:0]
}
// Set sets the given 'key: value' header.
func (h *ResponseHeader) Set(key, value string) {
initHeaderKV(&h.bufKV, key, value)
h.set(h.bufKV.key, h.bufKV.value)
@@ -130,6 +152,9 @@ func (h *ResponseHeader) set(key, value []byte) {
}
}
// SetBytes sets the given 'key: value' header.
//
// It is safe modifying value buffer after SetBytes return.
func (h *ResponseHeader) SetBytes(key string, value []byte) {
k := getHeaderKeyBytes(&h.bufKV, key)
h.set(k, value)
@@ -140,6 +165,7 @@ func (h *ResponseHeader) setStr(key []byte, value string) {
h.set(key, h.bufKV.value)
}
// Set sets the given 'key: value' header.
func (h *RequestHeader) Set(key, value string) {
initHeaderKV(&h.bufKV, key, value)
h.set(h.bufKV.key, h.bufKV.value)
@@ -162,16 +188,27 @@ func (h *RequestHeader) set(key, value []byte) {
}
}
// SetBytes sets the given 'key: value' header.
//
// It is safe modifying value buffer after SetBytes return.
func (h *RequestHeader) SetBytes(key string, value []byte) {
k := getHeaderKeyBytes(&h.bufKV, key)
h.set(k, value)
}
// Peek returns header value for the given key.
//
// Returned value may change on the next call to ResponseHeader.
// Do not store references to returned value. Make copies instead.
func (h *ResponseHeader) Peek(key string) []byte {
k := getHeaderKeyBytes(&h.bufKV, key)
return h.peek(k)
}
// Peek returns header value for the given key.
//
// Returned value may change on the next call to RequestHeader.
// Do not store references to returned value. Make copies instead.
func (h *RequestHeader) Peek(key string) []byte {
k := getHeaderKeyBytes(&h.bufKV, key)
return h.peek(k)
@@ -204,14 +241,21 @@ func (h *RequestHeader) peek(key []byte) []byte {
}
}
// Get returns header value for the given key.
//
// Get allocates memory on each call, so prefer using Peek instead.
func (h *ResponseHeader) Get(key string) string {
return string(h.Peek(key))
}
// Get returns header value for the given key.
//
// Get allocates memory on each call, so prefer using Peek instead.
func (h *RequestHeader) Get(key string) string {
return string(h.Peek(key))
}
// Read reads response header from r.
func (h *ResponseHeader) Read(r *bufio.Reader) error {
n := 1
for {
@@ -253,6 +297,7 @@ func (h *ResponseHeader) tryRead(r *bufio.Reader, n int) error {
return nil
}
// Read reads request header from r.
func (h *RequestHeader) Read(r *bufio.Reader) error {
n := 1
for {
@@ -320,6 +365,7 @@ func refreshServerDate() {
serverDate.Store([]byte(s))
}
// Write writes response header to w.
func (h *ResponseHeader) Write(w *bufio.Writer) error {
statusCode := h.StatusCode
if statusCode < 0 {
@@ -363,6 +409,7 @@ func (h *ResponseHeader) Write(w *bufio.Writer) error {
return err
}
// Write writes request header to w.
func (h *RequestHeader) Write(w *bufio.Writer) error {
method := h.Method
if len(method) == 0 {
+41 -3
View File
@@ -9,14 +9,22 @@ import (
"time"
)
// Request represents HTTP request.
//
// It is forbidden copying Request instances. Create new instances instead.
type Request struct {
// Request header
Header RequestHeader
Body []byte
// Request body
Body []byte
// Request URI.
// URI becomes available only after Request.ParseURI() call.
URI URI
parsedURI bool
// Arguments sent in POST.
// PostArgs becomes available only after Request.ParsePostArgs() call.
PostArgs Args
parsedPostArgs bool
@@ -25,11 +33,17 @@ type Request struct {
timeoutTimer *time.Timer
}
// Response represents HTTP response.
//
// It is forbidden copying Response instances. Create new instances instead.
type Response struct {
// Response header
Header ResponseHeader
Body []byte
// if set to true, Response.Read() skips reading body.
// Response body
Body []byte
// If set to true, Response.Read() skips reading body.
// Use it for HEAD requests.
SkipBody bool
@@ -37,6 +51,7 @@ type Response struct {
timeoutTimer *time.Timer
}
// ParseURI parses request uri and fills Request.URI.
func (req *Request) ParseURI() {
if req.parsedURI {
return
@@ -45,6 +60,7 @@ func (req *Request) ParseURI() {
req.parsedURI = true
}
// ParsePostArgs parses args sent in POST body and fills Request.PostArgs.
func (req *Request) ParsePostArgs() error {
if req.parsedPostArgs {
return nil
@@ -62,6 +78,7 @@ func (req *Request) ParsePostArgs() error {
return nil
}
// Clear clears request contents.
func (req *Request) Clear() {
req.Header.Clear()
req.Body = req.Body[:0]
@@ -71,13 +88,21 @@ func (req *Request) Clear() {
req.parsedPostArgs = false
}
// Clear clears response contents.
func (resp *Response) Clear() {
resp.Header.Clear()
resp.Body = resp.Body[:0]
}
// ErrReadTimeout may be returned from Request.ReadTimeout
// or Response.ReadTimeout on timeout.
var ErrReadTimeout = errors.New("read timeout")
// ReadTimeout reads request (including body) from the given r during
// the given timeout.
//
// If request couldn't be read during the given timeout,
// it returns ErrReadTimeout.
func (req *Request) ReadTimeout(r *bufio.Reader, timeout time.Duration) error {
if timeout <= 0 {
return req.Read(r)
@@ -107,6 +132,11 @@ func (req *Request) ReadTimeout(r *bufio.Reader, timeout time.Duration) error {
return err
}
// ReadTimeout reads response (including body) from the given r during
// the given timeout.
//
// If response couldn't be read during the given timeout,
// it returns ErrReadTimeout.
func (resp *Response) ReadTimeout(r *bufio.Reader, timeout time.Duration) error {
if timeout <= 0 {
return resp.Read(r)
@@ -136,6 +166,7 @@ func (resp *Response) ReadTimeout(r *bufio.Reader, timeout time.Duration) error
return err
}
// Read reads request (including body) from the given r.
func (req *Request) Read(r *bufio.Reader) error {
req.Body = req.Body[:0]
req.URI.Clear()
@@ -159,6 +190,7 @@ func (req *Request) Read(r *bufio.Reader) error {
return nil
}
// Read reads response (including body) from the given r.
func (resp *Response) Read(r *bufio.Reader) error {
resp.Body = resp.Body[:0]
@@ -190,6 +222,9 @@ func isSkipResponseBody(statusCode int) bool {
return statusCode == StatusNoContent || statusCode == StatusNotModified
}
// Write write request to w.
//
// Write doesn't flush request to w for performance reasons.
func (req *Request) Write(w *bufio.Writer) error {
contentLengthOld := req.Header.ContentLength
req.Header.ContentLength = len(req.Body)
@@ -206,6 +241,9 @@ func (req *Request) Write(w *bufio.Writer) error {
return err
}
// Write writes response to w.
//
// Write doesn't flush response to w for performance reasons.
func (resp *Response) Write(w *bufio.Writer) error {
contentLengthOld := resp.Header.ContentLength
resp.Header.ContentLength = len(resp.Body)
+38 -9
View File
@@ -4,24 +4,46 @@ import (
"bytes"
)
// URI represents URI :) .
//
// It is forbidden copying URI instances. Create new instances instead.
type URI struct {
// Full uri like {Scheme}://{Host}{Path}?{QueryString}#{Hash}
URI []byte
// Original Path passed to URI.Parse()
// Original path passed to URI.Parse()
PathOriginal []byte
Scheme []byte
Host []byte
Path []byte
QueryString []byte
Hash []byte
// Scheme part, i.e. http of http://aaa.com/foo/bar?baz=123#qwe .
//
// Scheme is always lowercased.
Scheme []byte
// Becomes available after URI.ParseQueryArgs() call
// Host part, i.e. aaa.com of http://aaa.com/foo/bar?baz=123#qwe .
//
// Host is always lowercased.
Host []byte
// Path part, i.e. /foo/bar of http://aaa.com/foo/bar?baz=123#qwe .
//
// Path is always urldecoded and normalized,
// i.e. '//f%20obar/baz/../zzz' becomes '/f obar/zzz'.
Path []byte
// Query string part, i.e. baz=123 of http://aaa.com/foo/bar?baz=123#qwe .
QueryString []byte
// Hash part, i.e. qwe of http://aaa.com/foo/bar?baz=123#qwe .
Hash []byte
// Parsed query string arguments.
//
// Becomes available after URI.ParseQueryArgs() call.
QueryArgs Args
parsedQueryArgs bool
}
// Clear clears uri.
func (x *URI) Clear() {
x.URI = x.URI[:0]
x.PathOriginal = x.PathOriginal[:0]
@@ -34,6 +56,9 @@ func (x *URI) Clear() {
x.parsedQueryArgs = false
}
// Parse initializes URI from the given host and uri.
//
// It is safe modifying host and uri buffers after the Parse call.
func (x *URI) Parse(host, uri []byte) {
x.Clear()
@@ -122,7 +147,10 @@ func normalizePath(dst, src []byte) []byte {
return b
}
// Appends RequestURI to dst. RequestURI doesn't contain Scheme and Host.
// AppendRequestURI appends RequestURI to dst and returns dst
// (which may be newly allocated).
//
// Appended RequestURI doesn't contain Scheme and Host.
func (x *URI) AppendRequestURI(dst []byte) []byte {
path := x.Path
if len(path) == 0 {
@@ -140,7 +168,7 @@ func (x *URI) AppendRequestURI(dst []byte) []byte {
return dst
}
// Appends URI to dst.
// AppendBytes appends URI to dst and returns dst (with may be newly allocated).
func (x *URI) AppendBytes(dst []byte) []byte {
startPos := len(dst)
scheme := x.Scheme
@@ -172,6 +200,7 @@ func splitHostUri(host, uri []byte) ([]byte, []byte, []byte) {
return scheme, uri[:n], uri[n:]
}
// ParseQueryArgs initializes QueryArgs by parsing QueryString.
func (x *URI) ParseQueryArgs() {
if x.parsedQueryArgs {
return