ITADN
nfriedly/node-unblocker
README.md
以下内容由 AI 翻译,如有问题请点此提交 issue 反馈

unblocker

Unblocker 最初是一个用于规避互联网审查的 Web 代理,类似于 CGIproxy / PHProxy / Glype,但 使用 node.js 编写。它已演变为一个用于代理和重写远程网页的通用库。

所有数据都在传输过程中即时处理并中继到客户端,无需不必要的缓冲,这使得 unblocker 成为 可用的最快的 Web 代理之一。

Node.js CI npm-version

神奇之处

该脚本使用“美观”的 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 等,目前无法正常工作。目前,尚不清楚何时会支持这些网站。

欢迎提交补丁,包括用于主库的通用改进,以及放入 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 对象,但 headersstream 字段会被替换为远程服务器响应中的对应字段,并且会添加若干用于远程请求和响应的新字段:

数据示例:

{
    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) 了解详情。

贡献者