如何在Nginx中按序链式转发请求至多个端点并支持请求修改
Nginx实现多端点链式请求转发方案
需求明确
- 按顺序将请求转发至多个后端端点,前一个端点的响应作为下一个端点的新请求
- 最终返回最后一个端点的响应结果
- 任意端点返回4xx/5xx状态码时,立即终止转发并返回错误
- 支持端点返回
HTTP/1.1 100 Continue响应 - 请求头传递为可选项
已尝试的无效方法
- Proxy Pass:多个
proxy_pass指令无法实现链式逻辑,配置示例:
server { listen 80; location /eval_type1 { proxy_pass http://localhost:134/compile; proxy_pass http://localhost:85/exec; } location /eval_type2 { proxy_pass http://localhost:134/compile http://localhost:85/exec; } }
- Mirror & Proxy Pass:仅能同时向多个端点发送请求,无法实现链式处理,镜像端点的响应会被忽略
- Post Action & Proxy Pass:后续动作的响应不被返回,不支持链式转发逻辑
- GoReplay:依赖第三方工具,不符合依赖Nginx原生能力的需求
- Upstream Server:仅支持配置主机地址,无法指定具体端点路由
- Auth Request:最多仅支持2个端点,无法适配多端点场景
- NJS/CGI:曾担心进程创建问题,后确认NJS基于Nginx核心架构,无此问题
可行实现方案
方案1:NJS(Nginx JavaScript)原生实现
NJS是Nginx官方提供的脚本扩展,运行在Nginx核心进程中,不会为每个请求创建新进程,完全适配需求。
核心逻辑
通过NJS的ngx.fetch() API依次请求后端端点,将前一个端点的响应体和响应头作为下一个请求的输入,同时处理状态码校验和100 Continue响应。
示例配置
- 创建NJS脚本文件
chain_proxy.js:
async function chainProxy(r) { // 定义链式调用的端点列表 const endpoints = [ 'http://localhost:134/compile', 'http://localhost:85/exec' ]; let reqBody = r.requestBody; let reqHeaders = r.headersIn; for (const endpoint of endpoints) { const fetchOpts = { method: r.method, headers: reqHeaders, body: reqBody, expectContinue: true // 启用100 Continue支持 }; try { const res = await ngx.fetch(endpoint, fetchOpts); // 校验状态码,非2xx直接返回错误 if (res.status >= 400) { r.return(res.status, await res.text()); return; } // 更新请求体和头信息,用于下一个端点 reqBody = await res.arrayBuffer(); reqHeaders = Object.fromEntries(res.headers.entries()); } catch (err) { r.return(500, `请求端点失败: ${err.message}`); return; } } // 返回最后一个端点的响应 r.headersOut = reqHeaders; r.return(200, reqBody); } export default { chainProxy };
- Nginx配置中引入脚本并使用:
http { js_import chain from ./chain_proxy.js; server { listen 80; location /eval_type1 { js_content chain.chainProxy; } } }
方案2:OpenResty(Lua模块)实现
OpenResty基于Nginx扩展,集成LuaJIT,性能优异,适合复杂业务逻辑处理。
核心逻辑
通过resty.http库依次请求后端端点,传递前一个响应的内容和头信息,实时校验状态码并处理错误。
示例配置
- 确保OpenResty已安装
resty.http库 - Nginx配置:
http { server { listen 80; location /eval_type1 { content_by_lua_block { local http = require "resty.http" local endpoints = { "http://localhost:134/compile", "http://localhost:85/exec" } local req_body = ngx.req.get_body_data() local req_headers = ngx.req.get_headers() for _, endpoint in ipairs(endpoints) do local httpc = http.new() httpc:set_timeout(5000) -- 构建请求,启用100 Continue支持 local res, err = httpc:request_uri(endpoint, { method = ngx.req.get_method(), headers = req_headers, body = req_body, expect = 100 }) if not res then ngx.status = 500 ngx.say("请求端点失败: ", err) return end -- 校验状态码 if res.status >= 400 then ngx.status = res.status ngx.say(res.body) return end -- 更新请求体和头信息 req_body = res.body req_headers = res.headers end -- 返回最终响应 ngx.status = 200 for k, v in pairs(req_headers) do ngx.header[k] = v end ngx.say(req_body) } } } }
方案对比
| 方案 | 优势 | 劣势 |
|---|---|---|
| NJS | 官方原生支持,无需额外安装第三方组件,配置简单 | 功能相对有限,复杂逻辑处理能力较弱 |
| OpenResty Lua | 功能强大,生态丰富,适合复杂业务场景 | 需要安装OpenResty,学习成本稍高 |
内容的提问来源于stack exchange,提问作者Farhan M Sabran
相关产品推荐
相关产品推荐

