diff --git a/FAQ.md b/FAQ.md index 55c1590fe14f..2335456c7381 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 2a576a6c685f..b954db902a8f 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 6cc28ac31307..a9df21f26b5a 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 ba7d94ce35eb..b32b88cf9e05 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 187cdc90f0af..6c7da6fbb7b3 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 2ff80ec67199..1415db2da82d 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]