基于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脚本依赖的第三方模块

  1. 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

  2. 文件lfs模块
    要想引入lfs模块,需要先引入luarocks
    参考:https://luarocks.org/
    在这里插入图片描述安装了luarocks才能更方便安装lfs
    参考:https://keplerproject.github.io/luafilesystem/
    在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值