Introduction to the
Resty is an HTTP client library for the Go language. Resty is powerful and feature-rich. It supports almost all of the HTTP method (GET/POST/PUT/DELETE/OPTION/HEAD/PATCH, etc.), and provides a simple and easy to use API.
Quick to use
The code in this article uses Go Modules.
Create directory and initialize:
$ mkdir resty && cd resty
$ go mod init github.com/darjun/go-daily-lib/resty
Copy the code
Install the Resty library:
$ go get -u github.com/go-resty/resty/v2
Copy the code
Let’s get baidu home page information:
package main
import (
"fmt"
"log"
"github.com/go-resty/resty/v2"
)
func main(a) {
client := resty.New()
resp, err := client.R().Get("https://baidu.com")
iferr ! =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)
}
}
Copy the code
Resty is simpler to use.
- First, call one
resty.New()
To create aclient
Object; - call
client
The object’sR()
Method to create a request object; - Invoking the request object
Get()/Post()
And so on, pass in the parameter URL, you can send HTTP request to the corresponding URL. Returns a response object; - The response object provides many methods to check the status, headers, cookies, and so on of the response.
In the above program we get:
StatusCode()
: Status code, for example, 200.Status()
: Status code and status information, for example, 200 OK.Proto()
: protocol, such as HTTP/1.1;Time()
: Time from sending a request to receiving a response;ReceivedAt()
: The time when the response is received;Size()
: Response size;Header()
: Responds to the header message withhttp.Header
The type returns, i.emap[string][]string
;Cookies()
: The server passes.Set-Cookie
Cookie information set in the header.
Basic response information output by the running program:
Response Info:
Status Code: 200
Status: 200 OK
Proto: HTTP/1.1
Time: 415.774352ms
Received At: 2021- 0626 - 11:42:45.307157 +0800 CST m=+0.416547795
Size: 302456
Copy the code
Header Information:
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-Dec37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com BIDUPSID=BF2EE47AAAF7A20C6971F1E897ABDD43; expires=Thu, 31-Dec37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com PSTM=1624678965; expires=Thu, 31-Dec37 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]
Copy the code
Note that there is a set-cookie header, which appears in the Cookie section:
Cookies:
cookie0: name:BAIDUID value:BF2EE47AAAF7A20C6971F1E897ABDD43:FG=1
cookie1: name:BIDUPSID value:BF2EE47AAAF7A20C6971F1E897ABDD43
cookie2: name:PSTM value:1624678965
cookie3: name:BAIDUID value:BF2EE47AAAF7A20C716E90B86906D6B0:FG=1
cookie4: name:BDSVRTM value:0
cookie5: name:BD_HOME value:1
cookie6: name:H_PS_PSSID value:34099_31253_34133_34072_33607_34135_26350
Copy the code
Automatic Unmarshal
Many web sites now offer apis that return structured data in JSON/XML formats. Resty can automatically Unmarshal the response data into the corresponding structure object. Here is an example. We know that many JS files are hosted on CDN. We can get the basic information of these libraries by going to api.cdnjs.com/libraries and return a JSON data in the following format:
Next, we define the structure and then use Resty to pull the information, automatically Unmarshal:
type Library struct {
Name string
Latest string
}
type Libraries struct {
Results []*Library
}
func main(a) {
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}}Copy the code
As you can see, all we need to do is create an object of the result type, then call the SetResult() method on the request object, and Resty will automatically Unmarshal the data for the response into the incoming object. The request information is set up in a chained invocation, where multiple Settings are done in a single line.
Run:
$ go run main.go
4040 libraries
first library:
name:vue latest:https:/ / cdnjs.cloudflare.com/ajax/libs/vue/3.1.2/vue.min.js
Copy the code
There are 4,040 libraries, the first of which is Vue✌️. We request https://api.cdnjs.com/libraries/vue can obtain Vue detailed information:
You can use Resty to pull this information yourself.
For a typical request, Resty will infer the data format based on the Content-Type in the response. But sometimes the response does not have a content-Type header or does not conform to the Content format, we can force Resty to parse the response in a specific format by calling ForceContentType() on the request object:
client.R().
SetResult(result).
ForceContentType("application/json")
Copy the code
Request information
Resty provides a wealth of ways to set up request information. We can set the query string in two ways. One is to call the request object’s SetQueryString() to set our concatenated query string:
client.R().
SetQueryString("name=dj&age=18").
Get(...)
Copy the code
The other is to call SetQueryParams() of the request object, pass in the map[string]string, and let Resty concatenate it for us. Obviously this is more convenient:
client.R().
SetQueryParams(map[string]string{
"name": "dj"."age": "18",
}).
Get(...)
Copy the code
Resty also provides a very useful interface for setting path parameters. We call SetPathParams() and pass in the map[string]string parameter, and then use the map key in the subsequent URL path:
client.R().
SetPathParams(map[string]string{
"user": "dj",
}).
Get("/v1/users/{user}/details")
Copy the code
Note that the keys in the path need to be wrapped in {}.
Setting header:
client.R().
SetHeader("Content-Type"."application/json").
Get(...)
Copy the code
Set the request message body:
client.R().
SetHeader("Content-Type"."application/json").
SetBody(`{"name": "dj", "age":18}`).
Get(...)
Copy the code
Message bodies can be of many types: string, []byte, object, map[string]interface{}, and so on.
Set to carry a Content-Length header that resty automatically calculates:
client.R().
SetBody(User{Name:"dj", Age:18}).
SetContentLength(true).
Get(...)
Copy the code
Some sites need to obtain a token before they can access its API. Set the token:
client.R().
SetAuthToken("youdontknow").
Get(...)
Copy the code
case
Finally, we use a case study to tie together the above. Now we want to access the organization’s repository information through the GitHub API, which is linked to the API documentation below. The GitHub API request address is https://api.github.com, and the request format is as follows:
GET /orgs/{org}/repos
Copy the code
We can also set the following parameters:
accept
:The first, this must be set toapplication/vnd.github.v3+json
;org
: Organization name,The path parameter;type
: Warehouse type,Query parameters, e.g.Public /private/forks(forks)
And so on;sort
: sorting rules for the warehouse,Query parameters, e.g.created/updated/pushed/full_name
And so on. Sort by creation time by default;direction
: ascendingasc
Or descending orderdsc
.Query parameters;per_page
: Number of entries per page, maximum 100, default 30,Query parameters;page
: Current request page, andper_page
Do paging together, default 1,Query parameters.
You must set a token to access the GitHub API. Log in to your GitHub account, click your profile picture in the upper right corner, and select Settings:
Then, select Developer Settings:
Select Personal Access Tokens and click Generate New Tokens in the upper right corner:
Note indicates the purpose of the token. You can fill in the Note according to your own situation. The following check boxes are used to select which permissions the token has and do not need to be checked:
To Generate a token, click the Generate Token button below:
Note that this token can only be seen now, close the page and enter the next time will not be seen. So keep it and don’t use my token. I will delete token😭 after testing the program.
The JSON data in the response looks like this:
There are a lot of fields. For convenience, I will deal with a few fields here:
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"`
}
Copy the code
Then use resty to set path parameters, query parameters, headers, tokens, etc., and initiate a request:
func main(a) {
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)
}
}
Copy the code
The above program pulls three warehouses in ascending order of creation time:
$ go run main.go
repo1: name:gddo stars:1097 forks:289
repo2: name:lint stars:3892 forks:518
repo3: name:glog stars:2738 forks:775
Copy the code
Trace
Now that we’ve covered the main features of Resty, let’s take a look at one of the ancillary features resty provides: Trace. We call the EnableTrace() method on the request object to EnableTrace. Enable trace to record time and other information for each step of the request. Resty supports chained calls, which means we can create the request in a single line, enable trace, and initiate the request:
client.R().EnableTrace().Get("https://baidu.com")
Copy the code
After completing the request, we get the information by calling the TraceInfo() method of the request object:
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())
Copy the code
We can obtain the following information:
DNSLookup
: DNS query time. If a domain name is provided instead of an IP address, you need to query the CORRESPONDING IP address in the DNS system before performing subsequent operations.ConnTime
: The time it takes to obtain a connection, either from the connection pool or from a new one.TCPConnTime
: Indicates the TCP connection time. The time is from the end of DNS query to the establishment of TCP connection.TLSHandshake
: TLS handshake time;ServerTime
: Server processing time, calculating the interval between the establishment of the connection and the receipt of the first byte by the client;ResponseTime
: Response time, the interval between receiving the first response byte and receiving the complete response;TotalTime
: Time consuming of the whole process;IsConnReused
: Whether the TCP connection is multiplexed.IsConnWasIdle
: Whether the connection was obtained from an idle connection pool;ConnIdleTime
: Connection idle time;RequestAttempt
: Request number of requests in the execution process, including retries.RemoteAddr
: Remote service address,IP:PORT
Format.
Resty makes a fine distinction between these. Resty actually uses functionality provided by the standard library net/ HTTP/httpTrace, which provides a structure for setting callback functions for each phase:
// src/net/http/httptrace.go
type ClientTrace struct {
GetConn func(hostPort string)
GotConn func(GotConnInfo)
PutIdleConn func(err error)
GotFirstResponseByte func(a)
Got100Continue func(a)
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(a) // Go 1.8
TLSHandshakeDone func(tls.ConnectionState, error) // Go 1.8
WroteHeaderField func(key string, value []string) // Go 1.11
WroteHeaders func(a)
Wait100Continue func(a)
WroteRequest func(WroteRequestInfo)
}
Copy the code
You can get a simple idea of what a callback means from the field name. Resty set the following callback after enabling trace:
// src/github.com/go-resty/resty/trace.go
func (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(a) {
t.gotFirstResponseByte = time.Now()
},
TLSHandshakeStart: func(a) {
t.tlsHandshakeStart = time.Now()
},
TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
t.tlsHandshakeDone = time.Now()
},
},
)
}
Copy the code
When obtaining TraceInfo, calculate the time at each point in time:
// src/github.com/go-resty/resty/request.go
func (r *Request) TraceInfo(a) 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) }ifct.gotConnInfo.Conn ! =nil {
ti.RemoteAddr = ct.gotConnInfo.Conn.RemoteAddr()
}
return ti
}
Copy the code
Run output:
$ go run main.go
Request Trace Info:
DNSLookup: 2.815171ms
ConnTime: 941.635171ms
TCPConnTime: 269.069692ms
TLSHandshake: 669.276011ms
ServerTime: 274.623991ms
ResponseTime: 112.216(including s TotalTime:1.216276906s
IsConnReused: false
IsConnWasIdle: false
ConnIdleTime: 0s
RequestAttempt: 1
RemoteAddr: 18.235124.214.:443
Copy the code
We saw that TLS consumed nearly half of the time.
conclusion
This article introduces the Go language a very convenient and easy to use HTTP Client library. Resty provides a very useful, rich API. Chain call, automatic Unmarshal, request parameter/path Settings these features are very convenient to use, let us work twice the result with half the effort. Due to lack of space, many advanced features, such as submitting forms, uploading files, etc., are not covered in detail. That’s for anyone interested to explore.
If you find a fun and useful Go library, please Go to GitHub and submit issue😄
reference
- GitHub: github.com/darjun/go-d…
- Resty GitHub:github.com/go-resty/re…
- Making API:docs.github.com/en/rest/ove…
I
My blog is darjun.github. IO
Welcome to follow my wechat public account [GoUpUp], learn together, progress together ~