grpc-web使用
一,grpc-web数据交互支持
二,proto协议编译js文件
hello.proto文件
1 syntax = "proto3"; 2 3 package api; 4 5 // 这里可以写服务注释 6 service HelloWorldService { 7 // 这里可以写方法注释 8 rpc SayHello (HelloRequest) returns (HelloResponse) {} 9 } 10 11 // 这里可以写请求结构注释 12 message HelloRequest { 13 // 这里可以写参数注释 14 string name = 1; 15 } 16 17 // 这里可以写响应结构注释 18 message HelloResponse { 19 // 这里可以写参数注释 20 string message = 1; 21 }
环境准备
cd /home/shimon/pkg # 将下载后的内容移动到bin路径中方便使用 mv protoc-gen-grpc-web-1.4.1-linux-x86_64 /usr/local/bin/protoc-gen-grpc-web # 增加可执行权限 chmod +x /usr/local/bin/protoc-gen-grpc-web # 根据hello.proto生成js调用文件.如果协议中使用stream,则使用mode=grpcwebtext编译 sudo protoc hello.proto --js_out=import_style=commonjs,binary:./output --grpc-web_out=import_style=commonjs,mode=grpcwebtext:./output
三,前端调用grpc服务
a.将hello_grpc_web_pb.js和hello_pb.js移到前端pb目录下
b.项目安装依赖yarn add google-protobuf grpc-web
c.调用代码:
import { HelloRequest } from './pb/hello_pb'; import { HelloWorldServiceClient } from './pb/hello_grpc_web_pb'; // 注意这个端口是代理服务器的端口,不是grpc的端口 const client = new HelloWorldServiceClient('http://localhost:8889', null, null); const request = new HelloRequest(); request.setName('World'); client.sayHello(request, {}, (err, response) => { console.log(response.getMessage()); });
四,代理请求
由于浏览器的特性,gRPC-web其实没办法直接向gRPC-server发送HTTP/2请求的,只有通过envoy(推荐)/nginx代理,将来自gRPC-web的HTTP/1的请求转换为gRPC-server能够接收的HTTP/2请求
envoy配置:
envoy.yaml
admin: access_log_path: /tmp/admin_access.log address: socket_address: { address: 0.0.0.0, port_value: 8890 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 8889 } 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 codec_type: auto stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: { prefix: "/" } route: cluster: greeter_service timeout: 0s max_stream_duration: grpc_timeout_header_max: 0s cors: allow_origin_string_match: - prefix: "*" allow_methods: GET, PUT, DELETE, POST, OPTIONS allow_headers: keep-alive,user-agent,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,grpc-timeout max_age: "1728000" expose_headers: custom-header-1,grpc-status,grpc-message http_filters: - name: envoy.filters.http.grpc_web typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb - name: envoy.filters.http.cors typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: greeter_service connect_timeout: 0.25s type: logical_dns http2_protocol_options: {} lb_policy: round_robin # win/mac hosts: Use address: host.docker.internal instead of address: localhost in the line below load_assignment: cluster_name: cluster_0 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 192.168.0.1 port_value: 8888
sudo docker run --name redirect_envoy -p 8889:8889 -p 8890:8890 -v ${PWD}/envoy.yaml:/etc/envoy/envoy.yaml -d --restart=always envoyproxy/envoy:v1.24-latest
nginx配置:
但是此处有限制,只能使用mode=grpcweb编译的js,如果使用grpcwebtext编译的js则报跨域问题,暂时未找到解决办法
此配置文件在${PWD}/nginx/conf.d/目录下
server {
listen 8889;
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 192.168.0.1:8888;
# 因浏览器有跨域限制,这里直接在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';
}
}
}
sudo docker run --name redirect_nginx -p 8889:8889 -v ${PWD}/nginx/conf.d:/etc/nginx/conf.d -v ${PWD}/nginx/logs:/tmp --privileged=true -d --restart=always nginx