本文記錄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