Golang-gRPC + grpc-web +nginx 服務搭建

Golang-gRPC 服務搭建

本文記錄Golang-gRPC、grpc-web + nginx 搭建過程,以及中途遇到的一些問題 項目代碼html

1、理解什麼是gRPC


gRPC的描述網上已經不少了gRPC, 大體涉及兩個知識前端

(1)、RPC Remote Procedure Call

3702846817-5cc80fda042a5_articlex.jpeg

RPC的核心是目的是: 本地調用遠程(跨內存可訪問的)方法。webpack

  1. RPC框架: 開箱即用的實現了RPC調用的框架,其中開源框架如 阿里Dubbo、Google gRPC、Facebook Thrift
  2. 遠程通訊協議: REST(HTTP JSON), SOAP(HTTP XML), gRPC(HTTP2 protobuf)
  3. 序列化/反序列化: 文本(XML、JSON)與二進制(Java原生的、Hessian、protobuf、Thrift、Avro、Kryo、MessagePack
(2) protobuf
  • 足夠簡單
  • 序列化後體積很小:消息大小隻須要XML的1/10 ~ 1/3
  • 解析速度快:解析速度比XML快20 ~ 100倍
  • 多語言支持
  • 更好的兼容性,Protobuf設計的一個原則就是要可以很好的支持向下或向上兼容

2、 搭建hello world


實現步驟

  1. 經過protobuf來定義接口和數據類型
  2. 生成接口代碼
  3. 編寫gRPC server端代碼
  4. 編寫gRPC client端代碼

目錄結構以下(推薦使用gihub上的一個 golang 項目標準框架 搭建本身的項目)nginx

image.png

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

7FDCC505-94F5-47B2-8AAC-DC0FCD9A864D.png

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
}
  • 啓動程序

image.png

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)
}

image.png

如今,一個簡單的gRPC程序就完成了git

3、配合 grpc-web


grpc-web 是針對web端的grpcClient 的項目,解決目前瀏覽器不能直接支持grpc協議的方案,需配合代理服務一塊兒使用, grpc-web 搭建分爲如下幾步github

  1. 生成grpc-web client代碼
  2. 配置代理服務

參考文章 grpc-web grpc-web-nginxgolang

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項目中,結構以下

image.png

  • 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個文件生成,以下圖

image.png

  • 初始化前端項目, 配置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
<!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
  • 在運行以前還須要配置代理服務器
2. 配置代理服務
  1. 代理服務選型 envoy 或 nginx,envoy配置官方示例中有 查看配置, 其餘示例官方也有提供, 本文使用nginx配置(本地搭建)。
  2. 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 文件便可,看到如下內容則表示運行正常

image.png

遇到的問題

1. [grpc 的響應頭中grpc-message] grpc: received message larger than max (1094795585 vs. 4194304) (能夠經過nginx日誌,或者curl -vvv 模式看到)

image.png

使用mode=grpcwebtext 時,顯示的消息大小問題(不過即使調大估計也不行, 這個值1094795585 已經約是1094M了,顯然從grpc接收到的值不對,猜想是nginx這邊須要進行什麼配置或者擴展,對grpc-web-text 類型數據進行轉換)
方案: 使用mode=grpcwebweb

2. [nginx] upstream rejected request with error 2 while reading response header from upstream

google了下也沒人說緣由是什麼,不過增長下面的請求頭後解決問題typescript

方案: grpc_set_header Content-Type application/grpc;json

3. grpc-web 目前在服務端error的時候會有兩次觸發回調函數。issue 目前已合併至master,發佈日期未知
相關文章
相關標籤/搜索