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)
}