• 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
    ", valueOrDefault(req.Method, "GET"), ruri)
    	if err != nil {
    		return err
    	}
    
    	// Header lines 写Host内容
    	_, err = fmt.Fprintf(w, "Host: %s
    ", 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
    ", 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, "
    ")
    	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, "
    ")
    	}
    	return err
    }
    

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

  • 相关阅读:
    mysql问题小结
    mysql批量执行sql文件
    VMware使用中常见问题
    mybaits中xml文件大于号和小于号的处理方法
    自调用匿名函数的三种写法
    Linux相关文章
    Linux常用命令
    不触发事件,vue子组件传值给父组件
    elementUi使用单选框,并且单击行的时候选中该条数据
    可以和正则表达式一起使用的4个字符串方法
  • 原文地址:https://www.cnblogs.com/enumx/p/12322936.html
Copyright © 2020-2023  润新知