1. 程式人生 > 實用技巧 >hasura的golang反向代理

hasura的golang反向代理

概述

一直在尋找一個好用的 graphql 服務, 之前使用比較多的是 prisma, 但是 prisma1 很久不再維護了, 而 prisma2 僅僅就是一個 ORM, 不是一個完備的系統.

後來, 朋友介紹了一個 graphql 引擎 hasura, 這個是完備的系統,
不僅提供 UI 來建立資料庫和表結構. 還有相應的許可權控制, 也有對接第三方服務的 Events, 對資料更多控制的 Actions.

為了使用其 graphql 介面, 同樣, 和之前使用 prisma 時一樣, 用 golang 的 gin 框架作為 gateway, 對 graphql 介面做一層反向代理.
資料的操作基本都使用 hasura 的 graphql 介面, 邏輯比較複雜的, 或者是檔案 上傳/下載 相關的, 利用 gin 開發 restful 介面.

反向代理程式碼

對請求的處理

  1. 路由部分

    1  r := gin.Default()
    2  apiV1 := r.Group("/api/v1")
    3  
    4  // proxy hasura graphql
    5  authRoute.POST("/graphql", ReverseProxy())
    
  2. 處理請求的 body, 並轉發到 hasura 上

     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  }
    

    這裡獲取了請求的 body, 但是沒有做任何處理, 直接轉發給 hasura. 這裡可以根據實際情況加入自己的處理.

對返回值的處理

請求的問題解決之後, 就是返回值的處理, 之所以要對返回值進行處理, 是因為 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")