前言

对 gopher 来说,虽然我们基本都是在写代码让别人来请求,但是有时候,我们也需要去请求第三方提供的 restful 接口,这个时候,我们才能感受到前端同学拼接 http 请求参数的痛苦。

比如,我们要发起类似这样一个请求,看起来很简单,实际写起来还是比较繁琐的。

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}

go 原生写法

这个 api 其实是蛮简单的,我们直接上手就可以写出来。

func main() {
    var buf bytes.buffer
    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://localhost:3333/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)
}

我们跑了测试一下,发现没有得到 200 ok,抓包看一下,请求如下。各位不要往下看,你能想到失败的原因吗?

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}

具体失败原因这里就不细讲了,我们先来分析这段代码。可以看到其中为了拼接参数使用了 map[string]interface{},对于其中每个字段我们是不能校验类型是否匹配的,只有发送出去了,收到了服务端的 200 ok,我们才能确认传对了。比如其中的 type 参数,这里是使用了 int 类型,我们可能顺手写成 string 类型,但是不请求我们还是很难发现这个参数写错了的。

那么让我们看看 go-zero 里 httpc 包是怎么使用并保证类型安全的。

httpc 实现

我们看看用 httpc 包来请求的代码怎么写。

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)
}

写完测试一下,结果正如预期:

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}

你发现了没有,跟前面的对比,其中多了 content-type: application/json; charset=utf-8,而我们之前写法里忘记设置 content-type 了。

而 httpc 的写法只要定义好请求的类型,然后通过 httpc.do 就可以做到类型安全,并且代码非常精简。支持了如我们代码所示的 path、form、header 和 json,可以非常方便且类型安全的发送 http 请求。

更多能力

除了上面展示的简单易用和类型安全以外,httpc 包还有以下特点:

  • context 的超时控制
  • opentelemetry 自动集成,服务端返回的 trace-id, span-id 都会自动被记录到日志里,便于后续客户端、服务端协同查问题
  • 可以通过 httpc.service 来获得熔断能力,当服务端有问题,会自动熔断隔离请求,避免浪费时间等待和加剧服务端压力

项目地址 github.com/zeromicro/g…

以上就是go类型安全的http请求示例详解的详细内容,更多关于go类型安全http请求的资料请关注其它相关文章!