Golang net/http标准库Request类型(五)

675 阅读5分钟

大家好,针对Go语言 net/http 标准库,将梳理的相关知识点分享给大家~~
围绕 net/http 标准库相关知识点还有许多章节,请大家多多关注。
文章中代码案例只有关键片段,完整代码请查看github仓库:https://github.com/hltfaith/go-example/tree/main/net-http
本章节案例,请大家以 go1.16+ 版本以上进行参考。

net/http标准库系列文章

本节内容

  • 案例三:浏览器获取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请求。

技术文章持续更新,请大家多多关注呀~~

搜索微信公众号,关注我【 帽儿山的枪手 】