大家好,针对Go语言 net/http
标准库,将梳理的相关知识点分享给大家~~
围绕 net/http
标准库相关知识点还有许多章节,请大家多多关注~~
文章中代码案例只有关键片段,完整代码请查看github
仓库:https://github.com/hltfaith/go-example/tree/main/net-http
本节内容
- http基础知识
- net/http库常用请求方法
http基础知识
HTTP 超文本传输协议(HyperText Transfer Protocol)。
Web应用的应用层协议,定义浏览器如何向Web服务器发送请求,以及Web服务器如何进行响应。
http报文格式
用于 HTTP 协议交互的信息被称为 HTTP 报文。请求端(客户端)的 HTTP 报文叫做请求报文,响应端(服务器端)的叫做响应报文。
HTTP 报文大致可分为 报文首部和报文主体 两块。两者由最初出现的空行(CR+LF)来划分。通常,并不一定要有报文主体。
请求报文结构
请求报文实例
响应报文结构
响应报文实例
注:上图来自《图解HTTP》书籍,更详细的http报文首部信息请查阅该书籍。
http1、http2协议
http1.0
- 每次请求都必须新建一次连接,必须通过TCP的三次握手才能开始传输数据,连接结束之后还要经历四次挥手。
- 不跟踪每个浏览器的历史请求。
- 连接无法复用,每次请求都需要建立一个TCP连接,费时费力。
- 队头阻塞,下一个请求必须在前一个请求响应到达后发送。如果某请求一直不到达,那么下一个请求就一直不发送。
http1.1
- 默认支持长连接,即在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟。
- 管道化,通过长连接实现在一个连接中传输多个文件。
- 支持断点续传。
- 添加了其他请求方法:put、delete、options...
http2.0
- 二进制传输:将请求和响应数据分为更小的帧,并且采用二进制编码。
- Header压缩:采用HPACK算法压缩头部,同时同一个域名下的两个请求,只会发送差异数据,减少冗余的数据传输,降低开销。
- 多路复用:同一个域名下所有通信都是单个连接上完成,单个连接可以承载任意数量的双向数据流,数据流以消息形式发送,而消息由一个或多个帧组成,可以乱序发送。
- 服务端推送:服务端可以新建“流”主动向客户端发送消息,提前推送客户端需要的静态资源,减少等待延迟。
- 提高安全性:HTTP2也是明文的,只不过格式是二进制的,但HTTP2都是https协议的,跑在TSL上面。
http协议9种请求类型
OPTIONS:允许客户端查看服务器的性能。
GET:请求指定的页面信息,并返回实体主体。
HEAD:类似于GET请求,响应中没有具体的内容,用于获取报头。
POST:向指定资源提交数据并进行处理请求。数据被包含在请求体中,POST请求可能会导致新的资源的建立或已有资源的修改。
PUT:从客户端向服务器传送新的数据到指定的位置中。
DELETE:请求服务器删除指定的页面。
TRACE:回显服务器收到的请求,主要用于测试或诊断。
CONNECT:可以开启一个客户端与所请求资源之间的双向沟通的通道。它可以用来创建隧道(tunnel)。
PATCH:是对请求方式中的PUT补充,用来对已知资源进行局部更新。
http常见状态码
更详细http状态码请查阅: https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
http头格式
HTTP 是一种无状态 (stateless) 协议, HTTP协议本身不会对发送过的请求和相应的通信状态进行持久化处理。这样做的目的是为了保持HTTP协议的简单性,从而能够快速处理大量的事务, 提高效率。
然而,在许多应用场景中,我们需要让浏览器记住当前的状态,常用的手段是在 http Header
设置 Token
等方式。 HTTP
头字段非常灵活,不仅可以使用标准里的 Host
、Connection
等已有头,也可以任意添加自定义头。
使用头字段需要注意下面几点:
- 字段名不区分大小写,例如
Host
也可以写成host
,但首字母大写的可读性更好; - 字段名里不允许出现空格,可以使用连字符
-
,但不能使用下划线_
。例如,test-name
是合法的字段名,而test name
、test_name
是不正确的字段名; - 字段名后面必须紧接着
:
,不能有空格,而:
后的字段值前可以有多个空格; - 字段的顺序是没有意义的,可以任意排列不影响语义;
- 字段原则上不能重复,除非这个字段本身的语义允许,例如
Set-Cookie
。
常见的一些 HTTP 标头:
更详细HTTP标头请查阅:https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers
http Payload概念
Payload
指的是在HTTP
通信中传输的实际数据部分。在HTTP
请求中,Payload
通常是请求体(Request Body),包含着客户端发送给服务器的数据。而在HTTP
响应中,Payload
则是响应体(Response Body),装载着服务器返回给客户端的数据。
常用的请求方法
Get()方法
通常我们需要的向指定的URL发起GET请求。
函数原型
func Get(url string) (resp *Response, err error)
接口中 *Response
类型,可以参考后续章节Response类型。
如下案例中,向 http://httpbin.org/get
URL发起 GET
请求,对端服务接口返回Response body内容。
func main() {
res, err := http.Get("http://httpbin.org/get")
if err != nil {
log.Fatal(err)
}
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
log.Fatalf("Response failed with status code: %d\n", res.StatusCode)
}
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", body)
}
响应内容,包含请求客户端的IP、请求URL等信息。
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-64a2c319-2b1c4b996944f20364070806"
},
"origin": "219.143.128.115",
"url": "http://httpbin.org/get"
}
注:这个GET
方法本身是基于NewRequest()
方法封装,其主要作用精简GET
请求。
Head()方法
Head
请求表示只请求目标 URL 的响应头信息,不返回响应实体。
函数原型
func Head(url string) (resp *Response, err error)
使用和 http.Get()
方法一样,只需要传入目标的 URL参数即可
func main() {
res, err := http.Head("http://httpbin.org/get")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
for k, v := range res.Header { // 打印头信息
fmt.Println(k, ":", v)
}
}
响应内容
Connection : [keep-alive]
Server : [gunicorn/19.9.0]
Access-Control-Allow-Origin : [*]
Access-Control-Allow-Credentials : [true]
Date : [Mon, 03 Jul 2023 12:55:56 GMT]
Content-Type : [application/json]
Content-Length : [241]
Post()方法
向指定URL发送POST
请求。
函数原型
func Post(url, contentType string, body io.Reader) (resp *Response, err error)
调用 http.Post()
方法需要依次传递3个参数
- 请求的目标URL
- 数据资源类型
- 数据的比特流([]byte 形式)
该 Post()方法本身也是基于 NewRequest()
方法封装。
本案例中,使用 application/json
载荷体类型,模拟数据向 http://httpbin.org/post api接口发送POST请求。
func main() {
data := make(map[string]string, 0)
data["key"] = "001"
buf, err := json.Marshal(data)
if err != nil {
log.Fatal(err)
}
reqBody := strings.NewReader(string(buf))
res, err := http.Post("http://httpbin.org/post", "application/json", reqBody)
if err != nil {
log.Fatal(err)
}
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
log.Fatalf("Response failed with status code: %d\n", res.StatusCode)
}
fmt.Printf("%s", body)
}
响应内容
{
"args": {},
"data": "{\"key\":\"001\"}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "13",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-64a2c7d6-17f1aadd5816ef5a3ae14ae8"
},
"json": {
"key": "001"
},
"origin": "219.143.128.115",
"url": "http://httpbin.org/post"
}
可以看到,在响应内容中 json
结构字段中已有传入的请求数据。
PostForm()方法
发送表单POST
请求。
函数原型
func PostForm(url string, data url.Values) (resp *Response, err error)
PostForm()
方法中,Content-Type
头信息类型是 application/x-www-form-urlencoded
。
注:POST
请求参数需要通过url.Values
方法进行编码和封装。
url.Values 类型
,归属于 net/url
模块下。
type Values map[string][]string
Values将字符串键映射到值列表,它通常用于查询参数和表单值、头映射、值映射中的键是区分大小写的。
案例中使用 url.Values
模拟数据
func main() {
res, err := http.PostForm("http://httpbin.org/post", url.Values{"key": {"001"}})
if err != nil {
log.Fatalln(err)
}
body, err := io.ReadAll(res.Body)
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
log.Fatalf("Response failed with status code: %d\n", res.StatusCode)
}
fmt.Printf("%s", body)
}
响应内容
{
"args": {},
"data": "",
"files": {},
"form": {
"key": "001"
},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "7",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-64a2d1fa-2ccbe28174e1e75e4742f109"
},
"json": null,
"origin": "219.143.128.115",
"url": "http://httpbin.org/post"
}
可以看到在响应内容中,form
表单字段已经有传入的数据。
NewRequest()方法
NewRequest
方法原型是 context.Background
封装的NewRequestWithContext()
方法。 net/http
包没有封装直接使用请求带header的 GET
或者 POST
方法,所以,要想请求中携带header,只能使用NewRequest()
方法。
函数原型
func NewRequest(method, url string, body io.Reader) (*Request, error)
函数参数依次是:请求类型、请求目标地址、请求体(byte类型)
案例中,请求头设置载荷体application/json
类型,发送POST请求。
func main() {
data := make(map[string]string, 0)
data["key"] = "001"
buf, err := json.Marshal(data)
if err != nil {
log.Fatal(err)
}
reqBody := strings.NewReader(string(buf))
request, err := http.NewRequest("POST", "http://httpbin.org/post", reqBody)
request.Header.Set("Content-Type", "application/json")
client := &http.Client{}
res, err := client.Do(request) // 发起客户端请求
if err != nil {
log.Fatal(err)
}
body, _ := io.ReadAll(res.Body)
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
log.Fatalf("Response failed with status code: %d\n", res.StatusCode)
}
fmt.Printf("%s", body)
}
响应内容
{
"args": {},
"data": "{\"key\":\"001\"}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "13",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-64a2d9ed-3adcecba48c87a5743068ea8"
},
"json": {
"key": "001"
},
"origin": "219.143.128.115",
"url": "http://httpbin.org/post"
}
NewRequestWithContext()方法
NewRequestWithContext返回一个给定方法、URL和可选主体的新Request。
NewRequestWithContext返回一个适合客户端使用的请求,可以使用 Client.Do
方法或 Transport.RoundTrip
。
body 类型可以是 *bytes.Buffer
,*bytes.Reader
或 *strings.Reader
。
返回的请求的ContentLength
被设置为它的确切值(而不是-1),如果ContentLength
为0,body被设置为NoBody。
函数原型
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error)
函数使用
func main() {
data := make(map[string]string, 0)
data["key"] = "001"
buf, err := json.Marshal(data)
if err != nil {
log.Fatal(err)
}
reqBody := strings.NewReader(string(buf))
request, err := http.NewRequestWithContext(context.Background(), "POST", "http://httpbin.org/post", reqBody)
request.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(request)
if err != nil {
log.Fatal(err)
}
body, _ := io.ReadAll(res.Body)
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
log.Fatalf("Response failed with status code: %d\n", res.StatusCode)
}
fmt.Printf("%s", body)
}
响应内容
{
"args": {},
"data": "{\"key\":\"001\"}",
"files": {},
"form": {},
"headers": {
"Accept-Encoding": "gzip",
"Content-Length": "13",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-64a2df9c-43bf00871026b6211598c1bf"
},
"json": {
"key": "001"
},
"origin": "219.143.128.115",
"url": "http://httpbin.org/post"
}
ReadRequest()方法
ReadRequest
用于读取解析HTTP请求报文,读取请求体为 *bufio.Reader
类型。 ReadRequest
是一个低级的函数,只能用于专门的应用程序。
注:ReadRequest()
函数只能处理HTTP/1.x
请求, HTTP/2请求请使用golang.org/x/net/http2
函数原型
func ReadRequest(b *bufio.Reader) (*Request, error)
函数使用
首先模拟 HTTP
GET
请求体报文,然后通过 ReadRequest
函数进行将请求报文内容进行解析。 readrequest.go
下面将请求报文通过 ReadRequest
函数做解析。
ReadResponse()方法
ReadResponse
用于读取解析HTTP响应报文,读取响应体为 *bufio.Reader
类型,以及请求封装 http.Request
类型。
函数原型
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
函数使用 readresponse.go
上述 ReadRequest() 、ReadResponse()
函数使用案例中,全部是我们自己构造模拟的HTTP
报文。 下面我们通过实际的 Get()
请求函数,将真实报文响应做解析。 readresponse2.go
代码输出
200
{
"args": {},
"headers": {
"Accept-Encoding": "gzip",
"Host": "httpbin.org",
"User-Agent": "Go-http-client/1.1",
"X-Amzn-Trace-Id": "Root=1-663b237c-07a4a929184bb68b65d2d032"
},
"origin": "222.128.49.98",
"url": "http://httpbin.org/get"
}
技术文章持续更新,请大家多多关注呀~~
搜索微信公众号,关注我【 帽儿山的枪手 】
参考材料
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。