zoukankan      html  css  js  c++  java
  • 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服务器可以借鉴一下此处代码

  • 相关阅读:
    解决xcode5升级后,Undefined symbols for architecture arm64:问题
    第8章 Foundation Kit介绍
    app 之间发送文件 ios
    iphone怎么检测屏幕是否被点亮 (用UIApplication的Delegate)
    CRM下载对象一直处于Wait状态的原因
    错误消息Customer classification does not exist when downloading
    How to resolve error message Distribution channel is not allowed for sales
    ABAP CCDEF, CCIMP, CCMAC, CCAU, CMXXX这些东东是什么鬼
    有了Debug权限就能干坏事?小心了,你的一举一动尽在系统监控中
    SAP GUI和Windows注册表
  • 原文地址:https://www.cnblogs.com/enumx/p/12322936.html
Copyright © 2011-2022 走看看