真正的 nginx 万能代理

June 18, 2017

需求

因为一些特殊要求, 要能通过 url 过访问国外的一些资源.

于是想用 nginx 来做一个反向代理, 实现一个 http 的 proxy.

真正要访问的那个资源 url 做为一个参数传过去.

这个需求够简单, 可是看似很简单的需求并不好弄, 尤其涉及到 nginx 这种配置很反人类, 文档又很糟糕的程序.

基础

用 nginx 的 proxy_pass 功能来实现, 这样无论 url 是网站, 图片还是视频都是 ok 的.

取参数

只能用 get, 那么 real url 如何传给 nginx 呢?

看了半天 http://nginx.org/en/docs/http/ngx_http_core_module.html#variables

找到 $arg_name, 那么只要弄一个 arg_url 来传递就行了, 类似

http://follow.center/p?url=https://video.xx.fbcdn.net/v/t42.1790-2/19175485_1939499709662168_1558819209981460480_n.mp4?efg=eyJybHIiOjM4OCwicmxhIjo1MTIsInZlbmNvZGVfdGFnIjoic3ZlX3NkIn0%3D&rl=388&vabr=216&oh=59654409bb8ef6f50ac3ddc5604b9820&oe=594253B9

nginx 这样配置

        location ^~ /p {
            proxy_pass $arg_url;
        }

够简单吧, 实现用下来发现, 有的资源能访问, 有的不能

这样传递其实是有问题的

url 带参数的问题

真正要访的 url 带有一些必需的参数, 可是本身已经是用参数传过去了, 那么第二个 ? 后的东西不再会传过去, 那么 arg_url 只取到了 https://video.xx.fbcdn.net/v/t42.1790-2/19175485_1939499709662168_1558819209981460480_n.mp4 资源无法代理过去了.

加上 url encode

那么就把真实的 url, 加一个 url encdoe, ? 被编码, 做为一个整体, 那就没问题了吧?

http://follow.center/p?url=https%3A%2F%2Fvideo.xx.fbcdn.net%2Fv%2Ft42.1790-2%2F19175485_1939499709662168_1558819209981460480_n.mp4%3Fefg%3DeyJybHIiOjM4OCwicmxhIjo1MTIsInZlbmNvZGVfdGFnIjoic3ZlX3NkIn0%253D%26rl%3D388%26vabr%3D216%26oh%3D59654409bb8ef6f50ac3ddc5604b9820%26oe%3D594253B9

遗憾的是 nginx 不会对 arg 参数里的东西进行 url decode, proxy_pass 把未解码的内容代理过去, 报错了.

nginx 只会对 url 本身的内容进行 decode

把 url 做为 sub url 传递

那么干脆把资源做为子 url 传给 nginx, 改为这样

http://follow.center/p/https://video.xx.fbcdn.net/v/t42.1790-2/19175485_1939499709662168_1558819209981460480_n.mp4?efg=eyJybHIiOjM4OCwicmxhIjo1MTIsInZlbmNvZGVfdGFnIjoic3ZlX3NkIn0%3D&rl=388&vabr=216&oh=59654409bb8ef6f50ac3ddc5604b9820&oe=594253B9

可是 nginx 并没有一个变量是子 url 的

$uri$request_uri

只能取到

/p/https://video.xx.fbcdn.net/v/t42.1790-2/19175485_1939499709662168_1558819209981460480_n.mp4?efg=eyJybHIiOjM4OCwicmxhIjo1MTIsInZlbmNvZGVfdGFnIjoic3ZlX3NkIn0%3D&rl=388&vabr=216&oh=59654409bb8ef6f50ac3ddc5604b9820&oe=594253B9

nginx 自已为是的解码

nginx 还有个坑的地方, 解码的时候会把 %2F%2F 解码为 /, 原本的 //, 解码后自已为是的替换为了 /

url 里有关键字传输, 会让你整个网站都无法被访问. 必须上 https

取真的 url

取到的 url 前多了 /p/, 那我干脆匹配 http , 于是取到的变成了 /https://.. 前面多了一个 /

nginx 又没有 replace 方法什么的, 没法拿到要 pass 的url

变相的 replace

        location ^~ /p {
            if ($request_uri ~* "/p/(.*)") {
                set $subdomain $1;
            }
            proxy_pass $subdomain;
        }

这个 if 的正则匹配, 反正我在官方文档没找到说明.

$1 存放了模糊匹配到的字符串, 好可怕的逻辑.

于是变相做了字符串截取..实现.


comments powered by Disqus