基于openresty的nginx+lua实践
- 功能简介
- openresty安装
- nginx的config
- lua模块
- cwiki.lua脚本详解
- lua脚本依赖的第三方模块
功能简介
浏览器中输入一个地址,nginx收到请求后,会调用相关的lua脚本。
比如访问:http://134.175.80.121/cwiki/自行车,则会将相关信息呈现给浏览器端。 lua实现的功能是:MD5加密,文件修改时间对比,发送消息到rabbitMq。
openresty安装
安装后 进入/opt/modules/openresty
文件夹如图:
常用命令介绍:
- 进入到openresty的目录: cd /opt/modules/openresty
- nginx的启动:./nginx/sbin/nginx
- nginx的停止:./nginx/sbin/nginx -s stop
- nginx重载:./nginx/sbin/nginx -s reload
- nginx进程查看:ps -ef|grep nginx
nginx的config
最简版,注意lua 模块和c模块
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
lua_package_path "/opt/modules/openresty/lualib/?.lua;;"; #lua 模块
lua_package_cpath "/opt/modules/openresty/lualib/?.so;;"; #c模块
log_by_lua_file lua/my.lua;
server {
listen 80;
location /cwiki/ {
default_type 'text/html;charset=utf-8';
lua_code_cache off;#作用是关闭缓存,修改lua,不用重启nginx,但正式线上要去掉,脚本里的变量域都是local才行
content_by_lua_file lua/cwiki.lua;#该文件在nginx目录下的lua目录
}
location / {
alias /$1;
lua_code_cache off;
}
}
}
lua模块
- lua测试命令
命令:lua - 退出:ctrl+c
- cwiki.lua脚本内容
local function file_exists(path)
local file = io.open(path, "rb")
if file then
file:close()
end
return file ~= nil
end
local function getContent(path, blockPath)
if (blockPath ~= nil)
then
ngx.header.content_type = "text/html"
local f = io.open(path, "rb")
local content = f:read("*all")
f:close()
local bf = io.open(blockPath, "rb")
local blockstr = bf:read("*all")
bf:close()
local rlt=content:gsub("</head>", blockstr, 1)
return rlt
else
ngx.header.content_type = "text/html"
local f = io.open(path, "rb")
local content = f:read("*all")
f:close()
return content;
end
end
local function getFileTime(path)
local lfs = require "lfs"
return lfs.attributes(path, "modification")
end
local function sendHdTtMq(docTitle)
local rabbitmq = require "resty.rabbitmqstomp"
local cjson = require 'cjson'
local opts = { username = "guest",
password = "guest",
vhost = "/" }
local mq, err = rabbitmq:new(opts)
if not mq then
-- ngx.say('cannot new mq')
-- ngx.say(err)
-- return
end
mq:set_timeout(20000)
-- 引用了gethost.lua ,该文件应该在openresty 中的lualib中,在nginx.conf中指定
local b= require("gethost")
local ipstr= b.getHost("/etc/hosts","1.rabbitmq.local")
-- 解析本机hosts文件中 1.rabbitmq.local 对应的ip地址
local ok, err = mq:connect(ipstr, 61613)
if not ok then
-- ngx.say('cannot conn mq')
-- return
end
local msg = { key = "lua" }
local headers = {}
-- 消息发送到哪里 /exchange/交换机名称/routing_key名称
headers["destination"] = "/exchange/hd.toutiaoLua.Exchange/hd.toutiaoLua.1"
-- 是否持久化
headers["persistent"] = "true"
-- 消息格式
headers["content-type"] = "application/json"
-- headers["receipt"] = "msg#1"
-- headers["app-id"] = "luaresty"
headers["title"] = docTitle
local ok, err = mq:send(cjson.encode(msg), headers)
if not ok then
-- ngx.say('cannot send mq')
-- return
end
local ok, err = mq:set_keepalive(10000, 500)
end
--静态html文件根目录
local root = '/data/tdata/'
--URL跳转前段地址
-- local preUrl = 'http://134.175.80.121:80/static/'
local httpUri = ngx.unescape_uri(ngx.var.request_uri)
local isContains = string.find(httpUri, "/cwiki/")
local isContainsTouTiao = string.find(httpUri, "&fr=toutiao")
if (isContains == nil or isContainsTouTiao == nil)
then
-- ngx.say("<p>非法请求</p>");
else
--citiao = string.sub(httpUri,8)
local idx1, idx2 = string.find(httpUri, "/cwiki/");
local idx3, idx4 = string.find(httpUri, "&fr=toutiao");
local citiao = string.sub(httpUri, idx2 + 1, idx3 - 1);
local citiaoMD5 = string.upper(ngx.md5(citiao))
local relativePaths = string.format("%s/%s/%s%s", string.sub(citiaoMD5, 1, 2), string.sub(citiaoMD5, 3, 4), citiaoMD5, ".html")
local absolutePath = string.format("%s%s", root, relativePaths)
local isExist = file_exists(absolutePath)
if (isExist == true)
then
-- return ngx.redirect(string.format('%s%s',preUrl,relativePaths))
local versionPath = string.format("%s%s", root, "version.txt")
local isExistVersion = file_exists(versionPath) -- 判断模板文件存在与否,注意该模板的权限是750,或者给读的权限
if (isExistVersion == true)
then
if (getFileTime(absolutePath) < getFileTime(versionPath))
then
sendHdTtMq(citiao)
else
end
-- require "lfs"
--ngx.say(getFileTime(versionPath))
-- return
else
-- ngx.say('模板version.txt不存在,请生成模板')
end
--ngx.exec(string.format('%s%s',root,relativePaths))
local blockPath = string.format("%s%s", root, "block.txt")
local isExistBlock = file_exists(blockPath)
if (isExistBlock == true)
then
ngx.print(getContent(string.format('%s%s', root, relativePaths), blockPath))
else
ngx.print(getContent(string.format('%s%s', root, relativePaths), nil))
end
else
ngx.exec(string.format('%s%s', root, '404.html'))
end
end
- java后台rabbitmq消费端代码:,后台为springboot项目。
@Component
public class TaskListener {
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "hd.toutiaoLua.Queue", durable = "true"),
exchange = @Exchange(value = "hd.toutiaoLua.Exchange", durable = "true", type = "topic", ignoreDeclarationExceptions = "true"),
key = "hd.toutiaoLua.*"))
@RabbitHandler
public void onMessageLua(Message message, Channel channel) {
System.out.println("message:" + message.getPayload());
Map<String, Object> headers = message.getHeaders();
String title = headers.get("title") == null ? "" : headers.get("title").toString();
logger.info("接收到从lua发送过来的消息,title=" + title);
if (!"".equals(title)) {
taskService.genetorHtml(title, true);
}
try {
long deliveryTag = (long) message.getHeaders().get(AmqpHeaders.DELIVERY_TAG);
//当消息处理完毕,执行ACK
//logger.info("onMessage1:消息执行完毕准备ACK");
channel.basicQos(0, 1, false);
channel.basicAck(deliveryTag, false);
} catch (IOException e) {
e.printStackTrace();
}
}
}
- lua脚本引用其他脚本里的自定义函数
lua脚本要通过rabbitmq发送消息,需要ip地址等信息,但是ip地址写在解析本机hosts文件中,是一个变量1.rabbitmq.local 对应的ip地址。因此需要解析hosts文件,因为cwiki.lua越来越大,所以将函数写在了gethost.lua里。只是在cwiki.lua里调用。
调用方法:
local b= require("gethost")
local ipstr= b.getHost("/etc/hosts","1.rabbitmq.local")
这里需要指出的是:gethost.lua存放的位置,该脚本存放在:/opt/modules/openresty/lualib 目录下
gethost.lua的内容如下
host_func = {}
function host_func.Fac(n)
if n <= 1 then
return n
end
return n * host_func. Fac(n-1)
end
function host_func.getHost(path,hostStr)
f = io.open(path,"r")
ipstr=""
for i in f:lines() do
if(i~=nil)
then
ipstr=string.match(i, "[%d]+[%.][%d]+[%.][%d]+[%.][%d]+")
-- idx1, idx2 = string.find(i, ipstr)
if(ipstr~=nil)
then
idx1, idx2 = string.find(i, ipstr)
endStr=string.sub(i, idx2 + 1)
if(hostStr==string.match(endStr,hostStr))
then
break
else
end
else
end
else
end
end
f:close()
return string.gsub(ipstr, "^%s*(.-)%s*$", "%1")
end
return host_func
cwiki.lua脚本详解
- 核心逻辑详解
- 文件存放在’/data/tdata/’,是浏览器输入后的中文字符加密后所得的md5值,比如md5值是:4F3411466AC2E18DD7CB7E2B2404E7D2,那么存放路径就是/data/tdata/4F/34/4F3411466AC2E18DD7CB7E2B2404E7D2.html。
- 浏览器中输入:http://134.175.80.121/cwiki/自行车&fr=toutiao nginx交给cwiki解析,先用正则取出“自行车”这个词,而后md5加密,判断对应的文件是否存在,如果存在,在判断version.txt(该文件为空文件,主要是使用其修改时间作为参照系)文件存在与否。如果该文件存在且修改时间比要访问的html文件新,那么要调用rabbitmq发消息(用于后台重新生成该html,这样再访问的时候就是最新的html)。
接着往下走,判断block.txt文件存在与否。如果存在,合并两个文件,调用ngx.print返回内容给客户端。 - block.txt文件的作用是想抽离出来页面的公用代码,比如css,js等。
- 函数详解
file_exists():判断文件是否存在。
getContent():根据路径合并文件内容。
getFileTime():获取文件的修改时间,注意这里用到了 require “lfs”,这里需要引入lfs,引入办法稍后详解。
sendHdTtMq():这个是发送rabbitMq用的。需要引入require “resty.rabbitmqstomp”,引入办法以及注意事项稍后详解。
lua脚本依赖的第三方模块
- rabbitmq发消息模块
该模块引用比较简单,参考:
1.http://www.dahouduan.com/2017/12/07/lua-stomp-rabbitmq/ 2.https://github.com/wingify/lua-resty-rabbitmqstomp?_blank 需要注意的是:rabbitmq 的 stomp 协议支持默认是不开启的,测试前需要手动开启:rabbitmq-plugins enable rabbitmq_stomp
图例: - 开启rabbitmq 的 stomp 协议后,一定要注意默认端口是:61613,这个不能错,否则发不了消息。
rabbitmq的启动与关闭: - 文件lfs模块
要想引入lfs模块,需要先引入luarocks
参考:https://luarocks.org/ - 安装了luarocks才能更方便安装lfs
参考:https://keplerproject.github.io/luafilesystem/