基于openresty的nginx+lua实践
功能简介
浏览器中输入一个地址,nginx收到请求后,会调用相关的lua脚本。
比如访问:http://134.175.80.121/cwiki/自行车,则会将相关信息呈现给浏览器端。
lua实现的功能是:MD5加密,文件修改时间对比,发送消息到rabbitMq。
openresty安装
参考:https://blog.csdn.net/chenshun123/article/details/80301587
安装后 进入/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的启动与关闭:
参考:https://blog.csdn.net/mysuppper/article/details/76598207 -
文件lfs模块
要想引入lfs模块,需要先引入luarocks
参考:https://luarocks.org/
安装了luarocks才能更方便安装lfs
参考:https://keplerproject.github.io/luafilesystem/