golang Http Request

golang_yh · · 26507 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

一起看一下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服务器可以借鉴一下此处代码


有疑问加站长微信联系(非本文作者)

本文来自:开源中国博客

感谢作者:golang_yh

查看原文:golang Http Request

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

26507 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传