这一期我们讲讲 Go Web 中的 ​​Request​​ ,关于 HTTP 请求(Request)和响应(Response)消息的结构这里简单提一下,毕竟是计算机网络的知识了(以后有时间再补充计算机网络这方面的内容),它们都具有相同的结构:

  • 请求(或者响应)行
  • 零个或多个头部(Header)
  • 一个空行
  • 可选的消息体(Body)

Request 结构体




在 Go 中的 net/http 包提供了用于表示 HTTP 消息的结构,例如 HTTP 请求结构 ​​Request​​​ ,它是一个结构体,代表了客户端发送的 HTTP 请求消息,下面是截取出来的 ​​Request​​ 结构体的定义:

type Request struct {
Method string
URL *url.URL

Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0

Header Header
Body io.ReadCloser
GetBody func() (io.ReadCloser, error)
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
Cancel <-chan struct{}
Response *Response
ctx context.Context
}

其中几个常用的比较重要的字段有 ​​URL​​​ 、 ​​Header​​​ 、 ​​Body​​​ 、 ​​Form​​​ 这些,当然我们也可以通过 ​​Request​​​ 的方法访问请求中的 ​​Cookie​​​ 、 ​​User Agent​​ 等信息。


Request 中的 URL



Request 中的 ​​URL​​​ 字段就代表了请求行里面的部分内容,查看源码我们知道 URL 字段是指向 ​​url.URL​​​ 类型的一个指针,而 ​​url.URL​​ 是一个结构体:

type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port
Path string // path (relative paths may omit leading slash)
RawPath string // encoded path hint (see EscapedPath method)
ForceQuery bool // append a query ('?') even if RawQuery is empty
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
RawFragment string // encoded fragment hint (see EscapedFragment method)
}

相对应的 URL 通用格式为:

scheme://[userinfo@]host/path[?query][#fragment]

或者

scheme:opaque[?query][#fragment]

URL 查询字符串由 ​​RawQuery​​​ 字段表示,其提供实际查询的字符串。例如:​​http://localhost:8080/post?a_id=1&b_id=2​​​ ,它的 ​​RawQuery​​​ 的值就是 ​​a_id=1&b_id=2​​ 。

URL 中的 ​​Query​​​ 方法提供查询字符串对应的 ​​map[string][]string​​ 。例如:

package main

import (
"log"
"net/http"
)

func main() {
server := http.Server{
Addr: "localhost:8080",
Handler: nil,
}

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
url := r.URL
query := url.Query()

aId := query["a_id"]
log.Println(aId)

bId := query["b_id"]
log.Println(bId)

cId := query.Get("c_id")
log.Println(cId)
})

server.ListenAndServe()
}

运行后访问 ​​http://localhost:8080/?a_id=12&b_id=56&c_id=89​​ 控制台会输出如下:

[12]
[56]
89

对于 URL 中的 ​​Fragment​​​ 字段来说,从浏览器发出的请求,我们无法提取出 ​​Fragment​​​ 字段的值,因为浏览器在发送请求时会把 ​​Fragment​​ 部分去掉,但注意,不是所有的请求都是从浏览器发出的。


Request 中的 Header




HTTP 请求和响应的 headers 是通过 ​​Header​​​ 类型来描述的,它是一个 ​​map​​​ ,用来表述 HTTP Header 里的 Key-Value 对。Header map 的 key 是 ​​string​​​ 类型,value 是 ​​[]string​​​ 。设置 key 的时候会创建一个空的 ​​[]string​​​ 作为 value ,value 里面第一个元素就是新 header 的值。执行 ​​append​​ 操作可以为指定的 key 添加一个新的 header 值。

通过 ​​r.Header​​​ 能获取到 ​​map​​ 。

例如:r.Header["Accept-Encoding"]

返回:[gzip, deflate]([]string 类型)

例如:r.Header.Get("Accept-Encoding")

返回:gzip, deflate(string 类型)

其中 r 为 *http.Request 类型。

例如:

package main

import (
"fmt"
"net/http"
)

func main() {
server := http.Server{
Addr: "localhost:8080",
}

http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.Header)
fmt.Fprintln(w, r.Header["Accept-Encoding"]) // [gzip, deflate, br]
fmt.Fprintln(w, r.Header.Get("Accept-Encoding")) // gzip, deflate, br
})

server.ListenAndServe()
}

使用浏览器访问 ​​http://localhost:8080/test​​ 会看到类似下面的输出:

map[Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9] Accept-Encoding:[gzip, deflate, br] Accept-Language:[zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6] Connection:[keep-alive] (后面省略)]
[gzip, deflate, br]
gzip, deflate, br

Request 中的 Body




请求和响应的 bodies 都是使用 ​​Body​​​ 字段来表示的, ​​Body​​​ 是一个 ​​io.ReadCloser​​ 接口:

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
Reader
Closer
}

其中, ​​ReadCloser​​​ 接口包含一个 ​​Reader​​​ 接口和一个 ​​Closer​​ 接口:

type Reader interface {
Read(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}

通过上面的源码,我们知道 ​​Reader​​​ 接口定义了一个 ​​Read​​​ 方法,其中的参数为 ​​[]byte​​​ ,返回 byte 的数量以及可选的错误。而 ​​Closer​​​ 接口定义了一个 ​​Close​​ 方法,该方法没有参数,返回可选的错误。

如果想要读取请求 body 的内容,可以调用 Body 的 ​​Read​​ 方法。