You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/06/20 06:13:53 UTC

[apisix] branch release/2.13 updated (9b0cc7a3b -> 24eea265e)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a change to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git


    from 9b0cc7a3b fix: hide 5xx error message from client (#6982)
     new 670213149 chore: validate etcd conf strictly (#7245)
     new 375eb8a15 fix(proxy-cache): bypass when method mismatch cache_method (#7111)
     new be890c389 fix: reduce memory usage when abnormal weights are given in chash (#7103)
     new 82c233e23 fix(proxy-cache): allow nil ctx vars in cache key (#7168)
     new 7b06f1406 refactor: merge grpc_server_example (#6653)
     new 314d13cff fix: grpc-transcode request support object array  (#7231)
     new 24eea265e ci: pass

The 7 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .editorconfig                                      |   5 +
 .github/workflows/chaos.yml                        |   3 +-
 .gitmodules                                        |   3 -
 .licenserc.yaml                                    |   1 +
 apisix/balancer/chash.lua                          |  16 +
 apisix/cli/schema.lua                              |  14 +-
 apisix/core.lua                                    |   1 +
 apisix/core/dns/client.lua                         |  10 +-
 apisix/{utils/router.lua => core/math.lua}         |  25 +-
 apisix/plugins/grpc-transcode/util.lua             |  17 +
 apisix/plugins/proxy-cache/disk_handler.lua        |   5 +
 apisix/plugins/proxy-cache/util.lua                |   2 +-
 ci/centos7-ci.sh                                   |   6 -
 ci/linux_openresty_common_runner.sh                |   4 -
 docs/zh/latest/plugins/proxy-cache.md              |   2 +-
 t/chaos/utils/Dockerfile                           |  75 ++
 t/cli/test_validate_config.sh                      |  27 +
 t/grpc_server_example                              |   1 -
 t/grpc_server_example/go.mod                       |   9 +
 t/grpc_server_example/go.sum                       |  60 ++
 t/grpc_server_example/main.go                      | 279 ++++++++
 t/grpc_server_example/proto.pb                     | Bin 0 -> 298 bytes
 t/grpc_server_example/proto/helloworld.pb.go       | 764 +++++++++++++++++++++
 t/grpc_server_example/proto/helloworld.proto       |  85 +++
 t/grpc_server_example/proto/helloworld_grpc.pb.go  | 423 ++++++++++++
 t/grpc_server_example/proto/import.pb.go           | 220 ++++++
 .../proto/import.proto}                            |  19 +-
 t/grpc_server_example/proto/src.pb.go              | 179 +++++
 .../proto/src.proto}                               |  20 +-
 t/grpc_server_example/proto/src_grpc.pb.go         | 105 +++
 t/node/chash-balance.t                             | 124 ++++
 t/plugin/grpc-transcode3.t                         | 124 ++++
 t/plugin/proxy-cache/disk.t                        |  65 +-
 33 files changed, 2624 insertions(+), 69 deletions(-)
 copy apisix/{utils/router.lua => core/math.lua} (71%)
 create mode 100644 t/chaos/utils/Dockerfile
 delete mode 160000 t/grpc_server_example
 create mode 100644 t/grpc_server_example/go.mod
 create mode 100644 t/grpc_server_example/go.sum
 create mode 100644 t/grpc_server_example/main.go
 create mode 100644 t/grpc_server_example/proto.pb
 create mode 100644 t/grpc_server_example/proto/helloworld.pb.go
 create mode 100644 t/grpc_server_example/proto/helloworld.proto
 create mode 100644 t/grpc_server_example/proto/helloworld_grpc.pb.go
 create mode 100644 t/grpc_server_example/proto/import.pb.go
 copy t/{plugin/grpc-web/a6/route.proto => grpc_server_example/proto/import.proto} (77%)
 create mode 100644 t/grpc_server_example/proto/src.pb.go
 copy t/{plugin/grpc-web/a6/route.proto => grpc_server_example/proto/src.proto} (77%)
 create mode 100644 t/grpc_server_example/proto/src_grpc.pb.go
 create mode 100644 t/plugin/grpc-transcode3.t


[apisix] 03/07: fix: reduce memory usage when abnormal weights are given in chash (#7103)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit be890c38970882290855a49122e7ee4e796e59bc
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Wed May 25 10:10:24 2022 +0800

    fix: reduce memory usage when abnormal weights are given in chash (#7103)
    
    * fix: reduce memory usage when abnormal weights are given in chash
    
    Unlike the planned, I choose to do the gcd in APISIX because in this way
    we don't need to take care of dynamically resize.
    Signed-off-by: spacewander <sp...@gmail.com>
    
    * Update apisix/core/math.lua
    
    Co-authored-by: tzssangglass <tz...@gmail.com>
    
    Co-authored-by: tzssangglass <tz...@gmail.com>
    Signed-off-by: spacewander <sp...@gmail.com>
---
 apisix/balancer/chash.lua  |  16 ++++++
 apisix/core/dns/client.lua |  10 +---
 apisix/core/math.lua       |  41 +++++++++++++++
 t/node/chash-balance.t     | 124 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 182 insertions(+), 9 deletions(-)

diff --git a/apisix/balancer/chash.lua b/apisix/balancer/chash.lua
index b191407d2..f0e971a36 100644
--- a/apisix/balancer/chash.lua
+++ b/apisix/balancer/chash.lua
@@ -71,11 +71,27 @@ function _M.new(up_nodes, upstream)
 
     local nodes_count = 0
     local safe_limit = 0
+    local gcd = 0
     local servers, nodes = {}, {}
+
+    for serv, weight in pairs(up_nodes) do
+        if gcd == 0 then
+            gcd = weight
+        else
+            gcd = core.math.gcd(gcd, weight)
+        end
+    end
+
+    if gcd == 0 then
+        -- all nodes' weight are 0
+        gcd = 1
+    end
+
     for serv, weight in pairs(up_nodes) do
         local id = str_gsub(serv, ":", str_null)
 
         nodes_count = nodes_count + 1
+        weight = weight / gcd
         safe_limit = safe_limit + weight
         servers[id] = serv
         nodes[id] = weight
diff --git a/apisix/core/dns/client.lua b/apisix/core/dns/client.lua
index b9dcfb9c8..1bf2aca4d 100644
--- a/apisix/core/dns/client.lua
+++ b/apisix/core/dns/client.lua
@@ -24,6 +24,7 @@ local config_local = require("apisix.core.config_local")
 local log = require("apisix.core.log")
 local json = require("apisix.core.json")
 local table = require("apisix.core.table")
+local gcd = require("apisix.core.math").gcd
 local insert_tab = table.insert
 local math_random = math.random
 local package_loaded = package.loaded
@@ -38,15 +39,6 @@ local _M = {
 }
 
 
-local function gcd(a, b)
-    if b == 0 then
-        return a
-    end
-
-    return gcd(b, a % b)
-end
-
-
 local function resolve_srv(client, answers)
     if #answers == 0 then
         return nil, "empty SRV record"
diff --git a/apisix/core/math.lua b/apisix/core/math.lua
new file mode 100644
index 000000000..1514cf7f0
--- /dev/null
+++ b/apisix/core/math.lua
@@ -0,0 +1,41 @@
+--
+-- Licensed to the Apache Software Foundation (ASF) under one or more
+-- contributor license agreements.  See the NOTICE file distributed with
+-- this work for additional information regarding copyright ownership.
+-- The ASF licenses this file to You under the Apache License, Version 2.0
+-- (the "License"); you may not use this file except in compliance with
+-- the License.  You may obtain a copy of the License at
+--
+--     http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+--- Common library about math
+--
+-- @module core.math
+local _M = {}
+
+
+---
+-- Calculate the greatest common divisor (GCD) of two numbers
+--
+-- @function core.math.gcd
+-- @tparam number a
+-- @tparam number b
+-- @treturn number the GCD of a and b
+local function gcd(a, b)
+    if b == 0 then
+        return a
+    end
+
+    return gcd(b, a % b)
+end
+_M.gcd = gcd
+
+
+return _M
diff --git a/t/node/chash-balance.t b/t/node/chash-balance.t
index dfde2aaff..0c846e27e 100644
--- a/t/node/chash-balance.t
+++ b/t/node/chash-balance.t
@@ -556,3 +556,127 @@ passed
 GET /t
 --- response_body
 200
+
+
+
+=== TEST 15: set routes with very big weights
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "uri": "/server_port",
+                    "upstream": {
+                        "key": "arg_device_id",
+                        "type": "chash",
+                        "nodes": {
+                            "127.0.0.1:1980": 1000000000,
+                            "127.0.0.1:1981": 2000000000,
+                            "127.0.0.1:1982": 1000000000
+                        }
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 16: hit
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local uri = "http://127.0.0.1:" .. ngx.var.server_port
+                        .. "/server_port?device_id=1"
+
+            local httpc = http.new()
+            local res, err = httpc:request_uri(uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+                return
+            end
+
+            -- a `size too large` error will be thrown if we don't reduce the weight
+            ngx.say(res.status)
+        }
+    }
+--- request
+GET /t
+--- response_body
+200
+
+
+
+=== TEST 17: set routes with very big weights, some nodes have zero weight
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "uri": "/server_port",
+                    "upstream": {
+                        "key": "arg_device_id",
+                        "type": "chash",
+                        "nodes": {
+                            "127.0.0.1:1980": 1000000000,
+                            "127.0.0.1:1981": 0,
+                            "127.0.0.1:1982": 4000000000
+                        }
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 18: hit
+--- config
+    location /t {
+        content_by_lua_block {
+            local http = require "resty.http"
+            local uri = "http://127.0.0.1:" .. ngx.var.server_port
+                        .. "/server_port?device_id=1"
+
+            local httpc = http.new()
+            local res, err = httpc:request_uri(uri, {method = "GET"})
+            if not res then
+                ngx.say(err)
+                return
+            end
+
+            -- a `size too large` error will be thrown if we don't reduce the weight
+            ngx.say(res.status)
+        }
+    }
+--- request
+GET /t
+--- response_body
+200


[apisix] 05/07: refactor: merge grpc_server_example (#6653)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit 7b06f140631dc2122e9201510005aa992527aa52
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Mon Mar 21 17:27:49 2022 +0800

    refactor: merge grpc_server_example (#6653)
---
 .editorconfig                                     |   5 +
 .gitmodules                                       |   3 -
 .licenserc.yaml                                   |   1 +
 ci/centos7-ci.sh                                  |   6 -
 ci/linux_openresty_common_runner.sh               |   4 -
 t/grpc_server_example                             |   1 -
 t/grpc_server_example/go.mod                      |   9 +
 t/grpc_server_example/go.sum                      |  60 +++
 t/grpc_server_example/main.go                     | 254 ++++++++++
 t/grpc_server_example/proto.pb                    | Bin 0 -> 298 bytes
 t/grpc_server_example/proto/helloworld.pb.go      | 573 ++++++++++++++++++++++
 t/grpc_server_example/proto/helloworld.proto      |  70 +++
 t/grpc_server_example/proto/helloworld_grpc.pb.go | 383 +++++++++++++++
 t/grpc_server_example/proto/import.pb.go          | 203 ++++++++
 t/grpc_server_example/proto/import.proto          |  29 ++
 t/grpc_server_example/proto/src.pb.go             | 162 ++++++
 t/grpc_server_example/proto/src.proto             |  32 ++
 t/grpc_server_example/proto/src_grpc.pb.go        | 101 ++++
 18 files changed, 1882 insertions(+), 14 deletions(-)

diff --git a/.editorconfig b/.editorconfig
index 0096ce7b2..79eed820e 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -45,3 +45,8 @@ indent_style = tab
 
 [t/coredns/db.test.local]
 indent_style = unset
+
+[*.pb]
+indent_style = unset
+insert_final_newline = unset
+trim_trailing_whitespace = unset
diff --git a/.gitmodules b/.gitmodules
index 6ad3766a0..beb354b89 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
 [submodule "t/toolkit"]
 	path = t/toolkit
 	url = https://github.com/api7/test-toolkit.git
-[submodule "t/grpc_server_example"]
-	path = t/grpc_server_example
-	url = https://github.com/api7/grpc_server_example
diff --git a/.licenserc.yaml b/.licenserc.yaml
index d2ce2e61c..58acb3d71 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -27,6 +27,7 @@ header:
     - '**/*.key'
     - '**/*.crt'
     - '**/*.pem'
+    - '**/*.pb.go'
     - '.github/'
     - 'conf/mime.types'
     # Exclude CI env_file
diff --git a/ci/centos7-ci.sh b/ci/centos7-ci.sh
index f182809d6..1cff4a5f8 100755
--- a/ci/centos7-ci.sh
+++ b/ci/centos7-ci.sh
@@ -56,12 +56,6 @@ install_dependencies() {
     # install and start grpc_server_example
     cd t/grpc_server_example
 
-    # unless pulled recursively, the submodule directory will remain empty. So it's better to initialize and set the submodule to the particular commit.
-    if [ ! "$(ls -A . )" ]; then
-        git submodule init
-        git submodule update
-    fi
-
     CGO_ENABLED=0 go build
     ./grpc_server_example \
         -grpc-address :50051 -grpcs-address :50052 -grpcs-mtls-address :50053 \
diff --git a/ci/linux_openresty_common_runner.sh b/ci/linux_openresty_common_runner.sh
index 4a76c385a..16579d527 100755
--- a/ci/linux_openresty_common_runner.sh
+++ b/ci/linux_openresty_common_runner.sh
@@ -45,10 +45,6 @@ do_install() {
     # install and start grpc_server_example
     cd t/grpc_server_example
 
-    if [ ! "$(ls -A . )" ]; then # for local development only
-        git submodule init
-        git submodule update
-    fi
     CGO_ENABLED=0 go build
     cd ../../
 
diff --git a/t/grpc_server_example b/t/grpc_server_example
deleted file mode 160000
index 5e74be697..000000000
--- a/t/grpc_server_example
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 5e74be697f24151648be1712fce0ab2fdd0ec964
diff --git a/t/grpc_server_example/go.mod b/t/grpc_server_example/go.mod
new file mode 100644
index 000000000..cccb735e9
--- /dev/null
+++ b/t/grpc_server_example/go.mod
@@ -0,0 +1,9 @@
+module github.com/api7/grpc_server_example
+
+go 1.11
+
+require (
+	github.com/golang/protobuf v1.5.0
+	google.golang.org/grpc v1.32.0
+	google.golang.org/protobuf v1.27.1 // indirect
+)
diff --git a/t/grpc_server_example/go.sum b/t/grpc_server_example/go.sum
new file mode 100644
index 000000000..d150a12f6
--- /dev/null
+++ b/t/grpc_server_example/go.sum
@@ -0,0 +1,60 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/t/grpc_server_example/main.go b/t/grpc_server_example/main.go
new file mode 100644
index 000000000..18bda0536
--- /dev/null
+++ b/t/grpc_server_example/main.go
@@ -0,0 +1,254 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/helloworld.proto
+//go:generate protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/import.proto
+//go:generate protoc  --include_imports --descriptor_set_out=proto.pb --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/src.proto
+
+// Package main implements a server for Greeter service.
+package main
+
+import (
+	"context"
+	"crypto/tls"
+	"crypto/x509"
+	"flag"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"log"
+	"net"
+	"os"
+	"os/signal"
+	"syscall"
+	"time"
+
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/codes"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/reflection"
+	"google.golang.org/grpc/status"
+
+	pb "github.com/api7/grpc_server_example/proto"
+)
+
+var (
+	grpcAddr      = ":50051"
+	grpcsAddr     = ":50052"
+	grpcsMtlsAddr string
+
+	crtFilePath = "../t/cert/apisix.crt"
+	keyFilePath = "../t/cert/apisix.key"
+	caFilePath  string
+)
+
+func init() {
+	flag.StringVar(&grpcAddr, "grpc-address", grpcAddr, "address for grpc")
+	flag.StringVar(&grpcsAddr, "grpcs-address", grpcsAddr, "address for grpcs")
+	flag.StringVar(&grpcsMtlsAddr, "grpcs-mtls-address", grpcsMtlsAddr, "address for grpcs in mTLS")
+	flag.StringVar(&crtFilePath, "crt", crtFilePath, "path to certificate")
+	flag.StringVar(&keyFilePath, "key", keyFilePath, "path to key")
+	flag.StringVar(&caFilePath, "ca", caFilePath, "path to ca")
+}
+
+// server is used to implement helloworld.GreeterServer.
+type server struct {
+	// Embed the unimplemented server
+	pb.UnimplementedGreeterServer
+	pb.UnimplementedTestImportServer
+}
+
+// SayHello implements helloworld.GreeterServer
+func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
+	log.Printf("Received: %v", in.Name)
+	log.Printf("Enum Gender: %v", in.GetGender())
+	msg := "Hello " + in.Name
+
+	person := in.GetPerson()
+	if person != nil {
+		if person.GetName() != "" {
+			msg += fmt.Sprintf(", name: %v", person.GetName())
+		}
+		if person.GetAge() != 0 {
+			msg += fmt.Sprintf(", age: %v", person.GetAge())
+		}
+	}
+
+	return &pb.HelloReply{
+		Message: msg,
+		Items:   in.GetItems(),
+		Gender:  in.GetGender(),
+	}, nil
+}
+
+func (s *server) SayHelloAfterDelay(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
+
+	select {
+	case <-time.After(1 * time.Second):
+		fmt.Println("overslept")
+	case <-ctx.Done():
+		errStr := ctx.Err().Error()
+		if ctx.Err() == context.DeadlineExceeded {
+			return nil, status.Error(codes.DeadlineExceeded, errStr)
+		}
+	}
+
+	time.Sleep(1 * time.Second)
+
+	log.Printf("Received: %v", in.Name)
+
+	return &pb.HelloReply{Message: "Hello delay " + in.Name}, nil
+}
+
+func (s *server) Plus(ctx context.Context, in *pb.PlusRequest) (*pb.PlusReply, error) {
+	log.Printf("Received: %v %v", in.A, in.B)
+	return &pb.PlusReply{Result: in.A + in.B}, nil
+}
+
+// SayHelloServerStream streams HelloReply back to the client.
+func (s *server) SayHelloServerStream(req *pb.HelloRequest, stream pb.Greeter_SayHelloServerStreamServer) error {
+	log.Printf("Received server side stream req: %v\n", req)
+
+	// Say Hello 5 times.
+	for i := 0; i < 5; i++ {
+		if err := stream.Send(&pb.HelloReply{
+			Message: fmt.Sprintf("Hello %s", req.Name),
+		}); err != nil {
+			return status.Errorf(codes.Unavailable, "Unable to stream request back to client: %v", err)
+		}
+	}
+	return nil
+}
+
+// SayHelloClientStream receives a stream of HelloRequest from a client.
+func (s *server) SayHelloClientStream(stream pb.Greeter_SayHelloClientStreamServer) error {
+	log.Println("SayHello client side streaming has been initiated.")
+	cache := ""
+	for {
+		req, err := stream.Recv()
+		if err == io.EOF {
+			return stream.SendAndClose(&pb.HelloReply{Message: cache})
+		}
+		if err != nil {
+			return status.Errorf(codes.Unavailable, "Failed to read client stream: %v", err)
+		}
+		cache = fmt.Sprintf("%sHello %s!", cache, req.Name)
+	}
+}
+
+// SayHelloBidirectionalStream establishes a bidirectional stream with the client.
+func (s *server) SayHelloBidirectionalStream(stream pb.Greeter_SayHelloBidirectionalStreamServer) error {
+	log.Println("SayHello bidirectional streaming has been initiated.")
+
+	for {
+		req, err := stream.Recv()
+		if err == io.EOF {
+			return stream.Send(&pb.HelloReply{Message: "stream ended"})
+		}
+		if err != nil {
+			return status.Errorf(codes.Unavailable, "Failed to read client stream: %v", err)
+		}
+
+		// A small 0.5 sec sleep
+		time.Sleep(500 * time.Millisecond)
+
+		if err := stream.Send(&pb.HelloReply{Message: fmt.Sprintf("Hello %s", req.Name)}); err != nil {
+			return status.Errorf(codes.Unknown, "Failed to stream response back to client: %v", err)
+		}
+	}
+}
+
+func (s *server) Run(ctx context.Context, in *pb.Request) (*pb.Response, error) {
+	return &pb.Response{Body: in.User.Name + " " + in.Body}, nil
+}
+
+func main() {
+	flag.Parse()
+
+	go func() {
+		lis, err := net.Listen("tcp", grpcAddr)
+		if err != nil {
+			log.Fatalf("failed to listen: %v", err)
+		}
+		s := grpc.NewServer()
+		reflection.Register(s)
+		pb.RegisterGreeterServer(s, &server{})
+		pb.RegisterTestImportServer(s, &server{})
+		if err := s.Serve(lis); err != nil {
+			log.Fatalf("failed to serve: %v", err)
+		}
+	}()
+
+	go func() {
+		lis, err := net.Listen("tcp", grpcsAddr)
+		if err != nil {
+			log.Fatalf("failed to listen: %v", err)
+		}
+
+		c, err := credentials.NewServerTLSFromFile(crtFilePath, keyFilePath)
+		if err != nil {
+			log.Fatalf("credentials.NewServerTLSFromFile err: %v", err)
+		}
+		s := grpc.NewServer(grpc.Creds(c))
+		reflection.Register(s)
+		pb.RegisterGreeterServer(s, &server{})
+		if err := s.Serve(lis); err != nil {
+			log.Fatalf("failed to serve: %v", err)
+		}
+	}()
+
+	if grpcsMtlsAddr != "" {
+		go func() {
+			lis, err := net.Listen("tcp", grpcsMtlsAddr)
+			if err != nil {
+				log.Fatalf("failed to listen: %v", err)
+			}
+
+			certificate, err := tls.LoadX509KeyPair(crtFilePath, keyFilePath)
+			if err != nil {
+				log.Fatalf("could not load server key pair: %s", err)
+			}
+
+			certPool := x509.NewCertPool()
+			ca, err := ioutil.ReadFile(caFilePath)
+			if err != nil {
+				log.Fatalf("could not read ca certificate: %s", err)
+			}
+
+			if ok := certPool.AppendCertsFromPEM(ca); !ok {
+				log.Fatalf("failed to append client certs")
+			}
+
+			c := credentials.NewTLS(&tls.Config{
+				ClientAuth:   tls.RequireAndVerifyClientCert,
+				Certificates: []tls.Certificate{certificate},
+				ClientCAs:    certPool,
+			})
+			s := grpc.NewServer(grpc.Creds(c))
+			reflection.Register(s)
+			pb.RegisterGreeterServer(s, &server{})
+			if err := s.Serve(lis); err != nil {
+				log.Fatalf("failed to serve: %v", err)
+			}
+		}()
+	}
+
+	signals := make(chan os.Signal)
+	signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
+	sig := <-signals
+	log.Printf("get signal %s, exit\n", sig.String())
+}
diff --git a/t/grpc_server_example/proto.pb b/t/grpc_server_example/proto.pb
new file mode 100644
index 000000000..8edcc5c64
Binary files /dev/null and b/t/grpc_server_example/proto.pb differ
diff --git a/t/grpc_server_example/proto/helloworld.pb.go b/t/grpc_server_example/proto/helloworld.pb.go
new file mode 100644
index 000000000..9cb209566
--- /dev/null
+++ b/t/grpc_server_example/proto/helloworld.pb.go
@@ -0,0 +1,573 @@
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.6.1
+// source: proto/helloworld.proto
+
+package proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Gender int32
+
+const (
+	Gender_GENDER_UNKNOWN Gender = 0
+	Gender_GENDER_MALE    Gender = 1
+	Gender_GENDER_FEMALE  Gender = 2
+)
+
+// Enum value maps for Gender.
+var (
+	Gender_name = map[int32]string{
+		0: "GENDER_UNKNOWN",
+		1: "GENDER_MALE",
+		2: "GENDER_FEMALE",
+	}
+	Gender_value = map[string]int32{
+		"GENDER_UNKNOWN": 0,
+		"GENDER_MALE":    1,
+		"GENDER_FEMALE":  2,
+	}
+)
+
+func (x Gender) Enum() *Gender {
+	p := new(Gender)
+	*p = x
+	return p
+}
+
+func (x Gender) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Gender) Descriptor() protoreflect.EnumDescriptor {
+	return file_proto_helloworld_proto_enumTypes[0].Descriptor()
+}
+
+func (Gender) Type() protoreflect.EnumType {
+	return &file_proto_helloworld_proto_enumTypes[0]
+}
+
+func (x Gender) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Use Gender.Descriptor instead.
+func (Gender) EnumDescriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{0}
+}
+
+type Person struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Age  int32  `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"`
+}
+
+func (x *Person) Reset() {
+	*x = Person{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_helloworld_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Person) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Person) ProtoMessage() {}
+
+func (x *Person) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_helloworld_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Person.ProtoReflect.Descriptor instead.
+func (*Person) Descriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Person) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *Person) GetAge() int32 {
+	if x != nil {
+		return x.Age
+	}
+	return 0
+}
+
+type HelloRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name   string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Items  []string `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"`
+	Gender Gender   `protobuf:"varint,3,opt,name=gender,proto3,enum=helloworld.Gender" json:"gender,omitempty"`
+	Person *Person  `protobuf:"bytes,4,opt,name=person,proto3" json:"person,omitempty"`
+}
+
+func (x *HelloRequest) Reset() {
+	*x = HelloRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_helloworld_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HelloRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloRequest) ProtoMessage() {}
+
+func (x *HelloRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_helloworld_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloRequest.ProtoReflect.Descriptor instead.
+func (*HelloRequest) Descriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *HelloRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *HelloRequest) GetItems() []string {
+	if x != nil {
+		return x.Items
+	}
+	return nil
+}
+
+func (x *HelloRequest) GetGender() Gender {
+	if x != nil {
+		return x.Gender
+	}
+	return Gender_GENDER_UNKNOWN
+}
+
+func (x *HelloRequest) GetPerson() *Person {
+	if x != nil {
+		return x.Person
+	}
+	return nil
+}
+
+type HelloReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Message string   `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	Items   []string `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"`
+	Gender  Gender   `protobuf:"varint,3,opt,name=gender,proto3,enum=helloworld.Gender" json:"gender,omitempty"`
+}
+
+func (x *HelloReply) Reset() {
+	*x = HelloReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_helloworld_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *HelloReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*HelloReply) ProtoMessage() {}
+
+func (x *HelloReply) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_helloworld_proto_msgTypes[2]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use HelloReply.ProtoReflect.Descriptor instead.
+func (*HelloReply) Descriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *HelloReply) GetMessage() string {
+	if x != nil {
+		return x.Message
+	}
+	return ""
+}
+
+func (x *HelloReply) GetItems() []string {
+	if x != nil {
+		return x.Items
+	}
+	return nil
+}
+
+func (x *HelloReply) GetGender() Gender {
+	if x != nil {
+		return x.Gender
+	}
+	return Gender_GENDER_UNKNOWN
+}
+
+type PlusRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	A int64 `protobuf:"varint,1,opt,name=a,proto3" json:"a,omitempty"`
+	B int64 `protobuf:"varint,2,opt,name=b,proto3" json:"b,omitempty"`
+}
+
+func (x *PlusRequest) Reset() {
+	*x = PlusRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_helloworld_proto_msgTypes[3]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PlusRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PlusRequest) ProtoMessage() {}
+
+func (x *PlusRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_helloworld_proto_msgTypes[3]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PlusRequest.ProtoReflect.Descriptor instead.
+func (*PlusRequest) Descriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{3}
+}
+
+func (x *PlusRequest) GetA() int64 {
+	if x != nil {
+		return x.A
+	}
+	return 0
+}
+
+func (x *PlusRequest) GetB() int64 {
+	if x != nil {
+		return x.B
+	}
+	return 0
+}
+
+type PlusReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Result int64 `protobuf:"varint,1,opt,name=result,proto3" json:"result,omitempty"`
+}
+
+func (x *PlusReply) Reset() {
+	*x = PlusReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_helloworld_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PlusReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PlusReply) ProtoMessage() {}
+
+func (x *PlusReply) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_helloworld_proto_msgTypes[4]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use PlusReply.ProtoReflect.Descriptor instead.
+func (*PlusReply) Descriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *PlusReply) GetResult() int64 {
+	if x != nil {
+		return x.Result
+	}
+	return 0
+}
+
+var File_proto_helloworld_proto protoreflect.FileDescriptor
+
+var file_proto_helloworld_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
+	0x6c, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
+	0x6f, 0x72, 0x6c, 0x64, 0x22, 0x2e, 0x0a, 0x06, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x03, 0x61, 0x67, 0x65, 0x22, 0x90, 0x01, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
+	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65,
+	0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12,
+	0x2a, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32,
+	0x12, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x47, 0x65, 0x6e,
+	0x64, 0x65, 0x72, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x06, 0x70,
+	0x65, 0x72, 0x73, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x65,
+	0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52,
+	0x06, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x22, 0x68, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f,
+	0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12,
+	0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05,
+	0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2a, 0x0a, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x18,
+	0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
+	0x6c, 0x64, 0x2e, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x06, 0x67, 0x65, 0x6e, 0x64, 0x65,
+	0x72, 0x22, 0x29, 0x0a, 0x0b, 0x50, 0x6c, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x12, 0x0c, 0x0a, 0x01, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x01, 0x61, 0x12, 0x0c,
+	0x0a, 0x01, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x01, 0x62, 0x22, 0x23, 0x0a, 0x09,
+	0x50, 0x6c, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73,
+	0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c,
+	0x74, 0x2a, 0x40, 0x0a, 0x06, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x0e, 0x47,
+	0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12,
+	0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x4d, 0x41, 0x4c, 0x45, 0x10, 0x01,
+	0x12, 0x11, 0x0a, 0x0d, 0x47, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x46, 0x45, 0x4d, 0x41, 0x4c,
+	0x45, 0x10, 0x02, 0x32, 0xc0, 0x03, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12,
+	0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65,
+	0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
+	0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12,
+	0x38, 0x0a, 0x04, 0x50, 0x6c, 0x75, 0x73, 0x12, 0x17, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
+	0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x50, 0x6c, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x15, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x50, 0x6c,
+	0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x12, 0x53, 0x61, 0x79,
+	0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x41, 0x66, 0x74, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12,
+	0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c,
+	0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c,
+	0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c,
+	0x79, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x14, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53,
+	0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65,
+	0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
+	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
+	0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30,
+	0x01, 0x12, 0x4c, 0x0a, 0x14, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x43, 0x6c, 0x69,
+	0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c,
+	0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
+	0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x28, 0x01, 0x12,
+	0x55, 0x0a, 0x1b, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x42, 0x69, 0x64, 0x69, 0x72,
+	0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18,
+	0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c,
+	0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
+	0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79,
+	0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_proto_helloworld_proto_rawDescOnce sync.Once
+	file_proto_helloworld_proto_rawDescData = file_proto_helloworld_proto_rawDesc
+)
+
+func file_proto_helloworld_proto_rawDescGZIP() []byte {
+	file_proto_helloworld_proto_rawDescOnce.Do(func() {
+		file_proto_helloworld_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_helloworld_proto_rawDescData)
+	})
+	return file_proto_helloworld_proto_rawDescData
+}
+
+var file_proto_helloworld_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_proto_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_proto_helloworld_proto_goTypes = []interface{}{
+	(Gender)(0),          // 0: helloworld.Gender
+	(*Person)(nil),       // 1: helloworld.Person
+	(*HelloRequest)(nil), // 2: helloworld.HelloRequest
+	(*HelloReply)(nil),   // 3: helloworld.HelloReply
+	(*PlusRequest)(nil),  // 4: helloworld.PlusRequest
+	(*PlusReply)(nil),    // 5: helloworld.PlusReply
+}
+var file_proto_helloworld_proto_depIdxs = []int32{
+	0, // 0: helloworld.HelloRequest.gender:type_name -> helloworld.Gender
+	1, // 1: helloworld.HelloRequest.person:type_name -> helloworld.Person
+	0, // 2: helloworld.HelloReply.gender:type_name -> helloworld.Gender
+	2, // 3: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
+	4, // 4: helloworld.Greeter.Plus:input_type -> helloworld.PlusRequest
+	2, // 5: helloworld.Greeter.SayHelloAfterDelay:input_type -> helloworld.HelloRequest
+	2, // 6: helloworld.Greeter.SayHelloServerStream:input_type -> helloworld.HelloRequest
+	2, // 7: helloworld.Greeter.SayHelloClientStream:input_type -> helloworld.HelloRequest
+	2, // 8: helloworld.Greeter.SayHelloBidirectionalStream:input_type -> helloworld.HelloRequest
+	3, // 9: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
+	5, // 10: helloworld.Greeter.Plus:output_type -> helloworld.PlusReply
+	3, // 11: helloworld.Greeter.SayHelloAfterDelay:output_type -> helloworld.HelloReply
+	3, // 12: helloworld.Greeter.SayHelloServerStream:output_type -> helloworld.HelloReply
+	3, // 13: helloworld.Greeter.SayHelloClientStream:output_type -> helloworld.HelloReply
+	3, // 14: helloworld.Greeter.SayHelloBidirectionalStream:output_type -> helloworld.HelloReply
+	9, // [9:15] is the sub-list for method output_type
+	3, // [3:9] is the sub-list for method input_type
+	3, // [3:3] is the sub-list for extension type_name
+	3, // [3:3] is the sub-list for extension extendee
+	0, // [0:3] is the sub-list for field type_name
+}
+
+func init() { file_proto_helloworld_proto_init() }
+func file_proto_helloworld_proto_init() {
+	if File_proto_helloworld_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_proto_helloworld_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Person); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_helloworld_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HelloRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_helloworld_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*HelloReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_helloworld_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PlusRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_helloworld_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PlusReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_proto_helloworld_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   5,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_proto_helloworld_proto_goTypes,
+		DependencyIndexes: file_proto_helloworld_proto_depIdxs,
+		EnumInfos:         file_proto_helloworld_proto_enumTypes,
+		MessageInfos:      file_proto_helloworld_proto_msgTypes,
+	}.Build()
+	File_proto_helloworld_proto = out.File
+	file_proto_helloworld_proto_rawDesc = nil
+	file_proto_helloworld_proto_goTypes = nil
+	file_proto_helloworld_proto_depIdxs = nil
+}
diff --git a/t/grpc_server_example/proto/helloworld.proto b/t/grpc_server_example/proto/helloworld.proto
new file mode 100644
index 000000000..2e18a467c
--- /dev/null
+++ b/t/grpc_server_example/proto/helloworld.proto
@@ -0,0 +1,70 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+syntax = "proto3";
+
+package helloworld;
+option go_package = "./proto";
+
+service Greeter {
+  // Unary RPC.
+  rpc SayHello (HelloRequest) returns (HelloReply) {}
+  rpc Plus (PlusRequest) returns (PlusReply) {}
+  rpc SayHelloAfterDelay (HelloRequest) returns (HelloReply) {}
+
+  // Server side streaming.
+  rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply) {}
+
+  // Client side streaming.
+  rpc SayHelloClientStream (stream HelloRequest) returns (HelloReply) {}
+
+  // Bidirectional streaming.
+  rpc SayHelloBidirectionalStream (stream HelloRequest) returns (stream HelloReply) {}
+}
+
+enum Gender {
+    GENDER_UNKNOWN = 0;
+    GENDER_MALE = 1;
+    GENDER_FEMALE = 2;
+}
+
+message Person {
+    string name = 1;
+    int32 age = 2;
+}
+
+message HelloRequest {
+  string name = 1;
+  repeated string items = 2;
+  Gender gender = 3;
+  Person person = 4;
+}
+
+message HelloReply {
+  string message = 1;
+  repeated string items = 2;
+  Gender gender = 3;
+}
+
+message PlusRequest {
+  int64 a = 1;
+  int64 b = 2;
+}
+
+message PlusReply {
+  int64 result = 1;
+}
diff --git a/t/grpc_server_example/proto/helloworld_grpc.pb.go b/t/grpc_server_example/proto/helloworld_grpc.pb.go
new file mode 100644
index 000000000..7d6d8ef8b
--- /dev/null
+++ b/t/grpc_server_example/proto/helloworld_grpc.pb.go
@@ -0,0 +1,383 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+
+package proto
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// GreeterClient is the client API for Greeter service.
+//
+// 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.
+type GreeterClient interface {
+	// Unary RPC.
+	SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
+	Plus(ctx context.Context, in *PlusRequest, opts ...grpc.CallOption) (*PlusReply, error)
+	SayHelloAfterDelay(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
+	// Server side streaming.
+	SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (Greeter_SayHelloServerStreamClient, error)
+	// Client side streaming.
+	SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_SayHelloClientStreamClient, error)
+	// Bidirectional streaming.
+	SayHelloBidirectionalStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_SayHelloBidirectionalStreamClient, error)
+}
+
+type greeterClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {
+	return &greeterClient{cc}
+}
+
+func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
+	out := new(HelloReply)
+	err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *greeterClient) Plus(ctx context.Context, in *PlusRequest, opts ...grpc.CallOption) (*PlusReply, error) {
+	out := new(PlusReply)
+	err := c.cc.Invoke(ctx, "/helloworld.Greeter/Plus", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *greeterClient) SayHelloAfterDelay(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
+	out := new(HelloReply)
+	err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHelloAfterDelay", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+func (c *greeterClient) SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (Greeter_SayHelloServerStreamClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], "/helloworld.Greeter/SayHelloServerStream", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &greeterSayHelloServerStreamClient{stream}
+	if err := x.ClientStream.SendMsg(in); err != nil {
+		return nil, err
+	}
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	return x, nil
+}
+
+type Greeter_SayHelloServerStreamClient interface {
+	Recv() (*HelloReply, error)
+	grpc.ClientStream
+}
+
+type greeterSayHelloServerStreamClient struct {
+	grpc.ClientStream
+}
+
+func (x *greeterSayHelloServerStreamClient) Recv() (*HelloReply, error) {
+	m := new(HelloReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *greeterClient) SayHelloClientStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_SayHelloClientStreamClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[1], "/helloworld.Greeter/SayHelloClientStream", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &greeterSayHelloClientStreamClient{stream}
+	return x, nil
+}
+
+type Greeter_SayHelloClientStreamClient interface {
+	Send(*HelloRequest) error
+	CloseAndRecv() (*HelloReply, error)
+	grpc.ClientStream
+}
+
+type greeterSayHelloClientStreamClient struct {
+	grpc.ClientStream
+}
+
+func (x *greeterSayHelloClientStreamClient) Send(m *HelloRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *greeterSayHelloClientStreamClient) CloseAndRecv() (*HelloReply, error) {
+	if err := x.ClientStream.CloseSend(); err != nil {
+		return nil, err
+	}
+	m := new(HelloReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func (c *greeterClient) SayHelloBidirectionalStream(ctx context.Context, opts ...grpc.CallOption) (Greeter_SayHelloBidirectionalStreamClient, error) {
+	stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[2], "/helloworld.Greeter/SayHelloBidirectionalStream", opts...)
+	if err != nil {
+		return nil, err
+	}
+	x := &greeterSayHelloBidirectionalStreamClient{stream}
+	return x, nil
+}
+
+type Greeter_SayHelloBidirectionalStreamClient interface {
+	Send(*HelloRequest) error
+	Recv() (*HelloReply, error)
+	grpc.ClientStream
+}
+
+type greeterSayHelloBidirectionalStreamClient struct {
+	grpc.ClientStream
+}
+
+func (x *greeterSayHelloBidirectionalStreamClient) Send(m *HelloRequest) error {
+	return x.ClientStream.SendMsg(m)
+}
+
+func (x *greeterSayHelloBidirectionalStreamClient) Recv() (*HelloReply, error) {
+	m := new(HelloReply)
+	if err := x.ClientStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// GreeterServer is the server API for Greeter service.
+// All implementations must embed UnimplementedGreeterServer
+// for forward compatibility
+type GreeterServer interface {
+	// Unary RPC.
+	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
+	Plus(context.Context, *PlusRequest) (*PlusReply, error)
+	SayHelloAfterDelay(context.Context, *HelloRequest) (*HelloReply, error)
+	// Server side streaming.
+	SayHelloServerStream(*HelloRequest, Greeter_SayHelloServerStreamServer) error
+	// Client side streaming.
+	SayHelloClientStream(Greeter_SayHelloClientStreamServer) error
+	// Bidirectional streaming.
+	SayHelloBidirectionalStream(Greeter_SayHelloBidirectionalStreamServer) error
+	mustEmbedUnimplementedGreeterServer()
+}
+
+// UnimplementedGreeterServer must be embedded to have forward compatible implementations.
+type UnimplementedGreeterServer struct {
+}
+
+func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
+}
+func (UnimplementedGreeterServer) Plus(context.Context, *PlusRequest) (*PlusReply, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Plus not implemented")
+}
+func (UnimplementedGreeterServer) SayHelloAfterDelay(context.Context, *HelloRequest) (*HelloReply, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SayHelloAfterDelay not implemented")
+}
+func (UnimplementedGreeterServer) SayHelloServerStream(*HelloRequest, Greeter_SayHelloServerStreamServer) error {
+	return status.Errorf(codes.Unimplemented, "method SayHelloServerStream not implemented")
+}
+func (UnimplementedGreeterServer) SayHelloClientStream(Greeter_SayHelloClientStreamServer) error {
+	return status.Errorf(codes.Unimplemented, "method SayHelloClientStream not implemented")
+}
+func (UnimplementedGreeterServer) SayHelloBidirectionalStream(Greeter_SayHelloBidirectionalStreamServer) error {
+	return status.Errorf(codes.Unimplemented, "method SayHelloBidirectionalStream not implemented")
+}
+func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
+
+// UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to GreeterServer will
+// result in compilation errors.
+type UnsafeGreeterServer interface {
+	mustEmbedUnimplementedGreeterServer()
+}
+
+func RegisterGreeterServer(s grpc.ServiceRegistrar, srv GreeterServer) {
+	s.RegisterService(&Greeter_ServiceDesc, srv)
+}
+
+func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(HelloRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GreeterServer).SayHello(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/helloworld.Greeter/SayHello",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Greeter_Plus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(PlusRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GreeterServer).Plus(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/helloworld.Greeter/Plus",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GreeterServer).Plus(ctx, req.(*PlusRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Greeter_SayHelloAfterDelay_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(HelloRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GreeterServer).SayHelloAfterDelay(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/helloworld.Greeter/SayHelloAfterDelay",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GreeterServer).SayHelloAfterDelay(ctx, req.(*HelloRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+func _Greeter_SayHelloServerStream_Handler(srv interface{}, stream grpc.ServerStream) error {
+	m := new(HelloRequest)
+	if err := stream.RecvMsg(m); err != nil {
+		return err
+	}
+	return srv.(GreeterServer).SayHelloServerStream(m, &greeterSayHelloServerStreamServer{stream})
+}
+
+type Greeter_SayHelloServerStreamServer interface {
+	Send(*HelloReply) error
+	grpc.ServerStream
+}
+
+type greeterSayHelloServerStreamServer struct {
+	grpc.ServerStream
+}
+
+func (x *greeterSayHelloServerStreamServer) Send(m *HelloReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func _Greeter_SayHelloClientStream_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(GreeterServer).SayHelloClientStream(&greeterSayHelloClientStreamServer{stream})
+}
+
+type Greeter_SayHelloClientStreamServer interface {
+	SendAndClose(*HelloReply) error
+	Recv() (*HelloRequest, error)
+	grpc.ServerStream
+}
+
+type greeterSayHelloClientStreamServer struct {
+	grpc.ServerStream
+}
+
+func (x *greeterSayHelloClientStreamServer) SendAndClose(m *HelloReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *greeterSayHelloClientStreamServer) Recv() (*HelloRequest, error) {
+	m := new(HelloRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+func _Greeter_SayHelloBidirectionalStream_Handler(srv interface{}, stream grpc.ServerStream) error {
+	return srv.(GreeterServer).SayHelloBidirectionalStream(&greeterSayHelloBidirectionalStreamServer{stream})
+}
+
+type Greeter_SayHelloBidirectionalStreamServer interface {
+	Send(*HelloReply) error
+	Recv() (*HelloRequest, error)
+	grpc.ServerStream
+}
+
+type greeterSayHelloBidirectionalStreamServer struct {
+	grpc.ServerStream
+}
+
+func (x *greeterSayHelloBidirectionalStreamServer) Send(m *HelloReply) error {
+	return x.ServerStream.SendMsg(m)
+}
+
+func (x *greeterSayHelloBidirectionalStreamServer) Recv() (*HelloRequest, error) {
+	m := new(HelloRequest)
+	if err := x.ServerStream.RecvMsg(m); err != nil {
+		return nil, err
+	}
+	return m, nil
+}
+
+// Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var Greeter_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "helloworld.Greeter",
+	HandlerType: (*GreeterServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "SayHello",
+			Handler:    _Greeter_SayHello_Handler,
+		},
+		{
+			MethodName: "Plus",
+			Handler:    _Greeter_Plus_Handler,
+		},
+		{
+			MethodName: "SayHelloAfterDelay",
+			Handler:    _Greeter_SayHelloAfterDelay_Handler,
+		},
+	},
+	Streams: []grpc.StreamDesc{
+		{
+			StreamName:    "SayHelloServerStream",
+			Handler:       _Greeter_SayHelloServerStream_Handler,
+			ServerStreams: true,
+		},
+		{
+			StreamName:    "SayHelloClientStream",
+			Handler:       _Greeter_SayHelloClientStream_Handler,
+			ClientStreams: true,
+		},
+		{
+			StreamName:    "SayHelloBidirectionalStream",
+			Handler:       _Greeter_SayHelloBidirectionalStream_Handler,
+			ServerStreams: true,
+			ClientStreams: true,
+		},
+	},
+	Metadata: "proto/helloworld.proto",
+}
diff --git a/t/grpc_server_example/proto/import.pb.go b/t/grpc_server_example/proto/import.pb.go
new file mode 100644
index 000000000..28fabf3f3
--- /dev/null
+++ b/t/grpc_server_example/proto/import.pb.go
@@ -0,0 +1,203 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.6.1
+// source: proto/import.proto
+
+package proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type User struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+}
+
+func (x *User) Reset() {
+	*x = User{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_import_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *User) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*User) ProtoMessage() {}
+
+func (x *User) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_import_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use User.ProtoReflect.Descriptor instead.
+func (*User) Descriptor() ([]byte, []int) {
+	return file_proto_import_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *User) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+type Response struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Body string `protobuf:"bytes,1,opt,name=body,proto3" json:"body,omitempty"`
+}
+
+func (x *Response) Reset() {
+	*x = Response{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_import_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Response) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Response) ProtoMessage() {}
+
+func (x *Response) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_import_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Response.ProtoReflect.Descriptor instead.
+func (*Response) Descriptor() ([]byte, []int) {
+	return file_proto_import_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *Response) GetBody() string {
+	if x != nil {
+		return x.Body
+	}
+	return ""
+}
+
+var File_proto_import_proto protoreflect.FileDescriptor
+
+var file_proto_import_proto_rawDesc = []byte{
+	0x0a, 0x12, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x12, 0x03, 0x70, 0x6b, 0x67, 0x22, 0x1a, 0x0a, 0x04, 0x55, 0x73, 0x65,
+	0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x1e, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
+	0x65, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x62, 0x6f, 0x64, 0x79, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_proto_import_proto_rawDescOnce sync.Once
+	file_proto_import_proto_rawDescData = file_proto_import_proto_rawDesc
+)
+
+func file_proto_import_proto_rawDescGZIP() []byte {
+	file_proto_import_proto_rawDescOnce.Do(func() {
+		file_proto_import_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_import_proto_rawDescData)
+	})
+	return file_proto_import_proto_rawDescData
+}
+
+var file_proto_import_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_proto_import_proto_goTypes = []interface{}{
+	(*User)(nil),     // 0: pkg.User
+	(*Response)(nil), // 1: pkg.Response
+}
+var file_proto_import_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_proto_import_proto_init() }
+func file_proto_import_proto_init() {
+	if File_proto_import_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_proto_import_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*User); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_import_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Response); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_proto_import_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_proto_import_proto_goTypes,
+		DependencyIndexes: file_proto_import_proto_depIdxs,
+		MessageInfos:      file_proto_import_proto_msgTypes,
+	}.Build()
+	File_proto_import_proto = out.File
+	file_proto_import_proto_rawDesc = nil
+	file_proto_import_proto_goTypes = nil
+	file_proto_import_proto_depIdxs = nil
+}
diff --git a/t/grpc_server_example/proto/import.proto b/t/grpc_server_example/proto/import.proto
new file mode 100644
index 000000000..b76505953
--- /dev/null
+++ b/t/grpc_server_example/proto/import.proto
@@ -0,0 +1,29 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+syntax = "proto3";
+
+package pkg;
+option go_package = "./proto";
+
+message User {
+    string name = 1;
+}
+
+message Response {
+  string body = 1;
+}
diff --git a/t/grpc_server_example/proto/src.pb.go b/t/grpc_server_example/proto/src.pb.go
new file mode 100644
index 000000000..8e6a32ae3
--- /dev/null
+++ b/t/grpc_server_example/proto/src.pb.go
@@ -0,0 +1,162 @@
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.27.1
+// 	protoc        v3.6.1
+// source: proto/src.proto
+
+package proto
+
+import (
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type Request struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	User *User  `protobuf:"bytes,1,opt,name=user,proto3" json:"user,omitempty"`
+	Body string `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
+}
+
+func (x *Request) Reset() {
+	*x = Request{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_src_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *Request) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*Request) ProtoMessage() {}
+
+func (x *Request) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_src_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use Request.ProtoReflect.Descriptor instead.
+func (*Request) Descriptor() ([]byte, []int) {
+	return file_proto_src_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *Request) GetUser() *User {
+	if x != nil {
+		return x.User
+	}
+	return nil
+}
+
+func (x *Request) GetBody() string {
+	if x != nil {
+		return x.Body
+	}
+	return ""
+}
+
+var File_proto_src_proto protoreflect.FileDescriptor
+
+var file_proto_src_proto_rawDesc = []byte{
+	0x0a, 0x0f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x73, 0x72, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x12, 0x0a, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x1a, 0x12, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x22, 0x3c, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x04,
+	0x75, 0x73, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x09, 0x2e, 0x70, 0x6b, 0x67,
+	0x2e, 0x55, 0x73, 0x65, 0x72, 0x52, 0x04, 0x75, 0x73, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x62,
+	0x6f, 0x64, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x32,
+	0x39, 0x0a, 0x0a, 0x54, 0x65, 0x73, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x2b, 0x0a,
+	0x03, 0x52, 0x75, 0x6e, 0x12, 0x13, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c,
+	0x64, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0d, 0x2e, 0x70, 0x6b, 0x67, 0x2e,
+	0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+}
+
+var (
+	file_proto_src_proto_rawDescOnce sync.Once
+	file_proto_src_proto_rawDescData = file_proto_src_proto_rawDesc
+)
+
+func file_proto_src_proto_rawDescGZIP() []byte {
+	file_proto_src_proto_rawDescOnce.Do(func() {
+		file_proto_src_proto_rawDescData = protoimpl.X.CompressGZIP(file_proto_src_proto_rawDescData)
+	})
+	return file_proto_src_proto_rawDescData
+}
+
+var file_proto_src_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_proto_src_proto_goTypes = []interface{}{
+	(*Request)(nil),  // 0: helloworld.Request
+	(*User)(nil),     // 1: pkg.User
+	(*Response)(nil), // 2: pkg.Response
+}
+var file_proto_src_proto_depIdxs = []int32{
+	1, // 0: helloworld.Request.user:type_name -> pkg.User
+	0, // 1: helloworld.TestImport.Run:input_type -> helloworld.Request
+	2, // 2: helloworld.TestImport.Run:output_type -> pkg.Response
+	2, // [2:3] is the sub-list for method output_type
+	1, // [1:2] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_proto_src_proto_init() }
+func file_proto_src_proto_init() {
+	if File_proto_src_proto != nil {
+		return
+	}
+	file_proto_import_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_proto_src_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*Request); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_proto_src_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   1,
+		},
+		GoTypes:           file_proto_src_proto_goTypes,
+		DependencyIndexes: file_proto_src_proto_depIdxs,
+		MessageInfos:      file_proto_src_proto_msgTypes,
+	}.Build()
+	File_proto_src_proto = out.File
+	file_proto_src_proto_rawDesc = nil
+	file_proto_src_proto_goTypes = nil
+	file_proto_src_proto_depIdxs = nil
+}
diff --git a/t/grpc_server_example/proto/src.proto b/t/grpc_server_example/proto/src.proto
new file mode 100644
index 000000000..11d9b6609
--- /dev/null
+++ b/t/grpc_server_example/proto/src.proto
@@ -0,0 +1,32 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+syntax = "proto3";
+
+package helloworld;
+option go_package = "./proto";
+
+import "proto/import.proto";
+
+service TestImport {
+  rpc Run (Request) returns (pkg.Response) {}
+}
+
+message Request {
+  pkg.User user = 1;
+  string body = 2;
+}
diff --git a/t/grpc_server_example/proto/src_grpc.pb.go b/t/grpc_server_example/proto/src_grpc.pb.go
new file mode 100644
index 000000000..01fe1502d
--- /dev/null
+++ b/t/grpc_server_example/proto/src_grpc.pb.go
@@ -0,0 +1,101 @@
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+
+package proto
+
+import (
+	context "context"
+	grpc "google.golang.org/grpc"
+	codes "google.golang.org/grpc/codes"
+	status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.32.0 or later.
+const _ = grpc.SupportPackageIsVersion7
+
+// TestImportClient is the client API for TestImport service.
+//
+// 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.
+type TestImportClient interface {
+	Run(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
+}
+
+type testImportClient struct {
+	cc grpc.ClientConnInterface
+}
+
+func NewTestImportClient(cc grpc.ClientConnInterface) TestImportClient {
+	return &testImportClient{cc}
+}
+
+func (c *testImportClient) Run(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error) {
+	out := new(Response)
+	err := c.cc.Invoke(ctx, "/helloworld.TestImport/Run", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
+// TestImportServer is the server API for TestImport service.
+// All implementations must embed UnimplementedTestImportServer
+// for forward compatibility
+type TestImportServer interface {
+	Run(context.Context, *Request) (*Response, error)
+	mustEmbedUnimplementedTestImportServer()
+}
+
+// UnimplementedTestImportServer must be embedded to have forward compatible implementations.
+type UnimplementedTestImportServer struct {
+}
+
+func (UnimplementedTestImportServer) Run(context.Context, *Request) (*Response, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method Run not implemented")
+}
+func (UnimplementedTestImportServer) mustEmbedUnimplementedTestImportServer() {}
+
+// UnsafeTestImportServer may be embedded to opt out of forward compatibility for this service.
+// Use of this interface is not recommended, as added methods to TestImportServer will
+// result in compilation errors.
+type UnsafeTestImportServer interface {
+	mustEmbedUnimplementedTestImportServer()
+}
+
+func RegisterTestImportServer(s grpc.ServiceRegistrar, srv TestImportServer) {
+	s.RegisterService(&TestImport_ServiceDesc, srv)
+}
+
+func _TestImport_Run_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(Request)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(TestImportServer).Run(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/helloworld.TestImport/Run",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(TestImportServer).Run(ctx, req.(*Request))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
+// TestImport_ServiceDesc is the grpc.ServiceDesc for TestImport service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var TestImport_ServiceDesc = grpc.ServiceDesc{
+	ServiceName: "helloworld.TestImport",
+	HandlerType: (*TestImportServer)(nil),
+	Methods: []grpc.MethodDesc{
+		{
+			MethodName: "Run",
+			Handler:    _TestImport_Run_Handler,
+		},
+	},
+	Streams:  []grpc.StreamDesc{},
+	Metadata: "proto/src.proto",
+}


[apisix] 01/07: chore: validate etcd conf strictly (#7245)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit 67021314906a7d678912134c66ab47e1444eaf71
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Tue Jun 14 09:28:37 2022 +0800

    chore: validate etcd conf strictly (#7245)
    
    Signed-off-by: spacewander <sp...@gmail.com>
---
 .github/workflows/chaos.yml   |  3 +-
 apisix/cli/schema.lua         | 14 +++++++-
 t/chaos/utils/Dockerfile      | 75 +++++++++++++++++++++++++++++++++++++++++++
 t/cli/test_validate_config.sh | 27 ++++++++++++++++
 4 files changed, 116 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/chaos.yml b/.github/workflows/chaos.yml
index 678033763..94445df47 100644
--- a/.github/workflows/chaos.yml
+++ b/.github/workflows/chaos.yml
@@ -37,9 +37,8 @@ jobs:
       - name: Creating minikube cluster
         run: |
           bash ./t/chaos/utils/setup_chaos_utils.sh start_minikube
-          wget https://raw.githubusercontent.com/apache/apisix-docker/master/alpine-local/Dockerfile
           mkdir logs
-          docker build -t apache/apisix:alpine-local --build-arg APISIX_PATH=. -f Dockerfile .
+          docker build -t apache/apisix:alpine-local --build-arg APISIX_PATH=. -f ./t/chaos/utils/Dockerfile .
           minikube cache add apache/apisix:alpine-local -v 7 --alsologtostderr
 
       - name: Print cluster information
diff --git a/apisix/cli/schema.lua b/apisix/cli/schema.lua
index e47907456..e44f0be63 100644
--- a/apisix/cli/schema.lua
+++ b/apisix/cli/schema.lua
@@ -212,8 +212,20 @@ local config_schema = {
                             type = "string",
                         },
                     }
+                },
+                prefix = {
+                    type = "string",
+                    pattern = [[^/[^/]+$]]
+                },
+                host = {
+                    type = "array",
+                    items = {
+                        type = "string",
+                        pattern = [[^https?://]]
+                    }
                 }
-            }
+            },
+            required = {"prefix", "host"}
         },
         wasm = {
             type = "object",
diff --git a/t/chaos/utils/Dockerfile b/t/chaos/utils/Dockerfile
new file mode 100644
index 000000000..700108283
--- /dev/null
+++ b/t/chaos/utils/Dockerfile
@@ -0,0 +1,75 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+ARG ENABLE_PROXY=false
+
+FROM openresty/openresty:1.19.3.2-alpine-fat AS production-stage
+
+ARG ENABLE_PROXY
+ARG APISIX_PATH
+COPY $APISIX_PATH ./apisix
+RUN set -x \
+    && (test "${ENABLE_PROXY}" != "true" || /bin/sed -i 's,http://dl-cdn.alpinelinux.org,https://mirrors.aliyun.com,g' /etc/apk/repositories) \
+    && apk add --no-cache --virtual .builddeps \
+    automake \
+    autoconf \
+    libtool \
+    pkgconfig \
+    cmake \
+    git \
+    openldap-dev \
+    pcre-dev \
+    && cd apisix \
+    && git config --global url.https://github.com/.insteadOf git://github.com/ \
+    && make deps \
+    && cp -v bin/apisix /usr/bin/ \
+    && mv ../apisix /usr/local/apisix \
+    && apk del .builddeps build-base make unzip
+
+FROM alpine:3.13 AS last-stage
+
+ARG ENABLE_PROXY
+# add runtime for Apache APISIX
+RUN set -x \
+    && (test "${ENABLE_PROXY}" != "true" || /bin/sed -i 's,http://dl-cdn.alpinelinux.org,https://mirrors.aliyun.com,g' /etc/apk/repositories) \
+    && apk add --no-cache \
+        bash \
+        curl \
+        libstdc++ \
+        openldap \
+        pcre \
+        tzdata
+
+WORKDIR /usr/local/apisix
+
+COPY --from=production-stage /usr/local/openresty/ /usr/local/openresty/
+COPY --from=production-stage /usr/local/apisix/ /usr/local/apisix/
+COPY --from=production-stage /usr/bin/apisix /usr/bin/apisix
+
+# forward request and error logs to docker log collector
+RUN mkdir -p logs && touch logs/access.log && touch logs/error.log \
+    && ln -sf /dev/stdout /usr/local/apisix/logs/access.log \
+    && ln -sf /dev/stderr /usr/local/apisix/logs/error.log
+
+ENV PATH=$PATH:/usr/local/openresty/luajit/bin:/usr/local/openresty/nginx/sbin:/usr/local/openresty/bin
+
+EXPOSE 9080 9443
+
+CMD ["sh", "-c", "/usr/bin/apisix init && /usr/bin/apisix init_etcd && /usr/local/openresty/bin/openresty -p /usr/local/apisix -g 'daemon off;'"]
+
+STOPSIGNAL SIGQUIT
+
diff --git a/t/cli/test_validate_config.sh b/t/cli/test_validate_config.sh
index 42cd2be4f..3310f472b 100755
--- a/t/cli/test_validate_config.sh
+++ b/t/cli/test_validate_config.sh
@@ -177,3 +177,30 @@ if ! echo "$out" | grep "missing '127.0.0.1' in the nginx_config.http.real_ip_fr
 fi
 
 echo "passed: check the realip configuration for batch-requests"
+
+echo '
+etcd:
+    host:
+        - 127.0.0.1
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "host" validation failed'; then
+    echo "failed: should check etcd schema during init"
+    exit 1
+fi
+
+echo '
+etcd:
+    prefix: "/apisix/"
+    host:
+        - https://127.0.0.1
+' > conf/config.yaml
+
+out=$(make init 2>&1 || true)
+if ! echo "$out" | grep 'property "prefix" validation failed'; then
+    echo "failed: should check etcd schema during init"
+    exit 1
+fi
+
+echo "passed: check etcd schema during init"


[apisix] 07/07: ci: pass

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit 24eea265e46c9ea7fc13be2cbeb01cd1f1276963
Author: spacewander <sp...@gmail.com>
AuthorDate: Mon Jun 20 10:30:28 2022 +0800

    ci: pass
    
    Signed-off-by: spacewander <sp...@gmail.com>
---
 apisix/core.lua                        | 1 +
 apisix/plugins/grpc-transcode/util.lua | 1 +
 2 files changed, 2 insertions(+)

diff --git a/apisix/core.lua b/apisix/core.lua
index f448f9549..ce368bf32 100644
--- a/apisix/core.lua
+++ b/apisix/core.lua
@@ -52,4 +52,5 @@ return {
     tablepool   = require("tablepool"),
     resolver    = require("apisix.core.resolver"),
     os          = require("apisix.core.os"),
+    math        = require("apisix.core.math"),
 }
diff --git a/apisix/plugins/grpc-transcode/util.lua b/apisix/plugins/grpc-transcode/util.lua
index 68d300203..c8594a8dd 100644
--- a/apisix/plugins/grpc-transcode/util.lua
+++ b/apisix/plugins/grpc-transcode/util.lua
@@ -20,6 +20,7 @@ local json              = core.json
 local pb                = require("pb")
 local ngx               = ngx
 local string            = string
+local ipairs            = ipairs
 local tonumber          = tonumber
 local type              = type
 


[apisix] 06/07: fix: grpc-transcode request support object array (#7231)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit 314d13cff555fe0b8110ad0053f6f44bba99d129
Author: 余茂林 <10...@qq.com>
AuthorDate: Fri Jun 17 11:07:33 2022 +0800

    fix: grpc-transcode request support object array  (#7231)
    
    Co-authored-by: jon.yu <jo...@ambergroup.io>
---
 apisix/plugins/grpc-transcode/util.lua            |  16 ++
 t/grpc_server_example/main.go                     |  25 ++
 t/grpc_server_example/proto/helloworld.pb.go      | 321 +++++++++++++++++-----
 t/grpc_server_example/proto/helloworld.proto      |  15 +
 t/grpc_server_example/proto/helloworld_grpc.pb.go |  40 +++
 t/grpc_server_example/proto/import.pb.go          |  21 +-
 t/grpc_server_example/proto/src.pb.go             |  21 +-
 t/grpc_server_example/proto/src_grpc.pb.go        |   4 +
 t/plugin/grpc-transcode3.t                        | 124 +++++++++
 9 files changed, 518 insertions(+), 69 deletions(-)

diff --git a/apisix/plugins/grpc-transcode/util.lua b/apisix/plugins/grpc-transcode/util.lua
index 2a7bfb648..68d300203 100644
--- a/apisix/plugins/grpc-transcode/util.lua
+++ b/apisix/plugins/grpc-transcode/util.lua
@@ -110,6 +110,22 @@ function _M.map_message(field, default_values, request_table)
         if ty ~= "enum" and field_type:sub(1, 1) == "." then
             if request_table[name] == nil then
                 sub = default_values and default_values[name]
+            elseif core.table.isarray(request_table[name]) then
+                local sub_array = core.table.new(#request_table[name], 0)
+                for i, value in ipairs(request_table[name]) do
+                    local sub_array_obj
+                    if type(value) == "table" then
+                        sub_array_obj, err = _M.map_message(field_type,
+                                default_values and default_values[name], value)
+                        if err then
+                            return nil, err
+                        end
+                    else
+                        sub_array_obj = value
+                    end
+                    sub_array[i] = sub_array_obj
+                end
+                sub = sub_array
             else
                 sub, err = _M.map_message(field_type, default_values and default_values[name],
                                           request_table[name])
diff --git a/t/grpc_server_example/main.go b/t/grpc_server_example/main.go
index 18bda0536..1b533582c 100644
--- a/t/grpc_server_example/main.go
+++ b/t/grpc_server_example/main.go
@@ -172,6 +172,31 @@ func (s *server) SayHelloBidirectionalStream(stream pb.Greeter_SayHelloBidirecti
 	}
 }
 
+// SayMultipleHello implements helloworld.GreeterServer
+func (s *server) SayMultipleHello(ctx context.Context, in *pb.MultipleHelloRequest) (*pb.MultipleHelloReply, error) {
+	log.Printf("Received: %v", in.Name)
+	log.Printf("Enum Gender: %v", in.GetGenders())
+	msg := "Hello " + in.Name
+
+	persons := in.GetPersons()
+	if persons != nil {
+		for _, person := range persons {
+			if person.GetName() != "" {
+				msg += fmt.Sprintf(", name: %v", person.GetName())
+			}
+			if person.GetAge() != 0 {
+				msg += fmt.Sprintf(", age: %v", person.GetAge())
+			}
+		}
+	}
+
+	return &pb.MultipleHelloReply{
+		Message: msg,
+		Items:   in.GetItems(),
+		Genders: in.GetGenders(),
+	}, nil
+}
+
 func (s *server) Run(ctx context.Context, in *pb.Request) (*pb.Response, error) {
 	return &pb.Response{Body: in.User.Name + " " + in.Body}, nil
 }
diff --git a/t/grpc_server_example/proto/helloworld.pb.go b/t/grpc_server_example/proto/helloworld.pb.go
index 9cb209566..71b16a345 100644
--- a/t/grpc_server_example/proto/helloworld.pb.go
+++ b/t/grpc_server_example/proto/helloworld.pb.go
@@ -1,8 +1,10 @@
-// Copyright 2015 gRPC authors.
 //
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
 //
 //     http://www.apache.org/licenses/LICENSE-2.0
 //
@@ -11,11 +13,12 @@
 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 // See the License for the specific language governing permissions and
 // limitations under the License.
+//
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.27.1
-// 	protoc        v3.6.1
+// 	protoc-gen-go v1.25.0-devel
+// 	protoc        v3.12.4
 // source: proto/helloworld.proto
 
 package proto
@@ -374,6 +377,140 @@ func (x *PlusReply) GetResult() int64 {
 	return 0
 }
 
+type MultipleHelloRequest struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Name    string    `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
+	Items   []string  `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"`
+	Genders []Gender  `protobuf:"varint,3,rep,packed,name=genders,proto3,enum=helloworld.Gender" json:"genders,omitempty"`
+	Persons []*Person `protobuf:"bytes,4,rep,name=persons,proto3" json:"persons,omitempty"`
+}
+
+func (x *MultipleHelloRequest) Reset() {
+	*x = MultipleHelloRequest{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_helloworld_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MultipleHelloRequest) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MultipleHelloRequest) ProtoMessage() {}
+
+func (x *MultipleHelloRequest) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_helloworld_proto_msgTypes[5]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use MultipleHelloRequest.ProtoReflect.Descriptor instead.
+func (*MultipleHelloRequest) Descriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *MultipleHelloRequest) GetName() string {
+	if x != nil {
+		return x.Name
+	}
+	return ""
+}
+
+func (x *MultipleHelloRequest) GetItems() []string {
+	if x != nil {
+		return x.Items
+	}
+	return nil
+}
+
+func (x *MultipleHelloRequest) GetGenders() []Gender {
+	if x != nil {
+		return x.Genders
+	}
+	return nil
+}
+
+func (x *MultipleHelloRequest) GetPersons() []*Person {
+	if x != nil {
+		return x.Persons
+	}
+	return nil
+}
+
+type MultipleHelloReply struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Message string   `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
+	Items   []string `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"`
+	Genders []Gender `protobuf:"varint,3,rep,packed,name=genders,proto3,enum=helloworld.Gender" json:"genders,omitempty"`
+}
+
+func (x *MultipleHelloReply) Reset() {
+	*x = MultipleHelloReply{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_proto_helloworld_proto_msgTypes[6]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *MultipleHelloReply) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*MultipleHelloReply) ProtoMessage() {}
+
+func (x *MultipleHelloReply) ProtoReflect() protoreflect.Message {
+	mi := &file_proto_helloworld_proto_msgTypes[6]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use MultipleHelloReply.ProtoReflect.Descriptor instead.
+func (*MultipleHelloReply) Descriptor() ([]byte, []int) {
+	return file_proto_helloworld_proto_rawDescGZIP(), []int{6}
+}
+
+func (x *MultipleHelloReply) GetMessage() string {
+	if x != nil {
+		return x.Message
+	}
+	return ""
+}
+
+func (x *MultipleHelloReply) GetItems() []string {
+	if x != nil {
+		return x.Items
+	}
+	return nil
+}
+
+func (x *MultipleHelloReply) GetGenders() []Gender {
+	if x != nil {
+		return x.Genders
+	}
+	return nil
+}
+
 var File_proto_helloworld_proto protoreflect.FileDescriptor
 
 var file_proto_helloworld_proto_rawDesc = []byte{
@@ -403,40 +540,63 @@ var file_proto_helloworld_proto_rawDesc = []byte{
 	0x0a, 0x01, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x01, 0x62, 0x22, 0x23, 0x0a, 0x09,
 	0x50, 0x6c, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x73,
 	0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c,
-	0x74, 0x2a, 0x40, 0x0a, 0x06, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x0e, 0x47,
-	0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12,
-	0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x4d, 0x41, 0x4c, 0x45, 0x10, 0x01,
-	0x12, 0x11, 0x0a, 0x0d, 0x47, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x46, 0x45, 0x4d, 0x41, 0x4c,
-	0x45, 0x10, 0x02, 0x32, 0xc0, 0x03, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12,
-	0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65,
-	0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
-	0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12,
-	0x38, 0x0a, 0x04, 0x50, 0x6c, 0x75, 0x73, 0x12, 0x17, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
-	0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x50, 0x6c, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
-	0x1a, 0x15, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x50, 0x6c,
-	0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x12, 0x53, 0x61, 0x79,
-	0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x41, 0x66, 0x74, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x12,
-	0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c,
-	0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c,
-	0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c,
-	0x79, 0x22, 0x00, 0x12, 0x4c, 0x0a, 0x14, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53,
-	0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65,
-	0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65,
-	0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
-	0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30,
-	0x01, 0x12, 0x4c, 0x0a, 0x14, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x43, 0x6c, 0x69,
-	0x65, 0x6e, 0x74, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c,
-	0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75,
-	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
-	0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x28, 0x01, 0x12,
-	0x55, 0x0a, 0x1b, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x42, 0x69, 0x64, 0x69, 0x72,
-	0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18,
+	0x74, 0x22, 0x9c, 0x01, 0x0a, 0x14, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x48, 0x65,
+	0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14,
+	0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x69,
+	0x74, 0x65, 0x6d, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x73, 0x18,
+	0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72,
+	0x6c, 0x64, 0x2e, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x07, 0x67, 0x65, 0x6e, 0x64, 0x65,
+	0x72, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
+	0x2e, 0x50, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x52, 0x07, 0x70, 0x65, 0x72, 0x73, 0x6f, 0x6e, 0x73,
+	0x22, 0x72, 0x0a, 0x12, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x48, 0x65, 0x6c, 0x6c,
+	0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65,
+	0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, 0x2c, 0x0a, 0x07, 0x67, 0x65, 0x6e, 0x64, 0x65, 0x72,
+	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0e, 0x32, 0x12, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77,
+	0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x52, 0x07, 0x67, 0x65, 0x6e,
+	0x64, 0x65, 0x72, 0x73, 0x2a, 0x40, 0x0a, 0x06, 0x47, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12, 0x12,
+	0x0a, 0x0e, 0x47, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e,
+	0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, 0x47, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x4d, 0x41, 0x4c,
+	0x45, 0x10, 0x01, 0x12, 0x11, 0x0a, 0x0d, 0x47, 0x45, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x46, 0x45,
+	0x4d, 0x41, 0x4c, 0x45, 0x10, 0x02, 0x32, 0x98, 0x04, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74,
+	0x65, 0x72, 0x12, 0x3e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18,
 	0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c,
 	0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f,
 	0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79,
-	0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
+	0x22, 0x00, 0x12, 0x38, 0x0a, 0x04, 0x50, 0x6c, 0x75, 0x73, 0x12, 0x17, 0x2e, 0x68, 0x65, 0x6c,
+	0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x50, 0x6c, 0x75, 0x73, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
+	0x2e, 0x50, 0x6c, 0x75, 0x73, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x12,
+	0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x41, 0x66, 0x74, 0x65, 0x72, 0x44, 0x65, 0x6c,
+	0x61, 0x79, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e,
+	0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68,
+	0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52,
+	0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x56, 0x0a, 0x10, 0x53, 0x61, 0x79, 0x4d, 0x75, 0x6c,
+	0x74, 0x69, 0x70, 0x6c, 0x65, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x20, 0x2e, 0x68, 0x65, 0x6c,
+	0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65,
+	0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x68,
+	0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70,
+	0x6c, 0x65, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x4c,
+	0x0a, 0x14, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72,
+	0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f,
+	0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
+	0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65,
+	0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x12, 0x4c, 0x0a, 0x14,
+	0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, 0x74,
+	0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c,
+	0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16,
+	0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c,
+	0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x28, 0x01, 0x12, 0x55, 0x0a, 0x1b, 0x53, 0x61,
+	0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x42, 0x69, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x69, 0x6f,
+	0x6e, 0x61, 0x6c, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c,
+	0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75,
+	0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64,
+	0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x28, 0x01, 0x30,
+	0x01, 0x42, 0x09, 0x5a, 0x07, 0x2e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x33,
 }
 
 var (
@@ -452,36 +612,43 @@ func file_proto_helloworld_proto_rawDescGZIP() []byte {
 }
 
 var file_proto_helloworld_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_proto_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
+var file_proto_helloworld_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
 var file_proto_helloworld_proto_goTypes = []interface{}{
-	(Gender)(0),          // 0: helloworld.Gender
-	(*Person)(nil),       // 1: helloworld.Person
-	(*HelloRequest)(nil), // 2: helloworld.HelloRequest
-	(*HelloReply)(nil),   // 3: helloworld.HelloReply
-	(*PlusRequest)(nil),  // 4: helloworld.PlusRequest
-	(*PlusReply)(nil),    // 5: helloworld.PlusReply
+	(Gender)(0),                  // 0: helloworld.Gender
+	(*Person)(nil),               // 1: helloworld.Person
+	(*HelloRequest)(nil),         // 2: helloworld.HelloRequest
+	(*HelloReply)(nil),           // 3: helloworld.HelloReply
+	(*PlusRequest)(nil),          // 4: helloworld.PlusRequest
+	(*PlusReply)(nil),            // 5: helloworld.PlusReply
+	(*MultipleHelloRequest)(nil), // 6: helloworld.MultipleHelloRequest
+	(*MultipleHelloReply)(nil),   // 7: helloworld.MultipleHelloReply
 }
 var file_proto_helloworld_proto_depIdxs = []int32{
-	0, // 0: helloworld.HelloRequest.gender:type_name -> helloworld.Gender
-	1, // 1: helloworld.HelloRequest.person:type_name -> helloworld.Person
-	0, // 2: helloworld.HelloReply.gender:type_name -> helloworld.Gender
-	2, // 3: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
-	4, // 4: helloworld.Greeter.Plus:input_type -> helloworld.PlusRequest
-	2, // 5: helloworld.Greeter.SayHelloAfterDelay:input_type -> helloworld.HelloRequest
-	2, // 6: helloworld.Greeter.SayHelloServerStream:input_type -> helloworld.HelloRequest
-	2, // 7: helloworld.Greeter.SayHelloClientStream:input_type -> helloworld.HelloRequest
-	2, // 8: helloworld.Greeter.SayHelloBidirectionalStream:input_type -> helloworld.HelloRequest
-	3, // 9: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
-	5, // 10: helloworld.Greeter.Plus:output_type -> helloworld.PlusReply
-	3, // 11: helloworld.Greeter.SayHelloAfterDelay:output_type -> helloworld.HelloReply
-	3, // 12: helloworld.Greeter.SayHelloServerStream:output_type -> helloworld.HelloReply
-	3, // 13: helloworld.Greeter.SayHelloClientStream:output_type -> helloworld.HelloReply
-	3, // 14: helloworld.Greeter.SayHelloBidirectionalStream:output_type -> helloworld.HelloReply
-	9, // [9:15] is the sub-list for method output_type
-	3, // [3:9] is the sub-list for method input_type
-	3, // [3:3] is the sub-list for extension type_name
-	3, // [3:3] is the sub-list for extension extendee
-	0, // [0:3] is the sub-list for field type_name
+	0,  // 0: helloworld.HelloRequest.gender:type_name -> helloworld.Gender
+	1,  // 1: helloworld.HelloRequest.person:type_name -> helloworld.Person
+	0,  // 2: helloworld.HelloReply.gender:type_name -> helloworld.Gender
+	0,  // 3: helloworld.MultipleHelloRequest.genders:type_name -> helloworld.Gender
+	1,  // 4: helloworld.MultipleHelloRequest.persons:type_name -> helloworld.Person
+	0,  // 5: helloworld.MultipleHelloReply.genders:type_name -> helloworld.Gender
+	2,  // 6: helloworld.Greeter.SayHello:input_type -> helloworld.HelloRequest
+	4,  // 7: helloworld.Greeter.Plus:input_type -> helloworld.PlusRequest
+	2,  // 8: helloworld.Greeter.SayHelloAfterDelay:input_type -> helloworld.HelloRequest
+	6,  // 9: helloworld.Greeter.SayMultipleHello:input_type -> helloworld.MultipleHelloRequest
+	2,  // 10: helloworld.Greeter.SayHelloServerStream:input_type -> helloworld.HelloRequest
+	2,  // 11: helloworld.Greeter.SayHelloClientStream:input_type -> helloworld.HelloRequest
+	2,  // 12: helloworld.Greeter.SayHelloBidirectionalStream:input_type -> helloworld.HelloRequest
+	3,  // 13: helloworld.Greeter.SayHello:output_type -> helloworld.HelloReply
+	5,  // 14: helloworld.Greeter.Plus:output_type -> helloworld.PlusReply
+	3,  // 15: helloworld.Greeter.SayHelloAfterDelay:output_type -> helloworld.HelloReply
+	7,  // 16: helloworld.Greeter.SayMultipleHello:output_type -> helloworld.MultipleHelloReply
+	3,  // 17: helloworld.Greeter.SayHelloServerStream:output_type -> helloworld.HelloReply
+	3,  // 18: helloworld.Greeter.SayHelloClientStream:output_type -> helloworld.HelloReply
+	3,  // 19: helloworld.Greeter.SayHelloBidirectionalStream:output_type -> helloworld.HelloReply
+	13, // [13:20] is the sub-list for method output_type
+	6,  // [6:13] is the sub-list for method input_type
+	6,  // [6:6] is the sub-list for extension type_name
+	6,  // [6:6] is the sub-list for extension extendee
+	0,  // [0:6] is the sub-list for field type_name
 }
 
 func init() { file_proto_helloworld_proto_init() }
@@ -550,6 +717,30 @@ func file_proto_helloworld_proto_init() {
 				return nil
 			}
 		}
+		file_proto_helloworld_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MultipleHelloRequest); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_proto_helloworld_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MultipleHelloReply); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
@@ -557,7 +748,7 @@ func file_proto_helloworld_proto_init() {
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_proto_helloworld_proto_rawDesc,
 			NumEnums:      1,
-			NumMessages:   5,
+			NumMessages:   7,
 			NumExtensions: 0,
 			NumServices:   1,
 		},
diff --git a/t/grpc_server_example/proto/helloworld.proto b/t/grpc_server_example/proto/helloworld.proto
index 2e18a467c..db056fade 100644
--- a/t/grpc_server_example/proto/helloworld.proto
+++ b/t/grpc_server_example/proto/helloworld.proto
@@ -25,6 +25,7 @@ service Greeter {
   rpc SayHello (HelloRequest) returns (HelloReply) {}
   rpc Plus (PlusRequest) returns (PlusReply) {}
   rpc SayHelloAfterDelay (HelloRequest) returns (HelloReply) {}
+  rpc SayMultipleHello(MultipleHelloRequest) returns (MultipleHelloReply) {}
 
   // Server side streaming.
   rpc SayHelloServerStream (HelloRequest) returns (stream HelloReply) {}
@@ -34,6 +35,7 @@ service Greeter {
 
   // Bidirectional streaming.
   rpc SayHelloBidirectionalStream (stream HelloRequest) returns (stream HelloReply) {}
+
 }
 
 enum Gender {
@@ -68,3 +70,16 @@ message PlusRequest {
 message PlusReply {
   int64 result = 1;
 }
+
+message MultipleHelloRequest {
+  string name = 1;
+  repeated string items = 2;
+  repeated Gender genders = 3;
+  repeated Person persons = 4;
+}
+
+message MultipleHelloReply{
+  string message = 1;
+  repeated string items = 2;
+  repeated Gender genders = 3;
+}
diff --git a/t/grpc_server_example/proto/helloworld_grpc.pb.go b/t/grpc_server_example/proto/helloworld_grpc.pb.go
index 7d6d8ef8b..c0527d754 100644
--- a/t/grpc_server_example/proto/helloworld_grpc.pb.go
+++ b/t/grpc_server_example/proto/helloworld_grpc.pb.go
@@ -1,4 +1,8 @@
 // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc             v3.12.4
+// source: proto/helloworld.proto
 
 package proto
 
@@ -22,6 +26,7 @@ type GreeterClient interface {
 	SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
 	Plus(ctx context.Context, in *PlusRequest, opts ...grpc.CallOption) (*PlusReply, error)
 	SayHelloAfterDelay(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
+	SayMultipleHello(ctx context.Context, in *MultipleHelloRequest, opts ...grpc.CallOption) (*MultipleHelloReply, error)
 	// Server side streaming.
 	SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (Greeter_SayHelloServerStreamClient, error)
 	// Client side streaming.
@@ -65,6 +70,15 @@ func (c *greeterClient) SayHelloAfterDelay(ctx context.Context, in *HelloRequest
 	return out, nil
 }
 
+func (c *greeterClient) SayMultipleHello(ctx context.Context, in *MultipleHelloRequest, opts ...grpc.CallOption) (*MultipleHelloReply, error) {
+	out := new(MultipleHelloReply)
+	err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayMultipleHello", in, out, opts...)
+	if err != nil {
+		return nil, err
+	}
+	return out, nil
+}
+
 func (c *greeterClient) SayHelloServerStream(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (Greeter_SayHelloServerStreamClient, error) {
 	stream, err := c.cc.NewStream(ctx, &Greeter_ServiceDesc.Streams[0], "/helloworld.Greeter/SayHelloServerStream", opts...)
 	if err != nil {
@@ -170,6 +184,7 @@ type GreeterServer interface {
 	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
 	Plus(context.Context, *PlusRequest) (*PlusReply, error)
 	SayHelloAfterDelay(context.Context, *HelloRequest) (*HelloReply, error)
+	SayMultipleHello(context.Context, *MultipleHelloRequest) (*MultipleHelloReply, error)
 	// Server side streaming.
 	SayHelloServerStream(*HelloRequest, Greeter_SayHelloServerStreamServer) error
 	// Client side streaming.
@@ -192,6 +207,9 @@ func (UnimplementedGreeterServer) Plus(context.Context, *PlusRequest) (*PlusRepl
 func (UnimplementedGreeterServer) SayHelloAfterDelay(context.Context, *HelloRequest) (*HelloReply, error) {
 	return nil, status.Errorf(codes.Unimplemented, "method SayHelloAfterDelay not implemented")
 }
+func (UnimplementedGreeterServer) SayMultipleHello(context.Context, *MultipleHelloRequest) (*MultipleHelloReply, error) {
+	return nil, status.Errorf(codes.Unimplemented, "method SayMultipleHello not implemented")
+}
 func (UnimplementedGreeterServer) SayHelloServerStream(*HelloRequest, Greeter_SayHelloServerStreamServer) error {
 	return status.Errorf(codes.Unimplemented, "method SayHelloServerStream not implemented")
 }
@@ -268,6 +286,24 @@ func _Greeter_SayHelloAfterDelay_Handler(srv interface{}, ctx context.Context, d
 	return interceptor(ctx, in, info, handler)
 }
 
+func _Greeter_SayMultipleHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+	in := new(MultipleHelloRequest)
+	if err := dec(in); err != nil {
+		return nil, err
+	}
+	if interceptor == nil {
+		return srv.(GreeterServer).SayMultipleHello(ctx, in)
+	}
+	info := &grpc.UnaryServerInfo{
+		Server:     srv,
+		FullMethod: "/helloworld.Greeter/SayMultipleHello",
+	}
+	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+		return srv.(GreeterServer).SayMultipleHello(ctx, req.(*MultipleHelloRequest))
+	}
+	return interceptor(ctx, in, info, handler)
+}
+
 func _Greeter_SayHelloServerStream_Handler(srv interface{}, stream grpc.ServerStream) error {
 	m := new(HelloRequest)
 	if err := stream.RecvMsg(m); err != nil {
@@ -360,6 +396,10 @@ var Greeter_ServiceDesc = grpc.ServiceDesc{
 			MethodName: "SayHelloAfterDelay",
 			Handler:    _Greeter_SayHelloAfterDelay_Handler,
 		},
+		{
+			MethodName: "SayMultipleHello",
+			Handler:    _Greeter_SayMultipleHello_Handler,
+		},
 	},
 	Streams: []grpc.StreamDesc{
 		{
diff --git a/t/grpc_server_example/proto/import.pb.go b/t/grpc_server_example/proto/import.pb.go
index 28fabf3f3..a5575fdbd 100644
--- a/t/grpc_server_example/proto/import.pb.go
+++ b/t/grpc_server_example/proto/import.pb.go
@@ -1,7 +1,24 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.27.1
-// 	protoc        v3.6.1
+// 	protoc-gen-go v1.25.0-devel
+// 	protoc        v3.12.4
 // source: proto/import.proto
 
 package proto
diff --git a/t/grpc_server_example/proto/src.pb.go b/t/grpc_server_example/proto/src.pb.go
index 8e6a32ae3..74fa884d1 100644
--- a/t/grpc_server_example/proto/src.pb.go
+++ b/t/grpc_server_example/proto/src.pb.go
@@ -1,7 +1,24 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.27.1
-// 	protoc        v3.6.1
+// 	protoc-gen-go v1.25.0-devel
+// 	protoc        v3.12.4
 // source: proto/src.proto
 
 package proto
diff --git a/t/grpc_server_example/proto/src_grpc.pb.go b/t/grpc_server_example/proto/src_grpc.pb.go
index 01fe1502d..d4015ed99 100644
--- a/t/grpc_server_example/proto/src_grpc.pb.go
+++ b/t/grpc_server_example/proto/src_grpc.pb.go
@@ -1,4 +1,8 @@
 // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.2.0
+// - protoc             v3.12.4
+// source: proto/src.proto
 
 package proto
 
diff --git a/t/plugin/grpc-transcode3.t b/t/plugin/grpc-transcode3.t
new file mode 100644
index 000000000..a027a84bd
--- /dev/null
+++ b/t/plugin/grpc-transcode3.t
@@ -0,0 +1,124 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+no_long_string();
+no_shuffle();
+no_root_location();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: set rule
+--- config
+    location /t {
+       content_by_lua_block {
+          local http = require "resty.http"
+          local t = require("lib.test_admin").test
+          local code, body = t('/apisix/admin/proto/1',
+                ngx.HTTP_PUT,
+                [[{
+                   "content" : "syntax = \"proto3\";
+                    package helloworld;
+                    service Greeter {
+                         rpc SayMultipleHello(MultipleHelloRequest) returns (MultipleHelloReply) {}
+                     }
+
+                     enum Gender {
+                           GENDER_UNKNOWN = 0;
+                           GENDER_MALE = 1;
+                           GENDER_FEMALE = 2;
+                      }
+
+                       message Person {
+                           string name = 1;
+                           int32 age = 2;
+                       }
+
+                      message MultipleHelloRequest {
+                          string name = 1;
+                          repeated string items = 2;
+                          repeated Gender genders = 3;
+                          repeated Person persons = 4;
+                    }
+
+                    message MultipleHelloReply{
+                          string message = 1;
+                    }"
+                }]]
+              )
+
+             if code >= 300 then
+                 ngx.say(body)
+                 return
+              end
+
+             local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                   "methods": ["POST"],
+                    "uri": "/grpctest",
+                    "plugins": {
+                        "grpc-transcode": {
+                            "proto_id": "1",
+                            "service": "helloworld.Greeter",
+                            "method": "SayMultipleHello"
+                       }
+                    },
+                    "upstream": {
+                        "scheme": "grpc",
+                            "type": "roundrobin",
+                            "nodes": {
+                            "127.0.0.1:50051": 1
+                        }
+                    }
+                }]]
+             )
+
+            if code >= 300 then
+                ngx.say(body)
+                return
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 2: hit route
+--- request
+POST /grpctest
+{"name":"world","persons":[{"name":"Joe","age":1},{"name":"Jake","age":2}]}
+--- more_headers
+Content-Type: application/json
+--- response_body chomp
+{"message":"Hello world, name: Joe, age: 1, name: Jake, age: 2"}


[apisix] 02/07: fix(proxy-cache): bypass when method mismatch cache_method (#7111)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit 375eb8a150976f0b6150af9d5a839038d4252a76
Author: soulbird <zh...@outlook.com>
AuthorDate: Tue May 24 15:01:27 2022 +0800

    fix(proxy-cache): bypass when method mismatch cache_method (#7111)
    
    Co-authored-by: soulbird <zh...@gmail.com>
---
 apisix/plugins/proxy-cache/disk_handler.lua |  5 +++++
 t/plugin/proxy-cache/disk.t                 | 15 ++++++++-------
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/apisix/plugins/proxy-cache/disk_handler.lua b/apisix/plugins/proxy-cache/disk_handler.lua
index bf131b158..70d3532ac 100644
--- a/apisix/plugins/proxy-cache/disk_handler.lua
+++ b/apisix/plugins/proxy-cache/disk_handler.lua
@@ -55,6 +55,11 @@ function _M.access(conf, ctx)
         ctx.var.upstream_cache_bypass = value
         core.log.info("proxy-cache cache bypass value:", value)
     end
+
+    if not util.match_method(conf, ctx) then
+        ctx.var.upstream_cache_bypass = "1"
+        core.log.info("proxy-cache cache bypass method: ", ctx.var.request_method)
+    end
 end
 
 
diff --git a/t/plugin/proxy-cache/disk.t b/t/plugin/proxy-cache/disk.t
index a1b09d184..954f967f7 100644
--- a/t/plugin/proxy-cache/disk.t
+++ b/t/plugin/proxy-cache/disk.t
@@ -451,21 +451,22 @@ Apisix-Cache-Status: MISS
 
 
 
-=== TEST 17: hit route (HEAD method)
+=== TEST 17: hit route (will be cached)
 --- request
-HEAD /hello-world
---- error_code: 200
+GET /hello
+--- response_body chop
+hello world!
 --- response_headers
-Apisix-Cache-Status: MISS
+Apisix-Cache-Status: HIT
 
 
 
-=== TEST 18: hit route (HEAD method there's no cache)
+=== TEST 18: hit route (HEAD method mismatch cache_method)
 --- request
-HEAD /hello-world
+HEAD /hello
 --- error_code: 200
 --- response_headers
-Apisix-Cache-Status: MISS
+Apisix-Cache-Status: BYPASS
 
 
 


[apisix] 04/07: fix(proxy-cache): allow nil ctx vars in cache key (#7168)

Posted by sp...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch release/2.13
in repository https://gitbox.apache.org/repos/asf/apisix.git

commit 82c233e2361c4755fb45ad6a35546d76b212a329
Author: PiteChen <49...@users.noreply.github.com>
AuthorDate: Wed Jun 1 19:29:55 2022 +0800

    fix(proxy-cache): allow nil ctx vars in cache key (#7168)
    
    Signed-off-by: spacewander <sp...@gmail.com>
---
 apisix/plugins/proxy-cache/util.lua   |  2 +-
 docs/zh/latest/plugins/proxy-cache.md |  2 +-
 t/plugin/proxy-cache/disk.t           | 50 +++++++++++++++++++++++++++++++++++
 3 files changed, 52 insertions(+), 2 deletions(-)

diff --git a/apisix/plugins/proxy-cache/util.lua b/apisix/plugins/proxy-cache/util.lua
index f20d2fc21..26c6e814b 100644
--- a/apisix/plugins/proxy-cache/util.lua
+++ b/apisix/plugins/proxy-cache/util.lua
@@ -37,7 +37,7 @@ function _M.generate_complex_value(data, ctx)
         core.log.info("proxy-cache complex value index-", i, ": ", value)
 
         if string.byte(value, 1, 1) == string.byte('$') then
-            tmp[i] = ctx.var[string.sub(value, 2)]
+            tmp[i] = ctx.var[string.sub(value, 2)] or ""
         else
             tmp[i] = value
         end
diff --git a/docs/zh/latest/plugins/proxy-cache.md b/docs/zh/latest/plugins/proxy-cache.md
index 4cdf93951..e3065134c 100644
--- a/docs/zh/latest/plugins/proxy-cache.md
+++ b/docs/zh/latest/plugins/proxy-cache.md
@@ -45,7 +45,7 @@ title: proxy-cache
 | no_cache           | array[string]  | 可选   |                           |                                                                                 | 是否缓存数据,可以使用变量,需要注意当此参数的值不为空或非'0'时将不会缓存数据                                                      |
 | cache_ttl          | integer        | 可选   | 300 秒                    |                                                                                 | 当选项 cache_control 未开启或开启以后服务端没有返回缓存控制头时,提供的默认缓存时间    |
 
-注:变量以$开头,也可以使用变量和字符串的结合,但是需要以数组的形式分开写,最终变量被解析后会和字符串拼接在一起。
+注:变量以$开头,不存在时等价于空字符串。也可以使用变量和字符串的结合,但是需要以数组的形式分开写,最终变量被解析后会和字符串拼接在一起。
 
 在 `conf/config.yaml` 文件中的配置示例:
 
diff --git a/t/plugin/proxy-cache/disk.t b/t/plugin/proxy-cache/disk.t
index 954f967f7..98e6e1d8b 100644
--- a/t/plugin/proxy-cache/disk.t
+++ b/t/plugin/proxy-cache/disk.t
@@ -707,3 +707,53 @@ GET /t
 --- error_code: 400
 --- response_body eval
 qr/failed to check the configuration of plugin proxy-cache err/
+
+
+
+=== TEST 28: nil vars for cache_key
+--- config
+       location /t {
+           content_by_lua_block {
+               local t = require("lib.test_admin").test
+               local code, body = t('/apisix/admin/routes/1',
+                    ngx.HTTP_PUT,
+                    [[{
+                        "plugins": {
+                            "proxy-cache": {
+                               "cache_key": ["$arg_foo", "$arg_bar", "$arg_baz"],
+                               "cache_zone": "disk_cache_one",
+                               "cache_bypass": ["$arg_bypass"],
+                               "cache_method": ["GET"],
+                               "cache_http_status": [200],
+                               "hide_cache_headers": true,
+                               "no_cache": ["$arg_no_cache"]
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1986": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/hello"
+                   }]]
+                   )
+
+               if code >= 300 then
+                   ngx.status = code
+               end
+               ngx.say(body)
+           }
+       }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 29: hit route with nil vars in cache_key
+--- request
+GET /hello?bar=a
+--- response_body chop
+hello world!