Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: grpc-web plugin supports trailers as part of response body #10613

Closed
wants to merge 10 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 65 additions & 16 deletions apisix/plugins/grpc-web.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,45 +13,64 @@
-- 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.

local ngx = ngx
local ngx_arg = ngx.arg
local core = require("apisix.core")
local req_set_uri = ngx.req.set_uri
local ngx = ngx
local ngx_arg = ngx.arg
local core = require("apisix.core")
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 decode_base64 = ngx.decode_base64
local encode_base64 = ngx.encode_base64

local ALLOW_METHOD_OPTIONS = "OPTIONS"
local ALLOW_METHOD_POST = "POST"
local CONTENT_ENCODING_BASE64 = "base64"
local CONTENT_ENCODING_BINARY = "binary"
local DEFAULT_CORS_ALLOW_ORIGIN = "*"
local DEFAULT_CORS_ALLOW_METHODS = ALLOW_METHOD_POST
local DEFAULT_CORS_ALLOW_HEADERS = "content-type,x-grpc-web,x-user-agent"
local DEFAULT_CORS_ALLOW_METHODS = "POST, OPTIONS"
local DEFAULT_CORS_ALLOW_HEADERS = "content-type,x-grpc-web,x-user-agent,grpc-accept-encoding"
local DEFAULT_PROXY_CONTENT_TYPE = "application/grpc"
local DEFAULT_CORS_ALLOW_EXPOSE_HEADERS = "grpc-status,grpc-message"

local GRPC_WEB_TRAILER_FRAME_HEADER = string.char(128, 0, 0, 0)
local GRPC_WEB_REQ_TRAILERS_DEFAULT = {
["grpc-status"] = "0",
["grpc-message"] = "OK"
}

local CRLF = "\r\n"

local plugin_name = "grpc-web"

local schema = {
type = "object",
properties = {},
properties = {
strip_path = {
description = "include prefix matched by path pattern in the path used for " ..
"upstream call, appropriate for prefix matching path " ..
"patterns with the format <package>.<service>/*",
type = "boolean",
default = false
},
enable_in_body_trailers_on_success = {
description = "append standard grpc-web in-body trailers frame in response body",
type = "boolean",
default = false
}
}
}

local grpc_web_content_encoding = {
["application/grpc-web"] = CONTENT_ENCODING_BINARY,
["application/grpc-web-text"] = CONTENT_ENCODING_BASE64,
["application/grpc-web+proto"] = CONTENT_ENCODING_BINARY,
["application/grpc-web-text+proto"] = CONTENT_ENCODING_BASE64,
["application/grpc-web-text+proto"] = CONTENT_ENCODING_BASE64
}

local _M = {
version = 0.1,
priority = 505,
name = plugin_name,
schema = schema,
schema = schema
}

function _M.check_schema(conf)
Expand Down Expand Up @@ -87,11 +106,18 @@ 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

local path = ctx.curr_req_matched[":ext"]
local path
if conf.strip_path and ctx.curr_req_matched._path:byte(-1) == core.string.byte("*") and
ctx.curr_req_matched[":ext"]:byte(1) ~= core.string.byte("/") then
path = string.sub(ctx.curr_req_matched._path, 1, -2) .. ctx.curr_req_matched[":ext"]
else
path = ctx.curr_req_matched[":ext"]
end

if path:byte(1) ~= core.string.byte("/") then
path = "/" .. path
end
Expand Down Expand Up @@ -129,24 +155,47 @@ function _M.header_filter(conf, ctx)
if not ctx.cors_allow_origins then
core.response.set_header("Access-Control-Allow-Origin", DEFAULT_CORS_ALLOW_ORIGIN)
end
core.response.set_header("Access-Control-Expose-Headers", DEFAULT_CORS_ALLOW_EXPOSE_HEADERS)
core.response.set_header("Content-Type", ctx.grpc_web_mime)
core.response.clear_header_as_body_modified()
end

function _M.body_filter(conf, ctx)
-- If the MIME extension type description of the gRPC-Web standard is not obtained,
-- indicating that the request is not based on the gRPC Web specification,
-- the processing of the request body will be ignored
-- If response body is not empty, in-body trailers required by gRPC-Web
-- are added to the end of response body
-- https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md
-- https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md#cors-support
if not ctx.grpc_web_mime then
return
end

if conf.enable_in_body_trailers_on_success then
local response = core.response.hold_body_chunk(ctx)
if response and string.len(response) ~= 0 then
local headers = ngx.resp.get_headers()
local trailers = " "
for trailer_key, trailer_default_value in pairs(GRPC_WEB_REQ_TRAILERS_DEFAULT) do
local trailer_value = headers[trailer_key]

if trailer_value == nil then
trailer_value = trailer_default_value
end

trailers = trailers .. trailer_key .. ":" .. trailer_value .. CRLF
end

response = response .. GRPC_WEB_TRAILER_FRAME_HEADER .. trailers
ngx_arg[1] = response
end
end

if ctx.grpc_web_encoding == CONTENT_ENCODING_BASE64 then
local chunk = ngx_arg[1]
chunk = encode_base64(chunk)
ngx_arg[1] = chunk
end
end

return _M
18 changes: 16 additions & 2 deletions t/plugin/grpc-web.t
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ node ./t/plugin/grpc-web/client.js TEXT STREAM
OPTIONS /grpc/web/a6.RouteService/GetRoute
--- error_code: 204
--- response_headers
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: content-type,x-grpc-web,x-user-agent
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: content-type,x-grpc-web,x-user-agent,grpc-accept-encoding
Access-Control-Allow-Origin: *


Expand Down Expand Up @@ -227,3 +227,17 @@ Content-Type: application/grpc-web
--- response_headers
Access-Control-Allow-Origin: http://test.com
Content-Type: application/grpc-web



=== TEST 11: grpc-web access control expose headers for non grpc servers that don't implement grpc-web
--- 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
Content-Type: application/grpc-web
Access-Control-Expose-Headers: grpc-status,grpc-message
Loading