TTTYGateway 是基于 OpenResty 的一套 HTTP 请求限制模块
Path: 可以对指定请求方法 (GET POST PUT DELETE) 及请求的路径进行匹配,路径支持简单的通配符
User: 代码中自由定义,可以未登陆时使用 IP,登陆后使用用户 ID 或 TOKEN 等
OpenResty 主体就是 Nginx + JITLua,通常 JITLua 的性能和 C 在一个量级,使用 OpenResty 写的应用,运行起来通常会有 C 程序运行的快感。
下面一个简单的对比示例,只是简单表示 JITLua 是很快的,因为只使用了简单的逻辑,在整体上他们性能还是有些差别的
C Code
#include "stdio.h"
int main() {
int sum=0;
int n = 5000000;
for(int i=1; i<=n; i++){
if(i%2==0){
sum = sum - i;
} else {
sum = sum + i;
}
}
printf("%i", sum);
}
Lua Code
local sum = 0
local n = 5000000
for i = 1, n do
if i % 2 == 0 then
sum = sum - i
else
sum = sum + i
end
end
print(sum)
C 编译后运行
$ time ./add
-2500000
real 0m0.025s
user 0m0.021s
sys 0m0.001s
Lua 编译后运行
$ time luajit ./add.lua
-2500000
real 0m0.025s
user 0m0.022s
sys 0m0.001s
只需要把你的 nginx 配置复制到项目的 nginx/conf/nginx.conf
下面,然后做如下配置
http {
# 指向 ttty-gateway 目录 下的 nginx/lualib
lua_package_path "/path/to/ttty-gateway/nginx/lualib/?.lua;;";
init_by_lua_file 'lualib/init.lua'; # load gateway config
server {
listen 80;
-- 为了直接在 nginx 中取得用户实际的 IP
set_real_ip_from 127.0.0.1;
set_real_ip_from 192.168.0.0/16;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.0.0.0/12;
real_ip_header 'X-Forwarded-For';
real_ip_recursive on;
# 关键点,运行 API 请求检查器
# 如果放在 server block 中,所检查所有请求,像静态资源什么的
access_by_lua_file 'lualib/access_checker.lua';
location /api {
# 通常我们都是在 location 下只限制 API 的请求
# access_by_lua_file 'lualib/access_checker.lua';
proxy_pass http://xxx;
}
}
}
然后跑起来
openresty -p /path/to/ttty-gateway
或者使用 docker
$ docker run -v /path/to/ttty-gateway:/openresty -e REDIS_HOST={IP} openresty/openresty -p /openresty
当然,你也可以先 build 好,再用 docker 运行
$ docker build ./ -t ttty_gateway:latest
$ docker run -v /path/to/your/nginx.conf:/openresty/conf/nginx.conf -e REDIS_HOST={IP} ttty_gateway
上面运行时会需要 Redis,如果没有,所有请求都不会进行限制,也就是说,运行一半时,如果你的 Redis 服务挂了,就不会进行限制检查了。而 Mysql 服务是可选的,可以让你动态配置限制规则。
上面运行的是默认规则,详见 nginx/lualib/web_shield_config.lua
,我们只是将内网 IP 设置为白名单:
whitelist: {'127.0.0.1', '192.168.0.1/16', '172.17.0.1/12', '10.0.0.0/8'}
并对所有的请求做了一个简单的分级限制
{
-- level 1
{matcher = {method = READ, path = '*'}, period = 20, limit = 15},
{matcher = {method = WRITE, path = '*'}, period = 20, limit = 7},
-- level 2
{matcher = {method = READ, path = '*'}, period = 60, limit = 30},
{matcher = {method = WRITE, path = '*'}, period = 60, limit = 14},
-- level 3
{matcher = {method = READ, path = '*'}, period = 120, limit = 45},
{matcher = {method = WRITE, path = '*'}, period = 120, limit = 21},
}
更详细的可以参考文档。上面的这些配置都支持直接在 Mysql 中动态配置的,ttty-gateway 会自动缓存 Mysql 中的配置,并简单的检查是否正确,所以,如果 Mysql 挂了的话,他会使用缓存,如果配置错误的话,将会直接忽略 Mysql 中的配置,而是使用默认文件中的配置
Mysql 配置的话,我们写了一个简单的配置项目 TTTYManager
在 nginx/lualib/access_checker.lua
文件中,默认使用了我们自己的逻辑取得用户标识,应该不会是你想要的,你需要做一些修改,告诉系统你的用户标识,比如下面的逻辑
local WebShield = require 'resty.web_shield'
local ip = ngx.var.remote_addr
local h = ngx.req.get_headers()
local uid = h['X-User-ID'] or 'nil'
-- 简单的取得 `nginx/lualib/web_shield_config' 中的配置,为了简单不再去取 mysql 配置
local config = require('web_shield_config')
local web_shield = WebShield.new(config.web_shield, config.shield)
-- 使用自定的用户标识进行检查,如果失败就返回 429
if not web_shield:check(ip, uid, ngx.var.request_method, ngx.var.uri) then
ngx.status = ngx.HTTP_TOO_MANY_REQUESTS
ngx.say("Too many requests")
ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS)
end
先就介绍到这,如果有问题的话,可以邮件 (github) 联系我,或是直接在项目中提 issue,此项目将会持续改进,欢迎关注我们“天天投研”团队。