golang Http Request

一起看一下golang的HTTP包怎么write Request信息

先看一下看golang http Request的struct,不解释,慢慢看(HTTP权威指南,RFC文档)

type Request struct {
    // Method specifies the HTTP method (GET, POST, PUT, etc.).
    // For client requests an empty string means GET.
    Method string

    // URL specifies either the URI being requested (for server
    // requests) or the URL to access (for client requests).
    //
    // For server requests the URL is parsed from the URI
    // supplied on the Request-Line as stored in RequestURI.  For
    // most requests, fields other than Path and RawQuery will be
    // empty. (See RFC 2616, Section 5.1.2)
    //
    // For client requests, the URL‘s Host specifies the server to
    // connect to, while the Request‘s Host field optionally
    // specifies the Host header value to send in the HTTP
    // request.
    URL *url.URL

    // The protocol version for incoming requests.
    // Client requests always use HTTP/1.1.
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0

    // A header maps request lines to their values.
    // If the header says
    //
    //  accept-encoding: gzip, deflate
    //  Accept-Language: en-us
    //  Connection: keep-alive
    //
    // then
    //
    //  Header = map[string][]string{
    //      "Accept-Encoding": {"gzip, deflate"},
    //      "Accept-Language": {"en-us"},
    //      "Connection": {"keep-alive"},
    //  }
    //
    // HTTP defines that header names are case-insensitive.
    // The request parser implements this by canonicalizing the
    // name, making the first character and any characters
    // following a hyphen uppercase and the rest lowercase.
    //
    // For client requests certain headers are automatically
    // added and may override values in Header.
    //
    // See the documentation for the Request.Write method.
    Header Header

    // Body is the request‘s body.
    //
    // For client requests a nil body means the request has no
    // body, such as a GET request. The HTTP Client‘s Transport
    // is responsible for calling the Close method.
    //
    // For server requests the Request Body is always non-nil
    // but will return EOF immediately when no body is present.
    // The Server will close the request body. The ServeHTTP
    // Handler does not need to.
    Body io.ReadCloser

    // ContentLength records the length of the associated content.
    // The value -1 indicates that the length is unknown.
    // Values >= 0 indicate that the given number of bytes may
    // be read from Body.
    // For client requests, a value of 0 means unknown if Body is not nil.
    ContentLength int64

    // TransferEncoding lists the transfer encodings from outermost to
    // innermost. An empty list denotes the "identity" encoding.
    // TransferEncoding can usually be ignored; chunked encoding is
    // automatically added and removed as necessary when sending and
    // receiving requests.
    TransferEncoding []string

    // Close indicates whether to close the connection after
    // replying to this request (for servers) or after sending
    // the request (for clients).
    Close bool

    // For server requests Host specifies the host on which the
    // URL is sought. Per RFC 2616, this is either the value of
    // the "Host" header or the host name given in the URL itself.
    // It may be of the form "host:port".
    //
    // For client requests Host optionally overrides the Host
    // header to send. If empty, the Request.Write method uses
    // the value of URL.Host.
    Host string

    // Form contains the parsed form data, including both the URL
    // field‘s query parameters and the POST or PUT form data.
    // This field is only available after ParseForm is called.
    // The HTTP client ignores Form and uses Body instead.
    Form url.Values

    // PostForm contains the parsed form data from POST or PUT
    // body parameters.
    // This field is only available after ParseForm is called.
    // The HTTP client ignores PostForm and uses Body instead.
    PostForm url.Values

    // MultipartForm is the parsed multipart form, including file uploads.
    // This field is only available after ParseMultipartForm is called.
    // The HTTP client ignores MultipartForm and uses Body instead.
    MultipartForm *multipart.Form

    // Trailer specifies additional headers that are sent after the request
    // body.
    //
    // For server requests the Trailer map initially contains only the
    // trailer keys, with nil values. (The client declares which trailers it
    // will later send.)  While the handler is reading from Body, it must
    // not reference Trailer. After reading from Body returns EOF, Trailer
    // can be read again and will contain non-nil values, if they were sent
    // by the client.
    //
    // For client requests Trailer must be initialized to a map containing
    // the trailer keys to later send. The values may be nil or their final
    // values. The ContentLength must be 0 or -1, to send a chunked request.
    // After the HTTP request is sent the map values can be updated while
    // the request body is read. Once the body returns EOF, the caller must
    // not mutate Trailer.
    //
    // Few HTTP clients, servers, or proxies support HTTP trailers.
    Trailer Header

    // RemoteAddr allows HTTP servers and other software to record
    // the network address that sent the request, usually for
    // logging. This field is not filled in by ReadRequest and
    // has no defined format. The HTTP server in this package
    // sets RemoteAddr to an "IP:port" address before invoking a
    // handler.
    // This field is ignored by the HTTP client.
    RemoteAddr string

    // RequestURI is the unmodified Request-URI of the
    // Request-Line (RFC 2616, Section 5.1) as sent by the client
    // to a server. Usually the URL field should be used instead.
    // It is an error to set this field in an HTTP client request.
    RequestURI string

    // TLS allows HTTP servers and other software to record
    // information about the TLS connection on which the request
    // was received. This field is not filled in by ReadRequest.
    // The HTTP server in this package sets the field for
    // TLS-enabled connections before invoking a handler;
    // otherwise it leaves the field nil.
    // This field is ignored by the HTTP client.
    TLS *tls.ConnectionState
}

再来具体分析一下http request write的具体执行流程

func (req *Request) write(w io.Writer, usingProxy bool, extraHeaders Header) error {
    host := req.Host
    if host == "" {
        if req.URL == nil {
            return errors.New("http: Request.Write on Request with no Host or URL set")
        }
        host = req.URL.Host
    }

    ruri := req.URL.RequestURI()
    //代理模式的时候ruri需要加上协议名http/https等
    if usingProxy && req.URL.Scheme != "" && req.URL.Opaque == "" {
        ruri = req.URL.Scheme + "://" + host + ruri
    } else if req.Method == "CONNECT" && req.URL.Path == "" {
        // CONNECT requests normally give just the host and port, not a full URL.
        ruri = host
    }
    // TODO(bradfitz): escape at least newlines in ruri?

    // Wrap the writer in a bufio Writer if it‘s not already buffered.
    // Don‘t always call NewWriter, as that forces a bytes.Buffer
    // and other small bufio Writers to have a minimum 4k buffer
    // size.
    
    //创建一个Writer,往里面写内容
    var bw *bufio.Writer
    if _, ok := w.(io.ByteWriter); !ok {
        bw = bufio.NewWriter(w)
        w = bw
    }
        
        //写http最开始数据
    _, err := fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(req.Method, "GET"), ruri)
    if err != nil {
        return err
    }

    // Header lines 写Host内容
    _, err = fmt.Fprintf(w, "Host: %s\r\n", host)
    if err != nil {
        return err
    }

    // Use the defaultUserAgent unless the Header contains one, which
    // may be blank to not send the header.
    
    //这东西的数据如下:
    /* const defaultUserAgent = "Go 1.1 package http" */
    
    userAgent := defaultUserAgent
    if req.Header != nil {
        if ua := req.Header["User-Agent"]; len(ua) > 0 {
            userAgent = ua[0]
        }
    }
    if userAgent != "" {
        _, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
        if err != nil {
            return err
        }
    }

    // Process Body,ContentLength,Close,Trailer
    //封装的transferWriter结构
    tw, err := newTransferWriter(req)
    if err != nil {
        return err
    }
    err = tw.WriteHeader(w)
    if err != nil {
        return err
    }

    err = req.Header.WriteSubset(w, reqWriteExcludeHeader)
    if err != nil {
        return err
    }

    if extraHeaders != nil {
        err = extraHeaders.Write(w)
        if err != nil {
            return err
        }
    }

    _, err = io.WriteString(w, "\r\n")
    if err != nil {
        return err
    }

    // Write body and trailer
    err = tw.WriteBody(w)
    if err != nil {
        return err
    }

    if bw != nil {
        return bw.Flush()
    }
    return nil
}

再来看看transferWriter结构相关的操作:

//主要用于写HTTP的Body,ContentLength,Close,Trailer
type transferWriter struct {
    Method           string 
    Body             io.Reader
    BodyCloser       io.Closer
    ResponseToHEAD   bool
    ContentLength    int64 // -1 means unknown, 0 means exactly none
    Close            bool
    TransferEncoding []string
    Trailer          Header
}

创建transferWriter的过程:

func newTransferWriter(r interface{}) (t *transferWriter, err error) {
    t = &transferWriter{}

    // Extract relevant fields
    atLeastHTTP11 := false
    switch rr := r.(type) {
    case *Request:
        if rr.ContentLength != 0 && rr.Body == nil {
            return nil, fmt.Errorf("http: Request.ContentLength=%d with nil Body", rr.ContentLength)
        }
        t.Method = rr.Method
        t.Body = rr.Body
        t.BodyCloser = rr.Body
        t.ContentLength = rr.ContentLength
        t.Close = rr.Close
        t.TransferEncoding = rr.TransferEncoding
        t.Trailer = rr.Trailer
        atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
        if t.Body != nil && len(t.TransferEncoding) == 0 && atLeastHTTP11 {
            if t.ContentLength == 0 {
                // Test to see if it‘s actually zero or just unset.
                var buf [1]byte
                n, rerr := io.ReadFull(t.Body, buf[:])
                if rerr != nil && rerr != io.EOF {
                    t.ContentLength = -1
                    t.Body = &errorReader{rerr}
                } else if n == 1 {
                    // Oh, guess there is data in this Body Reader after all.
                    // The ContentLength field just wasn‘t set.
                    // Stich the Body back together again, re-attaching our
                    // consumed byte.
                    t.ContentLength = -1
                    t.Body = io.MultiReader(bytes.NewReader(buf[:]), t.Body)
                } else {
                    // Body is actually empty.
                    t.Body = nil
                    t.BodyCloser = nil
                }
            }
            if t.ContentLength < 0 {
                t.TransferEncoding = []string{"chunked"}
            }
        }
    case *Response:
        if rr.Request != nil {
            t.Method = rr.Request.Method
        }
        t.Body = rr.Body
        t.BodyCloser = rr.Body
        t.ContentLength = rr.ContentLength
        t.Close = rr.Close
        t.TransferEncoding = rr.TransferEncoding
        t.Trailer = rr.Trailer
        atLeastHTTP11 = rr.ProtoAtLeast(1, 1)
        t.ResponseToHEAD = noBodyExpected(t.Method)
    }

    // Sanitize Body,ContentLength,TransferEncoding
    if t.ResponseToHEAD {
        t.Body = nil
        if chunked(t.TransferEncoding) {
            t.ContentLength = -1
        }
    } else {
        if !atLeastHTTP11 || t.Body == nil {
            t.TransferEncoding = nil
        }
        if chunked(t.TransferEncoding) {
            t.ContentLength = -1
        } else if t.Body == nil { // no chunking, no body
            t.ContentLength = 0
        }
    }

    // Sanitize Trailer
    if !chunked(t.TransferEncoding) {
        t.Trailer = nil
    }

    return t, nil
}

最后再看看WriteBody的操作:

func (t *transferWriter) WriteBody(w io.Writer) error {
    var err error
    var ncopy int64

    // Write body 写body的操作在这里
    if t.Body != nil { 
        if chunked(t.TransferEncoding) {
            cw := internal.NewChunkedWriter(w)
            _, err = io.Copy(cw, t.Body)
            if err == nil {
                err = cw.Close()
            }
        } else if t.ContentLength == -1 {
            ncopy, err = io.Copy(w, t.Body)
        } else {
            ncopy, err = io.Copy(w, io.LimitReader(t.Body, t.ContentLength))
            if err != nil {
                return err
            }
            var nextra int64
            nextra, err = io.Copy(ioutil.Discard, t.Body)
            ncopy += nextra
        }
        if err != nil {
            return err
        }
        if err = t.BodyCloser.Close(); err != nil {
            return err
        }
    }

    if !t.ResponseToHEAD && t.ContentLength != -1 && t.ContentLength != ncopy {
        return fmt.Errorf("http: ContentLength=%d with Body length %d",
            t.ContentLength, ncopy)
    }

    // TODO(petar): Place trailer writer code here.
    if chunked(t.TransferEncoding) {
        // Write Trailer header
        if t.Trailer != nil {
            if err := t.Trailer.Write(w); err != nil {
                return err
            }
        }
        // Last chunk, empty trailer
        _, err = io.WriteString(w, "\r\n")
    }
    return err
}

自己实现HTTP服务器可以借鉴一下此处代码

猜你喜欢

转载自www.cnblogs.com/enumx/p/12322936.html