官网代码
下载代码git clone https://github.com/grpc/grpc-web.git
进到grpc-web/net/grpc/gateway/examples/hellpworld/
按照README.md运行示例
proto文件定义
请求 HelloRequest、RepeatHelloRequest
响应 HelloReply
方法定义 SayHello、SayRepeatHello
hello.proto
syntax = "proto3";
package helloworld;
service Greeter { //定义了2个rpc:SayHello、SayRepeatHello
// unary call
rpc SayHello(HelloRequest) returns (HelloReply); //请求只有一个参数name
// server streaming call
rpc SayRepeatHello(RepeatHelloRequest) returns (stream HelloReply); //请求有2个参数:name、count(重复几次)
}
message HelloRequest {
string name = 1;
}
message RepeatHelloRequest {
string name = 1;
int32 count = 2;
}
message HelloReply {
string message = 1;
}
实现server
node.js实现server,实现2个rpc的具体操作
server.js
var PROTO_PATH = __dirname + '/helloworld.proto';
var assert = require('assert');
var async = require('async');
var _ = require('lodash');
var grpc = require('@grpc/grpc-js');
var protoLoader = require('@grpc/proto-loader');
var packageDefinition = protoLoader.loadSync(
PROTO_PATH,
{keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true
});
var protoDescriptor = grpc.loadPackageDefinition(packageDefinition);
var helloworld = protoDescriptor.helloworld;
/**
* @param {!Object} call
* @param {function():?} callback
*/
//实现rpc:SayHello
function doSayHello(call, callback) {
//取出请求中的参数,组装发回响应response
callback(null, {message: 'Hello! '+ call.request.name});
}
/**
* @param {!Object} call
*/
//实现rpc:SayRepeatHello
function doSayRepeatHello(call) {
var senders = [];
function sender(name) {
return (callback) => {
call.write({
message: 'Hey! ' + name
});
_.delay(callback, 500); // in ms
};
}
for (var i = 0; i < call.request.count; i++) { //取出请求中参数:重复次数,返回多次
senders[i] = sender(call.request.name + i);
}
async.series(senders, () => {
call.end();
});
}
/**
* @return {!Object} gRPC server
*/
function getServer() {
var server = new grpc.Server();
server.addService(helloworld.Greeter.service, {
sayHello: doSayHello,
sayRepeatHello: doSayRepeatHello,
});
return server;
}
if (require.main === module) {
var server = getServer();
server.bindAsync(
'0.0.0.0:9090', grpc.ServerCredentials.createInsecure(), (err, port) => {
assert.ifError(err);
server.start();
});
}
exports.getServer = getServer;
Envoy proxy
监听8080,转发浏览器的grpc请求给后端,cluster端口9090
envoy.yaml
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
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
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
load_assignment:
cluster_name: cluster_0
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 0.0.0.0
port_value: 9090
protob编译js
protoc -I=. helloworld.proto \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
得到2个新文件
helloworld_grpc_web_pb.js //定义GreeterClient
helloworld_pb.js //定义HelloRequest, HelloReply
client实现
用到上面生成的那2个文件
client.js
const {HelloRequest, HelloReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');
var client = new GreeterClient('http://localhost:8080');
var request = new HelloRequest();
request.setName('World');
client.sayHello(request, {}, (err, response) => {
console.log(response.getMessage());
});
----
const {HelloRequest, RepeatHelloRequest,
HelloReply} = require('./helloworld_pb.js');
const {GreeterClient} = require('./helloworld_grpc_web_pb.js');
var client = new GreeterClient('http://' + window.location.hostname + ':8080',
null, null); //请求发送到8080端口
// simple unary call
var request = new HelloRequest(); //构建请求
request.setName('World'); //给请求中的参数赋值,设置name为World
client.sayHello(request, {}, (err, response) => {
if (err) {
console.log(`Unexpected error for sayHello: code = ${err.code}` +
`, message = "${err.message}"`);
} else {
console.log(response.getMessage());
}
});
// server streaming call
var streamRequest = new RepeatHelloRequest();
streamRequest.setName('World');
streamRequest.setCount(5); //重复5次
var stream = client.sayRepeatHello(streamRequest, {});
stream.on('data', (response) => {
console.log(response.getMessage());
});
stream.on('error', (err) => {
console.log(`Unexpected stream error: code = ${err.code}` +
`, message = "${err.message}"`);
});
"client.js" 52L, 1615C
编译client js代码
npm install
多了文件夹node_modules、package-lock.json
npx webpack client.js
把client.js编译为main.js,可在浏览器执行
输出
$ npx webpack client.js
Hash: 311d4a8b03eac66704bc
Version: webpack 4.43.0
Time: 1542ms
Built at: 2022/06/14 17:22:34
Asset Size Chunks Chunk Names
main.js 293 KiB 0 [emitted] [big] main
Entrypoint main [big] = main.js
[0] (webpack)/buildin/global.js 472 bytes {0} [built]
[1] ./helloworld_pb.js 14.1 KiB {0} [built]
[2] ./client.js 1.58 KiB {0} [built]
[8] ./helloworld_grpc_web_pb.js 4.69 KiB {0} [built]
+ 6 hidden modules
WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value. Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
main.js (293 KiB)
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
main (293 KiB)
main.js
WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
生成 dist/main.js
运行
启grpc server,监听9090端口
$ node server.js &
运行envoy代理,运行一个docker镜像,把8080转到9090端口
$ docker run -d -v "$(pwd)"/envoy.yaml:/etc/envoy/envoy.yaml:ro \
--network=host envoyproxy/envoy:v1.22.0
运行web server
开启文件服务,浏览器解析index.html,脚本main.js(由client.js编译得到)
$ python3 -m http.server 8081 &
打开浏览器,F12打开调试看输出
地址 localhost:8081
结果
中间遇到的问题
浏览器打不开localhost:8081
phthon -m client.js报错输出 code 400, message Bad request syntax 乱码
地址栏https改为http