├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ ├── codeql.yml │ ├── license-checker.yml │ ├── lint.yml │ ├── release.yml │ └── stress.yml ├── .gitignore ├── .licenserc.yaml ├── .luacheckrc ├── LICENSE ├── Makefile ├── README.md ├── config ├── grpc-client-nginx-module-dev-0-0.rockspec ├── grpc-engine ├── conn │ ├── grpc.go │ ├── grpc_test.go │ └── proto.go ├── go.mod ├── go.sum ├── main.go └── task │ └── task.go ├── install-util.sh ├── lib └── resty │ └── grpc.lua ├── rockspec └── grpc-client-nginx-module-0.3.1-0.rockspec ├── src └── ngx_grpc_client.c └── t ├── GRPC_CLI.pm ├── backend ├── Dockerfile ├── go.mod ├── go.sum ├── main.go └── proto │ ├── test.pb.go │ ├── test.proto │ └── test_grpc.pb.go ├── bad.t ├── bidirectional_stream.t ├── certs ├── apisix.crt ├── apisix.key ├── ca.crt ├── client.crt ├── client.key ├── etcd.key ├── etcd.pem ├── incorrect.crt ├── incorrect.key ├── server.crt └── server.key ├── client_stream.t ├── conn.t ├── docker-compose.yml ├── env └── etcd ├── hup.t ├── metadata.t ├── mtls.t ├── nginx.t ├── server_stream.t ├── stream ├── bidirectional_stream.t ├── client_stream.t ├── conn.t ├── server_stream.t └── unary.t ├── stress ├── conf │ └── nginx.conf └── test.py ├── testdata ├── bad.proto └── rpc.proto ├── tls.t └── unary.t /.gitattributes: -------------------------------------------------------------------------------- 1 | *.t linguist-language=Text 2 | *.pm linguist-language=Perl 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: "ubuntu-20.04" 12 | env: 13 | OPENRESTY_PREFIX: "/usr/local/openresty" 14 | 15 | steps: 16 | - name: Check out code 17 | uses: actions/checkout@v3 18 | 19 | - name: Set up Clang 20 | uses: egor-tensin/setup-clang@v1 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v3 24 | 25 | - name: Get dependencies 26 | run: sudo apt install -y cpanminus build-essential libncurses5-dev libreadline-dev libssl-dev perl luarocks 27 | 28 | - name: Before install 29 | run: | 30 | sudo cpanm --notest Test::Nginx > build.log 2>&1 || (cat build.log && exit 1) 31 | git clone https://github.com/openresty/test-nginx.git test-nginx 32 | docker-compose --project-directory . -f t/docker-compose.yml up -d 33 | 34 | - name: Install 35 | run: | 36 | wget https://raw.githubusercontent.com/api7/apisix-build-tools/master/build-apisix-base.sh 37 | chmod +x build-apisix-base.sh 38 | OR_PREFIX=$OPENRESTY_PREFIX CC="clang -fsanitize=address -fcolor-diagnostics -Qunused-arguments" \ 39 | cc_opt="-Werror" \ 40 | ./build-apisix-base.sh latest 41 | sudo luarocks make ./grpc-client-nginx-module-dev-0-0.rockspec 42 | cd ./grpc-engine && go build -o libgrpc_engine.so -buildmode=c-shared main.go 43 | 44 | - name: Script 45 | run: | 46 | export PATH=$OPENRESTY_PREFIX/nginx/sbin:$PATH 47 | export GODEBUG=x509sha1=1 48 | prove -I. -Itest-nginx/lib -r t/ 49 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | schedule: 9 | - cron: "30 7 * * 0" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ go ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v2 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /.github/workflows/license-checker.yml: -------------------------------------------------------------------------------- 1 | name: License checker 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | check-license: 11 | runs-on: ubuntu-latest 12 | timeout-minutes: 3 13 | 14 | steps: 15 | - uses: actions/checkout@v3.0.2 16 | - name: Check License Header 17 | uses: apache/skywalking-eyes@v0.4.0 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: "ubuntu-20.04" 12 | 13 | steps: 14 | - name: Check out code 15 | uses: actions/checkout@v2 16 | 17 | - name: Get dependencies 18 | run: | 19 | sudo apt install -y luarocks 20 | sudo luarocks install luacheck > build.log 2>&1 || (cat build.log && exit 1) 21 | 22 | - name: Script 23 | run: | 24 | luacheck . 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths: 8 | - 'rockspec/**' 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v2 17 | 18 | - name: Install Lua 19 | uses: leafo/gh-actions-lua@v8 20 | 21 | - name: Install Luarocks 22 | uses: leafo/gh-actions-luarocks@v4 23 | 24 | - name: Extract release name 25 | id: release_env 26 | shell: bash 27 | run: | 28 | title="${{ github.event.head_commit.message }}" 29 | re="^feat: release v*(\S+)" 30 | if [[ $title =~ $re ]]; then 31 | v=v${BASH_REMATCH[1]} 32 | echo "##[set-output name=version;]${v}" 33 | echo "##[set-output name=version_withou_v;]${BASH_REMATCH[1]}" 34 | else 35 | echo "commit format is not correct" 36 | exit 1 37 | fi 38 | 39 | - name: Create Release 40 | uses: actions/create-release@v1 41 | env: 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | with: 44 | tag_name: ${{ steps.release_env.outputs.version }} 45 | release_name: ${{ steps.release_env.outputs.version }} 46 | draft: false 47 | prerelease: false 48 | 49 | - name: Upload to luarocks 50 | env: 51 | LUAROCKS_TOKEN: ${{ secrets.LUAROCKS_TOKEN }} 52 | run: | 53 | luarocks install dkjson 54 | luarocks upload rockspec/grpc-client-nginx-module-${{ steps.release_env.outputs.version_withou_v }}-0.rockspec --api-key=${LUAROCKS_TOKEN} 55 | -------------------------------------------------------------------------------- /.github/workflows/stress.yml: -------------------------------------------------------------------------------- 1 | name: stress 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths-ignore: 7 | - '**/*.md' 8 | pull_request: 9 | branches: [main] 10 | paths-ignore: 11 | - '**/*.md' 12 | 13 | jobs: 14 | stress: 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 30 17 | env: 18 | OPENRESTY_PREFIX: "/usr/local/openresty" 19 | 20 | steps: 21 | - name: Check out code 22 | uses: actions/checkout@v3 23 | with: 24 | submodules: recursive 25 | 26 | - name: Before install 27 | run: | 28 | sudo apt install -y perl build-essential libssl-dev luarocks 29 | docker-compose --project-directory . -f t/docker-compose.yml up -d 30 | 31 | - name: Install 32 | run: | 33 | wget https://raw.githubusercontent.com/api7/apisix-build-tools/master/build-apisix-base.sh 34 | chmod +x build-apisix-base.sh 35 | OR_PREFIX=$OPENRESTY_PREFIX CC="clang -fsanitize=address -fcolor-diagnostics -Qunused-arguments" \ 36 | cc_opt="-Werror" \ 37 | ./build-apisix-base.sh latest 38 | 39 | wget https://raw.githubusercontent.com/apache/apisix/master/ci/linux-install-etcd-client.sh 40 | chmod +x linux-install-etcd-client.sh 41 | ./linux-install-etcd-client.sh 42 | 43 | sudo luarocks make ./grpc-client-nginx-module-dev-0-0.rockspec 44 | cd ./grpc-engine && go build -o libgrpc_engine.so -buildmode=c-shared main.go 45 | 46 | - name: run tests 47 | run: | 48 | python -m unittest t/stress/test.py 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .ccls 2 | t/servroot 3 | utils/reindex 4 | grpc-engine/libgrpc_engine.* 5 | __pycache__ 6 | t/stress/logs 7 | t/backend/backend 8 | -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # 15 | header: 16 | license: 17 | spdx-id: Apache-2.0 18 | copyright-owner: Shenzhen ZhiLiu Technology Co., Ltd. 19 | 20 | paths-ignore: 21 | - '.gitignore' 22 | - '.gitattributes' 23 | - 'LICENSE' 24 | - '*.md' 25 | - '**/go.mod' 26 | - '**/go.sum' 27 | - 't/backend/proto' 28 | - 't/certs' 29 | - 't/env' 30 | - 't/testdata' 31 | # https://github.com/api7/grpc-client-nginx-module/pull/54 32 | - '.github' 33 | 34 | comment: on-failure 35 | -------------------------------------------------------------------------------- /.luacheckrc: -------------------------------------------------------------------------------- 1 | -- Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | 15 | cache = true 16 | ignore = { 17 | '_', 18 | } 19 | std = 'ngx_lua' 20 | globals = { 'ngx' } 21 | unused_args = false 22 | redefined = false 23 | read_globals = { 24 | "coroutine._yield" 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022- Shenzhen ZhiLiu Technology Co., Ltd. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | OPENRESTY_PREFIX ?= /usr/local/openresty 16 | INSTALL ?= install 17 | 18 | .PHONY: install 19 | install: 20 | if [ ! -f /usr/local/go/bin/go ]; then ./install-util.sh install_go; fi 21 | cd ./grpc-engine && PATH="$(PATH):/usr/local/go/bin" go build -o libgrpc_engine.so -buildmode=c-shared main.go 22 | $(INSTALL) -m 664 ./grpc-engine/libgrpc_engine.so $(OPENRESTY_PREFIX)/ 23 | $(INSTALL) -m 664 lib/resty/*.lua $(OPENRESTY_PREFIX)/lualib/resty/ 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gRPC-client-nginx-module 2 | 3 | This module is experimental. 4 | 5 | ## Install 6 | 7 | First of all, build this module into your OpenResty: 8 | 9 | ```shell 10 | ./configure ... \ 11 | --with-threads \ 12 | --with-cc-opt="-DNGX_GRPC_CLI_ENGINE_PATH=/path/to/libgrpc_engine.so" \ 13 | --add-module=/path/to/grpc-client-nginx-module 14 | ``` 15 | 16 | We need to specify the path of engine via build argument "-DNGX_GRPC_CLI_ENGINE_PATH". 17 | 18 | Then, compile the gRPC engine: 19 | 20 | ```shell 21 | cd ./grpc-engine && go build -o libgrpc_engine.so -buildmode=c-shared main.go 22 | ``` 23 | 24 | After that, install the Lua rock: 25 | 26 | luarocks install grpc-client-nginx-module 27 | 28 | Make sure the Lua rock version matches the tag version of the Nginx module. 29 | 30 | Finally, setup the thread pool: 31 | 32 | ```nginx 33 | # Only one background thread is used to communicate with the gRPC engine 34 | thread_pool grpc-client-nginx-module threads=1; 35 | http { 36 | ... 37 | } 38 | stream { 39 | ... 40 | } 41 | ``` 42 | 43 | ## Synopsis 44 | 45 | ```lua 46 | access_by_lua_block { 47 | -- we can't require this library in the init_by_lua 48 | local gcli = require("resty.grpc") 49 | assert(gcli.load("t/testdata/rpc.proto")) -- load proto definition into the library 50 | -- connect the target 51 | local conn = assert(gcli.connect("127.0.0.1:2379", {insecure = true})) 52 | -- send unary request 53 | local res = assert(conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'})) 54 | } 55 | ``` 56 | 57 | This module can be run in stream subsystem too: 58 | 59 | ```lua 60 | preread_by_lua_block { 61 | local gcli = require("resty.grpc") 62 | assert(gcli.load("t/testdata/rpc.proto")) 63 | 64 | local conn = assert(gcli.connect("127.0.0.1:2379")) 65 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 66 | conn:close() 67 | } 68 | return ''; 69 | ``` 70 | 71 | ## Method 72 | 73 | ### load 74 | 75 | **syntax:** *ok, err = resty.grpc.load(proto[, proto_type])* 76 | 77 | Load the definition of the given proto, which can be used later. 78 | The `proto_type` can be one of: 79 | 80 | * `PROTO_TYPE_FILE` 81 | * `PROTO_TYPE_STR` 82 | 83 | For instance, `grpc.load("t/testdata/rpc.proto", grpc.PROTO_TYPE_FILE)` 84 | 85 | If not given, `PROTO_TYPE_FILE` will be used by default. 86 | 87 | ### connect 88 | 89 | **syntax:** *conn, err = resty.grpc.connect(target, connectOpt)* 90 | 91 | Create a gRPC connection 92 | 93 | connectOpt: 94 | 95 | * `insecure`: whether connecting the target in an insecure(plaintext) way. 96 | True by default. Set it to false will use TLS connection. 97 | * `tls_verify`: whether to verify the server's TLS certificate. 98 | * `max_recv_msg_size`: sets the maximum message size in bytes the client can receive. 99 | * `client_cert`: the path of certificate used in client certificate verification. 100 | * `client_key`: the path of key used in client certificate verification. The key should match the given certificate. 101 | * `trusted_ca`: the path of trusted certificate. 102 | 103 | ### call 104 | 105 | **syntax:** *res, err = conn:call(service, method, request, callOpt)* 106 | 107 | Send a unary request. 108 | The `request` is a Lua table that will be encoded according to the proto. 109 | The `res` is a Lua table that is decoded from the proto message. 110 | 111 | callOpt: 112 | 113 | * `timeout`: Set the timeout value in milliseconds for the whole call. 114 | 60000 milliseconds by default. 115 | * `int64_encoding`: Set the eocoding for int64. The value can be one of: 116 | * INT64_AS_NUMBER: set value to integer when it fit into uint32, otherwise return a number **(default)** 117 | * INT64_AS_STRING: same as above, but return a string instead 118 | * INT64_AS_HEXSTRING: same as above, but return a hexadigit string instead 119 | 120 | For example, 121 | 122 | ``` 123 | conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, 124 | {int64_encoding = gcli.INT64_AS_STRING}) 125 | ``` 126 | 127 | will decode int64 result in `#number`. 128 | 129 | *Note*: The string returned by `int64_as_string` or `int64_as_hexstring` will prefix a `'#'` character. 130 | This behavior is done in `lua-protobuf`. 131 | 132 | * `metadata`: Set the gRPC metadata with an array of key-value pairs. 133 | 134 | For example, 135 | 136 | ``` 137 | conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, 138 | {metadata = { 139 | {"user", "john"}, 140 | {"password", "*&()"}, 141 | {"key", "val1"}, 142 | {"key", "val2"}, 143 | }}) 144 | ``` 145 | 146 | ### new_server_stream 147 | 148 | **syntax:** *stream, err = conn:new_server_stream(service, method, request, callOpt)* 149 | 150 | Create a server stream. 151 | The `request` is a Lua table that will be encoded according to the proto. 152 | The `stream` is the server stream which can be used to read the data. 153 | 154 | callOpt: 155 | 156 | * `timeout`: Set the timeout value in milliseconds for the whole lifetime of 157 | the stream. 60000 milliseconds by default. 158 | * `int64_encoding`: Set the eocoding for int64. 159 | * `metadata`: Set the gRPC metadata with an array of key-value pairs. 160 | 161 | #### recv 162 | 163 | **syntax:** *res, err = stream:recv()* 164 | 165 | Receive a response from the stream. 166 | The `res` is a Lua table that is decoded from the proto message. 167 | 168 | ### new_client_stream 169 | 170 | **syntax:** *stream, err = conn:new_client_stream(service, method, request, callOpt)* 171 | 172 | Create a client stream. 173 | The `request` is a Lua table that will be encoded according to the proto. 174 | The `stream` is the client stream which can be used to send/recv the data. 175 | 176 | callOpt: 177 | 178 | * `timeout`: Set the timeout value in milliseconds for the whole lifetime of 179 | the stream. 60000 milliseconds by default. 180 | * `int64_encoding`: Set the eocoding for int64. 181 | 182 | #### send 183 | 184 | **syntax:** *ok, err = stream:send(request)* 185 | 186 | Send a request via the stream. 187 | The `request` is a Lua table that will be encoded according to the proto. 188 | 189 | #### recv_close 190 | 191 | **syntax:** *res, err = stream:recv_close()* 192 | 193 | Receive a response from the stream and close the stream. 194 | The `res` is a Lua table that is decoded from the proto message. 195 | 196 | ### new_bidirectional_stream 197 | 198 | **syntax:** *stream, err = conn:new_bidirectional_stream(service, method, request, callOpt)* 199 | 200 | Create a bidirectional stream. 201 | The `request` is a Lua table that will be encoded according to the proto. 202 | The `stream` is the client stream which can be used to send/recv the data. 203 | 204 | callOpt: 205 | 206 | * `timeout`: Set the timeout value in milliseconds for the whole lifetime of 207 | the stream. 60000 milliseconds by default. 208 | * `int64_encoding`: Set the eocoding for int64. 209 | 210 | The bidirectional stream has `send` and `recv`, which are equal to the corresponding 211 | version in client/server streams. 212 | 213 | #### close_send 214 | 215 | **syntax:** *ok, err = stream:close_send()* 216 | 217 | Close the send side of the bidirectional stream. 218 | 219 | ## Why don't we 220 | 221 | ### Why don't we use the gRPC code in Nginx 222 | 223 | Because Nginx doesn't provide an interface for the gRPC feature. It requires 224 | we to copy & paste and assemble the components. 225 | 226 | ### Why don't we compile the gRPC library into the Nginx module 227 | 228 | The official gRPC library is written in C++ and requires >2GB dependencies. 229 | We don't use bazel/cmake to build Nginx and downloading >2GB dependencies will 230 | slow down our build process. 231 | 232 | Therefore we choose gRPC-go as the core of gRPC engine. If the performance is 233 | an issue, we may consider using Rust instead. 234 | 235 | ## Debug 236 | 237 | Because go's scheduler doesn't work after `fork`, we have to load the Go library 238 | at runtime in each worker. 239 | 240 | As a consequence of that, we can't use dlv to debug the process. Therefore we need 241 | to use `log debug`. All logs printed by the std log library will be written to file 242 | `/tmp/grpc-engine-debug.log`. 243 | 244 | ## Limitation 245 | 246 | * We require this Nginx module runs on 64bit aligned machine, like x64 and arm64. 247 | * resolve `import` in the loaded `.proto` file 248 | (we can handle it like what we have done in Apache APISIX) 249 | * very big integer in int64 can't be displayed correctly due to the missing int64 250 | support in LuaJIT. 251 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # the workaround and threads support test are borrowed from 16 | # https://github.com/tokers/lua-io-nginx-module under BSD 2-Clause License 17 | 18 | if [ $USE_THREADS != YES ]; then 19 | cat << END 20 | 21 | $0: grpc-client-nginx-module depends on the threads support, please reconfigure with "--with-threads" option. 22 | 23 | END 24 | exit 1 25 | fi 26 | 27 | # threads support test has been done 28 | 29 | # this is a workaround, to include the private header files in ngx_lua module, 30 | # we need this to solve the context checking problem, and remember to add the 31 | # ngx_lua module prior to this module. This embarrassed situation will be 32 | # avoided if ngx_lua module can expose some APIs to allow us check the current 33 | # request context. 34 | for header in $HTTP_LUA_DEPS 35 | do 36 | has=`echo $header | grep -q "ngx_http_lua_util.h"` 37 | if [ -n $has ]; then 38 | dir=`dirname $header` 39 | CORE_INCS="$CORE_INCS $dir" 40 | break 41 | fi 42 | done 43 | for header in $STREAM_LUA_DEPS 44 | do 45 | has=`echo $header | grep -q "ngx_stream_lua_util.h"` 46 | if [ -n $has ]; then 47 | dir=`dirname $header` 48 | CORE_INCS="$CORE_INCS $dir" 49 | break 50 | fi 51 | done 52 | 53 | ngx_module_type=CORE 54 | ngx_module_name=ngx_grpc_client_module 55 | ngx_module_srcs=" \ 56 | $ngx_addon_dir/src/ngx_grpc_client.c \ 57 | " 58 | ngx_module_deps=" \ 59 | " 60 | ngx_module_incs=" \ 61 | " 62 | 63 | . auto/module 64 | 65 | ngx_addon_name=$ngx_module_name 66 | 67 | # After loading Go code from .so, the signalfd doesn't work anymore 68 | sed -i "s/#ifndef NGX_HTTP_LUA_HAVE_SIGNALFD/#ifdef NGX_HTTP_LUA_HAVE_SIGNALFD/" $NGX_AUTO_CONFIG_H 69 | -------------------------------------------------------------------------------- /grpc-client-nginx-module-dev-0-0.rockspec: -------------------------------------------------------------------------------- 1 | -- Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | 15 | package = "grpc-client-nginx-module-dev" 16 | version = "0-0" 17 | source = { 18 | url = "git://github.com/api7/grpc-client-nginx-module", 19 | branch = "main", 20 | } 21 | 22 | description = { 23 | summary = "Call gRPC service in Nginx", 24 | homepage = "https://github.com/api7/grpc-client-nginx-module", 25 | license = "Apache License 2.0", 26 | maintainer = "Yuansheng Wang " 27 | } 28 | 29 | dependencies = { 30 | "lua-protobuf = 0.3.4", 31 | } 32 | 33 | 34 | build = { 35 | type = "builtin", 36 | modules = { 37 | ["resty.grpc"] = "lib/resty/grpc.lua", 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /grpc-engine/conn/grpc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package conn 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "crypto/tls" 21 | "crypto/x509" 22 | "fmt" 23 | "os" 24 | "time" 25 | 26 | "google.golang.org/grpc" 27 | "google.golang.org/grpc/credentials" 28 | "google.golang.org/grpc/credentials/insecure" 29 | "google.golang.org/grpc/metadata" 30 | ) 31 | 32 | const ( 33 | ClientStream int = 1 34 | ServerStream = 2 35 | BidirectionalStream = 3 36 | ) 37 | 38 | type ConnectOption struct { 39 | Insecure bool 40 | TLSVerify bool 41 | MaxRecvMsgSize int 42 | ClientCertFile string 43 | ClientKeyFile string 44 | TrustedCA string 45 | } 46 | 47 | type CallOption struct { 48 | Timeout time.Duration 49 | Metadata []string 50 | } 51 | 52 | func Connect(target string, opt *ConnectOption) (*grpc.ClientConn, error) { 53 | opts := []grpc.DialOption{ 54 | // connect timeout 55 | grpc.WithTimeout(60 * time.Second), 56 | } 57 | 58 | if opt.MaxRecvMsgSize != 0 { 59 | opts = append(opts, grpc.WithMaxMsgSize(opt.MaxRecvMsgSize)) 60 | } 61 | if opt.Insecure { 62 | opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) 63 | return grpc.Dial(target, opts...) 64 | } 65 | 66 | tc := &tls.Config{} 67 | if !opt.TLSVerify { 68 | tc.InsecureSkipVerify = true 69 | } 70 | 71 | if opt.ClientCertFile != "" && opt.ClientKeyFile != "" { 72 | // Load the client certificate and its key 73 | tlsCert, err := tls.LoadX509KeyPair(opt.ClientCertFile, opt.ClientKeyFile) 74 | if err != nil { 75 | return nil, err 76 | } 77 | if opt.TrustedCA != "" { 78 | // Load the CA certificate 79 | trustedCA, err := os.ReadFile(opt.TrustedCA) 80 | if err != nil { 81 | return nil, err 82 | } 83 | // Put the CA certificate to certificate pool 84 | caPool := x509.NewCertPool() 85 | if !caPool.AppendCertsFromPEM(trustedCA) { 86 | return nil, fmt.Errorf("failed to append trusted certificate to certificate pool. %s", trustedCA) 87 | } 88 | tc.RootCAs = caPool 89 | } 90 | tc.Certificates = []tls.Certificate{tlsCert} 91 | } 92 | 93 | opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tc))) 94 | 95 | conn, err := grpc.Dial(target, opts...) 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | return conn, nil 101 | } 102 | 103 | func Close(c *grpc.ClientConn) { 104 | // ignore close err 105 | c.Close() 106 | } 107 | 108 | func createCtxWithMd(pairs []string) context.Context { 109 | ctx := context.Background() 110 | md := metadata.Pairs(pairs...) 111 | return metadata.NewOutgoingContext(ctx, md) 112 | } 113 | 114 | func Call(c *grpc.ClientConn, method string, req []byte, opt *CallOption) ([]byte, error) { 115 | ctx := createCtxWithMd(opt.Metadata) 116 | var cancel context.CancelFunc 117 | if opt.Timeout > 0 { 118 | ctx, cancel = context.WithTimeout(ctx, opt.Timeout) 119 | defer cancel() 120 | } 121 | 122 | out := &bytes.Buffer{} 123 | err := c.Invoke(ctx, method, req, out) 124 | if err != nil { 125 | return nil, err 126 | } 127 | return out.Bytes(), nil 128 | } 129 | 130 | type Stream struct { 131 | grpc.ClientStream 132 | cancel context.CancelFunc 133 | streamType int 134 | } 135 | 136 | func NewStream(c *grpc.ClientConn, method string, req []byte, opt *CallOption, streamType int) (*Stream, error) { 137 | ctx := createCtxWithMd(opt.Metadata) 138 | var cancel context.CancelFunc 139 | if opt.Timeout > 0 { 140 | ctx, cancel = context.WithTimeout(ctx, opt.Timeout) 141 | } 142 | 143 | desc := &grpc.StreamDesc{} 144 | switch streamType { 145 | case ClientStream: 146 | desc.ClientStreams = true 147 | case ServerStream: 148 | desc.ServerStreams = true 149 | case BidirectionalStream: 150 | desc.ClientStreams = true 151 | desc.ServerStreams = true 152 | default: 153 | panic(fmt.Sprintf("Unknown stream type: %d", streamType)) 154 | } 155 | 156 | cs, err := c.NewStream(ctx, desc, method) 157 | if err != nil { 158 | cancel() 159 | return nil, err 160 | } 161 | if err := cs.SendMsg(req); err != nil { 162 | cancel() 163 | return nil, err 164 | } 165 | 166 | if streamType == ServerStream { 167 | // TODO: non-ServerStream don't work like this 168 | if err := cs.CloseSend(); err != nil { 169 | cancel() 170 | return nil, err 171 | } 172 | } 173 | 174 | s := &Stream{ 175 | ClientStream: cs, 176 | streamType: streamType, 177 | cancel: cancel, 178 | } 179 | return s, nil 180 | } 181 | 182 | func (s *Stream) Recv() ([]byte, error) { 183 | cs := s.ClientStream 184 | if s.streamType == ClientStream { 185 | // called by recv_close 186 | if err := cs.CloseSend(); err != nil { 187 | return nil, err 188 | } 189 | } 190 | 191 | out := &bytes.Buffer{} 192 | if err := cs.RecvMsg(out); err != nil { 193 | return nil, err 194 | } 195 | return out.Bytes(), nil 196 | } 197 | 198 | func (s *Stream) Send(req []byte) (bool, error) { 199 | cs := s.ClientStream 200 | if err := cs.SendMsg(req); err != nil { 201 | return false, err 202 | } 203 | return true, nil 204 | } 205 | 206 | func (s *Stream) CloseSend() (bool, error) { 207 | cs := s.ClientStream 208 | if err := cs.CloseSend(); err != nil { 209 | return false, err 210 | } 211 | return true, nil 212 | } 213 | 214 | func (s *Stream) Close() { 215 | if s.cancel != nil { 216 | s.cancel() 217 | s.cancel = nil 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /grpc-engine/conn/grpc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package conn 16 | 17 | import "testing" 18 | 19 | func TestConnLifeCycle(t *testing.T) { 20 | conn, err := Connect("localhost:2379", &ConnectOption{}) 21 | if err != nil { 22 | t.FailNow() 23 | } 24 | Close(conn) 25 | } 26 | -------------------------------------------------------------------------------- /grpc-engine/conn/proto.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package conn 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | 21 | "google.golang.org/grpc/encoding" 22 | ) 23 | 24 | // Name is the name registered for the proto compressor. 25 | const Name = "proto" 26 | 27 | func init() { 28 | encoding.RegisterCodec(codec{}) 29 | } 30 | 31 | // codec is a Codec implementation with protobuf. It is the default codec for gRPC. 32 | type codec struct{} 33 | 34 | func (codec) Marshal(v interface{}) ([]byte, error) { 35 | vv, ok := v.([]byte) 36 | if !ok { 37 | return nil, fmt.Errorf("failed to marshal, message is %T, want []byte", v) 38 | } 39 | return vv, nil 40 | } 41 | 42 | func (codec) Unmarshal(data []byte, v interface{}) error { 43 | vv, ok := v.(*bytes.Buffer) 44 | if !ok { 45 | return fmt.Errorf("failed to unmarshal, message is %T, want *bytes.Buffer", v) 46 | } 47 | _, err := vv.Write(data) 48 | return err 49 | } 50 | 51 | func (codec) Name() string { 52 | return Name 53 | } 54 | -------------------------------------------------------------------------------- /grpc-engine/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/api7/grpc-client-nginx-module 2 | 3 | go 1.21 4 | 5 | require google.golang.org/grpc v1.55.1 6 | 7 | require ( 8 | github.com/golang/protobuf v1.5.3 // indirect 9 | golang.org/x/net v0.15.0 // indirect 10 | golang.org/x/sys v0.12.0 // indirect 11 | golang.org/x/text v0.13.0 // indirect 12 | google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect 13 | google.golang.org/protobuf v1.30.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /grpc-engine/go.sum: -------------------------------------------------------------------------------- 1 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 2 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 3 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 4 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 5 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 6 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 7 | golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= 8 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 9 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 10 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 11 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 12 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 13 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 14 | google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 h1:DdoeryqhaXp1LtT/emMP1BRJPHHKFi5akj/nbx/zNTA= 15 | google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= 16 | google.golang.org/grpc v1.55.1 h1:36vzoa06ohIaveIgzr0qWDCImkKeVjvnNSV6uOmwnOw= 17 | google.golang.org/grpc v1.55.1/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= 18 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 19 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 20 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 21 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 22 | -------------------------------------------------------------------------------- /grpc-engine/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | /* 18 | #cgo LDFLAGS: -shared -ldl -lpthread 19 | #include 20 | #include 21 | #include 22 | 23 | typedef struct DialOpt { 24 | bool insecure; 25 | bool tls_verify; 26 | int max_recv_msg_size; 27 | int client_cert_len; 28 | char *client_cert; 29 | int client_key_len; 30 | char *client_key; 31 | int trusted_ca_len; 32 | char *trusted_ca; 33 | } DialOpt; 34 | 35 | typedef struct Metadata { 36 | int key_len; 37 | int value_len; 38 | char *key; 39 | char *value; 40 | } Metadata; 41 | 42 | typedef uintptr_t ngx_msec_t; 43 | typedef struct CallOpt { 44 | ngx_msec_t timeout; 45 | 46 | int metadata_len; 47 | Metadata *metadata; 48 | } CallOpt; 49 | */ 50 | import "C" 51 | import ( 52 | "fmt" 53 | "log" 54 | "os" 55 | "runtime/debug" 56 | "sync" 57 | "time" 58 | "unsafe" 59 | 60 | "google.golang.org/grpc" 61 | 62 | "github.com/api7/grpc-client-nginx-module/conn" 63 | "github.com/api7/grpc-client-nginx-module/task" 64 | ) 65 | 66 | func main() { 67 | } 68 | 69 | func init() { 70 | // only keep the latest debug log 71 | f, err := os.OpenFile("/tmp/grpc-engine-debug.log", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 72 | if err != nil { 73 | log.Printf(err.Error()) 74 | return 75 | } 76 | log.Default().SetOutput(f) 77 | log.Default().SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile) 78 | } 79 | 80 | const ( 81 | // buffer size allocated in the grpc-client-nginx-module 82 | ERR_BUF_SIZE = 512 83 | ) 84 | 85 | type EngineCtx struct { 86 | c *grpc.ClientConn 87 | } 88 | 89 | var EngineCtxRef = sync.Map{} 90 | var StreamRef = sync.Map{} 91 | 92 | func reportErr(err error, errBuf unsafe.Pointer, errLen *C.size_t) { 93 | s := err.Error() 94 | if len(s) > ERR_BUF_SIZE-1 { 95 | s = s[:ERR_BUF_SIZE-1] 96 | } 97 | 98 | pp := (*[1 << 30]byte)(errBuf) 99 | copy(pp[:], s) 100 | *errLen = C.size_t(len(s)) 101 | } 102 | 103 | func reportPanic(id uint64) { 104 | if r := recover(); r != nil { 105 | err := fmt.Errorf("%v: stack %s", r, string(debug.Stack())) 106 | task.ReportFinishedTask(id, nil, err) 107 | } 108 | } 109 | 110 | func mustFind(m *sync.Map, ref unsafe.Pointer) interface{} { 111 | res, found := m.Load(ref) 112 | if !found { 113 | log.Panicf("can't find with ref %v", ref) 114 | } 115 | return res 116 | } 117 | 118 | //export grpc_engine_connect 119 | func grpc_engine_connect(errBuf unsafe.Pointer, errLen *C.size_t, 120 | targetData unsafe.Pointer, targetLen C.int, opt *C.struct_DialOpt) unsafe.Pointer { 121 | 122 | target := string(C.GoBytes(targetData, targetLen)) 123 | 124 | co := &conn.ConnectOption{ 125 | Insecure: bool(opt.insecure), 126 | TLSVerify: bool(opt.tls_verify), 127 | MaxRecvMsgSize: int(opt.max_recv_msg_size), 128 | ClientCertFile: C.GoStringN(opt.client_cert, opt.client_cert_len), 129 | ClientKeyFile: C.GoStringN(opt.client_key, opt.client_key_len), 130 | TrustedCA: C.GoStringN(opt.trusted_ca, opt.trusted_ca_len), 131 | } 132 | 133 | defer func() { 134 | if r := recover(); r != nil { 135 | err := fmt.Errorf("%v: stack %s", r, string(debug.Stack())) 136 | reportErr(err, errBuf, errLen) 137 | } 138 | }() 139 | 140 | c, err := conn.Connect(target, co) 141 | if err != nil { 142 | reportErr(err, errBuf, errLen) 143 | return nil 144 | } 145 | 146 | ctx := EngineCtx{} 147 | ctx.c = c 148 | 149 | // A Go function called by C code may not return a Go pointer 150 | var ref unsafe.Pointer = C.malloc(C.size_t(1)) 151 | EngineCtxRef.Store(ref, &ctx) 152 | return ref 153 | } 154 | 155 | //export grpc_engine_close 156 | func grpc_engine_close(ref unsafe.Pointer) { 157 | res, found := EngineCtxRef.LoadAndDelete(ref) 158 | if !found { 159 | return 160 | } 161 | 162 | defer func() { 163 | if r := recover(); r != nil { 164 | log.Printf("%v: stack %s", r, string(debug.Stack())) 165 | } 166 | }() 167 | 168 | ctx := res.(*EngineCtx) 169 | conn.Close(ctx.c) 170 | 171 | C.free(ref) 172 | } 173 | 174 | func convertMetadata(opt *C.struct_CallOpt) []string { 175 | if opt.metadata_len == 0 { 176 | return nil 177 | } 178 | 179 | md := make([]string, opt.metadata_len*2) 180 | pair := opt.metadata 181 | for i := 0; i < int(opt.metadata_len*2); i += 2 { 182 | md[i] = C.GoStringN(pair.key, pair.key_len) 183 | md[i+1] = C.GoStringN(pair.value, pair.value_len) 184 | pair = (*C.struct_Metadata)(unsafe.Pointer(uintptr(unsafe.Pointer(pair)) + unsafe.Sizeof(*pair))) 185 | } 186 | return md 187 | } 188 | 189 | //export grpc_engine_call 190 | func grpc_engine_call(errBuf unsafe.Pointer, errLen *C.size_t, 191 | taskId C.long, ref unsafe.Pointer, 192 | methodData unsafe.Pointer, methodLen C.int, 193 | reqData unsafe.Pointer, reqLen C.int, 194 | opt *C.struct_CallOpt, 195 | ) { 196 | method := string(C.GoBytes(methodData, methodLen)) 197 | req := C.GoBytes(reqData, reqLen) 198 | ctx := mustFind(&EngineCtxRef, ref).(*EngineCtx) 199 | c := ctx.c 200 | co := &conn.CallOption{ 201 | Timeout: time.Duration(opt.timeout) * time.Millisecond, 202 | Metadata: convertMetadata(opt), 203 | } 204 | 205 | go func() { 206 | id := uint64(taskId) 207 | defer reportPanic(id) 208 | 209 | out, err := conn.Call(c, method, req, co) 210 | task.ReportFinishedTask(id, out, err) 211 | }() 212 | } 213 | 214 | //export grpc_engine_new_stream 215 | func grpc_engine_new_stream(errBuf unsafe.Pointer, errLen *C.size_t, 216 | sctx unsafe.Pointer, ref unsafe.Pointer, 217 | methodData unsafe.Pointer, methodLen C.int, 218 | reqData unsafe.Pointer, reqLen C.int, 219 | opt *C.struct_CallOpt, streamType C.int, 220 | ) { 221 | method := string(C.GoBytes(methodData, methodLen)) 222 | req := C.GoBytes(reqData, reqLen) 223 | ctx := mustFind(&EngineCtxRef, ref).(*EngineCtx) 224 | c := ctx.c 225 | co := &conn.CallOption{ 226 | Timeout: time.Duration(opt.timeout) * time.Millisecond, 227 | Metadata: convertMetadata(opt), 228 | } 229 | 230 | go func() { 231 | id := uint64(uintptr(sctx)) 232 | defer reportPanic(id) 233 | 234 | s, err := conn.NewStream(c, method, req, co, int(streamType)) 235 | if err != nil { 236 | task.ReportFinishedTask(id, nil, err) 237 | return 238 | } 239 | 240 | StreamRef.Store(sctx, s) 241 | task.ReportFinishedTask(id, nil, nil) 242 | }() 243 | } 244 | 245 | //export grpc_engine_close_stream 246 | func grpc_engine_close_stream(sctx unsafe.Pointer) { 247 | res, found := StreamRef.LoadAndDelete(sctx) 248 | if !found { 249 | // stream is already closed 250 | return 251 | } 252 | 253 | defer func() { 254 | if r := recover(); r != nil { 255 | log.Printf("%v: stack %s", r, string(debug.Stack())) 256 | } 257 | }() 258 | 259 | s := res.(*conn.Stream) 260 | s.Close() 261 | } 262 | 263 | //export grpc_engine_stream_recv 264 | func grpc_engine_stream_recv(sctx unsafe.Pointer) { 265 | s := mustFind(&StreamRef, sctx).(*conn.Stream) 266 | 267 | go func() { 268 | id := uint64(uintptr(sctx)) 269 | defer reportPanic(id) 270 | 271 | out, err := s.Recv() 272 | task.ReportFinishedTask(id, out, err) 273 | }() 274 | } 275 | 276 | //export grpc_engine_stream_send 277 | func grpc_engine_stream_send(sctx unsafe.Pointer, reqData unsafe.Pointer, reqLen C.int) { 278 | s := mustFind(&StreamRef, sctx).(*conn.Stream) 279 | req := C.GoBytes(reqData, reqLen) 280 | 281 | go func() { 282 | id := uint64(uintptr(sctx)) 283 | defer reportPanic(id) 284 | 285 | _, err := s.Send(req) 286 | task.ReportFinishedTask(id, nil, err) 287 | }() 288 | } 289 | 290 | //export grpc_engine_stream_close_send 291 | func grpc_engine_stream_close_send(sctx unsafe.Pointer) { 292 | s := mustFind(&StreamRef, sctx).(*conn.Stream) 293 | 294 | go func() { 295 | id := uint64(uintptr(sctx)) 296 | defer reportPanic(id) 297 | 298 | _, err := s.CloseSend() 299 | task.ReportFinishedTask(id, nil, err) 300 | }() 301 | } 302 | 303 | //export grpc_engine_free 304 | func grpc_engine_free(ptr unsafe.Pointer) { 305 | C.free(ptr) 306 | } 307 | 308 | //export grpc_engine_wait 309 | func grpc_engine_wait(taskNum *C.int, timeoutMSec C.int) unsafe.Pointer { 310 | timeout := time.Duration(int(timeoutMSec)) * time.Millisecond 311 | out, n := task.WaitFinishedTasks(timeout) 312 | *taskNum = C.int(n) 313 | if n == 0 { 314 | return nil 315 | } 316 | 317 | return C.CBytes(out) 318 | } 319 | -------------------------------------------------------------------------------- /grpc-engine/task/task.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package task 16 | 17 | import "C" 18 | import ( 19 | "encoding/binary" 20 | "sync" 21 | "time" 22 | "unsafe" 23 | ) 24 | 25 | const ( 26 | GrpcResTypeOk = 0 27 | GrpcResTypeErr = 1 28 | GrpcResTypeOkNotRes = 2 29 | ) 30 | 31 | var hostEndian binary.ByteOrder 32 | 33 | func init() { 34 | buf := [2]byte{} 35 | *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD) 36 | 37 | switch buf { 38 | case [2]byte{0xCD, 0xAB}: 39 | hostEndian = binary.LittleEndian 40 | case [2]byte{0xAB, 0xCD}: 41 | hostEndian = binary.BigEndian 42 | default: 43 | panic("Could not determine host endianness.") 44 | } 45 | } 46 | 47 | type taskResult struct { 48 | id uint64 49 | result []byte 50 | err error 51 | } 52 | 53 | type taskQueue struct { 54 | cond *sync.Cond 55 | 56 | done bool 57 | 58 | taskIdBuf []byte 59 | tasks []*taskResult 60 | } 61 | 62 | func NewTaskQueue() *taskQueue { 63 | lock := &sync.Mutex{} 64 | cond := sync.NewCond(lock) 65 | return &taskQueue{ 66 | cond: cond, 67 | taskIdBuf: make([]byte, 24), 68 | tasks: []*taskResult{}, 69 | } 70 | } 71 | 72 | func (self *taskQueue) signal() { 73 | self.done = true 74 | self.cond.Signal() 75 | } 76 | 77 | func (self *taskQueue) Wait(timeout time.Duration) ([]byte, int) { 78 | timer := time.AfterFunc(timeout, func() { 79 | self.cond.L.Lock() 80 | self.signal() 81 | self.cond.L.Unlock() 82 | }) 83 | 84 | self.cond.L.Lock() 85 | 86 | for !self.done { 87 | self.cond.Wait() 88 | } 89 | 90 | self.done = false 91 | timer.Stop() 92 | 93 | out := []byte{} 94 | 95 | for _, res := range self.tasks { 96 | var size uint64 97 | var ptrRes uintptr 98 | 99 | if res.err != nil { 100 | errStr := res.err.Error() 101 | size = uint64(len(errStr)) 102 | ptrRes = uintptr(C.CBytes([]byte(errStr))) | GrpcResTypeErr 103 | } else if res.result == nil { 104 | size = 0 105 | ptrRes = GrpcResTypeOkNotRes 106 | } else { 107 | size = uint64(len(res.result)) 108 | ptrRes = uintptr(C.CBytes(res.result)) 109 | } 110 | 111 | hostEndian.PutUint64(self.taskIdBuf, res.id) 112 | hostEndian.PutUint64(self.taskIdBuf[8:16], size) 113 | hostEndian.PutUint64(self.taskIdBuf[16:], uint64(ptrRes)) 114 | out = append(out, self.taskIdBuf...) 115 | } 116 | 117 | self.tasks = []*taskResult{} 118 | self.cond.L.Unlock() 119 | 120 | return out, len(out) / 24 121 | } 122 | 123 | func (self *taskQueue) Done(id uint64, result []byte, err error) { 124 | self.cond.L.Lock() 125 | 126 | self.tasks = append(self.tasks, &taskResult{ 127 | id: id, 128 | result: result, 129 | err: err, 130 | }) 131 | 132 | self.signal() 133 | self.cond.L.Unlock() 134 | } 135 | 136 | var ( 137 | finishedTaskQueue = NewTaskQueue() 138 | ) 139 | 140 | func WaitFinishedTasks(timeout time.Duration) ([]byte, int) { 141 | return finishedTaskQueue.Wait(timeout) 142 | } 143 | 144 | func ReportFinishedTask(id uint64, result []byte, err error) { 145 | finishedTaskQueue.Done(id, result, err) 146 | } 147 | 148 | func EncodePointerToBuf(ptr unsafe.Pointer) []byte { 149 | ptrRes := uintptr(ptr) 150 | buf := make([]byte, 8) 151 | hostEndian.PutUint64(buf, uint64(ptrRes)) 152 | return buf 153 | } 154 | -------------------------------------------------------------------------------- /install-util.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | set -euo pipefail 17 | set -x 18 | 19 | arch=$(uname -m | tr '[:upper:]' '[:lower:]') 20 | if [ "$arch" = "x86_64" ]; then 21 | arch="amd64" 22 | fi 23 | if [ "$arch" = "aarch64" ]; then 24 | arch="arm64" 25 | fi 26 | 27 | install_go() { 28 | if grep "NAME=" /etc/os-release | grep "Alpine"; then 29 | # the official Go binary is linked with glibc, so we need to download 30 | # it from the package manager 31 | apk add --no-cache go 32 | return 33 | fi 34 | 35 | GO_VER=1.19 36 | wget --quiet https://go.dev/dl/go${GO_VER}.linux-${arch}.tar.gz > /dev/null 37 | rm -rf /usr/local/go && tar -C /usr/local -xzf go${GO_VER}.linux-${arch}.tar.gz 38 | /usr/local/go/bin/go version 39 | } 40 | 41 | case_opt=$1 42 | case "${case_opt}" in 43 | "install_go") 44 | install_go 45 | ;; 46 | *) 47 | echo "Unsupported method: ${case_opt}" 48 | ;; 49 | esac 50 | -------------------------------------------------------------------------------- /rockspec/grpc-client-nginx-module-0.3.1-0.rockspec: -------------------------------------------------------------------------------- 1 | -- Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | -- 3 | -- Licensed under the Apache License, Version 2.0 (the "License"); 4 | -- you may not use this file except in compliance with the License. 5 | -- You may obtain a copy of the License at 6 | -- 7 | -- http://www.apache.org/licenses/LICENSE-2.0 8 | -- 9 | -- Unless required by applicable law or agreed to in writing, software 10 | -- distributed under the License is distributed on an "AS IS" BASIS, 11 | -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | -- See the License for the specific language governing permissions and 13 | -- limitations under the License. 14 | 15 | package = "grpc-client-nginx-module" 16 | version = "0.3.1-0" 17 | source = { 18 | url = "git://github.com/api7/grpc-client-nginx-module", 19 | branch = "v0.3.1", 20 | } 21 | 22 | description = { 23 | summary = "Call gRPC service in Nginx", 24 | homepage = "https://github.com/api7/grpc-client-nginx-module", 25 | license = "Apache License 2.0", 26 | maintainer = "Yuansheng Wang " 27 | } 28 | 29 | dependencies = { 30 | "lua-protobuf = 0.3.4", 31 | } 32 | 33 | 34 | build = { 35 | type = "builtin", 36 | modules = { 37 | ["resty.grpc"] = "lib/resty/grpc.lua", 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /t/GRPC_CLI.pm: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | package t::GRPC_CLI; 16 | 17 | use Test::Nginx::Socket::Lua; 18 | use Test::Nginx::Socket::Lua::Stream -Base; 19 | use Cwd qw(cwd); 20 | 21 | log_level("info"); 22 | no_long_string(); 23 | no_shuffle(); 24 | master_on(); 25 | worker_connections(128); 26 | 27 | 28 | $ENV{TEST_NGINX_HTML_DIR} ||= html_dir(); 29 | 30 | add_block_preprocessor(sub { 31 | my ($block) = @_; 32 | 33 | if (!$block->no_error_log && !$block->error_log && !$block->grep_error_log) { 34 | $block->set_value("no_error_log", "[error]\n[alert]\nERROR: AddressSanitizer"); 35 | } 36 | 37 | if (!$block->no_shutdown_error_log) { 38 | $block->set_value("no_shutdown_error_log", "LeakSanitizer"); 39 | } 40 | 41 | if (defined $block->stream_server_config) { 42 | my $stream_config = $block->stream_config // ''; 43 | $stream_config .= <<_EOC_; 44 | lua_package_path "lib/?.lua;;"; 45 | _EOC_ 46 | 47 | $block->set_value("stream_config", $stream_config); 48 | 49 | my $main_config = $block->main_config // ''; 50 | $main_config .= <<_EOC_; 51 | thread_pool grpc-client-nginx-module threads=1; 52 | _EOC_ 53 | 54 | $block->set_value("main_config", $main_config); 55 | } 56 | 57 | if (defined $block->config) { 58 | if (!$block->request) { 59 | $block->set_value("request", "GET /t"); 60 | } 61 | 62 | my $http_config = $block->http_config // ''; 63 | $http_config .= <<_EOC_; 64 | lua_package_path "lib/?.lua;;"; 65 | _EOC_ 66 | 67 | $block->set_value("http_config", $http_config); 68 | 69 | my $main_config = $block->main_config // ''; 70 | $main_config .= <<_EOC_; 71 | thread_pool grpc-client-nginx-module threads=1; 72 | _EOC_ 73 | 74 | $block->set_value("main_config", $main_config); 75 | } 76 | }); 77 | 78 | 1; 79 | -------------------------------------------------------------------------------- /t/backend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM golang:1.19-bullseye 16 | 17 | ADD . /usr/local/backend 18 | WORKDIR /usr/local/backend 19 | ARG ENABLE_PROXY 20 | RUN if [ "$ENABLE_PROXY" = "true" ] ; then go env -w GOPROXY=https://goproxy.io,direct ; fi \ 21 | && go env \ 22 | && go build 23 | ENTRYPOINT [ "/usr/local/backend/backend" ] 24 | -------------------------------------------------------------------------------- /t/backend/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/api7/grpc-client-nginx-module/t/backend 2 | 3 | go 1.19 4 | 5 | require ( 6 | google.golang.org/grpc v1.49.0 7 | google.golang.org/protobuf v1.27.1 8 | ) 9 | 10 | require ( 11 | github.com/golang/protobuf v1.5.2 // indirect 12 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect 13 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect 14 | golang.org/x/text v0.3.7 // indirect 15 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /t/backend/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 5 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 6 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 7 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 8 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 9 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 10 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 11 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 12 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 13 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 14 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 15 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 16 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 17 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 18 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 19 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 20 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 21 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 22 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 23 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 24 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 25 | github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= 26 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 27 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 28 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 29 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 30 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 31 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 32 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 33 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 34 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 35 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 36 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo= 37 | golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 38 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 39 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 40 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 41 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 42 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 43 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 44 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= 45 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 46 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 47 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 48 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 49 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 50 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 51 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 52 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 53 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 54 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 55 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 56 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 57 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 58 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 59 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= 60 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 61 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 62 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 63 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 64 | google.golang.org/grpc v1.49.0 h1:WTLtQzmQori5FUH25Pq4WT22oCsv8USpQ+F6rqtsmxw= 65 | google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= 66 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 67 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 68 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 69 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 70 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 71 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 72 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 73 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 74 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 75 | google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= 76 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 77 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 78 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 79 | -------------------------------------------------------------------------------- /t/backend/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/test.proto 16 | package main 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "io" 22 | "log" 23 | "net" 24 | "os" 25 | "os/signal" 26 | "strings" 27 | "syscall" 28 | 29 | "google.golang.org/grpc" 30 | "google.golang.org/grpc/codes" 31 | "google.golang.org/grpc/metadata" 32 | "google.golang.org/grpc/reflection" 33 | "google.golang.org/grpc/status" 34 | 35 | pb "github.com/api7/grpc-client-nginx-module/t/backend/proto" 36 | ) 37 | 38 | const ( 39 | grpcAddr = ":50051" 40 | ) 41 | 42 | type server struct { 43 | pb.UnimplementedEchoServer 44 | pb.UnimplementedClientStreamServer 45 | pb.UnimplementedBidirectionalStreamServer 46 | } 47 | 48 | func (s *server) Metadata(ctx context.Context, in *pb.RecvReq) (*pb.RecvResp, error) { 49 | data := "" 50 | md, ok := metadata.FromIncomingContext(ctx) 51 | if ok { 52 | res := map[string][]string{} 53 | for k, val := range md { 54 | if strings.HasPrefix(k, "test-") { 55 | res[k] = val 56 | } 57 | } 58 | data = fmt.Sprintf("%v", res) 59 | } 60 | return &pb.RecvResp{ 61 | Data: data, 62 | }, nil 63 | } 64 | 65 | func (s *server) Recv(stream pb.ClientStream_RecvServer) error { 66 | log.Println("client side streaming has been initiated.") 67 | var count int32 = 0 68 | totalData := "" 69 | for { 70 | req, err := stream.Recv() 71 | if err == io.EOF { 72 | log.Printf("send count:%d, data:%s\n", count, totalData) 73 | return stream.SendAndClose(&pb.RecvResp{Count: count, Data: totalData}) 74 | } 75 | if err != nil { 76 | return status.Errorf(codes.Unavailable, "Failed to read client stream: %v", err) 77 | } 78 | 79 | data := req.GetData() 80 | totalData += data 81 | count++ 82 | log.Printf("recv count:%d, data:%s\n", count, totalData) 83 | } 84 | } 85 | 86 | func (s *server) RecvMetadata(stream pb.ClientStream_RecvMetadataServer) error { 87 | log.Println("client side streaming has been initiated.") 88 | var count int32 = 0 89 | totalData := "" 90 | for { 91 | md, ok := metadata.FromIncomingContext(stream.Context()) 92 | if ok { 93 | res := map[string][]string{} 94 | for k, val := range md { 95 | if strings.HasPrefix(k, "test-") { 96 | res[k] = val 97 | } 98 | } 99 | totalData = fmt.Sprintf("%v", res) 100 | } 101 | 102 | _, err := stream.Recv() 103 | if err == io.EOF { 104 | return stream.SendAndClose(&pb.RecvResp{Count: count, Data: totalData}) 105 | } 106 | if err != nil { 107 | return status.Errorf(codes.Unavailable, "Failed to read client stream: %v", err) 108 | } 109 | } 110 | } 111 | 112 | func (s *server) Echo(stream pb.BidirectionalStream_EchoServer) error { 113 | log.Println("bidirectional streaming has been initiated.") 114 | var count int32 = 0 115 | 116 | for { 117 | req, err := stream.Recv() 118 | if err == io.EOF { 119 | return nil 120 | } 121 | if err != nil { 122 | return status.Errorf(codes.Unavailable, "Failed to read stream: %v", err) 123 | } 124 | 125 | count++ 126 | 127 | if err := stream.Send(&pb.RecvResp{Data: req.GetData(), Count: count}); err != nil { 128 | return status.Errorf(codes.Unknown, "Failed to stream response back to client: %v", err) 129 | } 130 | } 131 | } 132 | 133 | func (s *server) EchoSum(stream pb.BidirectionalStream_EchoSumServer) error { 134 | log.Println("bidirectional streaming has been initiated.") 135 | var count int32 = 0 136 | totalData := "" 137 | 138 | for { 139 | req, err := stream.Recv() 140 | if err == io.EOF { 141 | return stream.Send(&pb.RecvResp{Count: count, Data: totalData}) 142 | } 143 | if err != nil { 144 | return status.Errorf(codes.Unavailable, "Failed to read stream: %v", err) 145 | } 146 | 147 | data := req.GetData() 148 | totalData += data 149 | count++ 150 | } 151 | } 152 | 153 | func main() { 154 | go func() { 155 | lis, err := net.Listen("tcp", grpcAddr) 156 | if err != nil { 157 | log.Fatalf("failed to listen: %v", err) 158 | } 159 | s := grpc.NewServer() 160 | reflection.Register(s) 161 | pb.RegisterEchoServer(s, &server{}) 162 | pb.RegisterClientStreamServer(s, &server{}) 163 | pb.RegisterBidirectionalStreamServer(s, &server{}) 164 | if err := s.Serve(lis); err != nil { 165 | log.Fatalf("failed to serve: %v", err) 166 | } 167 | }() 168 | 169 | signals := make(chan os.Signal) 170 | signal.Notify(signals, os.Interrupt, syscall.SIGTERM) 171 | <-signals 172 | } 173 | -------------------------------------------------------------------------------- /t/backend/proto/test.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.27.1 4 | // protoc v3.14.0 5 | // source: proto/test.proto 6 | 7 | package proto 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type RecvReq struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Data string `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` 29 | } 30 | 31 | func (x *RecvReq) Reset() { 32 | *x = RecvReq{} 33 | if protoimpl.UnsafeEnabled { 34 | mi := &file_proto_test_proto_msgTypes[0] 35 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 36 | ms.StoreMessageInfo(mi) 37 | } 38 | } 39 | 40 | func (x *RecvReq) String() string { 41 | return protoimpl.X.MessageStringOf(x) 42 | } 43 | 44 | func (*RecvReq) ProtoMessage() {} 45 | 46 | func (x *RecvReq) ProtoReflect() protoreflect.Message { 47 | mi := &file_proto_test_proto_msgTypes[0] 48 | if protoimpl.UnsafeEnabled && x != nil { 49 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 50 | if ms.LoadMessageInfo() == nil { 51 | ms.StoreMessageInfo(mi) 52 | } 53 | return ms 54 | } 55 | return mi.MessageOf(x) 56 | } 57 | 58 | // Deprecated: Use RecvReq.ProtoReflect.Descriptor instead. 59 | func (*RecvReq) Descriptor() ([]byte, []int) { 60 | return file_proto_test_proto_rawDescGZIP(), []int{0} 61 | } 62 | 63 | func (x *RecvReq) GetData() string { 64 | if x != nil { 65 | return x.Data 66 | } 67 | return "" 68 | } 69 | 70 | type RecvResp struct { 71 | state protoimpl.MessageState 72 | sizeCache protoimpl.SizeCache 73 | unknownFields protoimpl.UnknownFields 74 | 75 | Count int32 `protobuf:"varint,1,opt,name=count,proto3" json:"count,omitempty"` 76 | Data string `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"` 77 | } 78 | 79 | func (x *RecvResp) Reset() { 80 | *x = RecvResp{} 81 | if protoimpl.UnsafeEnabled { 82 | mi := &file_proto_test_proto_msgTypes[1] 83 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 84 | ms.StoreMessageInfo(mi) 85 | } 86 | } 87 | 88 | func (x *RecvResp) String() string { 89 | return protoimpl.X.MessageStringOf(x) 90 | } 91 | 92 | func (*RecvResp) ProtoMessage() {} 93 | 94 | func (x *RecvResp) ProtoReflect() protoreflect.Message { 95 | mi := &file_proto_test_proto_msgTypes[1] 96 | if protoimpl.UnsafeEnabled && x != nil { 97 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 98 | if ms.LoadMessageInfo() == nil { 99 | ms.StoreMessageInfo(mi) 100 | } 101 | return ms 102 | } 103 | return mi.MessageOf(x) 104 | } 105 | 106 | // Deprecated: Use RecvResp.ProtoReflect.Descriptor instead. 107 | func (*RecvResp) Descriptor() ([]byte, []int) { 108 | return file_proto_test_proto_rawDescGZIP(), []int{1} 109 | } 110 | 111 | func (x *RecvResp) GetCount() int32 { 112 | if x != nil { 113 | return x.Count 114 | } 115 | return 0 116 | } 117 | 118 | func (x *RecvResp) GetData() string { 119 | if x != nil { 120 | return x.Data 121 | } 122 | return "" 123 | } 124 | 125 | var File_proto_test_proto protoreflect.FileDescriptor 126 | 127 | var file_proto_test_proto_rawDesc = []byte{ 128 | 0x0a, 0x10, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 129 | 0x74, 0x6f, 0x12, 0x04, 0x74, 0x65, 0x73, 0x74, 0x22, 0x1d, 0x0a, 0x07, 0x52, 0x65, 0x63, 0x76, 130 | 0x52, 0x65, 0x71, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 131 | 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x22, 0x34, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x76, 0x52, 132 | 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 133 | 0x28, 0x05, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 134 | 0x61, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x32, 0x33, 0x0a, 135 | 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 0x2b, 0x0a, 0x08, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 136 | 0x61, 0x12, 0x0d, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x71, 137 | 0x1a, 0x0e, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x73, 0x70, 138 | 0x22, 0x00, 0x32, 0x6c, 0x0a, 0x0c, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 139 | 0x61, 0x6d, 0x12, 0x29, 0x0a, 0x04, 0x52, 0x65, 0x63, 0x76, 0x12, 0x0d, 0x2e, 0x74, 0x65, 0x73, 140 | 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x71, 0x1a, 0x0e, 0x2e, 0x74, 0x65, 0x73, 0x74, 141 | 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x28, 0x01, 0x12, 0x31, 0x0a, 142 | 0x0c, 0x52, 0x65, 0x63, 0x76, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x0d, 0x2e, 143 | 0x74, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x71, 0x1a, 0x0e, 0x2e, 0x74, 144 | 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x28, 0x01, 145 | 0x32, 0x72, 0x0a, 0x13, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 146 | 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x2b, 0x0a, 0x04, 0x45, 0x63, 0x68, 0x6f, 0x12, 147 | 0x0d, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x71, 0x1a, 0x0e, 148 | 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 149 | 0x28, 0x01, 0x30, 0x01, 0x12, 0x2e, 0x0a, 0x07, 0x45, 0x63, 0x68, 0x6f, 0x53, 0x75, 0x6d, 0x12, 150 | 0x0d, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x71, 0x1a, 0x0e, 151 | 0x2e, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x52, 0x65, 0x63, 0x76, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 152 | 0x28, 0x01, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 153 | 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 154 | } 155 | 156 | var ( 157 | file_proto_test_proto_rawDescOnce sync.Once 158 | file_proto_test_proto_rawDescData = file_proto_test_proto_rawDesc 159 | ) 160 | 161 | func file_proto_test_proto_rawDescGZIP() []byte { 162 | file_proto_test_proto_rawDescOnce.Do(func() { 163 | file_proto_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_test_proto_rawDescData) 164 | }) 165 | return file_proto_test_proto_rawDescData 166 | } 167 | 168 | var file_proto_test_proto_msgTypes = make([]protoimpl.MessageInfo, 2) 169 | var file_proto_test_proto_goTypes = []interface{}{ 170 | (*RecvReq)(nil), // 0: test.RecvReq 171 | (*RecvResp)(nil), // 1: test.RecvResp 172 | } 173 | var file_proto_test_proto_depIdxs = []int32{ 174 | 0, // 0: test.Echo.Metadata:input_type -> test.RecvReq 175 | 0, // 1: test.ClientStream.Recv:input_type -> test.RecvReq 176 | 0, // 2: test.ClientStream.RecvMetadata:input_type -> test.RecvReq 177 | 0, // 3: test.BidirectionalStream.Echo:input_type -> test.RecvReq 178 | 0, // 4: test.BidirectionalStream.EchoSum:input_type -> test.RecvReq 179 | 1, // 5: test.Echo.Metadata:output_type -> test.RecvResp 180 | 1, // 6: test.ClientStream.Recv:output_type -> test.RecvResp 181 | 1, // 7: test.ClientStream.RecvMetadata:output_type -> test.RecvResp 182 | 1, // 8: test.BidirectionalStream.Echo:output_type -> test.RecvResp 183 | 1, // 9: test.BidirectionalStream.EchoSum:output_type -> test.RecvResp 184 | 5, // [5:10] is the sub-list for method output_type 185 | 0, // [0:5] is the sub-list for method input_type 186 | 0, // [0:0] is the sub-list for extension type_name 187 | 0, // [0:0] is the sub-list for extension extendee 188 | 0, // [0:0] is the sub-list for field type_name 189 | } 190 | 191 | func init() { file_proto_test_proto_init() } 192 | func file_proto_test_proto_init() { 193 | if File_proto_test_proto != nil { 194 | return 195 | } 196 | if !protoimpl.UnsafeEnabled { 197 | file_proto_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 198 | switch v := v.(*RecvReq); i { 199 | case 0: 200 | return &v.state 201 | case 1: 202 | return &v.sizeCache 203 | case 2: 204 | return &v.unknownFields 205 | default: 206 | return nil 207 | } 208 | } 209 | file_proto_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 210 | switch v := v.(*RecvResp); i { 211 | case 0: 212 | return &v.state 213 | case 1: 214 | return &v.sizeCache 215 | case 2: 216 | return &v.unknownFields 217 | default: 218 | return nil 219 | } 220 | } 221 | } 222 | type x struct{} 223 | out := protoimpl.TypeBuilder{ 224 | File: protoimpl.DescBuilder{ 225 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 226 | RawDescriptor: file_proto_test_proto_rawDesc, 227 | NumEnums: 0, 228 | NumMessages: 2, 229 | NumExtensions: 0, 230 | NumServices: 3, 231 | }, 232 | GoTypes: file_proto_test_proto_goTypes, 233 | DependencyIndexes: file_proto_test_proto_depIdxs, 234 | MessageInfos: file_proto_test_proto_msgTypes, 235 | }.Build() 236 | File_proto_test_proto = out.File 237 | file_proto_test_proto_rawDesc = nil 238 | file_proto_test_proto_goTypes = nil 239 | file_proto_test_proto_depIdxs = nil 240 | } 241 | -------------------------------------------------------------------------------- /t/backend/proto/test.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package test; 4 | option go_package = "./proto"; 5 | 6 | service Echo { 7 | rpc Metadata (RecvReq) returns (RecvResp) {} 8 | } 9 | 10 | service ClientStream { 11 | rpc Recv (stream RecvReq) returns (RecvResp) {} 12 | rpc RecvMetadata (stream RecvReq) returns (RecvResp) {} 13 | } 14 | 15 | service BidirectionalStream { 16 | rpc Echo (stream RecvReq) returns (stream RecvResp) {} 17 | rpc EchoSum (stream RecvReq) returns (stream RecvResp) {} 18 | } 19 | 20 | message RecvReq { 21 | string data = 1; 22 | } 23 | 24 | message RecvResp { 25 | int32 count = 1; 26 | string data = 2; 27 | } 28 | -------------------------------------------------------------------------------- /t/backend/proto/test_grpc.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT. 2 | 3 | package proto 4 | 5 | import ( 6 | context "context" 7 | grpc "google.golang.org/grpc" 8 | codes "google.golang.org/grpc/codes" 9 | status "google.golang.org/grpc/status" 10 | ) 11 | 12 | // This is a compile-time assertion to ensure that this generated file 13 | // is compatible with the grpc package it is being compiled against. 14 | // Requires gRPC-Go v1.32.0 or later. 15 | const _ = grpc.SupportPackageIsVersion7 16 | 17 | // EchoClient is the client API for Echo service. 18 | // 19 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 20 | type EchoClient interface { 21 | Metadata(ctx context.Context, in *RecvReq, opts ...grpc.CallOption) (*RecvResp, error) 22 | } 23 | 24 | type echoClient struct { 25 | cc grpc.ClientConnInterface 26 | } 27 | 28 | func NewEchoClient(cc grpc.ClientConnInterface) EchoClient { 29 | return &echoClient{cc} 30 | } 31 | 32 | func (c *echoClient) Metadata(ctx context.Context, in *RecvReq, opts ...grpc.CallOption) (*RecvResp, error) { 33 | out := new(RecvResp) 34 | err := c.cc.Invoke(ctx, "/test.Echo/Metadata", in, out, opts...) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return out, nil 39 | } 40 | 41 | // EchoServer is the server API for Echo service. 42 | // All implementations must embed UnimplementedEchoServer 43 | // for forward compatibility 44 | type EchoServer interface { 45 | Metadata(context.Context, *RecvReq) (*RecvResp, error) 46 | mustEmbedUnimplementedEchoServer() 47 | } 48 | 49 | // UnimplementedEchoServer must be embedded to have forward compatible implementations. 50 | type UnimplementedEchoServer struct { 51 | } 52 | 53 | func (UnimplementedEchoServer) Metadata(context.Context, *RecvReq) (*RecvResp, error) { 54 | return nil, status.Errorf(codes.Unimplemented, "method Metadata not implemented") 55 | } 56 | func (UnimplementedEchoServer) mustEmbedUnimplementedEchoServer() {} 57 | 58 | // UnsafeEchoServer may be embedded to opt out of forward compatibility for this service. 59 | // Use of this interface is not recommended, as added methods to EchoServer will 60 | // result in compilation errors. 61 | type UnsafeEchoServer interface { 62 | mustEmbedUnimplementedEchoServer() 63 | } 64 | 65 | func RegisterEchoServer(s grpc.ServiceRegistrar, srv EchoServer) { 66 | s.RegisterService(&Echo_ServiceDesc, srv) 67 | } 68 | 69 | func _Echo_Metadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 70 | in := new(RecvReq) 71 | if err := dec(in); err != nil { 72 | return nil, err 73 | } 74 | if interceptor == nil { 75 | return srv.(EchoServer).Metadata(ctx, in) 76 | } 77 | info := &grpc.UnaryServerInfo{ 78 | Server: srv, 79 | FullMethod: "/test.Echo/Metadata", 80 | } 81 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 82 | return srv.(EchoServer).Metadata(ctx, req.(*RecvReq)) 83 | } 84 | return interceptor(ctx, in, info, handler) 85 | } 86 | 87 | // Echo_ServiceDesc is the grpc.ServiceDesc for Echo service. 88 | // It's only intended for direct use with grpc.RegisterService, 89 | // and not to be introspected or modified (even as a copy) 90 | var Echo_ServiceDesc = grpc.ServiceDesc{ 91 | ServiceName: "test.Echo", 92 | HandlerType: (*EchoServer)(nil), 93 | Methods: []grpc.MethodDesc{ 94 | { 95 | MethodName: "Metadata", 96 | Handler: _Echo_Metadata_Handler, 97 | }, 98 | }, 99 | Streams: []grpc.StreamDesc{}, 100 | Metadata: "proto/test.proto", 101 | } 102 | 103 | // ClientStreamClient is the client API for ClientStream service. 104 | // 105 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 106 | type ClientStreamClient interface { 107 | Recv(ctx context.Context, opts ...grpc.CallOption) (ClientStream_RecvClient, error) 108 | RecvMetadata(ctx context.Context, opts ...grpc.CallOption) (ClientStream_RecvMetadataClient, error) 109 | } 110 | 111 | type clientStreamClient struct { 112 | cc grpc.ClientConnInterface 113 | } 114 | 115 | func NewClientStreamClient(cc grpc.ClientConnInterface) ClientStreamClient { 116 | return &clientStreamClient{cc} 117 | } 118 | 119 | func (c *clientStreamClient) Recv(ctx context.Context, opts ...grpc.CallOption) (ClientStream_RecvClient, error) { 120 | stream, err := c.cc.NewStream(ctx, &ClientStream_ServiceDesc.Streams[0], "/test.ClientStream/Recv", opts...) 121 | if err != nil { 122 | return nil, err 123 | } 124 | x := &clientStreamRecvClient{stream} 125 | return x, nil 126 | } 127 | 128 | type ClientStream_RecvClient interface { 129 | Send(*RecvReq) error 130 | CloseAndRecv() (*RecvResp, error) 131 | grpc.ClientStream 132 | } 133 | 134 | type clientStreamRecvClient struct { 135 | grpc.ClientStream 136 | } 137 | 138 | func (x *clientStreamRecvClient) Send(m *RecvReq) error { 139 | return x.ClientStream.SendMsg(m) 140 | } 141 | 142 | func (x *clientStreamRecvClient) CloseAndRecv() (*RecvResp, error) { 143 | if err := x.ClientStream.CloseSend(); err != nil { 144 | return nil, err 145 | } 146 | m := new(RecvResp) 147 | if err := x.ClientStream.RecvMsg(m); err != nil { 148 | return nil, err 149 | } 150 | return m, nil 151 | } 152 | 153 | func (c *clientStreamClient) RecvMetadata(ctx context.Context, opts ...grpc.CallOption) (ClientStream_RecvMetadataClient, error) { 154 | stream, err := c.cc.NewStream(ctx, &ClientStream_ServiceDesc.Streams[1], "/test.ClientStream/RecvMetadata", opts...) 155 | if err != nil { 156 | return nil, err 157 | } 158 | x := &clientStreamRecvMetadataClient{stream} 159 | return x, nil 160 | } 161 | 162 | type ClientStream_RecvMetadataClient interface { 163 | Send(*RecvReq) error 164 | CloseAndRecv() (*RecvResp, error) 165 | grpc.ClientStream 166 | } 167 | 168 | type clientStreamRecvMetadataClient struct { 169 | grpc.ClientStream 170 | } 171 | 172 | func (x *clientStreamRecvMetadataClient) Send(m *RecvReq) error { 173 | return x.ClientStream.SendMsg(m) 174 | } 175 | 176 | func (x *clientStreamRecvMetadataClient) CloseAndRecv() (*RecvResp, error) { 177 | if err := x.ClientStream.CloseSend(); err != nil { 178 | return nil, err 179 | } 180 | m := new(RecvResp) 181 | if err := x.ClientStream.RecvMsg(m); err != nil { 182 | return nil, err 183 | } 184 | return m, nil 185 | } 186 | 187 | // ClientStreamServer is the server API for ClientStream service. 188 | // All implementations must embed UnimplementedClientStreamServer 189 | // for forward compatibility 190 | type ClientStreamServer interface { 191 | Recv(ClientStream_RecvServer) error 192 | RecvMetadata(ClientStream_RecvMetadataServer) error 193 | mustEmbedUnimplementedClientStreamServer() 194 | } 195 | 196 | // UnimplementedClientStreamServer must be embedded to have forward compatible implementations. 197 | type UnimplementedClientStreamServer struct { 198 | } 199 | 200 | func (UnimplementedClientStreamServer) Recv(ClientStream_RecvServer) error { 201 | return status.Errorf(codes.Unimplemented, "method Recv not implemented") 202 | } 203 | func (UnimplementedClientStreamServer) RecvMetadata(ClientStream_RecvMetadataServer) error { 204 | return status.Errorf(codes.Unimplemented, "method RecvMetadata not implemented") 205 | } 206 | func (UnimplementedClientStreamServer) mustEmbedUnimplementedClientStreamServer() {} 207 | 208 | // UnsafeClientStreamServer may be embedded to opt out of forward compatibility for this service. 209 | // Use of this interface is not recommended, as added methods to ClientStreamServer will 210 | // result in compilation errors. 211 | type UnsafeClientStreamServer interface { 212 | mustEmbedUnimplementedClientStreamServer() 213 | } 214 | 215 | func RegisterClientStreamServer(s grpc.ServiceRegistrar, srv ClientStreamServer) { 216 | s.RegisterService(&ClientStream_ServiceDesc, srv) 217 | } 218 | 219 | func _ClientStream_Recv_Handler(srv interface{}, stream grpc.ServerStream) error { 220 | return srv.(ClientStreamServer).Recv(&clientStreamRecvServer{stream}) 221 | } 222 | 223 | type ClientStream_RecvServer interface { 224 | SendAndClose(*RecvResp) error 225 | Recv() (*RecvReq, error) 226 | grpc.ServerStream 227 | } 228 | 229 | type clientStreamRecvServer struct { 230 | grpc.ServerStream 231 | } 232 | 233 | func (x *clientStreamRecvServer) SendAndClose(m *RecvResp) error { 234 | return x.ServerStream.SendMsg(m) 235 | } 236 | 237 | func (x *clientStreamRecvServer) Recv() (*RecvReq, error) { 238 | m := new(RecvReq) 239 | if err := x.ServerStream.RecvMsg(m); err != nil { 240 | return nil, err 241 | } 242 | return m, nil 243 | } 244 | 245 | func _ClientStream_RecvMetadata_Handler(srv interface{}, stream grpc.ServerStream) error { 246 | return srv.(ClientStreamServer).RecvMetadata(&clientStreamRecvMetadataServer{stream}) 247 | } 248 | 249 | type ClientStream_RecvMetadataServer interface { 250 | SendAndClose(*RecvResp) error 251 | Recv() (*RecvReq, error) 252 | grpc.ServerStream 253 | } 254 | 255 | type clientStreamRecvMetadataServer struct { 256 | grpc.ServerStream 257 | } 258 | 259 | func (x *clientStreamRecvMetadataServer) SendAndClose(m *RecvResp) error { 260 | return x.ServerStream.SendMsg(m) 261 | } 262 | 263 | func (x *clientStreamRecvMetadataServer) Recv() (*RecvReq, error) { 264 | m := new(RecvReq) 265 | if err := x.ServerStream.RecvMsg(m); err != nil { 266 | return nil, err 267 | } 268 | return m, nil 269 | } 270 | 271 | // ClientStream_ServiceDesc is the grpc.ServiceDesc for ClientStream service. 272 | // It's only intended for direct use with grpc.RegisterService, 273 | // and not to be introspected or modified (even as a copy) 274 | var ClientStream_ServiceDesc = grpc.ServiceDesc{ 275 | ServiceName: "test.ClientStream", 276 | HandlerType: (*ClientStreamServer)(nil), 277 | Methods: []grpc.MethodDesc{}, 278 | Streams: []grpc.StreamDesc{ 279 | { 280 | StreamName: "Recv", 281 | Handler: _ClientStream_Recv_Handler, 282 | ClientStreams: true, 283 | }, 284 | { 285 | StreamName: "RecvMetadata", 286 | Handler: _ClientStream_RecvMetadata_Handler, 287 | ClientStreams: true, 288 | }, 289 | }, 290 | Metadata: "proto/test.proto", 291 | } 292 | 293 | // BidirectionalStreamClient is the client API for BidirectionalStream service. 294 | // 295 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. 296 | type BidirectionalStreamClient interface { 297 | Echo(ctx context.Context, opts ...grpc.CallOption) (BidirectionalStream_EchoClient, error) 298 | EchoSum(ctx context.Context, opts ...grpc.CallOption) (BidirectionalStream_EchoSumClient, error) 299 | } 300 | 301 | type bidirectionalStreamClient struct { 302 | cc grpc.ClientConnInterface 303 | } 304 | 305 | func NewBidirectionalStreamClient(cc grpc.ClientConnInterface) BidirectionalStreamClient { 306 | return &bidirectionalStreamClient{cc} 307 | } 308 | 309 | func (c *bidirectionalStreamClient) Echo(ctx context.Context, opts ...grpc.CallOption) (BidirectionalStream_EchoClient, error) { 310 | stream, err := c.cc.NewStream(ctx, &BidirectionalStream_ServiceDesc.Streams[0], "/test.BidirectionalStream/Echo", opts...) 311 | if err != nil { 312 | return nil, err 313 | } 314 | x := &bidirectionalStreamEchoClient{stream} 315 | return x, nil 316 | } 317 | 318 | type BidirectionalStream_EchoClient interface { 319 | Send(*RecvReq) error 320 | Recv() (*RecvResp, error) 321 | grpc.ClientStream 322 | } 323 | 324 | type bidirectionalStreamEchoClient struct { 325 | grpc.ClientStream 326 | } 327 | 328 | func (x *bidirectionalStreamEchoClient) Send(m *RecvReq) error { 329 | return x.ClientStream.SendMsg(m) 330 | } 331 | 332 | func (x *bidirectionalStreamEchoClient) Recv() (*RecvResp, error) { 333 | m := new(RecvResp) 334 | if err := x.ClientStream.RecvMsg(m); err != nil { 335 | return nil, err 336 | } 337 | return m, nil 338 | } 339 | 340 | func (c *bidirectionalStreamClient) EchoSum(ctx context.Context, opts ...grpc.CallOption) (BidirectionalStream_EchoSumClient, error) { 341 | stream, err := c.cc.NewStream(ctx, &BidirectionalStream_ServiceDesc.Streams[1], "/test.BidirectionalStream/EchoSum", opts...) 342 | if err != nil { 343 | return nil, err 344 | } 345 | x := &bidirectionalStreamEchoSumClient{stream} 346 | return x, nil 347 | } 348 | 349 | type BidirectionalStream_EchoSumClient interface { 350 | Send(*RecvReq) error 351 | Recv() (*RecvResp, error) 352 | grpc.ClientStream 353 | } 354 | 355 | type bidirectionalStreamEchoSumClient struct { 356 | grpc.ClientStream 357 | } 358 | 359 | func (x *bidirectionalStreamEchoSumClient) Send(m *RecvReq) error { 360 | return x.ClientStream.SendMsg(m) 361 | } 362 | 363 | func (x *bidirectionalStreamEchoSumClient) Recv() (*RecvResp, error) { 364 | m := new(RecvResp) 365 | if err := x.ClientStream.RecvMsg(m); err != nil { 366 | return nil, err 367 | } 368 | return m, nil 369 | } 370 | 371 | // BidirectionalStreamServer is the server API for BidirectionalStream service. 372 | // All implementations must embed UnimplementedBidirectionalStreamServer 373 | // for forward compatibility 374 | type BidirectionalStreamServer interface { 375 | Echo(BidirectionalStream_EchoServer) error 376 | EchoSum(BidirectionalStream_EchoSumServer) error 377 | mustEmbedUnimplementedBidirectionalStreamServer() 378 | } 379 | 380 | // UnimplementedBidirectionalStreamServer must be embedded to have forward compatible implementations. 381 | type UnimplementedBidirectionalStreamServer struct { 382 | } 383 | 384 | func (UnimplementedBidirectionalStreamServer) Echo(BidirectionalStream_EchoServer) error { 385 | return status.Errorf(codes.Unimplemented, "method Echo not implemented") 386 | } 387 | func (UnimplementedBidirectionalStreamServer) EchoSum(BidirectionalStream_EchoSumServer) error { 388 | return status.Errorf(codes.Unimplemented, "method EchoSum not implemented") 389 | } 390 | func (UnimplementedBidirectionalStreamServer) mustEmbedUnimplementedBidirectionalStreamServer() {} 391 | 392 | // UnsafeBidirectionalStreamServer may be embedded to opt out of forward compatibility for this service. 393 | // Use of this interface is not recommended, as added methods to BidirectionalStreamServer will 394 | // result in compilation errors. 395 | type UnsafeBidirectionalStreamServer interface { 396 | mustEmbedUnimplementedBidirectionalStreamServer() 397 | } 398 | 399 | func RegisterBidirectionalStreamServer(s grpc.ServiceRegistrar, srv BidirectionalStreamServer) { 400 | s.RegisterService(&BidirectionalStream_ServiceDesc, srv) 401 | } 402 | 403 | func _BidirectionalStream_Echo_Handler(srv interface{}, stream grpc.ServerStream) error { 404 | return srv.(BidirectionalStreamServer).Echo(&bidirectionalStreamEchoServer{stream}) 405 | } 406 | 407 | type BidirectionalStream_EchoServer interface { 408 | Send(*RecvResp) error 409 | Recv() (*RecvReq, error) 410 | grpc.ServerStream 411 | } 412 | 413 | type bidirectionalStreamEchoServer struct { 414 | grpc.ServerStream 415 | } 416 | 417 | func (x *bidirectionalStreamEchoServer) Send(m *RecvResp) error { 418 | return x.ServerStream.SendMsg(m) 419 | } 420 | 421 | func (x *bidirectionalStreamEchoServer) Recv() (*RecvReq, error) { 422 | m := new(RecvReq) 423 | if err := x.ServerStream.RecvMsg(m); err != nil { 424 | return nil, err 425 | } 426 | return m, nil 427 | } 428 | 429 | func _BidirectionalStream_EchoSum_Handler(srv interface{}, stream grpc.ServerStream) error { 430 | return srv.(BidirectionalStreamServer).EchoSum(&bidirectionalStreamEchoSumServer{stream}) 431 | } 432 | 433 | type BidirectionalStream_EchoSumServer interface { 434 | Send(*RecvResp) error 435 | Recv() (*RecvReq, error) 436 | grpc.ServerStream 437 | } 438 | 439 | type bidirectionalStreamEchoSumServer struct { 440 | grpc.ServerStream 441 | } 442 | 443 | func (x *bidirectionalStreamEchoSumServer) Send(m *RecvResp) error { 444 | return x.ServerStream.SendMsg(m) 445 | } 446 | 447 | func (x *bidirectionalStreamEchoSumServer) Recv() (*RecvReq, error) { 448 | m := new(RecvReq) 449 | if err := x.ServerStream.RecvMsg(m); err != nil { 450 | return nil, err 451 | } 452 | return m, nil 453 | } 454 | 455 | // BidirectionalStream_ServiceDesc is the grpc.ServiceDesc for BidirectionalStream service. 456 | // It's only intended for direct use with grpc.RegisterService, 457 | // and not to be introspected or modified (even as a copy) 458 | var BidirectionalStream_ServiceDesc = grpc.ServiceDesc{ 459 | ServiceName: "test.BidirectionalStream", 460 | HandlerType: (*BidirectionalStreamServer)(nil), 461 | Methods: []grpc.MethodDesc{}, 462 | Streams: []grpc.StreamDesc{ 463 | { 464 | StreamName: "Echo", 465 | Handler: _BidirectionalStream_Echo_Handler, 466 | ServerStreams: true, 467 | ClientStreams: true, 468 | }, 469 | { 470 | StreamName: "EchoSum", 471 | Handler: _BidirectionalStream_EchoSum_Handler, 472 | ServerStreams: true, 473 | ClientStreams: true, 474 | }, 475 | }, 476 | Metadata: "proto/test.proto", 477 | } 478 | -------------------------------------------------------------------------------- /t/bad.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # incorrect use case 16 | use t::GRPC_CLI 'no_plan'; 17 | 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: call in non-yield phases 23 | --- config 24 | location /t { 25 | content_by_lua_block { 26 | local gcli = require("resty.grpc") 27 | assert(gcli.load("t/testdata/rpc.proto")) 28 | 29 | local conn = assert(gcli.connect("127.0.0.1:2379")) 30 | package.loaded.conn = conn 31 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 32 | ngx.say(err) 33 | } 34 | header_filter_by_lua_block { 35 | local conn = package.loaded.conn 36 | local ok, err = conn:call("etcdserverpb.KV", "Range", {key = 'k'}) 37 | ngx.log(ngx.WARN, "failed to call: ", err) 38 | conn:close() 39 | } 40 | } 41 | --- response_body 42 | nil 43 | --- error_log 44 | API disabled in the context of header_filter_by_lua* 45 | 46 | 47 | 48 | === TEST 2: ensure state is recovered 49 | --- config 50 | location /t { 51 | content_by_lua_block { 52 | local gcli = require("resty.grpc") 53 | local pb = require("pb") 54 | local protoc = require("protoc").new() 55 | protoc:load([[ 56 | syntax = "proto3"; 57 | package test; 58 | message test { 59 | string data = 1; 60 | } 61 | ]]) 62 | -- trigger an error 63 | gcli.load("t/testdata/missing.proto") 64 | local encoded = pb.encode("test.test", {data = "ok"}) 65 | gcli.load("t/testdata/rpc.proto") 66 | local decoded = pb.decode("test.test", encoded) 67 | ngx.say(decoded.data) 68 | 69 | local conn = assert(gcli.connect("127.0.0.1:2379")) 70 | package.loaded.conn = conn 71 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 72 | local decoded = pb.decode("test.test", encoded) 73 | ngx.say(decoded.data) 74 | } 75 | } 76 | --- response_body 77 | ok 78 | ok 79 | -------------------------------------------------------------------------------- /t/bidirectional_stream.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: sanity 22 | --- config 23 | location /t { 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/backend/proto/test.proto")) 27 | 28 | local conn = assert(gcli.connect("127.0.0.1:50051")) 29 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "Echo", {data = "a"}) 30 | if not st then 31 | ngx.say(err) 32 | return 33 | end 34 | st:close() 35 | ngx.say("ok") 36 | } 37 | } 38 | --- response_body 39 | ok 40 | 41 | 42 | === TEST 2: send & recv 43 | --- config 44 | location /t { 45 | content_by_lua_block { 46 | local gcli = require("resty.grpc") 47 | assert(gcli.load("t/backend/proto/test.proto")) 48 | 49 | local conn = assert(gcli.connect("127.0.0.1:50051")) 50 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "Echo", {data = "a"}) 51 | if not st then 52 | ngx.say(err) 53 | return 54 | end 55 | local data, err = st:recv() 56 | if not data then 57 | ngx.say(err) 58 | return 59 | end 60 | ngx.say(data.count) 61 | ngx.say(data.data) 62 | } 63 | } 64 | --- response_body 65 | 1 66 | a 67 | 68 | 69 | 70 | === TEST 3: multi send & recv 71 | --- config 72 | location /t { 73 | content_by_lua_block { 74 | local gcli = require("resty.grpc") 75 | assert(gcli.load("t/backend/proto/test.proto")) 76 | 77 | local conn = assert(gcli.connect("127.0.0.1:50051")) 78 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "Echo", {data = "a"}) 79 | if not st then 80 | ngx.say(err) 81 | return 82 | end 83 | local data, err = st:recv() 84 | if not data then 85 | ngx.say(err) 86 | return 87 | end 88 | ngx.say(data.count) 89 | ngx.say(data.data) 90 | 91 | local sum = "" 92 | local sum_count = 0 93 | for i = 1, 4 do 94 | assert(st:send({data = tostring(i)})) 95 | local data, err = st:recv() 96 | if not data then 97 | ngx.say(err) 98 | return 99 | end 100 | 101 | sum = sum .. data.data 102 | sum_count = sum_count + data.count 103 | end 104 | ngx.say(sum_count) 105 | ngx.say(sum) 106 | } 107 | } 108 | --- response_body 109 | 1 110 | a 111 | 14 112 | 1234 113 | 114 | 115 | 116 | === TEST 4: send & close_send & recv 117 | --- config 118 | location /t { 119 | content_by_lua_block { 120 | local gcli = require("resty.grpc") 121 | assert(gcli.load("t/backend/proto/test.proto")) 122 | 123 | local conn = assert(gcli.connect("127.0.0.1:50051")) 124 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "EchoSum", {data = "a"}) 125 | if not st then 126 | ngx.say(err) 127 | return 128 | end 129 | for i = 1, 4 do 130 | assert(st:send({data = tostring(i)})) 131 | end 132 | assert(st:close_send()) 133 | local data, err = st:recv() 134 | if not data then 135 | ngx.say(err) 136 | return 137 | end 138 | ngx.say(data.count) 139 | ngx.say(data.data) 140 | } 141 | } 142 | --- response_body 143 | 5 144 | a1234 145 | -------------------------------------------------------------------------------- /t/certs/apisix.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEojCCAwqgAwIBAgIJAK253pMhgCkxMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV 3 | BAYTAkNOMRIwEAYDVQQIDAlHdWFuZ0RvbmcxDzANBgNVBAcMBlpodUhhaTEPMA0G 4 | A1UECgwGaXJlc3R5MREwDwYDVQQDDAh0ZXN0LmNvbTAgFw0xOTA2MjQyMjE4MDVa 5 | GA8yMTE5MDUzMTIyMTgwNVowVjELMAkGA1UEBhMCQ04xEjAQBgNVBAgMCUd1YW5n 6 | RG9uZzEPMA0GA1UEBwwGWmh1SGFpMQ8wDQYDVQQKDAZpcmVzdHkxETAPBgNVBAMM 7 | CHRlc3QuY29tMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAyCM0rqJe 8 | cvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5jhZB3W6BkWUWR4oNFLLSqcVb 9 | VDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfoeLj0efMiOepOSZflj9Ob4yKR 10 | 2bGdEFOdHPjm+4ggXU9jMKeLqdVvxll/JiVFBW5smPtW1Oc/BV5terhscJdOgmRr 11 | abf9xiIis9/qVYfyGn52u9452V0owUuwP7nZ01jt6iMWEGeQU6mwPENgvj1olji2 12 | WjdG2UwpUVp3jp3l7j1ekQ6mI0F7yI+LeHzfUwiyVt1TmtMWn1ztk6FfLRqwJWR/ 13 | Evm95vnfS3Le4S2ky3XAgn2UnCMyej3wDN6qHR1onpRVeXhrBajbCRDRBMwaNw/1 14 | /3Uvza8QKK10PzQR6OcQ0xo9psMkd9j9ts/dTuo2fzaqpIfyUbPST4GdqNG9NyIh 15 | /B9g26/0EWcjyO7mYVkaycrtLMaXm1u9jyRmcQQI1cGrGwyXbrieNp63AgMBAAGj 16 | cTBvMB0GA1UdDgQWBBSZtSvV8mBwl0bpkvFtgyiOUUcbszAfBgNVHSMEGDAWgBSZ 17 | tSvV8mBwl0bpkvFtgyiOUUcbszAMBgNVHRMEBTADAQH/MB8GA1UdEQQYMBaCCHRl 18 | c3QuY29tggoqLnRlc3QuY29tMA0GCSqGSIb3DQEBCwUAA4IBgQAHGEul/x7ViVgC 19 | tC8CbXEslYEkj1XVr2Y4hXZXAXKd3W7V3TC8rqWWBbr6L/tsSVFt126V5WyRmOaY 20 | 1A5pju8VhnkhYxYfZALQxJN2tZPFVeME9iGJ9BE1wPtpMgITX8Rt9kbNlENfAgOl 21 | PYzrUZN1YUQjX+X8t8/1VkSmyZysr6ngJ46/M8F16gfYXc9zFj846Z9VST0zCKob 22 | rJs3GtHOkS9zGGldqKKCj+Awl0jvTstI4qtS1ED92tcnJh5j/SSXCAB5FgnpKZWy 23 | hme45nBQj86rJ8FhN+/aQ9H9/2Ib6Q4wbpaIvf4lQdLUEcWAeZGW6Rk0JURwEog1 24 | 7/mMgkapDglgeFx9f/XztSTrkHTaX4Obr+nYrZ2V4KOB4llZnK5GeNjDrOOJDk2y 25 | IJFgBOZJWyS93dQfuKEj42hA79MuX64lMSCVQSjX+ipR289GQZqFrIhiJxLyA+Ve 26 | U/OOcSRr39Kuis/JJ+DkgHYa/PWHZhnJQBxcqXXk1bJGw9BNbhM= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /t/certs/apisix.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIG5AIBAAKCAYEAyCM0rqJecvgnCfOw4fATotPwk5Ba0gC2YvIrO+gSbQkyxXF5 3 | jhZB3W6BkWUWR4oNFLLSqcVbVDPitz/Mt46Mo8amuS6zTbQetGnBARzPLtmVhJfo 4 | eLj0efMiOepOSZflj9Ob4yKR2bGdEFOdHPjm+4ggXU9jMKeLqdVvxll/JiVFBW5s 5 | mPtW1Oc/BV5terhscJdOgmRrabf9xiIis9/qVYfyGn52u9452V0owUuwP7nZ01jt 6 | 6iMWEGeQU6mwPENgvj1olji2WjdG2UwpUVp3jp3l7j1ekQ6mI0F7yI+LeHzfUwiy 7 | Vt1TmtMWn1ztk6FfLRqwJWR/Evm95vnfS3Le4S2ky3XAgn2UnCMyej3wDN6qHR1o 8 | npRVeXhrBajbCRDRBMwaNw/1/3Uvza8QKK10PzQR6OcQ0xo9psMkd9j9ts/dTuo2 9 | fzaqpIfyUbPST4GdqNG9NyIh/B9g26/0EWcjyO7mYVkaycrtLMaXm1u9jyRmcQQI 10 | 1cGrGwyXbrieNp63AgMBAAECggGBAJM8g0duoHmIYoAJzbmKe4ew0C5fZtFUQNmu 11 | O2xJITUiLT3ga4LCkRYsdBnY+nkK8PCnViAb10KtIT+bKipoLsNWI9Xcq4Cg4G3t 12 | 11XQMgPPgxYXA6m8t+73ldhxrcKqgvI6xVZmWlKDPn+CY/Wqj5PA476B5wEmYbNC 13 | GIcd1FLl3E9Qm4g4b/sVXOHARF6iSvTR+6ol4nfWKlaXSlx2gNkHuG8RVpyDsp9c 14 | z9zUqAdZ3QyFQhKcWWEcL6u9DLBpB/gUjyB3qWhDMe7jcCBZR1ALyRyEjmDwZzv2 15 | jlv8qlLFfn9R29UI0pbuL1eRAz97scFOFme1s9oSU9a12YHfEd2wJOM9bqiKju8y 16 | DZzePhEYuTZ8qxwiPJGy7XvRYTGHAs8+iDlG4vVpA0qD++1FTpv06cg/fOdnwshE 17 | OJlEC0ozMvnM2rZ2oYejdG3aAnUHmSNa5tkJwXnmj/EMw1TEXf+H6+xknAkw05nh 18 | zsxXrbuFUe7VRfgB5ElMA/V4NsScgQKBwQDmMRtnS32UZjw4A8DsHOKFzugfWzJ8 19 | Gc+3sTgs+4dNIAvo0sjibQ3xl01h0BB2Pr1KtkgBYB8LJW/FuYdCRS/KlXH7PHgX 20 | 84gYWImhNhcNOL3coO8NXvd6+m+a/Z7xghbQtaraui6cDWPiCNd/sdLMZQ/7LopM 21 | RbM32nrgBKMOJpMok1Z6zsPzT83SjkcSxjVzgULNYEp03uf1PWmHuvjO1yELwX9/ 22 | goACViF+jst12RUEiEQIYwr4y637GQBy+9cCgcEA3pN9W5OjSPDVsTcVERig8++O 23 | BFURiUa7nXRHzKp2wT6jlMVcu8Pb2fjclxRyaMGYKZBRuXDlc/RNO3uTytGYNdC2 24 | IptU5N4M7iZHXj190xtDxRnYQWWo/PR6EcJj3f/tc3Itm1rX0JfuI3JzJQgDb9Z2 25 | s/9/ub8RRvmQV9LM/utgyOwNdf5dyVoPcTY2739X4ZzXNH+CybfNa+LWpiJIVEs2 26 | txXbgZrhmlaWzwA525nZ0UlKdfktdcXeqke9eBghAoHARVTHFy6CjV7ZhlmDEtqE 27 | U58FBOS36O7xRDdpXwsHLnCXhbFu9du41mom0W4UdzjgVI9gUqG71+SXrKr7lTc3 28 | dMHcSbplxXkBJawND/Q1rzLG5JvIRHO1AGJLmRgIdl8jNgtxgV2QSkoyKlNVbM2H 29 | Wy6ZSKM03lIj74+rcKuU3N87dX4jDuwV0sPXjzJxL7NpR/fHwgndgyPcI14y2cGz 30 | zMC44EyQdTw+B/YfMnoZx83xaaMNMqV6GYNnTHi0TO2TAoHBAKmdrh9WkE2qsr59 31 | IoHHygh7Wzez+Ewr6hfgoEK4+QzlBlX+XV/9rxIaE0jS3Sk1txadk5oFDebimuSk 32 | lQkv1pXUOqh+xSAwk5v88dBAfh2dnnSa8HFN3oz+ZfQYtnBcc4DR1y2X+fVNgr3i 33 | nxruU2gsAIPFRnmvwKPc1YIH9A6kIzqaoNt1f9VM243D6fNzkO4uztWEApBkkJgR 34 | 4s/yOjp6ovS9JG1NMXWjXQPcwTq3sQVLnAHxZRJmOvx69UmK4QKBwFYXXjeXiU3d 35 | bcrPfe6qNGjfzK+BkhWznuFUMbuxyZWDYQD5yb6ukUosrj7pmZv3BxKcKCvmONU+ 36 | CHgIXB+hG+R9S2mCcH1qBQoP/RSm+TUzS/Bl2UeuhnFZh2jSZQy3OwryUi6nhF0u 37 | LDzMI/6aO1ggsI23Ri0Y9ZtqVKczTkxzdQKR9xvoNBUufjimRlS80sJCEB3Qm20S 38 | wzarryret/7GFW1/3cz+hTj9/d45i25zArr3Pocfpur5mfz3fJO8jg== 39 | -----END RSA PRIVATE KEY----- 40 | -------------------------------------------------------------------------------- /t/certs/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFIjCCAwoCCQDTReWtwTGoCTANBgkqhkiG9w0BAQUFADBTMQswCQYDVQQGEwJD 3 | TjEOMAwGA1UECAwFSHViZWkxDjAMBgNVBAcMBVd1aGFuMRAwDgYDVQQKDAdhcGk3 4 | LmFpMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMjIxMDEzMDU1MzU0WhcNMzIxMDEw 5 | MDU1MzU0WjBTMQswCQYDVQQGEwJDTjEOMAwGA1UECAwFSHViZWkxDjAMBgNVBAcM 6 | BVd1aGFuMRAwDgYDVQQKDAdhcGk3LmFpMRIwEAYDVQQDDAlsb2NhbGhvc3QwggIi 7 | MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQD0qjnVgsqRs2YrnCWgDljZAzlV 8 | 44vKKGVLrtHXQd+w2ONCiAT8nimr3sZpgCCO3vHaRKDxD3AvKxTEiIwwzE1hErBF 9 | /LeEQ1rPfJTUsxYaCOfPFkz3TC6NPJEYe8oL33Bk/X2wp+FMdOqR6l98iF3ITRG3 10 | 3ZcwiW7dBj+7G0ywgw2S0ZoXiJlCF3lFAEkNwrHlxEqYg4qnOaA91qGX3AKQESV1 11 | FExIMvvXaKfv2TFZZ0Nka7GmkY9wo7p3ApDtd7qYdDWBMNVS0j5z8itfbwY1PRtX 12 | zVruPq0B9qonrqzrZQ7Ub8zYIrBpTM6AFI4JLAXbLdFi1eJdkTv/oD0JwTKrVNht 13 | e0LculPPpBNoCMBukr3NuMlsLYL/GsFEp6BFLoaWLHLYLcxar+79AUcplfEA4ejj 14 | kYZFEOkA9xFvVCdyEvjEKqEzlPeeBTIGBurtC1cQAoacQDhb+r2vjfztGdiQfBNz 15 | 9GaKCiuKH+vz4DHGC8Hz9GONzZqxmYGIUY1p92GSPtSAmV8EB94MEHonZymgwXHD 16 | PlvqaTGHExOZEPFEV41HRcwlafdaDrr8BEmRSu9FP0L7RxziF7uv7t3auPH/Qdu1 17 | yCsH0y5dTRQGblCMT4hzIwv+QpO1rgGLrgIjWdoJjaeV5J1DeskCtVrC7uLJV8Hq 18 | cN3gGu/zJpoXyzZ7DQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCf3a4a54JV7BdX 19 | xXxhPt/Of9gQMvZAxf2TGEjvrYBjWw9sebGuZ3FD4Tf1D8yjbVgthL0t6CRTmX5B 20 | xYaWop4RuIupfjo3R5ODPipmadP5H0sTVBmZwNvXDw/mCNkD4BdYYPmaey73zvX2 21 | OWIz/gI1r81nTqIa+vEVnPGiuG1U1nTxk+xD6KgLAeoVk9YfTEVsAufvNcfrMwb7 22 | hUUM2bHbAN0T6uQpLV6hVmLmuk+aKX+xnyjOQFumpDR0NFeGDWQeoRSuw4b56coK 23 | 8U2iHmw/9SeBZtEZ0X5/2tTS7YBUOPYSnIE+3Vi+8eVRcBOZFiI6wqwUFNLqAlTo 24 | yMWwTIo4mNmcf68faXh/bTWx8FW5sUJe/06kMgfRlrgh7PkCuPjNCmITiEquM/AY 25 | adxXJY3VwDakHbCLTA+fNPQS1seKtGHKq514P/4aQMMHEl6bIxfkPzZ601DJe29I 26 | j7gp1s7E7KgWOCo+6pO0XQgk5qLYHnG9+IVzoszBuOPvJgtNRCrsbWDOWZrSj782 27 | Az/G6JQa4qYTVa0cMim4An/D0JHS/VGC/tMfUJs6K8/Xh+06JHsiDRAqM3baTY2b 28 | MSehXZRXtkfynUwh50yI7j260DjFeqvolAzWpX9KIMc4ALzsQUBclS71CLk9QdaY 29 | TVXnCadZi0Ohi7gKJk0Af4CjHlEL4w== 30 | -----END CERTIFICATE----- 31 | -------------------------------------------------------------------------------- /t/certs/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEPTCCAiWgAwIBAgIJALYnX0giCHG5MA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV 3 | BAYTAkNOMQ4wDAYDVQQIDAVIdWJlaTEOMAwGA1UEBwwFV3VoYW4xEDAOBgNVBAoM 4 | B2FwaTcuYWkxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjEwMTMwNjAwMDJaFw0z 5 | MjEwMTAwNjAwMDJaMD8xCzAJBgNVBAYTAkNOMQ4wDAYDVQQIDAVIdWJlaTEOMAwG 6 | A1UEBwwFV3VoYW4xEDAOBgNVBAoMB2FwaTcuYWkwggEiMA0GCSqGSIb3DQEBAQUA 7 | A4IBDwAwggEKAoIBAQDEkYOGCj7ACscy7glMZij+Nm4xUezeZb9fLJUrSh0nyJ6y 8 | 29p7ktbHOgoCn0Mx0i7fGrbkAjVIggdL6Q+8tnwxhLygIBqmoHVioAuct8m+JPRD 9 | IMZxpbWcM1EIM6KP/iEWNMNV6bb2Vxx+uCq3QvFgEjqtEgljwRwC2wpzswSgu9Bi 10 | PZuQKbzQ+UEUnlYMyCiEzVZQmQz017eGOPdxAD6G8OVi4GN0zA3K6f4GBEyy7TAB 11 | BSDjVQVOI7mG+KvqtTA+sqAVGJZHrDNMoqYfPuVawf4AHlLVxl+jwjC0JNWAqduS 12 | 5P6YE5lYtUZq9mdxHzsZNJc6qI8cjWIf2Yc1jR1lAgMBAAGjKDAmMCQGA1UdEQQd 13 | MBuCCWxvY2FsaG9zdIIIdGVzdC5jb22HBH8AAAEwDQYJKoZIhvcNAQEFBQADggIB 14 | AK8KpRseu0nvWXlj8pcPSdlhAK15onMcCy7Njxdm3i7s4ScIEOQl3gp6wUuOLCyZ 15 | y/xhIOfl9W4qjw2Baxw45S1qQZeBUKVjTf4CKsOYoMVpZ6W6i3Le83ZZVe8hishq 16 | 1sLegApg6TUU6+INM5FXQpoM1tumVWUalf4mSjfO48gXXh0r1eVSHg37XynLyHKG 17 | UVdCU3qop5uXAauNW5cNGzoLaiZiFlx74JBu7bNK/xS4PNewrWcPMoXhom9kTOWN 18 | DQKl/XuG1s+8TQBIMbgOCPTqeFnNNtfuVkInHVOC/uj2VxhqiHFA9LBdbw22DDs+ 19 | 1cAaVxzs2ap21R6gRSKgEd0FtrX6pfW8eD0T6SFQc/muA/rya1bV11nKbfp7kaJS 20 | cuMk/GLe7lx0Fs0tUBiYea+Dlpbac3uO2Tz6WEzkl130klY1tG8eTDAt+OfbkhSE 21 | VCSJHPf+9HsrLorzO/J62g32QhpJ49BIkBkCx7rH3wADWiJreUobU4ICTjnXzHoT 22 | Bf9UigS6OEJii3ztVCshkCfRBAb2VeuWDo6/lOUUylrOYuNxrAXtD4eij/3LOFTm 23 | /GuKoAyu5gG5rhPtKulCn1ZD9oF7rV67/aUw9ceJkH5lV/873Y3HHtKZ8kYoPRaq 24 | Vi3RO8j1z9J9hQIGYEXz8EUK+POOLx2vZ4SNpLCIc3lP 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /t/certs/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAxJGDhgo+wArHMu4JTGYo/jZuMVHs3mW/XyyVK0odJ8iestva 3 | e5LWxzoKAp9DMdIu3xq25AI1SIIHS+kPvLZ8MYS8oCAapqB1YqALnLfJviT0QyDG 4 | caW1nDNRCDOij/4hFjTDVem29lccfrgqt0LxYBI6rRIJY8EcAtsKc7MEoLvQYj2b 5 | kCm80PlBFJ5WDMgohM1WUJkM9Ne3hjj3cQA+hvDlYuBjdMwNyun+BgRMsu0wAQUg 6 | 41UFTiO5hvir6rUwPrKgFRiWR6wzTKKmHz7lWsH+AB5S1cZfo8IwtCTVgKnbkuT+ 7 | mBOZWLVGavZncR87GTSXOqiPHI1iH9mHNY0dZQIDAQABAoIBAGV7psaOBYWKSvtt 8 | T1mlKKbOHSfTXxIWv614S+8UjWbbuasnvG2uCrrCqSZfaQaQt+gxV5jx1uZ/UxsK 9 | 0Tghko7SM7wLtxatoszvSe94J2zGkWESy8Eix1dEWyJ73gKvNg/L38k8J1yY7rox 10 | hMQmiI2L+8UcmcJpkXeb9JJ1Kv977rTXmq6EhamRQ7K5EiUncaZZDMNFrmrnY0ja 11 | tZATAkkewbK6vcl5Tr499l7lHu6qfwqZmCWXOmwEJlpCUvJUcvXaR6uLVPzDeBiF 12 | w0kWtjscQH1QQNFwtGi6J/2iKJWYlsVCEguDGam5C8m/oPI36rTs8rIKWZsnGZ0j 13 | W3pseyECgYEA+BZ37+F8eeLEeSiK+Q5zUV7RjzOHwGew7mw5E5GL1bEY6Rd0zdHL 14 | 7ukIALvrQ+FEPmF3Y+n/U3AlUCb/qhCtUz+b7DVWIacA8+zYPFkd3gcgo7jo9c8p 15 | acZa17A2ZPCLHJalVYmYhSJ2OGQHmL3CWo0KlcJ+uYn9Fa6ceJ93/x0CgYEAytZp 16 | XCGRIwQQthDyd3sVuaoscyo20mJb5Arved2PEeB3Wi/FMaBA1QSD4oOU3t+8vTJi 17 | ffoaiP46jz9yxmRf2PvuiaQpQpeSLKnH6vWLxOesT7oIsxPnId+vNJQWdn8l4Myu 18 | fouvGjKzMtZ9x4GjpMqaNt0YYcLzh7ieGmIt3OkCgYA6z5i/FQUdH9TTo/jN8c8E 19 | iyXCfMTBp9MZsi4VpXswvTrahuT5pbJigHuVMPcksFItN43F8cLFVOCDj7sr5agx 20 | A/NCm6I/eAeRy6O3KqkZ6TajmUIgg2ud15fRacR7SUqxDEVFccjmRrUfyRbD7rgU 21 | HztTJThFY6DLcwlEzwjzQQKBgGSdmSPfbrw83wFBRNUp1CwGEwiJjyorKkPjfMkp 22 | szd97hZpjdrBEIfaNoWdGbK4r0n2fU7aSmFcIrY+FfP9hGIBDmFuch0bTKIlEgsm 23 | BFAHT704neoe3E88Qw/EyaO8DJEHVyNkrNZFAgkcxsW5M/Cho3/Firur1dukI6EY 24 | xIFJAoGAXvPpXj6y1UCBJaX5Fd0zdvSRIoef00CeiZDObaB7tki9fn/re6MY6cgd 25 | kmscS9UETZvMgnFftwWjJwen0TqvId54O0/Vs5L3PJjelTRPpFdJEYy9NFqOubYZ 26 | 1K/M6xruUm7vrpI5IMT8EtZ9OAzdUeb/2ZYW9fF6OfMyYYS0m90= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /t/certs/etcd.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCf6sMQke4OUrPf 3 | lZXqKZC4uaQLMxJB2DgacBf4Q3kXcVNCNHtc2U3U5cMD8tr3/Jt5MKKun5jQrbXV 4 | FF4eVr4Iv9jgPBwQc2kBUC9QL/alsMhEuXMeqdGQcCK3F0CLJdV3zUlKUDU0kg+O 5 | Exnbl1CHXrIbpD7zLy1i3s8p39v1pYFYf4WlrQxvfa/xo97gXY5dJv8RryryLzRc 6 | uhHYBvX5MHCGpbrY61JxpfZqBo8CmLuHl1tmbeXpdHdQB11LKiuL6HtKflNjc6rg 7 | 5r8bXl1nZbM/KOZEE+muA1LVoaTyHzY/aGXz0bNy4QRUO+De9JFcTDgnXnNZVG5x 8 | cyyDBpc9AgMBAAECggEAatcEtehZPJaCeClPPF/Cwbe9YoIfe4BCk186lHI3z7K1 9 | 5nB7zt+bwVY0AUpagv3wvXoB5lrYVOsJpa9y5iAb3GqYMc/XDCKfD/KLea5hwfcn 10 | BctEn0LjsPVKLDrLs2t2gBDWG2EU+udunwQh7XTdp2Nb6V3FdOGbGAg2LgrSwP1g 11 | 0r4z14F70oWGYyTQ5N8UGuyryVrzQH525OYl38Yt7R6zJ/44FVi/2TvdfHM5ss39 12 | SXWi00Q30fzaBEf4AdHVwVCRKctwSbrIOyM53kiScFDmBGRblCWOxXbiFV+d3bjX 13 | gf2zxs7QYZrFOzOO7kLtHGua4itEB02497v+1oKDwQKBgQDOBvCVGRe2WpItOLnj 14 | SF8iz7Sm+jJGQz0D9FhWyGPvrN7IXGrsXavA1kKRz22dsU8xdKk0yciOB13Wb5y6 15 | yLsr/fPBjAhPb4h543VHFjpAQcxpsH51DE0b2oYOWMmz+rXGB5Jy8EkP7Q4njIsc 16 | 2wLod1dps8OT8zFx1jX3Us6iUQKBgQDGtKkfsvWi3HkwjFTR+/Y0oMz7bSruE5Z8 17 | g0VOHPkSr4XiYgLpQxjbNjq8fwsa/jTt1B57+By4xLpZYD0BTFuf5po+igSZhH8s 18 | QS5XnUnbM7d6Xr/da7ZkhSmUbEaMeHONSIVpYNgtRo4bB9Mh0l1HWdoevw/w5Ryt 19 | L/OQiPhfLQKBgQCh1iG1fPh7bbnVe/HI71iL58xoPbCwMLEFIjMiOFcINirqCG6V 20 | LR91Ytj34JCihl1G4/TmWnsH1hGIGDRtJLCiZeHL70u32kzCMkI1jOhFAWqoutMa 21 | 7obDkmwraONIVW/kFp6bWtSJhhTQTD4adI9cPCKWDXdcCHSWj0Xk+U8HgQKBgBng 22 | t1HYhaLzIZlP/U/nh3XtJyTrX7bnuCZ5FhKJNWrYjxAfgY+NXHRYCKg5x2F5j70V 23 | be7pLhxmCnrPTMKZhik56AaTBOxVVBaYWoewhUjV4GRAaK5Wc8d9jB+3RizPFwVk 24 | V3OU2DJ1SNZ+W2HBOsKrEfwFF/dgby6i2w6MuAP1AoGBAIxvxUygeT/6P0fHN22P 25 | zAHFI4v2925wYdb7H//D8DIADyBwv18N6YH8uH7L+USZN7e4p2k8MGGyvTXeC6aX 26 | IeVtU6fH57Ddn59VPbF20m8RCSkmBvSdcbyBmqlZSBE+fKwCliKl6u/GH0BNAWKz 27 | r8yiEiskqRmy7P7MY9hDmEbG 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /t/certs/etcd.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDrzCCApegAwIBAgIJAI3Meu/gJVTLMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV 3 | BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL 4 | BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl 5 | ci5sb2NhbDAeFw0yMDEwMjgwMzMzMDJaFw0yMTEwMjgwMzMzMDJaMG4xCzAJBgNV 6 | BAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8GA1UEBwwISGFuZ3pob3UxDTAL 7 | BgNVBAoMBHRlc3QxDTALBgNVBAsMBHRlc3QxGzAZBgNVBAMMEmV0Y2QuY2x1c3Rl 8 | ci5sb2NhbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/qwxCR7g5S 9 | s9+VleopkLi5pAszEkHYOBpwF/hDeRdxU0I0e1zZTdTlwwPy2vf8m3kwoq6fmNCt 10 | tdUUXh5Wvgi/2OA8HBBzaQFQL1Av9qWwyES5cx6p0ZBwIrcXQIsl1XfNSUpQNTSS 11 | D44TGduXUIdeshukPvMvLWLezynf2/WlgVh/haWtDG99r/Gj3uBdjl0m/xGvKvIv 12 | NFy6EdgG9fkwcIalutjrUnGl9moGjwKYu4eXW2Zt5el0d1AHXUsqK4voe0p+U2Nz 13 | quDmvxteXWdlsz8o5kQT6a4DUtWhpPIfNj9oZfPRs3LhBFQ74N70kVxMOCdec1lU 14 | bnFzLIMGlz0CAwEAAaNQME4wHQYDVR0OBBYEFFHeljijrr+SPxlH5fjHRPcC7bv2 15 | MB8GA1UdIwQYMBaAFFHeljijrr+SPxlH5fjHRPcC7bv2MAwGA1UdEwQFMAMBAf8w 16 | DQYJKoZIhvcNAQELBQADggEBAG6NNTK7sl9nJxeewVuogCdMtkcdnx9onGtCOeiQ 17 | qvh5Xwn9akZtoLMVEdceU0ihO4wILlcom3OqHs9WOd6VbgW5a19Thh2toxKidHz5 18 | rAaBMyZsQbFb6+vFshZwoCtOLZI/eIZfUUMFqMXlEPrKru1nSddNdai2+zi5rEnM 19 | HCot43+3XYuqkvWlOjoi9cP+C4epFYrxpykVbcrtbd7TK+wZNiK3xtDPnVzjdNWL 20 | geAEl9xrrk0ss4nO/EreTQgS46gVU+tLC+b23m2dU7dcKZ7RDoiA9bdVc4a2IsaS 21 | 2MvLL4NZ2nUh8hAEHiLtGMAV3C6xNbEyM07hEpDW6vk6tqk= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /t/certs/incorrect.crt: -------------------------------------------------------------------------------- 1 | test not base64 encoded crt 2 | test not base64 encoded crt 3 | test not base64 encoded crt 4 | test not base64 encoded crt 5 | test not base64 encoded crt 6 | test not base64 encoded crt 7 | test not base64 encoded crt 8 | test not base64 encoded crt 9 | test not base64 encoded crt 10 | test not base64 encoded crt 11 | test not base64 encoded crt 12 | test not base64 encoded crt 13 | -------------------------------------------------------------------------------- /t/certs/incorrect.key: -------------------------------------------------------------------------------- 1 | test not base64 encoded key 2 | test not base64 encoded key 3 | test not base64 encoded key 4 | test not base64 encoded key 5 | test not base64 encoded key 6 | test not base64 encoded key 7 | test not base64 encoded key 8 | test not base64 encoded key 9 | test not base64 encoded key 10 | test not base64 encoded key 11 | test not base64 encoded key 12 | test not base64 encoded key 13 | -------------------------------------------------------------------------------- /t/certs/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEPTCCAiWgAwIBAgIJALYnX0giCHG3MA0GCSqGSIb3DQEBBQUAMFMxCzAJBgNV 3 | BAYTAkNOMQ4wDAYDVQQIDAVIdWJlaTEOMAwGA1UEBwwFV3VoYW4xEDAOBgNVBAoM 4 | B2FwaTcuYWkxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0yMjEwMTMwNTU0MjlaFw0z 5 | MjEwMTAwNTU0MjlaMD8xCzAJBgNVBAYTAkNOMQ4wDAYDVQQIDAVIdWJlaTEOMAwG 6 | A1UEBwwFV3VoYW4xEDAOBgNVBAoMB2FwaTcuYWkwggEiMA0GCSqGSIb3DQEBAQUA 7 | A4IBDwAwggEKAoIBAQCx54COJcrFBHedG3Uy/awcOcFS6Ljlw+dFfos4sbcQUCT1 8 | DIQuw1HaI4WYlbAbRzkgfQEuOpGkKXr9Gw6ovlEyyjqpAnHfxFbLikEd6im8vLxC 9 | BzJDENoffdfWRfhjxfCrnNzxYfe0dBTpSkHikj4oBOjcidW9zsmXfHZSzjkyYPw1 10 | tofeaLFNR0hzgvDBCN6/cm8kW4Ia5fJP6wuKnEDygAVHiJJICTHRWWmH1Fmz1fmd 11 | lMQ1LECxAjSxPRm4biSQKki/PccnYFNzXWANXmOURZlsrVNMqa4ZM90lx7wZ99Cj 12 | G0Q8vMG/udIe50bBknMaNuHtmaBmjO0We3+oH6ElAgMBAAGjKDAmMCQGA1UdEQQd 13 | MBuCCWxvY2FsaG9zdIIIdGVzdC5jb22HBH8AAAEwDQYJKoZIhvcNAQEFBQADggIB 14 | AHE2mYWqGBcyNmG5xuPb/4U/G6PUtk2PHsVP6sFqoRbVcvBGSypMuGFEBztSltze 15 | k0agNfQ24FJpAK4jqVHEkF3MG0F35AyAv2GfKhiZegNsVpj6g7p+jdQivrCu263D 16 | puXlgeMGrNBh8C0zdjbcRFDQ0bszZDQQnm4YaaOSY/k9Y5f3SD1hO5XWKboPyoYg 17 | Oh8OFtFD9ePLrPjf8q31Hsu7JFfKl4OGa2qV+ND4VahFBaU3QPXTh7yQB4eShtlp 18 | XpBxz4jmFoBOccr0ZM57sTqXUQDF75amv85ce2nVAFHxvjvTapgdZvVJADBKmFwc 19 | xYVu2zx3uE/jLZ2tS2eBXaoZqbmhZ4I2Whc6pJzm3RzGe6v0Cb7wOz06K2SSTLFO 20 | vDtEQutHcDpGc8RJ6omIf3NXY+/ShlsJPRqYktaoxYRz8Mc4X/wSg2oPx7EbA/ww 21 | 6CQ8bEZqXKTvISIgHVE09O2/nbWOyrfk7+JsaMqn2NXt5HGwN+ZLFdRSpukEo72a 22 | uzCuDiGgIjIIH0I7ScU7+bzouqo3Enn3Y+RWdyQBq/TKk+KXSHFL6UM7VGs33fus 23 | IaJP6xaKd94+8lHWR6Hbz0C8jfbCpJWqc9wPM+64ggr4jATqJS2qQ0UAA7OOQ+WN 24 | ynMselyUwmSa+nq0Rh6w8i+IBmUOUqbrMrY96lNS6vSD 25 | -----END CERTIFICATE----- 26 | -------------------------------------------------------------------------------- /t/certs/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAseeAjiXKxQR3nRt1Mv2sHDnBUui45cPnRX6LOLG3EFAk9QyE 3 | LsNR2iOFmJWwG0c5IH0BLjqRpCl6/RsOqL5RMso6qQJx38RWy4pBHeopvLy8Qgcy 4 | QxDaH33X1kX4Y8Xwq5zc8WH3tHQU6UpB4pI+KATo3InVvc7Jl3x2Us45MmD8NbaH 5 | 3mixTUdIc4LwwQjev3JvJFuCGuXyT+sLipxA8oAFR4iSSAkx0Vlph9RZs9X5nZTE 6 | NSxAsQI0sT0ZuG4kkCpIvz3HJ2BTc11gDV5jlEWZbK1TTKmuGTPdJce8GffQoxtE 7 | PLzBv7nSHudGwZJzGjbh7ZmgZoztFnt/qB+hJQIDAQABAoIBAEeTKz6FZ+oSHQmh 8 | ZWqpcpg8iFIR/Wm7go2MFvktTsiKZgQNEBU8Ue8ggXxdJOgkpJA0msf9mmDgq3Gv 9 | dBthzotPn4Al0qjDdmQXcJFXh4k+qcMq4AbpfchTugWkSwXhOi3hQ2b+EkmQtmUR 10 | kiZUzhsNtp4b+lmOG78NvREdlRd2Dvnj7WizkkRVTmcMPtlqK748AqALDhtAspkc 11 | QtYFTi3ENfi/1OykYUgsLgEYlzhDx4fQ4Hjzsk651I1EaINR1v25LjuoOt/P0h0I 12 | C0a3w5VuKyPSytKZ7j3VyLeaW8qlSuiqY84EVJZM124qzAwozTSMXlpqPbAF7gmf 13 | 3V05DrUCgYEA7Ogfrs4cBAvflMnjL7rsoF9UCys/ukTOjk6SEp8iPjV7VtVtyy1x 14 | jBg6IVoeDDl7zYlCarJx1utQBSE6KFguSLGhjJY2yz6anZYOK8gXs5B0BGWoaUt2 15 | XDN95i05M165jkW3ZKKeya6VRp8/zCsJKXD36nE2yslUziocGq9lNr8CgYEAwD4J 16 | RE3p6AaP9VTgbWcTMXfXJ/rg8SVC5gqLHtJKMQBlTJ393gDIWVYTOzHfHd2qrCCD 17 | KgtkVGh6QjZ5eUlkCqvhSsxeaMvmf7Bpt33SUiytUCauZlD2klHK1X17wLvGwRGs 18 | 9YLzJ9vYZiXLUqKvD/TdV7cF90DtnM5kUSoG5RsCgYBTelO3r+7VSi3FawI/zlkV 19 | BaJCsUGqbBZwtKTqJOLXEtdcOKaQgnnRSLQrDmsqGZNTUHtzx18vwQkYVaX9k1zz 20 | xovLUx8JrQykXLyvlrkrT8phxkR6ndmeEZLaabMPM+CkjlJaBqd3H6qAERtU4RFA 21 | ZgsltDD/ccQNRW9Tw9whVwKBgF65Mo+5ZFuzgBN+Muc1NrvVNOq6L0Qi/Aelgk85 22 | ht0GOOO7pW9SK2X5h5hPRoTMyF/v1TFOU61TnYoqlUgx5V1su6HDeXFhpr2cCzsi 23 | gBOFeBBaPuyjUBtgufTf065v18siDnsafv1Q0hvi24BoMKJIIRgrUoE2ON5exAdK 24 | EgOpAoGACw6g92h4DWlIn5DPEHwKEJSJQAXLZlVIzkCwsqS1mF12Ps76ATTgrFDl 25 | A3gXfxSh8L43geZHKHINh23Ev3GYuptR5jeH/bqeJqjWeZc39qBHAuBNWjOxaPiS 26 | nc1j/Upx3fyXmsxStNzszaYMB3B2lOPQ4jG4UUVLhN3pqdejIxg= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /t/client_stream.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: sanity 22 | --- config 23 | location /t { 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/backend/proto/test.proto")) 27 | 28 | local conn = assert(gcli.connect("127.0.0.1:50051")) 29 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 30 | if not st then 31 | ngx.say(err) 32 | return 33 | end 34 | st:close() 35 | ngx.say("ok") 36 | } 37 | } 38 | --- response_body 39 | ok 40 | 41 | 42 | 43 | === TEST 2: stream closed by gc 44 | --- config 45 | location /t { 46 | content_by_lua_block { 47 | local gcli = require("resty.grpc") 48 | assert(gcli.load("t/backend/proto/test.proto")) 49 | 50 | local conn = assert(gcli.connect("127.0.0.1:50051")) 51 | do 52 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 53 | if not st then 54 | ngx.say(err) 55 | return 56 | end 57 | end 58 | ngx.say("ok") 59 | } 60 | } 61 | --- response_body 62 | ok 63 | 64 | 65 | 66 | === TEST 3: send & recv 67 | --- config 68 | location /t { 69 | content_by_lua_block { 70 | local gcli = require("resty.grpc") 71 | assert(gcli.load("t/backend/proto/test.proto")) 72 | 73 | local conn = assert(gcli.connect("127.0.0.1:50051")) 74 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 75 | if not st then 76 | ngx.say(err) 77 | return 78 | end 79 | local data, err = st:recv_close() 80 | if not data then 81 | ngx.say(err) 82 | return 83 | end 84 | ngx.say(data.count) 85 | ngx.say(data.data) 86 | } 87 | } 88 | --- response_body 89 | 1 90 | a 91 | 92 | 93 | 94 | === TEST 4: multi req & recv & close 95 | --- config 96 | location /t { 97 | content_by_lua_block { 98 | local gcli = require("resty.grpc") 99 | assert(gcli.load("t/backend/proto/test.proto")) 100 | 101 | local conn = assert(gcli.connect("127.0.0.1:50051")) 102 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 103 | if not st then 104 | ngx.say(err) 105 | return 106 | end 107 | for i = 1, 4 do 108 | local ok, err = st:send({data = tostring(i)}) 109 | if not ok then 110 | ngx.say(err) 111 | return 112 | end 113 | end 114 | local data, err = st:recv_close() 115 | if not data then 116 | ngx.say(err) 117 | return 118 | end 119 | ngx.say(data.count) 120 | ngx.say(data.data) 121 | } 122 | } 123 | --- response_body 124 | 5 125 | a1234 126 | -------------------------------------------------------------------------------- /t/conn.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | log_level("debug"); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: bad target 23 | --- config 24 | location /t { 25 | content_by_lua_block { 26 | local gcli = require("resty.grpc") 27 | local ok, err = pcall(gcli.connect, nil) 28 | ngx.say(ok) 29 | } 30 | } 31 | --- response_body 32 | false 33 | 34 | 35 | 36 | === TEST 2: without close 37 | --- config 38 | location /t { 39 | content_by_lua_block { 40 | local gcli = require("resty.grpc") 41 | local conn = assert(gcli.connect("127.0.0.1:2379")) 42 | ngx.say("ok") 43 | } 44 | } 45 | --- response_body 46 | ok 47 | 48 | 49 | 50 | === TEST 3: close twice 51 | --- config 52 | location /t { 53 | content_by_lua_block { 54 | local gcli = require("resty.grpc") 55 | local conn = assert(gcli.connect("127.0.0.1:2379")) 56 | conn:close() 57 | conn:close() 58 | ngx.say("ok") 59 | } 60 | } 61 | --- response_body 62 | ok 63 | 64 | 65 | 66 | === TEST 4: call after close 67 | --- config 68 | location /t { 69 | content_by_lua_block { 70 | local gcli = require("resty.grpc") 71 | assert(gcli.load("t/testdata/rpc.proto")) 72 | 73 | local conn = assert(gcli.connect("127.0.0.1:2379")) 74 | conn:close() 75 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 76 | ngx.say(err) 77 | } 78 | } 79 | --- response_body 80 | closed 81 | 82 | 83 | 84 | === TEST 5: call recv max size, normal 85 | --- config 86 | location /t { 87 | content_by_lua_block { 88 | local gcli = require("resty.grpc") 89 | assert(gcli.load("t/testdata/rpc.proto")) 90 | 91 | local conn = assert(gcli.connect("127.0.0.1:2379", {max_recv_msg_size = 100})) 92 | local _, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 93 | if err then 94 | return ngx.say(err) 95 | end 96 | ngx.say("ok") 97 | } 98 | } 99 | --- response_body 100 | ok 101 | 102 | 103 | 104 | === TEST 6: call recv max size, exceed 105 | --- config 106 | location /t { 107 | content_by_lua_block { 108 | local gcli = require("resty.grpc") 109 | assert(gcli.load("t/testdata/rpc.proto")) 110 | 111 | local conn = assert(gcli.connect("127.0.0.1:2379", {max_recv_msg_size = 5})) 112 | local _, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 113 | if err then 114 | return ngx.say(err) 115 | end 116 | ngx.say("ok") 117 | } 118 | } 119 | --- response_body 120 | failed to call: rpc error: code = ResourceExhausted desc = grpc: received message larger than max (28 vs. 5) 121 | 122 | 123 | 124 | === TEST 7: load str 125 | --- config 126 | location /t { 127 | content_by_lua_block { 128 | local gcli = require("resty.grpc") 129 | local f = io.open("t/testdata/rpc.proto") 130 | local content = f:read("*a") 131 | f:close() 132 | assert(gcli.load(content, gcli.PROTO_TYPE_STR)) 133 | 134 | local conn = assert(gcli.connect("127.0.0.1:2379")) 135 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 136 | local old = res.header.revision 137 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 138 | ngx.say(res.header.revision - old) 139 | } 140 | } 141 | --- response_body 142 | 1 143 | 144 | 145 | 146 | === TEST 8: int64_as_string/number/hexstring 147 | --- config 148 | location /t { 149 | content_by_lua_block { 150 | local gcli = require("resty.grpc") 151 | assert(gcli.load("t/testdata/rpc.proto")) 152 | 153 | local conn = assert(gcli.connect("127.0.0.1:2379")) 154 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, 155 | {int64_encoding = gcli.INT64_AS_STRING}) 156 | ngx.say(type(res.header.member_id)) 157 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 158 | ngx.say(type(res.header.member_id)) 159 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, 160 | {int64_encoding = gcli.INT64_AS_NUMBER}) 161 | ngx.say(type(res.header.member_id)) 162 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, 163 | {int64_encoding = gcli.INT64_AS_HEXSTRING}) 164 | ngx.say(type(res.header.member_id)) 165 | conn:close() 166 | } 167 | } 168 | --- response_body 169 | string 170 | number 171 | number 172 | string 173 | 174 | 175 | 176 | === TEST 9: stream closed during processing 177 | --- http_config 178 | server { 179 | listen 2376 http2; 180 | 181 | location / { 182 | access_by_lua_block { 183 | if package.loaded.count == nil then 184 | package.loaded.count = 0 185 | end 186 | 187 | local count = package.loaded.count 188 | if count == 1 then 189 | package.loaded.count = package.loaded.count + 1 190 | ngx.exit(499) 191 | end 192 | package.loaded.count = package.loaded.count + 1 193 | } 194 | grpc_pass grpc://127.0.0.1:2379; 195 | } 196 | } 197 | --- config 198 | location /t { 199 | content_by_lua_block { 200 | local gcli = require("resty.grpc") 201 | assert(gcli.load("t/testdata/rpc.proto")) 202 | 203 | local conn = assert(gcli.connect("127.0.0.1:2376")) 204 | local function co() 205 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}) 206 | if not st then 207 | ngx.say(err) 208 | return 209 | end 210 | 211 | local ok, err = st:recv() 212 | if not ok then 213 | ngx.log(ngx.ERR, err) 214 | return 215 | end 216 | end 217 | local ths = {} 218 | for i = 1, 3 do 219 | co() 220 | end 221 | ngx.say("true") 222 | } 223 | } 224 | --- response_body 225 | true 226 | --- grep_error_log eval 227 | qr/failed to recv: rpc error: code = Internal desc = stream terminated/ 228 | --- grep_error_log_out 229 | failed to recv: rpc error: code = Internal desc = stream terminated 230 | -------------------------------------------------------------------------------- /t/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: "3.8" 16 | 17 | services: 18 | etcd: 19 | image: bitnami/etcd:3.5.4 20 | restart: unless-stopped 21 | env_file: 22 | - t/env/etcd 23 | environment: 24 | ETCD_ADVERTISE_CLIENT_URLS: http://0.0.0.0:2379 25 | ports: 26 | - "2379:2379" 27 | - "2380:2380" 28 | 29 | etcd_tls: 30 | image: bitnami/etcd:3.5.4 31 | restart: unless-stopped 32 | env_file: 33 | - t/env/etcd 34 | environment: 35 | ETCD_ADVERTISE_CLIENT_URLS: https://0.0.0.0:12379 36 | ETCD_LISTEN_CLIENT_URLS: https://0.0.0.0:12379 37 | ETCD_CERT_FILE: /certs/etcd.pem 38 | ETCD_KEY_FILE: /certs/etcd.key 39 | ports: 40 | - "12379:12379" 41 | - "12380:12380" 42 | volumes: 43 | - ./t/certs:/certs 44 | 45 | etcd_mtls: 46 | image: bitnami/etcd:3.5.4 47 | restart: unless-stopped 48 | env_file: 49 | - t/env/etcd 50 | environment: 51 | ETCD_ADVERTISE_CLIENT_URLS: https://0.0.0.0:22379 52 | ETCD_LISTEN_CLIENT_URLS: https://0.0.0.0:22379 53 | ETCD_CERT_FILE: /certs/server.crt 54 | ETCD_KEY_FILE: /certs/server.key 55 | ETCD_CLIENT_CERT_AUTH: "true" 56 | ETCD_TRUSTED_CA_FILE: /certs/ca.crt 57 | ports: 58 | - "22379:22379" 59 | - "22380:22380" 60 | volumes: 61 | - ./t/certs:/certs 62 | 63 | backend: 64 | image: grpc-client-nginx-module-test-backend:latest 65 | restart: unless-stopped 66 | build: 67 | context: ./t/backend 68 | args: 69 | ENABLE_PROXY: ${ENABLE_PROXY} 70 | ports: 71 | - "50051:50051" 72 | -------------------------------------------------------------------------------- /t/env/etcd: -------------------------------------------------------------------------------- 1 | ALLOW_NONE_AUTHENTICATION=yes 2 | -------------------------------------------------------------------------------- /t/hup.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | BEGIN { 16 | if ($ENV{TEST_NGINX_CHECK_LEAK}) { 17 | $SkipReason = "unavailable for the hup tests"; 18 | 19 | } else { 20 | $ENV{TEST_NGINX_USE_HUP} = 1; 21 | undef $ENV{TEST_NGINX_USE_STAP}; 22 | } 23 | } 24 | 25 | use t::GRPC_CLI 'no_plan'; 26 | 27 | run_tests(); 28 | 29 | __DATA__ 30 | 31 | === TEST 1: sanity 32 | --- config 33 | location /t { 34 | content_by_lua_block { 35 | local gcli = require("resty.grpc") 36 | assert(gcli.load("t/testdata/rpc.proto")) 37 | 38 | local conn = assert(gcli.connect("127.0.0.1:2379")) 39 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 40 | local old = res.header.revision 41 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 42 | ngx.say(res.header.revision - old) 43 | conn:close() 44 | } 45 | } 46 | --- response_body 47 | 1 48 | 49 | 50 | 51 | === TEST 2: call repeatedly 52 | --- config 53 | location /t { 54 | content_by_lua_block { 55 | local gcli = require("resty.grpc") 56 | assert(gcli.load("t/testdata/rpc.proto")) 57 | 58 | local conn = assert(gcli.connect("127.0.0.1:2379")) 59 | for i = 1, 3 do 60 | assert(conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'})) 61 | local res, err = conn:call("etcdserverpb.KV", "Range", {key = 'k', range_end = 'ka', revision = 0}) 62 | if not res then 63 | ngx.log(ngx.ERR, err) 64 | return 65 | end 66 | local kv = res.kvs[1] 67 | if kv == nil then 68 | return 69 | end 70 | ngx.say(kv.key, " ", kv.value) 71 | local res, err = conn:call("etcdserverpb.KV", "DeleteRange", {key = 'k', range_end = 'ka'}) 72 | if not res then 73 | ngx.log(ngx.ERR, err) 74 | return 75 | end 76 | ngx.say(res.deleted) 77 | end 78 | } 79 | } 80 | --- response_body 81 | k v 82 | 1 83 | k v 84 | 1 85 | k v 86 | 1 87 | -------------------------------------------------------------------------------- /t/metadata.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: unary 22 | --- config 23 | location /t { 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/backend/proto/test.proto", gcli.PROTO_TYPE_FILE)) 27 | 28 | local conn = assert(gcli.connect("127.0.0.1:50051")) 29 | local res = assert(conn:call("test.Echo", "Metadata", {data = "a"}, {metadata = { 30 | {"test-k", "v"}, 31 | {"test-repeated", "v1"}, 32 | {"test-repeated", "v2"}, 33 | }})) 34 | ngx.say(res.data) 35 | conn:close() 36 | } 37 | } 38 | --- response_body 39 | map[test-k:[v] test-repeated:[v1 v2]] 40 | 41 | 42 | 43 | === TEST 2: stream 44 | --- config 45 | location /t { 46 | content_by_lua_block { 47 | local gcli = require("resty.grpc") 48 | assert(gcli.load("t/backend/proto/test.proto", gcli.PROTO_TYPE_FILE)) 49 | 50 | local conn = assert(gcli.connect("127.0.0.1:50051")) 51 | local st, err = conn:new_client_stream("test.ClientStream", "RecvMetadata", {data = "a"}, {metadata = { 52 | {"test-k", "v"}, 53 | {"test-repeated", "v1"}, 54 | {"test-repeated", "v2"}, 55 | }}) 56 | if not st then 57 | ngx.say(err) 58 | return 59 | end 60 | local data, err = st:recv_close() 61 | if not data then 62 | ngx.say(err) 63 | return 64 | end 65 | ngx.say(data.data) 66 | conn:close() 67 | } 68 | } 69 | --- response_body 70 | map[test-k:[v] test-repeated:[v1 v2]] 71 | -------------------------------------------------------------------------------- /t/mtls.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: mtls ok 22 | --- config 23 | location /t { 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/testdata/rpc.proto")) 27 | 28 | local conn = assert(gcli.connect("127.0.0.1:22379", 29 | { 30 | tls_verify = true, 31 | insecure = false, 32 | client_cert = "t/certs/client.crt", 33 | client_key = "t/certs/client.key", 34 | trusted_ca = "t/certs/ca.crt" 35 | }) 36 | ) 37 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 38 | if err then 39 | return ngx.say(err) 40 | end 41 | conn:close() 42 | ngx.say("ok") 43 | } 44 | } 45 | --- response_body 46 | ok 47 | 48 | 49 | 50 | === TEST 2: mtls missing key 51 | --- config 52 | location /t { 53 | content_by_lua_block { 54 | local gcli = require("resty.grpc") 55 | assert(gcli.load("t/testdata/rpc.proto")) 56 | 57 | local conn, err = gcli.connect("127.0.0.1:22379", 58 | { 59 | tls_verify = true, 60 | insecure = false, 61 | client_cert = "t/certs/client.crt" 62 | } 63 | ) 64 | if err then 65 | return ngx.say(err) 66 | end 67 | conn:close() 68 | ngx.say("ok") 69 | } 70 | } 71 | --- response_body 72 | client_cert and client_key must both be present or both absent: cert: t/certs/client.crt key: 73 | 74 | 75 | 76 | === TEST 3: mtls cert not exits 77 | --- config 78 | location /t { 79 | content_by_lua_block { 80 | local gcli = require("resty.grpc") 81 | assert(gcli.load("t/testdata/rpc.proto")) 82 | 83 | local conn, err = gcli.connect("127.0.0.1:22379", 84 | { 85 | tls_verify = true, 86 | insecure = false, 87 | client_cert = "tt/certs/client.crt", 88 | client_key = "t/certs/client.key" 89 | } 90 | ) 91 | if err then 92 | return ngx.say(err) 93 | end 94 | conn:close() 95 | ngx.say("ok") 96 | } 97 | } 98 | --- response_body 99 | open tt/certs/client.crt: no such file or directory 100 | 101 | 102 | 103 | === TEST 4: mtls cert incorrect cert format 104 | --- config 105 | location /t { 106 | content_by_lua_block { 107 | local gcli = require("resty.grpc") 108 | assert(gcli.load("t/testdata/rpc.proto")) 109 | 110 | local conn, err = gcli.connect("127.0.0.1:22379", 111 | { 112 | tls_verify = true, 113 | insecure = false, 114 | client_cert = "t/certs/incorrect.crt", 115 | client_key = "t/certs/incorrect.key" 116 | } 117 | ) 118 | if err then 119 | return ngx.say(err) 120 | end 121 | conn:close() 122 | ngx.say("ok") 123 | } 124 | } 125 | --- response_body 126 | tls: failed to find any PEM data in certificate input 127 | 128 | 129 | 130 | === TEST 5: mtls wrong client certificate 131 | --- config 132 | location /t { 133 | content_by_lua_block { 134 | local gcli = require("resty.grpc") 135 | assert(gcli.load("t/testdata/rpc.proto")) 136 | 137 | local conn = assert(gcli.connect("127.0.0.1:22379", 138 | { 139 | tls_verify = true, 140 | insecure = false, 141 | client_cert = "t/certs/client.crt", 142 | client_key = "t/certs/client.key", 143 | }) 144 | ) 145 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 146 | if err then 147 | return ngx.say(err) 148 | end 149 | conn:close() 150 | ngx.say("ok") 151 | } 152 | } 153 | --- response_body 154 | failed to call: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: tls: failed to verify certificate: x509: certificate signed by unknown authority" 155 | 156 | 157 | 158 | === TEST 6: pass client cert when we don't verify server cert 159 | --- config 160 | location /t { 161 | content_by_lua_block { 162 | local gcli = require("resty.grpc") 163 | assert(gcli.load("t/testdata/rpc.proto")) 164 | 165 | local conn = assert(gcli.connect("127.0.0.1:22379", 166 | { 167 | tls_verify = true, 168 | insecure = false, 169 | client_cert = "t/certs/client.crt", 170 | client_key = "t/certs/client.key", 171 | trusted_ca = "t/certs/ca.crt" 172 | }) 173 | ) 174 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 175 | if err then 176 | return ngx.say(err) 177 | end 178 | conn:close() 179 | ngx.say("ok") 180 | } 181 | } 182 | --- response_body 183 | ok 184 | -------------------------------------------------------------------------------- /t/nginx.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: lua-resty-shell 22 | --- config 23 | location /t { 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/testdata/rpc.proto")) 27 | local shell = require("resty.shell") 28 | local stdin = "hello" 29 | local timeout = 1000 -- ms 30 | local max_size = 4096 -- byte 31 | 32 | local ok, stdout, stderr, reason, status = 33 | shell.run([[perl -e 'warn "he\n"; print <>']], stdin, timeout, max_size) 34 | if not ok then 35 | ngx.say(stderr) 36 | return 37 | end 38 | 39 | local conn = assert(gcli.connect("127.0.0.1:2379")) 40 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 41 | local old = res.header.revision 42 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 43 | ngx.say(res.header.revision - old) 44 | conn:close() 45 | } 46 | } 47 | --- response_body 48 | 1 49 | -------------------------------------------------------------------------------- /t/server_stream.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: sanity 22 | --- config 23 | location /t { 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/testdata/rpc.proto")) 27 | 28 | local conn = assert(gcli.connect("127.0.0.1:2379")) 29 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}) 30 | if not st then 31 | ngx.say(err) 32 | return 33 | end 34 | st:close() 35 | ngx.say("ok") 36 | } 37 | } 38 | --- response_body 39 | ok 40 | 41 | 42 | 43 | === TEST 2: multi streams 44 | --- config 45 | location /t { 46 | content_by_lua_block { 47 | local gcli = require("resty.grpc") 48 | assert(gcli.load("t/testdata/rpc.proto")) 49 | 50 | local conn = assert(gcli.connect("127.0.0.1:2379")) 51 | local function co() 52 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}) 53 | if not st then 54 | ngx.say(err) 55 | return 56 | end 57 | end 58 | local ths = {} 59 | for i = 1, 10 do 60 | local th = ngx.thread.spawn(co) 61 | table.insert(ths, th) 62 | end 63 | local ok = ngx.thread.wait(unpack(ths)) 64 | ngx.say(ok) 65 | } 66 | } 67 | --- response_body 68 | true 69 | 70 | 71 | 72 | === TEST 3: recv 73 | --- config 74 | location /t { 75 | content_by_lua_block { 76 | local gcli = require("resty.grpc") 77 | assert(gcli.load("t/testdata/rpc.proto")) 78 | 79 | local conn = assert(gcli.connect("127.0.0.1:2379")) 80 | local write_conn = assert(gcli.connect("127.0.0.1:2379")) 81 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}, {timeout = 3000}) 82 | if not st then 83 | ngx.say(err) 84 | return 85 | end 86 | 87 | local function co() 88 | assert(write_conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'recv'})) 89 | assert(write_conn:call("etcdserverpb.KV", "DeleteRange", {key = 'k'})) 90 | end 91 | ngx.thread.spawn(co) 92 | 93 | local old = assert(st:recv()) 94 | local res = assert(st:recv()) 95 | ngx.say(res.header.revision - old.header.revision) 96 | ngx.say(res.events[1].type, " ", res.events[1].kv.value) 97 | local res = assert(st:recv()) 98 | ngx.say(res.events[1].type, " ", res.events[1].kv.key) 99 | } 100 | } 101 | --- response_body 102 | 1 103 | PUT recv 104 | DELETE k 105 | 106 | 107 | 108 | === TEST 4: multiple watcher 109 | --- config 110 | location /t { 111 | content_by_lua_block { 112 | local gcli = require("resty.grpc") 113 | assert(gcli.load("t/testdata/rpc.proto")) 114 | 115 | local conn = assert(gcli.connect("127.0.0.1:2379")) 116 | 117 | local function co(st, i) 118 | local res = assert(st:recv()) 119 | for n = 1, #res.events do 120 | ngx.log(ngx.WARN, i, ": event type: ", res.events[n].type) 121 | end 122 | assert(res.events[1].type == "PUT") 123 | 124 | local ev 125 | if not res.events[2] then 126 | res = assert(st:recv()) 127 | ev = res.events[1] 128 | else 129 | ev = res.events[2] 130 | end 131 | ngx.log(ngx.WARN, i, ": event type: ", ev.type) 132 | assert(ev.type == "DELETE") 133 | ngx.log(ngx.WARN, i, ": event on key: ", ev.kv.key) 134 | end 135 | 136 | local ths = {} 137 | for i = 1, 5 do 138 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}, {timeout = 3000}) 139 | if not st then 140 | ngx.say(err) 141 | return 142 | end 143 | assert(st:recv()) 144 | local th = ngx.thread.spawn(co, st, i) 145 | table.insert(ths, th) 146 | end 147 | 148 | assert(conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'recv'})) 149 | assert(conn:call("etcdserverpb.KV", "DeleteRange", {key = 'k'})) 150 | 151 | local ok = ngx.thread.wait(unpack(ths)) 152 | ngx.say(ok) 153 | } 154 | } 155 | --- response_body 156 | true 157 | --- grep_error_log eval 158 | qr/event on key: \w+/ 159 | --- grep_error_log_out 160 | event on key: k 161 | event on key: k 162 | event on key: k 163 | event on key: k 164 | event on key: k 165 | 166 | 167 | 168 | === TEST 5: lifetime timeout 169 | --- config 170 | location /t { 171 | content_by_lua_block { 172 | local gcli = require("resty.grpc") 173 | assert(gcli.load("t/testdata/rpc.proto")) 174 | 175 | local conn = assert(gcli.connect("127.0.0.1:2379")) 176 | local write_conn = assert(gcli.connect("127.0.0.1:2379")) 177 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}, {timeout = 100}) 178 | if not st then 179 | ngx.say(err) 180 | return 181 | end 182 | 183 | assert(st:recv()) 184 | local res, err = st:recv() 185 | ngx.say(err) 186 | } 187 | } 188 | --- response_body eval 189 | qr/(context deadline exceeded|timeout)/ 190 | 191 | 192 | 193 | === TEST 6: int64 encoding 194 | --- config 195 | location /t { 196 | content_by_lua_block { 197 | local gcli = require("resty.grpc") 198 | assert(gcli.load("t/testdata/rpc.proto")) 199 | 200 | local conn = assert(gcli.connect("127.0.0.1:2379")) 201 | local write_conn = assert(gcli.connect("127.0.0.1:2379")) 202 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}, 203 | {timeout = 3000, int64_encoding = gcli.INT64_AS_STRING}) 204 | if not st then 205 | ngx.say(err) 206 | return 207 | end 208 | 209 | local function co() 210 | assert(write_conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'recv'})) 211 | assert(write_conn:call("etcdserverpb.KV", "DeleteRange", {key = 'k'})) 212 | end 213 | ngx.thread.spawn(co) 214 | 215 | local res = assert(st:recv()) 216 | ngx.say(type(res.header.member_id)) 217 | local res = assert(st:recv()) 218 | ngx.say(type(res.header.member_id)) 219 | } 220 | } 221 | --- response_body 222 | string 223 | string 224 | -------------------------------------------------------------------------------- /t/stream/bidirectional_stream.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: sanity 22 | --- stream_server_config 23 | content_by_lua_block { 24 | local gcli = require("resty.grpc") 25 | assert(gcli.load("t/backend/proto/test.proto")) 26 | 27 | local conn = assert(gcli.connect("127.0.0.1:50051")) 28 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "Echo", {data = "a"}) 29 | if not st then 30 | ngx.say(err) 31 | return 32 | end 33 | st:close() 34 | ngx.say("ok") 35 | } 36 | --- stream_response 37 | ok 38 | 39 | 40 | === TEST 2: send & recv 41 | --- stream_server_config 42 | content_by_lua_block { 43 | local gcli = require("resty.grpc") 44 | assert(gcli.load("t/backend/proto/test.proto")) 45 | 46 | local conn = assert(gcli.connect("127.0.0.1:50051")) 47 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "Echo", {data = "a"}) 48 | if not st then 49 | ngx.say(err) 50 | return 51 | end 52 | local data, err = st:recv() 53 | if not data then 54 | ngx.say(err) 55 | return 56 | end 57 | ngx.say(data.count) 58 | ngx.say(data.data) 59 | } 60 | --- stream_response 61 | 1 62 | a 63 | 64 | 65 | 66 | === TEST 3: multi send & recv 67 | --- stream_server_config 68 | content_by_lua_block { 69 | local gcli = require("resty.grpc") 70 | assert(gcli.load("t/backend/proto/test.proto")) 71 | 72 | local conn = assert(gcli.connect("127.0.0.1:50051")) 73 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "Echo", {data = "a"}) 74 | if not st then 75 | ngx.say(err) 76 | return 77 | end 78 | local data, err = st:recv() 79 | if not data then 80 | ngx.say(err) 81 | return 82 | end 83 | ngx.say(data.count) 84 | ngx.say(data.data) 85 | 86 | local sum = "" 87 | local sum_count = 0 88 | for i = 1, 4 do 89 | assert(st:send({data = tostring(i)})) 90 | local data, err = st:recv() 91 | if not data then 92 | ngx.say(err) 93 | return 94 | end 95 | 96 | sum = sum .. data.data 97 | sum_count = sum_count + data.count 98 | end 99 | ngx.say(sum_count) 100 | ngx.say(sum) 101 | } 102 | --- stream_response 103 | 1 104 | a 105 | 14 106 | 1234 107 | 108 | 109 | 110 | === TEST 4: send & close_send & recv 111 | --- stream_server_config 112 | content_by_lua_block { 113 | local gcli = require("resty.grpc") 114 | assert(gcli.load("t/backend/proto/test.proto")) 115 | 116 | local conn = assert(gcli.connect("127.0.0.1:50051")) 117 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "EchoSum", {data = "a"}) 118 | if not st then 119 | ngx.say(err) 120 | return 121 | end 122 | for i = 1, 4 do 123 | assert(st:send({data = tostring(i)})) 124 | end 125 | assert(st:close_send()) 126 | local data, err = st:recv() 127 | if not data then 128 | ngx.say(err) 129 | return 130 | end 131 | ngx.say(data.count) 132 | ngx.say(data.data) 133 | } 134 | --- stream_response 135 | 5 136 | a1234 137 | -------------------------------------------------------------------------------- /t/stream/client_stream.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: sanity 22 | --- stream_server_config 23 | content_by_lua_block { 24 | local gcli = require("resty.grpc") 25 | assert(gcli.load("t/backend/proto/test.proto")) 26 | 27 | local conn = assert(gcli.connect("127.0.0.1:50051")) 28 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 29 | if not st then 30 | ngx.say(err) 31 | return 32 | end 33 | st:close() 34 | ngx.say("ok") 35 | } 36 | --- stream_response 37 | ok 38 | 39 | 40 | 41 | === TEST 2: stream closed by gc 42 | --- stream_server_config 43 | content_by_lua_block { 44 | local gcli = require("resty.grpc") 45 | assert(gcli.load("t/backend/proto/test.proto")) 46 | 47 | local conn = assert(gcli.connect("127.0.0.1:50051")) 48 | do 49 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 50 | if not st then 51 | ngx.say(err) 52 | return 53 | end 54 | end 55 | ngx.say("ok") 56 | } 57 | --- stream_response 58 | ok 59 | 60 | 61 | 62 | === TEST 3: send & recv 63 | --- stream_server_config 64 | content_by_lua_block { 65 | local gcli = require("resty.grpc") 66 | assert(gcli.load("t/backend/proto/test.proto")) 67 | 68 | local conn = assert(gcli.connect("127.0.0.1:50051")) 69 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 70 | if not st then 71 | ngx.say(err) 72 | return 73 | end 74 | local data, err = st:recv_close() 75 | if not data then 76 | ngx.say(err) 77 | return 78 | end 79 | ngx.say(data.count) 80 | ngx.say(data.data) 81 | } 82 | --- stream_response 83 | 1 84 | a 85 | 86 | 87 | 88 | === TEST 4: multi req & recv & close 89 | --- stream_server_config 90 | content_by_lua_block { 91 | local gcli = require("resty.grpc") 92 | assert(gcli.load("t/backend/proto/test.proto")) 93 | 94 | local conn = assert(gcli.connect("127.0.0.1:50051")) 95 | local st, err = conn:new_client_stream("test.ClientStream", "Recv", {data = "a"}) 96 | if not st then 97 | ngx.say(err) 98 | return 99 | end 100 | for i = 1, 4 do 101 | local ok, err = st:send({data = tostring(i)}) 102 | if not ok then 103 | ngx.say(err) 104 | return 105 | end 106 | end 107 | local data, err = st:recv_close() 108 | if not data then 109 | ngx.say(err) 110 | return 111 | end 112 | ngx.say(data.count) 113 | ngx.say(data.data) 114 | } 115 | --- stream_response 116 | 5 117 | a1234 118 | -------------------------------------------------------------------------------- /t/stream/conn.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | log_level("debug"); 18 | run_tests(); 19 | 20 | __DATA__ 21 | 22 | === TEST 1: bad target 23 | --- stream_server_config 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | local ok, err = pcall(gcli.connect, nil) 27 | ngx.say(ok) 28 | } 29 | --- stream_response 30 | false 31 | 32 | 33 | 34 | === TEST 2: without close 35 | --- stream_server_config 36 | content_by_lua_block { 37 | local gcli = require("resty.grpc") 38 | local conn = assert(gcli.connect("127.0.0.1:2379")) 39 | ngx.say("ok") 40 | } 41 | --- stream_response 42 | ok 43 | 44 | 45 | 46 | === TEST 3: close twice 47 | --- stream_server_config 48 | content_by_lua_block { 49 | local gcli = require("resty.grpc") 50 | local conn = assert(gcli.connect("127.0.0.1:2379")) 51 | conn:close() 52 | conn:close() 53 | ngx.say("ok") 54 | } 55 | --- stream_response 56 | ok 57 | 58 | 59 | 60 | === TEST 4: call after close 61 | --- stream_server_config 62 | content_by_lua_block { 63 | local gcli = require("resty.grpc") 64 | assert(gcli.load("t/testdata/rpc.proto")) 65 | 66 | local conn = assert(gcli.connect("127.0.0.1:2379")) 67 | conn:close() 68 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 69 | ngx.say(err) 70 | } 71 | --- stream_response 72 | closed 73 | -------------------------------------------------------------------------------- /t/stream/server_stream.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: sanity 22 | --- stream_server_config 23 | content_by_lua_block { 24 | local gcli = require("resty.grpc") 25 | assert(gcli.load("t/testdata/rpc.proto")) 26 | 27 | local conn = assert(gcli.connect("127.0.0.1:2379")) 28 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}) 29 | if not st then 30 | ngx.say(err) 31 | return 32 | end 33 | st:close() 34 | ngx.say("ok") 35 | } 36 | --- stream_response 37 | ok 38 | 39 | 40 | 41 | === TEST 2: multi streams 42 | --- stream_server_config 43 | content_by_lua_block { 44 | local gcli = require("resty.grpc") 45 | assert(gcli.load("t/testdata/rpc.proto")) 46 | 47 | local conn = assert(gcli.connect("127.0.0.1:2379")) 48 | local function co() 49 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}) 50 | if not st then 51 | ngx.say(err) 52 | return 53 | end 54 | end 55 | local ths = {} 56 | for i = 1, 10 do 57 | local th = ngx.thread.spawn(co) 58 | table.insert(ths, th) 59 | end 60 | local ok = ngx.thread.wait(unpack(ths)) 61 | ngx.say(ok) 62 | } 63 | --- stream_response 64 | true 65 | 66 | 67 | 68 | === TEST 3: recv 69 | --- stream_server_config 70 | content_by_lua_block { 71 | local gcli = require("resty.grpc") 72 | assert(gcli.load("t/testdata/rpc.proto")) 73 | 74 | local conn = assert(gcli.connect("127.0.0.1:2379")) 75 | local write_conn = assert(gcli.connect("127.0.0.1:2379")) 76 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}, {timeout = 3000}) 77 | if not st then 78 | ngx.say(err) 79 | return 80 | end 81 | 82 | local function co() 83 | assert(write_conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'recv'})) 84 | assert(write_conn:call("etcdserverpb.KV", "DeleteRange", {key = 'k'})) 85 | end 86 | ngx.thread.spawn(co) 87 | 88 | local old = assert(st:recv()) 89 | local res = assert(st:recv()) 90 | ngx.say(res.header.revision - old.header.revision) 91 | ngx.say(res.events[1].type, " ", res.events[1].kv.value) 92 | local res = assert(st:recv()) 93 | ngx.say(res.events[1].type, " ", res.events[1].kv.key) 94 | } 95 | --- stream_response 96 | 1 97 | PUT recv 98 | DELETE k 99 | 100 | 101 | 102 | === TEST 4: multiple watcher 103 | --- stream_server_config 104 | content_by_lua_block { 105 | local gcli = require("resty.grpc") 106 | assert(gcli.load("t/testdata/rpc.proto")) 107 | 108 | local conn = assert(gcli.connect("127.0.0.1:2379")) 109 | 110 | local function co(st, i) 111 | local res = assert(st:recv()) 112 | for n = 1, #res.events do 113 | ngx.log(ngx.WARN, i, ": event type: ", res.events[n].type) 114 | end 115 | assert(res.events[1].type == "PUT") 116 | 117 | local ev 118 | if not res.events[2] then 119 | res = assert(st:recv()) 120 | ev = res.events[1] 121 | else 122 | ev = res.events[2] 123 | end 124 | ngx.log(ngx.WARN, i, ": event type: ", ev.type) 125 | assert(ev.type == "DELETE") 126 | ngx.log(ngx.WARN, i, ": event on key: ", ev.kv.key) 127 | end 128 | 129 | local ths = {} 130 | for i = 1, 5 do 131 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}, {timeout = 3000}) 132 | if not st then 133 | ngx.say(err) 134 | return 135 | end 136 | assert(st:recv()) 137 | local th = ngx.thread.spawn(co, st, i) 138 | table.insert(ths, th) 139 | end 140 | 141 | assert(conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'recv'})) 142 | assert(conn:call("etcdserverpb.KV", "DeleteRange", {key = 'k'})) 143 | 144 | local ok = ngx.thread.wait(unpack(ths)) 145 | ngx.say(ok) 146 | } 147 | --- stream_response 148 | true 149 | --- grep_error_log eval 150 | qr/event on key: \w+/ 151 | --- grep_error_log_out 152 | event on key: k 153 | event on key: k 154 | event on key: k 155 | event on key: k 156 | event on key: k 157 | 158 | 159 | 160 | === TEST 5: lifetime timeout 161 | --- stream_server_config 162 | content_by_lua_block { 163 | local gcli = require("resty.grpc") 164 | assert(gcli.load("t/testdata/rpc.proto")) 165 | 166 | local conn = assert(gcli.connect("127.0.0.1:2379")) 167 | local write_conn = assert(gcli.connect("127.0.0.1:2379")) 168 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", {create_request = {key = 'k'}}, {timeout = 100}) 169 | if not st then 170 | ngx.say(err) 171 | return 172 | end 173 | 174 | assert(st:recv()) 175 | local res, err = st:recv() 176 | ngx.say(err) 177 | } 178 | --- stream_response eval 179 | qr/(context deadline exceeded|timeout)/ 180 | 181 | -------------------------------------------------------------------------------- /t/stream/unary.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: sanity 22 | --- log_level: debug 23 | --- stream_server_config 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/testdata/rpc.proto")) 27 | 28 | local conn = assert(gcli.connect("127.0.0.1:2379")) 29 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 30 | local old = res.header.revision 31 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 32 | ngx.say(res.header.revision - old) 33 | conn:close() 34 | } 35 | --- stream_response 36 | 1 37 | --- grep_error_log eval 38 | qr/(create|close) gRPC connection/ 39 | --- grep_error_log_out 40 | create gRPC connection 41 | close gRPC connection 42 | 43 | 44 | 45 | === TEST 2: call repeatedly 46 | --- stream_server_config 47 | content_by_lua_block { 48 | local gcli = require("resty.grpc") 49 | assert(gcli.load("t/testdata/rpc.proto")) 50 | 51 | local conn = assert(gcli.connect("127.0.0.1:2379")) 52 | for i = 1, 3 do 53 | assert(conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'})) 54 | local res, err = conn:call("etcdserverpb.KV", "Range", {key = 'k', range_end = 'ka', revision = 0}) 55 | if not res then 56 | ngx.log(ngx.ERR, err) 57 | return 58 | end 59 | local kv = res.kvs[1] 60 | if kv == nil then 61 | return 62 | end 63 | ngx.say(kv.key, " ", kv.value) 64 | local res, err = conn:call("etcdserverpb.KV", "DeleteRange", {key = 'k', range_end = 'ka'}) 65 | if not res then 66 | ngx.log(ngx.ERR, err) 67 | return 68 | end 69 | ngx.say(res.deleted) 70 | end 71 | } 72 | --- stream_response 73 | k v 74 | 1 75 | k v 76 | 1 77 | k v 78 | 1 79 | 80 | 81 | 82 | === TEST 3: not yieldable 83 | --- stream_server_config 84 | return 200; 85 | log_by_lua_block { 86 | local gcli = require("resty.grpc") 87 | assert(gcli.load("t/testdata/rpc.proto")) 88 | local conn = assert(gcli.connect("127.0.0.1:2379")) 89 | local ok, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 90 | ngx.log(ngx.ERR, "failed to call: ", err) 91 | } 92 | --- error_log 93 | failed to call: API disabled in the context of 94 | 95 | 96 | 97 | === TEST 4: connect refused 98 | --- stream_server_config 99 | content_by_lua_block { 100 | local gcli = require("resty.grpc") 101 | assert(gcli.load("t/testdata/rpc.proto")) 102 | 103 | local conn = assert(gcli.connect("127.0.0.1:1979")) 104 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 105 | if not res then 106 | ngx.say(err) 107 | end 108 | } 109 | --- stream_response eval 110 | qr/connect: connection refused/ 111 | 112 | 113 | 114 | === TEST 5: service not found 115 | --- stream_server_config 116 | content_by_lua_block { 117 | local gcli = require("resty.grpc") 118 | assert(gcli.load("t/testdata/rpc.proto")) 119 | 120 | local conn = assert(gcli.connect("127.0.0.1:2379")) 121 | local res, err = conn:call("etcdserverpb.KVs", "Put", {key = 'k', value = 'v'}) 122 | if not res then 123 | ngx.say(err) 124 | end 125 | } 126 | --- stream_response 127 | service etcdserverpb.KVs not found 128 | 129 | 130 | 131 | === TEST 6: method not found 132 | --- stream_server_config 133 | content_by_lua_block { 134 | local gcli = require("resty.grpc") 135 | assert(gcli.load("t/testdata/rpc.proto")) 136 | 137 | local conn = assert(gcli.connect("127.0.0.1:2379")) 138 | local res, err = conn:call("etcdserverpb.KV", "Puts", {key = 'k', value = 'v'}) 139 | if not res then 140 | ngx.say(err) 141 | end 142 | } 143 | --- stream_response 144 | method Puts not found 145 | 146 | 147 | 148 | === TEST 7: failed to encode 149 | --- stream_server_config 150 | content_by_lua_block { 151 | local gcli = require("resty.grpc") 152 | assert(gcli.load("t/testdata/rpc.proto")) 153 | 154 | local conn = assert(gcli.connect("127.0.0.1:2379")) 155 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 1, value = 'v'}) 156 | if not res then 157 | ngx.say(err) 158 | end 159 | } 160 | --- stream_response 161 | failed to encode: bad argument #2 to '?' (string expected for field 'key', got number) 162 | 163 | 164 | 165 | === TEST 8: wrong request proto 166 | --- stream_server_config 167 | content_by_lua_block { 168 | local gcli = require("resty.grpc") 169 | assert(gcli.load("t/testdata/bad.proto")) 170 | 171 | local conn = assert(gcli.connect("127.0.0.1:2379")) 172 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 1}) 173 | if not res then 174 | ngx.say(err) 175 | end 176 | } 177 | --- stream_response eval 178 | qr/error unmarshalling request/ 179 | 180 | 181 | 182 | === TEST 9: wrong response proto 183 | --- stream_server_config 184 | content_by_lua_block { 185 | local gcli = require("resty.grpc") 186 | assert(gcli.load("t/testdata/bad.proto")) 187 | 188 | local conn = assert(gcli.connect("127.0.0.1:2379")) 189 | local res, err = conn:call("etcdserverpb.KV", "Range", {key = 'k'}) 190 | if not res then 191 | ngx.say(err) 192 | end 193 | } 194 | --- stream_response 195 | failed to decode: type mismatch for field 'key' at offset 4, bytes expected for type bytes, got varint 196 | 197 | 198 | 199 | === TEST 10: resume before content_by_lua 200 | --- stream_server_config 201 | preread_by_lua_block { 202 | local gcli = require("resty.grpc") 203 | assert(gcli.load("t/testdata/rpc.proto")) 204 | 205 | local conn = assert(gcli.connect("127.0.0.1:2379")) 206 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 207 | local old = res.header.revision 208 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 209 | ngx.say(res.header.revision - old) 210 | conn:close() 211 | } 212 | return ''; 213 | --- stream_response 214 | 1 215 | 216 | 217 | 218 | === TEST 11: not insecure 219 | --- stream_server_config 220 | content_by_lua_block { 221 | local gcli = require("resty.grpc") 222 | assert(gcli.load("t/testdata/rpc.proto")) 223 | local conn = assert(gcli.connect("127.0.0.1:2379", {insecure = false})) 224 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 225 | ngx.say(err) 226 | } 227 | --- stream_response eval 228 | qr/authentication handshake failed/ 229 | 230 | 231 | 232 | === TEST 12: default insecure 233 | --- stream_server_config 234 | content_by_lua_block { 235 | local gcli = require("resty.grpc") 236 | assert(gcli.load("t/testdata/rpc.proto")) 237 | local conn = assert(gcli.connect("127.0.0.1:2379")) 238 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 239 | ngx.say(err) 240 | } 241 | --- stream_response 242 | nil 243 | 244 | 245 | 246 | === TEST 13: with timeout 247 | --- log_level: debug 248 | --- stream_server_config 249 | content_by_lua_block { 250 | local gcli = require("resty.grpc") 251 | assert(gcli.load("t/testdata/rpc.proto")) 252 | 253 | local opt = {timeout = 1.1 * 1000} 254 | local conn = assert(gcli.connect("127.0.0.1:2379")) 255 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, opt) 256 | local old = res.header.revision 257 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}, opt) 258 | ngx.say(res.header.revision - old) 259 | conn:close() 260 | } 261 | --- stream_response 262 | 1 263 | --- grep_error_log eval 264 | qr/yield gRPC ctx:\w+, timeout:\d+/ 265 | --- grep_error_log_out eval 266 | qr/yield gRPC ctx:\w+, timeout:1100 267 | yield gRPC ctx:\w+, timeout:1100/ 268 | 269 | 270 | 271 | === TEST 14: timed out 272 | --- http_config 273 | server { 274 | listen 2376 http2; 275 | 276 | location / { 277 | access_by_lua_block { 278 | ngx.sleep(4) 279 | } 280 | grpc_pass grpc://127.0.0.1:2379; 281 | } 282 | } 283 | --- stream_server_config 284 | content_by_lua_block { 285 | local gcli = require("resty.grpc") 286 | assert(gcli.load("t/testdata/rpc.proto")) 287 | 288 | local opt = {timeout = 0.1 * 1000} 289 | local conn = assert(gcli.connect("127.0.0.1:2376")) 290 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, opt) 291 | ngx.say(err) 292 | conn:close() 293 | } 294 | --- stream_response 295 | failed to call: timeout 296 | 297 | 298 | 299 | === TEST 15: call with delay 300 | --- http_config 301 | server { 302 | listen 2376 http2; 303 | 304 | location / { 305 | access_by_lua_block { 306 | ngx.sleep(1) 307 | } 308 | grpc_pass grpc://127.0.0.1:2379; 309 | } 310 | } 311 | --- stream_server_config 312 | content_by_lua_block { 313 | local gcli = require("resty.grpc") 314 | assert(gcli.load("t/testdata/rpc.proto")) 315 | 316 | local opt = {timeout = 1.1 * 1000} 317 | local conn = assert(gcli.connect("127.0.0.1:2376")) 318 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, opt) 319 | local old = res.header.revision 320 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 321 | ngx.say(res.header.revision - old) 322 | conn:close() 323 | } 324 | --- stream_response 325 | 1 326 | 327 | 328 | 329 | === TEST 16: close before finishing 330 | --- http_config 331 | server { 332 | listen 2376 http2; 333 | 334 | location / { 335 | access_by_lua_block { 336 | ngx.sleep(0.5) 337 | } 338 | grpc_pass grpc://127.0.0.1:2379; 339 | } 340 | } 341 | --- stream_server_config 342 | content_by_lua_block { 343 | local gcli = require("resty.grpc") 344 | assert(gcli.load("t/testdata/rpc.proto")) 345 | 346 | local conn = assert(gcli.connect("127.0.0.1:2376")) 347 | local function co() 348 | conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 349 | end 350 | local th = ngx.thread.spawn(co) 351 | conn:close() 352 | ngx.say('ok') 353 | } 354 | --- stream_response 355 | ok 356 | 357 | 358 | 359 | === TEST 17: close before timeout 360 | --- http_config 361 | server { 362 | listen 2376 http2; 363 | 364 | location / { 365 | access_by_lua_block { 366 | ngx.sleep(0.5) 367 | } 368 | grpc_pass grpc://127.0.0.1:2379; 369 | } 370 | } 371 | --- stream_server_config 372 | content_by_lua_block { 373 | local gcli = require("resty.grpc") 374 | assert(gcli.load("t/testdata/rpc.proto")) 375 | 376 | local conn = assert(gcli.connect("127.0.0.1:2376")) 377 | local function co() 378 | local opt = {timeout = 100} 379 | conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, opt) 380 | end 381 | local th = ngx.thread.spawn(co) 382 | conn:close() 383 | ngx.say('ok') 384 | } 385 | --- stream_response 386 | ok 387 | 388 | 389 | 390 | === TEST 18: abort 391 | --- http_config 392 | server { 393 | listen 2376 http2; 394 | 395 | location / { 396 | access_by_lua_block { 397 | ngx.sleep(100) 398 | } 399 | grpc_pass grpc://127.0.0.1:2379; 400 | } 401 | } 402 | --- stream_server_config 403 | content_by_lua_block { 404 | local gcli = require("resty.grpc") 405 | assert(gcli.load("t/testdata/rpc.proto")) 406 | 407 | local conn = assert(gcli.connect("127.0.0.1:2376")) 408 | local opt = {timeout = 1000} 409 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, opt) 410 | ngx.log(ngx.ERR, "unreacheable") 411 | } 412 | --- timeout: 0.2 413 | --- abort 414 | --- wait: 0.7 415 | --- ignore_response 416 | 417 | 418 | 419 | === TEST 19: mix req 420 | --- stream_config 421 | init_worker_by_lua_block { 422 | local gcli = require("resty.grpc") 423 | assert(gcli.load("t/testdata/rpc.proto")) 424 | 425 | package.loaded.conn = assert(gcli.connect("127.0.0.1:2379")) 426 | } 427 | --- stream_server_config 428 | content_by_lua_block { 429 | local conn = package.loaded.conn 430 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 431 | local old = res.header.revision 432 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 433 | ngx.say(res.header.revision - old) 434 | conn:close() 435 | } 436 | --- stream_response 437 | 1 438 | 439 | 440 | 441 | === TEST 20: many threads wait for one conn 442 | --- http_config 443 | server { 444 | listen 2376 http2; 445 | 446 | location / { 447 | access_by_lua_block { 448 | ngx.sleep(0.1) 449 | } 450 | grpc_pass grpc://127.0.0.1:2379; 451 | } 452 | } 453 | --- stream_server_config 454 | content_by_lua_block { 455 | local gcli = require("resty.grpc") 456 | assert(gcli.load("t/testdata/rpc.proto")) 457 | 458 | local conn = assert(gcli.connect("127.0.0.1:2376")) 459 | assert(conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'})) 460 | local function co() 461 | local res, err = conn:call("etcdserverpb.KV", "Range", {key = 'k'}) 462 | if not res then 463 | ngx.log(ngx.ERR, err) 464 | return 465 | end 466 | local kv = res.kvs[1] 467 | if kv == nil then 468 | return 469 | end 470 | if kv.key ~= "k" or kv.value ~= "v" then 471 | ngx.log(ngx.ERR, "k: ", kv.key, ", v: ", kv.value) 472 | end 473 | end 474 | local ths = {} 475 | for i = 1, 10 do 476 | local th = ngx.thread.spawn(co) 477 | table.insert(ths, th) 478 | end 479 | local ok = ngx.thread.wait(unpack(ths)) 480 | ngx.say(ok) 481 | } 482 | --- stream_response 483 | true 484 | 485 | 486 | 487 | === TEST 21: mix error in different req 488 | --- stream_server_config 489 | content_by_lua_block { 490 | local gcli = require("resty.grpc") 491 | assert(gcli.load("t/testdata/bad.proto")) 492 | local conn1 = assert(gcli.connect("127.0.0.1:2379")) 493 | local conn2 = assert(gcli.connect("127.0.0.1:2379", {insecure = false})) 494 | 495 | local function co1() 496 | local res, err = conn1:call("etcdserverpb.KV", "Put", {key = 1}) 497 | if not res then 498 | ngx.log(ngx.WARN, err) 499 | end 500 | end 501 | local function co2() 502 | local res, err = conn2:call("etcdserverpb.KV", "Put", {key = 1}) 503 | if not res then 504 | ngx.log(ngx.WARN, err) 505 | end 506 | end 507 | local ths = {} 508 | for i = 1, 10 do 509 | local th 510 | if i % 2 == 1 then 511 | th = ngx.thread.spawn(co1) 512 | else 513 | th = ngx.thread.spawn(co2) 514 | end 515 | table.insert(ths, th) 516 | end 517 | local ok = ngx.thread.wait(unpack(ths)) 518 | ngx.say(ok) 519 | } 520 | --- grep_error_log eval 521 | qr/error: desc = "transport: authentication handshake failed: EOF",|error unmarshalling request: proto: wrong wireType = 0 for field Key,/ 522 | --- grep_error_log_out eval 523 | qr/(error: desc = "transport: authentication handshake failed: EOF",|error unmarshalling request: proto: wrong wireType = 0 for field Key,)\n{1,5}/ 524 | 525 | 526 | 527 | === TEST 22: called in init_worker_by_lua_block 528 | --- stream_config 529 | init_worker_by_lua_block { 530 | local gcli = require("resty.grpc") 531 | assert(gcli.load("t/testdata/rpc.proto")) 532 | 533 | local conn = assert(gcli.connect("127.0.0.1:2379")) 534 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 535 | local old = res.header.revision 536 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 537 | package.loaded.diff = res.header.revision - old 538 | } 539 | --- stream_server_config 540 | content_by_lua_block { 541 | ngx.say(package.loaded.diff) 542 | } 543 | --- stream_response 544 | 1 545 | 546 | 547 | 548 | === TEST 23: called in init_worker_by_lua_block, something wrong happened 549 | --- stream_config 550 | init_worker_by_lua_block { 551 | local gcli = require("resty.grpc") 552 | assert(gcli.load("t/testdata/rpc.proto")) 553 | 554 | local conn = assert(gcli.connect("127.0.0.1:2376")) 555 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 556 | package.loaded.err = err 557 | } 558 | --- stream_server_config 559 | content_by_lua_block { 560 | ngx.say(package.loaded.err) 561 | } 562 | --- stream_response eval 563 | qr/connect: connection refused/ 564 | 565 | 566 | 567 | === TEST 24: called in init_worker_by_lua_block, timeout 568 | --- http_config 569 | server { 570 | listen 2376 http2; 571 | 572 | location / { 573 | access_by_lua_block { 574 | ngx.sleep(4) 575 | } 576 | grpc_pass grpc://127.0.0.1:2379; 577 | } 578 | } 579 | --- stream_config 580 | init_worker_by_lua_block { 581 | local gcli = require("resty.grpc") 582 | assert(gcli.load("t/testdata/rpc.proto")) 583 | 584 | local conn = assert(gcli.connect("127.0.0.1:2376")) 585 | local opt = {timeout = 100} 586 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}, opt) 587 | package.loaded.err = err 588 | } 589 | --- stream_server_config 590 | content_by_lua_block { 591 | ngx.say(package.loaded.err) 592 | } 593 | --- stream_response eval 594 | qr/context deadline exceeded/ 595 | -------------------------------------------------------------------------------- /t/stress/conf/nginx.conf: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | daemon off; 16 | master_process on; 17 | worker_processes 1; 18 | error_log logs/error.log warn; 19 | worker_rlimit_nofile 20480; 20 | 21 | events { 22 | worker_connections 10240; 23 | } 24 | 25 | worker_shutdown_timeout 1; 26 | thread_pool grpc-client-nginx-module threads=1; 27 | 28 | http { 29 | access_log off; 30 | server_tokens off; 31 | more_clear_headers Server; 32 | keepalive_requests 10000; 33 | #lua_package_path "lib/?.lua;;"; 34 | 35 | init_worker_by_lua_block { 36 | local gcli = require("resty.grpc") 37 | assert(gcli.load("t/testdata/rpc.proto")) 38 | assert(gcli.load("t/backend/proto/test.proto")) 39 | local conn = assert(gcli.connect("127.0.0.1:2379")) 40 | local timeout_conn = assert(gcli.connect("127.0.0.1:2376")) 41 | local echo_conn = assert(gcli.connect("127.0.0.1:50051")) 42 | 43 | package.loaded.conn = conn 44 | package.loaded.timeout_conn = timeout_conn 45 | package.loaded.echo_conn = echo_conn 46 | } 47 | 48 | server { 49 | listen 2376 http2; 50 | 51 | location / { 52 | access_by_lua_block { 53 | ngx.sleep(1) 54 | } 55 | grpc_pass grpc://127.0.0.1:2379; 56 | } 57 | } 58 | 59 | server { 60 | listen 6666 reuseport; 61 | location /etcd_get { 62 | access_by_lua_block { 63 | local conn = package.loaded.conn 64 | local res, err = conn:call("etcdserverpb.KV", "Range", {key = 'k'}) 65 | if not res then 66 | ngx.say(err) 67 | return 68 | end 69 | local kv = res.kvs[1] 70 | if kv == nil then 71 | ngx.say(kv) 72 | return 73 | end 74 | ngx.say(kv.key, " ", kv.value) 75 | } 76 | } 77 | location /etcd_put { 78 | access_by_lua_block { 79 | local gcli = require("resty.grpc") 80 | local conn = assert(gcli.connect("127.0.0.1:2379")) 81 | local res, err = conn:call("etcdserverpb.KV", "Put", { 82 | key = ngx.var.arg_key, 83 | value = ngx.var.arg_value, 84 | }) 85 | if not res then 86 | ngx.status = 503 87 | ngx.say(err) 88 | return 89 | end 90 | ngx.say("ok") 91 | conn:close() 92 | } 93 | } 94 | location /etcd_delete { 95 | access_by_lua_block { 96 | local gcli = require("resty.grpc") 97 | local conn = assert(gcli.connect("127.0.0.1:2379")) 98 | local res, err = conn:call("etcdserverpb.KV", "DeleteRange", { 99 | key = ngx.var.arg_key, 100 | }) 101 | if not res then 102 | ngx.status = 503 103 | ngx.say(err) 104 | return 105 | end 106 | ngx.say("ok") 107 | conn:close() 108 | } 109 | } 110 | location /etcd_watch { 111 | access_by_lua_block { 112 | local cjson = require("cjson") 113 | local conn = package.loaded.conn 114 | local st, err = conn:new_server_stream("etcdserverpb.Watch", "Watch", 115 | {create_request = 116 | {key = ngx.var.arg_key}}, 117 | {timeout = 30000}) 118 | if not st then 119 | ngx.status = 503 120 | ngx.say(err) 121 | return 122 | end 123 | 124 | for i = 1, (ngx.var.arg_count or 10) do 125 | local res, err = st:recv() 126 | ngx.log(ngx.WARN, "received ", cjson.encode(res)) 127 | if not res then 128 | ngx.status = 503 129 | ngx.say(err) 130 | break 131 | end 132 | 133 | if i == 1 then 134 | -- tell the client we now start to watch 135 | ngx.send_headers() 136 | ngx.flush(true) 137 | end 138 | 139 | for _, e in ipairs(res.events) do 140 | ngx.say(e.type, " ", e.kv.key, e.kv.value ~= "" and (" " .. e.kv.value) or "") 141 | ngx.flush(true) 142 | end 143 | end 144 | 145 | st:close() 146 | } 147 | } 148 | location /timeout { 149 | access_by_lua_block { 150 | local conn = package.loaded.conn 151 | local res, err = conn:call("etcdserverpb.KV", "Range", {key = 'k'}, {timeout = 0.05}) 152 | if not res then 153 | ngx.say(err) 154 | else 155 | ngx.say("expected to fail") 156 | end 157 | } 158 | } 159 | location /echo { 160 | access_by_lua_block { 161 | local conn = package.loaded.echo_conn 162 | local st, err = conn:new_bidirectional_stream("test.BidirectionalStream", "Echo", 163 | {data = ngx.var.arg_data or "a"}) 164 | if not st then 165 | ngx.say(err) 166 | return 167 | end 168 | 169 | local data, err = st:recv() 170 | if not data then 171 | ngx.say(err) 172 | return 173 | end 174 | 175 | ngx.say(data.data) 176 | } 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /t/stress/test.py: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | import http.client 16 | import os 17 | import random 18 | import shlex 19 | import shutil 20 | import subprocess 21 | import threading 22 | import time 23 | import unittest 24 | from contextlib import contextmanager 25 | 26 | 27 | CALL_PER_THREAD = 50 28 | REQ_PER_CALL = 50 29 | THREADS_NUM = 4 30 | 31 | 32 | class PropagatingThread(threading.Thread): 33 | def run(self): 34 | self.exc = None 35 | try: 36 | self.ret = self._target(*self._args, **self._kwargs) 37 | except BaseException as e: 38 | self.exc = e 39 | 40 | def join(self, timeout=None): 41 | super(PropagatingThread, self).join(timeout) 42 | if self.exc: 43 | raise self.exc 44 | return self.ret 45 | 46 | 47 | @contextmanager 48 | def http_conn(*args, **kwds): 49 | conn = http.client.HTTPConnection(*args, **kwds) 50 | try: 51 | yield conn 52 | finally: 53 | conn.close() 54 | 55 | def etcd_get(self, conn): 56 | conn.request("GET", "/etcd_get") 57 | response = conn.getresponse() 58 | self.assertEqual(response.status, 200) 59 | res = response.read().rstrip().decode() 60 | self.assertEqual(res, "k v") 61 | # give a bit randomness 62 | time.sleep(random.uniform(0, 0.01)) 63 | 64 | def timeout(self, conn): 65 | conn.request("GET", "/timeout") 66 | response = conn.getresponse() 67 | self.assertEqual(response.status, 200) 68 | res = response.read().rstrip().decode() 69 | self.assertEqual(res, "failed to call: timeout") 70 | 71 | def echo(self, conn): 72 | conn.request("GET", "/echo?data=k") 73 | response = conn.getresponse() 74 | self.assertEqual(response.status, 200) 75 | res = response.read().rstrip().decode() 76 | self.assertEqual(res, "k") 77 | # give a bit randomness 78 | time.sleep(random.uniform(0, 0.01)) 79 | 80 | 81 | def run_in_thread(self, f): 82 | def _f(): 83 | for i in range(CALL_PER_THREAD): 84 | with http_conn("127.0.0.1", port=6666) as conn: 85 | for i in range(REQ_PER_CALL): 86 | f(self, conn) 87 | return _f 88 | 89 | def bombard(self, f): 90 | th = [PropagatingThread(target=run_in_thread(self, f)) for i in range(THREADS_NUM)] 91 | for t in th: 92 | t.start() 93 | for t in th: 94 | t.join() 95 | 96 | 97 | class StressTest(unittest.TestCase): 98 | @classmethod 99 | def setUpClass(cls): 100 | subprocess.check_call(shlex.split("etcdctl put k v")) 101 | log_dir = "t/stress/logs" 102 | if os.path.exists(log_dir): 103 | shutil.rmtree(log_dir) 104 | os.mkdir(log_dir) 105 | pwd = os.getcwd() 106 | # avoid using builtin Nginx in the CI env 107 | cls.nginx = subprocess.Popen(shlex.split(f"/usr/local/openresty/nginx/sbin/nginx -p {pwd}/t/stress -c conf/nginx.conf")) 108 | time.sleep(1) 109 | 110 | @classmethod 111 | def tearDownClass(cls): 112 | nginx = cls.nginx 113 | nginx.terminate() 114 | nginx.wait() 115 | 116 | def test_etcd_get(self): 117 | bombard(self, etcd_get) 118 | 119 | def test_timeout(self): 120 | bombard(self, timeout) 121 | 122 | def test_server_stream(self): 123 | self.count = 20 124 | self.turn = 100 125 | 126 | def watch(self, i): 127 | def _f(): 128 | with http_conn("127.0.0.1", port=6666) as conn: 129 | conn.request("GET", f"/etcd_watch?key={i}&count={self.turn+1}") 130 | resp = conn.getresponse() 131 | self.barrier.wait() 132 | if resp.status != 200: 133 | res = resp.read().rstrip().decode() 134 | print(res) 135 | self.assertEqual(resp.status, 200) 136 | res = resp.read().rstrip().decode() 137 | exp = [] 138 | for j in range(self.turn): 139 | if j % 2 == 0: 140 | v = j * 10 + i 141 | exp.append(f"PUT {i} {v}") 142 | else: 143 | exp.append(f"DELETE {i}") 144 | self.assertEqual("\n".join(exp), res) 145 | return _f 146 | 147 | def write(self): 148 | def _f(): 149 | self.barrier.wait() 150 | with http_conn("127.0.0.1", port=6666) as conn: 151 | for i in range(self.turn): 152 | for j in range(self.count): 153 | if i % 2 == 0: 154 | conn.request("GET", f"/etcd_put?key={j}&value={i*10+j}") 155 | else: 156 | conn.request("GET", f"/etcd_delete?key={j}") 157 | resp = conn.getresponse() 158 | self.assertEqual(resp.status, 200) 159 | resp.read() 160 | # give a bit randomness 161 | time.sleep(random.uniform(0, 0.01)) 162 | return _f 163 | 164 | self.barrier = threading.Barrier(self.count + 1, timeout = 30) 165 | th = [PropagatingThread(target=watch(self, i)) for i in range(self.count)] 166 | for t in th: 167 | t.start() 168 | w_th = PropagatingThread(target=write(self)) 169 | w_th.start() 170 | w_th.join() 171 | for t in th: 172 | t.join() 173 | 174 | def test_bidirectional_stream(self): 175 | bombard(self, echo) 176 | 177 | 178 | if __name__ == '__main__': 179 | unittest.main() 180 | -------------------------------------------------------------------------------- /t/testdata/bad.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package etcdserverpb; 3 | 4 | service KV { 5 | rpc Put(WrongRequest) returns (RangeResponse) {} 6 | rpc Range(RangeRequest) returns (WrongResponse) {} 7 | } 8 | 9 | message ResponseHeader { 10 | // cluster_id is the ID of the cluster which sent the response. 11 | uint64 cluster_id = 1; 12 | // member_id is the ID of the member which sent the response. 13 | uint64 member_id = 2; 14 | // revision is the key-value store revision when the request was applied. 15 | // For watch progress responses, the header.revision indicates progress. All future events 16 | // recieved in this stream are guaranteed to have a higher revision number than the 17 | // header.revision number. 18 | int64 revision = 3; 19 | // raft_term is the raft term when the request was applied. 20 | uint64 raft_term = 4; 21 | } 22 | 23 | message RangeRequest { 24 | bytes key = 1; 25 | } 26 | 27 | message WrongRequest { 28 | int32 key = 1; 29 | } 30 | 31 | message KeyValue { 32 | bytes key = 1; 33 | bytes value = 2; 34 | } 35 | 36 | message RangeResponse { 37 | repeated KeyValue kvs = 2; 38 | } 39 | 40 | message WrongResponse { 41 | KeyValue kvs = 1; 42 | } 43 | -------------------------------------------------------------------------------- /t/testdata/rpc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package etcdserverpb; 3 | 4 | service KV { 5 | // Range gets the keys in the range from the key-value store. 6 | rpc Range(RangeRequest) returns (RangeResponse) {} 7 | // Put puts the given key into the key-value store. 8 | 9 | // A put request increments the revision of the key-value store 10 | // and generates one event in the event history. 11 | rpc Put(PutRequest) returns (PutResponse) {} 12 | 13 | // DeleteRange deletes the given range from the key-value store. 14 | // A delete request increments the revision of the key-value store 15 | // and generates a delete event in the event history for every deleted key. 16 | rpc DeleteRange(DeleteRangeRequest) returns (DeleteRangeResponse) {} 17 | } 18 | 19 | message ResponseHeader { 20 | // cluster_id is the ID of the cluster which sent the response. 21 | uint64 cluster_id = 1; 22 | // member_id is the ID of the member which sent the response. 23 | uint64 member_id = 2; 24 | // revision is the key-value store revision when the request was applied. 25 | // For watch progress responses, the header.revision indicates progress. All future events 26 | // recieved in this stream are guaranteed to have a higher revision number than the 27 | // header.revision number. 28 | int64 revision = 3; 29 | // raft_term is the raft term when the request was applied. 30 | uint64 raft_term = 4; 31 | } 32 | 33 | message RangeRequest { 34 | enum SortOrder { 35 | NONE = 0; // default, no sorting 36 | ASCEND = 1; // lowest target value first 37 | DESCEND = 2; // highest target value first 38 | } 39 | enum SortTarget { 40 | KEY = 0; 41 | VERSION = 1; 42 | CREATE = 2; 43 | MOD = 3; 44 | VALUE = 4; 45 | } 46 | 47 | // key is the first key for the range. If range_end is not given, the request only looks up key. 48 | bytes key = 1; 49 | // range_end is the upper bound on the requested range [key, range_end). 50 | // If range_end is '\0', the range is all keys >= key. 51 | // If range_end is key plus one (e.g., "aa"+1 == "ab", "a\xff"+1 == "b"), 52 | // then the range request gets all keys prefixed with key. 53 | // If both key and range_end are '\0', then the range request returns all keys. 54 | bytes range_end = 2; 55 | // limit is a limit on the number of keys returned for the request. When limit is set to 0, 56 | // it is treated as no limit. 57 | int64 limit = 3; 58 | // revision is the point-in-time of the key-value store to use for the range. 59 | // If revision is less or equal to zero, the range is over the newest key-value store. 60 | // If the revision has been compacted, ErrCompacted is returned as a response. 61 | int64 revision = 4; 62 | 63 | // sort_order is the order for returned sorted results. 64 | SortOrder sort_order = 5; 65 | 66 | // sort_target is the key-value field to use for sorting. 67 | SortTarget sort_target = 6; 68 | 69 | // serializable sets the range request to use serializable member-local reads. 70 | // Range requests are linearizable by default; linearizable requests have higher 71 | // latency and lower throughput than serializable requests but reflect the current 72 | // consensus of the cluster. For better performance, in exchange for possible stale reads, 73 | // a serializable range request is served locally without needing to reach consensus 74 | // with other nodes in the cluster. 75 | bool serializable = 7; 76 | 77 | // keys_only when set returns only the keys and not the values. 78 | bool keys_only = 8; 79 | 80 | // count_only when set returns only the count of the keys in the range. 81 | bool count_only = 9; 82 | 83 | // min_mod_revision is the lower bound for returned key mod revisions; all keys with 84 | // lesser mod revisions will be filtered away. 85 | int64 min_mod_revision = 10; 86 | 87 | // max_mod_revision is the upper bound for returned key mod revisions; all keys with 88 | // greater mod revisions will be filtered away. 89 | int64 max_mod_revision = 11; 90 | 91 | // min_create_revision is the lower bound for returned key create revisions; all keys with 92 | // lesser create revisions will be filtered away. 93 | int64 min_create_revision = 12; 94 | 95 | // max_create_revision is the upper bound for returned key create revisions; all keys with 96 | // greater create revisions will be filtered away. 97 | int64 max_create_revision = 13; 98 | } 99 | 100 | message KeyValue { 101 | // key is the key in bytes. An empty key is not allowed. 102 | bytes key = 1; 103 | // create_revision is the revision of last creation on this key. 104 | int64 create_revision = 2; 105 | // mod_revision is the revision of last modification on this key. 106 | int64 mod_revision = 3; 107 | // version is the version of the key. A deletion resets 108 | // the version to zero and any modification of the key 109 | // increases its version. 110 | int64 version = 4; 111 | // value is the value held by the key, in bytes. 112 | bytes value = 5; 113 | // lease is the ID of the lease that attached to key. 114 | // When the attached lease expires, the key will be deleted. 115 | // If lease is 0, then no lease is attached to the key. 116 | int64 lease = 6; 117 | } 118 | 119 | message RangeResponse { 120 | ResponseHeader header = 1; 121 | // kvs is the list of key-value pairs matched by the range request. 122 | // kvs is empty when count is requested. 123 | repeated KeyValue kvs = 2; 124 | // more indicates if there are more keys to return in the requested range. 125 | bool more = 3; 126 | // count is set to the number of keys within the range when requested. 127 | int64 count = 4; 128 | } 129 | 130 | message PutRequest { 131 | // key is the key, in bytes, to put into the key-value store. 132 | bytes key = 1; 133 | // value is the value, in bytes, to associate with the key in the key-value store. 134 | bytes value = 2; 135 | // lease is the lease ID to associate with the key in the key-value store. A lease 136 | // value of 0 indicates no lease. 137 | int64 lease = 3; 138 | 139 | // If prev_kv is set, etcd gets the previous key-value pair before changing it. 140 | // The previous key-value pair will be returned in the put response. 141 | bool prev_kv = 4; 142 | 143 | // If ignore_value is set, etcd updates the key using its current value. 144 | // Returns an error if the key does not exist. 145 | bool ignore_value = 5; 146 | 147 | // If ignore_lease is set, etcd updates the key using its current lease. 148 | // Returns an error if the key does not exist. 149 | bool ignore_lease = 6; 150 | } 151 | 152 | message PutResponse { 153 | ResponseHeader header = 1; 154 | // if prev_kv is set in the request, the previous key-value pair will be returned. 155 | KeyValue prev_kv = 2; 156 | } 157 | 158 | message DeleteRangeRequest { 159 | // key is the first key to delete in the range. 160 | bytes key = 1; 161 | // range_end is the key following the last key to delete for the range [key, range_end). 162 | // If range_end is not given, the range is defined to contain only the key argument. 163 | // If range_end is one bit larger than the given key, then the range is all the keys 164 | // with the prefix (the given key). 165 | // If range_end is '\0', the range is all keys greater than or equal to the key argument. 166 | bytes range_end = 2; 167 | 168 | // If prev_kv is set, etcd gets the previous key-value pairs before deleting it. 169 | // The previous key-value pairs will be returned in the delete response. 170 | bool prev_kv = 3; 171 | } 172 | 173 | message DeleteRangeResponse { 174 | ResponseHeader header = 1; 175 | // deleted is the number of keys deleted by the delete range request. 176 | int64 deleted = 2; 177 | // if prev_kv is set in the request, the previous key-value pairs will be returned. 178 | repeated KeyValue prev_kvs = 3; 179 | } 180 | 181 | message Event { 182 | enum EventType { 183 | PUT = 0; 184 | DELETE = 1; 185 | } 186 | // type is the kind of event. If type is a PUT, it indicates 187 | // new data has been stored to the key. If type is a DELETE, 188 | // it indicates the key was deleted. 189 | EventType type = 1; 190 | // kv holds the KeyValue for the event. 191 | // A PUT event contains current kv pair. 192 | // A PUT event with kv.Version=1 indicates the creation of a key. 193 | // A DELETE/EXPIRE event contains the deleted key with 194 | // its modification revision set to the revision of deletion. 195 | KeyValue kv = 2; 196 | 197 | // prev_kv holds the key-value pair before the event happens. 198 | KeyValue prev_kv = 3; 199 | } 200 | 201 | message WatchRequest { 202 | // request_union is a request to either create a new watcher or cancel an existing watcher. 203 | oneof request_union { 204 | WatchCreateRequest create_request = 1; 205 | WatchCancelRequest cancel_request = 2; 206 | WatchProgressRequest progress_request = 3; 207 | } 208 | } 209 | 210 | message WatchCreateRequest { 211 | // key is the key to register for watching. 212 | bytes key = 1; 213 | 214 | // range_end is the end of the range [key, range_end) to watch. If range_end is not given, 215 | // only the key argument is watched. If range_end is equal to '\0', all keys greater than 216 | // or equal to the key argument are watched. 217 | // If the range_end is one bit larger than the given key, 218 | // then all keys with the prefix (the given key) will be watched. 219 | bytes range_end = 2; 220 | 221 | // start_revision is an optional revision to watch from (inclusive). No start_revision is "now". 222 | int64 start_revision = 3; 223 | } 224 | 225 | message WatchCancelRequest { 226 | // watch_id is the watcher id to cancel so that no more events are transmitted. 227 | int64 watch_id = 1; 228 | } 229 | 230 | // Requests the a watch stream progress status be sent in the watch response stream as soon as 231 | // possible. 232 | message WatchProgressRequest { 233 | } 234 | 235 | message WatchResponse { 236 | ResponseHeader header = 1; 237 | // watch_id is the ID of the watcher that corresponds to the response. 238 | int64 watch_id = 2; 239 | 240 | // created is set to true if the response is for a create watch request. 241 | // The client should record the watch_id and expect to receive events for 242 | // the created watcher from the same stream. 243 | // All events sent to the created watcher will attach with the same watch_id. 244 | bool created = 3; 245 | 246 | // canceled is set to true if the response is for a cancel watch request. 247 | // No further events will be sent to the canceled watcher. 248 | bool canceled = 4; 249 | 250 | // compact_revision is set to the minimum index if a watcher tries to watch 251 | // at a compacted index. 252 | // 253 | // This happens when creating a watcher at a compacted revision or the watcher cannot 254 | // catch up with the progress of the key-value store. 255 | // 256 | // The client should treat the watcher as canceled and should not try to create any 257 | // watcher with the same start_revision again. 258 | int64 compact_revision = 5; 259 | 260 | // cancel_reason indicates the reason for canceling the watcher. 261 | string cancel_reason = 6; 262 | 263 | // framgment is true if large watch response was split over multiple responses. 264 | bool fragment = 7; 265 | 266 | repeated Event events = 11; 267 | } 268 | 269 | service Watch { 270 | // Watch watches for events happening or that have happened. Both input and output 271 | // are streams; the input stream is for creating and canceling watchers and the output 272 | // stream sends events. One watch RPC can watch on multiple key ranges, streaming events 273 | // for several watches at once. The entire event history can be watched starting from the 274 | // last compaction revision. 275 | rpc Watch(stream WatchRequest) returns (stream WatchResponse) { 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /t/tls.t: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Shenzhen ZhiLiu Technology Co., Ltd. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | use t::GRPC_CLI 'no_plan'; 16 | 17 | run_tests(); 18 | 19 | __DATA__ 20 | 21 | === TEST 1: call tls service 22 | --- config 23 | location /t { 24 | content_by_lua_block { 25 | local gcli = require("resty.grpc") 26 | assert(gcli.load("t/testdata/rpc.proto")) 27 | 28 | local conn = assert(gcli.connect("127.0.0.1:12379", {insecure = false})) 29 | local res, err = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 30 | ngx.say(err) 31 | conn:close() 32 | } 33 | } 34 | --- response_body eval 35 | qr/certificate has expired or is not yet valid/ 36 | 37 | 38 | 39 | === TEST 2: call tls service, without verify 40 | --- config 41 | location /t { 42 | content_by_lua_block { 43 | local gcli = require("resty.grpc") 44 | assert(gcli.load("t/testdata/rpc.proto")) 45 | 46 | local conn = assert(gcli.connect("127.0.0.1:12379", {insecure = false, tls_verify = false})) 47 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'v'}) 48 | local old = res.header.revision 49 | local res = conn:call("etcdserverpb.KV", "Put", {key = 'k', value = 'c'}) 50 | ngx.say(res.header.revision - old) 51 | conn:close() 52 | } 53 | } 54 | --- response_body 55 | 1 56 | --------------------------------------------------------------------------------