NR;TL
今天我們要實(shí)現(xiàn)的需求如下:
App
組的小伙伴們會(huì)不定期發(fā)布一個(gè)新產(chǎn)品, 包含iOS
和Android
兩個(gè)平臺(tái).
他們希望后端提供一個(gè)統(tǒng)一的下載鏈接, 可以根據(jù)手機(jī)的不同, 自動(dòng)返回相應(yīng)的實(shí)現(xiàn)下載地址.
本文會(huì)以Kong
插件的形式提供一個(gè)解決方案.
0x01 以終為始,我們的效果是什么?
我們會(huì)提供一個(gè)這樣的統(tǒng)一入口URL
:
http://aaa.com/app/url/{appName}
當(dāng)我們的插件監(jiān)聽到這個(gè)路由的請(qǐng)求時(shí),
通過分析Header
里的User-Agent
來(lái)區(qū)分手機(jī)設(shè)備類型.
然后跳轉(zhuǎn)到與appName
相應(yīng)平臺(tái)的下載結(jié)果.
這里會(huì)用到一個(gè)方法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")
說一下重點(diǎn):
- 在
access()
方法中可以使用 - 支持完整路徑
URL
跳轉(zhuǎn)
0x02 代碼實(shí)現(xiàn)
為了方便理解代碼實(shí)現(xiàn), 我們先看一下使用效果圖
插件配置時(shí), 我們需要配置一個(gè)參數(shù)項(xiàng):appSettings
它可以接收多個(gè)值.
每一個(gè)值的字符格式為 : appName`android下載地址`iOS下載地址`
注意的是, 我使用了反引號(hào)作為字符串分隔符
然后需要說明的地方就是這個(gè)配置參數(shù)加載的時(shí)候
我做了兩步:
- 把每一行參數(shù)先轉(zhuǎn)換成一個(gè)
lua table
- 把所有參數(shù)放到一個(gè)大的
table
里,key
就是appName
, 值就是上面的完整的table
這一部分的代碼實(shí)現(xiàn)受限于目前對(duì)
lua
的熟悉程序, 請(qǐng)小伙伴們自行優(yōu)化完善.
- 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}參數(shù)
local pathVars = splitStr(kong.request.get_path(), '/')
if #pathVars < 3 then
not_found()
end
local appName = pathVars[3]
-- 判斷客戶端設(shè)備類型
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
-- 根據(jù)類型返回相應(yīng)實(shí)際下載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 插件使用
插件安裝部分, 這里略過, 前文已經(jīng)多次提及, 這里真的不想寫了 - -!
-
添加一個(gè)
Service
:
-
添加一個(gè)
Router
-
為
Router
綁定插件
插件參數(shù)配置
輸入內(nèi)容為 jianshu`http://www.android.com`http://www.apple.com
0x04 驗(yàn)證
-
先來(lái)看一下正常系測(cè)試 :
-
再來(lái)看看異常系測(cè)試:
主要看看URL
格式不正確, 和沒有配置項(xiàng)時(shí)的表現(xiàn)
-
瀏覽器測(cè)試
先改代理, 模擬iPhone
手機(jī)請(qǐng)求
失敗時(shí)的請(qǐng)求: http://aaa.com/app/url/lala
成功時(shí)的請(qǐng)求: http://aaa.com/app/url/jianshu
android.com 網(wǎng)站打不開, 看不到效果, 哇哈哈哈, 不想改地址重測(cè)了.
0x05 小結(jié)
是的, 我們做到了, 技術(shù)難度不大, 但真的在API
網(wǎng)關(guān)層面做完了..
不需要后端服務(wù)開發(fā), 而且還帶UI
配置界面, 使用體驗(yàn)極好 :)
PS: 昨天中午午覺后,突發(fā)耳鳴,唉, 我真的想靜靜了