diff --git a/changelog/unreleased/kong/grpc-web-large-req-error.yml b/changelog/unreleased/kong/grpc-web-large-req-error.yml new file mode 100644 index 00000000000..b91b5d429ad --- /dev/null +++ b/changelog/unreleased/kong/grpc-web-large-req-error.yml @@ -0,0 +1,3 @@ +message: "**grpc-web**: fix an issue where large requests would result in an error" +type: bugfix +scope: Plugin diff --git a/kong/plugins/grpc-web/handler.lua b/kong/plugins/grpc-web/handler.lua index 8159fb5c196..767aa9c478a 100644 --- a/kong/plugins/grpc-web/handler.lua +++ b/kong/plugins/grpc-web/handler.lua @@ -7,6 +7,7 @@ local ngx = ngx local kong = kong local string_format = string.format +local io_open = io.open local ngx_arg = ngx.arg local ngx_var = ngx.var @@ -15,10 +16,12 @@ local kong_request_get_path = kong.request.get_path local kong_request_get_header = kong.request.get_header local kong_request_get_method = kong.request.get_method local kong_request_get_raw_body = kong.request.get_raw_body +local ngx_req_get_body_file = ngx.req.get_body_file local kong_response_exit = kong.response.exit local kong_response_set_header = kong.response.set_header local kong_service_request_set_header = kong.service.request.set_header local kong_service_request_set_raw_body = kong.service.request.set_raw_body +local warn = kong.log.warn local grpc_web = { @@ -34,6 +37,26 @@ local CORS_HEADERS = { ["Access-Control-Allow-Headers"] = "content-type,x-grpc-web,x-user-agent", } +local function get_body() + local body, err = kong_request_get_raw_body() + if body then + return body + end + + -- if body_file is not nil, the error of get_raw_body is expected + -- otherwise return the error + local body_file = ngx_req_get_body_file() + assert(body_file, err) + + warn("client_body_buffer_size exceeded and reading the request from disk. Please consider increasing the value.") + + local file = assert(io_open(body_file, "rb")) + body = assert(file:read("*a")) + file:close() + + return body +end + function grpc_web:access(conf) kong_response_set_header("Access-Control-Allow-Origin", conf.allow_origin_header) @@ -63,7 +86,7 @@ function grpc_web:access(conf) kong_service_request_set_header("Content-Type", "application/grpc") kong_service_request_set_header("TE", "trailers") - kong_service_request_set_raw_body(dec:upstream(kong_request_get_raw_body())) + kong_service_request_set_raw_body(dec:upstream(get_body())) end diff --git a/spec/03-plugins/32-grpc-web/01-proxy_spec.lua b/spec/03-plugins/32-grpc-web/01-proxy_spec.lua index 8c37776204a..de03b456f55 100644 --- a/spec/03-plugins/32-grpc-web/01-proxy_spec.lua +++ b/spec/03-plugins/32-grpc-web/01-proxy_spec.lua @@ -22,6 +22,10 @@ for _, strategy in helpers.each_strategy() do "gAAAAB5ncnBjLXN0YXR1czowDQpncnBjLW1lc3NhZ2U6DQo=" local HELLO_RESPONSE_BODY = ngx.decode_base64("AAAAAAwKCmhlbGxvIGhleWE=") .. ngx.decode_base64("gAAAAB5ncnBjLXN0YXR1czowDQpncnBjLW1lc3NhZ2U6DQo=") + -- larger than 1k + local LARGE_MESSAGE = string.rep("OK", 10000) + local LARGE_REQUEST_BODY = '{"greeting": \"' .. LARGE_MESSAGE .. '\"}' + local LARGE_RESPONSE_BODY = '{"reply":\"hello ' .. LARGE_MESSAGE .. '\"}' lazy_setup(function() local bp = helpers.get_db_utils(strategy, { @@ -78,7 +82,7 @@ for _, strategy in helpers.each_strategy() do end) lazy_teardown(function() - helpers.stop_kong() + helpers.stop_kong(nil, true) end) @@ -155,7 +159,7 @@ for _, strategy in helpers.each_strategy() do assert.is_nil(err) end) - test("Call plain JSON via HTTP", function() + test("Call plain JSON via HTTP", function() local res, err = proxy_client:post("/hello.HelloService/SayHello", { headers = { ["Content-Type"] = "application/json", @@ -167,16 +171,31 @@ for _, strategy in helpers.each_strategy() do assert.is_nil(err) end) - test("Pass stripped URI", function() - local res, err = proxy_client:post("/prefix/hello.HelloService/SayHello", { - headers = { - ["Content-Type"] = "application/json", - }, - body = cjson.encode{ greeting = "heya" }, - }) - - assert.same({ reply = "hello heya" }, cjson.decode((res:read_body()))) - assert.is_nil(err) - end) + test("Pass stripped URI", function() + local res, err = proxy_client:post("/prefix/hello.HelloService/SayHello", { + headers = { + ["Content-Type"] = "application/json", + }, + body = cjson.encode{ greeting = "heya" }, + }) + + assert.same({ reply = "hello heya" }, cjson.decode((res:read_body()))) + assert.is_nil(err) + end) + + test("#regression large body causes error", function() + local res = assert(proxy_client:post("/hello.HelloService/SayHello", { + headers = { + ["Content-Type"] = "application/json", + ["Content-Length"] = tostring(#LARGE_REQUEST_BODY), + }, + body = LARGE_REQUEST_BODY, + })) + + local body = assert(res:read_body()) + assert.equal(LARGE_RESPONSE_BODY, body) + + assert.logfile().has.line("client_body_buffer_size exceeded") + end) end) end