×

在openresty 中使用lua模块对wordpress限流

Falcon 2024-09-18 views:
自动摘要

正在生成中……

可以通过 OpenResty 的 Lua 模块(如 lua-resty-limit-req 模块)来控制用户的访问频率。例如,可以限制某个 IP 在 10 秒内不能超过 3 次请求。

lua-resty-limit-req 并没有单独的库,实际上限流功能已经包含在 lua-resty-limit-traffic 这个库中。这个库提供了多种限流功能,包括请求速率限制(类似 limit_req),以及连接数限制等。

lua-resty-limit-traffic 包含的限流模块:

  1. 请求限流模块 (resty.limit.req):用于请求速率限制,类似 Nginx 的 limit_req
  2. 连接限流模块 (resty.limit.conn):用于并发连接数限制。
  3. 漏桶算法 (resty.limit.count):用于基于计数的限流。

因此,lua-resty-limit-req 功能实际上已经集成在 lua-resty-limit-traffic 中,安装和使用 lua-resty-limit-traffic 即可。

安装 lua-resty-limit-traffic

  1. 使用 opm 安装:

    如果你已经有 opm,可以通过以下命令安装:

    opm get openresty/lua-resty-limit-traffic
    
  2. 通过 GitHub 安装:

    你也可以从 GitHub 直接获取:

    git clone https://github.com/openresty/lua-resty-limit-traffic.git
    

    然后将其复制到 OpenResty 的 lua_package_path 中指定的目录。

使用 resty.limit.req 进行请求限流:

安装 lua-resty-limit-traffic 后,你可以继续使用之前的 resty.limit.req 限流方式。以下是如何在 Lua 模块中使用 resty.limit.req 进行限流的示例:

local limit_req = require "resty.limit.req"

-- 每秒最多0.3次请求(10秒最多3次)
local lim, err = limit_req.new("limit_req_store", 0.3, 3)
if not lim then
    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
    return ngx.exit(500)
end

local key = ngx.var.http_cf_connecting_ip or ngx.var.remote_addr
local delay, err = lim:incoming(key, true)
if not delay then
    if err == "rejected" then
        ngx.log(ngx.WARN, "request rejected due to rate limiting")
        return ngx.exit(429)
    end
    ngx.log(ngx.ERR, "failed to limit req: ", err)
    return ngx.exit(500)
end

if delay > 0 then
    ngx.sleep(delay)
end

通过这个配置,你可以对请求进行速率限制,功能与 Nginx 内建的 limit_req 类似。

对于使用 WordPress 且开启了 URL 重写(固定链接)的站点,你可以在 OpenResty/Nginx 中加入限流逻辑,同时保留 WordPress 的 URL 重写规则。以下是一个配置示例,包含 WordPress 典型的重写规则和访问频率限流的配置。

示例1:

对于使用 WordPress 且开启了 URL 重写(固定链接)的站点,你可以在 OpenResty/Nginx 中加入限流逻辑,同时保留 WordPress 的 URL 重写规则。以下是一个配置示例,包含 WordPress 典型的重写规则和访问频率限流的配置。

http {
    lua_shared_dict limit_req_store 10m;

    server {
        listen 80;
        server_name yourdomain.com;
        root /path/to/your/wordpress;  # 修改为你 WordPress 的根目录路径
        index index.php index.html index.htm;

        # 限制访问频率
        location / {
            access_by_lua_block {
                local limit_req = require "resty.limit.req"

                -- 每秒最多允许0.3次请求(10秒最多3次)
                local lim, err = limit_req.new("limit_req_store", 0.3, 3)
                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
                end

                -- 使用客户端IP作为限流键
                local key = ngx.var.binary_remote_addr
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then
                        ngx.log(ngx.WARN, "request is rejected due to rate limiting")
                        return ngx.exit(429)
                    end
                    ngx.log(ngx.ERR, "failed to limit req: ", err)
                    return ngx.exit(500)
                end

                -- 如果需要延迟响应,进行延迟
                if delay > 0 then
                    ngx.sleep(delay)
                end
            }

            # WordPress 的 URL 重写规则
            try_files $uri $uri/ /index.php?$args;
        }

        # 处理 PHP 脚本
        location ~ \.php$ {
            include fastcgi_params;
            fastcgi_pass unix:/run/php/php7.4-fpm.sock;  # 确保路径正确
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }

        # 静态文件处理
        location ~* \.(jpg|jpeg|png|gif|css|js|ico|html)$ {
            expires 30d;
            access_log off;
        }

        # 限制访问 /wp-admin 区域
        location /wp-admin/ {
            access_by_lua_block {
                local limit_req = require "resty.limit.req"
                local lim, err = limit_req.new("limit_req_store", 0.5, 5) -- 每秒最多0.5次请求,突发5次
                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
                end
                local key = ngx.var.binary_remote_addr
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then
                        ngx.log(ngx.WARN, "request is rejected due to rate limiting")
                        return ngx.exit(429)
                    end
                    ngx.log(ngx.ERR, "failed to limit req: ", err)
                    return ngx.exit(500)
                end
                if delay > 0 then
                    ngx.sleep(delay)
                end
            }
            try_files $uri $uri/ /index.php?$args;
        }
    }
}

示例2:

分别对 PHP 请求和其他请求进行不同的限流设置。你可以根据请求的类型(如 .php 文件)来分别应用不同的限流策略。

以下是针对 PHP 请求和其他请求设置不同限流规则的配置示例:

示例配置:

http {
    lua_shared_dict limit_req_store 10m;

    server {
        listen 80;
        server_name yourdomain.com;
        root /path/to/your/wordpress;  # 修改为你 WordPress 的根目录路径
        index index.php index.html index.htm;

        # 对 PHP 文件请求进行限流(10秒内最多3次)
        location ~ \.php$ {
            access_by_lua_block {
                local limit_req = require "resty.limit.req"

                -- PHP 请求:每秒最多0.3次请求(10秒最多3次)
                local lim, err = limit_req.new("limit_req_store", 0.3, 3)
                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
                end

                local key = ngx.var.binary_remote_addr
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then
                        ngx.log(ngx.WARN, "PHP request rejected due to rate limiting")
                        return ngx.exit(429)
                    end
                    ngx.log(ngx.ERR, "failed to limit PHP req: ", err)
                    return ngx.exit(500)
                end

                if delay > 0 then
                    ngx.sleep(delay)
                end
            }

            # PHP-FPM 配置
            include fastcgi_params;
            fastcgi_pass unix:/run/php/php7.4-fpm.sock;  # 确保路径正确
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }

        # 对非 PHP 文件的请求进行限流(10秒内最多5次,允许突发)
        location / {
            access_by_lua_block {
                local limit_req = require "resty.limit.req"

                -- 非 PHP 请求:每秒最多0.5次请求(10秒最多5次,允许突发)
                local lim, err = limit_req.new("limit_req_store", 0.5, 10)
                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
                end

                local key = ngx.var.binary_remote_addr
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then
                        ngx.log(ngx.WARN, "Non-PHP request rejected due to rate limiting")
                        return ngx.exit(429)
                    end
                    ngx.log(ngx.ERR, "failed to limit non-PHP req: ", err)
                    return ngx.exit(500)
                end

                if delay > 0 then
                    ngx.sleep(delay)
                end
            }

            try_files $uri $uri/ /index.php?$args;
        }

        # 静态文件处理
        location ~* \.(jpg|jpeg|png|gif|css|js|ico|html)$ {
            expires 30d;
            access_log off;
        }
    }
}

配置说明:

  1. location ~ \.php$:此配置块专门处理 PHP 请求,并应用限流规则,限制为 10 秒内最多 3 次请求local lim = limit_req.new("limit_req_store", 0.3, 3))。
  2. location /:此块处理除 PHP 文件之外的所有请求,限流为 10 秒内最多 5 次请求,允许突发local lim = limit_req.new("limit_req_store", 0.5, 10))。
  3. 突发限制:在处理非 PHP 请求时,突发参数设置为 10,允许突发请求数量更高,但在过高时仍会触发限流。
  4. 静态文件处理:对静态文件设置缓存,并禁用日志记录,减少服务器负载。

通过这个配置,PHP 请求和其他请求可以分别应用不同的限流策略,符合对访问频率的需求。

示例3:

当网站使用了 Cloudflare 的 CDN 时,默认情况下,你从 ngx.var.remote_addrngx.var.binary_remote_addr 获取到的是 Cloudflare 服务器的 IP,而不是用户的真实 IP。为了获取用户的真实 IP,需要使用 Cloudflare 添加的 X-Forwarded-ForCF-Connecting-IP 请求头。你可以根据这些请求头来获取用户的真实 IP。

修改步骤:

  1. 使用 CF-Connecting-IP 获取真实 IP
    Cloudflare 会将用户的真实 IP 添加到 CF-Connecting-IP 请求头中。因此,你可以在限流逻辑中使用这个请求头来代替 ngx.var.remote_addr

  2. 修改 access_by_lua_block 来读取真实 IP
    使用 ngx.var.http_cf_connecting_ip 变量获取 CF-Connecting-IP,并替换原来的 ngx.var.binary_remote_addr

修改后的配置示例:

http {
    # 设置 Lua 模块路径
    lua_package_path "/opt/lua-resty-limit-traffic/lib/?.lua;;";

    # 定义共享内存区域,用于存储限流数据
    lua_shared_dict limit_req_store 10m;

    server {
        listen 80;
        server_name yourdomain.com;
        root /path/to/your/wordpress;  # 修改为你 WordPress 的根目录路径
        index index.php index.html index.htm;

        # 对 PHP 文件请求进行限流(10秒内最多3次)
        location ~ \.php$ {
            access_by_lua_block {
                -- 引入 resty.limit.req 模块
                local limit_req = require "resty.limit.req"

                -- PHP 请求:每秒最多0.3次请求(10秒最多3次)
                local lim, err = limit_req.new("limit_req_store", 0.3, 3)
                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
                end

                -- 使用 CF-Connecting-IP 作为限流键
                local key = ngx.var.http_cf_connecting_ip or ngx.var.remote_addr
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then
                        ngx.log(ngx.WARN, "PHP request rejected due to rate limiting")
                        return ngx.exit(429)
                    end
                    ngx.log(ngx.ERR, "failed to limit PHP req: ", err)
                    return ngx.exit(500)
                end

                -- 如果请求延迟存在,则进行休眠
                if delay > 0 then
                    ngx.sleep(delay)
                end
            }

            # PHP-FPM 配置
            include fastcgi_params;
            fastcgi_pass unix:/run/php/php7.4-fpm.sock;  # 确保路径正确
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        }

        # 对非 PHP 文件的请求进行限流(10秒内最多5次,允许突发)
        location / {
            access_by_lua_block {
                -- 引入 resty.limit.req 模块
                local limit_req = require "resty.limit.req"

                -- 非 PHP 请求:每秒最多0.5次请求(10秒最多5次,允许突发)
                local lim, err = limit_req.new("limit_req_store", 0.5, 10)
                if not lim then
                    ngx.log(ngx.ERR, "failed to instantiate a resty.limit.req object: ", err)
                    return ngx.exit(500)
                end

                -- 使用 CF-Connecting-IP 作为限流键
                local key = ngx.var.http_cf_connecting_ip or ngx.var.remote_addr
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then
                        ngx.log(ngx.WARN, "Non-PHP request rejected due to rate limiting")
                        return ngx.exit(429)
                    end
                    ngx.log(ngx.ERR, "failed to limit non-PHP req: ", err)
                    return ngx.exit(500)
                end

                -- 如果请求延迟存在,则进行休眠
                if delay > 0 then
                    ngx.sleep(delay)
                end
            }

            # WordPress 固定链接处理
            try_files $uri $uri/ /index.php?$args;
        }

        # 静态文件处理,禁止访问日志并设置长时间缓存
        location ~* \.(jpg|jpeg|png|gif|css|js|ico|html)$ {
            expires 30d;
            access_log off;
        }
    }
}

配置说明:

  1. ngx.var.http_cf_connecting_ip:从 Cloudflare 发送的请求头中提取用户的真实 IP。如果 Cloudflare 没有提供这个请求头,默认会使用 ngx.var.remote_addr
  2. 限流逻辑:修改后的限流逻辑会基于 CF-Connecting-IP(即用户的真实 IP)进行限流,而不是 Cloudflare 的服务器 IP。

通过这种方式,你可以正确地获取用户的真实 IP,并应用限流策略。

本文收录于