• OpenResty入门之使用Lua开发Nginx插件


    记住一点:nginx配置文件很多坑来源自你的空格少了或多了。

    OpenResty

    OpenResty® 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

    OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由 OpenResty 团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。这样,Web 开发人员和系统工程师可以使用 Lua 脚本语言调动 Nginx 支持的各种 C 以及 Lua 模块,快速构造出足以胜任 10K 乃至 1000K 以上单机并发连接的高性能 Web 应用系统。

    OpenResty® 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如 MySQL、PostgreSQL、Memcached 以及 Redis 等都进行一致的高性能响应。

    1.Centos下载安装

    如果你的系统是 Centos 或 RedHat 可以使用以下命令:

    yum install readline-devel pcre-devel openssl-devel
    

    接下我们可以在官方(https://openresty.org/cn/)下载最新的 OpenResty 源码包并解压编译安装:

    wget https://openresty.org/download/ngx_openresty-1.9.7.1.tar.gz   # 下载
    tar xzvf ngx_openresty-1.9.7.1.tar.gz       # 解压
    cd ngx_openresty-1.9.7.1/ 
    ./configure
    make 
    make install
    

    默认情况下程序会被安装到 /usr/local/openresty 目录,你可以使用 ./configure --help 查看更多的配置选项。

    2.HelloWorld实例

    安装成功后,我们就可以使用 openresty 直接输出 html 页面。

    首先我们可以创建一个工作目录:

    mkdir /home/www
    cd /home/www/
    mkdir logs/ conf/
    

    其中 logs 目录用于存放日志,conf 用于存放配置文件。

    接着,我们在 conf 目录下创建一个 nginx.conf 文件 代码如下:

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }
    http {
        server {
            listen 9000;
            location / {
                default_type text/html;
                content_by_lua '
                    ngx.say("<p>Hello, World!</p>")
                ';
            }
        }
    }
    

    如果你熟悉 nginx 的配置,应该对以上代码就很熟悉。这里我们将 html 代码直接写在了配置文件中。

    启动 openresty

    默认情况下 openresty 安装在 /usr/local/openresty 目录中,启动命令为:

    /usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx.conf
    

    如果没有任何输出,说明启动成功,-p 指定我们的项目目录,-c 指定配置文件。

    接下来我们可以使用 curl 来测试是否能够正常范围:

    curl http://localhost:9000/
    

    输出结果为:

    <p>Hello, World!</p>
    

    3.调用Lua脚本文件

    在 HelloWorld 实例中,我们直接在 nginx.conf 中写Lua脚本,很多时候,Lua脚本是一个文件。下面演示使用 content_by_lua_file 指令调用Lua脚本文件。

    在conf文件夹下创建helloworld.lua:

    ngx.say("<p>Hello, World!</p>")
    

    修改你的 nginx.conf 文件内容为:

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }
    http {
        server {
            listen 9000;
            location / {
                default_type text/html;
                content_by_lua_file 'conf/helloworld.lua';
            }
        }
    }
    

    停止已启动的nginx进程:

    killall -9 nginx
    

    启动nginx进程:

    /usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx.conf
    

    接下来我们可以使用 curl 来测试是否能够正常范围:

    curl 'localhost:9000'
    

    输出结果为:

    <p>Hello, World!</p>
    

    4.set_by_lua指令

    使用 set_by_lua 指定可以用类似调用函数的形式去调用Lua脚本。语法:

    set_by_lua $res <lua-script-str> [$arg1 $arg2 ...]
    

    修改你的conf/nginx.conf文件:

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }
    http {
        server {
            listen 9000;
            location / {
                default_type text/html;
                set_by_lua $res '
                    local a = tonumber(ngx.arg[1])
                    local b = tonumber(ngx.arg[2])
                    return a+b' $arg_a $arg_b;
                echo $res;
            }
        }
    }
    

    停止已启动的nginx进程,命令:

    killall -9 nginx
    

    启动nginx进程:

    /usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx.conf
    

    接下来我们可以使用 curl 来测试是否能够正常范围:

    curl 'localhost:9000/?a=2&b=5'
    

    输出结果为:

    7
    

    5.set_by_lua_file指令

    set_by_lua_file可以调用本地Lua脚本文件。语法与set_by_lua相同:

    set_by_lua_file $res <lua-script-str> [$arg1 $arg2 ...]
    

    在conf文件夹下创建hello.lua文件:

    local a = tonumber(ngx.arg[1])
    local b = tonumber(ngx.arg[2])
    return a+b
    

    在conf文件夹下创建nginx_lua.conf文件:

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }
    http {
        server {
            listen 9000;
            location = / {
                default_type text/html;
                set_by_lua_file $res "conf/hello.lua" $arg_a $arg_b;
                echo $res;
            }
        }
    }
    

    启动nginx进程:

    /usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx_lua.conf
    

    接下来我们可以使用 curl 来测试是否能够正常范围:

    curl 'localhost:9000/?a=2&b=5'
    

    输出结果为:

    7
    

    6.设置nginx变量

    在conf文件夹下创建nginx.conf文件:

    worker_processes  1;
    error_log logs/error.log;
    events {
        worker_connections 1024;
    }
    http {
        server {
            listen 9000;
            location / {
            	set $a $host;
            	set $b 'hello world';
                default_type text/html;
                content_by_lua_file 'conf/hello.lua';
            }
        }
    }
    

    在conf文件夹下创建hello.lua:

    local var = ngx.var
    ngx.say("ngx.var.a : ", var.a, "<br/>")
    ngx.say("ngx.var.b : ", var.b, "<br/>")
    ngx.say("<br/>")
    

    启动nginx进程:

    /usr/local/openresty/nginx/sbin/nginx -p /home/www/ -c conf/nginx_lua.conf
    

    接下来我们可以使用 curl 来测试是否能够正常范围:

    curl 'localhost:9000'
    

    输出结果为:

    ngx.var.a : localhost<br/>
    ngx.var.b : hello world<br/>
    

    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

    7.运行周期

    现在已经学会了content_by_lua 与 set_by_lua 指令,其它类似的指令还有很多,那么这些指令都是有什么区别呢?主要区别是指令的运行周期不同,如图所示。(图片来源于网络)

    8.其它指令

    指令 所处处理阶段 使用范围 解释
    init_by_lua
    init_by_lua_file
    loading-config http nginx Master进程加载配置时执行;通常用于初始化全局配置/预加载Lua模块
    init_worker_by_lua
    init_worker_by_lua_file
    starting-worker http 每个Nginx Worker进程启动时调用的计时器,如果Master进程不允许则只会在init_by_lua之后调用;通常用于定时拉取配置/数据,或者后端服务的健康检查
    set_by_lua
    set_by_lua_file
    rewrite server,server if,location,location if 设置nginx变量,可以实现复杂的赋值逻辑;此处是阻塞的,Lua代码要做到非常快;
    rewrite_by_lua
    rewrite_by_lua_file
    rewrite tail http,server,location,location if rrewrite阶段处理,可以实现复杂的转发/重定向逻辑;
    access_by_lua
    access_by_lua_file
    access tail http,server,location,location if 请求访问阶段处理,用于访问控制
    content_by_lua
    content_by_lua_file
    content location,location if 内容处理器,接收请求处理并输出响应
    header_filter_by_lua
    header_filter_by_lua_file
    output-header-filter http,server,location,location if 设置header和cookie
    body_filter_by_lua
    body_filter_by_lua_file
    output-body-filter http,server,location,location if 对响应数据进行过滤,比如截断、替换。
    log_by_lua
    log_by_lua_file
    log http,server,location,location if log阶段处理,比如记录访问量/统计平均响应时间

    9.Nginx API

    将下面的lua脚本复制到你的content_by_lua_file指定的lua文件中即可。

    --请求头
    local headers = ngx.req.get_headers()
    ngx.say("headers begin", "<br/>")
    ngx.say("Host : ", headers["Host"], "<br/>")
    ngx.say("user-agent : ", headers["user-agent"], "<br/>")
    ngx.say("user-agent : ", headers.user_agent, "<br/>")
    for k, v in pairs(headers) do
        if type(v) == "table" then
            ngx.say(k, " : ", table.concat(v, ","), "<br/>")
        else
            ngx.say(k, " : ", v, "<br/>")
        end
    end
    ngx.say("headers end", "<br/>")
    ngx.say("<br/>")
    
    --get请求uri参数
    ngx.say("uri args begin", "<br/>")
    local uri_args = ngx.req.get_uri_args()
    for k, v in pairs(uri_args) do
        if type(v) == "table" then
            ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
        else
            ngx.say(k, ": ", v, "<br/>")
        end
    end
    ngx.say("uri args end", "<br/>")
    ngx.say("<br/>")
    
    --post请求参数
    ngx.req.read_body()
    ngx.say("post args begin", "<br/>")
    local post_args = ngx.req.get_post_args()
    for k, v in pairs(post_args) do
        if type(v) == "table" then
            ngx.say(k, " : ", table.concat(v, ", "), "<br/>")
        else
            ngx.say(k, ": ", v, "<br/>")
        end
    end
    ngx.say("post args end", "<br/>")
    ngx.say("<br/>")
    
    --请求的http协议版本
    ngx.say("ngx.req.http_version : ", ngx.req.http_version(), "<br/>")
    --请求方法
    ngx.say("ngx.req.get_method : ", ngx.req.get_method(), "<br/>")
    --原始的请求头内容
    ngx.say("ngx.req.raw_header : ", ngx.req.raw_header(), "<br/>")
    --请求的body内容体
    ngx.say("ngx.req.get_body_data() : ", ngx.req.get_body_data(), "<br/>")
    ngx.say("<br/>")
    
    local request_uri = ngx.var.request_uri;
    ngx.say("request_uri : ", request_uri, "<br/>");
    --解码
    ngx.say("decode request_uri : ", ngx.unescape_uri(request_uri), "<br/>");
    --MD5
    ngx.say("ngx.md5 : ", ngx.md5("123"), "<br/>")
    --http time
    ngx.say("ngx.http_time : ", ngx.http_time(ngx.time()), "<br/>")
    
    --当前时间
    ngx.update_time()
    local now = ngx.now()
    ngx.say("nowTime : ", now, "<br/>")
    

    访问 http://127.0.0.1:9000/?a=8&b=55 ,手动输入两个Cookie,输出结果:

    headers begin
    Host : 127.0.0.1:9000
    user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
    user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
    accept-language : zh-CN,zh;q=0.9
    connection : keep-alive
    accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
    cache-control : max-age=0
    host : 127.0.0.1:9000
    cookie : hello=world; Heelo=Sdd
    accept-encoding : gzip, deflate
    upgrade-insecure-requests : 1
    user-agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36
    headers end
    
    uri args begin
    b: 55
    a: 8
    uri args end
    
    post args begin
    post args end
    
    ngx.req.http_version : 1.1
    ngx.req.get_method : GET
    ngx.req.raw_header : GET /?a=8&b=55 HTTP/1.1 Host: 127.0.0.1:9000 Connection: keep-alive Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: hello=world; Heelo=Sdd 
    ngx.req.get_body_data() : nil
    
    request_uri : /?a=8&b=55
    decode request_uri : /?a=8&b=55
    ngx.md5 : 202cb962ac59075b964b07152d234b70
    ngx.http_time : Mon, 29 Apr 2019 14:53:17 GMT
    

    Lua如何调用系统shell呢?

    --调用系统命令
    local t = io.popen('cat /home/www/conf/hello.lua')
    local a = t:read("*all")
    t:close()
    ngx.say(a)
    

    10.Lua发起Http请求

    Lua发送Http请求,默认是不支持的,需要引入第三方库。也就是下方这个Github地址的两个文件:http.luahttp_headers.lua

    Github:https://github.com/ledgetech/lua-resty-http/tree/master/lib/resty

    流程和上方的Demo类似,不同的是,需要在你的 lua 文件中写入一行代码:

    local http = require "resty.http"
    

    启动你的 nginx ,命令同上。使用 curl 命令测试访问,查看 logs 目录下的 error.log 文件:

    2019/04/30 14:23:21 [error] 7093#0: *1 lua entry thread aborted: runtime error: /home/www/conf/helloworld.lua:95: module 'resty.http' not found:
    	no field package.preload['resty.http']
    	no file '/usr/local/openresty/lualib/resty/http.lua'
    	no file '/usr/local/openresty/lualib/resty/http/init.lua'
    	no file './resty/http.lua'
    	no file '/usr/local/openresty/luajit/share/luajit-2.1.0-beta1/resty/http.lua'
    	no file '/usr/local/share/lua/5.1/resty/http.lua'
    	no file '/usr/local/share/lua/5.1/resty/http/init.lua'
    	no file '/usr/local/openresty/luajit/share/lua/5.1/resty/http.lua'
    	no file '/usr/local/openresty/luajit/share/lua/5.1/resty/http/init.lua'
    	no file '/usr/local/openresty/lualib/resty/http.so'
    	no file './resty/http.so'
    	no file '/usr/local/lib/lua/5.1/resty/http.so'
    	no file '/usr/local/openresty/luajit/lib/lua/5.1/resty/http.so'
    	no file '/usr/local/lib/lua/5.1/loadall.so'
    	no file '/usr/local/openresty/lualib/resty.so'
    	no file './resty.so'
    	no file '/usr/local/lib/lua/5.1/resty.so'
    	no file '/usr/local/openresty/luajit/lib/lua/5.1/resty.so'
    	no file '/usr/local/lib/lua/5.1/loadall.so'
    

    我们只需要在错误日志的第一个目录下添加上面两个http模块的lua文件即可。目录:

    /usr/local/openresty/lualib/resty/
    

    接下来就是重启你的nginx,再次访问,发现不报错了。

    GET请求

    local http = require "resty.http"
    local function http_post_client(url, timeout)
            local httpc = http.new()
     
            timeout = timeout or 30000
            httpc:set_timeout(timeout)
     
            local res, err_ = httpc:request_uri(url, {
                    method = "GET",
                    headers = {
                        ["Content-Type"] = "application/x-www-form-urlencoded",
                    }
            })
            httpc:set_keepalive(5000, 100)
            --httpc:close()
            return res, err_
    end
    

    POST请求

    local http = require "resty.http"
    local function http_post_client(url,body,timeout)
            local httpc = http.new()
     
            timeout = timeout or 30000
            httpc:set_timeout(timeout)
     
            local res, err_ = httpc:request_uri(url, {
                    method = "POST",
                    body = body,
                    headers = {
                        ["Content-Type"] = "application/x-www-form-urlencoded",
                    }
            })
            httpc:set_keepalive(5000, 100)
             httpc:close()
            if not res then 
                return nil, err_ 
             else if res.status == 200 then 
                 return res.body, err_ 
             else 
                 return nil, err_ end 
             end
     
    end
    

    Demo

    --get
    local resp, err = http_post_client("http://127.0.0.1/index.html?name=test",3000)
    --post
    local body = {"name" = "test"}
    local resp, err = http_post_client("http://127.0.0.1/index.html?name=test",body,3000)
    

    参考

    https://blog.csdn.net/qq_21860077/article/details/83623888

    Nginx API for Lua:https://www.cnblogs.com/wangxusummer/p/4309007.html

    版权声明

    【本文版权归微信公众号"代码艺术"(ID:onblog)所有,若是转载请务必保留本段原创声明,违者必究。若是文章有不足之处,欢迎关注微信公众号私信与我进行交流!】

  • 相关阅读:
    Scrapy爬虫快速入门
    python垃圾回收机制
    django项目的uwsgi方式启停脚本
    hdu 5504 GT and sequence
    python 在 for i in range() 块中改变 i 的值的效果
    linux 在终端中打开图形化文件管理器
    apache 支持 php
    Mysql 学习记录
    git 导入代码到已有仓库
    python import 自己的包
  • 原文地址:https://www.cnblogs.com/onblog/p/13044230.html
Copyright © 2020-2023  润新知