Golang-gRPC 服务搭建
本文记录Golang-gRPC、grpc-web + nginx 搭建过程,以及中途遇到的一些问题 项目代码
一、理解什么是gRPC
gRPC的描述网上已经很多了gRPC, 大致涉及两个知识
(1)、RPC Remote Procedure Call
RPC的核心是目的是: 本地调用远程(跨内存可访问的)方法。
- RPC框架: 开箱即用的实现了RPC调用的框架,其中开源框架如 阿里Dubbo、Google gRPC、Facebook Thrift
- 远程通信协议: REST(HTTP JSON), SOAP(HTTP XML), gRPC(HTTP2 protobuf)
- 序列化/反序列化: 文本(XML、JSON)与二进制(Java原生的、Hessian、protobuf、Thrift、Avro、Kryo、MessagePack
(2) protobuf
- 足够简单
- 序列化后体积很小:消息大小只需要XML的1/10 ~ 1/3
- 解析速度快:解析速度比XML快20 ~ 100倍
- 多语言支持
- 更好的兼容性,Protobuf设计的一个原则就是要能够很好的支持向下或向上兼容
二、 搭建hello world
实现步骤
- 通过protobuf来定义接口和数据类型
- 生成接口代码
- 编写gRPC server端代码
- 编写gRPC client端代码
目录结构如下(推荐使用gihub上的一个 golang 项目标准框架 搭建自己的项目)
1. 定义接口和数据类型
syntax = "proto3";
package api;
// 这里可以写服务注释
service HelloWorldService {
// 这里可以写方法注释
rpc SayHello (HelloRequest) returns (HelloResponse) {}
}
// 这里可以写请求结构注释
message HelloRequest {
// 这里可以写参数注释
string name = 1;
}
// 这里可以写响应结构注释
message HelloResponse {
// 这里可以写参数注释
string message = 1;
}
2. 生成接口代码
- 下载 protobuf 生成工具 protoc, 将其中的可执行文件放在PATH中方便使用
mv protoc-3.11.4-osx-x86_64/bin/protoc /usr/local/bin
- 下载生成go语言接口代码的插件 protoc-gen-go
go get -u github.com/golang/protobuf/protoc-gen-go
- 编写api/proto/v1/init.go, 作为生成脚本(也可以直接在命令行执行)
package api
//go:generate protoc -I. --go_out=plugins=grpc:. ./hello.proto
func init() {}
- 执行后将得到文件 hello.pb.go 里面是hello服务相关描述和接口申明
3. 编写gRPC server端代码
- api/service/hello/hello_service.go
package hello
type Service struct {}
- api/service/hello/say_hello.go
package hello
import (
"context"
api "grpc-demo/api/proto/v1"
)
func (hello Service) SayHello (_ context.Context, params *api.HelloRequest) (res *api.HelloResponse, err error) {
res = &api.HelloResponse{
Message: "server response: hello " + params.Name,
}
return res, nil
}
- api/server.go
package api
import (
"google.golang.org/grpc"
api "grpc-demo/api/proto/v1"
"grpc-demo/api/service/hello"
"log"
"net"
"strconv"
)
func RungGRPCServer (grpcPort int16) {
// 启动一个grpc server
grpcServer := grpc.NewServer()
// 绑定服务实现 RegisterHelloWorldServiceServer
api.RegisterHelloWorldServiceServer(grpcServer, &hello.Service{})
// 监听端口
listen, e := net.Listen("tcp", ":"+strconv.Itoa(int(grpcPort)))
if e != nil {
log.Fatal(e)
}
// 绑定监听端口
log.Printf("serve gRPC server: 127.0.0.1:%d", grpcPort)
if err := grpcServer.Serve(listen); err != nil {
log.Printf("failed to serve: %v", err)
return
}
}
- main.go
package main
import "grpc-demo/api"
func main () {
c := make(chan bool, 1)
go api.RungGRPCServer(9999)
<-c
}
- 启动程序
4. 编写gRPC client端代码
- examples/client/go/mian.go
package main
import (
"context"
"google.golang.org/grpc"
api "grpc-demo/api/proto/v1"
"log"
"os"
)
const (
address = "localhost:9999"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := api.NewHelloWorldServiceClient(conn)
name := "world"
if len(os.Args) > 1 {
name = os.Args[1]
}
r, err := c.SayHello(context.Background(), &api.HelloRequest{Name: name})
if err != nil {
log.Fatalf("call say hello fail: %v", err)
}
log.Println(r.Message)
}
现在,一个简单的gRPC程序就完成了
三、配合 grpc-web
grpc-web 是针对web端的grpcClient 的项目,解决目前浏览器不能直接支持grpc协议的方案,需配合代理服务一起使用, grpc-web 搭建分为以下几步
- 生成grpc-web client代码
- 配置代理服务
参考文章 grpc-web grpc-web-nginx
1. 生成grpc-web client代码
- 1 和go语言一样也是要下载一个插件,下载生成js语言接口代码的插件protoc-gen-grpc-web
# 将下载后的内容移动到bin路径中方便使用
mv ~/Downloads/protoc-gen-grpc-web-1.0.7-darwin-x86_64 /usr/local/bin/protoc-gen-grpc-web
# 增加可执行权限
chmod +x /usr/local/bin/protoc-gen-grpc-web
- 2 建立examples/client/js目录,并将申明文件proto移动到js项目中,结构如下
- 3 编写生成js脚本examples/client/ts/protogen.sh方便执行(也可以直接执行其中命令)
#!/bin/bash
PROJ_ROOT="$(dirname "$(dirname "$(readlink "$0")")")"
protoc \
-I ${PROJ_ROOT}/src/api/v1 \
--js_out=import_style=commonjs:${PROJ_ROOT}/src/api/v1 \
--grpc-web_out=import_style=typescript,mode=grpcweb:${PROJ_ROOT}/src/api/v1 \
${PROJ_ROOT}/src/api/v1/hello.proto
- 执行后将会有2个文件生成,如下图
- 初始化前端项目, 配置package.json
{
"name": "js",
"version": "1.0.0",
"dependencies": {},
"main": "src/main.js",
"devDependencies": {
"google-protobuf": "^3.11.4",
"grpc-web": "^1.0.7",
"webpack": "^4.16.5",
"webpack-cli": "^3.1.0"
}
}
- examples/client/js/src/main.js
const { HelloRequest } = require('./api/v1/hello_pb');
const { HelloWorldServiceClient } = require('./api/v1/hello_grpc_web_pb');
// 注意这个端口是代理服务器的端口,不是grpc的端口
var client = new HelloWorldServiceClient('http://localhost:8199',
null, null);
// simple unary call
var request = new HelloRequest();
request.setName('World');
client.sayHello(request, {}, (err, response) => {
document.getElementById("response").innerHTML = response.getMessage();
});
- examples/client/js/index.html
gRPC-Web Demode
error get message
- 构建前端项目
yarn install
npx webpack src/main.js
- 在运行之前还需要配置代理服务器
2. 配置代理服务
- 代理服务选型 envoy 或 nginx,envoy配置官方示例中有 查看配置, 其他示例官方也有提供, 本文使用nginx配置(本地搭建)。
- grpc.proxy.conf
server {
listen 8199;
server_name _;
access_log /tmp/grpc.log;
error_log /tmp/grpc.log debug;
location ~ \.(html|js)$ {
root /var/www/html;
}
location / {
# 重点!!需要将Content-Type更改为 application/grpc
# grpc-web过来的是application/grpc-web+proto || application/grpc-web+text (取决于生成js代码时grpc-web_out 的mode选项,本文用grpcweb 则为application/grpc-web+proto)
grpc_set_header Content-Type application/grpc;
grpc_pass localhost:9999;
# 因浏览器有跨域限制,这里直接在nginx支持跨域
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Transfer-Encoding,Custom-Header-1,X-Accept-Content-Transfer-Encoding,X-Accept-Response-Streaming,X-User-Agent,X-Grpc-Web';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Transfer-Encoding,Custom-Header-1,X-Accept-Content-Transfer-Encoding,X-Accept-Response-Streaming,X-User-Agent,X-Grpc-Web';
add_header 'Access-Control-Expose-Headers' 'Content-Transfer-Encoding, grpc-message,grpc-status';
}
}
}
3. 运行前端项目
- 在浏览器打开 index.html 文件即可,看到以下内容则表示运行正常
遇到的问题
1. [grpc 的响应头中grpc-message] grpc: received message larger than max (1094795585 vs. 4194304) (可以通过nginx日志,或者curl -vvv 模式看到)
使用mode=grpcwebtext 时,显示的消息大小问题(不过即便调大估计也不行, 这个值1094795585 已经约是1094M了,显然从grpc接收到的值不对,猜测是nginx这边需要进行什么配置或者扩展,对grpc-web-text 类型数据进行转换)
方案: 使用mode=grpcweb
2. [nginx] upstream rejected request with error 2 while reading response header from upstream
google了下也没人说原因是什么,不过增加下面的请求头后解决问题
方案: grpc_set_header Content-Type application/grpc;