NGINX 从 proxy_pass 响应中读取正文

NGINX read body from proxy_pass response

我有两台服务器:

  1. NGINX(它将文件 ID 交换为文件路径)
  2. Golang(它接受文件 ID 和 return 它的路径)

例如: 当浏览器客户端向 https://example.com/file?id=123 发出请求时,NGINX 应该将此请求代理到 Golang 服务器 https://go.example.com/getpath?file_id=123,后者将 return对 NGINX 的回应:

{
  data: {
    filePath: "/static/..."
  },
  status: "ok"
}

然后 NGINX 应该从 filePath 和 return file 从该位置获取值。

所以问题是如何在 NGINX 中读取响应(获取文件路径)?

看起来您想要 api 调用数据以反对 运行 决策和逻辑。这不是代理的全部内容。

nginx 的核心代理能力并不是为您的目的而设计的。

可能的解决方法:扩展 nginx...


Nginx + PHP

您的 php 代码可以解决问题。
作为客户端连接到 Golang 服务器并对响应应用附加逻辑。

<?php
    $response = file_get_contents('https://go.example.com/getpath?file_id='.$_GET["id"]);
    preg_match_all("/filePath: \"(.*?)\"/", $response, $filePath);
    readfile($filePath[1][0]);
?>
    location /getpath {
        try_files /getpath.php;
    }

这只是让它滚动的伪代码示例。

一些杂项观察/评论:

  • Golang 响应看起来无效 json,如果有效,请将 preg_match_all 替换为 json_decode。
  • readfile 效率不高。考虑通过 302 响应发挥创意。

Nginx + Lua

已启用站点:

lua_package_path "/etc/nginx/conf.d/lib/?.lua;;";

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    location /getfile {
        root /var/www/html;
        resolver 8.8.8.8;
        set $filepath "/index.html";
        access_by_lua_file /etc/nginx/conf.d/getfile.lua;
        try_files $filepath =404;
    }
}

测试 lua 是否按预期运行:

getfile.lua (v1)

  ngx.var.filepath = "/static/...";

将 Golang 响应主体简化为 return 一个平淡的路径,然后用它来设置文件路径:

getfile.lua (v2)

local http = require "resty.http"
local httpc = http.new()
local query_string = ngx.req.get_uri_args()
local res, err = httpc:request_uri('https://go.example.com/getpath?file_id=' .. query_string["id"], {
    method = "GET",
    keepalive_timeout = 60,
    keepalive_pool = 10
})

if res and res.status == ngx.HTTP_OK then
    body = string.gsub(res.body, '[\r\n%z]', '')
    ngx.var.filepath = body;
    ngx.log(ngx.ERR, "[" .. body .. "]");
else
    ngx.log(ngx.ERR, "missing response");
    ngx.exit(504);
end

resty.http

mkdir -p /etc/nginx/conf.d/lib/resty
wget "https://raw.githubusercontent.com/ledgetech/lua-resty-http/master/lib/resty/http_headers.lua" -P /etc/nginx/conf.d/lib/resty
wget "https://raw.githubusercontent.com/ledgetech/lua-resty-http/master/lib/resty/http.lua" -P /etc/nginx/conf.d/lib/resty

我假设您是软件开发人员并且您可以完全控制您的应用程序,因此无需在此处强行将方钉插入圆孔。

不同类型的反向代理支持ESI(Edge Side Includes)技术,允许开发者用静态文件内容或响应替换响应的不同部分body来自上游服务器的正文。

Nginx也有这样的技术。它被称为 SSI(服务器端包含)

location /file {
    ssi on;
    proxy_pass http://go.example.com;
}

您的上游服务器可以生成内容为 <!--# include file="/path-to-static-files/some-static-file.ext" --> 的 body 并且 nginx 会将此 in-body 指令替换为文件 的内容。

但是你提到了流媒体...

这意味着文件将具有任意大小并且使用 SSI 构建响应肯定会占用宝贵的 RAM 资源,因此我们需要一个 计划 #B.

有 "good enough" 方法可以将大文件提供给客户端而不向客户端显示文件的静态位置。 您可以使用 nginx 的错误处理程序根据上游服务器提供的信息来处理静态文件。 例如,上游服务器可以发送回重定向 302,其中 Location header 字段包含文件的真实文件路径。 此响应未到达客户端并被馈送到错误处理程序中。

配置示例如下:

location /file {
    error_page 302 = @service_static_file;
    proxy_intercept_errors on;
    proxy_set_header Host            $host;
    proxy_pass http://go.example.com;
}

location @service_static_file {
    root /hidden-files;
    try_files $upstream_http_location 404.html;
}

使用此方法,您将能够在没有 over-loading 系统的情况下提供文件,同时可以控制您将文件提供给谁。

为此,您的上游服务器应以状态 302 和典型的 "Location:" 字段进行响应,nginx 将使用位置内容在 "new" 根目录中查找静态文件的文件。

这个方法之所以是"good enough"类型的(而不是完美的)是因为它不支持部分请求(即Range: bytes ...)