本文记录Golang-gRPC、grpc-web + nginx 搭建过程,以及中途遇到的一些问题 项目代码html
gRPC的描述网上已经不少了gRPC, 大体涉及两个知识前端
RPC的核心是目的是: 本地调用远程(跨内存可访问的)方法。webpack
目录结构以下(推荐使用gihub上的一个 golang 项目标准框架 搭建本身的项目)nginx
syntax = "proto3"; package api; // 这里能够写服务注释 service HelloWorldService { // 这里能够写方法注释 rpc SayHello (HelloRequest) returns (HelloResponse) {} } // 这里能够写请求结构注释 message HelloRequest { // 这里能够写参数注释 string name = 1; } // 这里能够写响应结构注释 message HelloResponse { // 这里能够写参数注释 string message = 1; }
mv protoc-3.11.4-osx-x86_64/bin/protoc /usr/local/bin
go get -u github.com/golang/protobuf/protoc-gen-go
package api //go:generate protoc -I. --go_out=plugins=grpc:. ./hello.proto func init() {}
package hello type Service struct {}
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 }
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 } }
package main import "grpc-demo/api" func main () { c := make(chan bool, 1) go api.RungGRPCServer(9999) <-c }
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程序就完成了git
grpc-web 是针对web端的grpcClient 的项目,解决目前浏览器不能直接支持grpc协议的方案,需配合代理服务一块儿使用, grpc-web 搭建分为如下几步github
参考文章 grpc-web grpc-web-nginxgolang
# 将下载后的内容移动到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
#!/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
{ "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" } }
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(); });
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>gRPC-Web Demode</title> <script src="./dist/main.js"></script> </head> <body> <p id="response">error get message</p> </body> </html>
yarn install npx webpack src/main.js
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'; } } }
使用mode=grpcwebtext 时,显示的消息大小问题(不过即使调大估计也不行, 这个值1094795585 已经约是1094M了,显然从grpc接收到的值不对,猜想是nginx这边须要进行什么配置或者扩展,对grpc-web-text 类型数据进行转换)
方案: 使用mode=grpcwebweb
google了下也没人说缘由是什么,不过增长下面的请求头后解决问题typescript
方案: grpc_set_header Content-Type application/grpc;json