This module is experimental.
First of all, build this module into your OpenResty:
./configure ... \
--with-threads \
--with-cc-opt="-DNGX_GRPC_CLI_ENGINE_PATH=/path/to/libgrpc_engine.so" \
--add-module=/path/to/grpc-client-nginx-moduleWe need to specify the path of engine via build argument "-DNGX_GRPC_CLI_ENGINE_PATH".
Then, compile the gRPC engine:
cd ./grpc-engine && go build -o libgrpc_engine.so -buildmode=c-shared main.goAfter that, install the Lua rock:
luarocks install grpc-client-nginx-module
Make sure the Lua rock version matches the tag version of the Nginx module.
Finally, setup the thread pool:
# Only one background thread is used to communicate with the gRPC engine
thread_pool grpc-client-nginx-module threads=1;
http {
...
}
stream {
...
}access_by_lua_block {
-- we can't require this library in the init_by_lua
local gcli = require("resty.grpc")
assert(gcli.load("t/testdata/rpc.proto")) -- load proto definition into the library
-- connect the target
local conn = assert(gcli.connect("127.0.0.1:2379", {insecure = true}))
-- send unary request
local res = assert(conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}))
}This module can be run in stream subsystem too:
preread_by_lua_block {
local gcli = require("resty.grpc")
assert(gcli.load("t/testdata/rpc.proto"))
local conn = assert(gcli.connect("127.0.0.1:2379"))
local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'})
conn:close()
}
return '';syntax: ok, err = resty.grpc.load(proto[, proto_type])
Load the definition of the given proto, which can be used later.
The proto_type can be one of:
PROTO_TYPE_FILEPROTO_TYPE_STR
For instance, grpc.load("t/testdata/rpc.proto", grpc.PROTO_TYPE_FILE)
If not given, PROTO_TYPE_FILE will be used by default.
syntax: conn, err = resty.grpc.connect(target, connectOpt)
Create a gRPC connection
connectOpt:
insecure: whether connecting the target in an insecure(plaintext) way. True by default. Set it to false will use TLS connection.tls_verify: whether to verify the server's TLS certificate.max_recv_msg_size: sets the maximum message size in bytes the client can receive.client_cert: the path of certificate used in client certificate verification.client_key: the path of key used in client certificate verification. The key should match the given certificate.trusted_ca: the path of trusted certificate.
syntax: res, err = conn:call(service, method, request, callOpt)
Send a unary request.
The request is a Lua table that will be encoded according to the proto.
The res is a Lua table that is decoded from the proto message.
callOpt:
timeout: Set the timeout value in milliseconds for the whole call. 60000 milliseconds by default.int64_encoding: Set the eocoding for int64. The value can be one of:- INT64_AS_NUMBER: set value to integer when it fit into uint32, otherwise return a number (default)
- INT64_AS_STRING: same as above, but return a string instead
- INT64_AS_HEXSTRING: same as above, but return a hexadigit string instead
For example,
conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'},
{int64_encoding = gcli.INT64_AS_STRING})
will decode int64 result in #number.
Note: The string returned by int64_as_string or int64_as_hexstring will prefix a '#' character.
This behavior is done in lua-protobuf.
metadata: Set the gRPC metadata with an array of key-value pairs.
For example,
conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'},
{metadata = {
{"user", "john"},
{"password", "*&()"},
{"key", "val1"},
{"key", "val2"},
}})
syntax: stream, err = conn:new_server_stream(service, method, request, callOpt)
Create a server stream.
The request is a Lua table that will be encoded according to the proto.
The stream is the server stream which can be used to read the data.
callOpt:
timeout: Set the timeout value in milliseconds for the whole lifetime of the stream. 60000 milliseconds by default.int64_encoding: Set the eocoding for int64.metadata: Set the gRPC metadata with an array of key-value pairs.
syntax: res, err = stream:recv()
Receive a response from the stream.
The res is a Lua table that is decoded from the proto message.
syntax: stream, err = conn:new_client_stream(service, method, request, callOpt)
Create a client stream.
The request is a Lua table that will be encoded according to the proto.
The stream is the client stream which can be used to send/recv the data.
callOpt:
timeout: Set the timeout value in milliseconds for the whole lifetime of the stream. 60000 milliseconds by default.int64_encoding: Set the eocoding for int64.
syntax: ok, err = stream:send(request)
Send a request via the stream.
The request is a Lua table that will be encoded according to the proto.
syntax: res, err = stream:recv_close()
Receive a response from the stream and close the stream.
The res is a Lua table that is decoded from the proto message.
syntax: stream, err = conn:new_bidirectional_stream(service, method, request, callOpt)
Create a bidirectional stream.
The request is a Lua table that will be encoded according to the proto.
The stream is the client stream which can be used to send/recv the data.
callOpt:
timeout: Set the timeout value in milliseconds for the whole lifetime of the stream. 60000 milliseconds by default.int64_encoding: Set the eocoding for int64.
The bidirectional stream has send and recv, which are equal to the corresponding
version in client/server streams.
syntax: ok, err = stream:close_send()
Close the send side of the bidirectional stream.
Because Nginx doesn't provide an interface for the gRPC feature. It requires we to copy & paste and assemble the components.
The official gRPC library is written in C++ and requires >2GB dependencies. We don't use bazel/cmake to build Nginx and downloading >2GB dependencies will slow down our build process.
Therefore we choose gRPC-go as the core of gRPC engine. If the performance is an issue, we may consider using Rust instead.
Because go's scheduler doesn't work after fork, we have to load the Go library
at runtime in each worker.
As a consequence of that, we can't use dlv to debug the process. Therefore we need
to use log debug. All logs printed by the std log library will be written to file
/tmp/grpc-engine-debug.log.
- We require this Nginx module runs on 64bit aligned machine, like x64 and arm64.
- resolve
importin the loaded.protofile (we can handle it like what we have done in Apache APISIX) - very big integer in int64 can't be displayed correctly due to the missing int64 support in LuaJIT.