This is an automated email from the ASF dual-hosted git repository. wenming pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-apisix.git
The following commit(s) were added to refs/heads/master by this push: new fc948f9 feautre: support for proxy mirror plugin. (#1288) fc948f9 is described below commit fc948f95787240d56cfd933b320e121adf7bef2c Author: agile6v <agil...@agile6v.com> AuthorDate: Thu Mar 19 08:10:03 2020 +0800 feautre: support for proxy mirror plugin. (#1288) --- bin/apisix | 12 ++ conf/config.yaml | 1 + doc/README.md | 1 + doc/README_CN.md | 1 + doc/plugins/proxy-mirror-cn.md | 93 ++++++++++++ doc/plugins/proxy-mirror.md | 97 ++++++++++++ lua/apisix/core/ctx.lua | 2 + lua/apisix/plugins/proxy-mirror.lua | 59 ++++++++ t/APISIX.pm | 12 ++ t/admin/plugins.t | 2 +- t/debug/debug-mode.t | 1 + t/plugin/proxy-mirror.t | 291 ++++++++++++++++++++++++++++++++++++ 12 files changed, 571 insertions(+), 1 deletion(-) diff --git a/bin/apisix b/bin/apisix index 78c2914..6f0a0f1 100755 --- a/bin/apisix +++ b/bin/apisix @@ -373,6 +373,7 @@ http { } location / { + set $upstream_mirror_host ''; set $upstream_scheme 'http'; set $upstream_host $host; set $upstream_upgrade ''; @@ -444,6 +445,7 @@ http { {% end %} proxy_pass $upstream_scheme://apisix_backend$upstream_uri; + mirror /proxy_mirror; header_filter_by_lua_block { apisix.http_header_filter_phase() @@ -480,6 +482,16 @@ http { apisix.http_log_phase() } } + + location = /proxy_mirror { + internal; + + if ($upstream_mirror_host = "") { + return 200; + } + + proxy_pass $upstream_mirror_host$request_uri; + } } } ]=] diff --git a/conf/config.yaml b/conf/config.yaml index e050092..65f79da 100644 --- a/conf/config.yaml +++ b/conf/config.yaml @@ -142,6 +142,7 @@ plugins: # plugin list - wolf-rbac - proxy-cache - tcp-logger + - proxy-mirror stream_plugins: - mqtt-proxy diff --git a/doc/README.md b/doc/README.md index cba2444..1854295 100644 --- a/doc/README.md +++ b/doc/README.md @@ -60,6 +60,7 @@ Plugins * [fault-injection](plugins/fault-injection.md): The specified response body, response code, and response time can be returned, which provides processing capabilities in different failure scenarios, such as service failure, service overload, and high service delay. * [proxy-cache](plugins/proxy-cache.md): Provides the ability to cache upstream response data. * [tcp-logger](plugins/tcp-logger.md): Log requests to TCP servers +* [proxy-mirror](plugins/proxy-mirror.md): Provides the ability to mirror client requests. Deploy to the Cloud ======= diff --git a/doc/README_CN.md b/doc/README_CN.md index 3fc9f9d..294727e 100644 --- a/doc/README_CN.md +++ b/doc/README_CN.md @@ -60,4 +60,5 @@ Reference document * [response-rewrite](plugins/response-rewrite-cn.md): 支持自定义修改返回内容的 `status code`、`body`、`headers`。 * [fault-injection](plugins/fault-injection-cn.md):故障注入,可以返回指定的响应体、响应码和响应时间,从而提供了不同的失败场景下处理的能力,例如服务失败、服务过载、服务高延时等。 * [proxy-cache](plugins/proxy-cache-cn.md):代理缓存插件提供缓存后端响应数据的能力。 +* [proxy-mirror](plugins/proxy-mirror-cn.md):代理镜像插件提供镜像客户端请求的能力。 diff --git a/doc/plugins/proxy-mirror-cn.md b/doc/plugins/proxy-mirror-cn.md new file mode 100644 index 0000000..4b3f073 --- /dev/null +++ b/doc/plugins/proxy-mirror-cn.md @@ -0,0 +1,93 @@ +<!-- +# +# 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. +# +--> + +[English](proxy-mirror.md) + +# proxy-mirror + +代理镜像插件,该插件提供了镜像客户端请求的能力。 + +注:镜像请求返回的响应会被忽略。 + +### 参数 + +|名称 |必须|类型|描述| +|------- |-----|------|------| +|host|是|string|指定镜像服务地址,例如:http://127.0.0.1:9797(地址中需要包含 schema :http或https,不能包含 URI 部分)| + +### 示例 + +#### 启用插件 + +示例1:为特定路由启用 `proxy-mirror` 插件: + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "plugins": { + "proxy-mirror": { + "host": "http://127.0.0.1:9797" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1999": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +测试: + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Content-Length: 12 +Connection: keep-alive +Server: APISIX web server +Date: Wed, 18 Mar 2020 13:01:11 GMT +Last-Modified: Thu, 20 Feb 2020 14:21:41 GMT + +hello world +``` + +> 由于指定的 mirror 地址是127.0.0.1:9797,所以验证此插件是否已经正常工作需要在端口为9797的服务上确认,例如,我们可以通过 python 启动一个简单的 server: python -m SimpleHTTPServer 9797。 + +#### 禁用插件 + +移除插件配置中相应的 JSON 配置可立即禁用该插件,无需重启服务: + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "uri": "/hello", + "plugins": {}, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1999": 1 + } + } +}' +``` + +这时该插件已被禁用。 diff --git a/doc/plugins/proxy-mirror.md b/doc/plugins/proxy-mirror.md new file mode 100644 index 0000000..3b6ee54 --- /dev/null +++ b/doc/plugins/proxy-mirror.md @@ -0,0 +1,97 @@ +<!-- +# +# 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. +# +--> + +[Chinese](proxy-mirror-cn.md) + +# proxy-mirror + +The proxy-mirror plugin, which provides the ability to mirror client requests. + +*Note*: The response returned by the mirror request is ignored. + +## Attributes + +|Name |Requirement | Type |Description| +|------- |-----|------|------| +|host|required|string|Specify a mirror service address, e.g. http://127.0.0.1:9797 (address needs to contain schema: http or https, not URI part)| + + +### Examples + +#### Enable the plugin + +1: enable the proxy-mirror plugin for a specific route : + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "plugins": { + "proxy-mirror": { + "host": "http://127.0.0.1:9797" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1999": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" +}' +``` + +Test plugin: + +```shell +$ curl http://127.0.0.1:9080/hello -i +HTTP/1.1 200 OK +Content-Type: application/octet-stream +Content-Length: 12 +Connection: keep-alive +Server: APISIX web server +Date: Wed, 18 Mar 2020 13:01:11 GMT +Last-Modified: Thu, 20 Feb 2020 14:21:41 GMT + +hello world +``` + +> Since the specified mirror address is 127.0.0.1:9797, so to verify whether this plugin is in effect, we need to confirm on the service with port 9797. +> For example, we can start a simple server: python -m SimpleHTTPServer 9797 + + +## Disable Plugin + +Remove the corresponding JSON in the plugin configuration to disable the plugin immediately without restarting the service: + + +```shell +curl http://127.0.0.1:9080/apisix/admin/routes/1 -X PUT -d ' +{ + "uri": "/hello", + "plugins": {}, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1999": 1 + } + } +}' +``` + +The plugin has been disabled now. diff --git a/lua/apisix/core/ctx.lua b/lua/apisix/core/ctx.lua index 31d0d89..1e5082b 100644 --- a/lua/apisix/core/ctx.lua +++ b/lua/apisix/core/ctx.lua @@ -48,6 +48,8 @@ do upstream_connection = true, upstream_uri = true, + upstream_mirror_host = true, + upstream_cache_zone = true, upstream_cache_zone_info = true, upstream_no_cache = true, diff --git a/lua/apisix/plugins/proxy-mirror.lua b/lua/apisix/plugins/proxy-mirror.lua new file mode 100644 index 0000000..830f889 --- /dev/null +++ b/lua/apisix/plugins/proxy-mirror.lua @@ -0,0 +1,59 @@ +-- +-- 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 core = require("apisix.core") + +local plugin_name = "proxy-mirror" + +local schema = { + type = "object", + properties = { + host = { + type = "string", + pattern = [[^http(s)?:\/\/[a-zA-Z0-9][-a-zA-Z0-9]{0,62}]] + .. [[(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+(:[0-9]{1,5})?$]], + }, + }, + required = {"host"}, + minProperties = 1, +} + +local _M = { + version = 0.1, + priority = 1010, + name = plugin_name, + schema = schema, +} + + +function _M.check_schema(conf) + local ok, err = core.schema.check(schema, conf) + if not ok then + return false, err + end + + return true +end + + +function _M.rewrite(conf, ctx) + core.log.info("proxy mirror plugin rewrite phase, conf: ", core.json.delay_encode(conf)) + + ctx.var.upstream_mirror_host = conf.host +end + + +return _M diff --git a/t/APISIX.pm b/t/APISIX.pm index 4dd710d..af7f2b0 100644 --- a/t/APISIX.pm +++ b/t/APISIX.pm @@ -263,6 +263,7 @@ _EOC_ } location / { + set \$upstream_mirror_host ''; set \$upstream_scheme 'http'; set \$upstream_host \$host; set \$upstream_upgrade ''; @@ -306,6 +307,7 @@ _EOC_ proxy_pass_header Server; proxy_pass_header Date; proxy_pass \$upstream_scheme://apisix_backend\$upstream_uri; + mirror /proxy_mirror; header_filter_by_lua_block { apisix.http_header_filter_phase() @@ -341,6 +343,16 @@ _EOC_ apisix.http_log_phase() } } + + location = /proxy_mirror { + internal; + + if (\$upstream_mirror_host = "") { + return 200; + } + + proxy_pass \$upstream_mirror_host\$request_uri; + } _EOC_ $block->set_value("config", $config); diff --git a/t/admin/plugins.t b/t/admin/plugins.t index c5f34b7..b8771c7 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -30,7 +30,7 @@ __DATA__ --- request GET /apisix/admin/plugins/list --- response_body_like eval -qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac","proxy-cache","tcp-logger"\]/ +qr/\["limit-req","limit-count","limit-conn","key-auth","basic-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction","grpc-transcode","serverless-pre-function","serverless-post-function","openid-connect","proxy-rewrite","redirect","response-rewrite","fault-injection","udp-logger","wolf-rbac","proxy-cache","tcp-logger","proxy-mirror"\]/ --- no_error_log [error] diff --git a/t/debug/debug-mode.t b/t/debug/debug-mode.t index ead99df..d5a6c1d 100644 --- a/t/debug/debug-mode.t +++ b/t/debug/debug-mode.t @@ -63,6 +63,7 @@ loaded plugin and sort by priority: 2555 name: wolf-rbac loaded plugin and sort by priority: 2520 name: basic-auth loaded plugin and sort by priority: 2510 name: jwt-auth loaded plugin and sort by priority: 2500 name: key-auth +loaded plugin and sort by priority: 1010 name: proxy-mirror loaded plugin and sort by priority: 1009 name: proxy-cache loaded plugin and sort by priority: 1008 name: proxy-rewrite loaded plugin and sort by priority: 1003 name: limit-conn diff --git a/t/plugin/proxy-mirror.t b/t/plugin/proxy-mirror.t new file mode 100644 index 0000000..728b6a6 --- /dev/null +++ b/t/plugin/proxy-mirror.t @@ -0,0 +1,291 @@ +# +# 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 'no_plan'; + +repeat_each(1); +no_long_string(); +no_shuffle(); +no_root_location(); +log_level('info'); + +add_block_preprocessor(sub { + my ($block) = @_; + + my $http_config = $block->http_config // <<_EOC_; + + server { + listen 1986; + server_tokens off; + + location / { + content_by_lua_block { + ngx.log(ngx.ERR, "uri: ", ngx.var.uri) + ngx.say("hello world") + } + } + } +_EOC_ + + $block->set_value("http_config", $http_config); +}); + +run_tests; + +__DATA__ + +=== TEST 1: sanity check (invalid schema) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-mirror": { + "host": "ftp://127.0.0.1:1999" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-mirror/ +--- no_error_log +[error] + + + +=== TEST 2: sanity check (invalid port format) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-mirror": { + "host": "http://127.0.0.1::1999" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-mirror/ +--- no_error_log +[error] + + + +=== TEST 3: sanity check (without schema) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-mirror": { + "host": "127.0.0.1:1999" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-mirror/ +--- no_error_log +[error] + + + +=== TEST 4: sanity check (without port) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-mirror": { + "host": "http://127.0.0.1" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] + + + +=== TEST 4: sanity check (include uri) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-mirror": { + "host": "http://127.0.0.1:1999/invalid_uri" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 400 +--- response_body eval +qr/failed to check the configuration of plugin proxy-mirror/ +--- no_error_log +[error] + + + +=== TEST 5: sanity check (normal case) +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "plugins": { + "proxy-mirror": { + "host": "http://127.0.0.1:1986" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } + } +--- request +GET /t +--- error_code: 200 +--- response_body +passed +--- no_error_log +[error] + + +=== TEST 6: hit route +--- request +GET /hello +--- error_code: 200 +--- response_body +hello world +--- error_log +uri: /hello +