diff --git a/apisix/plugins/grpc-web.lua b/apisix/plugins/grpc-web.lua index 18465063b343..a528985d9a8f 100644 --- a/apisix/plugins/grpc-web.lua +++ b/apisix/plugins/grpc-web.lua @@ -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 ./*", + 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) @@ -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 @@ -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 diff --git a/t/plugin/grpc-web.t b/t/plugin/grpc-web.t index 7340add60fce..b9c35c81182a 100644 --- a/t/plugin/grpc-web.t +++ b/t/plugin/grpc-web.t @@ -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: * @@ -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