gRPC 是什么?
gRPC
是由 Google 开发的一个高性能、通用的开源RPC框架,主要面向移动应用开发且基于 HTTP/2
协议标准而设计,同时支持大多数流行的编程语言。
gRPC
基于 HTTP/2
协议传输, HTTP/2
相比 HTTP1.x
有以下优势:
-
采用二进制格式传输协议,支持多路复用。
-
支持通过同一个连接发送多个并发的请求,支持流式传输。
-
服务器可以对客户端的一个请求发送多个响应。
-
对消息头进行压缩传输,节省消息头占用的网络流量。gRPC使用Protocol Buffers 作为序列化协议。
但同时,gRPC 也有自身的局限性:
浏览器支持有限
当下,不可能直接从浏览器调用 gRPC 服务。gRPC 大量使用HTTP/2 功能,没有浏览器提供支持 gRPC 客户机的 Web 请求所需的控制级别。例如,浏览器不允许调用者要求使用的 HTTP/2,或者提供对底层 HTTP/2 框架的访问。
不是人类可读的
HTTP API 请求以文本形式发送,可以由人读取和创建。默认情况下,gRPC 消息使用 protobuf 编码。虽然 protobuf 的发送和接收效率很高,但它的二进制格式是不可读的。protobuf 需要在 *.proto 文件中指定的消息接口描述才能正确反序列化。需要额外的工具来分析线路上的Protobuf 有效负载,并手工编写请求。
若需要将内部 gRPC 作为接口开放给外部用户或浏览器调用,则需要有第三方代理解决 HTTP 协议转换成 gRPC 协议的问题。
为此,我们在 Apinto 多协议支持的基础上,发布了 HTTP 转 gRPC 插件
(📌eolinker.com:apinto:http_to_grpc)
Apinto 网关如何实现 HTTP 转 gRPC?
Apinto 通过插件的方式支持 HTTP协议转换成gRPC协议请求,在http_to_grpc 插件中完成了 HTTP 客户端 与 gRPC Server 通讯时的协议转换及数据编解码工作,其通讯的过程如下:
接下来通过一个完整的示例向大家演示怎样构建一个 HTTP 请求,并通过 Apinto 进行 HTTP协议 转换成 gRPC 协议请求。在以下的示例中,我们会将 Go 作为 gRPC Server 服务端处理程序,使用 Eolink 作为 HTTP 客户端,发起 HTTP 请求。
以下示例可以在 Apinto 仓库中获取。
配置 Protocol Buffer
1. 创建示例文件 msg.proto
syntax = "proto3";
option go_package = "github.com/eolinker/apinto/example/grpc/demo_service";
package Service;
// HelloRequest 请求
message HelloRequest {
string name = 1;
}
// HelloResponse 响应,成功消息时,msg为json数据
message HelloResponse {
string msg = 1;
string err = 2;
}
该文件定义了示例消息类型,我们在这里定义了一个HelloRequest
和HelloResponse
的message
。
2. 创建示例文件 service.proto
syntax = "proto3";
option go_package = "github.com/eolinker/apinto/example/grpc/demo_service";
import "msg.proto";
package Service;
service Hello {
rpc Hello(HelloRequest) returns (HelloResponse){};
rpc StreamRequest(stream HelloRequest) returns (HelloResponse){};
rpc StreamResponse(HelloRequest) returns (stream HelloResponse) {};
rpc AllStream(stream HelloRequest)returns (stream HelloResponse) {};
}
该文件定义了服务 Hello,引入了第一步创建的文件 msg.proto
,定义了四个方法,包含了一元 RPC、客户端流、服务端流、双向流四种 gRPC 通信模式。
配置服务端程序
1. 创建自动生成 gRPC 文件脚本(grpc.sh)
#!/bin/bash
OUT_DIR=demo_service
set -e
mkdir -p $OUT_DIR
protoc --grpc-gateway_opt logtostderr=true \
--grpc-gateway_opt paths=source_relative \
--grpc-gateway_opt generate_unbound_methods=true \
--grpc-gateway_out=$OUT_DIR \
--go_out=$OUT_DIR --go_opt=paths=source_relative \
--go-grpc_out=$OUT_DIR --go-grpc_opt=paths=source_relative *.proto
该脚本将生成 gRPC 客户端/服务端调用相关代码,并将其存储到demo_service
目录下。
执行grpc.sh
,生成服务端 Go 原始消息和服务/客户端存根。
./grpc.sh
2.实现服务端处理程序接口
package main
import (
"fmt"
service "github.com/eolinker/apinto/example/grpc/demo_service"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc"
"golang.org/x/net/context"
)
var _ service.HelloServer = (*Server)(nil)
type Server struct {
service.UnimplementedHelloServer
}
func NewServer() *Server {
return &Server{}
}
func (s *Server) Hello(ctx context.Context, request *service.HelloRequest) (*service.HelloResponse, error) {
trailingMD, ok := metadata.FromIncomingContext(ctx)
if ok {
grpc.SetTrailer(ctx, trailingMD)
}
return &service.HelloResponse{
Msg: fmt.Sprintf("hello,%s", request.Name),
}, nil
}
上述代码重新定义了Hello方法:
-
将
HelloRequest中
的name
字段通过HelloResponse
的msg
字段封装成hello,%s
的结果返回 -
将请求的 Header 作为 gRPC 响应的
Trailer
头部返回
3.定义 gRPC 服务端入口文件
package main
import (
"fmt"
"net"
"google.golang.org/grpc/reflection"
service "github.com/eolinker/apinto/example/grpc/demo_service"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
func main() {
Parse()
err := listen()
if err != nil {
fmt.Println(err)
return
}
}
func listen() error {
address := fmt.Sprintf("%s:%d", BindIP, ListenPort)
l, err := net.Listen("tcp", address)
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
var opts []grpc.ServerOption
if TlsKey != "" && TlsPem != "" {
creds, err := credentials.NewServerTLSFromFile(TlsPem, TlsKey)
if err != nil {
grpclog.Fatalf("Failed to generate credentials %v", err)
}
opts = append(opts, grpc.Creds(creds))
}
// 实例化grpc Server
s := grpc.NewServer(opts...)
ser := NewServer()
// 注册HelloService
service.RegisterHelloServer(s, ser)
// 开启grpc反射
reflection.Register(s)
fmt.Println("Listen on " + address)
return s.Serve(l)
}
在此处,gRPC 服务端开启了 gRPC 反射,配置 Apinto 网关时,可选择绑定具体的 Protobuf 资源,也可以直接启用反射,动态获取 gRPC 服务端的 Protobuf 信息。
4. 编译gRPC服务器程序
cd server && go build -o grpcServer
5. 启动gRPC服务
./grpcServer
配置 Apinto HTTP 转 gRPC 插件
Apinto 提供了可视化界面工具 Apinto-Dashboard,降低初学者的使用成本,以下操作均在 Apinto-Dashboard 中进行配置。
为了让使用者快速上手,我们此处演示的教程使用 Apinto 可视化项目 Apinto-Dashboard 进行演示。项目仓库地址请按需点击:
-
Apinto 项目地址:https://github.com/eolinker/apinto
-
Apinto-Dashboard 项目地址:https://github.com/eolinker/apinto-dashboard
1.新增节点插件
在左侧导航栏中,点击基础设施
> 节点插件
,进入节点插件列表。点击添加插件
点击拓展ID
单选框,在下拉选项后选中 http_to_grpc
插件,填写插件名称信息,点击保存
2. 发布节点插件
在左侧导航栏中,点击基础设施
> 集群
,进入集群列表。选中需要发布节点插件的集群,点击进入
点击节点插件
选项卡,选中插件后方的扳手按钮
在弹出框中,将状态改成启用
,点击 提交
。
在节点插件列表,点击 发布
在弹出框中点击 提交
注意:该步骤非必需,仅在节点插件有改动时(新增、删除、修改节点插件顺序等),才需要重新在集群中发布上线。
3. 新增 API 操作模版,绑定 http_to_grpc 插件
在左侧导航栏中,点击公共配置
> API 操作模版
,进入操作模版列表后,点击新建模版
。
点击添加插件
在弹出框中选中 http_to_grpc
,填写插件配置
配置参数说明
-
当服务名称不填时,则默认使用
HTTP
请求路径的第一个/和第二个/之间的值作为服务名 -
当方法名称不填时,则默认使用
HTTP
请求路径的第二个/和第三个/之间的值作为服务名 -
即:若
HTTP
请求路径上/Service.Hello/Hello
,则此时服务名称为Service.Hello
,方法名称为Hello
示例配置
由于 gRPC
服务端示例中,我们开启了 gRPC
反射,因此,在配置插件时,reflect
设置为 true
{
"authority": "",
"format": "json",
"headers": {},
"method": "",
"reflect": true,
"service": ""
}
填写完成后点击保存
。
点击保存成功的插件模版,进入到 上线管理
页面,点击 上线
按钮
4. 新增上游服务,填写 gRPC 服务地址
在左侧导航栏中,点击 上游
,进入上游列表。点击新建上游服务
填写上游服务信息,目标节点支持多个地址,此处填写上面启动的 gRPC 服务地址。
保存后,点击上线管理
选中需要上线的集群,点击上线
5. 新增 API,绑定 API 操作模版,绑定 gRPC 上游服务
在左侧导航栏中,点击 API
,进入API列表后,点击新建API
,选中 HTTP
。
填写接口的基本信息,绑定上游,绑定插件模版。
保存后,点击 API 后方的上线管理
按钮,将 API 上线到对应的集群即可。
请求 Service.Hello 服务的 Hello 方法
在上文中,我们定义了Hello
方法的功能:
-
将
HelloRequest
中的name
字段通过HelloResponse
的msg
字段封装成hello,%s
的结果返回。 -
将请求的
Header
作为gRPC
响应的Trailer
头部返回。
调用结果如下:
写在最后
目前 Apinto 及其周边项目已经开源,我们希望通过 Apinto 强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展 Apinto 的插件,以满足不同的业务市场需求。
Apinto 目前属于萌芽阶段,我们希望集合广大开源爱好者的力量,与大家一起讨论方案,接受大家的批评指正,一起将产品打磨完善,做下一个端与端间的Traffic Middleware。这是一个开放和积极的项目,我们诚挚地邀请您一起参与到我们的项目开源工作中。每一个贡献都是有意义的,包括但不限于:
·查找 bugs,取得性能上的提升
·帮助完善文档,提供用户操作体验
·提交你们的 issue,让我们知道您的奇思妙想
·参与自定义插件的开发,丰富 Apinto 的能力
...
欢迎各位开源爱好者参与到 Apinto 项目中,和我们一起为开源事业贡献自己的力量!