https://github.com/api7/grpc-client-nginx-module
https://github.com/api7/grpc-client-nginx-module
Last synced: 10 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/api7/grpc-client-nginx-module
- Owner: api7
- License: apache-2.0
- Created: 2022-07-13T02:19:00.000Z (over 3 years ago)
- Default Branch: main
- Last Pushed: 2024-02-21T02:59:27.000Z (almost 2 years ago)
- Last Synced: 2024-02-21T08:45:47.551Z (almost 2 years ago)
- Language: C
- Size: 207 KB
- Stars: 45
- Watchers: 2
- Forks: 13
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# gRPC-client-nginx-module
This module is experimental.
## Install
First of all, build this module into your OpenResty:
```shell
./configure ... \
--with-threads \
--with-cc-opt="-DNGX_GRPC_CLI_ENGINE_PATH=/path/to/libgrpc_engine.so" \
--add-module=/path/to/grpc-client-nginx-module
```
We need to specify the path of engine via build argument "-DNGX_GRPC_CLI_ENGINE_PATH".
Then, compile the gRPC engine:
```shell
cd ./grpc-engine && go build -o libgrpc_engine.so -buildmode=c-shared main.go
```
After 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:
```nginx
# Only one background thread is used to communicate with the gRPC engine
thread_pool grpc-client-nginx-module threads=1;
http {
...
}
stream {
...
}
```
## Synopsis
```lua
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:
```lua
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 '';
```
## Method
### load
**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_FILE`
* `PROTO_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.
### connect
**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.
### call
**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"},
}})
```
### new_server_stream
**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.
#### recv
**syntax:** *res, err = stream:recv()*
Receive a response from the stream.
The `res` is a Lua table that is decoded from the proto message.
### new_client_stream
**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.
#### send
**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.
#### recv_close
**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.
### new_bidirectional_stream
**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.
#### close_send
**syntax:** *ok, err = stream:close_send()*
Close the send side of the bidirectional stream.
## Why don't we
### Why don't we use the gRPC code in Nginx
Because Nginx doesn't provide an interface for the gRPC feature. It requires
we to copy & paste and assemble the components.
### Why don't we compile the gRPC library into the Nginx module
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.
## Debug
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`.
## Limitation
* We require this Nginx module runs on 64bit aligned machine, like x64 and arm64.
* resolve `import` in the loaded `.proto` file
(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.