项目地址: https://github.com/EDDYCJY/go-grpc-example
前言
接口需要提供给其他业务组访问, 但是 RPC 协议不同无法内调, 对方问能否走 HTTP 接口, 怎么办?
微信 (公众号, 小程序) 等第三方回调接口只支持 HTTP 接口, 怎么办
我相信你在实际工作中都会遇到如上问题, 在 gRPC 中都是有解决方案的, 本章节将会进行介绍
为什么可以同时提供 HTTP 接口
关键一点, gRPC 的协议是基于 HTTP/2 的, 因此应用程序能够在单个 TCP 端口上提供 HTTP/1.1 和 gRPC 接口服务(两种不同的流量)
怎么同时提供 HTTP 接口
检测协议
- if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
- server.ServeHTTP(w, r)
- } else {
- mux.ServeHTTP(w, r)
- }
流程
检测请求协议是否为 HTTP/2
判断 Content-Type 是否为 application/grpc(gRPC 的默认标识位)
根据协议的不同转发到不同的服务处理
gRPC
TLS
在前面的章节, 为了便于展示因此没有简单封装
在本节需复用代码, 重新封装了, 可详见:
目录结构
新建 simple_http_client,simple_http_server 目录, 目录结构如下:
go-grpc-example
├── client
│ ├── simple_client
│ ├── simple_http_client
│ └── stream_client
├── conf
├── pkg
│ └── gtls
├── proto
├── server
│ ├── simple_http_server
│ ├── simple_server
│ └── stream_server
Server
在 simple_http_server 目录下新建 server.go, 写入文件内容:
- package main
- import (
- "context"
- "log"
- "net/http"
- "strings"
- "github.com/EDDYCJY/go-grpc-example/pkg/gtls"
- pb "github.com/EDDYCJY/go-grpc-example/proto"
- "google.golang.org/grpc"
- )
- type SearchService struct{}
- func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) {
- return &pb.SearchResponse{Response: r.GetRequest() + "HTTP Server"}, nil
- }
- const PORT = "9003"
- func main() {
- certFile := "../../conf/server/server.pem"
- keyFile := "../../conf/server/server.key"
- tlsServer := gtls.Server{
- CertFile: certFile,
- KeyFile: keyFile,
- }
- c, err := tlsServer.GetTLSCredentials()
- if err != nil {
- log.Fatalf("tlsServer.GetTLSCredentials err: %v", err)
- }
- mux := GetHTTPServeMux()
- server := grpc.NewServer(grpc.Creds(c))
- pb.RegisterSearchServiceServer(server, &SearchService{})
- http.ListenAndServeTLS(":"+PORT,
- certFile,
- keyFile,
- http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
- server.ServeHTTP(w, r)
- } else {
- mux.ServeHTTP(w, r)
- }
- return
- }),
- )
- }
- func GetHTTPServeMux() *http.ServeMux {
- mux := http.NewServeMux()
- mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- w.Write([]byte("eddycjy: go-grpc-example"))
- })
- return mux
- }
http.NewServeMux: 创建一个新的 ServeMux,ServeMux 本质上是一个路由表. 它默认实现了 ServeHTTP, 因此返回 Handler 后可直接通过 HandleFunc 注册 pattern 和处理逻辑的方法
http.ListenAndServeTLS: 可简单的理解为提供监听 HTTPS 服务的方法, 重点的协议判断转发, 也在这里面
其实, 你理解后就会觉得很简单, 核心步骤: 判断 -> 转发 -> 响应. 我们改变了前两步的默认逻辑, 仅此而已
Client
在 simple_http_server 目录下新建 client.go, 写入文件内容:
- package main
- import (
- "context"
- "log"
- "google.golang.org/grpc"
- "github.com/EDDYCJY/go-grpc-example/pkg/gtls"
- pb "github.com/EDDYCJY/go-grpc-example/proto"
- )
- const PORT = "9003"
- func main() {
- tlsClient := gtls.Client{
- ServerName: "go-grpc-example",
- CertFile: "../../conf/server/server.pem",
- }
- c, err := tlsClient.GetTLSCredentials()
- if err != nil {
- log.Fatalf("tlsClient.GetTLSCredentials err: %v", err)
- }
- conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c))
- if err != nil {
- log.Fatalf("grpc.Dial err: %v", err)
- }
- defer conn.Close()
- client := pb.NewSearchServiceClient(conn)
- resp, err := client.Search(context.Background(), &pb.SearchRequest{
- Request: "gRPC",
- })
- if err != nil {
- log.Fatalf("client.Search err: %v", err)
- }
- log.Printf("resp: %s", resp.GetResponse())
- }
验证
- gRPC Client
- $ go run client.go
- 2018/10/04 14:56:56 resp: gRPC HTTP Server
HTTP/1.1 访问
总结
通过本章节, 表面上完成了同端口提供双服务的功能, 但实际上, 应该是加深了 HTTP/2 的理解和使用, 这才是本质
拓展
如果你有一个需求, 是要同时提供 RPC 和 RESTful JSON API 两种接口的, 不要犹豫, 点进去: gRPC + gRPC Gateway 实践
问题
你以为这个方案就万能了吗, 不. Envoy Proxy 的支持就不完美, 无法同时监听一个端口的两种流量
参考
本系列示例代码
https://github.com/EDDYCJY/go-grpc-example
系列目录
带入 gRPC:gRPC 及相关介绍
带入 gRPC:gRPC Client and Server
带入 gRPC:gRPC Streaming, Client and Server
带入 gRPC:TLS 证书认证
带入 gRPC: 基于 CA 的 TLS 证书认证
带入 gRPC:Unary and Stream interceptor
来源: https://segmentfault.com/a/1190000016601836