关于request
request 表示由服务器接收或由客户端发送的HTTP请求,例如客户端(client)在发送各种请求时,需要先新建一个请求对象,然后调用一些请求的方法开始自定义一些配置,服务端监听到该请求便会做出相应的应答

关于request.go文件
主要包含如下几个部分代码:
1、http请求中的一些错误类型(部分已弃用)
2、request请求结构体对象,以及该对象的 获取上下文方法,2个修改上下文方法(都用到clone方法)
3、以及请求头的处理方法:获取发送请求的用户代理,获取请求cookie、添加请求cookie方法,获取来源URL,获取协议是否至少是0(major.minor)
4、以及请求体的处理方法:生成一个读取器(以及内部实现)
5、以及 根据写入器编写请求对象(以及内部实现),实现自定义请求
6、以及请求对象的其他字段 如协议获取、协议解析、设置、获取身份认证,内部清除host、判断请求方法是否有效、请求的内容长度方法
7、以及 根据 请求体读取器(io.Reader) (和给定的ctx、url、method、body )生成一个请求对象(以及内部实现)
8、以及 根据 读取器(*bufio.Reader),读取一个请求实例方法(readRequest内部方法实现),以及读取器的相关方法(新建一个最大字节的读取器,以及读取器结构体对象的读取关闭方法)
9、请求体的解析方法(如表单解析、多部件表单解析)
10、请求实例的 内部预期继续 方法(使用 token实现),保持连接方法:(使用 token实现),关闭请求,关闭请求体方法,获取请求的内容长度等方法

源码解析
1、request错误类型

const (
	defaultMaxMemory = 32 << 20 // 32 MB			// 默认最大内存32 MB
)

var ErrMissingFile = errors.New("http: no such file")// 提供的文件字段名不在请求中或非文件字段时,FormFile返回

// 弃用的省略(并非http包中与协议错误相关的所有错误都属于ProtocolError类型。)
type ProtocolError struct {
	ErrorString string
}
func (pe *ProtocolError) Error() string { return pe.ErrorString }
var (
	// Pusher实现的Push方法返回ErrNotSupported,以指示HTTP/2push支持不可用
	ErrNotSupported = &ProtocolError{"feature not supported"}
	// 当请求的内容类型不包含“boundary”参数时,request.MultipartReader返回
	ErrMissingBoundary = &ProtocolError{"no multipart boundary param in Content-Type"}
	// Content-Type不是multipart/form-data时,request.MultipartReader返回
	ErrNotMultipart = &ProtocolError{"request Content-Type isn't multipart/form-data"}
)  
func badStringError(what, val string) error { return fmt.Errorf("%s %q", what, val) }

代码风格:
ErrMissingFile:这是请求对象的一个查找方法返回的错误,调用了errors包的New方法来返回一个错误类型的错误(即errors.New可以传string类型参数 来返回一个简单的字符串错误)
ProtocolError :ProtocolError表示HTTP协议错误结构体对象。该对象 实现了error接口(Error() string),因此,所有该结构体实例都可以当成error返回传参
badStringError:是一个根据两个 string参数返回一个error的函数,当必要时,可以调用此函数实现返回错误

2、request 结构体定义

// Headers that Request.Write 处理自身应跳过
var reqWriteExcludeHeader = map[string]bool{
	"Host":              true, // 反正不在Header的map中
	"User-Agent":        true,
	"Content-Length":    true,
	"Transfer-Encoding": true,
	"Trailer":           true,
}

// Request 请求结构体:在客户机和服务器使用之间,字段语义略有不同。 除了以下字段的注释外,请参阅文档以了解Request.Write and RoundTripper
type Request struct {
	// 指定发送的HTTP请求的方法(GET、POST、PUT等),Go的HTTP客户端不支持使用CONNECT方法发送请求
	Method string

	// URL 指定被请求的URI(对于服务器请求)或要访问的URL(对于客户端请求,就是要连接的服务器地址)
	URL *url.URL

	// 入服务器请求的协议版本(主版本号.次版本号)
	Proto      string // "HTTP/1.0"
	ProtoMajor int    // 1
	ProtoMinor int    // 0

	// 请求头(为请求报文添加了一些附加信息),不区分大小写,对于客户端请求,某些头(如内容长度和连接)会在需要时自动写入
	Header Header

	// 请求体,get请求没有请求体-Body字段(get请求的参数都在url里)
	Body io.ReadCloser

	// 获取请求体的副本
	GetBody func() (io.ReadCloser, error)

	// 请求Body的大小(字节数)
	ContentLength int64

	// 列出从最外层到最内层的传输编码。空列表表示“身份”编码,通常可以忽略;在发送和接收请求时,根据需要自动添加和删除分块编码。
	TransferEncoding []string

	// 在回复了此次请求后结束连接。对服务端来说就是回复了这个 request ,对客户端来说就是收到了 response
	// 且 对于服务端是Handlers 会自动调用Close, 对客户端,如果设置长连接(Transport.DisableKeepAlives=false),就不会关闭。没设置就关闭
	Close bool

	// 对于服务器请求,Host指定查找URL的主机
	Host string

	// Form 包含解析的表单数据,包括URL字段的查询参数和补丁、POST或PUT表单数据, 此字段仅在调用ParseForm后可用。
	Form url.Values

	// PostForm 包含来自PATCH、POST或PUT body参数的解析表单数据, 此字段仅在调用ParseForm后可用。HTTP客户机忽略PostForm,而是使用Body。
	PostForm url.Values

	// MultipartForm 是经过解析的多部件 表单,包括文件上传. 此字段仅在解析表单即 调用ParseMultipartForm后可用。 HTTP客户机忽略MultipartForm,而是使用Body。
	MultipartForm *multipart.Form

	// Trailer 指定在 当body部分发送完成之后 发送的附加头
	// 对于服务器请求,尾部映射最初只包含尾部键,值为nil。(客户端声明它稍后将发送哪些trailers)当处理程序从主体读取时,它不能引用trailers
	// 从Body读取返回EOF后,可以再次读取Trailer并包含非nil值
	// 对于客户端请求,必须将拖车初始化为包含trailer键的map,以便稍后发送。
	// 很少有HTTP客户机、服务器或代理支持HTTP Trailer
	Trailer Header

	// RemoteAddr 允许HTTP服务器和其他软件记录发送请求的网络地址,通常用于日志记录。
	// 此字段不是由ReadRequest填写的,并且没有定义的格式。此包中的HTTP服务器在调用处理程序之前将RemoteAddr设置为“IP:port”地址。 HTTP客户端将忽略此字段。
	RemoteAddr string

	// RequestURI 是客户端发送到服务器的请求行(RFC 7230,第3.1.1节)的未修改的请求目标。通常应该改用URL字段。在HTTP客户端请求中设置此字段是错误的。
	RequestURI string

	// TLS 允许HTTP服务器和其他软件记录有关接收请求的TLS连接的信息。此字段不是由ReadRequest填写的。HTTP客户端将忽略此字段
	TLS *tls.ConnectionState

	// Cancel 是一个可选通道,它的关闭表示客户端请求应被视为已取消
	// 不推荐:改为使用NewRequestWithContext设置请求的上下文
	Cancel <-chan struct{}

	// Response是导致创建此请求的重定向响应。此字段仅在客户端重定向期间填充
	Response *Response

	// 请求的上下文。(修改时,通过使用WithContext复制整个请求来修改它)
	// 对于传出的客户机请求,上下文控制请求及其响应的整个生存期:获取连接、发送请求以及读取响应头和主体
	ctx context.Context
}

在上面的代码中,我们定义了 http.Request类型中的公开数据成员,备注详细说明了其中的各行代码。
下面介绍 该对象的 获取内部成员ctx(上下文)的方法,2个修改上下文方法(都用到了内部自定义clone函数)

func (r *Request) Context() context.Context {
	if r.ctx != nil {
		return r.ctx
	}
	return context.Background()
}

func (r *Request) WithContext(ctx context.Context) *Request {
	if ctx == nil {
		panic("nil context")
	}
	// 1、首先,使用new(type), 返回一个 指向 Request类型的 零值 的指针
	r2 := new(Request)
	// 2、将*Request 赋值给 r2的指针(即将 指向请求数据的指针,赋值给新变量的指针。此时,新变量的指针也是指向原请求,即新变量r2是一个 数据和r相同的变量)
	*r2 = *r
	// 3、把r2的上下文修改成新上下文
	r2.ctx = ctx
	// 4、将 原请求的Url克隆进新请求的Url (cloneURL的本质就是上面指针修改方法, 内部还进行了user属性的克隆)
	r2.URL = cloneURL(r.URL)
	// 5、返回新的 请求
	return r2
}

func (r *Request) Clone(ctx context.Context) *Request {
	if ctx == nil {
		panic("nil context")
	}
	r2 := new(Request)
	*r2 = *r
	r2.ctx = ctx
	r2.URL = cloneURL(r.URL)

	// 对比上面方法,这里新增其他 属性的克隆:Header、Trailer、TransferEncoding、Form、PostForm、MultipartForm
	if r.Header != nil {
		r2.Header = r.Header.Clone()
	}
	if r.Trailer != nil {
		r2.Trailer = r.Trailer.Clone()
	}
	if s := r.TransferEncoding; s != nil {
		s2 := make([]string, len(s))
		copy(s2, s)
		r2.TransferEncoding = s2
	}
	r2.Form = cloneURLValues(r.Form)
	r2.PostForm = cloneURLValues(r.PostForm)
	r2.MultipartForm = cloneMultipartForm(r.MultipartForm)
	return r2
}

// ProtoAtLeast 报告请求中使用的HTTP协议是否至少 major.minor.(入服务器请求的协议版本)
func (r *Request) ProtoAtLeast(major, minor int) bool {
	return r.ProtoMajor > major ||
		r.ProtoMajor == major && r.ProtoMinor >= minor
}

Context():获取对象内部成员,可以使用一个公开的方法封装实现
WithContext(): 更改请求的上下文方法一,传入新的上下文,返回修改后的请求–r的浅层副本,该方法 通过新建一个变量,使变量的指针指向原 请求,然后克隆请求的URL实现。此方法很少用。要使用上下文创建新请求,请使用NewRequestWithContext
Clone(): 更改请求的上下文方法二,传入新的上下文,返回修改后的请求–r的深度副本, 通过除了克隆请求的URL,还有对请求结构里的请求头等一一克隆进一个新的请求结构体 实现,一般用此方法

从代码可看出,修改结构体对象的成员方法,需要使用克隆方法实现。即先创建一个指向该类型的存储变量r2 := new(Request),然后将原指针同样也赋值给新变量*r2 = *r,此时就得到了一个副本,将需要修改的字段重新赋值给副本,最后再返回副本实现。这里每次调用的克隆方法,其内部实现也其实是这个副本生成的封装过程。例如:

func cloneURL(u *url.URL) *url.URL {
	if u == nil {
		return nil
	}
	u2 := new(url.URL)
	*u2 = *u
	if u.User != nil {
		u2.User = new(url.Userinfo)
		*u2.User = *u.User
	}
	return u2
}

3、下面是request请求头的一些字段的修改方法

// 如果在请求中发送,UserAgent将返回 发送请求的应用程序名称。
func (r *Request) UserAgent() string {
	return r.Header.Get("User-Agent")
}

// Cookies (所有cookie)解析并返回与请求一起发送的HTTP Cookies(其实就是调用请求头的解析cookie方法)
func (r *Request) Cookies() []*Cookie {
	return readCookies(r.Header, "")
}

// 当找不到Cookie时,请求的Cookie方法将返回ErrNoCookie
var ErrNoCookie = errors.New("http: named cookie not present")

// Cookie (单个cookie)返回请求中提供的指定Cookie,如果找不到则返回ErrNoCookie
// 调用请求头的过滤器解析cookie的方法,实现(入参name就是过滤器,即指定的cookie命名)
func (r *Request) Cookie(name string) (*Cookie, error) {
	for _, c := range readCookies(r.Header, name) {
		return c, nil
	}
	return nil, ErrNoCookie
}

// AddCookie将 cookie添加到请求中。
func (r *Request) AddCookie(c *Cookie) {
	// 1、清理入参cookie(只清理入参cookie的名称和值,不清理请求中已经存在的Cookie头。)
	s := fmt.Sprintf("%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value))

	// 2、获取原请求头的cookie,然后设置为 : c;s (不会附加多个Cookie头字段。 都写在同一行中,用;分隔)
	if c := r.Header.Get("Cookie"); c != "" {
		r.Header.Set("Cookie", c+"; "+s)
	} else {
		r.Header.Set("Cookie", s)
	}
}

// 如果在请求中发送,Referer返回引用URL(提供了Request的上下文信息的服务器,告诉服务器我是从哪个链接过来的,比如从我主页上链接到一个朋友那里, 他的服务器就能够从HTTP Referer中统计出每天有多少用户点击我主页上的链接访问 他的网站)
// Referer的拼写与请求本身一样错误,这是HTTP最早出现的一个错误。
func (r *Request) Referer() string {
	return r.Header.Get("Referer")
}

4、请求体的处理方法(针对post请求):生成一个读取器(以及内部实现)
multipart/form-data是post请求的请求体内容类型,当设置为此类型时,请求头必须包含这个类型的Content-Type,请求体必须规定为multipart/form-data

在这里插入代码片
// multipartByReader 是一个sentinel(哨兵)值
// 它在Request.MultipartForm里的存在,表明了 请求体的解析 已经被 传递给了 一个MultipartReader实例,而不是还需要解析-ParseMultipartForm
var multipartByReader = &multipart.Form{
	Value: make(map[string][]string),
	File:  make(map[string][]*multipart.FileHeader),		// FileHeader描述一个multipart请求的(一个)文件记录的信息。
}
// 生成一个 多部件表单 读取器(内部multipartReader方法的封装)
// 实现过程: 只有在 请求体 字段MultipartForm 为nil时,才会调用生成 读取器
// 使用这个函数可 代替 请求体解析方法ParseMultipartForm() 将请求体作为流进行处理(即要么 解析处理,要么使用读取器处理)
func (r *Request) MultipartReader() (*multipart.Reader, error) {
	// 1、判断 如果 请求体 多部件表单字段,已经是一个 解析过的表单类型,说明已经调用了一次,不需要再处理
	if r.MultipartForm == multipartByReader {
		return nil, errors.New("http: MultipartReader called twice")
	}
	// 2、如果 请求体 多部件表单字段 不为空,说明 已经被解析了
	if r.MultipartForm != nil {
		return nil, errors.New("http: multipart handled by ParseMultipartForm")
	}
	// 3、只有当 该字段为空时, 就会先 将该字段设置为 多部件表单类型,然后调用 生成读取器方法来 返回
	r.MultipartForm = multipartByReader
	// 4、返回 一个读取器
	return r.multipartReader(true)
}

// 内部生成 读取器的过程:
// 主要是调用 multipart.NewReader()方法实现
func (r *Request) multipartReader(allowMixed bool) (*multipart.Reader, error) {
	// 1、获取、解析请求头里设置的 请求内容类型
	v := r.Header.Get("Content-Type")
	if v == "" {
		return nil, ErrNotMultipart
	}

	// 2、将设置的 类型 转换为小写后判断 是否是多部件类型 ,因为只有这个类型才需要生成 读取器
	d, params, err := mime.ParseMediaType(v)
	if err != nil || !(d == "multipart/form-data" || allowMixed && d == "multipart/mixed") {
		return nil, ErrNotMultipart
	}

	// 3、获取 内容类型的键为boundary的值
	boundary, ok := params["boundary"]
	if !ok {
		return nil, ErrMissingBoundary
	}

	// 4、(使用给定的MIMIE边缘值-boundary)创建一个新的 请求体 读取的多元读取器
	return multipart.NewReader(r.Body, boundary), nil
}

5、request写入方法实现

// 根据 写入器io.Writer 编写http请求,调用了内部write(),传参:不使用代理、空的附加头、不等待
// 它是头和正文; 此方法引用请求的字段:Host、URL、Method、Header、ContentLength、TransferEncoding、Body
func (r *Request) Write(w io.Writer) error {
	return r.write(w, false, nil, nil)
}

// WriteProxy与Write类似,但以HTTP代理所期望的形式写入请求。
func (r *Request) WriteProxy(w io.Writer) error {
	return r.write(w, true, nil, nil)
}

// 当请求中没有主机或URL时,通过Write返回errMissingHost.
var errMissingHost = errors.New("http: Request.Write on Request with no Host or URL set")

// 根据写入器 编写 请求,内部实现
// 除了写入器,可能会附加使用代理、附加请求头、是否等待参数
func (r *Request) write(w io.Writer, usingProxy bool, extraHeaders Header, waitForContinue func() bool) (err error) {
	// 1、根据请求的上下文 返回一个跟踪对象
	trace := httptrace.ContextClientTrace(r.Context())

	// 2、程序结束之后,调用 WroteRequest()函数来传入 任何错误进跟踪器
	if trace != nil && trace.WroteRequest != nil {
		defer func() {
			trace.WroteRequest(httptrace.WroteRequestInfo{
				Err: err,
			})
		}()
	}

	// 3、找到目标主机。首选Host:header,但如果没有给出,则使用请求URL中的Host。清理主机,以防主机中有意外的东西
	host := cleanHost(r.Host)
	if host == "" {
		if r.URL == nil {
			return errMissingHost
		}
		host = cleanHost(r.URL.Host)    // 这里判断了如果 请求的host为空,则使用 请求URL的host
	}

	// 4、host清理:HTTP客户机、代理或其他中介必须删除附加到传出URI的任何IPv6区域标识符。
 	host = removeZone(host)

	// 5、如果又代理,组合uri:uri = Scheme://host+r.URL.RequestURI()
	ruri := r.URL.RequestURI()
	if usingProxy && r.URL.Scheme != "" && r.URL.Opaque == "" {
		ruri = r.URL.Scheme + "://" + host + ruri
		// 6、如果没有代理, ruri = host
	} else if r.Method == "CONNECT" && r.URL.Path == "" {
		// CONNECT请求通常只提供主机和端口,而不是完整的URL
		ruri = host
		if r.URL.Opaque != "" {
			ruri = r.URL.Opaque
		}
	}
	if stringContainsCTLByte(ruri) {
		return errors.New("net/http: can't write control character in Request.URL")
	}
	
	var bw *bufio.Writer
	// 7、写入器转 字节写入器,再转缓冲写入器
	if _, ok := w.(io.ByteWriter); !ok {
		bw = bufio.NewWriter(w)
		w = bw
	}

	_, err = fmt.Fprintf(w, "%s %s HTTP/1.1\r\n", valueOrDefault(r.Method, "GET"), ruri)
	if err != nil {
		return err
	}

	// 8、标题行 写入追踪缓冲
	_, err = fmt.Fprintf(w, "Host: %s\r\n", host)
	if err != nil {
		return err
	}
	if trace != nil && trace.WroteHeaderField != nil {
		// WroteHeaderField在Transport  写入每个请求头之后被调用。在调用时,这些值可能已被缓冲,尚未写入网络
		trace.WroteHeaderField("Host", []string{host})
	}

	// 9、 使用defaultUserAgent,除非标头包含一个,否则该标头可能为空以不发送标头
	// 代理写入 追踪缓冲
	userAgent := defaultUserAgent
	if r.Header.has("User-Agent") {
		userAgent = r.Header.Get("User-Agent")
	}
	if userAgent != "" {
		_, err = fmt.Fprintf(w, "User-Agent: %s\r\n", userAgent)
		if err != nil {
			return err
		}
		if trace != nil && trace.WroteHeaderField != nil {
			trace.WroteHeaderField("User-Agent", []string{userAgent})
		}
	}

	// 进程主体,ContentLength,结束,尾部
	tw, err := newTransferWriter(r)		// 根据请求new一个新的 传输写入器
	if err != nil {
		return err
	}

	// 10、传输写入器,调用writeHeader()方法,把追踪器里的请求头写入传入的写入器里
	err = tw.writeHeader(w, trace)
	if err != nil {
		return err
	}

	// 11、调用请求头的writeSubset()方法,用wire格式写入Header。
	err = r.Header.writeSubset(w, reqWriteExcludeHeader, trace)
	if err != nil {
		return err
	}

	// 12、如果附加请求头存在,也写入
	if extraHeaders != nil {
		err = extraHeaders.write(w, trace)
		if err != nil {
			return err
		}
	}

	// 13、将"\r\n" 写入w
	_, err = io.WriteString(w, "\r\n")
	if err != nil {
		return err
	}

	if trace != nil && trace.WroteHeaders != nil {
		trace.WroteHeaders()
	}

	// 14、刷新并等待100
	if waitForContinue != nil {
		if bw, ok := w.(*bufio.Writer); ok {
			err = bw.Flush()
			if err != nil {
				return err
			}
		}
		if trace != nil && trace.Wait100Continue != nil {
			trace.Wait100Continue()
		}
		if !waitForContinue() {
			r.closeBody()
			return nil
		}
	}

	if bw, ok := w.(*bufio.Writer); ok && tw.FlushHeaders {
		if err := bw.Flush(); err != nil {
			return err
		}
	}

	// 15、写请求体
	err = tw.writeBody(w)
	if err != nil {
		if tw.bodyReadError == err {
			err = requestBodyReadError{err}
		}
		return err
	}

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

7、以及根据读取器 读出一个请求对象(以及内部实现)方法自定义request请求

// 新建一个请求的方法(使用后台空上下文包装NewRequestWithContext)
func NewRequest(method, url string, body io.Reader) (*Request, error) {
	return NewRequestWithContext(context.Background(), method, url, body)
}

// 使用给定的上下文 新建一个请求方法
// 如果提供的主体也是io.Closer, 返回的 请求体被设置为body 后就会被客户端 的 Do\Post\PostForm\Transport.RoundTrip关闭
// 要创建用于测试服务器处理程序的请求,请使用net/http/httptest包中的NewRequest函数、ReadRequest或手动更新请求字段
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error) {
	// 1、默认 Get请求
	if method == "" {
		method = "GET"
	}
	// 2、method、ctx 参数检查
	if !validMethod(method) {
		return nil, fmt.Errorf("net/http: invalid method %q", method)
	}
	if ctx == nil {
		return nil, errors.New("net/http: nil Context")
	}
	// 3、解析string的URL
	u, err := urlpkg.Parse(url)
	if err != nil {
		return nil, err
	}
	// 4、读取请求体
	rc, ok := body.(io.ReadCloser)
	if !ok && body != nil {
		rc = ioutil.NopCloser(body)
	}
	// 5、Url里的Host端口应该正常化
	u.Host = removeEmptyPort(u.Host)
	// 6、构建新请求
	req := &Request{
		ctx:        ctx,
		Method:     method,
		URL:        u,
		Proto:      "HTTP/1.1",
		ProtoMajor: 1,
		ProtoMinor: 1,
		Header:     make(Header),
		Body:       rc,
		Host:       u.Host,
	}
	// 7、body参数处理 (这里实际应用:jpush包内的 自己构建request)
	if body != nil {
		// 根据传入的不同类型的body,经过 读取body后,转成 io.ReadCloser 返回
		switch v := body.(type) {
		case *bytes.Buffer:
			req.ContentLength = int64(v.Len())
			buf := v.Bytes()
			req.GetBody = func() (io.ReadCloser, error) {
				r := bytes.NewReader(buf)
				return ioutil.NopCloser(r), nil
			}
		case *bytes.Reader:
			req.ContentLength = int64(v.Len())
			snapshot := *v
			req.GetBody = func() (io.ReadCloser, error) {
				r := snapshot
				return ioutil.NopCloser(&r), nil
			}
		case *strings.Reader:
			req.ContentLength = int64(v.Len())
			snapshot := *v
			req.GetBody = func() (io.ReadCloser, error) {
				r := snapshot
				return ioutil.NopCloser(&r), nil
			}
		default:
		}
		// 对于客户请求,请求.ContentLength0表示实际为0或未知。 要明确表示ContentLength为零的唯一方法是将Body设置为nil
		// 但是太多的代码依赖于NewRequest返回一个非空请求体,所以我们使用ReadCloser变量,并让http包也将sentinel变量显式地表示为零。
		if req.GetBody != nil && req.ContentLength == 0 {
			req.Body = NoBody    // var NoBody = noBody{},空结构体
			req.GetBody = func() (io.ReadCloser, error) { return NoBody, nil }
		}
	}
	return req, nil
}

8、以及 根据 读取器(*bufio.Reader),读取一个请求实例方法(readRequest内部方法实现),以及读取器的相关方法(新建一个最大字节的读取器,以及读取器结构体对象的读取关闭方法)

// ReadRequest 读取并解析来自Reader的传入请求
func ReadRequest(b *bufio.Reader) (*Request, error) {
	return readRequest(b, deleteHostHeader)
}

// readRequest的deleteHostHeader参数的常量
const (
	deleteHostHeader = true
	keepHostHeader   = false
)

// 从读取器中 解析出 请求*Request
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error) {
	// 1、将读取器转成 *textproto.Reader
	tp := newTextprotoReader(b)
	req = new(Request)

	// 2、读取器第一行:获取索引.htmlHTTP/1.0协议
	var s string
	if s, err = tp.ReadLine(); err != nil {
		return nil, err
	}
	defer func() {
		putTextprotoReader(tp)
		if err == io.EOF {
			err = io.ErrUnexpectedEOF
		}
	}()

	// 3、解析 第一行
	var ok bool
	req.Method, req.RequestURI, req.Proto, ok = parseRequestLine(s)
	if !ok {
		return nil, badStringError("malformed HTTP request", s)
	}
	// 4、检查解析结果
	if !validMethod(req.Method) {
		return nil, badStringError("invalid method", req.Method)
	}
	rawurl := req.RequestURI
	if req.ProtoMajor, req.ProtoMinor, ok = ParseHTTPVersion(req.Proto); !ok {
		return nil, badStringError("malformed HTTP version", req.Proto)
	}

	// 连接请求有两种不同的使用方式,都不使用完整的URL
	// 标准用法是通过HTTP代理来 贯穿HTTPS
	justAuthority := req.Method == "CONNECT" && !strings.HasPrefix(rawurl, "/")
	if justAuthority {
		rawurl = "http://" + rawurl
	}
	// 5、解析url
	if req.URL, err = url.ParseRequestURI(rawurl); err != nil {
		return nil, err
	}

	if justAuthority {
		// 把假“http://”去掉。
		req.URL.Scheme = ""
	}

	// 后续行:Key:value
	mimeHeader, err := tp.ReadMIMEHeader()
	if err != nil {
		return nil, err
	}
	req.Header = Header(mimeHeader)

	req.Host = req.URL.Host
	if req.Host == "" {
		req.Host = req.Header.get("Host")
	}
	if deleteHostHeader {
		delete(req.Header, "Host")
	}

	fixPragmaCacheControl(req.Header)

	req.Close = shouldClose(req.ProtoMajor, req.ProtoMinor, req.Header, false)

	err = readTransfer(req, b)
	if err != nil {
		return nil, err
	}

	if req.isH2Upgrade() {
		//因为它既不是块,也不是声明
		req.ContentLength = -1

		// 我们希望给处理程序一个劫持连接的机会,但是如果连接没有被劫持,我们需要阻止服务器进一步处理连接。设置为关闭以确保
		req.Close = true
	}
	return req, nil
}

最大字节读取器

// 新建一个最大字节的读取器:类似于io.LimitReader但旨在限制传入请求主体的大小
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser {
	return &maxBytesReader{w: w, r: r, n: n}
}

// 内部 最大字节 读取器结构体对象
type maxBytesReader struct {
	w   ResponseWriter
	r   io.ReadCloser // 底层读取器
	n   int64         // 剩余最大字节数
	err error         // 粘性错误
}

// 读取器 读取方法
func (l *maxBytesReader) Read(p []byte) (n int, err error) {
	if l.err != nil {
		return 0, l.err
	}
	if len(p) == 0 {
		return 0, nil
	}
	// 如果要求读取32KB字节,但只剩下5个字节,则无需读取32KB。6字节将回答我们是否达到极限或超过极限的问题
	if int64(len(p)) > l.n+1 {
		p = p[:l.n+1]
	}
	n, err = l.r.Read(p)

	if int64(n) <= l.n {
		l.n -= int64(n)
		l.err = err
		return n, err
	}

	n = int(l.n)
	l.n = 0

	// 服务器代码和客户端代码都使用maxBytesReader, 此“requestTooLarge”检查仅由服务器代码使用
	// 为了防止仅使用HTTP客户机代码(如cmd/go)的二进制文件也在HTTP服务器中链接,不要对服务器“*response”类型使用静态类型断言。改为检查此接口:
	type requestTooLarger interface {
		requestTooLarge()
	}
	if res, ok := l.w.(requestTooLarger); ok {
		res.requestTooLarge()
	}
	l.err = errors.New("http: request body too large")
	return n, l.err
}

// 读取器关闭方法
func (l *maxBytesReader) Close() error {
	return l.r.Close()
}

func copyValues(dst, src url.Values) {
	for k, vs := range src {
		dst[k] = append(dst[k], vs...)
	}
}

9、请求体的解析方法(如表单解析、多部件表单解析)
上传文件请求主要三步处理:
表单中增加enctype=“multipart/form-data”
服务器调用r.ParseMultipartForm,把上传的文件存储在内存和临时文件中
使用r.FormFile获取文件句柄,然后对文件进行存储等处理

第二步代码:ParseMultipartForm将请求主体解析为多部分/表单数据,把上传的文件存储在内存和临时文件中。当需要的时候,ParseMultipartForm会自动调用ParseForm。

// 传参:上传的文件存储在maxMemory大小的内存中(解析)
func (r *Request) ParseMultipartForm(maxMemory int64) error {
	if r.MultipartForm == multipartByReader {
		return errors.New("http: multipart handled by MultipartReader")
	}
	if r.Form == nil {
		err := r.ParseForm()
		if err != nil {
			return err
		}
	}
	if r.MultipartForm != nil {
		return nil
	}

	mr, err := r.multipartReader(false)
	if err != nil {
		return err
	}

	f, err := mr.ReadForm(maxMemory)
	if err != nil {
		return err
	}

	if r.PostForm == nil {
		r.PostForm = make(url.Values)
	}
	for k, v := range f.Value {
		r.Form[k] = append(r.Form[k], v...)
		// r.PostForm 也应该被填充
		r.PostForm[k] = append(r.PostForm[k], v...)
	}

	r.MultipartForm = f

	return nil
}

// 内部方法:解析请求的post表格  成 Values
func parsePostForm(r *Request) (vs url.Values, err error) {
	if r.Body == nil {
		err = errors.New("missing form body")
		return
	}
	ct := r.Header.Get("Content-Type")
	if ct == "" {
		ct = "application/octet-stream"
	}

	// 根据表格不同类型解析
	ct, _, err = mime.ParseMediaType(ct)
	switch {
	case ct == "application/x-www-form-urlencoded":
		var reader io.Reader = r.Body
		maxFormSize := int64(1<<63 - 1)
		if _, ok := r.Body.(*maxBytesReader); !ok {
			maxFormSize = int64(10 << 20) // 10 MB is a lot of text.
			reader = io.LimitReader(r.Body, maxFormSize+1)
		}
		b, e := ioutil.ReadAll(reader)
		if e != nil {
			if err == nil {
				err = e
			}
			break
		}
		if int64(len(b)) > maxFormSize {
			err = errors.New("http: POST too large")
			return
		}
		vs, e = url.ParseQuery(string(b))
		if err == nil {
			err = e
		}
	case ct == "multipart/form-data":
		// 由ParseMultipartForm处理
	}
	return
}

第二步代码:解析后生成的文的对象和句柄(解析后)
FormFile 返回所提供表单里键为key的第一个文件。查询的request.MultipartForm,是已经解析好了的多部件表单,包括上传的文件,因此该查询函数只有在调用ParseMultipartForm后才有效。 当然,如果必要,本函数会隐式调用ParseMultipartForm和ParseForm。查询失败会返回ErrMissingFile错误。

// 返回值一是文件对象,二是文件句柄是(*multipart.FileHeader类型,可通过句柄获取 上传文件的Header和文件名,可用于打开并读取文件,)
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error) {
	if r.MultipartForm == multipartByReader {
		return nil, nil, errors.New("http: multipart handled by MultipartReader")  // Reader是MIME的multipart主体所有记录的迭代器。Reader的底层会根据需要解析输入,不支持Seek
	}
	if r.MultipartForm == nil {
		err := r.ParseMultipartForm(defaultMaxMemory)
		if err != nil {
			return nil, nil, err
		}
	}
	if r.MultipartForm != nil && r.MultipartForm.File != nil {
		if fhs := r.MultipartForm.File[key]; len(fhs) > 0 {
			f, err := fhs[0].Open()
			return f, fhs[0], err
		}
	}
	return nil, nil, ErrMissingFile
}

请求体实例对象的 成员填充:r.Form和r.PostForm
对于POST、PUT和PATCH请求,它还读取请求主体,将其解析为表单,并将结果放入r.PostForm和r.form中。请求主体参数优先于r.Form中的URL查询字符串值。
如果请求主体的大小尚未受到MaxBytesReader的限制,那么大小将被限制为10MB。
对于其他HTTP方法,或者当内容类型不是application/x-www-form-urlencoded时,不读取请求正文,并且r.PostForm被初始化为非nil的空值。
获取其他非文件字段信息的时候就不需要调用r.ParseForm,因为在需要的时候Go自动会去调用

func (r *Request) ParseForm() error {
	var err error
	// 1、如果请求方式是post、put、patch则调用内部 parsePostForm 解析 表格数据方法
	if r.PostForm == nil {
		if r.Method == "POST" || r.Method == "PUT" || r.Method == "PATCH" {
			r.PostForm, err = parsePostForm(r)
		}
		// 2、初始化请求地址的值给 PostForm 字段
		if r.PostForm == nil {
			r.PostForm = make(url.Values)
		}
	}
	// 3、如果 解析的表单数据 是空的
	if r.Form == nil {
		// 但是 来自PATCH、POST或PUT body参数的解析表单数据 不为空
		if len(r.PostForm) > 0 {
			// 则 初始化请求地址的值给 Form 字段,然后把 PostForm 复制到Form
			r.Form = make(url.Values)
			copyValues(r.Form, r.PostForm)
		}
		var newValues url.Values
		// 4、当存在URL时,解析URL,然后赋值给Form
		if r.URL != nil {
			var e error
			newValues, e = url.ParseQuery(r.URL.RawQuery)
			if err == nil {
				err = e
			}
		}
		if newValues == nil {
			newValues = make(url.Values)
		}
		if r.Form == nil {
			r.Form = newValues
		} else {
			copyValues(r.Form, newValues)
		}
	}
	return err
}

其他方法

// FormValue 返回查询的命名组件的第一个值
func (r *Request) FormValue(key string) string {
	if r.Form == nil {
		r.ParseMultipartForm(defaultMaxMemory)
	}
	if vs := r.Form[key]; len(vs) > 0 {
		return vs[0]
	}
	return ""
}

// PostFormValue 返回POST、PATCH或PUT请求主体的命名组件的第一个值。URL查询参数被忽略。
// 在必要时调用ParseMultipartForm和ParseForm,并忽略这些函数返回的任何错误。
func (r *Request) PostFormValue(key string) string {
	if r.PostForm == nil {
		r.ParseMultipartForm(defaultMaxMemory)
	}
	if vs := r.PostForm[key]; len(vs) > 0 {
		return vs[0]
	}
	return ""
}

补充第6点:请求对象的其他字段 如协议获取、协议解析、设置、获取身份认证方法

// 报告 请求 是否表示http2“client preface”魔术字符串。
func (r *Request) isH2Upgrade() bool {
	return r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0"
}

// value 非空返回value,否则返回def
func valueOrDefault(value, def string) string {
	if value != "" {
		return value
	}
	return def
}

// 注意:这不是为了反映实际使用的Go版本。 它在Go1.1发布时被更改,因为以前的用户代理最终被一些入侵检测系统阻止。
const defaultUserAgent = "Go-http-client/1.1"

// requestBodyReadError包装来自(*Request)的错误。write表示错误来自 Request.Body. 此错误类型不应将net/http包转义给用户。
type requestBodyReadError struct{ error }

// 内部方法:验证是否是isASCII
func idnaASCII(v string) (string, error) {
	// TODO: 请考虑在验证性能正常后删除此检查。
	// 现在,punycode验证、长度检查、上下文检查和允许的字符测试都被省略了。
	// 它还可以防止ToASCII调用在可能的情况下挽救无效的IDN。 因此,可能有两个idn与用户看起来相同,其中仅ASCII版本会导致下游错误,而非ASCII版本不会。
	// 注意,对于正确的ASCII-IDNs-ToASCII只会做更多的工作,但不会导致分配。
	if isASCII(v) {
		return v, nil
	}
	return idna.Lookup.ToASCII(v)
}

// 内部清除请求的主机头中发送的主机方法,会去掉“/”或“”之后的任何内容
func cleanHost(in string) string {
	if i := strings.IndexAny(in, " /"); i != -1 {
		in = in[:i]
	}
	host, port, err := net.SplitHostPort(in)
	if err != nil { // input was just a host
		a, err := idnaASCII(in)
		if err != nil {
			return in // garbage in, garbage out
		}
		return a
	}
	a, err := idnaASCII(host)
	if err != nil {
		return in // garbage in, garbage out
	}
	return net.JoinHostPort(a, port)
}

// removeZone 从主机中删除IPv6区域标识符。 列如:"[fe80::1%en0]:8080" to "[fe80::1]:8080"
func removeZone(host string) string {
	if !strings.HasPrefix(host, "[") {
		return host
	}
	i := strings.LastIndex(host, "]")
	if i < 0 {
		return host
	}
	j := strings.LastIndex(host[:i], "%")
	if j < 0 {
		return host
	}
	return host[:j] + host[i:]
}

// 解析为HTTP版本字符方法。/“HTTP/1.0”返回(1,0,true)
func ParseHTTPVersion(vers string) (major, minor int, ok bool) {
	const Big = 1000000 // arbitrary upper bound
	switch vers {
	case "HTTP/1.1":
		return 1, 1, true
	case "HTTP/1.0":
		return 1, 0, true
	}
	if !strings.HasPrefix(vers, "HTTP/") {
		return 0, 0, false
	}
	dot := strings.Index(vers, ".")
	if dot < 0 {
		return 0, 0, false
	}
	major, err := strconv.Atoi(vers[5:dot])
	if err != nil || major < 0 || major > Big {
		return 0, 0, false
	}
	minor, err = strconv.Atoi(vers[dot+1:])
	if err != nil || minor < 0 || minor > Big {
		return 0, 0, false
	}
	return major, minor, true
}

// 内部 检查是否有效请求方法
func validMethod(method string) bool {
	/*
	     Method         = "OPTIONS"                ; Section 9.2
	                    | "GET"                    ; Section 9.3
	// ...省略
	   extension-method = token
	     token          = 1*<any CHAR except CTLs or separators>
	*/
	return len(method) > 0 && strings.IndexFunc(method, isNotToken) == -1
}
// 如果请求使用HTTP基本身份验证,则BasicAuth返回请求的授权标头中提供的用户名和密码。
// 是 parseBasicAuth()解析auth的包装
func (r *Request) BasicAuth() (username, password string, ok bool) {
	// 1、获取请求头的授权信息(主要用于证明客户端有权查看某个资源)
	auth := r.Header.Get("Authorization")
	if auth == "" {
		return
	}
	return parseBasicAuth(auth)
}

// parseBasicAuth解析HTTP基本身份验证字符串。
func parseBasicAuth(auth string) (username, password string, ok bool) {
	const prefix = "Basic "
	// 不区分大小写的前缀匹配
	if len(auth) < len(prefix) || !strings.EqualFold(auth[:len(prefix)], prefix) {
		return
	}
	c, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
	if err != nil {
		return
	}
	cs := string(c)
	s := strings.IndexByte(cs, ':')
	if s < 0 {
		return
	}
	return cs[:s], cs[s+1:], true
}

// SetBasicAuth 将请求的授权头设置为使用提供的用户名和密码进行HTTP基本身份验证。
// 使用HTTP基本身份验证,提供的用户名和密码不会加密
// 某些协议可能会对用户名和密码的预转义提出附加要求。 例如,当与OAuth2一起使用时,这两个参数必须首先用URL编码url.QueryEscape
func (r *Request) SetBasicAuth(username, password string) {
	r.Header.Set("Authorization", "Basic "+basicAuth(username, password))
}

// parseRequestLine 将“GET/fooHTTP/1.1”解析为三个部分。
func parseRequestLine(line string) (method, requestURI, proto string, ok bool) {
	s1 := strings.Index(line, " ")
	s2 := strings.Index(line[s1+1:], " ")
	if s1 < 0 || s2 < 0 {
		return
	}
	s2 += s1 + 1
	return line[:s1], line[s1+1 : s2], line[s2+1:], true
}

var textprotoReaderPool sync.Pool

func newTextprotoReader(br *bufio.Reader) *textproto.Reader {
	if v := textprotoReaderPool.Get(); v != nil {
		tr := v.(*textproto.Reader)
		tr.R = br
		return tr
	}
	return textproto.NewReader(br)
}

func putTextprotoReader(r *textproto.Reader) {
	r.R = nil
	textprotoReaderPool.Put(r)
}

其他


// 预期继续 方法(使用 token实现)
func (r *Request) expectsContinue() bool {
	return hasToken(r.Header.get("Expect"), "100-continue")
}

// 保持连接方法:(使用 token实现), HTTP 1.1默认进行持久连接,如果客户端再次访问这个服务器上的 网页,会继续使用这一条已经建立的连接
func (r *Request) wantsHttp10KeepAlive() bool {
	if r.ProtoMajor != 1 || r.ProtoMinor != 0 {
		return false
	}
	return hasToken(r.Header.get("Connection"), "keep-alive")
}

func (r *Request) wantsClose() bool {
	if r.Close {
		return true
	}
	return hasToken(r.Header.get("Connection"), "close")
}

func (r *Request) closeBody() {
	if r.Body != nil {
		r.Body.Close()
	}
}

// 是否 可重播
func (r *Request) isReplayable() bool {
	if r.Body == nil || r.Body == NoBody || r.GetBody != nil {
		switch valueOrDefault(r.Method, "GET") {
		case "GET", "HEAD", "OPTIONS", "TRACE":
			return true
		}
		// 幂等键虽然是非标准的,但被广泛用于表示POST或其他请求是幂等
		if r.Header.has("Idempotency-Key") || r.Header.has("X-Idempotency-Key") {
			return true
		}
	}
	return false
}

// 检查此传出(客户端)请求的内容长度。
func (r *Request) outgoingLength() int64 {
	if r.Body == nil || r.Body == NoBody {
		return 0
	}
	if r.ContentLength != 0 {
		return r.ContentLength
	}
	return -1
}

// requestMethodUsuallyLacksBody 告给定的请求方法是否通常不涉及请求主体
func requestMethodUsuallyLacksBody(method string) bool {
	switch method {
	case "GET", "HEAD", "DELETE", "OPTIONS", "PROPFIND", "SEARCH":
		return true
	}
	return false
}

// requiresHTTP1报告此请求是否需要通过HTTP/1连接发送。
func (r *Request) requiresHTTP1() bool {
	return hasToken(r.Header.Get("Connection"), "upgrade") &&
		strings.EqualFold(r.Header.Get("Upgrade"), "websocket")
}

实际应用
构建 Post请求:指定url,其他成员自定义赋值配置

func Post(url string) *HttpRequest {
	var req http.Request
	req.Method = "POST"
	req.Header = http.Header{}
	return &HttpRequest{url, &req, map[string]string{}, 60 * time.Second, 60 * time.Second, nil, nil, nil}
}

自定义req请求体,其中包含了http库的请求体请求体 *http.Request 。该自定义结构体对象里的成员是小写,外部不可以直接调用赋值,需要通过方法设置

type HttpRequest struct {
	url              string		        // http.url是URl类型,这里单独拿出来 自定义成了string,可由后端自己传入
	req				 *http.Request		// 自带
	params			 map[string]string	// 自定义参数(组合后还是要传给req)
	connectTimeout   time.Duration		// 拨号超时(用于客户端拨号时的trans)
	readWriteTimeout time.Duration		// 截止时间(用于客户端拨号时的trans)
	tlsClientConfig	 *tls.Config		//配置TLS客户机或服务器,用于transport 为空时创建
	proxy			 func(r *http.Request) (*url.URL, error)  // 代理,用于transport 为空时创建
	transport		 http.RoundTripper		// 表示执行单个HTTP事务的能力,从而获得给定请求的响应(客户端拨号时带有的trans)
}

设置header

func (b *HttpRequest) Header(key, value string) *HttpRequest  {
	// 调用req直接设置内部的header
	b.req.Header.Set(key, value)
	return b
}

设置请求体数据

// 支持传入string参数和[]byte参数
func (b *HttpRequest) Body(data interface{}) *HttpRequest  {
	// 根据不同 请求体类型
	switch t := data.(type) {
	case string:
		bf := bytes.NewBufferString(t) // 创建并初始化一个新的缓冲区,用t做初始值
		b.req.Body= ioutil.NopCloser(bf)         // 原body是io.ReadCloser类型,将data转成该类型(NopCloser返回一个ReadCloser)
		b.req.ContentLength = int64(len(t))
	case []byte:
		bf := bytes.NewBuffer(t)
		b.req.Body = ioutil.NopCloser(bf)
		b.req.ContentLength = int64(len(t))
	}
	return b
}