Skip to content

Instantly share code, notes, and snippets.

@taoso
Created March 22, 2022 14:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taoso/adb850fbcf11580bd00e3ca058361db3 to your computer and use it in GitHub Desktop.
Save taoso/adb850fbcf11580bd00e3ca058361db3 to your computer and use it in GitHub Desktop.
package main
import (
"bytes"
"context"
"crypto/tls"
"encoding/binary"
"fmt"
"io"
"net"
"net/http"
"strconv"
"github.com/taoso/foo/hello"
"golang.org/x/net/http2"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/proto"
)
var plainTextH2Transport = &http2.Transport{
AllowHTTP: true,
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
}
type grpcClient struct {
http.Client
}
func NewGrpcClient() grpcClient {
return grpcClient{http.Client{Transport: plainTextH2Transport}}
}
func (c *grpcClient) DoUnary(ctx context.Context, api string, in, out proto.Message) (resp *http.Response, err error) {
pb, err := proto.Marshal(in)
if err != nil {
return
}
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests
bs := make([]byte, 5)
// 第一个字节默认为0,表示不压缩
// 后四个字节保存 pb 消息长度
binary.BigEndian.PutUint32(bs[1:], uint32(len(pb)))
body := io.MultiReader(bytes.NewReader(bs), bytes.NewReader(pb))
req, err := http.NewRequest("POST", api, body)
if err != nil {
return
}
req = req.WithContext(ctx)
req.Header.Set("trailers", "TE")
req.Header.Set("content-type", "application/grpc+proto")
if resp, err = c.Do(req); err != nil {
return
}
defer resp.Body.Close()
if pb, err = io.ReadAll(resp.Body); err != nil {
return
}
// Unary 调用出错不会返回 body
// 此时 grpc-status 跟 http 状态码一同返回
// 不能通过 Trailer 读取
if status := resp.Header.Get("grpc-status"); status != "" {
var c int
if c, err = strconv.Atoi(status); err != nil {
return
}
err = grpc.Errorf(codes.Code(c), resp.Header.Get("grpc-message"))
return
}
// 因为是 Unary,可以直接跳过前五个字节
err = proto.Unmarshal(pb[5:], out)
return
}
func main() {
c := NewGrpcClient()
in := &hello.HelloRequest{Name: "涛叔"}
out := &hello.HelloReply{}
api := "http://127.0.0.1:8080/hello.Greeter/SayHello"
resp, err := c.DoUnary(context.Background(), api, in, out)
if err != nil {
panic(err)
}
fmt.Println(out.Message, resp.Trailer.Get("trace-bin"))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment