概述

一直在寻找一个好用的 graphql 服务, 之前使用比较多的是 prisma, 但是 prisma1 很久不再维护了, 而 prisma2 仅仅就是一个 ORM, 不是一个完备的系统.

后来, 朋友介绍了一个 graphql 引擎 hasura, 这个是完备的系统,
不仅提供 UI 来创建数据库和表结构. 还有相应的权限控制, 也有对接第三方服务的 Events, 对数据更多控制的 Actions.

为了使用其 graphql 接口, 同样, 和之前使用 prisma 时一样, 用 golang 的 gin 框架作为 gateway, 对 graphql 接口做一层反向代理.
数据的操作基本都使用 hasura 的 graphql 接口, 逻辑比较复杂的, 或者是文件 上传/下载 相关的, 利用 gin 开发 restful 接口.

反向代理代码

对请求的处理

1  r := gin.Default()
2  apiV1 := r.Group("/api/v1")
3  
4  // proxy hasura graphql
5  authRoute.POST("/graphql", ReverseProxy())
 1  func ReverseProxy() gin.HandlerFunc {
 2  
 3    u, err := url.Parse("your hasura graphql endpoint")
 4    if err != nil {
 5        log.Fatal(err)
 6    }
 7  
 8    return func(c *gin.Context) {
 9      director := func(req *http.Request) {
10        req.URL.Scheme = u.Scheme
11        req.URL.Host = u.Host
12        req.URL.Path = u.Path
13        delete(req.Header, "Authorization")
14        delete(req.Header, "Accept-Encoding")
15  
16        req.Header.Set("Content-Type", "application/json; charset=utf-8")
17        fmt.Printf("req header: %v\n", req.Header)
18      }
19  
20      body, err := c.GetRawData()
21      if err != nil {
22        fmt.Printf("get body raw data: %s\n", err)
23      }
24  
25  fmt.Printf("%s\n", string(body))
26  
27      c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
28  
29      proxy := &httputil.ReverseProxy{Director: director}
30      proxy.ModifyResponse = util.RewriteBody
31      proxy.ServeHTTP(c.Writer, c.Request)
32    }
33  }

对返回值的处理

请求的问题解决之后, 就是返回值的处理, 之所以要对返回值进行处理, 是因为 hasura 直接返回的值没有我们自定义的一些 code.
所以需要对返回值进行一些包装, 也就是上面代码中的 RewriteBody

 1  func RewriteBody(resp *http.Response) error {
 2   b, err := ioutil.ReadAll(resp.Body)
 3   if err != nil {
 4     return err
 5   }
 6  
 7   err = resp.Body.Close()
 8   if err != nil {
 9     return err
10   }
11  
12   var gResp GraphqlResp
13   var rResp RestResp
14  
15   err = json.Unmarshal(b, &gResp)
16   if err != nil {
17     return err
18   }
19  
20   if gResp.Errors != nil {
21     rResp = RestResp{
22       Code:    FAIL,
23       Message: gResp.Errors[0].Message,
24       Data:    gResp.Data,
25     }
26   } else {
27     rResp = RestResp{
28       Code:    SUCCESS,
29       Message: "",
30       Data:    gResp.Data,
31     }
32   }
33  
34   nb, err := json.Marshal(&rResp)
35   if err != nil {
36     return err
37   }
38   body := ioutil.NopCloser(bytes.NewReader(nb))
39   resp.Body = body
40   resp.ContentLength = int64(len(nb))
41   resp.Header.Set("Content-Length", strconv.Itoa(len(nb)))
42   return nil
43  }

这样, graphql 接口的返回值也和其他自己写的 restful 接口的返回值格式一致了.

遇到的问题

上面反向代理的代码编写过程中, 遇到一个问题, 弄了解决了大半天才解决. 在请求 graphql 接口时, 始终报这个错误:

invalid character '\x1f' looking for beginning of value

解决方法很简单, 就是上面的这行代码:

1  delete(req.Header, "Accept-Encoding")