You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by we...@apache.org on 2020/06/03 09:47:06 UTC

[incubator-apisix] branch master updated: feature: support http_to_https in redirect plugin. (#1642)

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 f615cf7  feature: support http_to_https in redirect plugin. (#1642)
f615cf7 is described below

commit f615cf7134f47110270e879627730b7b6f9525b2
Author: Wen Ming <mo...@gmail.com>
AuthorDate: Wed Jun 3 17:46:58 2020 +0800

    feature: support http_to_https in redirect plugin. (#1642)
---
 FAQ.md                      |  18 ++-
 FAQ_CN.md                   |  18 ++-
 apisix/plugins/redirect.lua |  37 ++++--
 doc/plugins/redirect-cn.md  |  20 ++-
 doc/plugins/redirect.md     |  18 ++-
 t/plugin/redirect.t         | 297 ++++++++++++++++++++++++++++++++++++++++++--
 6 files changed, 378 insertions(+), 30 deletions(-)

diff --git a/FAQ.md b/FAQ.md
index 55c1590..2335456 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -116,7 +116,21 @@ https://github.com/iresty/lua-resty-radixtree#operator-list
 An example, redirect `http://foo.com` to `https://foo.com`
 
 There are several different ways to do this.
-1. `redirect` plugin:
+1. Directly use the `http_to_https` in `redirect` plugin:
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/hello",
+    "host": "foo.com",
+    "plugins": {
+        "redirect": {
+            "http_to_https": true
+        }
+    }
+}'
+```
+
+2. Use with advanced routing rule `vars` with `redirect` plugin:
 
 ```shell
 curl -i http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@@ -139,7 +153,7 @@ curl -i http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f03433
 }'
 ```
 
-2. `serverless` plugin:
+3. `serverless` plugin:
 
 ```shell
 curl -i http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
diff --git a/FAQ_CN.md b/FAQ_CN.md
index 2a576a6..b954db9 100644
--- a/FAQ_CN.md
+++ b/FAQ_CN.md
@@ -119,7 +119,21 @@ https://github.com/iresty/lua-resty-radixtree#operator-list
 比如,将 `http://foo.com` 重定向到 `https://foo.com`
 
 有几种不同的方法来实现:
-1. 使用`redirect`插件:
+1. 直接使用 `redirect` 插件的 `http_to_https` 功能:
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/hello",
+    "host": "foo.com",
+    "plugins": {
+        "redirect": {
+            "http_to_https": true
+        }
+    }
+}'
+```
+
+2. 结合高级路由规则 `vars` 和 `redirect` 插件一起使用:
 
 ```shell
 curl -i http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
@@ -142,7 +156,7 @@ curl -i http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f03433
 }'
 ```
 
-2. 使用`serverless`插件:
+3. 使用`serverless`插件:
 
 ```shell
 curl -i http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
diff --git a/apisix/plugins/redirect.lua b/apisix/plugins/redirect.lua
index 6cc28ac..a9df21f 100644
--- a/apisix/plugins/redirect.lua
+++ b/apisix/plugins/redirect.lua
@@ -30,8 +30,12 @@ local schema = {
     properties = {
         ret_code = {type = "integer", minimum = 200, default = 302},
         uri = {type = "string", minLength = 2},
+        http_to_https = {type = "boolean"}, -- default is false
     },
-    required = {"uri"},
+    oneOf = {
+        {required = {"uri"}},
+        {required = {"http_to_https"}}
+    }
 }
 
 
@@ -80,11 +84,13 @@ function _M.check_schema(conf)
         return false, err
     end
 
-    local uri_segs, err = parse_uri(conf.uri)
-    if not uri_segs then
-        return false, err
+    if conf.uri then
+        local uri_segs, err = parse_uri(conf.uri)
+        if not uri_segs then
+            return false, err
+        end
+        core.log.info(core.json.delay_encode(uri_segs))
     end
-    core.log.info(core.json.delay_encode(uri_segs))
 
     return true
 end
@@ -120,15 +126,22 @@ end
 function _M.rewrite(conf, ctx)
     core.log.info("plugin rewrite phase, conf: ", core.json.delay_encode(conf))
 
-    local new_uri, err = concat_new_uri(conf.uri, ctx)
-    if not new_uri then
-        core.log.error("failed to generate new uri by: ", conf.uri, " error: ",
-                       err)
-        core.response.exit(500)
+    if conf.http_to_https and ctx.var.scheme == "http" then
+        conf.uri = "https://$host$request_uri"
+        conf.ret_code = 301
     end
 
-    core.response.set_header("Location", new_uri)
-    core.response.exit(conf.ret_code)
+    if conf.uri and conf.ret_code then
+        local new_uri, err = concat_new_uri(conf.uri, ctx)
+        if not new_uri then
+            core.log.error("failed to generate new uri by: ", conf.uri, " error: ",
+                        err)
+            core.response.exit(500)
+        end
+
+        core.response.set_header("Location", new_uri)
+        core.response.exit(conf.ret_code)
+    end
 end
 
 
diff --git a/doc/plugins/redirect-cn.md b/doc/plugins/redirect-cn.md
index ba7d94c..b32b88c 100644
--- a/doc/plugins/redirect-cn.md
+++ b/doc/plugins/redirect-cn.md
@@ -27,8 +27,9 @@ URI 重定向插件。
 
 |名称    |必须|描述|
 |------- |-----|------|
-|uri     |是| 可以包含 Nginx 变量的 URI,例如:`/test/index.html`, `$uri/index.html`。你可以通过类似于 `$ {xxx}` 的方式引用变量,以避免产生歧义,例如:`${uri}foo/index.html`。若你需要保留 `$` 字符,那么使用如下格式:`/\$foo/index.html`。|
-|ret_code|否|请求响应码,默认值为 `302`。|
+|uri     |是,与 `http_to_https` 二选一| 可以包含 Nginx 变量的 URI,例如:`/test/index.html`, `$uri/index.html`。你可以通过类似于 `$ {xxx}` 的方式引用变量,以避免产生歧义,例如:`${uri}foo/index.html`。若你需要保留 `$` 字符,那么使用如下格式:`/\$foo/index.html`。|
+|ret_code|否,只和 `uri` 配置使用。|请求响应码,默认值为 `302`。|
+|http_to_https|是,与 `uri` 二选一|布尔值,默认是 `false`。当设置为 `ture` 并且请求是 http 时,会自动 301 重定向为 https,uri 保持不变|
 
 ### 示例
 
@@ -94,6 +95,21 @@ Location: /test/default.html
 
 我们可以检查响应码和响应头中的 `Location` 参数,它表示该插件已启用。
 
+```
+
+下面是一个实现 http 到 https 跳转的示例:
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/hello",
+    "plugins": {
+        "redirect": {
+            "http_to_https": true
+        }
+    }
+}'
+```
+
 #### 禁用插件
 
 移除插件配置中相应的 JSON 配置可立即禁用该插件,无需重启服务:
diff --git a/doc/plugins/redirect.md b/doc/plugins/redirect.md
index 187cdc9..6c7da6f 100644
--- a/doc/plugins/redirect.md
+++ b/doc/plugins/redirect.md
@@ -34,8 +34,9 @@ URI redirect.
 
 |Name    |Requirement|Description|
 |------- |-----|------|
-|uri     |required| New uri 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.|
-|ret_code|optional|Response code, the default value is `302`.|
+|uri     |required, need pick one from `uri` and `http_to_https`| New uri 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.|
+|ret_code|optional, only works with `uri`|Response code, the default value is `302`.|
+|http_to_https|required, need pick one from `uri` and `http_to_https`|Boolean value. The default value is `false`. When it is set to `ture` 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.|
 
 ## How To Enable
 
@@ -101,6 +102,19 @@ We can check the response code and the response header `Location`.
 
 It shows that the `redirect` plugin is in effect.
 
+ Here is an example of redirect HTTP to HTTPS:
+```shell
+curl http://127.0.0.1:9080/apisix/admin/routes/1  -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "uri": "/hello",
+    "plugins": {
+        "redirect": {
+            "http_to_https": true
+        }
+    }
+}'
+```
+
 ## Disable Plugin
 
 When you want to disable the `redirect` plugin, it is very simple,
diff --git a/t/plugin/redirect.t b/t/plugin/redirect.t
index 2ff80ec..1415db2 100644
--- a/t/plugin/redirect.t
+++ b/t/plugin/redirect.t
@@ -14,18 +14,10 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-BEGIN {
-    if ($ENV{TEST_NGINX_CHECK_LEAK}) {
-        $SkipReason = "unavailable for the hup tests";
-
-    } else {
-        $ENV{TEST_NGINX_USE_HUP} = 1;
-        undef $ENV{TEST_NGINX_USE_STAP};
-    }
-}
-
 use t::APISIX 'no_plan';
 
+$ENV{TEST_NGINX_HTML_DIR} ||= html_dir();
+
 repeat_each(1);
 no_long_string();
 no_shuffle();
@@ -398,3 +390,288 @@ Host: foo.com
 --- error_code: 301
 --- response_headers
 Location: https://foo.com/hello
+
+
+
+=== TEST 17: enable http_to_https
+--- 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,
+                [[{
+                    "uri": "/hello",
+                    "host": "foo.com",
+                    "plugins": {
+                        "redirect": {
+                            "http_to_https": true
+                        }
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 18: redirect
+--- request
+GET /hello
+--- more_headers
+Host: foo.com
+--- error_code: 301
+--- response_headers
+Location: https://foo.com/hello
+
+
+
+=== TEST 19: enable http_to_https with ret_code(not take effect)
+--- 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,
+                [[{
+                    "uri": "/hello",
+                    "host": "foo.com",
+                    "plugins": {
+                        "redirect": {
+                            "http_to_https": true,
+                            "ret_code": 302
+                        }
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 20: redirect
+--- request
+GET /hello
+--- more_headers
+Host: foo.com
+--- error_code: 301
+--- response_headers
+Location: https://foo.com/hello
+
+
+
+=== TEST 21: wrong configure, enable http_to_https with 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,
+                [[{
+                    "uri": "/hello",
+                    "host": "foo.com",
+                    "plugins": {
+                        "redirect": {
+                            "http_to_https": true,
+                            "uri": "/hello"
+                        }
+                    }
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+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/
+--- no_error_log
+[error]
+
+
+
+=== TEST 22: enable http_to_https with upstream
+--- 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,
+                [[{
+                    "uri": "/hello",
+                    "host": "test.com",
+                    "plugins": {
+                        "redirect": {
+                            "http_to_https": true
+                        }
+                    },
+                    "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 23: redirect
+--- request
+GET /hello
+--- more_headers
+Host: test.com
+--- error_code: 301
+--- response_headers
+Location: https://test.com/hello
+
+
+
+=== TEST 24: set ssl(sni: test.com)
+--- config
+location /t {
+    content_by_lua_block {
+        local core = require("apisix.core")
+        local t = require("lib.test_admin")
+
+        local ssl_cert = t.read_file("conf/cert/apisix.crt")
+        local ssl_key =  t.read_file("conf/cert/apisix.key")
+        local data = {cert = ssl_cert, key = ssl_key, sni = "test.com"}
+
+        local code, body = t.test('/apisix/admin/ssl/1',
+            ngx.HTTP_PUT,
+            core.json.encode(data),
+            [[{
+                "node": {
+                    "value": {
+                        "sni": "test.com"
+                    },
+                    "key": "/apisix/ssl/1"
+                },
+                "action": "set"
+            }]]
+            )
+
+        ngx.status = code
+        ngx.say(body)
+    }
+}
+--- request
+GET /t
+--- response_body
+passed
+--- no_error_log
+[error]
+
+
+
+=== TEST 25: client https request
+--- config
+listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
+
+location /t {
+    content_by_lua_block {
+        -- etcd sync
+        ngx.sleep(0.2)
+
+        do
+            local sock = ngx.socket.tcp()
+
+            sock:settimeout(2000)
+
+            local ok, err = sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock")
+            if not ok then
+                ngx.say("failed to connect: ", err)
+                return
+            end
+
+            ngx.say("connected: ", ok)
+
+            local sess, err = sock:sslhandshake(nil, "test.com", false)
+            if not sess then
+                ngx.say("failed to do SSL handshake: ", err)
+                return
+            end
+
+            ngx.say("ssl handshake: ", type(sess))
+
+            local req = "GET /hello HTTP/1.0\r\nHost: test.com\r\nConnection: close\r\n\r\n"
+            local bytes, err = sock:send(req)
+            if not bytes then
+                ngx.say("failed to send http request: ", err)
+                return
+            end
+
+            ngx.say("sent http request: ", bytes, " bytes.")
+
+            while true do
+                local line, err = sock:receive()
+                if not line then
+                    -- ngx.say("failed to receive response status line: ", err)
+                    break
+                end
+
+                ngx.say("received: ", line)
+            end
+
+            local ok, err = sock:close()
+            ngx.say("close: ", ok, " ", err)
+        end  -- do
+        -- collectgarbage()
+    }
+}
+--- request
+GET /t
+--- response_body eval
+qr{connected: 1
+ssl handshake: userdata
+sent http request: 58 bytes.
+received: HTTP/1.1 200 OK
+received: Content-Type: text/plain
+received: Connection: close
+received: Server: \w+
+received: \nreceived: hello world
+close: 1 nil}
+--- no_error_log
+[error]
+[alert]