envoy+grpc-json transcoder将grpc接口转restful

项目中服务都是以GRPC的方式提供,但是为了和前台对接,需要以Restful的方式提供接口。于是研究了GPRC到Restful接口的转换方法。常用的转换方法有一下3种。

  • grpc + envoy + grpc-web,grpc-web是一套js库,前台通过grpc-web和envoy实现和grpc服务的交互。该方法的缺点是前台仍然需要拿到proto文件,然后生成对应的js文件才可以调用服务。无法达到服务对调用者透明的效果。
  • grpc + grpc-gateway,grpc-gateway是go实现的一个代理转发库,未实验。
  • grpc + envoy + grpc-json transcoder,本文主要通过这种方式实现grpc服务的Restful接口化。

无论使用grpc-web还是grpc-json transcode都需要envoy的转发。下面先说明如何使用envoy。

1 envoy的使用

envoy是由C++开发的一个代理服务,功能非常强大。可以编译安装,也可以直接使用docker。本文实验直接通过docker的方式使用envoy。具体操作命令如下:

1.1 下载镜像

docker pull envoyproxy/envoy-dev

1.2 启动envoy

docker run --name envoy --rm -d  -p 5858:5858 -p 9901:9901 \
-v /root/service/etc/envoy:/etc/envoy \
envoyproxy/envoy-dev

各个参数的说明:

  • --name,启动的容器名称。
  • 5858为监听器端口, 9901为管理端口,端口号和后面的envoy.yaml文件中的配置相对应。
  • -v /root/service/etc/envoy:/etc/envoy,将envoy.yaml和proto.pb映射到envoy的默认配置路径下。

envoy涉及的概念较多,可以先参考基本概念,理解envoy配置文件中各个参数的含义。

2 创建grpc服务

2.1 编写grpc的proto文件

本实验中使用的proto文件wind.proto的内容如下,

syntax = "proto3";

import "google/api/annotations.proto";

package wind_power;

service WindServer {
    rpc wind_predict(Request) returns (Response) {
        option (google.api.http) = {
          get: "/predict"
        };
    }
    
    rpc send_data(Request) returns (Response) {
        option (google.api.http) = {
          post: "/send",
          body: "*"
        };
    }
}

message Request {
    string content = 1;
}

message Response {
    string msg = 1;
    int32 code = 2;
}

proto中添加了两个接口,分别用于测试get方法和post方法。

2.2 生成proto.pb描述文件以及wind.proto对应的pb文件

python -m grpc_tools.protoc -I../../googleapis-master -I. \
--include_imports --include_source_info --descriptor_set_out=proto.pb \
--python_out=.. --grpc_python_out=.. wind.proto

执行上述命令之后会生成3个文件,proto.pb/wind_pb2.py/wind_pb2_grpc.py。其中proto.pb为对应的协议描述文件,后面envoy.yaml中需要配置该文件所在的路径。

注意:需要先将googleapis这个googleapis项目下载都指定的路径下,并将上述命令中的第一个-I替换成googleapis所在的路径。

2.3 grpc服务端代码

服务文件名称wind_predict_srv.py

# -*- coding: utf-8 -*- 
# --------------------------------
# Name:     wind_predict_srv.py
# Author:   george
# Date:     2020/7/25
# --------------------------------

import grpc
import logging
from concurrent import futures

import proto.wind_pb2 as wind_pb2
import proto.wind_pb2_grpc as wind_pb2_grpc

class WindPredictSrv(wind_pb2_grpc.WindServerServicer):

    def wind_predict(self, request, context):
        print("call wind_predict")
        return wind_pb2.Response(msg='%s!' % request.content)
        
    def send_data(self, request, context):
        print("call send_data")
        return wind_pb2.Response(msg='%s!' % request.content)

def server():
    grpc_server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
    wind_pb2_grpc.add_WindServerServicer_to_server(WindPredictSrv(), grpc_server)
    grpc_server.add_insecure_port('[::]:50052')
    grpc_server.start()
    grpc_server.wait_for_termination()

if __name__ == '__main__':
    logging.basicConfig()
    server()

2.4 grpc客户端代码

客户端文件名称wind_predict_client.py

import grpc
import logging

import proto.wind_pb2 as wind_pb2
import proto.wind_pb2_grpc as wind_pb2_grpc

def run():
    option = [('grpc.keepalive_timeout_ms', 10000)]
    with grpc.insecure_channel(target='127.0.0.1:50052', options=option) as channel:
        stub = wind_pb2_grpc.WindServerStub(channel)
        request = wind_pb2.Request(content='hello grpc')
        response = stub.wind_predict(request, timeout=10)
    print("Greeter client received: " + response.msg)

if __name__ == '__main__':
    logging.basicConfig()
    run()

2.5 测试grpc服务是否正常

启动服务端

python wind_predict_srv.py

新启动一个窗口,使用grpc客户端测试grpc服务

python wind_predict_client.py

若打印Greeter client received: hello grpc!,说明grpc服务构建成功。

3 配置envoy

envoy的使用主要是通过envoy的配置文件实现。本实验中envoy的配置文件如下,配置文件名称envoy.yaml。

admin:
  access_log_path: /tmp/admin_access.log
  address:
    socket_address: { address: 0.0.0.0, port_value: 9901 }

static_resources:
  listeners:
  - name: listener1
    address:
      socket_address: { address: 0.0.0.0, port_value: 5858 }
    filter_chains:
    - filters:
      - name: envoy.filters.network.http_connection_manager
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
          stat_prefix: grpc_json
          codec_type: AUTO
          route_config:
            name: local_route
            virtual_hosts:
            - name: local_service
              domains: ["*"]
              routes:
              - match: { prefix: "/wind_power.WindServer" }
                route: { cluster: grpc, timeout: { seconds: 60 } }
          http_filters:
          - name: envoy.filters.http.grpc_json_transcoder
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_json_transcoder.v3.GrpcJsonTranscoder
              proto_descriptor: "/etc/envoy/proto.pb"
              services: ["wind_power.WindServer"]
              print_options:
                add_whitespace: true
                always_print_primitive_fields: true
                always_print_enums_as_ints: false
                preserve_proto_field_names: false
          - name: envoy.filters.http.router

  clusters:
  - name: grpc
    connect_timeout: 1.25s
    type: logical_dns
    lb_policy: round_robin
    dns_lookup_family: V4_ONLY
    http2_protocol_options: {}
    upstream_connection_options:
      tcp_keepalive:
        keepalive_time: 300
    load_assignment:
      cluster_name: grpc
      endpoints:
      - lb_endpoints:
        - endpoint:
            address:
              socket_address:
                address: 0.0.0.0
                port_value: 50052

4 请求验证

grpc服务正常启动,envoy的yaml文件编辑好且envoy正常启动就可以测试请求了。

4.1 测试get请求

 curl http://localhost:5858/predict?content=george

4.2 测试post方式请求

curl -H "Content-Type:application/json" -X POST -d '{"content": "hello grpc"}'  \
http://localhost:5858/send

注意:这里名的predict和send方法是在proto文件中通过option的方式绑定的,如:

    rpc wind_predict(Request) returns (Response) {
        option (google.api.http) = {
          get: "/predict"
        };
    }

FAQ

问题1:upstream connect error or disconnect/reset before headers. reset reason: connection failure

出现这种情况一般都是因为网络问题。实验中启动envoy容器时没有指定网络,默认使用的是bridge网络,而gprc服务是在宿主机上的,二者不在同一个网段,这种情况下访问就会出现上面的问题。解决方法:

  • ① 启动容器时指定host网络,即添加 --network="host"。
  • ② 将grpc服务和envoy放置在同一个网段的网络上。

参考

anvoy + grpc-json 参考

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354