Go Web 学习笔记(四)路由处理
Go Web 路由处理
路由是一个过程,指的是一个http请求。而路由处理就是针对发来的 HTTP 请求进行响应的过程。
Go 语言中的 net/http
包提供了一系列用于表示 HTTP 报文的结构体,可以用于处理请求和发送响应。
net/http
包中的 Request 结构体代表了客户端发送的请求报文。其中可以获取到一些有关请求的信息:
type Request struct {
// Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
Method string
// URL在服务端表示被请求的URI,在客户端表示要访问的URL。
//
// 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
// 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
// (参见RFC 2616, Section 5.1.2)
//
// 在客户端,URL的Host字段指定了要连接的服务器,
// 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
URL *url.URL
// 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// Connection: keep-alive
// 则:
// Header = map[string][]string{
// "Accept-Encoding": {"gzip, deflate"},
// "Accept-Language": {"en-us"},
// "Connection": {"keep-alive"},
// }
// HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
// 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
Header Header
// Body是请求的主体。
//
// 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
// Client的Transport字段会负责调用Body的Close方法。
//
// 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
// Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
Body io.ReadCloser
// ContentLength记录相关内容的长度。
// 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
// 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
ContentLength int64
// TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
// 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
TransferEncoding []string
// Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
Close bool
// 在服务端,Host指定URL会在其上寻找资源的主机。
// 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
// Host的格式可以是"host:port"。
//
// 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
// 如过该字段为"",Request.Write方法会使用URL字段的Host。
Host string
// Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
// 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
Form url.Values
// PostForm是解析好的POST或PUT的表单数据。
// 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
PostForm url.Values
// MultipartForm是解析好的多部件表单,包括上传的文件。
// 本字段只有在调用ParseMultipartForm后才有效。
// 在客户端,会忽略请求中的本字段而使用Body替代。
MultipartForm *multipart.Form
// Trailer指定了会在请求主体之后发送的额外的头域。
//
// 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
// (客户端会声明哪些trailer会发送)
// 在处理器从Body读取时,不能使用本字段。
// 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
// (如果客户端发送了这些键值对),此时才可以访问本字段。
//
// 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
// ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
// 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
// 一旦请求主体返回EOF,调用者就不可再修改Trailer。
//
// 很少有HTTP客户端、服务端或代理支持HTTP trailer。
Trailer Header
// RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
// 本字段不是ReadRequest函数填写的,也没有定义格式。
// 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
// 客户端会忽略请求中的RemoteAddr字段。
RemoteAddr string
// RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
// (参见RFC 2616, Section 5.1)
// 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
RequestURI string
// TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
// 本字段不是ReadRequest函数填写的。
// 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
// 客户端会忽略请求中的TLS字段。
TLS *tls.ConnectionState
}
获取请求信息
获取请求URL
Request 结构中的 URL 字段用于表示请求行中包含的 URL,该字段是一个指向 url.URL
结构的指针:
type URL struct {
Scheme string
Opaque string // 编码后的不透明数据
User *Userinfo // 用户名和密码信息
Host string // host或host:port
Path string // 查询的路径信息,在HandlerFunc中设置的
RawQuery string // 编码后的查询字符串,没有'?'
Fragment string // 引用的片段(文档位置),没有'#'
}
URL 类型代表一个解析后的 URL:
scheme://[userinfo@]host/path[?query][#fragment]
scheme 后不是冒号加双斜线的 URL 被解释为如下格式:
scheme:opaque[?query][#fragment]
在处理器函数中获取请求 URL:
// 创建测试请求信息处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
// 获取请求路径
fmt.Fprintln(w, "你发送的请求的请求地址是:", r.URL.Path)
// 获取请求原始参数
fmt.Fprintln(w, "你发送的请求的请求地址后的查询字符串是:", r.URL.RawQuery)
}
获取请求头信息
请求头的信息可以使用 Request 结构对应的 Header 字段进行获取,返回结果是一个 map(可以对map进行指定键的值获取):
// 创建测试请求信息处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
// 获取所有的请求头信息
fmt.Fprintln(w, "请求头中的所有信息是:", r.Header)
// 获取请求头中的指定信息
fmt.Fprintln(w, "请求头中的User-Agent信息是:", r.Header["User-Agent"])
// 获取请求头中的指定信息Get方式
fmt.Fprintln(w, "请求头中的User-Agent信息是:", r.Header.Get("User-Agent"))
}
获取请求体信息
请求体的信息可以使用 Request 结构对应的 Body 字段来进行获取,只是在获取的过程中需要使用流的方式进行读取:
// 创建测试请求信息处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
// 获取请求体中内容的长度
length := r.ContentLength
// 创建一个指定长度的切片
body := make([]byte, length)
// 读取body中的全部信息(以流的方式读取,读取之后请求体中的数据被清空,导致后续r.PostForm获取不到数据,bug处理)
r.Body.Read(body)
fmt.Fprintln(w, "请求体中的内容是:", string(body))
}
获取请求参数
net/http
包中的 Request 结构中的字段提供了获取请求 URL 后面的请求参数以及form表单中提交的请求参数。
form 表单中的字段的类型是 url.Values
类型,对应的是 Form 字段:
Form 字段只有在调用 Request 的 ParseForm 方法之后才有效,在客户端,会忽略请求中的本字段而使用 Body 替代。
// 创建测试请求信息处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
// 获取get请求参数
// 解析请求数据,在获取r.Form以及r.PostForm之前必须执行该方法
r.ParseForm()
fmt.Fprintln(w, "get请求参数是:", r.Form)
// 获取post请求参数
fmt.Fprintln(w, "post请求参数是:", r.PostForm)
// 直接调用 FormValue 方法和 PostFormValue 获取请求参数
fmt.Fprintln(w, "url中的user参数的值是:", r.FormValue("user"))
fmt.Fprintln(w, "form表单中的username的值是:", r.PostFormValue("username"))
}
响应请求数据
在处理请求的过程中需要向客户端响应对应的数据,这里就需要使用 http.ResponseWriter
(ResponseWriter 接口被 HTTP 处理器用于构造 HTTP 回复。):
ResponseWriter结构体:
type ResponseWriter interface {
// Header返回一个Header类型值,该值会被WriteHeader方法发送。
// 在调用WriteHeader或Write方法后再改变该对象是没有意义的。
Header() Header
// WriteHeader该方法发送HTTP回复的头域和状态码。
// 如果没有被显式调用,第一次调用Write时会触发隐式调用WriteHeader(http.StatusOK)
// WriterHeader的显式调用主要用于发送错误码。
WriteHeader(int)
// Write向连接中写入作为HTTP的一部分回复的数据。
// 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)
// 如果Header中没有"Content-Type"键,
// 本方法会使用包函数DetectContentType检查数据的前512字节,将返回值作为该键的值。
Write([]byte) (int, error)
}
响应文本数据
响应文本数据只需要简单的返回对应的字符串即可:
// 创建测试文本响应处理器函数
func testTexRes(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("已收到该请求"))
}
响应 html 界面:
支持响应一个 html 页面(非模版):
// 创建测试html响应处理器函数
func testHtmlRes(w http.ResponseWriter, r *http.Request) {
html := `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/hello?user=root&pwd=123456" method="post" >
用户名:<input type="text" name="username" /> <br/>
密码:<input type="password" name="password" /> <br/>
<input type="submit" value="登陆"/>
</form>
</body>
</html>`
w.Write([]byte(html))
}
响应 json 数据
// 创建测试json响应处理器函数
func testJsonRes(w http.ResponseWriter, r *http.Request) {
// 设置响应类型
w.Header().Set("Content-Type", "application/json")
// 创建User类型
user := model.User{
Id: 1,
Username: "admin",
Password: "123",
Email: "admin@test.com",
}
// 将user转换为json格式
json, _ := json.Marshal(user)
w.Write(json)
}
客户端重定向
利用 HTTP Header 中的 Location 字段以及状态码可以实现重定向的功能。
// 创建测试重定向处理器函数
func testRedirect(w http.ResponseWriter, r *http.Request) {
// 设置响应头中的信息(必须在WriteHeader之前设置)
w.Header().Set("Location", "https://www.baidu.com")
// 设置响应状态吗
w.WriteHeader(302)
}