您的位置 首页 golang

Go 每日一库之 twirp:又一个 RPC 框架

简介

twirp是一个基于 Google Protobuf 的 RPC 框架。twirp通过在.proto文件中定义服务,然后自动生产服务器和客户端的代码。让我们可以将更多的精力放在业务逻辑上。咦?这不就是 gRPC 吗?不同的是,gRPC 自己实现了一套 HTTP 服务器和网络传输层,twirp 使用标准库net/http。另外 gRPC 只支持 HTTP/2 协议,twirp 还可以运行在 HTTP 1.1 之上。同时 twirp 还可以使用 JSON 格式交互。当然并不是说 twirp 比 gRPC 好,只是多了解一种框架也就多了一个选择

快速使用

首先需要安装 twirp 的代码生成插件:

 $ go get  github .com/twitchtv/twirp/protoc-gen-twirp  

上面命令会在$GOPATH/bin目录下生成可执行程序protoc-gen-twirp。我的习惯是将$GOPATH/bin放到 PATH 中,所以可在任何地方执行该命令。

接下来安装 protobuf 编译器,直接到 GitHub 上下载编译好的二进制程序放到 PATH 目录即可。

最后是 Go 语言的 protobuf 生成插件:

 Go  get github.com/golang/protobuf/protoc-gen-go  

同样地,命令protoc-gen-go会安装到$GOPATH/bin目录中。

本文代码采用 Go Modules 。先创建目录,然后初始化:

 $ mkdir twirp && cd twirp
$ go mod init github.com/darjun/go-daily-lib/twirp  

接下来,我们开始代码编写。先编写.proto文件:

 syntax = "proto3";
option go_package = "proto";

service  echo  {
  rpc Say( Request ) returns (Response);
}

message Request {
  string text = 1;
}

message Response {
  string text = 2;
}  

我们定义一个service实现 echo 功能,即发送什么就返回什么。切换到echo.proto所在目录,使用protoc命令生成代码:

 $ protoc --twirp_out=. --go_out=. ./echo.proto  

上面命令会生成echo.pb.go和echo.twirp.go两个文件。前一个是 Go Protobuf 文件,后一个文件中包含了twirp的服务器和客户端代码。

然后我们就可以编写服务器和客户端程序了。服务器:

 package main

import (
  "context"
  "net/http"

  "github.com/darjun/go-daily-lib/twirp/get-started/proto"
)

type  Server   struct {}

func (s *Server) Say( ctx  context.Context, request *proto.Request) (*proto.Response, error) {
  return &proto.Response{Text: request.GetText()}, nil
}

func main() {
  server := &Server{}
  twirpHandler := proto.NewEchoServer(server, nil)

  http.ListenAndServe(":8080", twirpHandler)
}  

使用自动生成的代码,我们只需要 3 步即可完成一个 RPC 服务器:

  1. 定义一个结构,可以存储一些状态。让它实现我们定义的service接口;
  2. 创建一个该结构的对象,调用生成的New{{ServiceName}}Server方法创建net/http需要的处理器,这里的ServiceName为我们的服务名;
  3. 监听端口。

客户端:

 package main

import (
  "context"
  "fmt"
  "log"
  "net/http"

  "github.com/darjun/go-daily-lib/twirp/get-started/proto"
)

func main() {
  client := proto.NewEchoProtobufClient("#34;, &http.Client{})

  response, err := client.Say(context.Background(), &proto.Request{Text: "Hello World"})
  if err != nil {
    log.Fatal(err)
  }
  fmt.Printf("response:%s\n", response.GetText())
}  

twirp也生成了客户端相关代码,直接调用NewEchoProtobufClient连接到对应的服务器,然后调用rpc请求。

开启两个控制台,分别运行服务器和客户端程序。服务器:

 $ cd server && go run main.go  

客户端:

 $ cd client && go run main.go  

正确返回结果:

 response:Hello World  

为了便于对照,下面列出该程序的目录结构。也可以去我的 GitHub 上查看示例代码:

 get-started
├── client
│   └── main.go
├── proto
│   ├── echo.pb.go
│   ├── echo.proto
│   └── echo.twirp.go
└── server
    └── main.go  

JSON 客户端

除了使用 Protobuf,twirp还支持 JSON 格式的请求。使用也非常简单,只需要在创建Client时将NewEchoProtobufClient改为NewEchoJSONClient即可:

 func main() {
  client := proto.NewEchoJSONClient("#34;, &http.Client{})

  response, err := client.Say(context.Background(), &proto.Request{Text: "Hello World"})
  if err != nil {
    log.Fatal(err)
  }
  fmt.Printf("response:%s\n", response.GetText())
}  

Protobuf Client 发送的请求带有Content-Type: application/protobuf的 Header ,JSON Client 则设置Content-Type为application/json。服务器收到请求时根据Content-Type来区分请求类型:

 // proto/echo.twirp.go
unc (s *echoServer) serveSay(ctx context.Context, resp http.ResponseWriter, req *http.Request) {
  header := req.Header.Get("Content-Type")
  i := strings.Index(header, ";")
  if i == -1 {
    i = len(header)
  }
  switch strings.TrimSpace(strings.ToLower(header[:i])) {
  case "application/json":
    s.serveSayJSON(ctx, resp, req)
  case "application/protobuf":
    s.serveSayProtobuf(ctx, resp, req)
  default:
    msg := fmt.Sprintf("unexpected Content-Type: %q", req.Header.Get("Content-Type"))
    twerr := badRouteError(msg, req.Method, req.URL.Path)
    s.writeError(ctx, resp, twerr)
  }
}  

提供其他 HTTP 服务

实际上,twirpHandler只是一个http的处理器,正如其他千千万万的处理器一样,没什么特殊的。我们当然可以挂载我们自己的处理器或处理器函数(概念有不清楚的可以参见我的《Go Web 编程》系列文章:

 type Server struct{}

func (s *Server) Say(ctx context.Context, request *proto.Request) (*proto.Response, error) {
  return &proto.Response{Text: request.GetText()}, nil
}

func greeting(w http.ResponseWriter, r *http.Request) {
  name := r.FormValue("name")
  if name == "" {
    name = "world"
  }

  w.Write([]byte("hi," + name))
}

func main() {
  server := &Server{}
  twirpHandler := proto.NewEchoServer(server, nil)

  mux := http.NewServeMux()
  mux.Handle(twirpHandler.PathPrefix(), twirpHandler)
  mux.HandleFunc("/greeting", greeting)

  err := http.ListenAndServe(":8080", mux)
  if err != nil {
    log.Fatal(err)
  }
}  

上面程序挂载了一个简单的/greeting请求,可以通过浏览器来请求地址。twirp的请求会挂载到路径twirp/{{ServiceName}}这个路径下,其中ServiceName为服务名。上面程序中的PathPrefix()会返回/twirp/Echo。

客户端:

 func main() {
  client := proto.NewEchoProtobufClient("#34;, &http.Client{})

  response, _ := client.Say(context.Background(), &proto.Request{Text: "Hello World"})
  fmt.Println("echo:", response.GetText())

  httpResp, _ := http.Get("#34;)
  data, _ := ioutil.ReadAll(httpResp.Body)
  httpResp.Body.Close()
  fmt.Println("greeting:", string(data))

  httpResp, _ = http.Get("#34;)
  data, _ = ioutil.ReadAll(httpResp.Body)
  httpResp.Body.Close()
  fmt.Println("greeting:", string(data))
}  

先运行服务器,然后执行客户端程序:

 $ go run main.go
echo: Hello World
greeting: hi,world
greeting: hi,dj  

发送自定义的 Header

默认情况下,twirp实现会发送一些 Header。例如上面介绍的,使用Content-Type辨别客户端使用的协议格式。有时候我们可能需要发送一些自定义的 Header,例如token。twirp提供了WithHTTPRequestHeaders方法实现这个功能,该方法返回一个context.Context。发送时会将保存在该对象中的 Header 一并发送。类似地,服务器使用WithHTTPResponseHeaders发送自定义 Header。

由于twirp封装了net/http,导致外层拿不到原始的http.Request和http.Response对象,所以 Header 的读取有点麻烦。在服务器端,NewEchoServer返回的是一个http.Handler,我们加一层中间件读取http.Request。看下面代码:

 type Server struct{}

func (s *Server) Say(ctx context.Context, request *proto.Request) (*proto.Response, error) {
  token := ctx.Value("token").(string)
  fmt.Println("token:", token)

  err := twirp.SetHTTPResponseHeader(ctx, "Token-Lifecycle", "60")
  if err != nil {
    return nil, twirp.InternalErrorWith(err)
  }
  return &proto.Response{Text: request.GetText()}, nil
}

func WithTwirpToken(h http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    token := r.Header.Get("Twirp-Token")
    ctx = context.WithValue(ctx, "token", token)
    r = r.WithContext(ctx)

    h.ServeHTTP(w, r)
  })
}

func main() {
  server := &Server{}
  twirpHandler := proto.NewEchoServer(server, nil)
  wrapped := WithTwirpToken(twirpHandler)

  http.ListenAndServe(":8080", wrapped)
}  

上面程序给客户端返回了一个名为Token-Lifecycle的 Header。客户端代码:

 func main() {
  client := proto.NewEchoProtobufClient("#34;, &http.Client{})

  header := make(http.Header)
  header.Set("Twirp-Token", "test-twirp-token")

  ctx := context.Background()
  ctx, err := twirp.WithHTTPRequestHeaders(ctx, header)
  if err != nil {
    log.Fatalf("twirp error setting headers: %v", err)
  }

  response, err := client.Say(ctx, &proto.Request{Text: "Hello World"})
  if err != nil {
    log.Fatalf("call say failed: %v", err)
  }
  fmt.Printf("response:%s\n", response.GetText())
}  

运行程序,服务器正确获取客户端传过来的 token。

请求路由

我们前面已经介绍过了,twirp的Server实际上也就是一个http.Handler,如果我们知道了它的挂载路径,完全可以通过浏览器或者curl之类的工具去请求。我们启动get-started的服务器,然后用curl命令行工具去请求:

 $ curl --request "POST" \
  --location "#34; \
  --header "Content-Type:application/json" \
  --data '{"text":"hello world"}'\
  --verbose
{"text":"hello world"}  

这在调试的时候非常有用。

总结

本文介绍了 Go 的一个基于 Protobuf 生成代码的 RPC 框架,非常简单,小巧,实用。twirp对许多常用的编程语言都提供了支持。可以作为 gRPC 等的备选方案考虑。

大家如果发现好玩、好用的 Go 语言库,欢迎到 Go 每日一库 GitHub 上提交 issue

参考

  1. twirp GitHub:
  2. twirp 官方文档:
  3. Go 每日一库 GitHub:

文章来源:智云一二三科技

文章标题:Go 每日一库之 twirp:又一个 RPC 框架

文章地址:https://www.zhihuclub.com/88487.shtml

关于作者: 智云科技

热门文章

评论已关闭

28条评论

  1. This month, the benign endometrial changes associated with tamoxifen use are reviewed Cataracts and cataract surgery were also less common in the raloxifene group

  2. Department of Community Health Sciences, Faculty of Medicine, University of Calgary, Calgary, Canada

  3. prograf para que sirve el provera 5mg Experts say the easiest way to stop abuse is to make the disabled pay the meter, especially those not in wheelchairs

  4. Backed with skilled workers and experience we trade in supplying the finest and the best quality of Xarelto 10 Mg Tablet

  5. permethrin amoxicillin for strep throat not working First, Winmill wants the Nez Perce Clearwater National Forests to assess impacts the enormous 225 foot long, 640, 000 pound water evaporator would have on the route and surrounding land, then engage the Nez Perce Tribe over its concerns

  6. Based on these results, we suggest that patients with an IHC score less than 18 do not benefit from additional molecular testing to confirm that they are unlikely to benefit from chemotherapy

  7. viagra avanafil ervaring They are not twins This is particularly important, given the increased life expectancy of these patients

  8. The ongoing phase II BYLieve trial is evaluating second line alpelisib in combination with either fulvestrant or letrozole in PIK3CA mutant patients who had previously received a CDK4 6 inhibitor

  9. Dim supplementation improves the estrogen ratio in a favorable direction that protects both men and women without the negative effect of raising 4 hydroxyestrogen, as seen with i3c

  10. 83 The prognosis may not be as good for cats with concurrent disease, such as co infection with FeLV We found that 63 genes were altered at a magnitude 1

  11. You ll have more pain, stiffness and even less range of motion than in stage 2, and you may start to see physical changes The robitussin is for a chesty cough and contains only GUAIFENESIN

  12. FASTING GLUCOSE TEST Blood is drawn from a vein does a s blood sugar drop if not eating in the patient s best diet to reduce blood sugar levels Blood Sugar Monitor arm after a period at least eight hours when the patient has not eaten, does a s blood sugar drop if not eating usually in the morning does a s blood sugar drop if not eating

  13. Gonadotropin releasing hormone agonists may not achieve OFS for a small number of women

  14. noni juice increases and ethacrynic acid decreases serum potassium You will be given a small dose of a radioactive tracer through an intravenous IV line

  15. 7 Mb, with its origin in P5 as for AZFb and its termination at the distal end of P1 101

  16. The fourth, outermost, membrane derives from the host s endomembrane Mitogen activated protein kinase MAPK cascades is a finely controlled signal transduction pathway consisting of phosphoserine threonine kinases that mediates the cellular response to external signals 73

  17. Furosemide Lasix side effects are not always fully described Now if the itinerary changes temporarily, he should also call and say

  18. Contact lenses aren t recommended after LASIK surgery Controlled Release 1995, 35, 1 21; Verma et al

网站地图