From 54289722df630c8d187833310327067c003d8ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BD=97=E6=B3=BD=E8=BD=A9?= Date: Fri, 14 Jan 2022 11:00:47 +0800 Subject: [PATCH] feat: set proxy_request_buffering dynamically (#6075) --- apisix/plugins/proxy-control.lua | 65 +++++++++++ conf/config-default.yaml | 1 + docs/en/latest/config.json | 1 + docs/en/latest/plugins/proxy-control.md | 95 ++++++++++++++++ t/admin/plugins.t | 1 + t/plugin/proxy-control.t | 138 ++++++++++++++++++++++++ 6 files changed, 301 insertions(+) create mode 100644 apisix/plugins/proxy-control.lua create mode 100644 docs/en/latest/plugins/proxy-control.md create mode 100644 t/plugin/proxy-control.t diff --git a/apisix/plugins/proxy-control.lua b/apisix/plugins/proxy-control.lua new file mode 100644 index 000000000000..93ddc0ffcb47 --- /dev/null +++ b/apisix/plugins/proxy-control.lua @@ -0,0 +1,65 @@ +-- +-- Licensed to the Apache Software Foundation (ASF) under one or more +-- contributor license agreements. See the NOTICE file distributed with +-- this work for additional information regarding copyright ownership. +-- The ASF licenses this file to You under the Apache License, Version 2.0 +-- (the "License"); you may not use this file except in compliance with +-- the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- 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 require = require +local core = require("apisix.core") +local ok, apisix_ngx_client = pcall(require, "resty.apisix.client") + + +local schema = { + type = "object", + properties = { + request_buffering = { + type = "boolean", + default = true, + }, + }, +} + + +local plugin_name = "proxy-control" +local _M = { + version = 0.1, + priority = 21990, + name = plugin_name, + schema = schema, +} + + +function _M.check_schema(conf) + return core.schema.check(schema, conf) +end + + +-- we want to control proxy behavior before auth, so put the code under rewrite method +function _M.rewrite(conf, ctx) + if not ok then + core.log.error("need to build APISIX-OpenResty to support proxy control") + return 501 + end + + local request_buffering = conf.request_buffering + if request_buffering ~= nil then + local ok, err = apisix_ngx_client.set_proxy_request_buffering(request_buffering) + if not ok then + core.log.error("failed to set request_buffering: ", err) + return 503 + end + end +end + + +return _M diff --git a/conf/config-default.yaml b/conf/config-default.yaml index e2757d866bb7..f73697f3cb3b 100644 --- a/conf/config-default.yaml +++ b/conf/config-default.yaml @@ -323,6 +323,7 @@ graphql: plugins: # plugin list (sorted by priority) - real-ip # priority: 23000 - client-control # priority: 22000 + - proxy-control # priority: 21990 - ext-plugin-pre-req # priority: 12000 - zipkin # priority: 11011 - request-id # priority: 11010 diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index 93eba0325ec1..9b7e1b7c082d 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -97,6 +97,7 @@ "plugins/api-breaker", "plugins/traffic-split", "plugins/request-id", + "plugins/proxy-control", "plugins/client-control" ] }, diff --git a/docs/en/latest/plugins/proxy-control.md b/docs/en/latest/plugins/proxy-control.md new file mode 100644 index 000000000000..c717062199a9 --- /dev/null +++ b/docs/en/latest/plugins/proxy-control.md @@ -0,0 +1,95 @@ +--- +title: proxy-control +--- + + + +## Summary + +- [**Name**](#name) +- [**Attributes**](#attributes) +- [**How To Enable**](#how-to-enable) +- [**Test Plugin**](#test-plugin) +- [**Disable Plugin**](#disable-plugin) + +## Name + +The `proxy-control` plugin dynamically controls the behavior of Nginx to proxy. + +**This plugin requires APISIX to run on [APISIX-OpenResty](../how-to-build.md#step-6-build-openresty-for-apache-apisix).** + +## Attributes + +| Name | Type | Requirement | Default | Valid | Description | +| --------- | ------------- | ----------- | ---------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| request_buffering | boolean | optional | true | | dynamically set the `proxy_request_buffering` directive | + +## How To Enable + +Here's an example, enable this plugin on the specified route: + +```shell +curl -i http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/upload", + "plugins": { + "proxy-control": { + "request_buffering": false + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +## Test Plugin + +Use curl to access: + +```shell +curl -i http://127.0.0.1:9080/upload -d @very_big_file +``` + +It's expected not to find "a client request body is buffered to a temporary file" in the error log. + +## Disable Plugin + +When you want to disable this plugin, it is very simple, +you can delete the corresponding json configuration in the plugin configuration, +no need to restart the service, it will take effect immediately: + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "/upload", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + } +}' +``` + +This plugin has been disabled now. It works for other plugins. diff --git a/t/admin/plugins.t b/t/admin/plugins.t index d203c37c5ea8..95425a2cdb74 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -62,6 +62,7 @@ GET /t --- response_body real-ip client-control +proxy-control ext-plugin-pre-req zipkin request-id diff --git a/t/plugin/proxy-control.t b/t/plugin/proxy-control.t new file mode 100644 index 000000000000..f3561bfe58d5 --- /dev/null +++ b/t/plugin/proxy-control.t @@ -0,0 +1,138 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# 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. +# +use t::APISIX; + +my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx'; +my $version = eval { `$nginx_binary -V 2>&1` }; + +if ($version !~ m/\/apisix-nginx-module/) { + plan(skip_all => "apisix-nginx-module not installed"); +} else { + plan('no_plan'); +} + +repeat_each(1); +log_level('info'); +no_root_location(); +no_shuffle(); + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } + + if ((!defined $block->error_log) && (!defined $block->no_error_log)) { + $block->set_value("no_error_log", "[error]"); + } +}); + +run_tests(); + +__DATA__ + +=== TEST 1: proxy_request_buffering off +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local json = require("toolkit.json") + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/hello", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "proxy-control": { + "request_buffering": false + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body +passed + + + +=== TEST 2: hit, only the upstream server will buffer the request +--- request eval +"POST /hello +" . "12345" x 10240 +--- grep_error_log eval +qr/a client request body is buffered to a temporary file/ +--- grep_error_log_out +a client request body is buffered to a temporary file + + + +=== TEST 3: proxy_request_buffering on +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local json = require("toolkit.json") + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/hello", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "proxy-control": { + "request_buffering": true + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body +passed + + + +=== TEST 4: hit +--- request eval +"POST /hello +" . "12345" x 10240 +--- grep_error_log eval +qr/a client request body is buffered to a temporary file/ +--- grep_error_log_out +a client request body is buffered to a temporary file +a client request body is buffered to a temporary file