0
点赞
收藏
分享

微信扫一扫

类型安全的 Go HTTP 请求

前言

对 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​​ 包还有以下特点:

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

项目地址

github.com/zeromicro/g…

欢迎使用 ​​go-zero​​ 并 star 支持我们!


关注『微服务实践』公众号

如果你有 ​​go-zero​​ 的使用心得文章,或者源码学习笔记,欢迎通过公众号联系投稿!

举报

相关推荐

0 条评论