简介
resty
是 Go 语言的一个 HTTP client 库。resty
功能强大,个性丰盛。它反对简直所有的 HTTP 办法(GET/POST/PUT/DELETE/OPTION/HEAD/PATCH等),并提供了简略易用的 API。
疾速应用
本文代码应用 Go Modules。
创立目录并初始化:
$ mkdir resty && cd resty$ go mod init github.com/darjun/go-daily-lib/resty
装置resty
库:
$ go get -u github.com/go-resty/resty/v2
上面咱们来获取百度首页信息:
package mainimport ( "fmt" "log" "github.com/go-resty/resty/v2")func main() { client := resty.New() resp, err := client.R().Get("https://baidu.com") if err != nil { log.Fatal(err) } fmt.Println("Response Info:") fmt.Println("Status Code:", resp.StatusCode()) fmt.Println("Status:", resp.Status()) fmt.Println("Proto:", resp.Proto()) fmt.Println("Time:", resp.Time()) fmt.Println("Received At:", resp.ReceivedAt()) fmt.Println("Size:", resp.Size()) fmt.Println("Headers:") for key, value := range resp.Header() { fmt.Println(key, "=", value) } fmt.Println("Cookies:") for i, cookie := range resp.Cookies() { fmt.Printf("cookie%d: name:%s value:%s\n", i, cookie.Name, cookie.Value) }}
resty
应用比较简单。
- 首先,调用一个
resty.New()
创立一个client
对象; - 调用
client
对象的R()
办法创立一个申请对象; - 调用申请对象的
Get()/Post()
等办法,传入参数 URL,就能够向对应的 URL 发送 HTTP 申请了。返回一个响应对象; - 响应对象提供很多办法能够查看响应的状态,首部,Cookie 等信息。
下面程序中咱们获取了:
StatusCode()
:状态码,如 200;Status()
:状态码和状态信息,如 200 OK;Proto()
:协定,如 HTTP/1.1;Time()
:从发送申请到收到响应的工夫;ReceivedAt()
:接管到响应的时刻;Size()
:响应大小;Header()
:响应首部信息,以http.Header
类型返回,即map[string][]string
;Cookies()
:服务器通过Set-Cookie
首部设置的 cookie 信息。
运行程序输入的响应根本信息:
Response Info:Status Code: 200Status: 200 OKProto: HTTP/1.1Time: 415.774352msReceived At: 2021-06-26 11:42:45.307157 +0800 CST m=+0.416547795Size: 302456
首部信息:
Headers:Server = [BWS/1.1]Date = [Sat, 26 Jun 2021 03:42:45 GMT]Connection = [keep-alive]Bdpagetype = [1]Bdqid = [0xf5a61d240003b218]Vary = [Accept-Encoding Accept-Encoding]Content-Type = [text/html;charset=utf-8]Set-Cookie = [BAIDUID=BF2EE47AAAF7A20C6971F1E897ABDD43:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com BIDUPSID=BF2EE47AAAF7A20C6971F1E897ABDD43; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com PSTM=1624678965; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com BAIDUID=BF2EE47AAAF7A20C716E90B86906D6B0:FG=1; max-age=31536000; expires=Sun, 26-Jun-22 03:42:45 GMT; domain=.baidu.com; path=/; version=1; comment=bd BDSVRTM=0; path=/ BD_HOME=1; path=/ H_PS_PSSID=34099_31253_34133_34072_33607_34135_26350; path=/; domain=.baidu.com]Traceid = [1624678965045126810617700867425882583576]P3p = [CP=" OTI DSP COR IVA OUR IND COM " CP=" OTI DSP COR IVA OUR IND COM "]X-Ua-Compatible = [IE=Edge,chrome=1]
留神其中有一个Set-Cookie
首部,这部分内容会呈现在 Cookie 局部:
Cookies:cookie0: name:BAIDUID value:BF2EE47AAAF7A20C6971F1E897ABDD43:FG=1cookie1: name:BIDUPSID value:BF2EE47AAAF7A20C6971F1E897ABDD43cookie2: name:PSTM value:1624678965cookie3: name:BAIDUID value:BF2EE47AAAF7A20C716E90B86906D6B0:FG=1cookie4: name:BDSVRTM value:0cookie5: name:BD_HOME value:1cookie6: name:H_PS_PSSID value:34099_31253_34133_34072_33607_34135_26350
主动 Unmarshal
当初很多网站提供 API 接口,返回结构化的数据,如 JSON/XML 格局等。resty
能够主动将响应数据 Unmarshal 到对应的构造体对象中。上面看一个例子,咱们晓得很多 js 文件都托管在 cdn 上,咱们能够通过api.cdnjs.com/libraries
获取这些库的根本信息,返回一个 JSON 数据,格局如下:
接下来,咱们定义构造,而后应用resty
拉取信息,主动 Unmarshal:
type Library struct { Name string Latest string}type Libraries struct { Results []*Library}func main() { client := resty.New() libraries := &Libraries{} client.R().SetResult(libraries).Get("https://api.cdnjs.com/libraries") fmt.Printf("%d libraries\n", len(libraries.Results)) for _, lib := range libraries.Results { fmt.Println("first library:") fmt.Printf("name:%s latest:%s\n", lib.Name, lib.Latest) break }}
能够看到,咱们只须要创立一个后果类型的对象,而后调用申请对象的SetResult()
办法,resty
会主动将响应的数据 Unmarshal 到传入的对象中。这里设置申请信息时应用链式调用的形式,即在一行中实现多个设置。
运行:
$ go run main.go4040 librariesfirst library:name:vue latest:https://cdnjs.cloudflare.com/ajax/libs/vue/3.1.2/vue.min.js
一共 4040 个库,第一个就是 Vue✌️。咱们申请https://api.cdnjs.com/libraries/vue
就能获取 Vue 的详细信息:
感兴趣可自行用resty
来拉取这些信息。
个别申请下,resty
会依据响应中的Content-Type
来推断数据格式。然而有时候响应中无Content-Type
首部或与内容格局不统一,咱们能够通过调用申请对象的ForceContentType()
强制让resty
依照特定的格局来解析响应:
client.R(). SetResult(result). ForceContentType("application/json")
申请信息
resty
提供了丰盛的设置申请信息的办法。咱们能够通过两种形式设置查问字符串。一种是调用申请对象的SetQueryString()
设置咱们拼接好的查问字符串:
client.R(). SetQueryString("name=dj&age=18"). Get(...)
另一种是调用申请对象的SetQueryParams()
,传入map[string]string
,由resty
来帮咱们拼接。显然这种更为不便:
client.R(). SetQueryParams(map[string]string{ "name": "dj", "age": "18", }). Get(...)
resty
还提供一种十分实用的设置门路参数接口,咱们调用SetPathParams()
传入map[string]string
参数,而后前面的 URL 门路中就能够应用这个map
中的键了:
client.R(). SetPathParams(map[string]string{ "user": "dj", }). Get("/v1/users/{user}/details")
留神,门路中的键须要用{}
包起来。
设置首部:
client.R(). SetHeader("Content-Type", "application/json"). Get(...)
设置申请音讯体:
client.R(). SetHeader("Content-Type", "application/json"). SetBody(`{"name": "dj", "age":18}`). Get(...)
音讯体能够是多种类型:字符串,[]byte
,对象,map[string]interface{}
等。
设置携带Content-Length
首部,resty
主动计算:
client.R(). SetBody(User{Name:"dj", Age:18}). SetContentLength(true). Get(...)
有些网站须要先获取 token,而后能力拜访它的 API。设置 token:
client.R(). SetAuthToken("youdontknow"). Get(...)
案例
最初,咱们通过一个案例来将下面介绍的这些串起来。当初咱们想通过 GitHub 提供的 API 获取组织的仓库信息,API 文档见文后链接。GitHub API 申请地址为https://api.github.com
,获取仓库信息的申请格局如下:
GET /orgs/{org}/repos
咱们还能够设置以下这些参数:
accept
:首部,这个必填,须要设置为application/vnd.github.v3+json
;org
:组织名,门路参数;type
:仓库类型,查问参数,例如public/private/forks(fork的仓库)
等;sort
:仓库的排序规定,查问参数,例如created/updated/pushed/full_name
等。默认按创立工夫排序;direction
:升序asc
或降序dsc
,查问参数;per_page
:每页多少条目,最大 100,默认 30,查问参数;page
:以后申请第几页,与per_page
一起做分页治理,默认 1,查问参数。
GitHub API 必须设置 token 能力拜访。登录 GitHub 账号,点开右上角头像,抉择Settings
:
而后,抉择Developer settings
:
抉择Personal access tokens
,而后点击右上角的Generate new token
:
填写 Note,示意 token 的用处,这个依据本人状况填写即可。上面复选框用于抉择该 token 有哪些权限,这里不须要勾选:
点击上面的Generate token
按钮即可生成 token:
留神,这个 token 只有当初能看见,关掉页面下次再进入就无奈看到了。所以要保留好,另外不要用我的 token,测试完程序后我会删除 token。
响应中的 JSON 格局数据如下所示:
字段十分多,为了不便起见,我这里之解决几个字段:
type Repository struct { ID int `json:"id"` NodeID string `json:"node_id"` Name string `json:"name"` FullName string `json:"full_name"` Owner *Developer `json:"owner"` Private bool `json:"private"` Description string `json:"description"` Fork bool `json:"fork"` Language string `json:"language"` ForksCount int `json:"forks_count"` StargazersCount int `json:"stargazers_count"` WatchersCount int `json:"watchers_count"` OpenIssuesCount int `json:"open_issues_count"`}type Developer struct { Login string `json:"login"` ID int `json:"id"` NodeID string `json:"node_id"` AvatarURL string `json:"avatar_url"` GravatarID string `json:"gravatar_id"` Type string `json:"type"` SiteAdmin bool `json:"site_admin"`}
而后应用resty
设置门路参数,查问参数,首部,Token 等信息,而后发动申请:
func main() { client := resty.New() var result []*Repository client.R(). SetAuthToken("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"). SetHeader("Accept", "application/vnd.github.v3+json"). SetQueryParams(map[string]string{ "per_page": "3", "page": "1", "sort": "created", "direction": "asc", }). SetPathParams(map[string]string{ "org": "golang", }). SetResult(&result). Get("https://api.github.com/orgs/{org}/repos") for i, repo := range result { fmt.Printf("repo%d: name:%s stars:%d forks:%d\n", i+1, repo.Name, repo.StargazersCount, repo.ForksCount) }}
下面程序拉取以创立工夫升序排列的 3 个仓库:
$ go run main.gorepo1: name:gddo stars:1097 forks:289repo2: name:lint stars:3892 forks:518repo3: name:glog stars:2738 forks:775
Trace
介绍完resty
的次要性能之后,咱们再来看看resty
提供的一个辅助性能:trace。咱们在申请对象上调用EnableTrace()
办法启用 trace。启用 trace 能够记录申请的每一步的耗时和其余信息。resty
反对链式调用,也就是说咱们能够在一行中实现创立申请,启用 trace,发动申请:
client.R().EnableTrace().Get("https://baidu.com")
在实现申请之后,咱们通过调用申请对象的TraceInfo()
办法获取信息:
ti := resp.Request.TraceInfo()fmt.Println("Request Trace Info:")fmt.Println("DNSLookup:", ti.DNSLookup)fmt.Println("ConnTime:", ti.ConnTime)fmt.Println("TCPConnTime:", ti.TCPConnTime)fmt.Println("TLSHandshake:", ti.TLSHandshake)fmt.Println("ServerTime:", ti.ServerTime)fmt.Println("ResponseTime:", ti.ResponseTime)fmt.Println("TotalTime:", ti.TotalTime)fmt.Println("IsConnReused:", ti.IsConnReused)fmt.Println("IsConnWasIdle:", ti.IsConnWasIdle)fmt.Println("ConnIdleTime:", ti.ConnIdleTime)fmt.Println("RequestAttempt:", ti.RequestAttempt)fmt.Println("RemoteAddr:", ti.RemoteAddr.String())
咱们能够获取以下信息:
DNSLookup
:DNS 查问工夫,如果提供的是一个域名而非 IP,就须要向 DNS 零碎查问对应 IP 能力进行后续操作;ConnTime
:获取一个连贯的耗时,可能从连接池获取,也可能新建;TCPConnTime
:TCP 连贯耗时,从 DNS 查问完结到 TCP 连贯建设;TLSHandshake
:TLS 握手耗时;ServerTime
:服务器解决耗时,计算从连贯建设到客户端收到第一个字节的工夫距离;ResponseTime
:响应耗时,从接管到第一个响应字节,到接管到残缺响应之间的工夫距离;TotalTime
:整个流程的耗时;IsConnReused
:TCP 连贯是否复用了;IsConnWasIdle
:连贯是否是从闲暇的连接池获取的;ConnIdleTime
:连贯闲暇工夫;RequestAttempt
:申请执行流程中的申请次数,包含重试次数;RemoteAddr
:近程的服务地址,IP:PORT
格局。
resty
对这些辨别得很细。实际上resty
也是应用规范库net/http/httptrace
提供的性能,httptrace
提供一个构造,咱们能够设置各个阶段的回调函数:
// src/net/http/httptrace.gotype ClientTrace struct { GetConn func(hostPort string) GotConn func(GotConnInfo) PutIdleConn func(err error) GotFirstResponseByte func() Got100Continue func() Got1xxResponse func(code int, header textproto.MIMEHeader) error // Go 1.11 DNSStart func(DNSStartInfo) DNSDone func(DNSDoneInfo) ConnectStart func(network, addr string) ConnectDone func(network, addr string, err error) TLSHandshakeStart func() // Go 1.8 TLSHandshakeDone func(tls.ConnectionState, error) // Go 1.8 WroteHeaderField func(key string, value []string) // Go 1.11 WroteHeaders func() Wait100Continue func() WroteRequest func(WroteRequestInfo)}
能够从字段名简略理解回调的含意。resty
在启用 trace 后设置了如下回调:
// src/github.com/go-resty/resty/trace.gofunc (t *clientTrace) createContext(ctx context.Context) context.Context { return httptrace.WithClientTrace( ctx, &httptrace.ClientTrace{ DNSStart: func(_ httptrace.DNSStartInfo) { t.dnsStart = time.Now() }, DNSDone: func(_ httptrace.DNSDoneInfo) { t.dnsDone = time.Now() }, ConnectStart: func(_, _ string) { if t.dnsDone.IsZero() { t.dnsDone = time.Now() } if t.dnsStart.IsZero() { t.dnsStart = t.dnsDone } }, ConnectDone: func(net, addr string, err error) { t.connectDone = time.Now() }, GetConn: func(_ string) { t.getConn = time.Now() }, GotConn: func(ci httptrace.GotConnInfo) { t.gotConn = time.Now() t.gotConnInfo = ci }, GotFirstResponseByte: func() { t.gotFirstResponseByte = time.Now() }, TLSHandshakeStart: func() { t.tlsHandshakeStart = time.Now() }, TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { t.tlsHandshakeDone = time.Now() }, }, )}
而后在获取TraceInfo
时,依据各个工夫点计算耗时:
// src/github.com/go-resty/resty/request.gofunc (r *Request) TraceInfo() TraceInfo { ct := r.clientTrace if ct == nil { return TraceInfo{} } ti := TraceInfo{ DNSLookup: ct.dnsDone.Sub(ct.dnsStart), TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart), ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn), IsConnReused: ct.gotConnInfo.Reused, IsConnWasIdle: ct.gotConnInfo.WasIdle, ConnIdleTime: ct.gotConnInfo.IdleTime, RequestAttempt: r.Attempt, } if ct.gotConnInfo.Reused { ti.TotalTime = ct.endTime.Sub(ct.getConn) } else { ti.TotalTime = ct.endTime.Sub(ct.dnsStart) } if !ct.connectDone.IsZero() { ti.TCPConnTime = ct.connectDone.Sub(ct.dnsDone) } if !ct.gotConn.IsZero() { ti.ConnTime = ct.gotConn.Sub(ct.getConn) } if !ct.gotFirstResponseByte.IsZero() { ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte) } if ct.gotConnInfo.Conn != nil { ti.RemoteAddr = ct.gotConnInfo.Conn.RemoteAddr() } return ti}
运行输入:
$ go run main.goRequest Trace Info:DNSLookup: 2.815171msConnTime: 941.635171msTCPConnTime: 269.069692msTLSHandshake: 669.276011msServerTime: 274.623991msResponseTime: 112.216µsTotalTime: 1.216276906sIsConnReused: falseIsConnWasIdle: falseConnIdleTime: 0sRequestAttempt: 1RemoteAddr: 18.235.124.214:443
咱们看到 TLS 耗费了近一半的工夫。
总结
本文我介绍了 Go 语言一款十分不便易用的 HTTP Client 库。 resty
提供十分实用的,丰盛的 API。链式调用,主动 Unmarshal,申请参数/门路设置这些性能十分不便好用,让咱们的工作事倍功半。限于篇幅起因,很多高级个性未能一一介绍,如提交表单,上传文件等等等等。只能留待感兴趣的大家去摸索了。
大家如果发现好玩、好用的 Go 语言库,欢送到 Go 每日一库 GitHub 上提交 issue
参考
- Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib
- resty GitHub:github.com/go-resty/resty
- GitHub API:https://docs.github.com/en/rest/overview/resources-in-the-rest-api
我
我的博客:https://darjun.github.io
欢送关注我的微信公众号【GoUpUp】,独特学习,一起提高~