真正的 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
存放了模糊匹配到的字符串, 好可怕的逻辑.
于是变相做了字符串截取..实现.