Nginx proxy intercept redirect and pass custom red

2020-04-18 06:01发布

问题:

I have a web application that wants to access files from a third party site without CORS enabled. The requests can be to an arbitrary domain with arbitrary parameters. I'm sending a request to my domain containing the target encoded as a GET parameter, i.e.

GET https://www.example.com/proxy/?url=http%3A%2F%2Fnginx.org%2Fen%2Fdocs%2Fhttp%2Fngx_http_proxy_module.html

Then in Nginx I do

location /proxy/ {
    resolver 8.8.8.8;
    set_unescape_uri $dst $arg_url;
    proxy_pass $dst;
}

This works for single files, but the target server sometimes returns a Location header, which I want to intercept and modify for the client to retry.

Basically I would like to escape $sent_http_location, append it to https://www.example.com/proxy/?url= and pass that back to the browser to retry.

I've tried doing

set_escape_uri $tmp $sent_http_location;
proxy_redirect $sent_http_header /pass/?v=$tmp;

but this doesn't work. I've also tried saving the Location header, then ignoring the incoming header with

proxy_hide_header

and replacing it with my own

proxy_set_header

but ignoring causes me to lose the variable saving it.

How can I configure Nginx to accomplish this handling of redirects so that I could pass a encoded URL would be returned to the user when the proxied site redirects?

回答1:

There are several problems with your unsuccessful approach:

  1. proxy_set_header sets the header that goes to the upstream server, not to the client. So even if $sent_http_location hadn't been empty, your configuration couldn't possibly work as you wanted it to.

  2. $sent_http_<header> variables point exactly to the same area of memory as the response headers that will be send to the client. So when proxy_hide_header takes effect, the specified header is being removed from the memory along with the value of the corresponding $sent_http_<header>.

  3. set_escape_uri works at a very early stage of the request processing, way before proxy_pass is called and the Location header is returned from the upstream server. So it will always process the at that time empty variable $sent_http_location and the result also will always be the empty variable.

The last problem is the most serious. The only way to make set_escape_uri work after proxy_pass is to force Nginx to leave the current location and start the processing all over again. This can be done with the error_page trick:

location /proxy/ {
    resolver 8.8.8.8;
    set_unescape_uri $dst $arg_url;
    proxy_pass $dst;

    proxy_intercept_errors on;
    error_page 301 = @rewrite_301;
}

location @rewrite_301 {
    set_escape_uri $location $upstream_http_location;
    return 301 /pass/?v=$location;
}

Note the use of $upstream_http_location instead of $sent_http_location. When Nginx leaves the context of the location, it assumes that the request will be proxied to another upstream, or processed in some other way, and so it clears the headers recieved from the last proxy_pass, to make room for new response headers.

Unlike $sent_http_<header> vairables, which represent response headers that will be send to the client, $upstream_http_<header> variables represent response headers that were recieved from the upstream. Because of that, they are only replaced with new values when the request is proxied to another upstream server. So, once set, these variables can be used at any moment, they will not be cleared.