从 https://github.com/improbable-eng/grpc-web/releases/tag/v0.13.0 按操作系统选择下载,如:grpcwebproxy-v0.13.0-win64.exe.zip 。
下载完成后把 grpcwebproxy.exe 放到自建的文件夹,并添加到环境变量的path中。
使用grpcwebproxy代理服务,命令为:
示例:grpcwebproxy --allow_all_origins --backend_addr=localhost:50051 --run_tls_server=false --server_http_debug_port=5005
本地实际为:
grpcwebproxy --allow_all_origins --backend_addr=192.168.1.103:8090 --run_tls_server=false --server_http_debug_port=5005
其中用到的参数说明:
使用命令把当前文件夹下面所有的.proto文件转成XXX_pb.js和XXX_grpc_web_pb.js,命令为:
protoc --js_out=import_style=commonjs:. --grpc-web_out=import_style=commonjs,mode=grpcwebtext:. ./*.proto
hello.proto 文件内容为:
syntax = "proto3";
package hello;
option go_package=".;hello";
// 请求参数
message HelloRequest {
string name = 1;
}
// 响应消息
message HelloInfo {
string hello_data = 1;
}
//服务定义
service HelloService {
rpc Hello (HelloRequest) returns (HelloInfo);
}
转成 XXX_pb.js和XXX_grpc_web_pb.js:
- hello_grpc_web_pb.js:
/**
* @fileoverview gRPC-Web generated client stub for hello
* @enhanceable
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
/* eslint-disable */
// @ts-nocheck
const grpc = {};
grpc.web = require('grpc-web');
const proto = {};
proto.hello = require('./hello_pb.js');
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?Object} options
* @constructor
* @struct
* @final
*/
proto.hello.HelloServiceClient =
function(hostname, credentials, options) {
if (!options) options = {};
options['format'] = 'text';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname;
};
/**
* @param {string} hostname
* @param {?Object} credentials
* @param {?Object} options
* @constructor
* @struct
* @final
*/
proto.hello.HelloServicePromiseClient =
function(hostname, credentials, options) {
if (!options) options = {};
options['format'] = 'text';
/**
* @private @const {!grpc.web.GrpcWebClientBase} The client
*/
this.client_ = new grpc.web.GrpcWebClientBase(options);
/**
* @private @const {string} The hostname
*/
this.hostname_ = hostname;
};
/**
* @const
* @type {!grpc.web.MethodDescriptor<
* !proto.hello.HelloRequest,
* !proto.hello.HelloInfo>}
*/
const methodDescriptor_HelloService_Hello = new grpc.web.MethodDescriptor(
'/hello.HelloService/Hello',
grpc.web.MethodType.UNARY,
proto.hello.HelloRequest,
proto.hello.HelloInfo,
/**
* @param {!proto.hello.HelloRequest} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hello.HelloInfo.deserializeBinary
);
/**
* @const
* @type {!grpc.web.AbstractClientBase.MethodInfo<
* !proto.hello.HelloRequest,
* !proto.hello.HelloInfo>}
*/
const methodInfo_HelloService_Hello = new grpc.web.AbstractClientBase.MethodInfo(
proto.hello.HelloInfo,
/**
* @param {!proto.hello.HelloRequest} request
* @return {!Uint8Array}
*/
function(request) {
return request.serializeBinary();
},
proto.hello.HelloInfo.deserializeBinary
);
/**
* @param {!proto.hello.HelloRequest} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @param {function(?grpc.web.Error, ?proto.hello.HelloInfo)}
* callback The callback function(error, response)
* @return {!grpc.web.ClientReadableStream<!proto.hello.HelloInfo>|undefined}
* The XHR Node Readable Stream
*/
proto.hello.HelloServiceClient.prototype.hello =
function(request, metadata, callback) {
return this.client_.rpcCall(this.hostname_ +
'/hello.HelloService/Hello',
request,
metadata || {},
methodDescriptor_HelloService_Hello,
callback);
};
/**
* @param {!proto.hello.HelloRequest} request The
* request proto
* @param {?Object<string, string>} metadata User defined
* call metadata
* @return {!Promise<!proto.hello.HelloInfo>}
* Promise that resolves to the response
*/
proto.hello.HelloServicePromiseClient.prototype.hello =
function(request, metadata) {
return this.client_.unaryCall(this.hostname_ +
'/hello.HelloService/Hello',
request,
metadata || {},
methodDescriptor_HelloService_Hello);
};
module.exports = proto.hello;
- hello_pb.js:
// source: hello.proto
/**
* @fileoverview
* @enhanceable
* @suppress {missingRequire} reports error on implicit type usages.
* @suppress {messageConventions} JS Compiler reports an error if a variable or
* field starts with 'MSG_' and isn't a translatable message.
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
/* eslint-disable */
// @ts-nocheck
var jspb = require('google-protobuf');
var goog = jspb;
var global = Function('return this')();
goog.exportSymbol('proto.hello.HelloInfo', null, global);
goog.exportSymbol('proto.hello.HelloRequest', null, global);
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.hello.HelloRequest = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.hello.HelloRequest, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.hello.HelloRequest.displayName = 'proto.hello.HelloRequest';
}
/**
* Generated by JsPbCodeGenerator.
* @param {Array=} opt_data Optional initial data array, typically from a
* server response, or constructed directly in Javascript. The array is used
* in place and becomes part of the constructed object. It is not cloned.
* If no data is provided, the constructed object will be empty, but still
* valid.
* @extends {jspb.Message}
* @constructor
*/
proto.hello.HelloInfo = function(opt_data) {
jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.hello.HelloInfo, jspb.Message);
if (goog.DEBUG && !COMPILED) {
/**
* @public
* @override
*/
proto.hello.HelloInfo.displayName = 'proto.hello.HelloInfo';
}
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.hello.HelloRequest.prototype.toObject = function(opt_includeInstance) {
return proto.hello.HelloRequest.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.hello.HelloRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hello.HelloRequest.toObject = function(includeInstance, msg) {
var f, obj = {
name: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.hello.HelloRequest}
*/
proto.hello.HelloRequest.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.hello.HelloRequest;
return proto.hello.HelloRequest.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.hello.HelloRequest} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.hello.HelloRequest}
*/
proto.hello.HelloRequest.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setName(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.hello.HelloRequest.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.hello.HelloRequest.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.hello.HelloRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hello.HelloRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getName();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string name = 1;
* @return {string}
*/
proto.hello.HelloRequest.prototype.getName = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.hello.HelloRequest} returns this
*/
proto.hello.HelloRequest.prototype.setName = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
if (jspb.Message.GENERATE_TO_OBJECT) {
/**
* Creates an object representation of this proto.
* Field names that are reserved in JavaScript and will be renamed to pb_name.
* Optional fields that are not set will be set to undefined.
* To access a reserved field use, foo.pb_<name>, eg, foo.pb_default.
* For the list of reserved names please see:
* net/proto2/compiler/js/internal/generator.cc#kKeyword.
* @param {boolean=} opt_includeInstance Deprecated. whether to include the
* JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @return {!Object}
*/
proto.hello.HelloInfo.prototype.toObject = function(opt_includeInstance) {
return proto.hello.HelloInfo.toObject(opt_includeInstance, this);
};
/**
* Static version of the {@see toObject} method.
* @param {boolean|undefined} includeInstance Deprecated. Whether to include
* the JSPB instance for transitional soy proto support:
* http://goto/soy-param-migration
* @param {!proto.hello.HelloInfo} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hello.HelloInfo.toObject = function(includeInstance, msg) {
var f, obj = {
helloData: jspb.Message.getFieldWithDefault(msg, 1, "")
};
if (includeInstance) {
obj.$jspbMessageInstance = msg;
}
return obj;
};
}
/**
* Deserializes binary data (in protobuf wire format).
* @param {jspb.ByteSource} bytes The bytes to deserialize.
* @return {!proto.hello.HelloInfo}
*/
proto.hello.HelloInfo.deserializeBinary = function(bytes) {
var reader = new jspb.BinaryReader(bytes);
var msg = new proto.hello.HelloInfo;
return proto.hello.HelloInfo.deserializeBinaryFromReader(msg, reader);
};
/**
* Deserializes binary data (in protobuf wire format) from the
* given reader into the given message object.
* @param {!proto.hello.HelloInfo} msg The message object to deserialize into.
* @param {!jspb.BinaryReader} reader The BinaryReader to use.
* @return {!proto.hello.HelloInfo}
*/
proto.hello.HelloInfo.deserializeBinaryFromReader = function(msg, reader) {
while (reader.nextField()) {
if (reader.isEndGroup()) {
break;
}
var field = reader.getFieldNumber();
switch (field) {
case 1:
var value = /** @type {string} */ (reader.readString());
msg.setHelloData(value);
break;
default:
reader.skipField();
break;
}
}
return msg;
};
/**
* Serializes the message to binary data (in protobuf wire format).
* @return {!Uint8Array}
*/
proto.hello.HelloInfo.prototype.serializeBinary = function() {
var writer = new jspb.BinaryWriter();
proto.hello.HelloInfo.serializeBinaryToWriter(this, writer);
return writer.getResultBuffer();
};
/**
* Serializes the given message to binary data (in protobuf wire
* format), writing to the given BinaryWriter.
* @param {!proto.hello.HelloInfo} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.hello.HelloInfo.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
f = message.getHelloData();
if (f.length > 0) {
writer.writeString(
1,
f
);
}
};
/**
* optional string hello_data = 1;
* @return {string}
*/
proto.hello.HelloInfo.prototype.getHelloData = function() {
return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, ""));
};
/**
* @param {string} value
* @return {!proto.hello.HelloInfo} returns this
*/
proto.hello.HelloInfo.prototype.setHelloData = function(value) {
return jspb.Message.setProto3StringField(this, 1, value);
};
goog.object.extend(exports, proto.hello);
编写client.js(只先执行一个hello方法):
var {
HelloServiceClient
} =require('./hello_grpc_web_pb');
var { HelloRequest } =require('./hello_pb');
let client = new HelloServiceClient('http://localhost:5005');
let helloRequest = new HelloRequest();
helloRequest.setName('tom');
client.hello(helloRequest, {}, (err, response) => {
console.log(err, response);
});
打包client.js放到dist/main.js并在index.html头部引入:
npx webpack client.js
运行index.html:
go写的服务端:
package main
import (
"context"
"fmt"
"golang-grpc-demo/hello"
"net"
"google.golang.org/grpc"
)
// HelloServerImpl 定义hello接口实现
type HelloServerImpl struct {
hello.UnimplementedHelloServiceServer
}
// Hello 定义hello的rpc方法
func (h *HelloServerImpl) Hello(ctx context.Context, request *hello.HelloRequest) (*hello.HelloInfo, error) {
name := request.GetName()
fmt.Println(name)
return &hello.HelloInfo{HelloData: "hello " + name}, nil
}
func main() {
lis, err := net.Listen("tcp", ":8090")
if err != nil {
fmt.Println(err)
}
server := grpc.NewServer()
hello.RegisterHelloServiceServer(server, &HelloServerImpl{})
if err := server.Serve(lis); err != nil {
fmt.Println(err)
}
}
使用这个:
也可以监听:
再写一个clientold.js(去执行sayRepeatHello方法):
var {
GreeterClient
} =require('./helloworld_grpc_web_pb');
var { HelloRequest,RepeatHelloRequest } =require('./helloworld_pb');
let client = new GreeterClient('http://localhost:5005');
let helloRequest = new HelloRequest();
helloRequest.setName('kitty');
// helloRequest.setCity('合肥');
client.sayHello(helloRequest, {}, (err, response) => {
console.log(err, 22,response,response.array);
});
// client.sayRepeatHello(repeatHelloRequest, {});
// server streaming call
var streamRequest = new RepeatHelloRequest();
streamRequest.setName('World');
streamRequest.setCount(7);
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}"`);
});
服务端go代码:
package main
import (
"context"
"fmt"
"golang-grpc-demo/hello"
"net"
"strconv"
"google.golang.org/grpc"
)
// HelloServerImpl 定义hello接口实现
type HelloServerImpl struct {
hello.UnimplementedGreeterServer
}
// SayHello 定义hello的rpc方法
func (h *HelloServerImpl) SayHello(ctx context.Context, request *hello.HelloRequest) (*hello.HelloReply, error) {
name := request.GetName()
fmt.Println("SayHello方法接收到请求参数:", name)
return &hello.HelloReply{Message: "SayHello: " + name}, nil
}
// SayRepeatHello 定义hello的rpc方法
func (h *HelloServerImpl) SayRepeatHello(request *hello.RepeatHelloRequest, stream hello.Greeter_SayRepeatHelloServer) error {
name := request.GetName()
count := request.GetCount()
fmt.Println("SayRepeatHello方法接收到请求参数:", name, count)
for i := 0; i < int(count); i++ {
stream.Send(&hello.HelloReply{Message: "Count:" + strconv.Itoa(i) + " SayHello: " + name + "\n"})
}
return nil
}
func main() {
lis, err := net.Listen("tcp", ":8090")
if err != nil {
fmt.Println(err)
}
server := grpc.NewServer()
hello.RegisterGreeterServer(server, new(HelloServerImpl))
if err := server.Serve(lis); err != nil {
fmt.Println(err)
}
}
要求的类型:
测试一下,故意写一个字符串类型的次数:
结果可见:控制台报错了,请求并没有发到服务端: