OpenResty®

OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。官网地址

安装前的准备

您必须将这些库 perl 5.6.1+, libpcre, libssl安装在您的电脑之中。 对于 Linux来说, 您需要确认使用 ldconfig 命令,让其在您的系统环境路径中能找到它们。

Debian 和 Ubuntu 用户

推荐您使用 apt-get安装以下的开发库:

1
apt-get install libpcre3-dev libssl-dev perl make build-essential curl

Mac OS X (macOS) 用户

1
brew install openresty/brew/openresty

如果你之前是从 homebrew/nginx 安装的 OpenResty,请先执行:

1
brew untap homebrew/nginx

Mac下常用命令、

1
2
3
4
5
6
# 启动
➜  ~ sudo openresty
# 关闭
➜  ~ sudo openresty -s stop
# 重载配置文件
➜  ~ sudo openresty -s reload

快速开始

我的安装目录是在/usr/local/opt/openresty/中, 其中包含nginx

1. 创建目录

创建work目录,下面测试都在work目录中.

  • work/conf 保存配置文件
  • work/logs 保存日志文件

    1
    2
    3
    
    mkdir ~/work
    cd ~/work
    mkdir logs/ conf/

    2. 准备nginx.conf 文件

    1
    
    vim conf/nginx.conf

    文件内容为

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    worker_processes  1;
    error_log logs/error.log;
    events {
    worker_connections 1024;
    }
    http {
    server {
        listen 8080;
        location / {
            default_type text/html;
            content_by_lua_block {
                ngx.say("<p>hello, world</p>")
            }
        }
    }
    }

    和我们常见的nginx.conf 类似

启动Nginx Server

首先我们先配置环境PATH, 因为我的安装目录在/usr/local/openresty 根据自己实际情况来, ⚠️ 这种配置是一次性的,当窗口关闭会失效

1
2
PATH=/usr/local/openresty/nginx/sbin:$PATH
export PATH

启动服务器

1
sudo /usr/local/opt/openresty/nginx/sbin/nginx -p `pwd`/ -c conf/nginx.conf

浏览器访问 http://localhost:8080/ 查看服务是否可以访问。正常返回hello, world

获取Nginx参数总结

ngx.var

Lua接受Nginx变量

1
2
3
4
-- 获取全部变量
var = ngx.var
-- 如接受Nginx的location的第二个变量890, http://127.0.0.1/lua_request/123/890
lua_2 = ngx.var[2] --lua_2 = 890`    

ngx.req.get_headers()

Lua 接受 Nginx 头部 header

1
2
3
4
5
6
7
8
-- 返回一个包含所有当前请求标头的Lua表
local headers = ngx.req.get_headers() 

-- 获取单个Host
headers["Host"] 或者 ngx.req.get_headers()["Host"]

-- 获取单个user-agent
headers["user-agent"] 或者 ngx.req.get_headers()['user-agent']

ngx.req.get_uri_args()

获取Get请求uri参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 请求地址
curl http://localhost:8080/?a=1&b=4

-- 获取GET参数表
local args = ngx.req.get_uri_args()

-- 获取指定变量a
args.a 

-- 获取指定变量b
args.b

ngx.req.get_post_args()

获取Post请求uri参数

1
2
3
4
5
6
7
8
-- 请求地址
curl -d "name=value&name2=value2" http://localhost:8080/

-- 获取POST参数表
local post_args = ngx.req.get_post_args()

-- 获取name
post_args['name']

ngx.req.http_version()

请求的http协议版本

ngx.req.get_method()

请求方法

ngx.req.raw_header()

原始的请求头内容

API中的常用方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
print()                         # ngx.print()方法有区别,print() 相当于ngx.log()  
ngx.ctx                         #这是一个lua的table,用于保存ngx上下文的变量,在整个请求的生命周期内都有效,详细参考官方  
ngx.location.capture()          #发出一个子请求,详细用法参考官方文档。  
ngx.location.capture_multi()    #发出多个子请求,详细用法参考官方文档。  
ngx.status                      #读或者写当前请求的相应状态. 必须在输出相应头之前被调用.  
ngx.header.HEADER               #访问或设置http header头信息,详细参考官方文档。  
ngx.req.set_uri()               #设置当前请求的URI,详细参考官方文档  
ngx.set_uri_args(args)          #根据args参数重新定义当前请求的URI参数.  
ngx.req.get_uri_args()          #返回一个LUA TABLE,包含当前请求的全部的URL参数  
ngx.req.get_post_args()         #返回一个LUA TABLE,包括所有当前请求的POST参数  
ngx.req.get_headers()           #返回一个包含当前请求头信息的lua table.  
ngx.req.set_header()            #设置当前请求头header某字段值.当前请求的子请求不会受到影响.  
ngx.req.read_body()             #在不阻塞ngnix其他事件的情况下同步读取客户端的body信息.[详细]  
ngx.req.discard_body()          #明确丢弃客户端请求的body  
ngx.req.get_body_data()         #以字符串的形式获得客户端的请求body内容  
ngx.req.get_body_file()         #当发送文件请求的时候,获得文件的名字  
ngx.req.set_body_data()         #设置客户端请求的BODY  
ngx.req.set_body_file()         #通过filename来指定当前请求的file data  
ngx.req.clear_header()          #清求某个请求头  
ngx.exec(uri,args)              #执行内部跳转,根据uri和请求参数  
ngx.redirect(uri, status)       #执行301或者302的重定向。  
ngx.send_headers()              #发送指定的响应头  
ngx.headers_sent                #判断头部是否发送给客户端ngx.headers_sent=true  
ngx.print(str)                  #发送给客户端的响应页面  
ngx.say()                       #作用类似ngx.print,不过say方法输出后会换行  
ngx.log(log.level,...)          #写入nginx日志  
ngx.flush()                     #将缓冲区内容输出到页面(刷新响应)  
ngx.exit(http-status)           #结束请求并输出状态码  
ngx.eof()                       #明确指定关闭结束输出流  
ngx.escape_uri()                #URI编码(本函数对逗号,不编码,而php的urlencode会编码)  
ngx.unescape_uri()              #uri解码  
ngx.encode_args(table)          #tabel解析成url参数  
ngx.decode_args(uri)            #将参数字符串编码为一个table  
ngx.encode_base64(str)          #BASE64编码  
ngx.decode_base64(str)          #BASE64解码  
ngx.crc32_short(str)            #字符串的crs32_short哈希  
ngx.crc32_long(str)             #字符串的crs32_long哈希  
ngx.hmac_sha1(str)              #字符串的hmac_sha1哈希  
ngx.md5(str)                    #返回16进制MD5  
ngx.md5_bin(str)                #返回2进制MD5  
ngx.today()                     #返回当前日期yyyy-mm-dd  
ngx.time()                      #返回当前时间戳  
ngx.now()                       #返回当前时间  
ngx.update_time()               #刷新后返回  
ngx.localtime()                 #返回 yyyy-mm-dd hh:ii:ss  
ngx.utctime()                   #返回yyyy-mm-dd hh:ii:ss格式的utc时间  
ngx.cookie_time(sec)            #返回用于COOKIE使用的时间  
ngx.http_time(sec)              #返回可用于http header使用的时间        
ngx.parse_http_time(str)        #解析HTTP头的时间  
ngx.is_subrequest               #是否子请求(值为 true or false  
ngx.re.match(subject,regex,options,ctx)     #ngx正则表达式匹配,详细参考官网  
ngx.re.gmatch(subject,regex,opt)            #全局正则匹配  
ngx.re.sub(sub,reg,opt)         #匹配和替换(未知)  
ngx.re.gsub()                   #未知  
ngx.shared.DICT                 #ngx.shared.DICT是一个table 里面存储了所有的全局内存共享变量  
    ngx.shared.DICT.get    
    ngx.shared.DICT.get_stale      
    ngx.shared.DICT.set    
    ngx.shared.DICT.safe_set       
    ngx.shared.DICT.add    
    ngx.shared.DICT.safe_add       
    ngx.shared.DICT.replace    
    ngx.shared.DICT.delete     
    ngx.shared.DICT.incr       
    ngx.shared.DICT.flush_all      
    ngx.shared.DICT.flush_expired      
    ngx.shared.DICT.get_keys  
ndk.set_var.DIRECTIVE          

redis

自带resty.redis,我们向下面放在content_by_lua_block中就可以啦

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 链接
local redis = require "resty.redis"
local red = redis:new()
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    writerLog(logTime.."redis connect error", errLog)
    return
end
-- 返回a的val
ngx.print(red:get('a'))

cjson

开发如何返回lua中table为json格式

1
2
3
4
local cjson = require "cjson"
local args = ngx.req.get_uri_args()

ngx.say(cjson.encode(args))

content_by_lua / content_by_lua_file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
http {
    server {
        listen 8080;
        location / {
            default_type 'text/plain';
            # 指定文件运行
            # ⚠️有可能指定文件后出现404,看下是否有权限本地直接777,然后将指定的文件移动到work目录。
            content_by_lua_file  /Users/eee/work/t.lua;
            
            # content_by_lua 代码直接写在nginx.conf中
            # content_by_lua  'ngx.say(123)';
        }
    }
}

t.lua 内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- ngx.sleep(1) -- 睡眠1秒
local args = ngx.req.get_uri_args()
-- ngx.say(tonumber(args.a) + tonumber(args.b))
-- ngx.say(cjson.encode(args))
ngx.print(ngx.req.raw_header())
ngx.say(ngx.req.get_method())
ngx.say("<p>hello, world</p>")
ngx.say("<p>hello, sxx</p>")

local cjson = require "cjson"
ngx.say(cjson.encode(args))

-- 获取redis实例
local redis = require "resty.redis"
local red = redis:new()
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
    writerLog(logTime.."redis connect error", errLog)
    return
end
-- 输出redis key 为h 的数据
ngx.say(red:get('h'))

ngx.say('1123')

完整列子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
worker_processes  4;

events {
    worker_connections  4096;
}

# optional: path of orange.conf
env ORANGE_CONF;

http {
    charset UTF-8;
    
    include ./mime.types;
    
    log_format  main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$request_time" "$ssl_protocol" "$ssl_cipher" "$http_x_forwarded_for"'
    '"$upstream_addr" "$upstream_status" "$upstream_response_length" "$upstream_response_time"';
    
    access_log  ./logs/access.log  main;
    error_log ./logs/error.log info;
    
    sendfile        on;
    keepalive_timeout  65;
    # ---------------------------------------------------------
    # lua_package_path 设置纯 Lua 扩展库的搜寻路径(';;' 是默认路径):
    lua_package_path '/usr/local/orange/?.lua;/usr/local/lor/?.lua;;';
    # lua_code_cache 开启lua缓存 重启后生效
    lua_code_cache on;
    # lua_shared_dict 定义一块名为orange_data的共享内存空间,内存大小为20m的空间,让几个不相干的进程都能访问存储在这里面的变量数据
    lua_shared_dict orange_data 20m;
    
    # init_by_lua、 init_by_lua_block、 init_by_lua_file、 是在nginx主进程加载nginx配置文件的时候在lua虚拟机全局范围内调用的
    init_by_lua_block {
        local orange = require("orange.orange")
        local env_orange_conf = os.getenv("ORANGE_CONF")
    }
    
    # init_worker_by_lua、 init_worker_by_lua_block、 init_worker_by_lua _file、是在nginx工作进程启动时调用的。
    init_worker_by_lua_block {
        local orange = context.orange
        orange.init_worker()
    }
    
    server {
        listen 8080;
        server_name  www.fluobo.cn;
        location / {
            # 执行流程
            # set_by_lua*: 流程分支处理判断变量初始化
            # rewrite_by_lua*: 转发、重定向、缓存等功能(例如特定请求代理到外网)
            # access_by_lua*: IP 准入、接口权限等情况集中处理(例如配合 iptable 完成简单防火墙)
            # content_by_lua*: 内容生成
            # header_filter_by_lua*: 响应头部过滤处理(例如添加头部信息)
            # body_filter_by_lua*: 响应体过滤处理(例如完成应答内容统一成大写)
            # log_by_lua*: 会话完成后本地异步完成日志记录(日志可以记录在本地,还可以同步到其他机器)
        }
        
        # Api 明文协议版本
        location /mixed {
            content_by_lua_file ...;       # 请求处理
        }
        
        # Api 加密协议版本修改版
        location /mixed {
            access_by_lua_file ...;        # 请求加密解码
            content_by_lua_file ...;       # 请求处理,不需要关心通信协议
            body_filter_by_lua_file ...;   # 应答加密编码
        }
    }
}