Files
fasthttp/http.go
T
2015-11-19 11:51:04 +02:00

545 lines
11 KiB
Go

package fasthttp
import (
"bufio"
"bytes"
"fmt"
"io"
"sync"
)
// Request represents HTTP request.
//
// It is forbidden copying Request instances. Create new instances
// and use CopyTo() instead.
type Request struct {
// Request header
Header RequestHeader
body []byte
uri URI
parsedURI bool
postArgs Args
parsedPostArgs bool
}
// Response represents HTTP response.
//
// It is forbidden copying Response instances. Create new instances
// and use CopyTo() instead.
type Response struct {
// Response header
Header ResponseHeader
body []byte
bodyStream io.Reader
// If set to true, Response.Read() skips reading body.
// Use it for HEAD requests.
SkipBody bool
}
// SetRequestURI sets RequestURI.
func (req *Request) SetRequestURI(requestURI string) {
req.Header.SetRequestURI(requestURI)
}
// SetRequestURIBytes sets RequestURI.
//
// It is safe using requestURI buffer after the function return.
func (req *Request) SetRequestURIBytes(requestURI []byte) {
req.Header.SetRequestURIBytes(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()
}
// SetBodyStream sets response body stream and, optionally body size.
//
// If bodySize is >= 0, then bodySize bytes are read from bodyStream
// and used as response body.
//
// 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.
func (resp *Response) SetBodyStream(bodyStream io.Reader, bodySize int) {
resp.bodyStream = bodyStream
resp.Header.SetContentLength(bodySize)
}
// BodyWriter returns writer for populating response body.
func (resp *Response) BodyWriter() io.Writer {
return responseBodyWriter{
Response: resp,
}
}
// BodyWriter returns writer for populating request body.
func (req *Request) BodyWriter() io.Writer {
return requestBodyWriter{
Request: req,
}
}
type responseBodyWriter struct {
*Response
}
func (w responseBodyWriter) Write(p []byte) (int, error) {
w.Response.body = append(w.body, p...)
return len(p), nil
}
type requestBodyWriter struct {
*Request
}
func (w requestBodyWriter) Write(p []byte) (int, error) {
w.Request.body = append(w.body, p...)
return len(p), nil
}
// Body returns response body.
func (resp *Response) Body() []byte {
return resp.body
}
// SetBody sets response body.
//
// It is safe modifying body buffer after function return.
func (resp *Response) SetBody(body []byte) {
resp.body = append(resp.body[:0], body...)
}
// Body returns request body.
func (req *Request) Body() []byte {
return req.body
}
// SetBody sets request body.
//
// It is safe modifying body buffer after function return.
func (req *Request) SetBody(body []byte) {
req.body = append(req.body[:0], body...)
}
// CopyTo copies req contents to dst.
func (req *Request) CopyTo(dst *Request) {
dst.Reset()
req.Header.CopyTo(&dst.Header)
dst.body = append(dst.body[:0], req.body...)
if req.parsedURI {
dst.parseURI()
}
if req.parsedPostArgs {
dst.parsePostArgs()
}
}
// CopyTo copies resp contents to dst except of BodyStream.
func (resp *Response) CopyTo(dst *Response) {
dst.Reset()
resp.Header.CopyTo(&dst.Header)
dst.body = append(dst.body[:0], resp.body...)
dst.SkipBody = resp.SkipBody
}
// 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.Parse(req.Header.Host(), req.Header.RequestURI())
}
// 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 !req.Header.IsPost() {
return
}
if !bytes.Equal(req.Header.ContentType(), strPostArgsContentType) {
return
}
req.postArgs.ParseBytes(req.body)
return
}
// Reset clears request contents.
func (req *Request) Reset() {
req.Header.Reset()
req.clearSkipHeader()
}
func (req *Request) clearSkipHeader() {
req.body = req.body[:0]
req.uri.Reset()
req.parsedURI = false
req.postArgs.Reset()
req.parsedPostArgs = false
}
// Reset clears response contents.
func (resp *Response) Reset() {
resp.Header.Reset()
resp.clearSkipHeader()
}
func (resp *Response) clearSkipHeader() {
resp.body = resp.body[:0]
resp.bodyStream = nil
}
// Read reads request (including body) from the given r.
func (req *Request) Read(r *bufio.Reader) error {
req.clearSkipHeader()
err := req.Header.Read(r)
if err != nil {
return err
}
if req.Header.IsPost() {
req.body, err = readBody(r, req.Header.ContentLength(), req.body)
if err != nil {
req.Reset()
return err
}
req.Header.SetContentLength(len(req.body))
}
return nil
}
// Read reads response (including body) from the given r.
func (resp *Response) Read(r *bufio.Reader) error {
resp.clearSkipHeader()
err := resp.Header.Read(r)
if err != nil {
return err
}
if !isSkipResponseBody(resp.Header.StatusCode()) && !resp.SkipBody {
resp.body, err = readBody(r, resp.Header.ContentLength(), resp.body)
if err != nil {
resp.Reset()
return err
}
resp.Header.SetContentLength(len(resp.body))
}
return nil
}
func isSkipResponseBody(statusCode int) bool {
// From http/1.1 specs:
// All 1xx (informational), 204 (no content), and 304 (not modified) responses MUST NOT include a message-body
if statusCode >= 100 && statusCode < 200 {
return true
}
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 {
if len(req.Header.Host()) == 0 {
uri := req.URI()
req.Header.SetHostBytes(uri.Host)
req.Header.SetRequestURIBytes(uri.RequestURI())
}
req.Header.SetContentLength(len(req.body))
err := req.Header.Write(w)
if err != nil {
return err
}
if req.Header.IsPost() {
_, err = w.Write(req.body)
} else if len(req.body) > 0 {
return fmt.Errorf("Non-zero body for non-POST request. body=%q", req.body)
}
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 {
var err error
if resp.bodyStream != nil {
contentLength := resp.Header.ContentLength()
if contentLength >= 0 {
if err = resp.Header.Write(w); err != nil {
return err
}
if err = writeBodyFixedSize(w, resp.bodyStream, contentLength); err != nil {
return err
}
} else {
resp.Header.SetContentLength(-1)
if err = resp.Header.Write(w); err != nil {
return err
}
if err = writeBodyChunked(w, resp.bodyStream); err != nil {
return err
}
}
if bsc, ok := resp.bodyStream.(io.Closer); ok {
err = bsc.Close()
}
return err
}
resp.Header.SetContentLength(len(resp.body))
if err = resp.Header.Write(w); err != nil {
return err
}
_, err = w.Write(resp.body)
return err
}
func writeBodyChunked(w *bufio.Writer, r io.Reader) error {
vbuf := copyBufPool.Get()
if vbuf == nil {
vbuf = make([]byte, 4096)
}
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
}
var limitReaderPool sync.Pool
func writeBodyFixedSize(w *bufio.Writer, r io.Reader, size int) error {
vbuf := copyBufPool.Get()
if vbuf == nil {
vbuf = make([]byte, 4096)
}
buf := vbuf.([]byte)
vlr := limitReaderPool.Get()
if vlr == nil {
vlr = &io.LimitedReader{}
}
lr := vlr.(*io.LimitedReader)
lr.R = r
lr.N = int64(size)
n, err := io.CopyBuffer(w, lr, buf)
limitReaderPool.Put(vlr)
copyBufPool.Put(vbuf)
if n != int64(size) && err == nil {
err = fmt.Errorf("read %d bytes from BodyStream instead of %d bytes", n, size)
}
return err
}
func writeChunk(w *bufio.Writer, b []byte) error {
n := len(b)
writeHexInt(w, n)
w.Write(strCRLF)
w.Write(b)
_, err := w.Write(strCRLF)
return err
}
var copyBufPool sync.Pool
func readBody(r *bufio.Reader, contentLength int, dst []byte) ([]byte, error) {
dst = dst[:0]
if contentLength >= 0 {
return appendBodyFixedSize(r, dst, contentLength)
}
if contentLength == -1 {
return readBodyChunked(r, dst)
}
return readBodyIdentity(r, dst)
}
func readBodyIdentity(r *bufio.Reader, 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 len(dst) == offset {
b := make([]byte, round2(2*offset))
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, 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
}
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
}