unblocker
Unblocker 最初是一个用于规避互联网审查的 Web 代理,类似于 CGIproxy / PHProxy / Glype,但 使用 node.js 编写。它已演变为一个用于代理和重写远程网页的通用库。
所有数据都在传输过程中即时处理并中继到客户端,无需不必要的缓冲,这使得 unblocker 成为 可用的最快的 Web 代理之一。
神奇之处
该脚本使用“美观”的 URL,除了看起来美观外,还允许使用相对路径的链接
无需修改即可正常工作。(例如 <a href="path/to/file2.html"></a>)
除此之外,相对于根目录的链接(例如 <a href="/path/to/file2.html"></a>)
可以通过检查 referrer 并将其 307 重定向到引用站点中的正确位置来处理,而无需修改。(尽管代理会尝试重写这些链接以避免重定向。)
Cookie 通过调整其路径以包含代理的 URL 来进行代理,并且会做一些额外的工作以确保在切换协议或子域名时 它们保持完整。
局限性
虽然代理对于标准登录表单甚至大多数 AJAX 内容都能很好地工作,但 OAuth 登录表单以及任何使用 postMessage(Google、Facebook 等)的内容都不太可能开箱即用。这不是一个无法解决的问题,但我预计短期内不会修复。
更高级的网站,例如 Roblox、Discord、YouTube*、Instagram 等,目前无法正常工作。目前,尚不清楚何时会支持这些网站。
- 有一个示例可以检测 YouTube 视频页面并将其替换为仅流式传输视频的自定义页面。
欢迎提交补丁,包括用于主库的通用改进,以及放入 examples 文件夹的特定站点修复。
在您的计算机上运行该网站
参见 https://github.com/nfriedly/nodeunblocker.com
在您的软件中作为库使用 unblocker
npm install --save unblocker
Unblocker 导出一个 express 兼容的 API,因此在 express 应用中使用非常简单:
var express = require('express')
var Unblocker = require('unblocker');
var app = express();
var unblocker = new Unblocker({prefix: '/proxy/'});
// 这必须是 app.use() 调用中最先的几个之一,且不能位于子目录下才能正常工作
app.use(unblocker);
app.get('/', function(req, res) {
//...
});
// upgrade 处理器允许 unblocker 代理 websockets
app.listen(process.env.PORT || 8080).on('upgrade', unblocker.onUpgrade);
参见 examples/simple/server.js 以获取完整示例。
不使用 express 的用法同样简单,参见 examples/simple/server.js 以获取示例。
配置
Unblocker 支持以下配置选项,默认值如下所示:
{
prefix: '/proxy/', // Path that the proxied URLs begin with. '/' is not recommended due to a few edge cases.
host: null, // Host used in redirects (e.g `example.com` or `localhost:8080`). Default behavior is to determine this from the request headers.
requestMiddleware: [], // Array of functions that perform extra processing on client requests before they are sent to the remote server. API is detailed below.
responseMiddleware: [], // Array of functions that perform extra processing on remote responses before they are sent back to the client. API is detailed below.
standardMiddleware: true, // Allows you to disable all built-in middleware if you need to perform advanced customization of requests or responses.
clientScripts: true, // Injects JavaScript to force things like WebSockets and XMLHttpRequest to go through the proxy.
processContentTypes: [ // All built-in middleware that modifies the content of responses limits itself to these content-types.
'text/html',
'application/xml+xhtml',
'application/xhtml+xml',
'text/css'
],
httpAgent: null, //override agent used to request http response from server. see https://nodejs.org/api/http.html#http_class_http_agent
httpsAgent: null //override agent used to request https response from server. see https://nodejs.org/api/https.html#https_class_https_agent
}
设置 process.env.NODE_ENV='production' 将在客户端脚本上启用更积极的缓存,并可能在未来引入其他优化。
自定义中间件
Unblocker 的“中间件”是小型函数,允许你检查和修改请求与响应。Unblocker 的大部分内部逻辑都实现为中间件,并且可以编写自定义中间件来增强或替换内置中间件。
自定义中间件应是一个接受单个 data 参数并同步运行的函数。
要处理请求和响应数据,请创建一个 Transform Stream 以分块执行处理,并通过此流进行管道传输。(示例见下文。)
要直接响应请求,请向 config.requestMiddleware 添加一个函数,该函数处理 clientResponse(直接使用时为标准 http.ServerResponse,与 Express 一起使用时为 Express Response。一旦响应已发送,该请求将不再执行任何后续中间件。(示例见下文。)
requestMiddleware
数据示例:
{
url: 'http://example.com/',
clientRequest: {request},
clientResponse: {response},
headers: {
//...
},
stream: {ReadableStream of data for PUT/POST requests, empty stream for other types}
}
requestMiddleware 可以检查 headers、url 等。它可以修改 headers,通过转换流管道传输 PUT/POST 数据,或直接响应请求。 如果你正在使用 express,请求和响应对象将包含所有常见的 express 特性。例如:
function validateRequest(data) {
if (!data.url.match(/^https?:\/\/en.wikipedia.org\//)) {
data.clientResponse.status(403).send('Wikipedia only.');
}
}
var config = {
requestMiddleware: [
validateRequest
]
}
如果任何中间件发送了响应,则不再执行后续的中间件。
在所有 requestMiddleware 执行完毕后,请求将连同(可能已修改的)url/headers/stream 等一起转发到远程服务器。
responseMiddleware
responseMiddleware 接收与 requestMiddleware 相同的 data 对象,但 headers 和 stream 字段会被替换为远程服务器响应中的对应字段,并且会添加若干用于远程请求和响应的新字段:
数据示例:
{
url: 'http://example.com/',
clientRequest: {request},
clientResponse: {response},
remoteRequest {request},
remoteResponse: {response},
contentType: 'text/html',
headers: {
//...
},
stream: {ReadableStream of response data}
}
若要修改内容,请创建一个新的流,然后将 data.stream 管道传输到该流,并用它替换 data.stream:
var Transform = require('stream').Transform;
function injectScript(data) {
if (data.contentType == 'text/html') {
// https://nodejs.org/api/stream.html#stream_transform
var myStream = new Transform({
decodeStrings: false,
function(chunk, encoding, next) {
chunk = chunk.toString.replace('</body>', '<script src="/my/script.js"></script></body>');
this.push(chunk);
next();
}
});
data.stream = data.stream.pipe(myStream);
}
}
var config = {
responseMiddleware: [
injectScript
]
}
请参阅 examples/nodeunblocker.com/app.js 了解添加中间件的另一个示例。此外,请参阅 lib/ 文件夹中的任何内置中间件。
内置中间件
代理的大多数内部功能也是以中间件的形式实现的:
-
host: 修正出站响应中的
host头 -
referer: 修正出站请求中的
referer头 -
cookies: 修正 set-cookie 头中的
Path属性,以将 cookie 限制在代理的 "path" 上(例如Path=/proxy/http://example.com/)。 同时注入重定向,以在给定域名的不同协议和子域名之间复制 cookie。 -
hsts: 移除 Strict-Transport-Security 头,因为它们可能泄露到其他站点并破坏代理。
-
hpkp: 移除 Public-Key-Pinning 头,因为它们可能泄露到其他站点并破坏代理。
-
csp: 移除 Content-Security-Policy 头,因为它们可能泄露到其他站点并破坏代理。
-
redirects: 重写 3xx 重定向中的 URL,以确保它们通过代理
-
decompress: 解压缩
Content-Encoding: gzip|deflate响应,并调整请求头以请求仅 gzip 或完全不压缩。(它会尝试解压缩deflate内容,但存在一些 issues,因此不宣传支持deflate。) -
charsets: 将响应的字符集转换为 UTF-8,以便在 node.js 中进行安全的字符串处理。从头部或 meta 标签中确定字符集,并重写出站响应中的所有头部和 meta 标签。
-
urlPrefixer: 重写链接/图片/css/等的 URL,以确保它们通过代理
-
metaRobots: 注入 ROBOTS: NOINDEX, NOFOLLOW meta 标签,以防止搜索引擎通过代理爬取整个网络。
-
contentLength: 如果响应体已被修改,则删除响应中的 content-length 头。
将 standardMiddleware 配置选项设置为 false 可禁用所有内置中间件,从而允许您选择性地启用、配置和重新排序内置中间件。
此配置将模拟默认设置:
var Unblocker = require('unblocker');
var config = {
prefix: '/proxy/',
host: null,
requestMiddleware: [],
responseMiddleware: [],
standardMiddleware: false, // disables all built-in middleware
processContentTypes: [
'text/html',
'application/xml+xhtml',
'application/xhtml+xml'
]
}
var host = Unblocker.host(config);
var referer = Unblocker.referer(config);
var cookies = Unblocker.cookies(config);
var hsts = Unblocker.hsts(config);
var hpkp = Unblocker.hpkp(config);
var csp = Unblocker.csp(config);
var redirects = Unblocker.redirects(config);
var decompress = Unblocker.decompress(config);
var charsets = Unblocker.charsets(config);
var urlPrefixer = Unblocker.urlPrefixer(config);
var metaRobots = Unblocker.metaRobots(config);
var contentLength = Unblocker.contentLength(config);
config.requestMiddleware = [
host,
referer,
decompress.handleRequest,
cookies.handleRequest
// custom requestMiddleware here
];
config.responseMiddleware = [
hsts,
hpkp,
csp,
redirects,
decompress.handleResponse,
charsets,
urlPrefixer,
cookies.handleResponse,
metaRobots,
// custom responseMiddleware here
contentLength
];
var unblocker = new Unblocker(config);
app.use(unblocker);
// ...
// the upgrade handler allows unblocker to proxy websockets
app.listen(process.env.PORT || 8080).on('upgrade', unblocker.onUpgrade);
调试
Unblocker 已全面集成 debug。 通过环境变量启用调试:
DEBUG=unblocker:* node mycoolapp.js
还有一个中间件调试器,它在每个现有中间件 函数前后添加额外的调试中间件,以报告变更。它包含在默认的 DEBUG 激活中,也可以选择性启用:
DEBUG=unblocker:middleware node mycoolapp.js
... 或者禁用:
DEBUG=*,-unblocker:middleware node mycoolapp.js
故障排除
如果你使用 Nginx 作为反向代理,你可能需要禁用 merge_slashes 以避免无限重定向和/或其他问题:
merge_slashes off;
待办事项
- 考虑添加 compress 中间件以压缩文本类响应
- 去除 GET / POST 数据中 URL 的前缀
- 注入 js 以代理 postMessage 数据并修复 origins
- 更多示例
- 更多测试
AGPL-3.0 许可证
本项目根据 GNU Affero General Public License version 3 的条款发布。
所有源代码版权归 Nathan Friedly 所有。
也可提供商业许可和支持,请联系 Nathan Friedly (nathan@nfriedly.com) 了解详情。