Skip to content

Commit

Permalink
fix(grpc-web): missing trailers issue (#10851)
Browse files Browse the repository at this point in the history
  • Loading branch information
sheharyaar authored Jan 26, 2024
1 parent 946a17e commit 0bea3db
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 3 deletions.
49 changes: 48 additions & 1 deletion apisix/plugins/grpc-web.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand All @@ -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
37 changes: 35 additions & 2 deletions t/plugin/grpc-web.t
Original file line number Diff line number Diff line change
Expand Up @@ -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: {} }
Expand Down Expand Up @@ -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:/
6 changes: 6 additions & 0 deletions t/plugin/grpc-web/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ class gRPCWebClient {
return
}
console.log(JSON.stringify(response.toObject()));
}).on("status", function (status) {
console.log("Status:", status);
});
}

Expand All @@ -62,6 +64,10 @@ class gRPCWebClient {
stream.on('end', function(end) {
stream.cancel();
});

stream.on("status", function (status) {
console.log("Status:", status);
});
}
}

Expand Down
Binary file added t/plugin/grpc-web/req.bin
Binary file not shown.

0 comments on commit 0bea3db

Please sign in to comment.