nginx通过lua-nginx-module自定义转发请求。
!!!注意事项:
1.当请求body大于 client_body_buffer_size 默认值8k或16k时,请求报文将会被nginx缓存到硬盘,此时 ngx.req.get_body_data() 无法获取到body正文。请修改nginx client_body_buffer_size 128k,或者更大。典型案例:如果是转发multipart/form-data类型,不去修改大小,就会转发失败。
2.Web 应用服务器-OpenResty
zdy_proxy.lua
local cookie = require "resty.cookie"
local http = require "resty.http"
local upload = require "resty.upload"
local json = require "cjson"
local resp = require "ngx.resp"
local redis = require "resty.redis"
-- redis
local function redis_func(key, value, expires_in)
local rds = redis:new()
if not rds then
error("rds is nil")
end
rds:set_timeout(1000) -- 设置超时时间为 1000 毫秒
local ok, err = rds:connect("127.0.0.1", 6379) -- 连接 Redis 服务器
if not ok then
rds:close() -- 连接失败需要及时关闭释放连接
error("failed to connect : " .. err)
end
local res, err = rds:auth("123456") -- 身份认证
if not res then
error("failed to authenticate: " .. err)
end
ngx.log(ngx.DEBUG, "SET " .. key .. " " .. value .. " EX " .. expires_in)
ok, err = rds:set(key, value, "EX", expires_in) -- 向 Redis 写入数据
if not ok then
error("failed to set dog: " .. err)
end
ngx.log(ngx.DEBUG, "set result: ", ok)
rds:close() -- 关闭连接
return ok
end
-- 输入响应内容,ngx.exit退出
local function response_func(resp_status, resp_headers, resp_body)
ngx.status = resp_status
--ngx.header = resp_headers -- !!!这种方式无法设置响应头,暂时未找到bug原因
for v, m in pairs(resp_headers) do
resp.add_header(v, m)
end
ngx.flush(true) -- 同步输出
ngx.print(resp_body)
ngx.exit(ngx.HTTP_OK)
end
-- 代理转发
local function proxy_func(proxy_url)
local client = http.new()
if not client then
error("client is nil")
end
client:set_timeout(30 * 1000)
local request_header = ngx.req.get_headers()
local request_method = ngx.req.get_method()
ngx.req.read_body()
local request_body = ngx.req.get_body_data() -- 当请求body大于 client_body_buffer_size 默认值8k或16k时,请求报文将会被nginx缓存到硬盘,此时 ngx.req.get_body_data() 无法获取到body正文。请修改nginx client_body_buffer_size 128k,或者更大。!!!典型案例:如果是转发multipart/form-data类型,不去修改大小,就会转发失败
ngx.log(ngx.DEBUG, "request_url ", proxy_url)
ngx.log(ngx.DEBUG, "request_method ", request_method)
ngx.log(ngx.DEBUG, "request_header ", json.encode(request_header))
ngx.log(ngx.DEBUG, "request_body ", request_body)
local ok, err =
client:request_uri(
proxy_url,
{
method = request_method,
headers = request_header,
body = request_body,
ssl_verify = false -- 验证SSL证书是否与主机名匹配
}
)
ngx.log(ngx.DEBUG, "ok type is ", type(ok))
ngx.log(ngx.DEBUG, "err type is ", type(err))
if not ok then -- 检查请求是否成功
error("failed to request : " .. err)
end
local response_body = ok.body
local response_headers = ok.headers
local response_status = ok.status
ngx.log(ngx.DEBUG, "response_status ", json.encode(response_status))
ngx.log(ngx.DEBUG, "response_headers ", json.encode(response_headers))
ngx.log(ngx.DEBUG, "response_body ", json.encode(response_body))
return response_status, response_headers, response_body
end
-- 主函数
local function mian_func()
local request_url = ngx.var.request_uri
ngx.log(ngx.DEBUG, "request_url=", request_url)
if string.find(request_url, "api/test/", 1, true) ~= nil then -- api/test开头的就要通过解析参数转发
ngx.log(ngx.DEBUG, "=============== start countyCode proxy ===============")
local countyCode = nil
-- 解析参数,获取countyCode begin
local args = ngx.req.get_uri_args() -- 先分析url参数,如:/test?foo=bar&bar=baz&countyCode=1111
if type(args) == "table" and _G.next(args) ~= nil then -- 排除args是空的table
countyCode = args["countyCode"]
else -- 从body的内容获取
ngx.req.read_body()
args = ngx.req.get_body_data()
args = json.decode(args)
countyCode = args["reqBody"]["countyCode"]
end
ngx.log(ngx.DEBUG, "args ", json.encode(args))
-- end
if countyCode ~= nil and string.len(countyCode) > 0 then
if countyCode == "1001" then -- 根据 countyCode 进行转发
local resp_status, resp_headers, resp_body = proxy_func("http://127.0.0.1:8045" .. request_url)
response_func(resp_status, resp_headers, resp_body)
else
local resp_status, resp_headers, resp_body = proxy_func("http://127.0.0.1:8002" .. request_url)
response_func(resp_status, resp_headers, resp_body)
end
else
error("failed get param countyCode") -- 参数解释失败抛出异常
end
elseif string.find(request_url, "/oauth2/get/token", 1, true) ~= nil then -- 统一身份认证
local resp_status, resp_headers, resp_body = proxy_func("http://127.0.0.1:8045" .. request_url)
-- 设置缓存 begin
local res_data = json.decode(resp_body)
if not res_data["error"] then -- 成功获取到了token
local key = "Cache@" .. res_data["access_token"]
local value = '{"TypeName":"string","Value":' .. json.encode(resp_body) .. "}"
local expires_in = tonumber(res_data["expires_in"])
redis_func(key, value, expires_in)
end
-- end
response_func(resp_status, resp_headers, resp_body)
else
-- 转发正常请求
local resp_status, resp_headers, resp_body = proxy_func("http://127.0.0.1:8011" .. request_url)
response_func(resp_status, resp_headers, resp_body)
end
end
-- runing......
mian_func()
nginx.conf
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
error_log logs/error.log debug;
pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
server {
listen 8011;
server_name localhost;
lua_http10_buffering off;
#charset koi8-r;
#access_log logs/host.access.log main;
#location / {
# root html;
# index index.html index.htm;
#}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
#==========================================================
location / {
default_type 'text/plain';
charset 'utf-8';
#指定由lua解析
content_by_lua_file lua/zdy_proxy.lua;
client_body_buffer_size 50M;
}
#==========================================================
}
# another virtual host using mix of IP-, name-, and port-based configuration
#
#server {
# listen 8000;
# listen somename:8080;
# server_name somename alias another.alias;
# location / {
# root html;
# index index.html index.htm;
# }
#}
# HTTPS server
#
#server {
# listen 443 ssl;
# server_name localhost;
# ssl_certificate cert.pem;
# ssl_certificate_key cert.key;
# ssl_session_cache shared:SSL:1m;
# ssl_session_timeout 5m;
# ssl_ciphers HIGH:!aNULL:!MD5;
# ssl_prefer_server_ciphers on;
# location / {
# root html;
# index index.html index.htm;
# }
#}
}
参考文献
openresty/lua-nginx-module
LuatOS
OpenResty