说起后端Web服务,技术选型主要有两个方向:一是企业大型应用,这块是java称霸,选择没有悬念。还有一个是轻快小省型应用,这块选择就比较多了,比如:以开发效率著称的php,python,ruby on rails;以全栈著称的nodejs;以运行效率和并发著称的go,。
今天主要介绍的主角是lua和nginx的组合:openresty,他运行效率不输go,开发效率不输python,是追求轻快小省极致的最佳选择。
== 安装 安装官网步骤安装即可: http://openresty.org/cn/linux-packages.html
== JWT 由于 https://github.com/auth0/nginx-jwt 已经没人维护,所以这里采用 https://github.com/SkyLothar/lua-resty-jwt。
=== lua-resty-jwt安装
[source,bash]
mkdir -p /usr/local/openresty/nginx/jwt-lua/ curl -L https://github.com/SkyLothar/lua-resty-jwt/releases/download/v0.1.11/lua-resty-jwt-0.1.11.tar.gz -o /usr/local/openresty/nginx/jwt-lua/lua-resty-jwt-0.1.11.tar.gz tar -xvf /usr/local/openresty/nginx/jwt-lua/lua-resty-jwt-0.1.11.tar.gz -C /usr/local/openresty/nginx/jwt-lua/ curl -L https://raw.githubusercontent.com/jkeys089/lua-resty-hmac/master/lib/resty/hmac.lua -o /usr/local/openresty/nginx/jwt-lua/lua-resty-jwt-0.1.11/lib/resty/hmac.lua
curl -L https://raw.githubusercontent.com/SkyLothar/lua-resty-jwt/master/examples/guard.lua -o /usr/local/openresty/nginx/jwt-lua/lua-resty-jwt-0.1.11/lib/resty/guard.lua
=== 配置保护资源
.nginx.conf
[source]
error_log logs/error.log debug; # <1> http { lua_code_cache off; # <2> lua_package_path “/usr/local/openresty/nginx/jwt-lua/lua-resty-jwt-0.1.11/lib/?.lua;;”; server { listen 80; charset utf-8; location = /login { set $jwt_secret “lua-resty-jwt”; set $jwt_duration 900; content_by_lua ‘ –获取get或post参数 local request_method = ngx.var.request_method local args = nil local username = nil local password = nil –获取参数的值 if “GET” == request_method then args = ngx.req.get_uri_args() elseif “POST” == request_method then ngx.req.read_body() args = ngx.req.get_post_args() end username = args[“username”] password = args[“password”]
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 |
local jwt = require("resty.nginx-jwt") if "admin" == username and "888888" == password then -- 登录成功 jwt.login(username) else jwt.problem( ngx.HTTP_BAD_REQUEST, "VERIFICATION_FAILED", "用户名或密码错误" ) end '; } location ~ ^/api/([-_a-zA-Z0-9/]+).json { access_by_lua_file /path/to/lua/api/access_check.lua; content_by_lua_file /path/to/lua/api/$1.lua; body_filter_by_lua_file /path/to/lua/api/body_filter.lua; } location / { set $jwt_secret "lua-resty-jwt"; set $jwt_duration 900; access_by_lua ' local jwt = require("resty.nginx-jwt") local validators = require "resty.jwt-validators" local claim_spec = { leeway = validators.set_system_leeway(ngx.var.jwt_duration), iss = validators.equals_any_of({"liming.pub", "fangmou.com"}), iat = validators.is_at(), __jwt = validators.require_one_of({"lvl", "registed"}) } jwt.auth(claim_spec) '; |
echo “i am protected by jwt guard”;
proxy_pass http://my-backend.com$uri;
1 2 3 4 |
root /var/www/openresty/html; } } |
}
<1> nginx输出日志主要是 ngx.log ,输出的日志文件为:error.log,如 ngx.log(ngx.DEBUG, 'print to error.log')
,可以在nginx.conf 文件中定义日志输出级别 error_log logs/error.log notice;
, 日志级别定义<<附nginx常量>>,生产记得配置为error级别。
<2> 值为:on | off,开启或关闭 Lua 代码缓存,影响以下指令: set_by_lua_file , content_by_lua_file, rewrite_by_lua_file, access_by_lua_file 及强制加载或者reload Lua 模块等,在开发阶段非常有用,可以避免频繁启动nginx,生产环境记得关闭。
. JWT验证顺序(Header -> Request -> Cookie) 核心代码: + .nginx-jwt.lua
[source,lua]
include::nginx-jwt.lua[]
== 获取参数 . GET +
[source]
local args = ngx.req.get_uri_args()
. POST
.. 普通表单提交: -H "Content-Type: application/x-www-form-urlencoded"
访问:curl -X POST -d 'username=admin&password=888881' localhost/login
+
[source]
ngx.req.read_body() local args = ngx.req.get_post_args()
ngx.log(ngx.DEBUG, args.username)
.. 将参数json字符串格式放入body上传,客户端自己解析: application/json,
访问:curl -X POST -d '{"username": "admin", "password":"888881"}' localhost/login
+
[source]
ngx.req.read_body() local data = ngx.req.get_body_data() local cjson = require “cjson” local args = cjson.decode(data)
ngx.log(ngx.DEBUG, args.username)
.. 含有文件的表单,二进制上传:multipart/form-data
访问: curl -d "@data.json" -X POST http://localhost:3000/data
… 设置 http://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size[client_max_body_size]
… 读取Body文件
+
[source]
ngx.req.read_body() local data = ngx.req.get_body_data() if nil == data then local data_file = ngx.req.get_body_file() ngx.log(ngx.DEBUG, “>> temp file: “.. data_file) if data_file then data = getFile(data_file) end
end
== 访问
[source,bash]
curl -i http://127.0.0.1 -H ‘Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.kFN4dVqqziINGpuP1rB6vbCqax7IUcSVT6zUG3udEtM’
== 附nginx常量
ngx.arg[index] #ngx指令参数,当这个变量在set_by_lua或者set_by_lua_file内使用的时候是只读的,指的是在配置指令输入的参数.
ngx.var.varname #读写NGINX变量的值,最好在lua脚本里缓存变量值,避免在当前请求的生命周期内内存的泄漏
ngx.config.ngx_lua_version #当前ngx_lua模块版本号
ngx.config.nginx_version #nginx版本
ngx.worker.exiting #当前worker进程是否正在关闭
ngx.worker.pid #当前worker进程的PID
ngx.config.nginx_configure #编译时的./configure命令选项
ngx.config.prefix #编译时的prefix选项
core constans: #ngx_lua 核心常量
ngx.OK (0)
ngx.ERROR (-1)
ngx.AGAIN (-2)
ngx.DONE (-4)
ngx.DECLINED (-5)
ngx.nil
http method constans: #经常在ngx.location.catpure和ngx.location.capture_multi方法中被调用.
ngx.HTTP_GET
ngx.HTTP_HEAD
ngx.HTTP_PUT
ngx.HTTP_POST
ngx.HTTP_DELETE
ngx.HTTP_OPTIONS
ngx.HTTP_MKCOL
ngx.HTTP_COPY
ngx.HTTP_MOVE
ngx.HTTP_PROPFIND
ngx.HTTP_PROPPATCH
ngx.HTTP_LOCK
ngx.HTTP_UNLOCK
ngx.HTTP_PATCH
ngx.HTTP_TRACE
http status constans: #http请求状态常量
ngx.HTTP_OK (200)
ngx.HTTP_CREATED (201)
ngx.HTTP_SPECIAL_RESPONSE (300)
ngx.HTTP_MOVED_PERMANENTLY (301)
ngx.HTTP_MOVED_TEMPORARILY (302)
ngx.HTTP_SEE_OTHER (303)
ngx.HTTP_NOT_MODIFIED (304)
ngx.HTTP_BAD_REQUEST (400)
ngx.HTTP_UNAUTHORIZED (401)
ngx.HTTP_FORBIDDEN (403)
ngx.HTTP_NOT_FOUND (404)
ngx.HTTP_NOT_ALLOWED (405)
ngx.HTTP_GONE (410)
ngx.HTTP_INTERNAL_SERVER_ERROR (500)
ngx.HTTP_METHOD_NOT_IMPLEMENTED (501)
ngx.HTTP_SERVICE_UNAVAILABLE (503)
ngx.HTTP_GATEWAY_TIMEOUT (504)
Nginx log level constants: #错误日志级别常量 ,这些参数经常在ngx.log方法中被使用.
ngx.STDERR
ngx.EMERG
ngx.ALERT
ngx.CRIT
ngx.ERR
ngx.WARN
ngx.NOTICE
ngx.INFO
ngx.DEBUG
##################
#API中的方法:
##################
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
== 参考 - http://openresty.org/cn/[openresty官网] - https://moonbingbing.gitbooks.io/openresty-best-practices/[openresty最佳实践]