| package fasthttp |
| |
| import ( |
| "bufio" |
| "bytes" |
| "errors" |
| "fmt" |
| "io" |
| "mime/multipart" |
| "os" |
| "sync" |
| |
| "github.com/valyala/bytebufferpool" |
| ) |
| |
| // Request represents HTTP request. |
| // |
| // It is forbidden copying Request instances. Create new instances |
| // and use CopyTo instead. |
| // |
| // Request instance MUST NOT be used from concurrently running goroutines. |
| type Request struct { |
| noCopy noCopy |
| |
| // Request header |
| // |
| // Copying Header by value is forbidden. Use pointer to Header instead. |
| Header RequestHeader |
| |
| uri URI |
| postArgs Args |
| |
| bodyStream io.Reader |
| w requestBodyWriter |
| body *bytebufferpool.ByteBuffer |
| |
| multipartForm *multipart.Form |
| multipartFormBoundary string |
| |
| // Group bool members in order to reduce Request object size. |
| parsedURI bool |
| parsedPostArgs bool |
| |
| keepBodyBuffer bool |
| |
| isTLS bool |
| } |
| |
| // Response represents HTTP response. |
| // |
| // It is forbidden copying Response instances. Create new instances |
| // and use CopyTo instead. |
| // |
| // Response instance MUST NOT be used from concurrently running goroutines. |
| type Response struct { |
| noCopy noCopy |
| |
| // Response header |
| // |
| // Copying Header by value is forbidden. Use pointer to Header instead. |
| Header ResponseHeader |
| |
| bodyStream io.Reader |
| w responseBodyWriter |
| body *bytebufferpool.ByteBuffer |
| |
| // Response.Read() skips reading body if set to true. |
| // Use it for reading HEAD responses. |
| // |
| // Response.Write() skips writing body if set to true. |
| // Use it for writing HEAD responses. |
| SkipBody bool |
| |
| keepBodyBuffer bool |
| } |
| |
| // SetHost sets host for the request. |
| func (req *Request) SetHost(host string) { |
| req.URI().SetHost(host) |
| } |
| |
| // SetHostBytes sets host for the request. |
| func (req *Request) SetHostBytes(host []byte) { |
| req.URI().SetHostBytes(host) |
| } |
| |
| // Host returns the host for the given request. |
| func (req *Request) Host() []byte { |
| return req.URI().Host() |
| } |
| |
| // SetRequestURI sets RequestURI. |
| func (req *Request) SetRequestURI(requestURI string) { |
| req.Header.SetRequestURI(requestURI) |
| req.parsedURI = false |
| } |
| |
| // SetRequestURIBytes sets RequestURI. |
| func (req *Request) SetRequestURIBytes(requestURI []byte) { |
| req.Header.SetRequestURIBytes(requestURI) |
| req.parsedURI = false |
| } |
| |
| // RequestURI returns request's URI. |
| func (req *Request) RequestURI() []byte { |
| if req.parsedURI { |
| requestURI := req.uri.RequestURI() |
| req.SetRequestURIBytes(requestURI) |
| } |
| return req.Header.RequestURI() |
| } |
| |
| // StatusCode returns response status code. |
| func (resp *Response) StatusCode() int { |
| return resp.Header.StatusCode() |
| } |
| |
| // SetStatusCode sets response status code. |
| func (resp *Response) SetStatusCode(statusCode int) { |
| resp.Header.SetStatusCode(statusCode) |
| } |
| |
| // ConnectionClose returns true if 'Connection: close' header is set. |
| func (resp *Response) ConnectionClose() bool { |
| return resp.Header.ConnectionClose() |
| } |
| |
| // SetConnectionClose sets 'Connection: close' header. |
| func (resp *Response) SetConnectionClose() { |
| resp.Header.SetConnectionClose() |
| } |
| |
| // ConnectionClose returns true if 'Connection: close' header is set. |
| func (req *Request) ConnectionClose() bool { |
| return req.Header.ConnectionClose() |
| } |
| |
| // SetConnectionClose sets 'Connection: close' header. |
| func (req *Request) SetConnectionClose() { |
| req.Header.SetConnectionClose() |
| } |
| |
| // SendFile registers file on the given path to be used as response body |
| // when Write is called. |
| // |
| // Note that SendFile doesn't set Content-Type, so set it yourself |
| // with Header.SetContentType. |
| func (resp *Response) SendFile(path string) error { |
| f, err := os.Open(path) |
| if err != nil { |
| return err |
| } |
| fileInfo, err := f.Stat() |
| if err != nil { |
| f.Close() |
| return err |
| } |
| size64 := fileInfo.Size() |
| size := int(size64) |
| if int64(size) != size64 { |
| size = -1 |
| } |
| |
| resp.Header.SetLastModified(fileInfo.ModTime()) |
| resp.SetBodyStream(f, size) |
| return nil |
| } |
| |
| // SetBodyStream sets request body stream and, optionally body size. |
| // |
| // If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes |
| // before returning io.EOF. |
| // |
| // If bodySize < 0, then bodyStream is read until io.EOF. |
| // |
| // bodyStream.Close() is called after finishing reading all body data |
| // if it implements io.Closer. |
| // |
| // Note that GET and HEAD requests cannot have body. |
| // |
| // See also SetBodyStreamWriter. |
| func (req *Request) SetBodyStream(bodyStream io.Reader, bodySize int) { |
| req.ResetBody() |
| req.bodyStream = bodyStream |
| req.Header.SetContentLength(bodySize) |
| } |
| |
| // SetBodyStream sets response body stream and, optionally body size. |
| // |
| // If bodySize is >= 0, then the bodyStream must provide exactly bodySize bytes |
| // before returning io.EOF. |
| // |
| // If bodySize < 0, then bodyStream is read until io.EOF. |
| // |
| // bodyStream.Close() is called after finishing reading all body data |
| // if it implements io.Closer. |
| // |
| // See also SetBodyStreamWriter. |
| func (resp *Response) SetBodyStream(bodyStream io.Reader, bodySize int) { |
| resp.ResetBody() |
| resp.bodyStream = bodyStream |
| resp.Header.SetContentLength(bodySize) |
| } |
| |
| // IsBodyStream returns true if body is set via SetBodyStream* |
| func (req *Request) IsBodyStream() bool { |
| return req.bodyStream != nil |
| } |
| |
| // IsBodyStream returns true if body is set via SetBodyStream* |
| func (resp *Response) IsBodyStream() bool { |
| return resp.bodyStream != nil |
| } |
| |
| // SetBodyStreamWriter registers the given sw for populating request body. |
| // |
| // This function may be used in the following cases: |
| // |
| // * if request body is too big (more than 10MB). |
| // * if request body is streamed from slow external sources. |
| // * if request body must be streamed to the server in chunks |
| // (aka `http client push` or `chunked transfer-encoding`). |
| // |
| // Note that GET and HEAD requests cannot have body. |
| // |
| /// See also SetBodyStream. |
| func (req *Request) SetBodyStreamWriter(sw StreamWriter) { |
| sr := NewStreamReader(sw) |
| req.SetBodyStream(sr, -1) |
| } |
| |
| // SetBodyStreamWriter registers the given sw for populating response body. |
| // |
| // This function may be used in the following cases: |
| // |
| // * if response body is too big (more than 10MB). |
| // * if response body is streamed from slow external sources. |
| // * if response body must be streamed to the client in chunks |
| // (aka `http server push` or `chunked transfer-encoding`). |
| // |
| // See also SetBodyStream. |
| func (resp *Response) SetBodyStreamWriter(sw StreamWriter) { |
| sr := NewStreamReader(sw) |
| resp.SetBodyStream(sr, -1) |
| } |
| |
| // BodyWriter returns writer for populating response body. |
| // |
| // If used inside RequestHandler, the returned writer must not be used |
| // after returning from RequestHandler. Use RequestCtx.Write |
| // or SetBodyStreamWriter in this case. |
| func (resp *Response) BodyWriter() io.Writer { |
| resp.w.r = resp |
| return &resp.w |
| } |
| |
| // BodyWriter returns writer for populating request body. |
| func (req *Request) BodyWriter() io.Writer { |
| req.w.r = req |
| return &req.w |
| } |
| |
| type responseBodyWriter struct { |
| r *Response |
| } |
| |
| func (w *responseBodyWriter) Write(p []byte) (int, error) { |
| w.r.AppendBody(p) |
| return len(p), nil |
| } |
| |
| type requestBodyWriter struct { |
| r *Request |
| } |
| |
| func (w *requestBodyWriter) Write(p []byte) (int, error) { |
| w.r.AppendBody(p) |
| return len(p), nil |
| } |
| |
| // Body returns response body. |
| // |
| // The returned body is valid until the response modification. |
| func (resp *Response) Body() []byte { |
| if resp.bodyStream != nil { |
| bodyBuf := resp.bodyBuffer() |
| bodyBuf.Reset() |
| _, err := copyZeroAlloc(bodyBuf, resp.bodyStream) |
| resp.closeBodyStream() |
| if err != nil { |
| bodyBuf.SetString(err.Error()) |
| } |
| } |
| return resp.bodyBytes() |
| } |
| |
| func (resp *Response) bodyBytes() []byte { |
| if resp.body == nil { |
| return nil |
| } |
| return resp.body.B |
| } |
| |
| func (req *Request) bodyBytes() []byte { |
| if req.body == nil { |
| return nil |
| } |
| return req.body.B |
| } |
| |
| func (resp *Response) bodyBuffer() *bytebufferpool.ByteBuffer { |
| if resp.body == nil { |
| resp.body = responseBodyPool.Get() |
| } |
| return resp.body |
| } |
| |
| func (req *Request) bodyBuffer() *bytebufferpool.ByteBuffer { |
| if req.body == nil { |
| req.body = requestBodyPool.Get() |
| } |
| return req.body |
| } |
| |
| var ( |
| responseBodyPool bytebufferpool.Pool |
| requestBodyPool bytebufferpool.Pool |
| ) |
| |
| // BodyGunzip returns un-gzipped body data. |
| // |
| // This method may be used if the request header contains |
| // 'Content-Encoding: gzip' for reading un-gzipped body. |
| // Use Body for reading gzipped request body. |
| func (req *Request) BodyGunzip() ([]byte, error) { |
| return gunzipData(req.Body()) |
| } |
| |
| // BodyGunzip returns un-gzipped body data. |
| // |
| // This method may be used if the response header contains |
| // 'Content-Encoding: gzip' for reading un-gzipped body. |
| // Use Body for reading gzipped response body. |
| func (resp *Response) BodyGunzip() ([]byte, error) { |
| return gunzipData(resp.Body()) |
| } |
| |
| func gunzipData(p []byte) ([]byte, error) { |
| var bb ByteBuffer |
| _, err := WriteGunzip(&bb, p) |
| if err != nil { |
| return nil, err |
| } |
| return bb.B, nil |
| } |
| |
| // BodyInflate returns inflated body data. |
| // |
| // This method may be used if the response header contains |
| // 'Content-Encoding: deflate' for reading inflated request body. |
| // Use Body for reading deflated request body. |
| func (req *Request) BodyInflate() ([]byte, error) { |
| return inflateData(req.Body()) |
| } |
| |
| // BodyInflate returns inflated body data. |
| // |
| // This method may be used if the response header contains |
| // 'Content-Encoding: deflate' for reading inflated response body. |
| // Use Body for reading deflated response body. |
| func (resp *Response) BodyInflate() ([]byte, error) { |
| return inflateData(resp.Body()) |
| } |
| |
| func inflateData(p []byte) ([]byte, error) { |
| var bb ByteBuffer |
| _, err := WriteInflate(&bb, p) |
| if err != nil { |
| return nil, err |
| } |
| return bb.B, nil |
| } |
| |
| // BodyWriteTo writes request body to w. |
| func (req *Request) BodyWriteTo(w io.Writer) error { |
| if req.bodyStream != nil { |
| _, err := copyZeroAlloc(w, req.bodyStream) |
| req.closeBodyStream() |
| return err |
| } |
| if req.onlyMultipartForm() { |
| return WriteMultipartForm(w, req.multipartForm, req.multipartFormBoundary) |
| } |
| _, err := w.Write(req.bodyBytes()) |
| return err |
| } |
| |
| // BodyWriteTo writes response body to w. |
| func (resp *Response) BodyWriteTo(w io.Writer) error { |
| if resp.bodyStream != nil { |
| _, err := copyZeroAlloc(w, resp.bodyStream) |
| resp.closeBodyStream() |
| return err |
| } |
| _, err := w.Write(resp.bodyBytes()) |
| return err |
| } |
| |
| // AppendBody appends p to response body. |
| // |
| // It is safe re-using p after the function returns. |
| func (resp *Response) AppendBody(p []byte) { |
| resp.AppendBodyString(b2s(p)) |
| } |
| |
| // AppendBodyString appends s to response body. |
| func (resp *Response) AppendBodyString(s string) { |
| resp.closeBodyStream() |
| resp.bodyBuffer().WriteString(s) |
| } |
| |
| // SetBody sets response body. |
| // |
| // It is safe re-using body argument after the function returns. |
| func (resp *Response) SetBody(body []byte) { |
| resp.SetBodyString(b2s(body)) |
| } |
| |
| // SetBodyString sets response body. |
| func (resp *Response) SetBodyString(body string) { |
| resp.closeBodyStream() |
| bodyBuf := resp.bodyBuffer() |
| bodyBuf.Reset() |
| bodyBuf.WriteString(body) |
| } |
| |
| // ResetBody resets response body. |
| func (resp *Response) ResetBody() { |
| resp.closeBodyStream() |
| if resp.body != nil { |
| if resp.keepBodyBuffer { |
| resp.body.Reset() |
| } else { |
| responseBodyPool.Put(resp.body) |
| resp.body = nil |
| } |
| } |
| } |
| |
| // ReleaseBody retires the response body if it is greater than "size" bytes. |
| // |
| // This permits GC to reclaim the large buffer. If used, must be before |
| // ReleaseResponse. |
| // |
| // Use this method only if you really understand how it works. |
| // The majority of workloads don't need this method. |
| func (resp *Response) ReleaseBody(size int) { |
| if cap(resp.body.B) > size { |
| resp.closeBodyStream() |
| resp.body = nil |
| } |
| } |
| |
| // ReleaseBody retires the request body if it is greater than "size" bytes. |
| // |
| // This permits GC to reclaim the large buffer. If used, must be before |
| // ReleaseRequest. |
| // |
| // Use this method only if you really understand how it works. |
| // The majority of workloads don't need this method. |
| func (req *Request) ReleaseBody(size int) { |
| if cap(req.body.B) > size { |
| req.closeBodyStream() |
| req.body = nil |
| } |
| } |
| |
| // SwapBody swaps response body with the given body and returns |
| // the previous response body. |
| // |
| // It is forbidden to use the body passed to SwapBody after |
| // the function returns. |
| func (resp *Response) SwapBody(body []byte) []byte { |
| bb := resp.bodyBuffer() |
| |
| if resp.bodyStream != nil { |
| bb.Reset() |
| _, err := copyZeroAlloc(bb, resp.bodyStream) |
| resp.closeBodyStream() |
| if err != nil { |
| bb.Reset() |
| bb.SetString(err.Error()) |
| } |
| } |
| |
| oldBody := bb.B |
| bb.B = body |
| return oldBody |
| } |
| |
| // SwapBody swaps request body with the given body and returns |
| // the previous request body. |
| // |
| // It is forbidden to use the body passed to SwapBody after |
| // the function returns. |
| func (req *Request) SwapBody(body []byte) []byte { |
| bb := req.bodyBuffer() |
| |
| if req.bodyStream != nil { |
| bb.Reset() |
| _, err := copyZeroAlloc(bb, req.bodyStream) |
| req.closeBodyStream() |
| if err != nil { |
| bb.Reset() |
| bb.SetString(err.Error()) |
| } |
| } |
| |
| oldBody := bb.B |
| bb.B = body |
| return oldBody |
| } |
| |
| // Body returns request body. |
| // |
| // The returned body is valid until the request modification. |
| func (req *Request) Body() []byte { |
| if req.bodyStream != nil { |
| bodyBuf := req.bodyBuffer() |
| bodyBuf.Reset() |
| _, err := copyZeroAlloc(bodyBuf, req.bodyStream) |
| req.closeBodyStream() |
| if err != nil { |
| bodyBuf.SetString(err.Error()) |
| } |
| } else if req.onlyMultipartForm() { |
| body, err := marshalMultipartForm(req.multipartForm, req.multipartFormBoundary) |
| if err != nil { |
| return []byte(err.Error()) |
| } |
| return body |
| } |
| return req.bodyBytes() |
| } |
| |
| // AppendBody appends p to request body. |
| // |
| // It is safe re-using p after the function returns. |
| func (req *Request) AppendBody(p []byte) { |
| req.AppendBodyString(b2s(p)) |
| } |
| |
| // AppendBodyString appends s to request body. |
| func (req *Request) AppendBodyString(s string) { |
| req.RemoveMultipartFormFiles() |
| req.closeBodyStream() |
| req.bodyBuffer().WriteString(s) |
| } |
| |
| // SetBody sets request body. |
| // |
| // It is safe re-using body argument after the function returns. |
| func (req *Request) SetBody(body []byte) { |
| req.SetBodyString(b2s(body)) |
| } |
| |
| // SetBodyString sets request body. |
| func (req *Request) SetBodyString(body string) { |
| req.RemoveMultipartFormFiles() |
| req.closeBodyStream() |
| req.bodyBuffer().SetString(body) |
| } |
| |
| // ResetBody resets request body. |
| func (req *Request) ResetBody() { |
| req.RemoveMultipartFormFiles() |
| req.closeBodyStream() |
| if req.body != nil { |
| if req.keepBodyBuffer { |
| req.body.Reset() |
| } else { |
| requestBodyPool.Put(req.body) |
| req.body = nil |
| } |
| } |
| } |
| |
| // CopyTo copies req contents to dst except of body stream. |
| func (req *Request) CopyTo(dst *Request) { |
| req.copyToSkipBody(dst) |
| if req.body != nil { |
| dst.bodyBuffer().Set(req.body.B) |
| } else if dst.body != nil { |
| dst.body.Reset() |
| } |
| } |
| |
| func (req *Request) copyToSkipBody(dst *Request) { |
| dst.Reset() |
| req.Header.CopyTo(&dst.Header) |
| |
| req.uri.CopyTo(&dst.uri) |
| dst.parsedURI = req.parsedURI |
| |
| req.postArgs.CopyTo(&dst.postArgs) |
| dst.parsedPostArgs = req.parsedPostArgs |
| dst.isTLS = req.isTLS |
| |
| // do not copy multipartForm - it will be automatically |
| // re-created on the first call to MultipartForm. |
| } |
| |
| // CopyTo copies resp contents to dst except of body stream. |
| func (resp *Response) CopyTo(dst *Response) { |
| resp.copyToSkipBody(dst) |
| if resp.body != nil { |
| dst.bodyBuffer().Set(resp.body.B) |
| } else if dst.body != nil { |
| dst.body.Reset() |
| } |
| } |
| |
| func (resp *Response) copyToSkipBody(dst *Response) { |
| dst.Reset() |
| resp.Header.CopyTo(&dst.Header) |
| dst.SkipBody = resp.SkipBody |
| } |
| |
| func swapRequestBody(a, b *Request) { |
| a.body, b.body = b.body, a.body |
| a.bodyStream, b.bodyStream = b.bodyStream, a.bodyStream |
| } |
| |
| func swapResponseBody(a, b *Response) { |
| a.body, b.body = b.body, a.body |
| a.bodyStream, b.bodyStream = b.bodyStream, a.bodyStream |
| } |
| |
| // URI returns request URI |
| func (req *Request) URI() *URI { |
| req.parseURI() |
| return &req.uri |
| } |
| |
| func (req *Request) parseURI() { |
| if req.parsedURI { |
| return |
| } |
| req.parsedURI = true |
| |
| req.uri.parseQuick(req.Header.RequestURI(), &req.Header, req.isTLS) |
| } |
| |
| // PostArgs returns POST arguments. |
| func (req *Request) PostArgs() *Args { |
| req.parsePostArgs() |
| return &req.postArgs |
| } |
| |
| func (req *Request) parsePostArgs() { |
| if req.parsedPostArgs { |
| return |
| } |
| req.parsedPostArgs = true |
| |
| if !bytes.HasPrefix(req.Header.ContentType(), strPostArgsContentType) { |
| return |
| } |
| req.postArgs.ParseBytes(req.bodyBytes()) |
| } |
| |
| // ErrNoMultipartForm means that the request's Content-Type |
| // isn't 'multipart/form-data'. |
| var ErrNoMultipartForm = errors.New("request has no multipart/form-data Content-Type") |
| |
| // MultipartForm returns requests's multipart form. |
| // |
| // Returns ErrNoMultipartForm if request's Content-Type |
| // isn't 'multipart/form-data'. |
| // |
| // RemoveMultipartFormFiles must be called after returned multipart form |
| // is processed. |
| func (req *Request) MultipartForm() (*multipart.Form, error) { |
| if req.multipartForm != nil { |
| return req.multipartForm, nil |
| } |
| |
| req.multipartFormBoundary = string(req.Header.MultipartFormBoundary()) |
| if len(req.multipartFormBoundary) == 0 { |
| return nil, ErrNoMultipartForm |
| } |
| |
| ce := req.Header.peek(strContentEncoding) |
| body := req.bodyBytes() |
| if bytes.Equal(ce, strGzip) { |
| // Do not care about memory usage here. |
| var err error |
| if body, err = AppendGunzipBytes(nil, body); err != nil { |
| return nil, fmt.Errorf("cannot gunzip request body: %s", err) |
| } |
| } else if len(ce) > 0 { |
| return nil, fmt.Errorf("unsupported Content-Encoding: %q", ce) |
| } |
| |
| f, err := readMultipartForm(bytes.NewReader(body), req.multipartFormBoundary, len(body), len(body)) |
| if err != nil { |
| return nil, err |
| } |
| req.multipartForm = f |
| return f, nil |
| } |
| |
| func marshalMultipartForm(f *multipart.Form, boundary string) ([]byte, error) { |
| var buf ByteBuffer |
| if err := WriteMultipartForm(&buf, f, boundary); err != nil { |
| return nil, err |
| } |
| return buf.B, nil |
| } |
| |
| // WriteMultipartForm writes the given multipart form f with the given |
| // boundary to w. |
| func WriteMultipartForm(w io.Writer, f *multipart.Form, boundary string) error { |
| // Do not care about memory allocations here, since multipart |
| // form processing is slooow. |
| if len(boundary) == 0 { |
| panic("BUG: form boundary cannot be empty") |
| } |
| |
| mw := multipart.NewWriter(w) |
| if err := mw.SetBoundary(boundary); err != nil { |
| return fmt.Errorf("cannot use form boundary %q: %s", boundary, err) |
| } |
| |
| // marshal values |
| for k, vv := range f.Value { |
| for _, v := range vv { |
| if err := mw.WriteField(k, v); err != nil { |
| return fmt.Errorf("cannot write form field %q value %q: %s", k, v, err) |
| } |
| } |
| } |
| |
| // marshal files |
| for k, fvv := range f.File { |
| for _, fv := range fvv { |
| vw, err := mw.CreateFormFile(k, fv.Filename) |
| if err != nil { |
| return fmt.Errorf("cannot create form file %q (%q): %s", k, fv.Filename, err) |
| } |
| fh, err := fv.Open() |
| if err != nil { |
| return fmt.Errorf("cannot open form file %q (%q): %s", k, fv.Filename, err) |
| } |
| if _, err = copyZeroAlloc(vw, fh); err != nil { |
| return fmt.Errorf("error when copying form file %q (%q): %s", k, fv.Filename, err) |
| } |
| if err = fh.Close(); err != nil { |
| return fmt.Errorf("cannot close form file %q (%q): %s", k, fv.Filename, err) |
| } |
| } |
| } |
| |
| if err := mw.Close(); err != nil { |
| return fmt.Errorf("error when closing multipart form writer: %s", err) |
| } |
| |
| return nil |
| } |
| |
| func readMultipartForm(r io.Reader, boundary string, size, maxInMemoryFileSize int) (*multipart.Form, error) { |
| // Do not care about memory allocations here, since they are tiny |
| // compared to multipart data (aka multi-MB files) usually sent |
| // in multipart/form-data requests. |
| |
| if size <= 0 { |
| panic(fmt.Sprintf("BUG: form size must be greater than 0. Given %d", size)) |
| } |
| lr := io.LimitReader(r, int64(size)) |
| mr := multipart.NewReader(lr, boundary) |
| f, err := mr.ReadForm(int64(maxInMemoryFileSize)) |
| if err != nil { |
| return nil, fmt.Errorf("cannot read multipart/form-data body: %s", err) |
| } |
| return f, nil |
| } |
| |
| // Reset clears request contents. |
| func (req *Request) Reset() { |
| req.Header.Reset() |
| req.resetSkipHeader() |
| } |
| |
| func (req *Request) resetSkipHeader() { |
| req.ResetBody() |
| req.uri.Reset() |
| req.parsedURI = false |
| req.postArgs.Reset() |
| req.parsedPostArgs = false |
| req.isTLS = false |
| } |
| |
| // RemoveMultipartFormFiles removes multipart/form-data temporary files |
| // associated with the request. |
| func (req *Request) RemoveMultipartFormFiles() { |
| if req.multipartForm != nil { |
| // Do not check for error, since these files may be deleted or moved |
| // to new places by user code. |
| req.multipartForm.RemoveAll() |
| req.multipartForm = nil |
| } |
| req.multipartFormBoundary = "" |
| } |
| |
| // Reset clears response contents. |
| func (resp *Response) Reset() { |
| resp.Header.Reset() |
| resp.resetSkipHeader() |
| resp.SkipBody = false |
| } |
| |
| func (resp *Response) resetSkipHeader() { |
| resp.ResetBody() |
| } |
| |
| // Read reads request (including body) from the given r. |
| // |
| // RemoveMultipartFormFiles or Reset must be called after |
| // reading multipart/form-data request in order to delete temporarily |
| // uploaded files. |
| // |
| // If MayContinue returns true, the caller must: |
| // |
| // - Either send StatusExpectationFailed response if request headers don't |
| // satisfy the caller. |
| // - Or send StatusContinue response before reading request body |
| // with ContinueReadBody. |
| // - Or close the connection. |
| // |
| // io.EOF is returned if r is closed before reading the first header byte. |
| func (req *Request) Read(r *bufio.Reader) error { |
| return req.ReadLimitBody(r, 0) |
| } |
| |
| const defaultMaxInMemoryFileSize = 16 * 1024 * 1024 |
| |
| var errGetOnly = errors.New("non-GET request received") |
| |
| // ReadLimitBody reads request from the given r, limiting the body size. |
| // |
| // If maxBodySize > 0 and the body size exceeds maxBodySize, |
| // then ErrBodyTooLarge is returned. |
| // |
| // RemoveMultipartFormFiles or Reset must be called after |
| // reading multipart/form-data request in order to delete temporarily |
| // uploaded files. |
| // |
| // If MayContinue returns true, the caller must: |
| // |
| // - Either send StatusExpectationFailed response if request headers don't |
| // satisfy the caller. |
| // - Or send StatusContinue response before reading request body |
| // with ContinueReadBody. |
| // - Or close the connection. |
| // |
| // io.EOF is returned if r is closed before reading the first header byte. |
| func (req *Request) ReadLimitBody(r *bufio.Reader, maxBodySize int) error { |
| req.resetSkipHeader() |
| return req.readLimitBody(r, maxBodySize, false) |
| } |
| |
| func (req *Request) readLimitBody(r *bufio.Reader, maxBodySize int, getOnly bool) error { |
| // Do not reset the request here - the caller must reset it before |
| // calling this method. |
| |
| err := req.Header.Read(r) |
| if err != nil { |
| return err |
| } |
| if getOnly && !req.Header.IsGet() { |
| return errGetOnly |
| } |
| |
| if req.Header.noBody() { |
| return nil |
| } |
| |
| if req.MayContinue() { |
| // 'Expect: 100-continue' header found. Let the caller deciding |
| // whether to read request body or |
| // to return StatusExpectationFailed. |
| return nil |
| } |
| |
| return req.ContinueReadBody(r, maxBodySize) |
| } |
| |
| // MayContinue returns true if the request contains |
| // 'Expect: 100-continue' header. |
| // |
| // The caller must do one of the following actions if MayContinue returns true: |
| // |
| // - Either send StatusExpectationFailed response if request headers don't |
| // satisfy the caller. |
| // - Or send StatusContinue response before reading request body |
| // with ContinueReadBody. |
| // - Or close the connection. |
| func (req *Request) MayContinue() bool { |
| return bytes.Equal(req.Header.peek(strExpect), str100Continue) |
| } |
| |
| // ContinueReadBody reads request body if request header contains |
| // 'Expect: 100-continue'. |
| // |
| // The caller must send StatusContinue response before calling this method. |
| // |
| // If maxBodySize > 0 and the body size exceeds maxBodySize, |
| // then ErrBodyTooLarge is returned. |
| func (req *Request) ContinueReadBody(r *bufio.Reader, maxBodySize int) error { |
| var err error |
| contentLength := req.Header.ContentLength() |
| if contentLength > 0 { |
| if maxBodySize > 0 && contentLength > maxBodySize { |
| return ErrBodyTooLarge |
| } |
| |
| // Pre-read multipart form data of known length. |
| // This way we limit memory usage for large file uploads, since their contents |
| // is streamed into temporary files if file size exceeds defaultMaxInMemoryFileSize. |
| req.multipartFormBoundary = string(req.Header.MultipartFormBoundary()) |
| if len(req.multipartFormBoundary) > 0 && len(req.Header.peek(strContentEncoding)) == 0 { |
| req.multipartForm, err = readMultipartForm(r, req.multipartFormBoundary, contentLength, defaultMaxInMemoryFileSize) |
| if err != nil { |
| req.Reset() |
| } |
| return err |
| } |
| } |
| |
| if contentLength == -2 { |
| // identity body has no sense for http requests, since |
| // the end of body is determined by connection close. |
| // So just ignore request body for requests without |
| // 'Content-Length' and 'Transfer-Encoding' headers. |
| req.Header.SetContentLength(0) |
| return nil |
| } |
| |
| bodyBuf := req.bodyBuffer() |
| bodyBuf.Reset() |
| bodyBuf.B, err = readBody(r, contentLength, maxBodySize, bodyBuf.B) |
| if err != nil { |
| req.Reset() |
| return err |
| } |
| req.Header.SetContentLength(len(bodyBuf.B)) |
| return nil |
| } |
| |
| // Read reads response (including body) from the given r. |
| // |
| // io.EOF is returned if r is closed before reading the first header byte. |
| func (resp *Response) Read(r *bufio.Reader) error { |
| return resp.ReadLimitBody(r, 0) |
| } |
| |
| // ReadLimitBody reads response from the given r, limiting the body size. |
| // |
| // If maxBodySize > 0 and the body size exceeds maxBodySize, |
| // then ErrBodyTooLarge is returned. |
| // |
| // io.EOF is returned if r is closed before reading the first header byte. |
| func (resp *Response) ReadLimitBody(r *bufio.Reader, maxBodySize int) error { |
| resp.resetSkipHeader() |
| err := resp.Header.Read(r) |
| if err != nil { |
| return err |
| } |
| if resp.Header.StatusCode() == StatusContinue { |
| // Read the next response according to http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html . |
| if err = resp.Header.Read(r); err != nil { |
| return err |
| } |
| } |
| |
| if !resp.mustSkipBody() { |
| bodyBuf := resp.bodyBuffer() |
| bodyBuf.Reset() |
| bodyBuf.B, err = readBody(r, resp.Header.ContentLength(), maxBodySize, bodyBuf.B) |
| if err != nil { |
| resp.Reset() |
| return err |
| } |
| resp.Header.SetContentLength(len(bodyBuf.B)) |
| } |
| return nil |
| } |
| |
| func (resp *Response) mustSkipBody() bool { |
| return resp.SkipBody || resp.Header.mustSkipContentLength() |
| } |
| |
| var errRequestHostRequired = errors.New("missing required Host header in request") |
| |
| // WriteTo writes request to w. It implements io.WriterTo. |
| func (req *Request) WriteTo(w io.Writer) (int64, error) { |
| return writeBufio(req, w) |
| } |
| |
| // WriteTo writes response to w. It implements io.WriterTo. |
| func (resp *Response) WriteTo(w io.Writer) (int64, error) { |
| return writeBufio(resp, w) |
| } |
| |
| func writeBufio(hw httpWriter, w io.Writer) (int64, error) { |
| sw := acquireStatsWriter(w) |
| bw := acquireBufioWriter(sw) |
| err1 := hw.Write(bw) |
| err2 := bw.Flush() |
| releaseBufioWriter(bw) |
| n := sw.bytesWritten |
| releaseStatsWriter(sw) |
| |
| err := err1 |
| if err == nil { |
| err = err2 |
| } |
| return n, err |
| } |
| |
| type statsWriter struct { |
| w io.Writer |
| bytesWritten int64 |
| } |
| |
| func (w *statsWriter) Write(p []byte) (int, error) { |
| n, err := w.w.Write(p) |
| w.bytesWritten += int64(n) |
| return n, err |
| } |
| |
| func acquireStatsWriter(w io.Writer) *statsWriter { |
| v := statsWriterPool.Get() |
| if v == nil { |
| return &statsWriter{ |
| w: w, |
| } |
| } |
| sw := v.(*statsWriter) |
| sw.w = w |
| return sw |
| } |
| |
| func releaseStatsWriter(sw *statsWriter) { |
| sw.w = nil |
| sw.bytesWritten = 0 |
| statsWriterPool.Put(sw) |
| } |
| |
| var statsWriterPool sync.Pool |
| |
| func acquireBufioWriter(w io.Writer) *bufio.Writer { |
| v := bufioWriterPool.Get() |
| if v == nil { |
| return bufio.NewWriter(w) |
| } |
| bw := v.(*bufio.Writer) |
| bw.Reset(w) |
| return bw |
| } |
| |
| func releaseBufioWriter(bw *bufio.Writer) { |
| bufioWriterPool.Put(bw) |
| } |
| |
| var bufioWriterPool sync.Pool |
| |
| func (req *Request) onlyMultipartForm() bool { |
| return req.multipartForm != nil && (req.body == nil || len(req.body.B) == 0) |
| } |
| |
| // Write writes request to w. |
| // |
| // Write doesn't flush request to w for performance reasons. |
| // |
| // See also WriteTo. |
| func (req *Request) Write(w *bufio.Writer) error { |
| if len(req.Header.Host()) == 0 || req.parsedURI { |
| uri := req.URI() |
| host := uri.Host() |
| if len(host) == 0 { |
| return errRequestHostRequired |
| } |
| req.Header.SetHostBytes(host) |
| req.Header.SetRequestURIBytes(uri.RequestURI()) |
| } |
| |
| if req.bodyStream != nil { |
| return req.writeBodyStream(w) |
| } |
| |
| body := req.bodyBytes() |
| var err error |
| if req.onlyMultipartForm() { |
| body, err = marshalMultipartForm(req.multipartForm, req.multipartFormBoundary) |
| if err != nil { |
| return fmt.Errorf("error when marshaling multipart form: %s", err) |
| } |
| req.Header.SetMultipartFormBoundary(req.multipartFormBoundary) |
| } |
| |
| hasBody := !req.Header.noBody() |
| if hasBody { |
| req.Header.SetContentLength(len(body)) |
| } |
| if err = req.Header.Write(w); err != nil { |
| return err |
| } |
| if hasBody { |
| _, err = w.Write(body) |
| } else if len(body) > 0 { |
| return fmt.Errorf("non-zero body for non-POST request. body=%q", body) |
| } |
| return err |
| } |
| |
| // WriteGzip writes response with gzipped body to w. |
| // |
| // The method gzips response body and sets 'Content-Encoding: gzip' |
| // header before writing response to w. |
| // |
| // WriteGzip doesn't flush response to w for performance reasons. |
| func (resp *Response) WriteGzip(w *bufio.Writer) error { |
| return resp.WriteGzipLevel(w, CompressDefaultCompression) |
| } |
| |
| // WriteGzipLevel writes response with gzipped body to w. |
| // |
| // Level is the desired compression level: |
| // |
| // * CompressNoCompression |
| // * CompressBestSpeed |
| // * CompressBestCompression |
| // * CompressDefaultCompression |
| // * CompressHuffmanOnly |
| // |
| // The method gzips response body and sets 'Content-Encoding: gzip' |
| // header before writing response to w. |
| // |
| // WriteGzipLevel doesn't flush response to w for performance reasons. |
| func (resp *Response) WriteGzipLevel(w *bufio.Writer, level int) error { |
| if err := resp.gzipBody(level); err != nil { |
| return err |
| } |
| return resp.Write(w) |
| } |
| |
| // WriteDeflate writes response with deflated body to w. |
| // |
| // The method deflates response body and sets 'Content-Encoding: deflate' |
| // header before writing response to w. |
| // |
| // WriteDeflate doesn't flush response to w for performance reasons. |
| func (resp *Response) WriteDeflate(w *bufio.Writer) error { |
| return resp.WriteDeflateLevel(w, CompressDefaultCompression) |
| } |
| |
| // WriteDeflateLevel writes response with deflated body to w. |
| // |
| // Level is the desired compression level: |
| // |
| // * CompressNoCompression |
| // * CompressBestSpeed |
| // * CompressBestCompression |
| // * CompressDefaultCompression |
| // * CompressHuffmanOnly |
| // |
| // The method deflates response body and sets 'Content-Encoding: deflate' |
| // header before writing response to w. |
| // |
| // WriteDeflateLevel doesn't flush response to w for performance reasons. |
| func (resp *Response) WriteDeflateLevel(w *bufio.Writer, level int) error { |
| if err := resp.deflateBody(level); err != nil { |
| return err |
| } |
| return resp.Write(w) |
| } |
| |
| func (resp *Response) gzipBody(level int) error { |
| if len(resp.Header.peek(strContentEncoding)) > 0 { |
| // It looks like the body is already compressed. |
| // Do not compress it again. |
| return nil |
| } |
| |
| if !resp.Header.isCompressibleContentType() { |
| // The content-type cannot be compressed. |
| return nil |
| } |
| |
| if resp.bodyStream != nil { |
| // Reset Content-Length to -1, since it is impossible |
| // to determine body size beforehand of streamed compression. |
| // For https://github.com/valyala/fasthttp/issues/176 . |
| resp.Header.SetContentLength(-1) |
| |
| // Do not care about memory allocations here, since gzip is slow |
| // and allocates a lot of memory by itself. |
| bs := resp.bodyStream |
| resp.bodyStream = NewStreamReader(func(sw *bufio.Writer) { |
| zw := acquireStacklessGzipWriter(sw, level) |
| fw := &flushWriter{ |
| wf: zw, |
| bw: sw, |
| } |
| copyZeroAlloc(fw, bs) |
| releaseStacklessGzipWriter(zw, level) |
| if bsc, ok := bs.(io.Closer); ok { |
| bsc.Close() |
| } |
| }) |
| } else { |
| bodyBytes := resp.bodyBytes() |
| if len(bodyBytes) < minCompressLen { |
| // There is no sense in spending CPU time on small body compression, |
| // since there is a very high probability that the compressed |
| // body size will be bigger than the original body size. |
| return nil |
| } |
| w := responseBodyPool.Get() |
| w.B = AppendGzipBytesLevel(w.B, bodyBytes, level) |
| |
| // Hack: swap resp.body with w. |
| if resp.body != nil { |
| responseBodyPool.Put(resp.body) |
| } |
| resp.body = w |
| } |
| resp.Header.SetCanonical(strContentEncoding, strGzip) |
| return nil |
| } |
| |
| func (resp *Response) deflateBody(level int) error { |
| if len(resp.Header.peek(strContentEncoding)) > 0 { |
| // It looks like the body is already compressed. |
| // Do not compress it again. |
| return nil |
| } |
| |
| if !resp.Header.isCompressibleContentType() { |
| // The content-type cannot be compressed. |
| return nil |
| } |
| |
| if resp.bodyStream != nil { |
| // Reset Content-Length to -1, since it is impossible |
| // to determine body size beforehand of streamed compression. |
| // For https://github.com/valyala/fasthttp/issues/176 . |
| resp.Header.SetContentLength(-1) |
| |
| // Do not care about memory allocations here, since flate is slow |
| // and allocates a lot of memory by itself. |
| bs := resp.bodyStream |
| resp.bodyStream = NewStreamReader(func(sw *bufio.Writer) { |
| zw := acquireStacklessDeflateWriter(sw, level) |
| fw := &flushWriter{ |
| wf: zw, |
| bw: sw, |
| } |
| copyZeroAlloc(fw, bs) |
| releaseStacklessDeflateWriter(zw, level) |
| if bsc, ok := bs.(io.Closer); ok { |
| bsc.Close() |
| } |
| }) |
| } else { |
| bodyBytes := resp.bodyBytes() |
| if len(bodyBytes) < minCompressLen { |
| // There is no sense in spending CPU time on small body compression, |
| // since there is a very high probability that the compressed |
| // body size will be bigger than the original body size. |
| return nil |
| } |
| w := responseBodyPool.Get() |
| w.B = AppendDeflateBytesLevel(w.B, bodyBytes, level) |
| |
| // Hack: swap resp.body with w. |
| if resp.body != nil { |
| responseBodyPool.Put(resp.body) |
| } |
| resp.body = w |
| } |
| resp.Header.SetCanonical(strContentEncoding, strDeflate) |
| return nil |
| } |
| |
| // Bodies with sizes smaller than minCompressLen aren't compressed at all |
| const minCompressLen = 200 |
| |
| type writeFlusher interface { |
| io.Writer |
| Flush() error |
| } |
| |
| type flushWriter struct { |
| wf writeFlusher |
| bw *bufio.Writer |
| } |
| |
| func (w *flushWriter) Write(p []byte) (int, error) { |
| n, err := w.wf.Write(p) |
| if err != nil { |
| return 0, err |
| } |
| if err = w.wf.Flush(); err != nil { |
| return 0, err |
| } |
| if err = w.bw.Flush(); err != nil { |
| return 0, err |
| } |
| return n, nil |
| } |
| |
| // Write writes response to w. |
| // |
| // Write doesn't flush response to w for performance reasons. |
| // |
| // See also WriteTo. |
| func (resp *Response) Write(w *bufio.Writer) error { |
| sendBody := !resp.mustSkipBody() |
| |
| if resp.bodyStream != nil { |
| return resp.writeBodyStream(w, sendBody) |
| } |
| |
| body := resp.bodyBytes() |
| bodyLen := len(body) |
| if sendBody || bodyLen > 0 { |
| resp.Header.SetContentLength(bodyLen) |
| } |
| if err := resp.Header.Write(w); err != nil { |
| return err |
| } |
| if sendBody { |
| if _, err := w.Write(body); err != nil { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func (req *Request) writeBodyStream(w *bufio.Writer) error { |
| var err error |
| |
| contentLength := req.Header.ContentLength() |
| if contentLength < 0 { |
| lrSize := limitedReaderSize(req.bodyStream) |
| if lrSize >= 0 { |
| contentLength = int(lrSize) |
| if int64(contentLength) != lrSize { |
| contentLength = -1 |
| } |
| if contentLength >= 0 { |
| req.Header.SetContentLength(contentLength) |
| } |
| } |
| } |
| if contentLength >= 0 { |
| if err = req.Header.Write(w); err == nil { |
| err = writeBodyFixedSize(w, req.bodyStream, int64(contentLength)) |
| } |
| } else { |
| req.Header.SetContentLength(-1) |
| if err = req.Header.Write(w); err == nil { |
| err = writeBodyChunked(w, req.bodyStream) |
| } |
| } |
| err1 := req.closeBodyStream() |
| if err == nil { |
| err = err1 |
| } |
| return err |
| } |
| |
| func (resp *Response) writeBodyStream(w *bufio.Writer, sendBody bool) error { |
| var err error |
| |
| contentLength := resp.Header.ContentLength() |
| if contentLength < 0 { |
| lrSize := limitedReaderSize(resp.bodyStream) |
| if lrSize >= 0 { |
| contentLength = int(lrSize) |
| if int64(contentLength) != lrSize { |
| contentLength = -1 |
| } |
| if contentLength >= 0 { |
| resp.Header.SetContentLength(contentLength) |
| } |
| } |
| } |
| if contentLength >= 0 { |
| if err = resp.Header.Write(w); err == nil && sendBody { |
| err = writeBodyFixedSize(w, resp.bodyStream, int64(contentLength)) |
| } |
| } else { |
| resp.Header.SetContentLength(-1) |
| if err = resp.Header.Write(w); err == nil && sendBody { |
| err = writeBodyChunked(w, resp.bodyStream) |
| } |
| } |
| err1 := resp.closeBodyStream() |
| if err == nil { |
| err = err1 |
| } |
| return err |
| } |
| |
| func (req *Request) closeBodyStream() error { |
| if req.bodyStream == nil { |
| return nil |
| } |
| var err error |
| if bsc, ok := req.bodyStream.(io.Closer); ok { |
| err = bsc.Close() |
| } |
| req.bodyStream = nil |
| return err |
| } |
| |
| func (resp *Response) closeBodyStream() error { |
| if resp.bodyStream == nil { |
| return nil |
| } |
| var err error |
| if bsc, ok := resp.bodyStream.(io.Closer); ok { |
| err = bsc.Close() |
| } |
| resp.bodyStream = nil |
| return err |
| } |
| |
| // String returns request representation. |
| // |
| // Returns error message instead of request representation on error. |
| // |
| // Use Write instead of String for performance-critical code. |
| func (req *Request) String() string { |
| return getHTTPString(req) |
| } |
| |
| // String returns response representation. |
| // |
| // Returns error message instead of response representation on error. |
| // |
| // Use Write instead of String for performance-critical code. |
| func (resp *Response) String() string { |
| return getHTTPString(resp) |
| } |
| |
| func getHTTPString(hw httpWriter) string { |
| w := AcquireByteBuffer() |
| bw := bufio.NewWriter(w) |
| if err := hw.Write(bw); err != nil { |
| return err.Error() |
| } |
| if err := bw.Flush(); err != nil { |
| return err.Error() |
| } |
| s := string(w.B) |
| ReleaseByteBuffer(w) |
| return s |
| } |
| |
| type httpWriter interface { |
| Write(w *bufio.Writer) error |
| } |
| |
| func writeBodyChunked(w *bufio.Writer, r io.Reader) error { |
| vbuf := copyBufPool.Get() |
| buf := vbuf.([]byte) |
| |
| var err error |
| var n int |
| for { |
| n, err = r.Read(buf) |
| if n == 0 { |
| if err == nil { |
| panic("BUG: io.Reader returned 0, nil") |
| } |
| if err == io.EOF { |
| if err = writeChunk(w, buf[:0]); err != nil { |
| break |
| } |
| err = nil |
| } |
| break |
| } |
| if err = writeChunk(w, buf[:n]); err != nil { |
| break |
| } |
| } |
| |
| copyBufPool.Put(vbuf) |
| return err |
| } |
| |
| func limitedReaderSize(r io.Reader) int64 { |
| lr, ok := r.(*io.LimitedReader) |
| if !ok { |
| return -1 |
| } |
| return lr.N |
| } |
| |
| func writeBodyFixedSize(w *bufio.Writer, r io.Reader, size int64) error { |
| if size > maxSmallFileSize { |
| // w buffer must be empty for triggering |
| // sendfile path in bufio.Writer.ReadFrom. |
| if err := w.Flush(); err != nil { |
| return err |
| } |
| } |
| |
| // Unwrap a single limited reader for triggering sendfile path |
| // in net.TCPConn.ReadFrom. |
| lr, ok := r.(*io.LimitedReader) |
| if ok { |
| r = lr.R |
| } |
| |
| n, err := copyZeroAlloc(w, r) |
| |
| if ok { |
| lr.N -= n |
| } |
| |
| if n != size && err == nil { |
| err = fmt.Errorf("copied %d bytes from body stream instead of %d bytes", n, size) |
| } |
| return err |
| } |
| |
| func copyZeroAlloc(w io.Writer, r io.Reader) (int64, error) { |
| vbuf := copyBufPool.Get() |
| buf := vbuf.([]byte) |
| n, err := io.CopyBuffer(w, r, buf) |
| copyBufPool.Put(vbuf) |
| return n, err |
| } |
| |
| var copyBufPool = sync.Pool{ |
| New: func() interface{} { |
| return make([]byte, 4096) |
| }, |
| } |
| |
| func writeChunk(w *bufio.Writer, b []byte) error { |
| n := len(b) |
| writeHexInt(w, n) |
| w.Write(strCRLF) |
| w.Write(b) |
| _, err := w.Write(strCRLF) |
| err1 := w.Flush() |
| if err == nil { |
| err = err1 |
| } |
| return err |
| } |
| |
| // ErrBodyTooLarge is returned if either request or response body exceeds |
| // the given limit. |
| var ErrBodyTooLarge = errors.New("body size exceeds the given limit") |
| |
| func readBody(r *bufio.Reader, contentLength int, maxBodySize int, dst []byte) ([]byte, error) { |
| dst = dst[:0] |
| if contentLength >= 0 { |
| if maxBodySize > 0 && contentLength > maxBodySize { |
| return dst, ErrBodyTooLarge |
| } |
| return appendBodyFixedSize(r, dst, contentLength) |
| } |
| if contentLength == -1 { |
| return readBodyChunked(r, maxBodySize, dst) |
| } |
| return readBodyIdentity(r, maxBodySize, dst) |
| } |
| |
| func readBodyIdentity(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) { |
| dst = dst[:cap(dst)] |
| if len(dst) == 0 { |
| dst = make([]byte, 1024) |
| } |
| offset := 0 |
| for { |
| nn, err := r.Read(dst[offset:]) |
| if nn <= 0 { |
| if err != nil { |
| if err == io.EOF { |
| return dst[:offset], nil |
| } |
| return dst[:offset], err |
| } |
| panic(fmt.Sprintf("BUG: bufio.Read() returned (%d, nil)", nn)) |
| } |
| offset += nn |
| if maxBodySize > 0 && offset > maxBodySize { |
| return dst[:offset], ErrBodyTooLarge |
| } |
| if len(dst) == offset { |
| n := round2(2 * offset) |
| if maxBodySize > 0 && n > maxBodySize { |
| n = maxBodySize + 1 |
| } |
| b := make([]byte, n) |
| copy(b, dst) |
| dst = b |
| } |
| } |
| } |
| |
| func appendBodyFixedSize(r *bufio.Reader, dst []byte, n int) ([]byte, error) { |
| if n == 0 { |
| return dst, nil |
| } |
| |
| offset := len(dst) |
| dstLen := offset + n |
| if cap(dst) < dstLen { |
| b := make([]byte, round2(dstLen)) |
| copy(b, dst) |
| dst = b |
| } |
| dst = dst[:dstLen] |
| |
| for { |
| nn, err := r.Read(dst[offset:]) |
| if nn <= 0 { |
| if err != nil { |
| if err == io.EOF { |
| err = io.ErrUnexpectedEOF |
| } |
| return dst[:offset], err |
| } |
| panic(fmt.Sprintf("BUG: bufio.Read() returned (%d, nil)", nn)) |
| } |
| offset += nn |
| if offset == dstLen { |
| return dst, nil |
| } |
| } |
| } |
| |
| func readBodyChunked(r *bufio.Reader, maxBodySize int, dst []byte) ([]byte, error) { |
| if len(dst) > 0 { |
| panic("BUG: expected zero-length buffer") |
| } |
| |
| strCRLFLen := len(strCRLF) |
| for { |
| chunkSize, err := parseChunkSize(r) |
| if err != nil { |
| return dst, err |
| } |
| if maxBodySize > 0 && len(dst)+chunkSize > maxBodySize { |
| return dst, ErrBodyTooLarge |
| } |
| dst, err = appendBodyFixedSize(r, dst, chunkSize+strCRLFLen) |
| if err != nil { |
| return dst, err |
| } |
| if !bytes.Equal(dst[len(dst)-strCRLFLen:], strCRLF) { |
| return dst, fmt.Errorf("cannot find crlf at the end of chunk") |
| } |
| dst = dst[:len(dst)-strCRLFLen] |
| if chunkSize == 0 { |
| return dst, nil |
| } |
| } |
| } |
| |
| func parseChunkSize(r *bufio.Reader) (int, error) { |
| n, err := readHexInt(r) |
| if err != nil { |
| return -1, err |
| } |
| c, err := r.ReadByte() |
| if err != nil { |
| return -1, fmt.Errorf("cannot read '\r' char at the end of chunk size: %s", err) |
| } |
| if c != '\r' { |
| return -1, fmt.Errorf("unexpected char %q at the end of chunk size. Expected %q", c, '\r') |
| } |
| c, err = r.ReadByte() |
| if err != nil { |
| return -1, fmt.Errorf("cannot read '\n' char at the end of chunk size: %s", err) |
| } |
| if c != '\n' { |
| return -1, fmt.Errorf("unexpected char %q at the end of chunk size. Expected %q", c, '\n') |
| } |
| return n, nil |
| } |
| |
| func round2(n int) int { |
| if n <= 0 { |
| return 0 |
| } |
| n-- |
| x := uint(0) |
| for n > 0 { |
| n >>= 1 |
| x++ |
| } |
| return 1 << x |
| } |