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

monkeydluffy pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 0bea3dbf6 fix(grpc-web): missing trailers issue (#10851)
0bea3dbf6 is described below

commit 0bea3dbf6b1b868ba35acdb2fd0205f2a352479d
Author: Shehar Yaar <[email protected]>
AuthorDate: Fri Jan 26 13:54:34 2024 +0530

    fix(grpc-web): missing trailers issue (#10851)
---
 apisix/plugins/grpc-web.lua |  49 +++++++++++++++++++++++++++++++++++++++++++-
 t/plugin/grpc-web.t         |  37 +++++++++++++++++++++++++++++++--
 t/plugin/grpc-web/client.js |   6 ++++++
 t/plugin/grpc-web/req.bin   | Bin 0 -> 14 bytes
 4 files changed, 89 insertions(+), 3 deletions(-)

diff --git a/apisix/plugins/grpc-web.lua b/apisix/plugins/grpc-web.lua
index 18465063b..5771604e7 100644
--- a/apisix/plugins/grpc-web.lua
+++ b/apisix/plugins/grpc-web.lua
@@ -21,6 +21,8 @@ local req_set_uri       = ngx.req.set_uri
 local req_set_body_data = ngx.req.set_body_data
 local decode_base64     = ngx.decode_base64
 local encode_base64     = ngx.encode_base64
+local bit               = require("bit")
+local string            = string
 
 
 local ALLOW_METHOD_OPTIONS = "OPTIONS"
@@ -87,7 +89,7 @@ function _M.access(conf, ctx)
     -- set grpc path
     if not (ctx.curr_req_matched and ctx.curr_req_matched[":ext"]) then
         core.log.error("routing configuration error, grpc-web plugin only 
supports ",
-                       "`prefix matching` pattern routing")
+            "`prefix matching` pattern routing")
         return 400
     end
 
@@ -130,6 +132,7 @@ function _M.header_filter(conf, ctx)
         core.response.set_header("Access-Control-Allow-Origin", 
DEFAULT_CORS_ALLOW_ORIGIN)
     end
     core.response.set_header("Content-Type", ctx.grpc_web_mime)
+    core.response.set_header("Access-Control-Expose-Headers", 
"grpc-message,grpc-status")
 end
 
 function _M.body_filter(conf, ctx)
@@ -147,6 +150,50 @@ function _M.body_filter(conf, ctx)
         chunk = encode_base64(chunk)
         ngx_arg[1] = chunk
     end
+
+    --[[
+    upstream_trailer_* available since NGINX version 1.13.10 :
+    
https://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_trailer_
+
+    grpc-web trailer format reference:
+    envoyproxy/envoy/source/extensions/filters/http/grpc_web/grpc_web_filter.cc
+
+    Format for grpc-web trailer
+        1 byte: 0x80
+        4 bytes: length of the trailer
+        n bytes: trailer
+
+    --]]
+    local status = ctx.var.upstream_trailer_grpc_status
+    local message = ctx.var.upstream_trailer_grpc_message
+    if status ~= "" and status ~= nil then
+        local status_str = "grpc-status:" .. status
+        local status_msg = "grpc-message:" .. ( message or "")
+        local grpc_web_trailer = status_str .. "\r\n" .. status_msg .. "\r\n"
+        local len = #grpc_web_trailer
+
+        -- 1 byte: 0x80
+        local trailer_buf = string.char(0x80)
+        -- 4 bytes: length of the trailer
+        trailer_buf = trailer_buf .. string.char(
+            bit.band(bit.rshift(len, 24), 0xff),
+            bit.band(bit.rshift(len, 16), 0xff),
+            bit.band(bit.rshift(len, 8), 0xff),
+            bit.band(len, 0xff)
+        )
+        -- n bytes: trailer
+        trailer_buf = trailer_buf .. grpc_web_trailer
+
+        if ctx.grpc_web_encoding == CONTENT_ENCODING_BINARY then
+            ngx_arg[1] = ngx_arg[1] .. trailer_buf
+        else
+            ngx_arg[1] = ngx_arg[1] .. encode_base64(trailer_buf)
+        end
+
+        -- clear trailer
+        ctx.var.upstream_trailer_grpc_status = nil
+        ctx.var.upstream_trailer_grpc_message = nil
+    end
 end
 
 return _M
diff --git a/t/plugin/grpc-web.t b/t/plugin/grpc-web.t
index 7340add60..7069a8c2c 100644
--- a/t/plugin/grpc-web.t
+++ b/t/plugin/grpc-web.t
@@ -68,25 +68,33 @@ passed
 
 
 
-=== TEST 2: Proxy unary request using APISIX gRPC-Web plugin
+=== TEST 2: Proxy unary request using APISIX with trailers gRPC-Web plugin
 --- exec
 node ./t/plugin/grpc-web/client.js BIN UNARY
 node ./t/plugin/grpc-web/client.js TEXT UNARY
 --- response_body
+Status: { code: 0, details: '', metadata: {} }
+Status: { code: 0, details: '', metadata: {} }
 {"name":"hello","path":"/hello"}
+Status: { code: 0, details: '', metadata: {} }
+Status: { code: 0, details: '', metadata: {} }
 {"name":"hello","path":"/hello"}
 
 
 
-=== TEST 3: Proxy server-side streaming request using APISIX gRPC-Web plugin
+=== TEST 3: Proxy server-side streaming request using APISIX with trailers 
gRPC-Web plugin
 --- exec
 node ./t/plugin/grpc-web/client.js BIN STREAM
 node ./t/plugin/grpc-web/client.js TEXT STREAM
 --- response_body
 {"name":"hello","path":"/hello"}
 {"name":"world","path":"/world"}
+Status: { code: 0, details: '', metadata: {} }
+Status: { code: 0, details: '', metadata: {} }
 {"name":"hello","path":"/hello"}
 {"name":"world","path":"/world"}
+Status: { code: 0, details: '', metadata: {} }
+Status: { code: 0, details: '', metadata: {} }
 
 
 
@@ -227,3 +235,28 @@ Content-Type: application/grpc-web
 --- response_headers
 Access-Control-Allow-Origin: http://test.com
 Content-Type: application/grpc-web
+
+
+
+=== TEST 11: check for Access-Control-Expose-Headers header in response
+--- request
+POST /grpc/web/a6.RouteService/GetRoute
+{}
+--- more_headers
+Origin: http://test.com
+Content-Type: application/grpc-web
+--- response_headers
+Access-Control-Allow-Origin: http://test.com
+Access-Control-Expose-Headers: grpc-message,grpc-status
+Content-Type: application/grpc-web
+
+
+
+=== TEST 12: verify trailers in response
+--- exec
+curl -iv --location 'http://127.0.0.1:1984/grpc/web/a6.RouteService/GetRoute' \
+--header 'Content-Type: application/grpc-web+proto' \
+--header 'X-Grpc-Web: 1' \
+--data-binary '@./t/plugin/grpc-web/req.bin'
+--- response_body eval
+qr/grpc-status:0\x0d\x0agrpc-message:/
diff --git a/t/plugin/grpc-web/client.js b/t/plugin/grpc-web/client.js
index 3f10a80bc..9ec044124 100644
--- a/t/plugin/grpc-web/client.js
+++ b/t/plugin/grpc-web/client.js
@@ -49,6 +49,8 @@ class gRPCWebClient {
                 return
             }
             console.log(JSON.stringify(response.toObject()));
+        }).on("status", function (status) {
+            console.log("Status:", status);
         });
     }
 
@@ -62,6 +64,10 @@ class gRPCWebClient {
         stream.on('end', function(end) {
             stream.cancel();
         });
+
+        stream.on("status", function (status) {
+            console.log("Status:", status);
+        });
     }
 }
 
diff --git a/t/plugin/grpc-web/req.bin b/t/plugin/grpc-web/req.bin
new file mode 100644
index 000000000..908c829c6
Binary files /dev/null and b/t/plugin/grpc-web/req.bin differ

Reply via email to