[url=https://gist.github.com/4549121#file-utils-lua]but you can use this one[/url].
现在,这个转换是个手动过程,我们需要知道json的字段名称,但是我们也可以采用自动的方式分配json对象名称为指定的xml标签。
既然我们已经转化为xml了,我们想要给输出的xml添加额外的字段,比如时间戳怎么处理呢?
[b]添加一个时间戳 [/b]
在lua代码块中,你有整个的lua环境变量可以自由使用,因此我们使用os模块来获取当前时间。
我们仅需在ngx.say行之前添加下面几行。
require("os")
response.timestamp = xml.new("timestamp")
table.insert(bar.timestamp, os.date())
当我们调用/xml时将从api输出下面结果
<?xml version="1.0" encoding="UTF-8"?>
<response>
<sentiment>3</sentiment>
<word>hello</word>
<timestamp>Wed Jan 9 15:34:56 2013</timestamp>
</response>
酷毙了吧?怎么样,不难吧骚年 :)
[b]XML 到 JSON[/b]
为了演示例子我们做一个从xml到json的转换. 让我们在nginx配置文件中添加一个新的配置:
location ~ ^/round-trip/v1/word/(.*).json$ {
content_by_lua_file /PATH_TO/xml_to_json.lua;
}
xml_to_json.lua如下所示:
local xml = require("LuaXml")
local cjson = require "cjson"
local path = ngx.var.request:split(" ")[2]
local m = ngx.re.match(path,[=[/([^/]+)\.json]=])
local res = ngx.location.capture("/v1/word/".. m[1] .. ".xml")
local my_xml = xml.eval(res.body)
local sent_val = my_xml:find("sentiment")[1]
local word_val = my_xml:find("word")[1]
local t = {sentiment = sent_val, word = word_val}
local value=cjson.encode(t)
ngx.say(value)
正如你所看到的,我们点击我们刚刚创建的xml端点路由时,那么,我们将使用LuaXml解析xml并且使用cjson生成合理的json。
注意我们在这里没有遵循任何规范,转换xml到json一般情况下是存在一些问题的,因为xml可读性比json好。一般做转换时你需要遵循一定的规范,比如BadgerFish 或者 Parker,或者你自己创建的规范。
[b]2) 重写API方法[/b]
使用nginx重写你的api方法是件微不足道的事,这样对于开发者开说就更容易使用他们的api了。典型的例子就是旧的扭曲的API,我们想对其美化使得它对REST更加友好。
解决这个问题的一个方式是修改API源码的路由。然而,很多时候你不想改变源码,虽然改变源码也能实现,但是是一种落后的方法。克服那些接触源码的担忧,可以在nginx上添加一层,这样就不用接触和重新部署那些奇怪的代码了 :-)
为了用例子来说明,我们将转换一个类似于REST的API方法
/v1/word/WORD.json
对于一些使用查询参数的更“漂亮的”方式,如下:
/sentiment?action=word&version=v1&word=WORD
这种“升级”可以有多种方式实现。 对于熟悉nginx的可以简单的通过重写规则来解决这个问题。如果你更喜欢使用“sysadmin”方式,你可以按如下方式:
location ~ /sentiment$ {
content_by_lua '
local params = ngx.req.get_query_args()
if (params.action == "word" and params.version ~= nil) then
local res= ngx.location.capture("/".. params.version ..
"/word/" .. params.word .. ".json")
ngx.say(res.body)
end
';
}
正如上面这样。现在sentiment API也接受如下旧的API方法:
curl http://localhost:8181/sentiment?action=word&word=fantastic&version=v1
这将返回预期的JSON对象。
[b]3) 数据聚合[/b]
Nginx和lua可以帮助我们完成更为复杂的事情,像根据不同的方法组成一个全新的API方法。
在例子中,我们通过创建一个新的方法扩展了Sentiment API,这个方法返回一个句子中最有情感价值的单词。
或许使用这样的方法不值得大谈特谈:-D但是每次你都希望通过调用一个API完成4个方法调用,或者你可以通过单个任务调用其他3个不同的方法。你可以把你的方法聚集到一个API方法里来供应用程序调用!
让我们继续看这个例子,首先我们需要添加一个新的配置,
location ~ ^/v1/max/(.*).json$ {
content_by_lua_file /PATH_TO/max.lua;
}
接下来,我们只需要把聚集方法写到lua脚本里:
local path = ngx.var.request:split(" ")[2] -- path
local t={}
local cjson = require "cjson"
ngx.log(0, path[2])
local m = ngx.re.match(path,[=[^/v1/max/(.+).json]=])
local words = m[1]:split("+") -- words in the sentence
local max = nil
for i,k in pairs(words) do
local res_word = ngx.location.capture("/v1/word/".. k .. ".json" )
local value=cjson.new().decode(res_word.body)
if max == nil or max.sentiment < value.sentiment then
max = value
end
end
ngx.say(cjson.new().encode(max))
如你所见,他不能再简单了。首先,我们获取到句子,切分单词,然后对每个单词调用API请求/v1/word。我们把情感分析价值较高的对象存储起来。
最终结果很简单,像下面的请求:
curl -g http://localhost:8181/v1/max/nginx+and+lua+are+amazing.json
我们获取到积极情绪最高的单词,
{"sentiment":4,"word":"amazing"}
max.lua聚合函数的逻辑可以按照你想要的更加复杂,也可以获取到任何你的API方法,不管是不是你能控制的API。
能否插件化? 完全可以。 你可以创建任意复杂的插件,然后让他们在应用程序中保持不可见。
[b]结论[/b]
我们提到的这三个例子只是使用nginx和lua做的一个玩具性质的实验。
在3scale上,我们已经把类似的架构应用于生产环境,像一些高负载环境,没有什么比这个结果更让我们高兴的了。
我们不断地发现越来越多的地方可以使用这个特性,像netflix post一篇帖子最近提醒我们减少对APIs的调用次数可以在一些大业务量终端或者有缺陷的设备上取得显著的性能提升效果。
Nginx + lua 是一个改变常规的技术,虽然它不太常用,但是相信我们的话,一旦你尝试下你就会被他的强大、灵活和简单所吸引。
扩展一个API从来没有这么简单过。爱过!