Nginx二次开发
一、Lua语言基础
1、 介绍
Lua 是由巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组于1993年开发的一种轻量、小巧的脚本语言,用标准 C 语言编写,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。
lua官网:http://www.lua.org/
Lua/luajit/nginx/openresty关系,lua是脚本语言,luajit是lua的功能包与解析器(相当于java对应的jdk),nginx可以内嵌luajit(承载的容器),openresty把额外扩展包集成到nginx里(相当于spring脚手架)
2、IDE
windows版lua下载
http://joedf.ahkscript.org/LuaBuilds/
http://luabinaries.sourceforge.net/
EmmyLua插件
https://github.com/EmmyLua/IntelliJ-EmmyLua
https://emmylua.github.io/zh_CN/
LDT 基于eclipse
https://www.eclipse.org/ldt/
3、Lua基础语法
3.1 保留关键字和注释
保留关键字
and/break/do/else/elseif/end/false/for/function if/in/local/nil/not/or/repeat/return/then/true/until/while
注释
1 | -- 两个减号是行注释 |
3.2 变量
1. 数字类型
Lua的数字只有double型,64bits
1 | -- 可以以如下的方式表示数字 |
2. 字符串
可以用单引号,也可以用双引号,也可以使用转义字符’\n’(换行),’\r’(回车), ‘\t’ (横向制表),’\v’(纵向制表),’’(反斜杠),’"’(双引号), 以及’’’(单引号)等等
下面的四种方式定义了完全相同的字符串(其中的两个中括号可以用于定义有换行的字符串)
1 | --[[ 输出alo |
3. 空值
C语言中的NULL在Lua中是nil,比如你访问一个没有声明过的变量,就是nil
4. 布尔类型
只有nil和false是 false;数字0,’’ 空字符串(’\0’)都是true
5. 作用域
lua中的变量如果没有特殊说明,全是全局变量,那怕是语句块或是函数里。
变量前加local关键字的是局部变量
3.3 控制语句
1. while循环
1 | local i = 0 |
2. if-else
1 | local function main() |
3. for循环
1 | sum = 0 |
4. 函数
1 | -- ==========================第一个============= |
3.4 返回值
1 | name, age,bGay = "shawn", 37, false, "shawn@hotmail.com" |
3.5 Table
key,value的键值对 类似 map
1 | local function main() |
3.6 数组
1 | local function main() |
3.7 成员函数
1 | local function main() |
二、Openresty Nginx + Lua
1、openresty简介与安装
1.1 openresty简介
OpenResty通过汇聚各种设计精良的Nginx模块(主要由OpenResty团队自主开发)将Nginx变成一个强大的通用Web应用平台。这样,Web开发人员和系统工程师可以使用Lua脚本语言调动Nginx支持的各种C以及Lua模块,快速构造出足以胜任10KB乃至1000KB以上单机并发连接的高性能Web应用系统。
OpenResty的目标是让Web服务直接跑在Nginx服务内部,充分利用Nginx的非阻塞I/O模型,不仅对HTTP客户端请求,甚至对远程后端(如MySQL、PostgreSQL、Memcached以及Redis等)都进行一致的高性能响应。
参考:一文带你详解Nginx/OpenResty
执行原理
OpenResty中,每个Worker进程使用一个Lua VM(Lua虚拟机),当请求被分配到Worker时,将在这个Lua VM中创建一个协程,协程之间数据隔离,每个协程都具有独立的全局变量。
ngx_lua是将Lua嵌入Nginx,让Nginx执行Lua脚本,并且高并发、非阻塞地处理各种请求。Lua内置协程可以很好地将异步回调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程序,ngx_lua会在进行阻塞的IO操作时自动中断,保存上下文,然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会恢复上下文,程序继续执行,这些操作对用户程序都是透明的。
每个Nginx的Worker进程持有一个Lua解释器或LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的context上下文会被Lua轻量级的协程分隔,从而保证各个请求是独立的
1.2 openresty安装
预编译安装
http://openresty.org/cn/linux-packages.html
1 | # 服务命令 |
源码编译安装
http://openresty.org/cn/download.html
1 | # 最小版本基于nginx1.21 |
测试lua脚本
此时的nginx和配置文件都是openresty包提供的
1 | # 在Nginx.conf 中写入 |
2、lua-nginx模块
github官方文档:https://github.com/openresty/lua-nginx-module#readme
wiki文档:https://www.nginx.com/resources/wiki/modules/lua/
文档:https://openresty-reference.readthedocs.io/en/latest/
2.1 热部署与lua配置文件
1 | # 设置包含的lua文件块 |
2.2 lua处理http请求相关操作
- set_by_lua
修改nginx变量
- rewrite_by_lua
修改uri
- access_by_lua
访问控制
- header_filter_by_lua
修改响应头
- boy_filter_by_lua
修改响应体
- log_by_lua
日志
2.3 获取Nginx uri中的所有变量
1 | local uri_args = ngx.req.get_uri_args() |
2.4 获取Nginx请求头信息
1 | local headers = ngx.req.get_headers() |
2.5 获取post请求参数
1 | ngx.req.read_body() |
2.6 其他信息
1 | -- http协议版本 |
2.7 常用参数与示例
ngx常用用法,例如
say
print可以作为content返回,redirect
可以实现跳转,ngx.location.capture
可以请求一个url,一般是内部url。exit
可以返回状态码,shared
可以作为一个所有进程共享的缓存kv池。
log
ngx.log(ngx.INFO, “日志内容”) 。级别有ngx.STDERR/ngx.EMERG /ngx.ALERT/ ngx.CRIT /ngx.ERR /ngx.WARN/ ngx.NOTICE/ ngx.INFO /ngx.DEBUG
var与ctx
ngx.var
可以获取或者修改nginx上下文的$xx变量,包括内置的变量,和在location范围内set的变量。nginx上下文的变量参考
一个request上下文的ngx.ctx是一个table可以自由的赋值和获取。例如rewrite_by_lua中ngx.ctx.a=10, content_by_lua中能拿到ngx.ctx.a
location.capture
1 | local res = ngx.location.capture('/foo?a=1&b=3&c=%3a') -- get请求, res有status body header和truncated属性 |
req
- ngx.req.get_method() 返回大写的字符串GET
- ngx.req.get_headers() 返回 kv table
- ngx.req.set_header(k, v)
- ngx.req.get_body_data() 返回是string
- ngx.req.get_uri_args() get查询参数,返回table
- ngx.req.get_post_args() form类型的post,返回table,err
hash与编码
- newstr = ngx.encode_base64(str, no_padding?)
- newstr = ngx.decode_base64(str)
- digest = ngx.md5(str)
- str = ngx.encode_args(table)
- table = ngx.decode_args(str, max_args?)
- newstr = ngx.escape_uri(str)
- newstr = ngx.unescape_uri(str)
re正则
- local m, err = ngx.re.match(“hello, 1234”, “[0-9]{3}”) – m[0] == “123” 这个正则比lua自带的正则强一点
- local from, to, err = ngx.re.find(s, “([0-9]+)”, “jo”)
- local iterator, err = ngx.re.gmatch(“hello, world!”, “([a-z]+)”, “i”)
- local newstr, n, err = ngx.re.sub(“hello, 1234”, “([0-9])[0-9]”, “[$0][$1]”)
os相关的
- ngx.sleep(n) 单位是s
- ngx.time() ngx.utctime()ngx.now() ngx.today()… 时间相关的
- ngx.exec(“shell”)
1 | # 其中#注释掉的可以在需要的时候开启并修改,没有注释掉的(除了下面location示例)不要删掉,基本都是必须的配置项。 |
3、Nginx常用模块–数据缓存
3.1 nginx全局缓存
lua_shared_dict shared_data 1m;
该配置文件配置在nginx.conf下的http模块,全局共享,会产生锁,是线程安全的
1 | local shared_data = ngx.shared.shared_data |
3.2 lua-resty-lrucache
Lua 实现的一个简单的 LRU 缓存,适合在 Lua 空间里直接缓存较为复杂的 Lua 数据结构:它相比 ngx_lua 共享内存字典可以省去较昂贵的序列化操作,相比 memcached 这样的外部服务又能省去较昂贵的 socket 操作
官方文档:https://github.com/openresty/lua-resty-lrucache
1 | # 引用lua文件,会调用cache.lua |
自定义函数,放在lualib/my/cache.lua
1 | local _M = {} |
3.3 lua-resty-redis
使用和redis2-nginx-module差不多,一个是通过lua直接操作
3.4 lua-resty-mysql
不推荐带参数
1 | local mysql = require "resty.mysql" |
4、模板引擎
4.1 简介
lua-resty-template模板引擎可以认为是JSP,其最终会被翻译成Lua代码,然后通过ngx.print输出。
安装的话首先需要下载包,然后将里面的template.lua
和template
放在lualib/resty
下
基础语法
1 | {(include_file)}:包含另一个模板文件; |
4.2 简单示例
nginx.conf进行配置
1 | server { |
模板文件
1 |
|
lua文件
1 | local template = require("resty.template") |
4.3 复杂示例
模板
1 | <!--可以引入其他html文件--> |
lua文件
1 | local template = require("resty.template") |
4.4 模板管理与缓存
模板缓存:默认开启,开发环境可以手动关闭template.caching(true)
模板文件需要业务系统更新与维护,当模板文件更新后,可以通过模板版本号或消息通知Openresty清空缓存重载模板到内存中template.cache = {}
4.5 Redis缓存+mysql+模板输出
模板
1 | {(header.html)} |
lua文件
1 | cjson = require "cjson" |
三、Lua开源项目
1、WAF
https://github.com/unixhot/waf
https://github.com/loveshell/ngx_lua_waf
优势
防止 SQL 注入,本地包含,部分溢出,fuzzing 测试,XSS/SSRF 等 Web 攻击
防止 Apache Bench 之类压力测试工具的攻击
屏蔽常见的扫描黑客工具,扫描器
屏蔽图片附件类目录执行权限、防止 webshell 上传
支持 IP 白名单和黑名单功能,直接将黑名单的 IP 访问拒绝
支持 URL 白名单,将不需要过滤的 URL 进行定义
支持 User-Agent 的过滤、支持 CC 攻击防护、限制单个 URL 指定时间的访问次数
支持支持 Cookie 过滤,URL 与 URL 参数过滤
支持日志记录,将所有拒绝的操作,记录到日志中去
2、Kong 基于Openresty的流量网关
2.1 介绍
Kong 基于 OpenResty,是一个云原生、快速、可扩展、分布式的微服务抽象层(Microservice Abstraction Layer),也叫 API 网关(API Gateway),在 Service Mesh 里也叫 API 中间件(API Middleware)。Kong 开源于 2015 年,核心价值在于高性能和扩展性。从全球 5000 强的组织统计数据来看,Kong 是现在依然在维护的,在生产环境使用最广泛的 API 网关。Kong 宣称自己是世界上最流行的开源微服务 API 网关(The World’s Most Popular Open Source Microservice API Gateway)。
2.2 核心优势
可扩展:可以方便的通过添加节点水平扩展,这意味着可以在很低的延迟下支持很大的系统负载。
模块化:可以通过添加新的插件来扩展 Kong 的能力,这些插件可以通过 RESTful Admin API 来安装和配置。
在任何基础架构上运行:Kong 可以在任何地方都能运行,比如在云或混合环境中部署 Kong,单个或全球的数据中心。
3、APISIX|ABTestingGateway
3.1 介绍
ABTestingGateway 是一个可以动态设置分流策略的网关,关注与灰度发布相关领域,基于 Nginx 和 ngx-lua 开发,使用 Redis 作为分流策略数据库,可以实现动态调度功能。
ABTestingGateway 是新浪微博内部的动态路由系统 dygateway 的一部分,目前已经开源。在以往的基于 Nginx 实现的灰度系统中,分流逻辑往往通过 rewrite 阶段的 if 和 rewrite 指令等实现,优点是性能较高,缺点是功能受限、容易出错,以及转发规则固定,只能静态分流。ABTestingGateway 则采用 ngx-lua,通过启用 lua-shared-dict 和 lua-resty-lock 作为系统缓存和缓存锁,系统获得了较为接近原生 Nginx 转发的性能。
3.2 优势
支持多种分流方式,目前包括 iprange、uidrange、uid 尾数和指定uid分流
支持多级分流,动态设置分流策略,即时生效,无需重启
可扩展性,提供了开发框架,开发者可以灵活添加新的分流方式,实现二次开发
高性能,压测数据接近原生 Nginx 转发
灰度系统配置写在 Nginx 配置文件中,方便管理员配置
适用于多种场景:灰度发布、AB 测试和负载均衡等