Kong[nginx]-19 玩转ngx.redirect
NR;TL
今天我们要实现的需求如下:
App
组的小伙伴们会不定期发布一个新产品, 包含iOS
和Android
两个平台.
他们希望后端提供一个统一的下载链接, 可以根据手机的不同, 自动返回相应的实现下载地址.
本文会以Kong
插件的形式提供一个解决方案.
0x01 以终为始,我们的效果是什么?
我们会提供一个这样的统一入口URL
:
http://aaa.com/app/url/{appName}
当我们的插件监听到这个路由的请求时,
通过分析Header
里的User-Agent
来区分手机设备类型.
然后跳转到与appName
相应平台的下载结果.
这里会用到一个方法ngx.redirect
ngx.redirect
syntax: ngx.redirect(uri, status?)context: rewrite_by_lua, access_by_lua, content_by_lua*
Issue an HTTP 301 or 302 redirection to uri.
Redirecting arbitrary external URLs is also supported, for >example:
return ngx.redirect("http://www.google.com")
说一下重点:
- 在
access()
方法中可以使用 - 支持完整路径
URL
跳转
0x02 代码实现
插件参数配置为了方便理解代码实现, 我们先看一下使用效果图
插件配置时, 我们需要配置一个参数项:appSettings
它可以接收多个值.
每一个值的字符格式为 : appName`android下载地址`iOS下载地址`
注意的是, 我使用了反引号作为字符串分隔符
然后需要说明的地方就是这个配置参数加载的时候
我做了两步:
- 把每一行参数先转换成一个
lua table
- 把所有参数放到一个大的
table
里,key
就是appName
, 值就是上面的完整的table
这一部分的代码实现受限于目前对
lua
的熟悉程序, 请小伙伴们自行优化完善.
- handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local Handler = BasePlugin:extend()
Handler.VERSION = "1.0.0"
Handler.PRIORITY = 10
local iOSDevices = {
'iPhone',
'iPad',
'iPod',
'iOS',
}
local function splitStr (inputStr, sep)
if sep == nil then
sep = "%s"
end
local t={}
for str in string.gmatch(inputStr, "([^"..sep.."]+)") do
table.insert(t, str)
end
return t
end
local function not_found()
return kong.response.exit(404, "404 Not Found", {
["Content-Type"] = "text/plain",
})
end
function Handler:access(config)
Handler.super.access(self)
-- 查找{appName}参数
local pathVars = splitStr(kong.request.get_path(), '/')
if #pathVars < 3 then
not_found()
end
local appName = pathVars[3]
-- 判断客户端设备类型
local userAgent = kong.request.get_header("User-Agent") or 'android'
local isAndroid = true
for _, v in ipairs(iOSDevices) do
if string.find(userAgent, v) then
isAndroid = false
end
end
kong.log(isAndroid)
-- 加载插件配置
local appUrlTable = {}
for _, v in ipairs(config.appSettings) do
local _app = splitStr(v, '`')
appUrlTable[_app[1]] = _app
end
-- 查找appName
local appNameUrls = appUrlTable[appName]
if appNameUrls == nil then
not_found()
end
-- 根据类型返回相应实际下载URL
if isAndroid then
ngx.redirect(appNameUrls[2])
else
ngx.redirect(appNameUrls[3])
end
end
return Handler
- schema.lua
local typedefs = require "kong.db.schema.typedefs"
local string_array = {
type = "array",
default = {},
elements = { type = "string" },
}
return {
name = "app-url",
fields = {
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ appSettings = string_array },
},
},
},
},
}
0x03 插件使用
插件安装部分, 这里略过, 前文已经多次提及, 这里真的不想写了 - -!
-
添加一个
serviceService
:
-
添加一个
routeRouter
-
为
绑定插件Router
绑定插件
-
插件参数配置
配置输入内容为 jianshu`http://www.android.com`http://www.apple.com
0x04 验证
-
先来看一下正常系测试 :
命令行测试 -
再来看看异常系测试:
404测试
主要看看URL
格式不正确, 和没有配置项时的表现
-
浏览器测试
改代理
先改代理, 模拟iPhone
手机请求
失败时的请求: http://aaa.com/app/url/lala
异常请求
成功时的请求: http://aaa.com/app/url/jianshu
android.com 网站打不开, 看不到效果, 哇哈哈哈, 不想改地址重测了.
0x05 小结
是的, 我们做到了, 技术难度不大, 但真的在API
网关层面做完了..
不需要后端服务开发, 而且还带UI
配置界面, 使用体验极好 :)
PS: 昨天中午午觉后,突发耳鸣,唉, 我真的想静静了