package fasthttp
import (
"bufio"
"bytes"
"compress/gzip"
"encoding/base64"
"errors"
"fmt"
"io"
"mime/multipart"
"net"
"os"
"sync"
"time"
"github.com/valyala/bytebufferpool"
)
var (
requestBodyPoolSizeLimit = -1
responseBodyPoolSizeLimit = -1
)
func SetBodySizePoolLimit (reqBodyLimit , respBodyLimit int ) {
requestBodyPoolSizeLimit = reqBodyLimit
responseBodyPoolSizeLimit = respBodyLimit
}
type Request struct {
noCopy noCopy
Header RequestHeader
uri URI
postArgs Args
bodyStream io .Reader
w requestBodyWriter
body *bytebufferpool .ByteBuffer
bodyRaw []byte
multipartForm *multipart .Form
multipartFormBoundary string
secureErrorLogMessage bool
parsedURI bool
parsedPostArgs bool
keepBodyBuffer bool
isTLS bool
timeout time .Duration
UseHostHeader bool
}
type Response struct {
noCopy noCopy
Header ResponseHeader
ImmediateHeaderFlush bool
StreamBody bool
bodyStream io .Reader
w responseBodyWriter
body *bytebufferpool .ByteBuffer
bodyRaw []byte
SkipBody bool
keepBodyBuffer bool
secureErrorLogMessage bool
raddr net .Addr
laddr net .Addr
}
func (req *Request ) SetHost (host string ) {
req .URI ().SetHost (host )
}
func (req *Request ) SetHostBytes (host []byte ) {
req .URI ().SetHostBytes (host )
}
func (req *Request ) Host () []byte {
return req .URI ().Host ()
}
func (req *Request ) SetRequestURI (requestURI string ) {
req .Header .SetRequestURI (requestURI )
req .parsedURI = false
}
func (req *Request ) SetRequestURIBytes (requestURI []byte ) {
req .Header .SetRequestURIBytes (requestURI )
req .parsedURI = false
}
func (req *Request ) RequestURI () []byte {
if req .parsedURI {
requestURI := req .uri .RequestURI ()
req .SetRequestURIBytes (requestURI )
}
return req .Header .RequestURI ()
}
func (resp *Response ) StatusCode () int {
return resp .Header .StatusCode ()
}
func (resp *Response ) SetStatusCode (statusCode int ) {
resp .Header .SetStatusCode (statusCode )
}
func (resp *Response ) ConnectionClose () bool {
return resp .Header .ConnectionClose ()
}
func (resp *Response ) SetConnectionClose () {
resp .Header .SetConnectionClose ()
}
func (req *Request ) ConnectionClose () bool {
return req .Header .ConnectionClose ()
}
func (req *Request ) SetConnectionClose () {
req .Header .SetConnectionClose ()
}
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
}
func (req *Request ) SetBodyStream (bodyStream io .Reader , bodySize int ) {
req .ResetBody ()
req .bodyStream = bodyStream
req .Header .SetContentLength (bodySize )
}
func (resp *Response ) SetBodyStream (bodyStream io .Reader , bodySize int ) {
resp .ResetBody ()
resp .bodyStream = bodyStream
resp .Header .SetContentLength (bodySize )
}
func (req *Request ) IsBodyStream () bool {
return req .bodyStream != nil
}
func (resp *Response ) IsBodyStream () bool {
return resp .bodyStream != nil
}
func (req *Request ) SetBodyStreamWriter (sw StreamWriter ) {
sr := NewStreamReader (sw )
req .SetBodyStream (sr , -1 )
}
func (resp *Response ) SetBodyStreamWriter (sw StreamWriter ) {
sr := NewStreamReader (sw )
resp .SetBodyStream (sr , -1 )
}
func (resp *Response ) BodyWriter () io .Writer {
resp .w .r = resp
return &resp .w
}
func (req *Request ) BodyStream () io .Reader {
return req .bodyStream
}
func (req *Request ) CloseBodyStream () error {
return req .closeBodyStream ()
}
func (resp *Response ) BodyStream () io .Reader {
return resp .bodyStream
}
func (resp *Response ) CloseBodyStream () error {
return resp .closeBodyStream ()
}
type closeReader struct {
io .Reader
closeFunc func () error
}
func newCloseReader(r io .Reader , closeFunc func () error ) io .ReadCloser {
if r == nil {
panic (`BUG: reader is nil` )
}
return &closeReader {Reader : r , closeFunc : closeFunc }
}
func (c *closeReader ) Close () error {
if c .closeFunc == nil {
return nil
}
return c .closeFunc ()
}
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
}
func (resp *Response ) parseNetConn (conn net .Conn ) {
resp .raddr = conn .RemoteAddr ()
resp .laddr = conn .LocalAddr ()
}
func (resp *Response ) RemoteAddr () net .Addr {
return resp .raddr
}
func (resp *Response ) LocalAddr () net .Addr {
return resp .laddr
}
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 .bodyRaw != nil {
return resp .bodyRaw
}
if resp .body == nil {
return nil
}
return resp .body .B
}
func (req *Request ) bodyBytes () []byte {
if req .bodyRaw != nil {
return req .bodyRaw
}
if req .bodyStream != nil {
bodyBuf := req .bodyBuffer ()
bodyBuf .Reset ()
_ , err := copyZeroAlloc (bodyBuf , req .bodyStream )
req .closeBodyStream ()
if err != nil {
bodyBuf .SetString (err .Error())
}
}
if req .body == nil {
return nil
}
return req .body .B
}
func (resp *Response ) bodyBuffer () *bytebufferpool .ByteBuffer {
if resp .body == nil {
resp .body = responseBodyPool .Get ()
}
resp .bodyRaw = nil
return resp .body
}
func (req *Request ) bodyBuffer () *bytebufferpool .ByteBuffer {
if req .body == nil {
req .body = requestBodyPool .Get ()
}
req .bodyRaw = nil
return req .body
}
var (
responseBodyPool bytebufferpool .Pool
requestBodyPool bytebufferpool .Pool
)
func (req *Request ) BodyGunzip () ([]byte , error ) {
return gunzipData (req .Body ())
}
func (resp *Response ) BodyGunzip () ([]byte , error ) {
return gunzipData (resp .Body ())
}
func gunzipData(p []byte ) ([]byte , error ) {
var bb bytebufferpool .ByteBuffer
_ , err := WriteGunzip (&bb , p )
if err != nil {
return nil , err
}
return bb .B , nil
}
func (req *Request ) BodyUnbrotli () ([]byte , error ) {
return unBrotliData (req .Body ())
}
func (resp *Response ) BodyUnbrotli () ([]byte , error ) {
return unBrotliData (resp .Body ())
}
func unBrotliData(p []byte ) ([]byte , error ) {
var bb bytebufferpool .ByteBuffer
_ , err := WriteUnbrotli (&bb , p )
if err != nil {
return nil , err
}
return bb .B , nil
}
func (req *Request ) BodyInflate () ([]byte , error ) {
return inflateData (req .Body ())
}
func (resp *Response ) BodyInflate () ([]byte , error ) {
return inflateData (resp .Body ())
}
func (ctx *RequestCtx ) RequestBodyStream () io .Reader {
return ctx .Request .bodyStream
}
func inflateData(p []byte ) ([]byte , error ) {
var bb bytebufferpool .ByteBuffer
_ , err := WriteInflate (&bb , p )
if err != nil {
return nil , err
}
return bb .B , nil
}
var ErrContentEncodingUnsupported = errors .New ("unsupported Content-Encoding" )
func (req *Request ) BodyUncompressed () ([]byte , error ) {
switch string (req .Header .ContentEncoding ()) {
case "" :
return req .Body (), nil
case "deflate" :
return req .BodyInflate ()
case "gzip" :
return req .BodyGunzip ()
case "br" :
return req .BodyUnbrotli ()
default :
return nil , ErrContentEncodingUnsupported
}
}
func (resp *Response ) BodyUncompressed () ([]byte , error ) {
switch string (resp .Header .ContentEncoding ()) {
case "" :
return resp .Body (), nil
case "deflate" :
return resp .BodyInflate ()
case "gzip" :
return resp .BodyGunzip ()
case "br" :
return resp .BodyUnbrotli ()
default :
return nil , ErrContentEncodingUnsupported
}
}
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
}
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
}
func (resp *Response ) AppendBody (p []byte ) {
resp .closeBodyStream ()
resp .bodyBuffer ().Write (p )
}
func (resp *Response ) AppendBodyString (s string ) {
resp .closeBodyStream ()
resp .bodyBuffer ().WriteString (s )
}
func (resp *Response ) SetBody (body []byte ) {
resp .closeBodyStream ()
bodyBuf := resp .bodyBuffer ()
bodyBuf .Reset ()
bodyBuf .Write (body )
}
func (resp *Response ) SetBodyString (body string ) {
resp .closeBodyStream ()
bodyBuf := resp .bodyBuffer ()
bodyBuf .Reset ()
bodyBuf .WriteString (body )
}
func (resp *Response ) ResetBody () {
resp .bodyRaw = nil
resp .closeBodyStream ()
if resp .body != nil {
if resp .keepBodyBuffer {
resp .body .Reset ()
} else {
responseBodyPool .Put (resp .body )
resp .body = nil
}
}
}
func (resp *Response ) SetBodyRaw (body []byte ) {
resp .ResetBody ()
resp .bodyRaw = body
}
func (req *Request ) SetBodyRaw (body []byte ) {
req .ResetBody ()
req .bodyRaw = body
}
func (resp *Response ) ReleaseBody (size int ) {
resp .bodyRaw = nil
if resp .body == nil {
return
}
if cap (resp .body .B ) > size {
resp .closeBodyStream ()
resp .body = nil
}
}
func (req *Request ) ReleaseBody (size int ) {
req .bodyRaw = nil
if req .body == nil {
return
}
if cap (req .body .B ) > size {
req .closeBodyStream ()
req .body = nil
}
}
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())
}
}
resp .bodyRaw = nil
oldBody := bb .B
bb .B = body
return oldBody
}
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())
}
}
req .bodyRaw = nil
oldBody := bb .B
bb .B = body
return oldBody
}
func (req *Request ) Body () []byte {
if req .bodyRaw != nil {
return req .bodyRaw
} else if req .onlyMultipartForm () {
body , err := marshalMultipartForm (req .multipartForm , req .multipartFormBoundary )
if err != nil {
return []byte (err .Error())
}
return body
}
return req .bodyBytes ()
}
func (req *Request ) AppendBody (p []byte ) {
req .RemoveMultipartFormFiles ()
req .closeBodyStream ()
req .bodyBuffer ().Write (p )
}
func (req *Request ) AppendBodyString (s string ) {
req .RemoveMultipartFormFiles ()
req .closeBodyStream ()
req .bodyBuffer ().WriteString (s )
}
func (req *Request ) SetBody (body []byte ) {
req .RemoveMultipartFormFiles ()
req .closeBodyStream ()
req .bodyBuffer ().Set (body )
}
func (req *Request ) SetBodyString (body string ) {
req .RemoveMultipartFormFiles ()
req .closeBodyStream ()
req .bodyBuffer ().SetString (body )
}
func (req *Request ) ResetBody () {
req .bodyRaw = nil
req .RemoveMultipartFormFiles ()
req .closeBodyStream ()
if req .body != nil {
if req .keepBodyBuffer {
req .body .Reset ()
} else {
requestBodyPool .Put (req .body )
req .body = nil
}
}
}
func (req *Request ) CopyTo (dst *Request ) {
req .copyToSkipBody (dst )
switch {
case req .bodyRaw != nil :
dst .bodyRaw = append (dst .bodyRaw [:0 ], req .bodyRaw ...)
if dst .body != nil {
dst .body .Reset ()
}
case req .body != nil :
dst .bodyBuffer ().Set (req .body .B )
case 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
dst .UseHostHeader = req .UseHostHeader
}
func (resp *Response ) CopyTo (dst *Response ) {
resp .copyToSkipBody (dst )
switch {
case resp .bodyRaw != nil :
dst .bodyRaw = append (dst .bodyRaw , resp .bodyRaw ...)
if dst .body != nil {
dst .body .Reset ()
}
case resp .body != nil :
dst .bodyBuffer ().Set (resp .body .B )
case dst .body != nil :
dst .body .Reset ()
}
}
func (resp *Response ) copyToSkipBody (dst *Response ) {
dst .Reset ()
resp .Header .CopyTo (&dst .Header )
dst .SkipBody = resp .SkipBody
dst .raddr = resp .raddr
dst .laddr = resp .laddr
}
func swapRequestBody(a , b *Request ) {
a .body , b .body = b .body , a .body
a .bodyRaw , b .bodyRaw = b .bodyRaw , a .bodyRaw
a .bodyStream , b .bodyStream = b .bodyStream , a .bodyStream
if rs , ok := a .bodyStream .(*requestStream ); ok {
rs .header = &a .Header
}
if rs , ok := b .bodyStream .(*requestStream ); ok {
rs .header = &b .Header
}
}
func swapResponseBody(a , b *Response ) {
a .body , b .body = b .body , a .body
a .bodyRaw , b .bodyRaw = b .bodyRaw , a .bodyRaw
a .bodyStream , b .bodyStream = b .bodyStream , a .bodyStream
}
func (req *Request ) URI () *URI {
req .parseURI ()
return &req .uri
}
func (req *Request ) SetURI (newURI *URI ) {
if newURI != nil {
newURI .CopyTo (&req .uri )
req .parsedURI = true
return
}
req .uri .Reset ()
req .parsedURI = false
}
func (req *Request ) parseURI () error {
if req .parsedURI {
return nil
}
req .parsedURI = true
return req .uri .parse (req .Header .Host (), req .Header .RequestURI (), req .isTLS )
}
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 ())
}
var ErrNoMultipartForm = errors .New ("request Content-Type has bad boundary or is not multipart/form-data" )
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
}
var err error
ce := req .Header .peek (strContentEncoding )
if req .bodyStream != nil {
bodyStream := req .bodyStream
if bytes .Equal (ce , strGzip ) {
if bodyStream , err = gzip .NewReader (bodyStream ); err != nil {
return nil , fmt .Errorf ("cannot gunzip request body: %w" , err )
}
} else if len (ce ) > 0 {
return nil , fmt .Errorf ("unsupported Content-Encoding: %q" , ce )
}
mr := multipart .NewReader (bodyStream , req .multipartFormBoundary )
req .multipartForm , err = mr .ReadForm (8 * 1024 )
if err != nil {
return nil , fmt .Errorf ("cannot read multipart/form-data body: %w" , err )
}
} else {
body := req .bodyBytes ()
if bytes .Equal (ce , strGzip ) {
if body , err = AppendGunzipBytes (nil , body ); err != nil {
return nil , fmt .Errorf ("cannot gunzip request body: %w" , err )
}
} else if len (ce ) > 0 {
return nil , fmt .Errorf ("unsupported Content-Encoding: %q" , ce )
}
req .multipartForm , err = readMultipartForm (bytes .NewReader (body ), req .multipartFormBoundary , len (body ), len (body ))
if err != nil {
return nil , err
}
}
return req .multipartForm , nil
}
func marshalMultipartForm(f *multipart .Form , boundary string ) ([]byte , error ) {
var buf bytebufferpool .ByteBuffer
if err := WriteMultipartForm (&buf , f , boundary ); err != nil {
return nil , err
}
return buf .B , nil
}
func WriteMultipartForm (w io .Writer , f *multipart .Form , boundary string ) error {
if len (boundary ) == 0 {
return errors .New ("form boundary cannot be empty" )
}
mw := multipart .NewWriter (w )
if err := mw .SetBoundary (boundary ); err != nil {
return fmt .Errorf ("cannot use form boundary %q: %w" , boundary , err )
}
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: %w" , k , v , err )
}
}
}
for k , fvv := range f .File {
for _ , fv := range fvv {
vw , err := mw .CreatePart (fv .Header )
if err != nil {
return fmt .Errorf ("cannot create form file %q (%q): %w" , k , fv .Filename , err )
}
fh , err := fv .Open ()
if err != nil {
return fmt .Errorf ("cannot open form file %q (%q): %w" , k , fv .Filename , err )
}
if _, err = copyZeroAlloc (vw , fh ); err != nil {
_ = fh .Close ()
return fmt .Errorf ("error when copying form file %q (%q): %w" , k , fv .Filename , err )
}
if err = fh .Close (); err != nil {
return fmt .Errorf ("cannot close form file %q (%q): %w" , k , fv .Filename , err )
}
}
}
if err := mw .Close (); err != nil {
return fmt .Errorf ("error when closing multipart form writer: %w" , err )
}
return nil
}
func readMultipartForm(r io .Reader , boundary string , size , maxInMemoryFileSize int ) (*multipart .Form , error ) {
if size <= 0 {
return nil , fmt .Errorf ("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: %w" , err )
}
return f , nil
}
func (req *Request ) Reset () {
if requestBodyPoolSizeLimit >= 0 && req .body != nil {
req .ReleaseBody (requestBodyPoolSizeLimit )
}
req .Header .Reset ()
req .resetSkipHeader ()
req .timeout = 0
req .UseHostHeader = false
}
func (req *Request ) resetSkipHeader () {
req .ResetBody ()
req .uri .Reset ()
req .parsedURI = false
req .postArgs .Reset ()
req .parsedPostArgs = false
req .isTLS = false
}
func (req *Request ) RemoveMultipartFormFiles () {
if req .multipartForm != nil {
req .multipartForm .RemoveAll ()
req .multipartForm = nil
}
req .multipartFormBoundary = ""
}
func (resp *Response ) Reset () {
if responseBodyPoolSizeLimit >= 0 && resp .body != nil {
resp .ReleaseBody (responseBodyPoolSizeLimit )
}
resp .resetSkipHeader ()
resp .Header .Reset ()
resp .SkipBody = false
resp .raddr = nil
resp .laddr = nil
resp .ImmediateHeaderFlush = false
resp .StreamBody = false
}
func (resp *Response ) resetSkipHeader () {
resp .ResetBody ()
}
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" )
func (req *Request ) ReadLimitBody (r *bufio .Reader , maxBodySize int ) error {
req .resetSkipHeader ()
if err := req .Header .Read (r ); err != nil {
return err
}
return req .readLimitBody (r , maxBodySize , false , true )
}
func (req *Request ) readLimitBody (r *bufio .Reader , maxBodySize int , getOnly bool , preParseMultipartForm bool ) error {
if getOnly && !req .Header .IsGet () && !req .Header .IsHead () {
return ErrGetOnly
}
if req .MayContinue () {
return nil
}
return req .ContinueReadBody (r , maxBodySize , preParseMultipartForm )
}
func (req *Request ) readBodyStream (r *bufio .Reader , maxBodySize int , getOnly bool , preParseMultipartForm bool ) error {
if getOnly && !req .Header .IsGet () && !req .Header .IsHead () {
return ErrGetOnly
}
if req .MayContinue () {
return nil
}
return req .ContinueReadBodyStream (r , maxBodySize , preParseMultipartForm )
}
func (req *Request ) MayContinue () bool {
return bytes .Equal (req .Header .peek (strExpect ), str100Continue )
}
func (req *Request ) ContinueReadBody (r *bufio .Reader , maxBodySize int , preParseMultipartForm ...bool ) error {
var err error
contentLength := req .Header .realContentLength ()
if contentLength > 0 {
if maxBodySize > 0 && contentLength > maxBodySize {
return ErrBodyTooLarge
}
if len (preParseMultipartForm ) == 0 || preParseMultipartForm [0 ] {
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 {
if !req .Header .ignoreBody () {
req .Header .SetContentLength (0 )
}
return nil
}
if err = req .ReadBody (r , contentLength , maxBodySize ); err != nil {
return err
}
if contentLength == -1 {
err = req .Header .ReadTrailer (r )
if err != nil && err != io .EOF {
return err
}
}
return nil
}
func (req *Request ) ReadBody (r *bufio .Reader , contentLength int , maxBodySize int ) (err error ) {
bodyBuf := req .bodyBuffer ()
bodyBuf .Reset ()
switch {
case contentLength >= 0 :
bodyBuf .B , err = readBody (r , contentLength , maxBodySize , bodyBuf .B )
case contentLength == -1 :
bodyBuf .B , err = readBodyChunked (r , maxBodySize , bodyBuf .B )
if err == nil && len (bodyBuf .B ) == 0 {
req .Header .SetContentLength (0 )
}
default :
bodyBuf .B , err = readBodyIdentity (r , maxBodySize , bodyBuf .B )
req .Header .SetContentLength (len (bodyBuf .B ))
}
if err != nil {
req .Reset ()
return err
}
return nil
}
func (req *Request ) ContinueReadBodyStream (r *bufio .Reader , maxBodySize int , preParseMultipartForm ...bool ) error {
var err error
contentLength := req .Header .realContentLength ()
if contentLength > 0 {
if len (preParseMultipartForm ) == 0 || preParseMultipartForm [0 ] {
req .multipartFormBoundary = b2s (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 {
if !req .Header .ignoreBody () {
req .Header .SetContentLength (0 )
}
return nil
}
bodyBuf := req .bodyBuffer ()
bodyBuf .Reset ()
bodyBuf .B , err = readBodyWithStreaming (r , contentLength , maxBodySize , bodyBuf .B )
if err != nil {
if err == ErrBodyTooLarge {
req .Header .SetContentLength (contentLength )
req .body = bodyBuf
req .bodyStream = acquireRequestStream (bodyBuf , r , &req .Header )
return nil
}
if err == errChunkedStream {
req .body = bodyBuf
req .bodyStream = acquireRequestStream (bodyBuf , r , &req .Header )
return nil
}
req .Reset ()
return err
}
req .body = bodyBuf
req .bodyStream = acquireRequestStream (bodyBuf , r , &req .Header )
req .Header .SetContentLength (contentLength )
return nil
}
func (resp *Response ) Read (r *bufio .Reader ) error {
return resp .ReadLimitBody (r , 0 )
}
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 {
if err = resp .Header .Read (r ); err != nil {
return err
}
}
if !resp .mustSkipBody () {
err = resp .ReadBody (r , maxBodySize )
if err != nil {
if isConnectionReset (err ) {
return nil
}
return err
}
}
if resp .Header .ContentLength () == -1 && !resp .StreamBody {
err = resp .Header .ReadTrailer (r )
if err != nil && err != io .EOF {
if isConnectionReset (err ) {
return nil
}
return err
}
}
return nil
}
func (resp *Response ) ReadBody (r *bufio .Reader , maxBodySize int ) (err error ) {
bodyBuf := resp .bodyBuffer ()
bodyBuf .Reset ()
contentLength := resp .Header .ContentLength ()
switch {
case contentLength >= 0 :
bodyBuf .B , err = readBody (r , contentLength , maxBodySize , bodyBuf .B )
if err == ErrBodyTooLarge && resp .StreamBody {
resp .bodyStream = acquireRequestStream (bodyBuf , r , &resp .Header )
err = nil
}
case contentLength == -1 :
if resp .StreamBody {
resp .bodyStream = acquireRequestStream (bodyBuf , r , &resp .Header )
} else {
bodyBuf .B , err = readBodyChunked (r , maxBodySize , bodyBuf .B )
}
default :
bodyBuf .B , err = readBodyIdentity (r , maxBodySize , bodyBuf .B )
resp .Header .SetContentLength (len (bodyBuf .B ))
}
if err == nil && resp .StreamBody && resp .bodyStream == nil {
resp .bodyStream = bytes .NewReader (bodyBuf .B )
}
return err
}
func (resp *Response ) mustSkipBody () bool {
return resp .SkipBody || resp .Header .mustSkipContentLength ()
}
var errRequestHostRequired = errors .New ("missing required Host header in request" )
func (req *Request ) WriteTo (w io .Writer ) (int64 , error ) {
return writeBufio (req , w )
}
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 )
}
func (req *Request ) Write (w *bufio .Writer ) error {
if len (req .Header .Host ()) == 0 || req .parsedURI {
uri := req .URI ()
host := uri .Host ()
if len (req .Header .Host ()) == 0 {
if len (host ) == 0 {
return errRequestHostRequired
} else {
req .Header .SetHostBytes (host )
}
} else if !req .UseHostHeader {
req .Header .SetHostBytes (host )
}
req .Header .SetRequestURIBytes (uri .RequestURI ())
if len (uri .username ) > 0 {
nl := len (uri .username ) + len (uri .password ) + 1
nb := nl + len (strBasicSpace )
tl := nb + base64 .StdEncoding .EncodedLen (nl )
if tl > cap (req .Header .bufKV .value ) {
req .Header .bufKV .value = make ([]byte , 0 , tl )
}
buf := req .Header .bufKV .value [:0 ]
buf = append (buf , uri .username ...)
buf = append (buf , strColon ...)
buf = append (buf , uri .password ...)
buf = append (buf , strBasicSpace ...)
base64 .StdEncoding .Encode (buf [nb :tl ], buf [:nl ])
req .Header .SetBytesKV (strAuthorization , buf [nl :tl ])
}
}
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: %w" , err )
}
req .Header .SetMultipartFormBoundary (req .multipartFormBoundary )
}
hasBody := false
if len (body ) == 0 {
body = req .postArgs .QueryString ()
}
if len (body ) != 0 || !req .Header .ignoreBody () {
hasBody = true
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 {
if req .secureErrorLogMessage {
return fmt .Errorf ("non-zero body for non-POST request" )
}
return fmt .Errorf ("non-zero body for non-POST request. body=%q" , body )
}
return err
}
func (resp *Response ) WriteGzip (w *bufio .Writer ) error {
return resp .WriteGzipLevel (w , CompressDefaultCompression )
}
func (resp *Response ) WriteGzipLevel (w *bufio .Writer , level int ) error {
if err := resp .gzipBody (level ); err != nil {
return err
}
return resp .Write (w )
}
func (resp *Response ) WriteDeflate (w *bufio .Writer ) error {
return resp .WriteDeflateLevel (w , CompressDefaultCompression )
}
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 ) brotliBody (level int ) error {
if len (resp .Header .ContentEncoding ()) > 0 {
return nil
}
if !resp .Header .isCompressibleContentType () {
return nil
}
if resp .bodyStream != nil {
resp .Header .SetContentLength (-1 )
bs := resp .bodyStream
resp .bodyStream = NewStreamReader (func (sw *bufio .Writer ) {
zw := acquireStacklessBrotliWriter (sw , level )
fw := &flushWriter {
wf : zw ,
bw : sw ,
}
copyZeroAlloc (fw , bs )
releaseStacklessBrotliWriter (zw , level )
if bsc , ok := bs .(io .Closer ); ok {
bsc .Close ()
}
})
} else {
bodyBytes := resp .bodyBytes ()
if len (bodyBytes ) < minCompressLen {
return nil
}
w := responseBodyPool .Get ()
w .B = AppendBrotliBytesLevel (w .B , bodyBytes , level )
if resp .body != nil {
responseBodyPool .Put (resp .body )
}
resp .body = w
resp .bodyRaw = nil
}
resp .Header .SetContentEncodingBytes (strBr )
resp .Header .addVaryBytes (strAcceptEncoding )
return nil
}
func (resp *Response ) gzipBody (level int ) error {
if len (resp .Header .ContentEncoding ()) > 0 {
return nil
}
if !resp .Header .isCompressibleContentType () {
return nil
}
if resp .bodyStream != nil {
resp .Header .SetContentLength (-1 )
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 {
return nil
}
w := responseBodyPool .Get ()
w .B = AppendGzipBytesLevel (w .B , bodyBytes , level )
if resp .body != nil {
responseBodyPool .Put (resp .body )
}
resp .body = w
resp .bodyRaw = nil
}
resp .Header .SetContentEncodingBytes (strGzip )
resp .Header .addVaryBytes (strAcceptEncoding )
return nil
}
func (resp *Response ) deflateBody (level int ) error {
if len (resp .Header .ContentEncoding ()) > 0 {
return nil
}
if !resp .Header .isCompressibleContentType () {
return nil
}
if resp .bodyStream != nil {
resp .Header .SetContentLength (-1 )
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 {
return nil
}
w := responseBodyPool .Get ()
w .B = AppendDeflateBytesLevel (w .B , bodyBytes , level )
if resp .body != nil {
responseBodyPool .Put (resp .body )
}
resp .body = w
resp .bodyRaw = nil
}
resp .Header .SetContentEncodingBytes (strDeflate )
resp .Header .addVaryBytes (strAcceptEncoding )
return nil
}
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
}
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 )
err = req .Header .Write (w )
if err == nil {
err = writeBodyChunked (w , req .bodyStream )
}
if err == nil {
err = req .Header .writeTrailer (w )
}
}
err1 := req .closeBodyStream ()
if err == nil {
err = err1
}
return err
}
type ErrBodyStreamWritePanic struct {
error
}
func (resp *Response ) writeBodyStream (w *bufio .Writer , sendBody bool ) (err error ) {
defer func () {
if r := recover (); r != nil {
err = &ErrBodyStreamWritePanic {
error : fmt .Errorf ("panic while writing body stream: %+v" , r ),
}
}
}()
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 {
if resp .ImmediateHeaderFlush {
err = w .Flush ()
}
if err == nil && sendBody {
err = writeBodyFixedSize (w , resp .bodyStream , int64 (contentLength ))
}
}
} else {
resp .Header .SetContentLength (-1 )
if err = resp .Header .Write (w ); err == nil {
if resp .ImmediateHeaderFlush {
err = w .Flush ()
}
if err == nil && sendBody {
err = writeBodyChunked (w , resp .bodyStream )
}
if err == nil {
err = resp .Header .writeTrailer (w )
}
}
}
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 ()
}
if rs , ok := req .bodyStream .(*requestStream ); ok {
releaseRequestStream (rs )
}
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 ()
}
if bsr , ok := resp .bodyStream .(*requestStream ); ok {
releaseRequestStream (bsr )
}
resp .bodyStream = nil
return err
}
func (req *Request ) String () string {
return getHTTPString (req )
}
func (resp *Response ) String () string {
return getHTTPString (resp )
}
func getHTTPString(hw httpWriter ) string {
w := bytebufferpool .Get ()
defer bytebufferpool .Put (w )
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 )
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 {
continue
}
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 {
if err := w .Flush (); err != nil {
return err
}
}
n , err := copyZeroAlloc (w , r )
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 )
if err := writeHexInt (w , n ); err != nil {
return err
}
if _ , err := w .Write (strCRLF ); err != nil {
return err
}
if _ , err := w .Write (b ); err != nil {
return err
}
if n > 0 {
if _ , err := w .Write (strCRLF ); err != nil {
return err
}
}
return w .Flush ()
}
var ErrBodyTooLarge = errors .New ("body size exceeds the given limit" )
func readBody(r *bufio .Reader , contentLength int , maxBodySize int , dst []byte ) ([]byte , error ) {
if maxBodySize > 0 && contentLength > maxBodySize {
return dst , ErrBodyTooLarge
}
return appendBodyFixedSize (r , dst , contentLength )
}
var errChunkedStream = errors .New ("chunked stream" )
func readBodyWithStreaming(r *bufio .Reader , contentLength int , maxBodySize int , dst []byte ) (b []byte , err error ) {
if contentLength == -1 {
return b , errChunkedStream
}
dst = dst [:0 ]
readN := maxBodySize
if readN > contentLength {
readN = contentLength
}
if readN > 8 *1024 {
readN = 8 * 1024
}
if contentLength >= 0 && maxBodySize >= contentLength {
b , err = appendBodyFixedSize (r , dst , readN )
} else {
b , err = readBodyIdentity (r , readN , dst )
}
if err != nil {
return b , err
}
if contentLength > maxBodySize {
return b , ErrBodyTooLarge
}
return b , nil
}
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 {
switch {
case errors .Is (err , io .EOF ):
return dst [:offset ], nil
case err != nil :
return dst [:offset ], err
default :
return dst [:offset ], fmt .Errorf ("bufio.Read() returned (%d, nil)" , nn )
}
}
offset += nn
if maxBodySize > 0 && offset > maxBodySize {
return dst [:offset ], ErrBodyTooLarge
}
if len (dst ) == offset {
n := roundUpForSliceCap (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 , roundUpForSliceCap (dstLen ))
copy (b , dst )
dst = b
}
dst = dst [:dstLen ]
for {
nn , err := r .Read (dst [offset :])
if nn <= 0 {
switch {
case errors .Is (err , io .EOF ):
return dst [:offset ], io .ErrUnexpectedEOF
case err != nil :
return dst [:offset ], err
default :
return dst [:offset ], fmt .Errorf ("bufio.Read() returned (%d, nil)" , nn )
}
}
offset += nn
if offset == dstLen {
return dst , nil
}
}
}
type ErrBrokenChunk struct {
error
}
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 chunkSize == 0 {
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 , ErrBrokenChunk {
error : fmt .Errorf ("cannot find crlf at the end of chunk" ),
}
}
dst = dst [:len (dst )-strCRLFLen ]
}
}
func parseChunkSize(r *bufio .Reader ) (int , error ) {
n , err := readHexInt (r )
if err != nil {
return -1 , err
}
for {
c , err := r .ReadByte ()
if err != nil {
return -1 , ErrBrokenChunk {
error : fmt .Errorf ("cannot read '\r' char at the end of chunk size: %w" , err ),
}
}
if c != '\r' {
continue
}
if err := r .UnreadByte (); err != nil {
return -1 , ErrBrokenChunk {
error : fmt .Errorf ("cannot unread '\r' char at the end of chunk size: %w" , err ),
}
}
break
}
err = readCrLf (r )
if err != nil {
return -1 , err
}
return n , nil
}
func readCrLf(r *bufio .Reader ) error {
for _ , exp := range []byte {'\r' , '\n' } {
c , err := r .ReadByte ()
if err != nil {
return ErrBrokenChunk {
error : fmt .Errorf ("cannot read %q char at the end of chunk size: %w" , exp , err ),
}
}
if c != exp {
return ErrBrokenChunk {
error : fmt .Errorf ("unexpected char %q at the end of chunk size. Expected %q" , c , exp ),
}
}
}
return nil
}
func (req *Request ) SetTimeout (t time .Duration ) {
req .timeout = t
}
The pages are generated with Golds v0.6.7 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @Go100and1 (reachable from the left QR code) to get the latest news of Golds .