Lua和Nginx结合使用的超级指南

 Nginx作为API代理

有很多原因说明你为什使用nginx作为API代理。首先因为他是开源的;其次,Nginx有大量的安装基础,他背后有一个强大的社区支持,在性能方面也表现的非常出色。对于我们来说,这是显而易见的,如果开源软件有相同的解决方案我们为啥还要用那些私有的软件。

另外一个极大的优势就是nginx对lua的支持,nginx+lua是一个非常好的组合,它允许使用一个高性能的脚本语言扩展nginx。nginx有很多方法是自带的,但是使用lua没有限制的。

原理很简单。有没有这样的情况你更喜欢使用基于nginx的API代理而不是它自带的方法呢?呵呵,你可以非常简单的添加。

扩展目标: Sentiment API (可以是任何API)

为了展示nginx和lua的强大之处,我们将使用一个简单的REST API调用Sentiment,不使用任何一行API源码(可以直接使用github上的)。

Sentiment API 是一个非常基础的API,它返回一个有情感价值分析的单词或者句子。比如,下面的请求(你可以自己试试)

代码如下:

curl http://api-sentiment.3scale.net/v1/word/fantastic.json

上面的请求将返回包含fantastic情感分析单词的json串。

代码如下:

{"sentiment":4,"word":"fantastic"}

我们有了扩展对象,接下来继续吧…

分析部分: 扩展Sentiment API

有很多方式你可以扩展Sentiment API(或者你自己的API)。为了符合这篇文章的主题,我们限定了三种场景来展示nginx+lua的强大之处和可扩展性。
1) 想做数据转换?

想把输出数据从json转换为xml格式?或者更好一些,把xml转换为json。
2) 想更换你的API方法的签名?

你想把漂亮的 REST形式的url path/v1/word/WORD.json替换为貌似更加 “漂亮的” 签名方式 parameters/sentiment?action=word&word=WORD&version=v1.

我们不能容忍变成那样的路径方式:-)这应该是个反面例子,自从Sentiment API改为RESTful方式,这个例子应该反过来了。
3) 想创建一个新的API方法?

没问题,你可以自己创建个新的API方法得到你想要的,或者有可能,你可以不接触任何API源码来扩展你的API方法。

我们将展示下创建一个Sentiment API的新方法:用来查找在一个句子中最有情感分析价值的单词。这个方法在Sentiment API没有提供,但是我们可以通过nginx和lua创建它。

这个案例无论对于用户还是对于API开发者多有很大的潜能。基本上可以允许你自己在不修改源码的基础上定制API,或者,这还有酷毙了的一部分,允许你定制你不能控制的API。想在包含一系列方法的Twitter API上创建自己的方法?当然可以,结果可能是你的应用程序代码更简洁了。

这只是使用nginx+lua扩展的三个简单例子。还有一些其他例子我们只是为了突出使用nginx+lua扩展你的API有多么简单和强大。

让我们开始做点实际的东西吧…

使用lua扩展nginx

我们假设你应经对nginx基础概念有了了解(servers, locations, 等…)

扩展nginx我们必须先提供lua的支持,它不是ngnix的一部分。我们无需担心因为已经有很多组件编译进了lua,像:

如果你坚持自己安装 :-) 你可以自己安装下面的组件:

事实上,如果你不想用lua而是更喜欢perl,查看下这个页面look at the CPAN page,这里提供了全部文档。

基础部分

整个处理过程是代理请求到真实的API,主要通过下面过程:1)捕获请求传递给API 2)响应请求,接着 3)处理响应。

下面展示了nginx配置文件中的相关配置:

代码如下:

upstream backend {
  # service name: API ;
  server api-sentiment.3scale.net:80 max_fails=5 fail_timeout=30;
}

server {
  listen 8181;

location ~ /v1/word/(.*)\.json$ {
    proxy_pass http://backend/v1/word/$1.json ;
  }
}

这里我们只配置了一个路由地址:/v1/word/your-word-goes-here.json。这个路由在Sentiment API上返回一个结果. Nginx 只是负责做一个简单的传递。

你可以启动你的nginx (监听本地端口 8181) ,用下面的方式发送一个请求

代码如下:

curl http://localhost:8181/v1/word/fantastic.json

它将返回一个同样的json

代码如下:

{"sentiment":4,"word":"fantastic"}

我们只是给真实的Sentiment API做了个中转。让我们带着兴趣继续吧…

1) 数据转换
JSON 到 XML

在nginx配置文件添加新的路由,如下:

代码如下:

upstream backend {
  # service name: API ;
  server api-sentiment.3scale.net:80 max_fails=5 fail_timeout=30;
}

server {
  listen 8181;

location ~ /v1/word/(.*)\.json$ {
    proxy_pass http://backend/v1/word/$1.json ;
  }

location ~ /v1/word/(.*)\.xml$ {
    content_by_lua_file /PATH_TO/json_to_xml.lua;
  }
}

我们仅添加了一个新路由:/v1/word/your-word-goes-here.xml。这个路由将把 Sentiment API输出的json转换为xml格式。我们没有做一个传递,而是通过调用一个lua文件实现逻辑的(不要担心,很简单)。

现在你可以做下面的工作了,

curl http://localhost:8181/v1/word/fantastic.xml

你将获取到下面信息:

代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<response>
  <sentiment>4</sentiment>
  <word>fantastic</word>
</response>

这里发生了什么?好吧,我们基本上把Sentiment API输出的json数据转换成了xml格式!
lua的魔法

转化json为xml需要一系列的lua libs:

  • cjson :通过luarocks安装或者在项目主页上下载手动安装。
  • luaXml :  我们将使用一个补丁版本来使他在nginx下工作,你可以在这里下载补丁版本here

如果你在安装luaxml时遇到问题,那么可以直接安装luarocks作为替代方案,把luaxml文件放到openresty里面的lua lib目录下,查找lua libs默认目录就是openresty。

当我们访问xml路由时,nginx将调用lua文件

代码如下:

local xml = require("LuaXml")
require("os")
local cjson = require "cjson"
 
local path = ngx.var.request:split(" ")[2]
local m = ngx.re.match(path,[=[/([^/]+)\.(json|xml)$]=]) -- match last word
local res = ngx.location.capture("/v1/word/".. m[1] .. ".json" )
local value=cjson.new().decode(res.body)
 
local response = xml.new("response")
 
response.word= xml.new("word")
response.sentiment = xml.new("sentiment")
response.timestamp = xml.new("timestamp")
table.insert(response.word, value.word)
table.insert(response.sentiment, value.sentiment)
table.insert(response.timestamp, os.date())
 
ngx.say('<?xml version="1.0" encoding="UTF-8"?>', xml.str(response,0))

这个lua文件做了一个本地json请求,使用下面的配置

代码如下:

local res = ngx.location.capture("/v1/word/".. m[1] .. ".json" )

它直接请求的真实的Sentiment API,一旦你有了json对象,我们就可以按照规则转化为xml格式,从

代码如下:

{"sentiment":4,"word":"fantastic"}

代码如下:

<?xml version="1.0" encoding="UTF-8"?>

<response>

<sentiment>4</sentiment>

<word>fantastic</word>

</response>

注意split函数在lua中不存在,但是你可以参照这里 but you can use this one.

现在,这个转换是个手动过程,我们需要知道json的字段名称,但是我们也可以采用自动的方式分配json对象名称为指定的xml标签。

既然我们已经转化为xml了,我们想要给输出的xml添加额外的字段,比如时间戳怎么处理呢?

添加一个时间戳

在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>

酷毙了吧?怎么样,不难吧骚年 :)
XML 到 JSON

为了演示例子我们做一个从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,或者你自己创建的规范。

2) 重写API方法

使用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对象。

3) 数据聚合

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。

能否插件化? 完全可以。 你可以创建任意复杂的插件,然后让他们在应用程序中保持不可见。

结论

我们提到的这三个例子只是使用nginx和lua做的一个玩具性质的实验。

在3scale上,我们已经把类似的架构应用于生产环境,像一些高负载环境,没有什么比这个结果更让我们高兴的了。

我们不断地发现越来越多的地方可以使用这个特性,像netflix post一篇帖子最近提醒我们减少对APIs的调用次数可以在一些大业务量终端或者有缺陷的设备上取得显著的性能提升效果。

Nginx + lua 是一个改变常规的技术,虽然它不太常用,但是相信我们的话,一旦你尝试下你就会被他的强大、灵活和简单所吸引。

扩展一个API从来没有这么简单过。爱过!

(0)

相关推荐

  • Nginx+Lua+Redis构建高并发Web应用

    本文介绍如何用Nginx+Lua+Redis来构建高并发Web应用,Curl请求Nginx,Nginx通过Lua查询Redis,返回json数据. 一.安装1.安装lua-redis-parser 复制代码 代码如下: #git clone https://github.com/agentzh/lua-redis-parser.git #export LUA_INCLUDE_DIR=/usr/include/lua5.1 #make CC=gcc #make install CC=gcc 2.安

  • 使用nginx+lua实现信息访问量统计

    根据URI参数后去信息类型和信息ID,通过lua client for memcached插入memcached 复制代码 代码如下: require('Memcached') local args = ngx.req.get_uri_args()   if (ngx.var.remote_addr == '192.168.1.5') then         local key = args['k']         local class = args['c']         local

  • linux系统安装Nginx Lua环境

    亦可参考官方安装指南: lua-nginx-module Installation 这是我总结的安装,供参考: 需要最新版的Nginx,LuaJIT,ngx_devel_kit,lua-nginx-module等安装文件: Nginx LuaJIT Lua或者LuaJIT都是可以的,但是出于性能的考虑,推荐安装LuaJIT ngx_devel_kit lua-nginx-module 参考命令下载: $ curl -O http://nginx.org/download/nginx-1.10.1

  • nginx中使用lua脚本的方法

    Lua是一种跟JavaScript很像的语言,Ngix_Lua同样使用异步单线程,语法甚至比JS更加简单,之前的评测指出,Ngix_lua的性能几乎是Node.JS的一倍. Nginx 特点 1.流行的高性能HTTP服务器 2.事件驱动(异步)架构 3.少量且可测内存占用 4.声明性配置语言 5.基于C的可扩展模块 通过lua-nginx-module即可在nginx上启动lua脚本. 一个例子: 复制代码 代码如下: location / {     content_by_lua '     

  • 安装Nginx+Lua开发环境

    首先我们选择使用OpenResty,其是由Nginx核心加很多第三方模块组成,其最大的亮点是默认集成了Lua开发环境,使得Nginx可以作为一个Web Server使用.借助于Nginx的事件驱动模型和非阻塞IO,可以实现高性能的Web应用程序.而且OpenResty提供了大量组件如Mysql.Redis.Memcached等等,使在Nginx上开发Web应用更方便更简单.目前在京东如实时价格.秒杀.动态服务.单品页.列表页等都在使用Nginx+Lua架构,其他公司如淘宝.去哪儿网等. 安装环境

  • openresty中使用lua-nginx创建socket实例

    Lua语言太强大了,至少我是这样觉得的.原始的Lua没有Socket功能,需要使用者下载Lua socket组件,require一下才行.而lua-nginx模块自带了socket功能,而且是100%的非阻塞模式,再次感谢作者章亦春. 使用socket功能很简单,只有几个简单的方法即可主要就是有TCP和UDP的区别.(这里只是lua文件,其他请见Hello world 文章) 复制代码 代码如下: local sock = ngx.socket.tcp() local ok,err = sock

  • Lua和Nginx结合使用的超级指南

     Nginx作为API代理 有很多原因说明你为什使用nginx作为API代理.首先因为他是开源的:其次,Nginx有大量的安装基础,他背后有一个强大的社区支持,在性能方面也表现的非常出色.对于我们来说,这是显而易见的,如果开源软件有相同的解决方案我们为啥还要用那些私有的软件. 另外一个极大的优势就是nginx对lua的支持,nginx+lua是一个非常好的组合,它允许使用一个高性能的脚本语言扩展nginx.nginx有很多方法是自带的,但是使用lua没有限制的. 原理很简单.有没有这样的情况你更

  • 使用Lua编写Nginx服务器的认证模块的方法

    过去两天里,我解决了一个非常有趣的问题.我用一个nginx服务器作为代理,需要能够向其中添加一个认证层,使其能够使用外部的认证源(比如某个web应用)来进行验证,如果用户在外部认证源有账号,就可以在代理里认证通过. 需求一览 我考虑了几种解决方案,罗列如下: 用一个简单的Python/Flask模块来做代理和验证. 一个使用subrequests做验证的nginx模块(nginx目前可以做到这一点) 使用Lua编写一个nginxren认证模块 很显然,给整个系统添加额外请求将执行的不是很好,因为

  • Nginx服务器初期基本配置指南

    一.准备 pcre,有关正则表达式匹配:zlib,用于压缩.这些就不细说了,如果要安装最简版的nginx,记得准备好这两样东西就好了. 用root账户启动服务是比较危险的!  前段时间,测试服务器被黑掉了,终归到底是通过一个root启动的服务上传了木马,最后连ssh都屏蔽了,活生生成为一台肉鸡... 所以,惨痛的经验告诉我,一定要为服务建立对应的组和用户,限制访问权限,降低风险!  这里为nginx建立一个www组,并建立一个不登录的账户nginx: #追加一个www组 groupadd -f

  • 使用Python来编写HTTP服务器的超级指南

    首先,到底什么是网络服务器? 简而言之,它是在物理服务器上搭建的一个网络连接服务器(networking server),永久地等待客户端发送请求.当服务器收到请求之后,它会生成响应并将 其返回至客户端.客户端与服务器之间的通信,是以HTTP协议进行的.客户端可以是浏览器,也可以是任何支持HTTP协议的软件. 那么,网络服务器的简单实现形式会是怎样的呢?下面是我对此的理解.示例代码使用Python语言实现,不过即使你不懂Python语言,你应该也可以从代码和下面的 解释中理解相关的概念: imp

  • Nginx反向代理入门实战指南

    目录 概述 反向代理的作用 实践Nginx反向代理内网穿透8081端口 实现步骤 实现方式二配置upstream Nginx配置https支持 总结 概述 Nginx反向代理(Reverse Proxy):反向代理是指服务器根据客户端的请求,从其关系的一组或多组后端服务器(如Web服务器)上获取资源,然后再将这些资源返回给客户端,客户端只会得知反向代理的IP地址,而不知道在代理服务器后面的服务器簇的存在. 反向代理的作用 对客户端隐藏服务器(集群)的IP地址 安全:作为应用层防火墙,为网站提供对

  • Go语言编程入门超级指南

    1.序言 Golang作为一门出身名门望族的编程语言新星,像豆瓣的Redis平台Codis.类Evernote的云笔记leanote等. 1.1 为什么要学习 如果有人说X语言比Y语言好,两方的支持者经常会激烈地争吵.如果你是某种语言老手,你就是那门语言的"传道者",下意识地会保护它.无论承认与否,你都已被困在一个隧道里,你看到的完全是局限的.<肖申克的救赎>对此有很好的注脚: [Red] These walls are funny. First you hate 'em,

  • 使用Browserify配合jQuery进行编程的超级指南

    引言 1. manually 以前,我新开一个网页项目,然后想到要用jQuery,我会打开浏览器,然后找到jQuery的官方网站,点击那个醒目的"Download jQuery"按钮,下载到.js文件,然后把它丢在项目目录里.在需要用到它的地方,这样用<script>引入它: <script src="path/to/jquery.js"></script> 2. Bower 后来,我开始用Bower这样的包管理工具.所以这个过程

  • 在Java SE上使用Headless模式的超级指南

    这篇文章介绍怎样在标准Java(Java SE,也称作J2SE)平台上用Headless模式. Headless模式是在缺少显示屏.键盘或者鼠标时的系统配置.听起来不可思议,但事实上你可以在这中模式下完成不同的操作,甚至是用图形数据也可以. 哪里才能用到此模式呢?想想你的应用不停的生成一张图片,比如,当用户每次登陆系统是都要生成一张认证图片.当创建图片时,你得应用既不需要显示器也不需要键盘.让我们假设一下,现在你的应用有个主架构或者专有服务器,但这个服务没有显示器,键盘或者鼠标.理想的决定是用环

  • 把Lua编译进nginx步骤方法

    Lua编译进nginx的方法 1.先安装lua-jit,网上说也可以下载lua,不过lua-jit效率比较高,地址:http://luajit.org/download.html 我下的是2.0.3版本的 下载解压后,直接make && make install就可以了,默认安装在/usr/local目录 2.下载ngx devel包 地址:https://github.com/simpl/ngx_devel_kit/tags 注意,这个是一个nginx的扩展,不需要编译 3.下载ngin

  • 通过lua来配置实现Nginx服务器的防盗链功能

    下载服务器时常被人盗链,时间久了导致服务器大量资源浪费,由于服务器使用nginx做为web服务器.nginx的防盗链方法有很多,可以使用现成的防盗链模块nginx-accesskey-2.0.3,编译ningx时添加此模块即可. 由于服务其他业务需要,所以nginx编译了lua模块,所以就想通过lua来实现下载服务器的防盗链功能(通过lua的Nginx模块lua_nginx_module.这里不再详细介绍配置过程),这样就可以免去了accesskey模块.原理就是生成经过处理过的下载链接,然后下

随机推荐