You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sh...@apache.org on 2021/05/07 19:55:48 UTC

[apisix] branch master updated: feat: redirect plugin support regex (#4152)

This is an automated email from the ASF dual-hosted git repository.

shuyangw pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new 3ebe800  feat: redirect plugin support regex (#4152)
3ebe800 is described below

commit 3ebe8004b11f1ee36fd9eaa72e2c9f608d0ecf14
Author: ken zhou <na...@gmail.com>
AuthorDate: Sat May 8 03:55:38 2021 +0800

    feat: redirect plugin support regex (#4152)
    
    Co-authored-by: jianzhou <zh...@huya.com>
    Co-authored-by: Alex Zhang <to...@apache.org>
    Co-authored-by: Yuelin Zheng <22...@qq.com>
    Co-authored-by: 琚致远 <ju...@apache.org>
---
 apisix/plugins/redirect.lua        | 66 ++++++++++++++++++++++++++++++++------
 docs/en/latest/plugins/redirect.md |  1 +
 docs/zh/latest/plugins/redirect.md |  1 +
 t/plugin/redirect.t                | 63 +++++++++++++++++++++++++++++++++++-
 4 files changed, 120 insertions(+), 11 deletions(-)

diff --git a/apisix/plugins/redirect.lua b/apisix/plugins/redirect.lua
index 2fafefa..40db3e0 100644
--- a/apisix/plugins/redirect.lua
+++ b/apisix/plugins/redirect.lua
@@ -17,7 +17,9 @@
 local core = require("apisix.core")
 local tab_insert = table.insert
 local tab_concat = table.concat
+local string_format = string.format
 local re_gmatch = ngx.re.gmatch
+local re_sub = ngx.re.sub
 local ipairs = ipairs
 local ngx = ngx
 
@@ -35,10 +37,22 @@ local schema = {
     properties = {
         ret_code = {type = "integer", minimum = 200, default = 302},
         uri = {type = "string", minLength = 2, pattern = reg},
+        regex_uri = {
+            description = "params for generating new uri that substitute from client uri, " ..
+                          "first param is regular expression, the second one is uri template",
+            type        = "array",
+            maxItems    = 2,
+            minItems    = 2,
+            items       = {
+                description = "regex uri",
+                type = "string",
+            }
+        },
         http_to_https = {type = "boolean"},
     },
     oneOf = {
         {required = {"uri"}},
+        {required = {"regex_uri"}},
         {required = {"http_to_https"}}
     }
 }
@@ -79,7 +93,22 @@ end
 
 
 function _M.check_schema(conf)
-    return core.schema.check(schema, conf)
+    local ok, err = core.schema.check(schema, conf)
+    if not ok then
+        return false, err
+    end
+
+    if conf.regex_uri and #conf.regex_uri > 0 then
+        local _, _, err = re_sub("/fake_uri", conf.regex_uri[1],
+                                 conf.regex_uri[2], "jo")
+        if err then
+            local msg = string_format("invalid regex_uri (%s, %s), err:%s",
+                                      conf.regex_uri[1], conf.regex_uri[2], err)
+            return false, msg
+        end
+    end
+
+    return true
 end
 
 
@@ -115,6 +144,7 @@ function _M.rewrite(conf, ctx)
 
     local ret_code = conf.ret_code
     local uri = conf.uri
+    local regex_uri = conf.regex_uri
 
     if conf.http_to_https and ctx.var.scheme == "http" then
         -- TODO: add test case
@@ -129,17 +159,33 @@ function _M.rewrite(conf, ctx)
         end
     end
 
-    if uri and ret_code then
-        local new_uri, err = concat_new_uri(uri, ctx)
-        if not new_uri then
-            core.log.error("failed to generate new uri by: ", uri, " error: ",
-                           err)
-            return 500
+    if ret_code then
+        if uri then
+            local new_uri, err = concat_new_uri(uri, ctx)
+            if not new_uri then
+                core.log.error("failed to generate new uri by: " .. uri .. err)
+                return 500
+            end
+
+            core.response.set_header("Location", new_uri)
+            return ret_code
+        elseif regex_uri then
+            local new_uri, n, err = re_sub(ctx.var.uri, regex_uri[1],
+                                           regex_uri[2], "jo")
+            if not new_uri then
+                local msg = string_format("failed to substitute the uri:%s (%s) with %s, error:%s",
+                                          ctx.var.uri, regex_uri[1], regex_uri[2], err)
+                core.log.error(msg)
+                return 500
+            end
+
+            if n > 0 then
+                core.response.set_header("Location", new_uri)
+                return ret_code
+            end
         end
-
-        core.response.set_header("Location", new_uri)
-        return ret_code
     end
+
 end
 
 
diff --git a/docs/en/latest/plugins/redirect.md b/docs/en/latest/plugins/redirect.md
index d04ee65..4652f26 100644
--- a/docs/en/latest/plugins/redirect.md
+++ b/docs/en/latest/plugins/redirect.md
@@ -39,6 +39,7 @@ URI redirect.
 | ------------- | ------- | ----------- | ------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | http_to_https | boolean | optional    | false   |       | When it is set to `true` and the request is HTTP, will be automatically redirected to HTTPS with 301 response code, and the URI will keep the same as client request.                                                                                                                                                                                                                                                              |
 | uri           | string  | optional    |         |       | New URL which can contain Nginx variable, eg: `/test/index.html`, `$uri/index.html`. You can refer to variables in a way similar to `${xxx}` to avoid ambiguity, eg: `${uri}foo/index.html`. If you just need the original `$` character, add `\` in front of it, like this one: `/\$foo/index.html`. If you refer to a variable name that does not exist, this will not produce an error, and it will be used as an empty string. |
+| regex_uri | array[string] | optional    |         |                   | Use regular expression to match URL from client, when the match is successful, the URL template will be redirected to. If the match is not successful, the URL from the client will be forwarded to the upstream. Only one of `uri` and `regex_uri` can be exist. For example: [" ^/iresty/(.*)/(.*)/(.*)", "/$1-$2-$3"], the first element represents the matching regular expression and the second element represents the URL t [...]
 | ret_code      | integer | optional    | 302     |  [200, ...]     | Response code                                                                                                                                                                                                                                                                                                                                                                                                                      |
 
 Only one of `http_to_https` or `uri` can be specified.
diff --git a/docs/zh/latest/plugins/redirect.md b/docs/zh/latest/plugins/redirect.md
index f20e379..2f64a25 100644
--- a/docs/zh/latest/plugins/redirect.md
+++ b/docs/zh/latest/plugins/redirect.md
@@ -29,6 +29,7 @@ URI 重定向插件。
 | ------------- | ------- | ----------- | ------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | http_to_https | boolean | 可选        | false   |            | 当设置为 `true` 并且请求是 http 时,会自动 301 重定向为 https,uri 保持不变                                                                                                                                                   |
 | uri           | string  | 可选        |         |            | 可以包含 Nginx 变量的 URI,例如:`/test/index.html`, `$uri/index.html`。你可以通过类似于 `$ {xxx}` 的方式引用变量,以避免产生歧义,例如:`${uri}foo/index.html`。若你需要保留 `$` 字符,那么使用如下格式:`/\$foo/index.html` |
+| regex_uri | array[string] | 可选        |         |                   | 转发到上游的新 `uri` 地址, 使用正则表达式匹配来自客户端的 `uri`,当匹配成功后使用模板替换发送重定向到客户端, 未匹配成功时将客户端请求的 `uri` 转发至上游。`uri` 和 `regex_uri` 不可以同时存在。例如:["^/iresty/(.*)/(.*)/(.*)","/$1-$2-$3"] 第一个元素代表匹配来自客户端请求的 `uri` 正则表达式,第二个元素代表匹配成功后发送重定向到客户端的 `uri` 模板。 |
 | ret_code      | integer | 可选        | 302     | [200, ...] | 请求响应码                                                                                                                                                                                                                    |
 
 `http_to_https` 和 `uri` 两个中只能配置一个。
diff --git a/t/plugin/redirect.t b/t/plugin/redirect.t
index 768ddd3..56e7425 100644
--- a/t/plugin/redirect.t
+++ b/t/plugin/redirect.t
@@ -513,7 +513,7 @@ Location: https://foo.com/hello
 GET /t
 --- error_code: 400
 --- response_body eval
-qr/error_msg":"failed to check the configuration of plugin redirect err: value should match only one schema, but matches both schemas 1 and 2/
+qr/error_msg":"failed to check the configuration of plugin redirect err: value should match only one schema, but matches both schemas 1 and 3/
 --- no_error_log
 [error]
 
@@ -752,3 +752,64 @@ Location: https://test.com/hello-https
 --- error_code: 301
 --- no_error_log
 [error]
+
+
+
+=== TEST 30: add plugin with new regex_uri: /test/1 redirect to http://test.com/1
+--- 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": {
+                        "redirect": {
+                            "regex_uri": ["^/test/(.*)", "http://test.com/${1}"],
+                            "ret_code": 301
+                        }
+                    },
+                    "uris": ["/test/*", "/hello"],
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 31: regex_uri redirect
+--- request
+GET /test/1
+--- response_headers
+Location: http://test.com/1
+--- error_code: 301
+--- no_error_log
+[error]
+
+
+
+=== TEST 32: regex_uri not match, get response from upstream
+--- request
+GET /hello
+--- error_code: 200
+--- response_body
+hello world
+--- no_error_log
+[error]