关于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
}