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协议请求,在http_to_grpc 插件中完成了 HTTP 客户端 与 gRPC Server 通讯时的协议转换及数据编解码工作,其通讯的过程如下:
接下来通过一个完整的示例向大家演示怎样构建一个 HTTP 请求,并通过 Apinto 进行 HTTP协议 转换成 gRPC 协议请求。在以下的示例中,我们会将 Go 作为 gRPC Server 服务端处理程序,使用 Eolink 作为 HTTP 客户端,发起 HTTP 请求。
以下示例可以在 Apinto 仓库中获取。
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
。
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 通信模式。
#!/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
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
头部返回
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 信息。
cd server && go build -o grpcServer
5. 启动gRPC服务
./grpcServer
Apinto 提供了可视化界面工具 Apinto-Dashboard,降低初学者的使用成本,以下操作均在 Apinto-Dashboard 中进行配置。
为了让使用者快速上手,我们此处演示的教程使用 Apinto 可视化项目 Apinto-Dashboard 进行演示。项目仓库地址请按需点击:
Apinto 项目地址:https://github.com/eolinker/apinto
Apinto-Dashboard 项目地址:https://github.com/eolinker/apinto-dashboard
在左侧导航栏中,点击 基础设施
> 节点插件
,进入节点插件列表。点击 添加插件
点击 拓展ID
单选框,在下拉选项后选中 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": ""
}
填写完成后点击 保存
。
点击保存成功的插件模版,进入到 上线管理
页面,点击 上线
按钮
在左侧导航栏中,点击 上游
,进入上游列表。点击新建 上游服务
填写上游服务信息,目标节点支持多个地址,此处填写上面启动的 gRPC 服务地址。
保存后,点击 上线管理
选中需要上线的集群,点击 上线
在左侧导航栏中,点击 API
,进入API列表后,点击 新建 API
,选中 HTTP
。
填写接口的基本信息,绑定上游,绑定插件模版。
保存后,点击 API 后方的 上线管理
按钮,将 API 上线到对应的集群即可。
在上文中,我们定义了 Hello
方法的功能:
将 HelloRequest
中的 name
字段通过 HelloResponse
的 msg
字段封装成 hello,%s
的结果返回。
将请求的 Header
作为 gRPC
响应的 Trailer
头部返回。
调用结果如下:
目前 Apinto 及其周边项目已经开源,我们希望通过 Apinto 强大的插件拓展能力,用户可像乐高积木一样根据需要自行拓展 Apinto 的插件,以满足不同的业务市场需求。
Apinto 目前属于萌芽阶段,我们希望集合广大开源爱好者的力量,与大家一起讨论方案,接受大家的批评指正,一起将产品打磨完善,做下一个端与端间的Traffic Middleware。这是一个开放和积极的项目,我们诚挚地邀请您一起参与到我们的项目开源工作中。每一个贡献都是有意义的,包括但不限于:
·查找 bugs,取得性能上的提升
·帮助完善文档,提供用户操作体验
·提交你们的 issue,让我们知道您的奇思妙想
·参与自定义插件的开发,丰富 Apinto 的能力
...
欢迎各位开源爱好者参与到 Apinto 项目中,和我们一起为开源事业贡献自己的力量!
Apinto GitHub 地址https://github.com/eolinker/apinto