Sending type-safe HTTP requests with Go

kevwan

Kevin Wan

Posted on May 23, 2022

Sending type-safe HTTP requests with Go

Scenarios

For Gophers, we basically write code for clients to request. Sometimes we need to request RESTful APIs provided by third parties. At this time, we feel that it's hard to assemble the requests, not difficult, but error-prone.

For example, if we want to send a request like this, it looks very simple, but it's still tedious to actually write it.

POST /articles/5/update?device=ios HTTP/1.1
Host: go-zero.dev
Authorization: Bearer <jwt-token>

{"author": "kevin", "body": "this is not important!", "title": "my title", "type":6}
Enter fullscreen mode Exit fullscreen mode

Go native way

This API is actually quite simple and we can write it directly from scratch.

func main() {
    var buf bytes.
    encoder := json.NewEncoder(&buf)
    params := map[string]interface{}{
        "title":  "my title",
        "body":   "this is not important!",
        "author": "kevin",
        "type":   6,
    }
    if err := encoder.Encode(params); err ! = nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    url := fmt.Sprintf("http://go-zero.dev/articles/%d/update?device=%s", 5, "ios")
    req, err := http.NewRequest(http.MethodPost, url, &buf)
    if err ! = nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    req.Header.Add("Authorization", "Bearer <jwt-token>")
    cli := http.Client{}
    resp, err := cli.Do(req)
    if err ! = nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    io.Copy(os.Stdout, resp.Body)
}
Enter fullscreen mode Exit fullscreen mode

We ran a test and found that we didn't get 200 OK, dumped the packet and the request looks as follows. Can you think of the reason for the failure?

POST /articles/5/update?device=ios HTTP/1.1
Host: go-zero.dev
User-Agent: Go-http-client/1.1
Content-Length: 79
Authorization: Bearer <jwt-token>
Accept-Encoding: gzip

{"author": "kevin", "body": "this is not important!", "title": "my title", "type":6}
Enter fullscreen mode Exit fullscreen mode

The specific reasons for the failure will be discussed below, so let's explain this code first. You can see that the map[string]interface{} is used for the splicing parameters, for each field we can't check if the type matches. Only when we send it out and receive 200 OK from the server, we can confirm that it is passed correctly. For example, the type parameter is used here as int type, we may write it as string type by mistake. But it is still hard to find out that this parameter is written incorrectly without requesting it.

So let's see how the httpc package in go-zero is used for type safety.

httpc implementation

Let's see how the code for requesting with the httpc package is written.

const url = "http://go-zero.dev/articles/:id/update"

type UpdateArticle struct {
    ID            int    `path: "id"`
    Device        string `form: "device,options=ios,android,web,desktop"`
    Authorization string `header: "Authorization"`
    Title         string `json: "title"`
    Body          string `json: "body"`
    Author        string `json: "author"`
    Type          int    `json: "type"`
}

func main() {
    data := &UpdateArticle{
        ID:            5,
        Device:        "ios",
        Authorization: "Bearer <jwt-token>",
        Title:         "my title",
        Body:          "this is not important!",
        Author:        "kevin",
        Type:          6,
    }

    resp, err := httpc.Do(context.Background(), http.MethodPost, url, data)
    if err ! = nil {
        fmt.Fprintln(os.Stderr, err)
        return
    }

    io.Copy(os.Stdout, resp.Body)
}
Enter fullscreen mode Exit fullscreen mode

Let's verify the code by sending a request, the result is as expected.

POST /articles/5/update?device=ios HTTP/1.1
Host: go-zero.dev
User-Agent: Go-http-client/1.1
Content-Length: 79
Content-Type: application/json; charset=utf-8
Authorization: Bearer <jwt-token>
Accept-Encoding: gzip

{"author": "kevin", "body": "this is not important!", "title": "my title", "type":6}
Enter fullscreen mode Exit fullscreen mode

Did you find out that, in contrast to the previous one, there is one more header was set, Content-Type: application/json; charset=utf-8, and we forgot to set Content-Type in the previous code.

httpc implementation is very easy to understand by defining the type of the request and sending with Do. With support for path, form, header and json as shown in our code, it is very easy and type-safe to send HTTP requests.

More Capabilities

In addition to the ease of use and type-safety shown above, the httpc package has the following features.

  1. timeout control with context. You can pass ctx for the requests.
  2. automatic integration of OpenTelemetry. The trace-id, span-id returned by the server will be automatically written in the log, so that the client and server can work together to investigate the problems.
  3. use httpc.Service to get the circuit breaker ability. When the server side has problems, it will automatically stop sending the requests, to avoid useless requests and reduce the pressure on the server side.

Project address

https://github.com/zeromicro/go-zero

Welcome to use go-zero and star to support us!

๐Ÿ’– ๐Ÿ’ช ๐Ÿ™… ๐Ÿšฉ
kevwan
Kevin Wan

Posted on May 23, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

ยฉ TheLazy.dev

About