简介
Go语音的net/http包提供了一系列用于表示HTTP报文的结构,可以使用它处理请求和发送响应,其中Request结构代表了客户端发送的请求报文,下面是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
}
Request类型代表一个服务端接受到的或者客户端发送出去的HTTP请求。
Request各字段的意义和用途在服务端和客户端是不同的。除了字段本身上方文档,还可参见Request.Write方法和RoundTripper接口的文档
一.获取请求url
Request结构体中的URL字段用于表示请求行中包含的URL,该字段是一个指向url.URL结构的指针,下面是URL结构讲解
import "net/url"
url包解析URL并实现了查询的逸码
(1).Path字段
* 获取请求的URL
* eg:http:localhost:8080/hello?username=admin&password=123456, 通过r.URL.Path 只能得到/hello
(2).RawQuery字段
* 获取请求的URL后面?后面的查询字符串
* eg:http:localhost:8080/hello?username=admin&password=123456, 通过r.URL.RawQuery得到的是 username=admin&password=123456
二.获取请求头中的信息
通过Request结果中的Header字段来获取请求头中的所有信息,Header字段的类型是Header类型,而Header类型是一个map[string][]string,下面是Header类型及它的方法
三.获取请求体中的信息
请求和响应的主体都是有Request结构中的body字段表示,这个字段的类型是io.ReadCloser接口,该接口包含了Reader接口和Closer接口,Reader接口拥有Read方法,Closer接口拥有Close方法
(1).由于GET请求没有请求体,所以需要在HTML页面创建一个form表单,通过指定method="post"来发送一个POST请求
form.html提交如下:
<form action="http://localhost:8080/getBody" method="POST">
用户名:<input type="text" name="username" value="admin"><br/>
密码:<input type="password" name="password" value="123456"><br/>
<input type="提交">
</form>
main.go获取客户端提交的信息,并解析如下:
func handler(w http.ResponseWriter, r *http.Request) {
//获取内容长度
length := r.ContentLenth
//创建一个切片
body := make([]byte, length)
//读取请求体
r.Body.Read(body)
fmt.Fprintln(w, "请求体中的内容是:", string(body))
}
四.获取请求参数
通过net/http库中的Request结构的字段以及方法来获取请求URL后面的请求参数以及form表单提交的请求参数
1.Form字段
(1).类型是url.Values类型,Form是解析好的表单数据,包括URL字段的query参数和POST或PUT表单数据
包net/url下url.Values
(2).Form字段只有在调用Request的ParseForm后才有效,在客户端,会忽略请求中的本字段而使用Body替代
代码:
func handler(w http.ResponseWriter, r *http.Request) {
//解析表单
r.ParseForm()
//获取请求参数
fmt.Fprintln(w, "请求参数:", r.Form)
}
注意:在执行r.Form之前一定要调用r.ParseForm方法对表单进行解析
结果:请求参数为:map[password:[123456]] username:[admin]]
2. 如果对form表单做一些修改,在action属性的URL后面也添加相同的请求参数
如果对form表单做一些修改,在action属性的URL后面也添加相同的请求参数,如下:
action="http://localhost:8080/getBody?pwd=123456&username=zhangsan"
则执行结果如下:
map[password:[123456]] pwd:[123456] username:[admin zhangsan]]
可以发现:
* 表单中的请求参数 username 和 URL 中的请求参数 username 都获取到了,而且表单中的请求参数的值排在 URL 请求参数值的前面
* 如果此时只想获取表单中的请求参数该怎么办呢?
* 那就需要使用 Request 结构中的PostForm字段,但是PostForm字段只支持 application/x-www-form-urlencoded编码,如果 form 表单的enctype属性值为 multipart/form-data,那么使用PostForm字段无法获取表单中的数据,此时需要使用MultipartForm字段
* 说明: form 表单的enctype属性默认值为application/x-www-form-urlencoded编码.,实现上传文件时需要将该属性的值设置为multipart/form-data编码格式
3.FormValue方法和PostFormValue方法
代码如下:
package main
import (
"fmt"
"net/http"
)
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintln(w, "请求地址:", r.URL.Path)
_, err = fmt.Fprintln(w, "请求地址中后面的查询字符串:", r.URL.RawQuery)
_, err = fmt.Fprintln(w, "请求地址中请求头数据:", r.Header)//返回map[string][][string]
_, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的信息:", r.Header["Accept-Encoding"]) //返回map切片
_, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的属性值:", r.Header.Get("Accept-Encoding"))//返回string
获取请求体中内容长度
//len := r.ContentLength
创建byte切片
//body := make([]byte, len)
将请求体中的内容读取到byte切片中
//_, err = r.Body.Read(body)
浏览器中显示请求体中的内容
//_, err = fmt.Fprintln(w, "浏览器中显示请求体中的内容:",string(body))
//在解析表单r.Form之前,需执行以下方法
//err = r.ParseForm()
如果form表单的action属性值的地址中也有与form表单参数名相同的请求参数,那么参数值都可以得到
并且form表单中的参数值会在url参数值的前面
//_, err = fmt.Fprintln(w, "请求表单请求参数:", r.Form)
//_, err = fmt.Fprintln(w, "post请求中Form表单请求参数:", r.PostForm)
//通过直接调用FormValue,PostFormValue直接获取请求参数中的值
_, err = fmt.Fprintln(w, "Url中username请求参数的值:", r.FormValue("username"))
_, err = fmt.Fprintln(w, "post请求中Form表单name请求参数:", r.PostFormValue("name"))
if err != nil {
fmt.Println(err)
}
}
func main() {
http.HandleFunc("/req", handler)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
}
}
<html>
<head>
<meta charset="UTF-8">
</head>
</head>
<body>
<form action="http://127.0.0.1:8080/req?username=张三" method="post">
用户名:<input type="text" name="name"/><br/>
用密码:<input type="text" name="password"/><br/>
<input type="submit">
</form>
</body>
</html>
五.给客户端响应
前面讲解了如何使用处理器中的*http.Request处理用户的请求,下面说一下如何使用http.ResponseWriter来给用户响应
1.给客户端响应一个字符串
(1).处理器中的代码
func handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("你的请求服务端已收到"))
}
(2).浏览器中的结果
你的请求服务端已收到
(3).响应报文的内容
HTTP/1.1 200 OK
Date:Fri, 10 Aug 2022 01:01:01 GMT
Content-Length: 27
Content-Type: text/plain; charset=utf-8
2.给客户端响应一个HTML页面
(1).处理器中的代码
func handler(w http.ResponseWriter, r *http.Request) {
html := `<html>
<head>
<title>测试响应内容为网页</title>
<meta charset="utf-8"/>
</head>
<body>
以网页形式响应
</body>
</html>`
w.Write([]byte(html))
}
(2).浏览器中的结果
以网页形式响应
通过在浏览器中右键->查看网页代码可以发现:有一个html页面
(3).响应报文中的内容
HTTP/1.1 200 OK
Date:Fri, 10 Aug 2022 01:01:01 GMT
Content-Length: 199
Content-Type: text/html; charset=utf-8
3.给客户端响应JSON格式的数据
(1).处理器中的代码
func handler(w http.ResponseWriter, r *http.Request) {
//设置响应头中的内容类型
w.Header().Set("Content-Type", "application/json")
user := User{
ID: 1,
Username: "张三",
Password: "123456",
}
//将user转换成JSON格式
json, _ := json.Marshal(user)
w.Write(json)
}
(2).浏览器中的结果
{"ID":1, "Username":"张三","Password":"123456"}
(3).响应报文中的内容
HTTP/1.1 200 OK
Date:Fri, 10 Aug 2022 01:01:01 GMT
Content-Length: 199
Content-Type: application/json
4.让客户端重定向
(1).处理器中的代码
func handler(w http.ResponseWriter, r *http.Request) {
//以下操作必须在WriteHeader之前进行
w.Header().Set("Location", "https://www.baidu.com")
w.WriteHeader(302)
}
(2).响应报文中的内容
HTTP/1.1 200 OK
Location: https://www.baidu.com
Date:Fri, 10 Aug 2022 01:01:01 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8
以上案例完整代码如下:
package main
import (
"encoding/json"
"fmt"
"go_code/web_app/sql/model"
"net/http"
)
//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request) {
_, err := fmt.Fprintln(w, "请求地址:", r.URL.Path)
_, err = fmt.Fprintln(w, "请求地址中后面的查询字符串:", r.URL.RawQuery)
_, err = fmt.Fprintln(w, "请求地址中请求头数据:", r.Header)//返回map[string][][string]
_, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的信息:", r.Header["Accept-Encoding"]) //返回map切片
_, err = fmt.Fprintln(w, "请求地址中请求头Accept-Encoding的属性值:", r.Header.Get("Accept-Encoding"))//返回string
获取请求体中内容长度
//len := r.ContentLength
创建byte切片
//body := make([]byte, len)
将请求体中的内容读取到byte切片中
//_, err = r.Body.Read(body)
浏览器中显示请求体中的内容
//_, err = fmt.Fprintln(w, "浏览器中显示请求体中的内容:",string(body))
//在解析表单r.Form之前,需执行以下方法
//err = r.ParseForm()
如果form表单的action属性值的地址中也有与form表单参数名相同的请求参数,那么参数值都可以得到
并且form表单中的参数值会在url参数值的前面
//_, err = fmt.Fprintln(w, "请求表单请求参数:", r.Form)
//_, err = fmt.Fprintln(w, "post请求中Form表单请求参数:", r.PostForm)
//
//通过直接调用FOrmValue,PostFormValue直接获取请求参数中的值
_, err = fmt.Fprintln(w, "Url中username请求参数的值:", r.FormValue("username"))
_, err = fmt.Fprintln(w, "post请求中Form表单name请求参数:", r.PostFormValue("name"))
if err != nil {
fmt.Println(err)
}
}
func handlerJson(w http.ResponseWriter, r *http.Request) {
//设置响应内容头
w.Header().Set("Content-Text", "application/json")
//创建User
user := &model.User{
ID: 3,
Name: "李四",
Email: "lisi@qqlcom",
}
//转换成json格式
data, _ := json.Marshal(user)
//将json格式数据响应给客户端
_, err := w.Write(data)
if err != nil {
fmt.Println(err)
}
}
func handlerRedirect(w http.ResponseWriter, r *http.Request) {
//设置响应头中的Location
w.Header().Set("Location", "https://www.baidu.com")
//设置响应状态码
w.WriteHeader(302)
}
func main() {
http.HandleFunc("/req", handler)
http.HandleFunc("/res/json", handlerJson)
http.HandleFunc("/res/redirect", handlerRedirect)
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println(err)
}
}