大家好,针对Go语言 net/http 标准库,将梳理的相关知识点分享给大家~~
围绕 net/http 标准库相关知识点还有许多章节,请大家多多关注。
文章中代码案例只有关键片段,完整代码请查看github仓库:https://github.com/hltfaith/go-example/tree/main/net-http
本章节案例,请大家以 go1.16+ 版本以上进行参考。
net/http标准库系列文章
- Golang net/http标准库常用请求方法(一)
- Golang net/http标准库常用方法(二)
- Golang net/http标准库常用方法(三)
- Golang net/http标准库Request类型(四)
- Golang net/http标准库Request类型(五)
本节内容
- 案例三:浏览器获取cookie信息
- 案例四:WirteProxy代理
- 案例五:BasicAuth认证
- 案例六:Clone克隆请求体
案例三:浏览器获取cookie信息
Cookie使Web服务器能在用户的设备存储状态信息或跟踪用户的浏览活动。 cookie的头字段为 Set-Cookie。
一块cookie可能有Domain、Path、Expires、Max-Age、Secure、HttpOnly等多种属性,每种赋值格式是:名=值。
下面通过浏览器第一次向服务器获取cookie,第二次发送请求时通过本地所存储的Cookie进行向服务端验证。
服务端代码片段
cookieserver.go
func main() {
http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
// 浏览器获取本地存储中是否有cookie
c, err := r.Cookie("ClientCookieID")
if err != nil {
log.Println(err)
}
if c != nil {
w.Write([]byte(c.Value))
return
}
// 服务端响应返回cookie信息
http.SetCookie(w, &http.Cookie{
Name: "ClientCookieID",
Value: "12345",
Expires: time.Now().Add(120 * time.Second),
})
w.Write([]byte("This is cookie\n"))
})
log.Fatal(http.ListenAndServe(":80", nil))
}
在浏览器上访问 http://127.0.0.1/post 后 ,第一次与服务端请求因没有cookie信息,显示我们预期的字符串。
这时我们查看浏览器本地的存储列表,已经发现已经将服务端响应 Set-Cookie 头已经存到了本地,在进行第二次请求。
这里的响应内容就是服务端返回的浏览器本地的cookie信息。
最后,通过抓包我们看下两次HTTP请求原始报文
案例四:WirteProxy代理
WriteProxy函数以HTTP代理所需的形式写入请求,实现了使用绝对URI写入请求的初始请求URI行。
writeproxy.go
func main() {
http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
r.URL.Scheme = "http"
r.WriteProxy(w)
})
log.Fatal(http.ListenAndServe(":80", nil))
}
当我们使用 curl 命令来访问
root@hc:~# curl http://127.0.0.1/get
GET http://127.0.0.1/get HTTP/1.1
Host: 127.0.0.1
User-Agent: curl/7.81.0
Accept: */*
可以看到 HTTP报文中请求行 url 路径已经变成了绝对路径的地址,这是代理规范要求。
具体的应用场景可以参考 httputil.NewProxyClientConn()
案例五:BasicAuth认证
BasicAuth是一种用于保护Web应用程序的身份验证机制。它通过在HTTP请求中添加身份验证信息来验证用户的身份,并且只有在提供正确的凭据时才允许访问受保护的资源。
BasicAuth认证通过发送包含用户名和密码,在HTTP头部信息中,格式为:Authorization: Basic <base64-encoded-username-and-password>。
服务端代码片段
basicauthserver.go
func main() {
http.HandleFunc("/post", func(w http.ResponseWriter, r *http.Request) {
username, password, ok := r.BasicAuth()
if !ok {
log.Panic("BasicAuth is none")
}
fmt.Fprintf(w, "username=%s, password=%s", username, password)
})
log.Fatal(http.ListenAndServe(":80", nil))
}
这里通过 request BasicAuth() 函数,获取客户端请求中携带的BasicAuth头信息,进行判断。
客户端代码片段
basicauthclient.go
func main() {
req := &http.Request{
// POST请求方法
Method: "POST",
// 拼接url路径 http://127.0.0.1/post
URL: &url.URL{
Scheme: "http",
Host: "127.0.0.1",
Path: "/post",
},
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
}
req.SetBasicAuth("changhao", "123456")
client := http.Client{}
res, err := client.Do(req)
if err != nil {
log.Panic(err)
}
defer res.Body.Close()
b, _ := io.ReadAll(res.Body)
fmt.Println(string(b))
}
客户端代码中使用 SetBasicAuth() 函数设置了认证头信息,默认会通过base64进行将账号密码进行转码。 这里没有通过加密传输,属于不安全的方式。
SetBasicAuth()函数原型,是在request请求头中加了 Authorization: Basic xxxxxxxxxx的信息。
func (r *Request) SetBasicAuth(username, password string) {
r.Header.Set("Authorization", "Basic "+basicAuth(username, password))
}
通过抓包,我们看下HTTP的原始报文
在POST请求时,发现客户端已经携带了 Authorization头,并将信息转成了base64编码。 服务端响应后将账号密码返回了Body内容。
案例六:Clone克隆请求体
在执行定时或者批量任务请求时,我们会对目标http请求执行相同请求体,这种情况可以使用 Clone() 函数,它会克隆request的请求体,但不包含Body内容。 下面通过案例说明Clone()函数如何使用。
代码片段
requestclone.go
type Info struct {
ID string `json:"id"`
}
func main() {
info := &Info{ID: "12345"}
data, _ := json.Marshal(info)
req, _ := http.NewRequest("POST", "http://httpbin.org/post", strings.NewReader(string(data)))
req.Header.Add("Content-Type", "application/json")
// 下面通过 Clone() 函数克隆request请求体
req2 := req.Clone(req.Context())
// 查看req原生的请求HTTP报文
reqRaw, _ := httputil.DumpRequest(req, true)
fmt.Println(string(reqRaw))
fmt.Println("---------------------------")
// 查看克隆后的req请求HTTP报文
req2Raw, _ := httputil.DumpRequest(req2, true)
fmt.Println(string(req2Raw))
fmt.Println("---------------------------")
// 下面通过 Clone() 函数克隆request请求体, 补充Body内容后, 发送HTTP请求
req2.Body = io.NopCloser(strings.NewReader(string(data)))
client := http.Client{}
res, err := client.Do(req2)
if err != nil {
panic(err)
}
defer res.Body.Close()
b, _ := ioutil.ReadAll(res.Body)
fmt.Println(string(b))
}
上述代码,首先使用 req.Clone(req.Context())函数将Request请求体克隆,然后针对克隆的request和原始的request 请求HTTP报文进行对比,发现只有Body内容没有克隆。
最后在克隆的request请求中补充Body内容后,进行http请求。
技术文章持续更新,请大家多多关注呀~~
搜索微信公众号,关注我【 帽儿山的枪手 】