【问题标题】:NGINX read body from proxy_pass responseNGINX 从 proxy_pass 响应中读取正文
【发布时间】:2019-12-16 10:39:05
【问题描述】:

我有两台服务器:

  1. NGINX(它将文件 id 交换为文件路径)
  2. Golang(它接受文件 ID 并返回其路径)

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

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

那么 NGINX 应该从 filePath 中获取值并从该位置返回文件。

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

【问题讨论】:

    标签: nginx lua proxypass


    【解决方案1】:

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

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

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

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

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

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

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

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

    这是一个配置示例:

    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;
    }
    

    使用这种方法,您将能够在不使系统过载的情况下提供文件,同时可以控制将文件提供给谁。

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

    此方法之所以为“足够好”类型(而不是完美),是因为它不支持部分请求(即 Range: bytes ...)

    【讨论】:

    • 好把戏)当我在等待答案时,我试图用这种方式解决它: location = /auth { internal; proxy_pass $auth_request_uri; proxy_pass_request_body 关闭; proxy_set_header 内容长度“”; proxy_set_header X-Original-URI $request_uri;设置 $filepath $upstream_http_x_file_path; } 位置 /hls { 设置 $auth_request_uri "docker-storage/info/videos/path?id=$arg_id";身份验证请求/身份验证; auth_request_set $filepath $upstream_http_x_file_path;
    • 很难使用 SO cmets :D 这个想法是代理通过返回 x-filepath 标头和文件路径作为响应,然后我可以通过$upstream_http_*
    • 你走上正轨了。我要注意的是,您并没有被 302 / Location 所束缚,实际上可以使用任何 http 状态代码和任意标头。而且你已经在这样做了:-)
    • proxy_intercept_errors - 这是一个简洁的用例,整体上更简单。
    • @MaximSagaydachny, ваш ответ принят
    【解决方案2】:

    看起来您想要对数据进行 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;
        }
    

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

    一些杂项观察/cmets:

    • 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 响应体,只返回一个平淡无奇的路径,然后用它来设置文件路径:

    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
    

    【讨论】:

    • 这个想法是隐藏服务器上的文件路径。所以用户只知道文件ID,但不知道文件路径。在我的问题之后,我在互联网上搜索答案,发现可以将 lua 连接到 nginx 并使用类似 body_filter_by_lua_block 的东西。但其实我不明白怎么用,你知道吗?
    • 我正在尝试使用 nginx 构建流式传输,使用 PHP 和其他语言会更加困难。实际上也许 NGINX 可以读取响应头?我可以在那里返回文件路径
    • lua 是一个不错的选择。出于某种原因,我记得它不是一个简单的安装即可运行。根据我的一些旧笔记,用 lua 方法更新了答案。
    • 谢谢,我试试看给个回复
    • 更新了lua示例,根据本地测试做了一些调整。应该比旧笔记中的草图获得更好的结果。完成后,请随意将 lib 文件夹和 lua 文件移动到更合适的位置。
    猜你喜欢
    • 1970-01-01
    • 2019-06-23
    • 2017-12-18
    • 1970-01-01
    • 2014-12-13
    • 2011-03-17
    • 2017-03-22
    • 2021-02-13
    • 1970-01-01
    相关资源
    最近更新 更多