“OpenResty是一个基于Nginx与Lua的高性能Web平台,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态Web应用、Web服务和动态网关。”
“OpenResty通过汇聚各种设计精良的Nginx模块(主要由OpenResty团队自主开发),从而将Nginx有效地变成一个强大的通用Web应用平台。这样,Web开发人员和系统工程师可以使用Lua脚本语言调动Nginx支持的各种C以及Lua模块,快速构造出足以胜任10K乃至1000K以上单机并发连接的高性能Web应用系统。”
“OpenResty的目标是让你的Web服务直接跑在Nginx服务内部,充分利用Nginx的非阻塞I/O模型,不仅仅对HTTP客户端请求,甚至于对远程后端诸如MySQL、PostgreSQL、Memcached以及Redis等都进行一致的高性能响应。”
从以上官网描述里我们可以知道,OpenResty官网对其定位是以Nginx为核心集成Lua,打造一个兼具开发效率和高性能的服务端开发平台。
OpenResty的核心是基于Nginx的一个C模块(lua-Nginx-module),该模块将LuaJIT嵌入到Nginx服务器中,并对外提供一套完整的LuaAPI,透明地支持非阻塞I/O,提供了轻量级线程、定时器等高级抽象。
我们可以用Lua语言来进行字符串和数值运算、查询数据库、发送HTTP请求、执行定时任务、调用外部命令等,还可以用FFI的方式调用外部C函数。这基本上可以满足服务端开发需要的所有功能。
掌握好了OpenResty,我们就可以同时拥有脚本语言的开发效率和迭代速度,以及NginxC模块的高并发和高性能优势。
下面为大家介绍本文大纲:
OpenResty的安装有多种方法,比如使用操作系统的包管理器、源码编译或者docker镜像。推荐优先使用yum、apt-get、brew这类包管理系统,来安装OpenResty。
对于MacOSX或macOS用户,强烈推荐您使用homebrew包管理工具安装OpenResty。可以直接使用下面这一条命令:
brewinstallopenresty/brew/openresty对于一些常见的Linux发行版本(Ubuntu、Debian、CentOS、RHEL、Fedora、OpenSUSE、Alpine和AmazonLinux),OpenResty提供官方预编译包。确保首先用这种方式来安装。这里用CentOS举例,可以使用如下方式,
sudoyuminstall-yopenrestyDocker安装Docker安装的方式就最为简单了,只需要输入以下命令,就可以获取打包好的镜像。
dockerpullopenresty/openresty目录结构安装OpenResty成功后的目录结构如下(以默认安装目录为例):
/usr/local/openresty/#安装主目录├──bin#存放可执行文件├──luajit#LuaJIT运行库├──lualib#Lua组件├──Nginx#Nginx核心运行平台├──pod#参考手册(restydoc)使用的数据└──site#包管理工具(opm)使用的数据启动服务yum安装完后,就可以直接运行openresty命令,启动OpenResty服务。
/usr/local/openresty/bin/openresty#启动OpenResty服务OpenResty默认开启了localhost:80服务,使用wget或者curl这样的工具就可以验证OpenResty是否正常工作:
/usr/local/openresty/bin/openresty-sstop#停止OpenResty服务/usr/local/openresty/bin/openresty-sreload#重新加载Nginx配置文件/usr/local/openresty/bin/openresty-t#检查Nginx配置文件是否正确/usr/local/openresty/bin/openresty-c#指定配置文件启动OpenResty的操作命令跟Nginx保持一致。可以执行openresty-h以及nginx-h对比看出,
如果你想安装命令行工具resty,那么可以像下面这样安装openresty-resty包:
sudoyuminstall-yopenresty-restyresty是一个cli工具,可以使用-e参数可以在命令行里直接执行Lua代码,我们可以在命令行执行如下命令,
[root@VM-4-5-centos~]#resty-e"print('helloworld')"helloOpenRestyresty工具还有很多选项用于配置行为,非常灵活,-e之外较常用的有
包管理工具opm
跟大多数语言一样有包管理工具一样,OpenResty也有自己的包管理工具opm(OpenRestyPackageManager),opm在openresty-opm包里,安装命令如下,
sudoyuminstall-yopenresty-opmopm是OpenResty自带的包管理器,在你安装好OpenResty之后,就可以直接使用。一些常见用法如下,
首先找到OpenResty安装目录下nginx/conf/nginx.conf文件,在server下新增OpenResty的content_by_lua指令,里面嵌入了ngx.say的代码:
server{listen88;server_namelocalhost;location/{roothtml;indexindex.htmlindex.htm;}location/hello{content_by_lua'ngx.say("hello,world")';}}接着我们执行openresty-sreload命令,重新加载nginx.conf配置文件。没有报错的话,OpenResty的服务就已经成功启动了。
最后使用curl命令,来查看结果的返回:
[root@VM-4-5-centosconf]#curllocalhost:88/hellohello,world到这里,一个真正的OpenResty开发的helloworld程序就完成了。
我们不用专门去安装标准Lua5.1之类的环境,因为OpenResty已经不再支持标准Lua,而只支持LuaJIT。这里我介绍的Lua语法,也是和LuaJIT兼容的部分,而不是基于最新的Lua5.3,这一点需要特别注意。
在OpenResty的安装目录下,可以找到LuaJIT的目录和可执行文件。在CentOS系统下,LuaJIT的目录如下,
[root@VM-4-5-centosluajit]#cd/usr/local/openresty/luajit/bin/[root@VM-4-5-centosbin]#lltotal536lrwxrwxrwx1rootroot18Oct1211:22luajit->luajit-2.1.0-beta3-rwxr-xr-x1rootroot547728Jul1812:38luajit-2.1.0-beta3我们可以执行cpluajit/usr/local/bin/将luajit文件复制到/usr/local/bin/目录下,进而可以直接使用luajit命令。
查看LuaJIT的版本号,
[root@VM-4-5-centos~]#echo'print("helloworld")'>1.lua[root@VM-4-5-centos~]#cat1.luaprint("helloworld")[root@VM-4-5-centos~]#luajit1.luahelloworld[root@VM-4-5-centos~]#也可以使用resty来直接运行,它最终也是用LuaJIT来执行的,
不加local关键字的话,变量默认是全局的。
两个减号是单行注释
--注释多行注释
--[[多行注释多行注释--]]行尾结束Lua中代码的行尾结束都不需要添加特殊字符,这跟Java不同(Java在行尾需要添加;)。
locala='a'print(a)数据类型Lua中的数据类型不多,你可以通过type函数来返回一个值的类型,比如下面这样的操作:
[root@VM-4-5-centos~]#resty-e'print(type("helloworld"))>print(type(print))>print(type(true))>print(type(360.0))>print(type({}))>print(type(nil))>'打印如下,
stringfunctionbooleannumbertablenil这几种就是Lua中的基本数据类型了。下面我们来简单介绍一下它们。
在Lua中,有三种方式可以表达一个字符串:单引号、双引号,以及长括号([[]]),示例如下,
新建str.lua文件,写入以下内容,
locals='a'locals1="b"locals2=[[c]]print(s)print(s1)print(s2)执行luajitstr.lua返回结果如下,
abc在Lua中,字符串拼接采用..的方式,示例如下,
编辑str.lua文件,写入以下内容,
locals='a'locals1="b"locals2=[[c]]print(s)print(s1)print(s2)locals3=s..s1..s2print(s3)执行luajitstr.lua返回结果如下,
abcabc布尔值在Lua中,只有nil和false为假,其他都为true,包括0和空字符串也为真。我们可以用示例印证一下:
新建bool.lua脚本文件,写入以下内容,
locala=0localbifathenprint("true")enda=""ifathenprint("true")endprint(b)执行luajitstr.lua返回结果如下,
truetruenil在Lua中,空值就是nil。如果你定义了一个变量,但没有赋值,它的默认值就是nil,对应的就是上面示例代码的局部变量b。
Lua的number类型,是用双精度浮点数来实现的。值得一提的是,LuaJIT支持dual-number(双数)模式,也就是说,LuaJIT会根据上下文来用整型来存储整数,而用双精度浮点数来存放浮点数。示例如下,
新建number.lua脚本文件,写入以下内容,
print(type(2))print(type(2.2))print(type(0.2))print(type(2e+1))print(type(0.2e-1))print(type(7.8263692594256e-06))print(2+2)print(2+22.2)执行luajitnumber.lua返回结果如下,
numbernumbernumbernumbernumbernumber424.2函数函数在Lua中是一等公民,你可以把函数存放在一个变量中,也可以当作另外一个函数的入参和出参。示例如下,
新建fun.lua文件,写入以下代码,
--阶乘functionfactorial1(n)ifn==0thenreturn1elsereturnn*factorial1(n-1)endendprint(factorial1(5))factorial2=factorial1print(factorial2(5))执行luajitfun.lua返回结果如下,
120120分支控制Lua提供了以下两种分支控制结构语句:
Luaif语句语法格式如下:
if(布尔表达式)then--[在布尔表达式为true时执行的语句--]end以下是一个判断变量a的值是否小于20的示例,
新建if1.lua,写入以下内容,
a小于20a的值为: 10if...else语句Luaif语句可以与else语句搭配使用,在if条件表达式为false时执行else语句代码块。
Luaif...else语句语法格式如下:
if(布尔表达式)then--[布尔表达式为true时执行该语句块--]else--[布尔表达式为false时执行该语句块--]end以下是一个判断变量a值的示例,
新建if2.lua,写入以下内容,
a大于20a的值为: 100if...elseif...else语句Luaif语句可以与elseif...else语句搭配使用,在if条件表达式为false时执行elseif...else语句代码块,用于检测多个条件语句。
Luaif...elseif...else语句语法格式如下:
if(布尔表达式1)then--[在布尔表达式1为true时执行该语句块--]elseif(布尔表达式2)then--[在布尔表达式2为true时执行该语句块--]elseif(布尔表达式3)then--[在布尔表达式3为true时执行该语句块--]else--[如果以上布尔表达式都不为true则执行该语句块--]end以下是一个判断变量a值的示例,
新建if3.lua,写入以下内容,
--[定义变量--]a=100--[检查布尔条件--]if(a==10)then--[如果条件为true打印以下信息--]print("a的值为10")elseif(a==20)then--[ifelseif条件为true时打印以下信息--]print("a的值为20")elseif(a==30)then--[ifelseifcondition条件为true时打印以下信息--]print("a的值为30")else--[以上条件语句没有一个为true时打印以下信息--]print("没有匹配a的值")endprint("a的真实值为:",a)执行luajitif3.lua返回结果如下,
没有匹配a的值a的真实值为: 100循环Lua编程语言中for循环语句可以重复执行指定语句,重复次数可在for语句中控制。
Lua编程语言中for语句有两大类:
Lua编程语言中数值for循环语法格式:
新建for1.lua文件,写入以下内容,
functionf(x)print("function")returnx*2endfori=1,f(5)doprint(i)end执行luajitfor1.lua返回结果如下,
function12345678910泛型for循环泛型for循环通过一个迭代器函数来遍历所有值,类似java中的foreach语句。
Lua编程语言中泛型for循环语法格式:
--打印数组a的所有值locala={"one","two","three"}fori,vinipairs(a)doprint(i,v)endi是数组索引值,v是对应索引的数组元素值。ipairs是Lua提供的一个迭代器函数,用来迭代数组。
1 one2 two3 threeLua模块与包模块类似于一个封装库,从Lua5.1开始,Lua加入了标准的模块管理机制,可以把一些公用的代码放在一个文件里,以API接口的形式在其他地方调用,有利于代码的重用和降低代码耦合度。
Lua提供了一个名为require的函数用来加载模块。要加载一个模块,只需要简单地调用就可以了。例如:
require("cjson")--或者require"cjson"Lua比较小巧,内置的标准库并不多。在OpenResty的环境中默认支持了一些官方模块,如cjson可以直接使用,其他的一些第三方库则需要先使用lua_package_path指令配置OpenResty的文件寻址路径,又或者直接使用opm包管理工具来安装一些第三方模块。
OpenResty在内置Lua引擎中新增了一些常用的内置变量如下所示。
OpenResty在内置Lua引擎中新增了一些常用的内置常量大致如下所示。
这些内置变量和常量都可以在Lua脚本中直接使用。
OpenResty定义了一系列Nginx配置指令,用于配置何时运行用户Lua脚本以及如何返回Lua脚本的执行结果,这些指令可以直接在nginx.conf配置文件中使用。
OpenResty定义的Nginx配置指令大致如下所示。
这些指令中有9个*_by_lua指令,它们和Nginx的关系如下图所示
其中,init_by_lua只会在Master进程被创建时执行,init_worker_by_lua只会在每个Worker进程被创建时执行。其他的*_by_lua指令则是由终端请求触发,会被反复执行。
所以在init_by_lua阶段,我们可以预先加载Lua模块和公共的只读数据,这样可以利用操作系统的COW(copyonwrite)特性,来节省一些内存。
对于业务代码来说,其实大部分的操作都可以在content_by_lua里面完成,但更推荐的做法,是根据不同的功能来进行拆分,比如下面这样:
利用这些阶段的特性,我们可以一些通用逻辑进行拆分处理,比如我们可以在access阶段解密,在bodyfilter阶段加密就可以了,在content阶段的代码是不用做任何修改的。
#加密协议版本location/test{access_by_lua'...';#请求体解密content_by_lua'...';#处理请求,不需要关心通信协议body_filter_by_lua'...';#应答体加密}OpenResty在网关安全中如何应用WAF介绍Web应用防火墙(WebApplicationFirewall,简称WAF)对网站或者App的业务流量进行恶意特征识别及防护,在对流量清洗和过滤后,将正常、安全的流量返回给服务器,避免网站服务器被恶意入侵导致性能异常等问题,从而保障网站的业务安全和数据安全。
鉴于极客精神(白嫖万岁),这里介绍几款业内开源的WAF产品,
对于以上WAF产品的一些评价指标如下:
最终的的得分如下,
需要注意的是软件WAF一般在第7层中进行防御(osi模型),并非能够防御所有类型的攻击,比如ddos攻击就不能防御。不过一般云厂商提供的WAF产品也有携带了ddos攻击防御的支持,比如阿里云。
使用OpenResty作为流量入口时,我们可以通过编写一些Lua脚本来实现WAF防御的功能。Lua脚本可以在Nginx配置文件中指定,在不同的阶段执行。
对于防火墙功能,我们通常可以在access_by_lua阶段执行Lua脚本,用于匹配请求或响应的头部或内容,并根据匹配结果决定是否放行数据包或返回错误信息。
下面我将给大家演示如何使用OpenResty实现一个基于Lua的WAF(WebApplicationFirewall)功能。用来识别和阻止常见的Web攻击,如cc防御、ip黑名单、ua参数校验等。