Openresty简介及开发
目录
openresty简介
OpenResty (也称为 ngx_openresty)是一个全功能的 Web 应用服务器,它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。 OpenResty 通过汇聚各种设计精良的 Nginx 模块, 从而将 Nginx 有效的变成一个强大的 Web 应用服务器, 这样, Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种C以及Lua 模块, 快速构造出足以胜任 10K+ 并发连接响应的超高性能Web 应用系统.
OpenResty 的目标是让你的Web服务直接跑在 Nginx 服务内部, 充分利用 Nginx 的非阻塞 I/O 模型, 不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL,PostgreSQL,Memcaches 以及 Redis 等都进行一致的高性能响应.
openresty模块
由 nginx 和lua (LuaJIT)组合而成 其中 nginx 又包含下列模块
- ArrayVarNginxModule
- AuthRequestNginxModule
- CoolkitNginxModule
- DrizzleNginxModule
- EchoNginxModule
- EncryptedSessionNginxModule
- FormInputNginxModule
- HeadersMoreNginxModule
- IconvNginxModule
- StandardLuaInterpreter
- MemcNginxModule
- Nginx
- NginxDevelKit
- LuaCjsonLibrary
- LuaNginxModule
- LuaRdsParserLibrary
- LuaRedisParserLibrary
- LuaRestyCoreLibrary
- LuaRestyDNSLibrary
- LuaRestyLockLibrary
- LuaRestyLrucacheLibrary
- LuaRestyMemcachedLibrary
- LuaRestyMySQLLibrary
- LuaRestyRedisLibrary
- LuaRestyStringLibrary
- LuaRestyUploadLibrary
- LuaRestyUpstreamHealthcheckLibrary
- LuaRestyWebSocketLibrary
- LuaUpstreamNginxModule
- PostgresNginxModule
- RdsCsvNginxModule
- RdsJsonNginxModule
- RedisNginxModule
- Redis2NginxModule
- SetMiscNginxModule
- SrcacheNginxModule
- XssNginxModule
openResty 安装
wget http://openresty.org/download/ngx_openresty-1.7.10.2.tar.gz
解压之后进入解压目录进行编译参数配置
./configure --prefix=/data/soft/openresty --user=nginx --group=nginx --with-luajit
以上编译参数可根据自己的实际需求调整,
./configure --help 查看参数
make & make install 编译安装
lua 简介
Lua的目标是成为一个很容易嵌入其它语言中使用的语言。大多数程序员也认为它的确做到了这一点。 很多应用程序使用Lua作为自己的嵌入式脚本语言,以此来实现可配置性、可扩展性。 Lua是一种轻量语言,它的官方版本只包括一个精简的核心和最基本的库。这使得Lua体积小、启动速度快。
lua 代码举例及组件
Lua是一种动态类型语言,因此语言中没有类型的定义,不需要声明变量类型,每个变量自己保存了类型。 有8种基本类型:nil、布尔值(boolean)、数字体(number)、字符串型(string)、用户自定义类型(userdata)、函数(function)、线程(thread)和表(table)print(type(nil)) -- 输出 nil print(type(99.7+12*9)) -- 输出 number print(type(true)) -- 输出 boolean print(type("Hello Wikipedia")) -- 输出 string print(type(print)) -- 输出 function print(type{1, 2, test = "test"}) -- 输出 table
hello 入门
print("Hello, world!") --经典的hell world;
函数体
function create_a_counter() local count = 0 return function() count = count + 1 return count end end
结构体 if
score = 0 if score == 100 then print("Very good!Your score is 100") elseif score >= 60 then print("Congratulations, you have passed it,your score greater or equal to 60") else if score > 0 then print("Your score is better than 0") else print("My God, your score turned out to be 0") end --与上一示例代码不同的是,此处要添加一个end end
结构体 while
x = 1 sum = 0 while x <= 5 do sum = sum + x x = x + 1 end print(sum) -->output 15
结构体 for
for i=1,10,2 do print(i) end
lua中支持 break return 与其他不同的是lua return 可以返回多个参数
local ab=function (flag) return flat,nil end local data,err = ab(1)更多信息 [[1]] 常用的lua组件:
cjson -- lua table 和json 转换工具 resty.redis --redis 连接工具 resty.ssdb --ssdb 连接工具 cmsgpack --msgpack 压缩解压工具 resty.string --字符串加解密工具 resty.cookie --cookie管理工具 resty.lock --共享锁工具 ...
ngx + lua 组合
nginx 执行顺序 nginx执行步骤 nginx在处理每一个用户请求时,都是按照若干个不同的阶段依次处理的,与配置文件上的顺序没有关系,详细内容可以阅读《深入理解nginx:模块开发与架构解析》这本书,这里只做简单介绍;
- post-read 读取请求内容阶段,nginx读取并解析完请求头之后就立即开始运行;
- server-rewrite server请求地址重写阶段;
- find-config 配置查找阶段,用来完成当前请求与location配重块之间的配对工作;
- rewrite location请求地址重写阶段,当ngx_rewrite指令用于location中,就是再这个阶段运行的;
- post-rewrite 请求地址重写提交阶段,当nginx完成rewrite阶段所要求的内部跳转动作,如果rewrite阶段有这个要求的话;
- preaccess 访问权限检查准备阶段,ngx_limit_req和ngx_limit_zone在这个阶段运行,ngx_limit_req可以控制请求的访问频率,ngx_limit_zone可以控制访问的并发度;
- access 权限检查阶段,ngx_access在这个阶段运行,配置指令多是执行访问控制相关的任务,如检查用户的访问权限,检查用户的来源IP是否合法;
- post-access 访问权限检查提交阶段;
- try-files 配置项try_files处理阶段;
- content 内容产生阶段,是所有请求处理阶段中最为重要的阶段,因为这个阶段的指令通常是用来生成HTTP响应内容的;
- log 日志模块处理阶段。
nginx +lua 执行顺序
- init_by_lua、init_by_lua_file --nginx 初始化 阶段
- init_worker_by_lua、init_worker_by_lua_file --nginx worker 初始化阶段
- set_by_lua、set_by_lua_file --设定nginx 变量
- rewrite_by_lua、rewrite_by_lua_file --检查rewrite 规则
- access_by_lua,access_by_lua_file --执行正式流程前的权限检查
- content_by_lua,content_by_lua_file --正文处理
- header_filter_by_lua,header_filter_by_lua_file -过滤头部 协议转换
- body_filter_by_lua,body_filter_by_lua_file --过滤正文 协议转换
- log_by_lua,log_by_lua_file 日志处理
ngx + lua 开发
- nginx +lua 与 openResty 区别
#设置纯lua的外部模块搜索路径(';;' is the default path): lua_package_path '/foo/bar/?.lua;/blah/?.lua;;'; # 设置使用c编写的lua外部模块搜索路径(can also use ';;'): lua_package_cpath '/bar/baz/?.so;/blah/blah/?.so;;'; lua_package_cpath 与 lua_package_path 在openResty下无需设定,openResty下默认库在lualibs下,其他基本没什么差异。
- 线下与上线区别,及优化
lua_code_cache on<线上> | off <测试>
nginx.conf 配置示范
user www; worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;}
http {include mime.types; default_type text/plain; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"';sendfile on; keepalive_timeout 65; #gzip on; init_by_lua_file "/usr/local/openresty/nginx/conf/lua/init.lua"; server { listen 60080; server_name localhost 192.168.30.225; #charset koi8-r; #if ($time_iso8601 ~ "^(\d{4})-(\d{2})-(\d{2})") { #set $year $1; #set $month $2; #set $day $3; #} #access_log logs/www.xxx.com_$year$month$day_access.log;set_by_lua $date 'local date=os.date("%Y",os.time())..os.date("%m",os.time())..os.date("%d",os.time()) return date '; access_log logs/host_test_$date.log main;location / { root html; index index.html index.htm; } location /lua { default_type 'text/plain'; content_by_lua_file "/usr/local/openresty/nginx/conf/lua/work.lua"; } location /get { default_type 'text/plain'; set_unescape_uri $key $arg_key; redis2_query get $key; redis2_pass 127.0.0.1:6379; } location /set { set_unescape_uri $key $arg_key; set_unescape_uri $val $arg_val; redis2_query set $key $val; redis2_pass 127.0.0.1:6379; }location /luasay { default_type 'text/plain'; content_by_lua "ngx.say('Hello,world!');ngx.exit(200);"; } location /luadate { default_type 'text/plain'; content_by_lua 'local date =os.time(); ngx.say(os.date("%Y%m%d",date)); ngx.say("now use ngx.var"); ngx.say(ngx.var.date); ngx.exit(200);'; } location /luaheader { content_by_lua " ngx.header.content_type = 'text/plain'; ngx.header['X-My-Header'] = 'blah blah'; ngx.header['server-id']=' web 1'; -- 服务器信息 方便调试 ngx.header['Set-Cookie'] = {'a=32; path=/', 'b=4; path=/'} ngx.say('see html header'); ngx.exit(200);";} location /luareq { content_by_lua " ngx.header.content_type = 'text/plain'; ngx.say('request header'); ngx.print(ngx.req.raw_header()); ngx.say('request method'); ngx.say(ngx.req.get_method()); ngx.say('body'); ngx.say(ngx.req.get_body_data()); "; } location /luaarg { content_by_lua " local a = ngx.var.arg_a or 0; ngx.say('args '..a); "; } location /luaarg_req { content_by_lua ' ngx.header.content_type = "text/plain"; local arg_a =tonumber(ngx.var.arg_a) or 0 if(ngx.var.arg_a==0) then ngx.say(" arg a not set ") else res=ngx.location.capture("/luaarg",{args={a=arg_a}}); if res.status == 200 then ngx.print(res.body) end end local args= ngx.decode_args(ngx.var.args); ngx.say("input args is:") for arg, value in pairs(args ) do ngx.say("arg "..arg.." is "..value) end '; }error_page 500 502 503 504 /50x.html; location = /50x.html { root html; }}
}
- init.lua 内容
cjson = require "cjson" redis = require "resty.redis" --lock = require "resty.lock" log = ngx.log local ERR = ngx.ERR --[[ nginx初始化: lua_shared_dict lock_cache 100k; ]] local lock_cache = ngx.shared.lock_cache redis_host = '127.0.0.1' redis_port = 6379 --[[ 动态修改 lua init 变量 ]] update_redis_info = function(host,port) if host ~= nil then redis_host = host end if port ~=nil and tonumber(port)>0 then redis_port = tonumber(port) end end -- [[ 写入文件 ]] function file_put_contents(logfile,msg,mode) mode = mode or "w+" local fd = io.open(logfile,mode) if fd == nil then return nil end fd:write(msg) fd:flush() fd:close() return 'ok' end --[[ 获取件内容 ]] function file_get_contents(filename) local file = io.open(filename, "r") if file then local content = file:read("*a") io.close(file) return content,nil end return nil end ngx.log(ERR,"init log") if redis then ngx.log(ERR,"redis ok") end
- worker.lua
local inspect = require "inspect" local function errorj(message,status) local _t=type(message) local _s=type(status) if t == 'userdata' then message = tostring(message) end if message == nil then message="" end if status == nil then status =0 end if _s ~= 'number' then status = tonumber(status) status = _s end local json='{"status":"'..status..'","data":"","msg":"' .. message .. '"}' --local json= inspect(message) ngx.header.content_type = "text/plain"; ngx.say(json) ngx.exit(200) end local red = redis:new() local method = ngx.var.arg_m local filename = "/usr/local/openresty/nginx/conf/lua/cache.txt" if method == "read" or method =="write" then ngx.header['content-type']="text/plain" local data = ngx.var.arg_data or "" if method == "write" and data ~= "" then status=file_put_contents(filename , data) if status then ngx.print("write ok") else ngx.print("write error") end else data=file_get_contents(filename) if data then ngx.print("read ok \n") ngx.print(data) else ngx.print("read error") end end end if method =='set' or method =='get' or method =="del" then ngx.header['content-type']="text/json" local red = redis:new() red:set_timeout(1000) local ok, err = red:connect(redis_host, redis_port) if not ok then errorj("redis_server_err") else local key = ngx.var.arg_key or "" if method == "get" and key ~='' then local res, err = red:get(key) if not res then errorj("failed to get "..key..": ", err) else errorj(res,200) end end if method == "set" and key ~='' then local val = ngx.var.arg_val or "" local res, err = red:set(key,val) if not res then errorj("failed to set "..key..": ", err) else errorj(res,200) end end if method == "del" and key ~='' then local res, err = red:del(key) if not res then errorj("failed to set "..key..": ", err) else errorj(res,200) end end end if not key then errorj ("key is empty ",200) end red:set_keepalive(10000, 100) end if method ~='set' and method ~='get' and method ~="del" and method ~= "read" and method ~="write" then errorj ("method not allowd ") end
- 通用api 接口设计
location ~ ^/api/([-_a-zA-Z0-9/]+).json { access_by_lua_file /path/to/lua/api/protocal_decode.lua; #协议解码 content_by_lua_file /path/to/lua/api/$1.lua; #主要逻辑 body_filter_by_lua_file /path/to/lua/api/protocal_encode.lua; #协议编码输出 }
nginx 变量参考
$arg_name 请求中的name参数 $args 请求中的参数 $binary_remote_addr 远程地址的二进制表示 $body_bytes_sent 已发送的消息体字节数 $content_length HTTP请求信息里的"Content-Length" $content_type 请求信息里的"Content-Type" $document_root 针对当前请求的根路径设置值 $document_uri 与$uri相同; 比如 /test2/test.php $host 请求信息中的"Host",如果请求中没有Host行,则等于设置的服务器名 $hostname 机器名使用 gethostname系统调用的值 $http_cookie cookie 信息 $http_referer 引用地址 $http_user_agent 客户端代理信息 $http_via 最后一个访问服务器的Ip地址。 $http_x_forwarded_for 相当于网络访问路径 $is_args 如果请求行带有参数,返回“?”,否则返回空字符串 $limit_rate 对连接速率的限制 $nginx_version 当前运行的nginx版本号 $pid worker进程的PID $query_string 与$args相同 $realpath_root 按root指令或alias指令算出的当前请求的绝对路径。其中的符号链接都会解析成真是文件路径 $remote_addr 客户端IP地址 $remote_port 客户端端口号 $remote_user 客户端用户名,认证用 $request 用户请求 $request_body 这个变量(0.7.58+)包含请求的主要信息。在使用proxy_pass或fastcgi_pass指令的location中比较有意义 $request_body_file 客户端请求主体信息的临时文件名 $request_completion 如果请求成功,设为"OK";如果请求未完成或者不是一系列请求中最后一部分则设为空 $request_filename 当前请求的文件路径名,比如/opt/nginx/www/test.php $request_method 请求的方法,比如"GET"、"POST"等 $request_uri 请求的URI,带参数; 比如http://localhost:88/test1/ test2/test.php $scheme 所用的协议,比如http或者是https $server_addr 服务器地址,如果没有用listen指明服务器地址,使用这个变量将发起一次系统调用以取得地址(造成资源浪费) $server_name 请求到达的服务器名 $server_port 请求到达的服务器端口号 $server_protocol 请求的协议版本,"HTTP/1.0"或"HTTP/1.1" $uri 请求的URI,可能和最初的值有不同,比如经过重定向之类的