関連記事Workers を利用して 2 つの WEB プロキシを構築し、匿名でインターネットにアクセスする と Hideipnetwork 基于 Node.js + services wokers オンライン WEB プロキシ
現在このスクリプトは v3 に更新されており、皆さんは直接:https://official.hideip.network/ 公式サイトで新しいコードを確認し、コピーすれば大丈夫です!
私が書いた記事では Workers を使用して構築しており、サーバーは必要ありません。現在、GitHub の作者はソースコードを削除しています。
しかし、ダウンロードは提供されており、サーバーを構築することができます。現在私も Workers のコードをバックアップしており、記事の最下部に貼り付けます。
Node での構築#
サーバーが必要で、Node 環境をインストールして以下のコマンドを実行すれば動きます。
# 下载安装脚本
git clone -b v2 https://github.com/Hideipnetwork/hideipnetwork-web.git
# フォルダに移動
cd hideipnetwork-web
# インストール&&初期化
npm install
# サービスを実行
npm run start
# バックグラウンドで実行
npm i pm2 -g && pm2 start index.js --name HNet
もし宝塔環境であれば、直接 Node プロジェクトを追加すればバックグラウンドで実行できます!実行ポートは:56559。
私は IP + ポートでアクセスすることができませんでしたが、HTTPS + ドメインを有効にすると使用できました。
Workers コード#
/**
* MIRROR_URL: カスタムURLが必要です
* BAN_REGION: アクセスを禁止したい国または地域
*/
const MIRROR_URL = 'github.com';
const BAN_REGION = ['IN', 'KP'];
/**
* @param {Promise} handleRequest
*/
async function handleRequest(request) {
const url = new URL(request.url);
url.hostname = MIRROR_URL;
const r = await fetch(url.toString(), request);
const country = request.cf.country;
const banCountry = BAN_REGION;
if (banCountry.includes(country)) {
return new Response('アクセスが拒否されました:このサイトはあなたの地域では利用できません。', {
status: 403
});
}
return r;
}
class KVAdapter {
ns;
constructor(ns) {
this.ns = ns;
}
async get(key) {
return await this.ns.get(key);
}
async set(key, value) {
await this.ns.put(key, value);
}
async has(key) {
return (await this.ns.list()).keys.some(e => e.name === key);
}
async delete(key) {
await this.ns.delete(key);
return true;
}
async *entries() {
for (const {
name
} of (await this.ns.list()).keys) yield [name, await this.get(name)];
}
}
/**
* @internal
*/
class JSONDatabaseAdapter {
impl;
constructor(impl) {
this.impl = impl;
}
async get(key) {
const res = await this.impl.get(key);
if (typeof res === 'string') return JSON.parse(res);
}
async set(key, value) {
return await this.impl.set(key, JSON.stringify(value));
}
async has(key) {
return await this.impl.has(key);
}
async delete(key) {
return await this.impl.delete(key);
}
async *[Symbol.asyncIterator]() {
for await (const [id, value] of await this.impl.entries()) {
yield [id, JSON.parse(value)];
}
}
}
/**
* Routine
*/
async function cleanupDatabase(database) {
const adapter = new JSONDatabaseAdapter(database);
for await (const [id, {
expires
}] of adapter) if (expires < Date.now()) database.delete(id);
}
const bs = 'bareServer';
var httpErrors = {exports: {}};
/*!
* depd
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module exports.
* @public
*/
var browser = depd;
/**
* Create deprecate for namespace in caller.
*/
function depd (namespace) {
if (!namespace) {
throw new TypeError('argument namespace is required')
}
function deprecate (message) {
// no-op in browser
}
deprecate._file = undefined;
deprecate._ignored = true;
deprecate._namespace = namespace;
deprecate._traced = false;
deprecate._warned = Object.create(null);
deprecate.function = wrapfunction;
deprecate.property = wrapproperty;
return deprecate
}
/**
* Return a wrapped function in a deprecation message.
*
* This is a no-op version of the wrapper, which does nothing but call
* validation.
*/
function wrapfunction (fn, message) {
if (typeof fn !== 'function') {
throw new TypeError('argument fn must be a function')
}
return fn
}
/**
* Wrap property in a deprecation message.
*
* This is a no-op version of the wrapper, which does nothing but call
* validation.
*/
function wrapproperty (obj, prop, message) {
if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
throw new TypeError('argument obj must be object')
}
var descriptor = Object.getOwnPropertyDescriptor(obj, prop);
if (!descriptor) {
throw new TypeError('must call property on owner object')
}
if (!descriptor.configurable) {
throw new TypeError('property must be configurable')
}
}
/* eslint no-proto: 0 */
var setprototypeof = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array ? setProtoOf : mixinProperties);
function setProtoOf (obj, proto) {
obj.__proto__ = proto;
return obj
}
function mixinProperties (obj, proto) {
for (var prop in proto) {
if (!Object.prototype.hasOwnProperty.call(obj, prop)) {
obj[prop] = proto[prop];
}
}
return obj
}
var require$$0 = {
"100": "続行",
"101": "プロトコルの切り替え",
"102": "処理中",
"103": "早期ヒント",
"200": "OK",
"201": "作成されました",
"202": "受け入れられました",
"203": "非権威情報",
"204": "コンテンツなし",
"205": "コンテンツをリセット",
"206": "部分コンテンツ",
"207": "マルチステータス",
"208": "すでに報告されました",
"226": "IM使用",
"300": "複数の選択肢",
"301": "恒久的に移動しました",
"302": "見つかりました",
"303": "他を参照",
"304": "変更されていません",
"305": "プロキシを使用",
"307": "一時的リダイレクト",
"308": "恒久的リダイレクト",
"400": "不正なリクエスト",
"401": "認証されていません",
"402": "支払いが必要",
"403": "禁止されています",
"404": "見つかりません",
"405": "許可されていないメソッド",
"406": "受け入れ不可",
"407": "プロキシ認証が必要",
"408": "リクエストタイムアウト",
"409": "競合",
"410": "消失",
"411": "長さが必要",
"412": "前提条件が失敗しました",
"413": "ペイロードが大きすぎます",
"414": "URIが長すぎます",
"415": "サポートされていないメディアタイプ",
"416": "範囲が満たされていません",
"417": "期待が失敗しました",
"418": "私はティーポットです",
"421": "誤ったリクエスト",
"422": "処理できないエンティティ",
"423": "ロックされています",
"424": "依存関係が失敗しました",
"425": "早すぎます",
"426": "アップグレードが必要",
"428": "前提条件が必要",
"429": "リクエストが多すぎます",
"431": "リクエストヘッダーのフィールドが大きすぎます",
"451": "法的理由で利用できません",
"500": "内部サーバーエラー",
"501": "未実装",
"502": "不正なゲートウェイ",
"503": "サービスが利用できません",
"504": "ゲートウェイタイムアウト",
"505": "HTTPバージョンはサポートされていません",
"506": "バリアントも交渉します",
"507": "ストレージ不足",
"508": "ループが検出されました",
"509": "帯域幅制限を超えました",
"510": "拡張されていません",
"511": "ネットワーク認証が必要"
};
/*!
* statuses
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2016 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module dependencies.
* @private
*/
var codes = require$$0;
/**
* Module exports.
* @public
*/
var statuses = status;
// ステータスコードからメッセージへのマップ
status.message = codes;
// ステータスメッセージ(小文字)からコードへのマップ
status.code = createMessageToStatusCodeMap(codes);
// ステータスコードの配列
status.codes = createStatusCodeList(codes);
// リダイレクト用のステータスコード
status.redirect = {
300: true,
301: true,
302: true,
303: true,
305: true,
307: true,
308: true
};
// 空のボディ用のステータスコード
status.empty = {
204: true,
205: true,
304: true
};
// リクエストを再試行すべき時のステータスコード
status.retry = {
502: true,
503: true,
504: true
};
/**
* メッセージからステータスコードのマップを作成します。
* @private
*/
function createMessageToStatusCodeMap (codes) {
var map = {};
Object.keys(codes).forEach(function forEachCode (code) {
var message = codes[code];
var status = Number(code);
// マップを埋める
map[message.toLowerCase()] = status;
});
return map
}
/**
* すべてのステータスコードのリストを作成します。
* @private
*/
function createStatusCodeList (codes) {
return Object.keys(codes).map(function mapCode (code) {
return Number(code)
})
}
/**
* 与えられたメッセージのステータスコードを取得します。
* @private
*/
function getStatusCode (message) {
var msg = message.toLowerCase();
if (!Object.prototype.hasOwnProperty.call(status.code, msg)) {
throw new Error('無効なステータスメッセージ: "' + message + '"')
}
return status.code[msg]
}
/**
* 与えられたコードのステータスメッセージを取得します。
* @private
*/
function getStatusMessage (code) {
if (!Object.prototype.hasOwnProperty.call(status.message, code)) {
throw new Error('無効なステータスコード: ' + code)
}
return status.message[code]
}
/**
* ステータスコードを取得します。
*
* 数字が与えられた場合、知られていないステータスコードであればエラーを投げます。
* それ以外の場合は、コードが返されます。文字列が与えられた場合、
* 文字列は数字として解析され、コードが有効であれば返されます。
* それ以外の場合は、ステータスメッセージとしてコードを検索します。
*
* @param {string|number} code
* @returns {number}
* @public
*/
function status (code) {
if (typeof code === 'number') {
return getStatusMessage(code)
}
if (typeof code !== 'string') {
throw new TypeError('コードは数字または文字列でなければなりません')
}
// '403'
var n = parseInt(code, 10);
if (!isNaN(n)) {
return getStatusMessage(n)
}
return getStatusCode(code)
}
var inherits_browser = {exports: {}};
if (typeof Object.create === 'function') {
// 標準のnode.js 'util'モジュールからの実装
inherits_browser.exports = function inherits(ctor, superCtor) {
if (superCtor) {
ctor.super_ = superCtor;
ctor.prototype = Object.create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
};
} else {
// 古いブラウザ用のシム
inherits_browser.exports = function inherits(ctor, superCtor) {
if (superCtor) {
ctor.super_ = superCtor;
var TempCtor = function () {};
TempCtor.prototype = superCtor.prototype;
ctor.prototype = new TempCtor();
ctor.prototype.constructor = ctor;
}
};
}
/*!
* toidentifier
* Copyright(c) 2016 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module exports.
* @public
*/
var toidentifier = toIdentifier;
/**
* 与えられた文字列をJavaScriptの識別子に変換します
*
* @param {string} str
* @returns {string}
* @public
*/
function toIdentifier (str) {
return str
.split(' ')
.map(function (token) {
return token.slice(0, 1).toUpperCase() + token.slice(1)
})
.join('')
.replace(/[^ _0-9a-z]/gi, '')
}
/*!
* http-errors
* Copyright(c) 2014 Jonathan Ong
* Copyright(c) 2016 Douglas Christopher Wilson
* MIT Licensed
*/
(function (module) {
/**
* Module dependencies.
* @private
*/
browser('http-errors');
var setPrototypeOf = setprototypeof;
var statuses$1 = statuses;
var inherits = inherits_browser.exports;
var toIdentifier = toidentifier;
/**
* Module exports.
* @public
*/
module.exports = createError;
module.exports.HttpError = createHttpErrorConstructor();
module.exports.isHttpError = createIsHttpErrorFunction(module.exports.HttpError);
// すべてのコンストラクタのエクスポートを埋める
populateConstructorExports(module.exports, statuses$1.codes, module.exports.HttpError);
/**
* ステータスコードのクラスを取得します。
* @private
*/
function codeClass (status) {
return Number(String(status).charAt(0) + '00')
}
/**
* 新しいHTTPエラーを作成します。
*
* @returns {Error}
* @public
*/
function createError () {
// たくさんの引数がある ~_~
var err;
var msg;
var status = 500;
var props = {};
for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i];
var type = typeof arg;
if (type === 'object' && arg instanceof Error) {
err = arg;
status = err.status || err.statusCode || status;
} else if (type === 'number' && i === 0) {
status = arg;
} else if (type === 'string') {
msg = arg;
} else if (type === 'object') {
props = arg;
} else {
throw new TypeError('引数 #' + (i + 1) + ' サポートされていないタイプ ' + type)
}
}
if (typeof status !== 'number' ||
(!statuses$1.message[status] && (status < 400 || status >= 600))) {
status = 500;
}
// コンストラクタ
var HttpError = createError[status] || createError[codeClass(status)];
if (!err) {
// エラーを作成
err = HttpError
? new HttpError(msg)
: new Error(msg || statuses$1.message[status]);
Error.captureStackTrace(err, createError);
}
if (!HttpError || !(err instanceof HttpError) || err.status !== status) {
// 一般的なエラーにプロパティを追加
err.expose = status < 500;
err.status = err.statusCode = status;
}
for (var key in props) {
if (key !== 'status' && key !== 'statusCode') {
err[key] = props[key];
}
}
return err
}
/**
* HTTPエラーの抽象基底クラスを作成します。
* @private
*/
function createHttpErrorConstructor () {
function HttpError () {
throw new TypeError('抽象クラスを構築できません')
}
inherits(HttpError, Error);
return HttpError
}
/**
* クライアントエラーのコンストラクタを作成します。
* @private
*/
function createClientErrorConstructor (HttpError, name, code) {
var className = toClassName(name);
function ClientError (message) {
// エラーオブジェクトを作成
var msg = message != null ? message : statuses$1.message[code];
var err = new Error(msg);
// 構築ポイントへのスタックトレースをキャプチャ
Error.captureStackTrace(err, ClientError);
// [[Prototype]]を調整
setPrototypeOf(err, ClientError.prototype);
// エラーメッセージを再定義
Object.defineProperty(err, 'message', {
enumerable: true,
configurable: true,
value: msg,
writable: true
});
// エラー名を再定義
Object.defineProperty(err, 'name', {
enumerable: false,
configurable: true,
value: className,
writable: true
});
return err
}
inherits(ClientError, HttpError);
nameFunc(ClientError, className);
ClientError.prototype.status = code;
ClientError.prototype.statusCode = code;
ClientError.prototype.expose = true;
return ClientError
}
/**
* 値がHttpErrorであるかどうかをテストする関数を作成します。
* @private
*/
function createIsHttpErrorFunction (HttpError) {
return function isHttpError (val) {
if (!val || typeof val !== 'object') {
return false
}
if (val instanceof HttpError) {
return true
}
return val instanceof Error &&
typeof val.expose === 'boolean' &&
typeof val.statusCode === 'number' && val.status === val.statusCode
}
}
/**
* サーバーエラーのコンストラクタを作成します。
* @private
*/
function createServerErrorConstructor (HttpError, name, code) {
var className = toClassName(name);
function ServerError (message) {
// エラーオブジェクトを作成
var msg = message != null ? message : statuses$1.message[code];
var err = new Error(msg);
// 構築ポイントへのスタックトレースをキャプチャ
Error.captureStackTrace(err, ServerError);
// [[Prototype]]を調整
setPrototypeOf(err, ServerError.prototype);
// エラーメッセージを再定義
Object.defineProperty(err, 'message', {
enumerable: true,
configurable: true,
value: msg,
writable: true
});
// エラー名を再定義
Object.defineProperty(err, 'name', {
enumerable: false,
configurable: true,
value: className,
writable: true
});
return err
}
inherits(ServerError, HttpError);
nameFunc(ServerError, className);
ServerError.prototype.status = code;
ServerError.prototype.statusCode = code;
ServerError.prototype.expose = false;
return ServerError
}
/**
* 可能であれば関数の名前を設定します。
* @private
*/
function nameFunc (func, name) {
var desc = Object.getOwnPropertyDescriptor(func, 'name');
if (desc && desc.configurable) {
desc.value = name;
Object.defineProperty(func, 'name', desc);
}
}
/**
* すべてのエラークラスのコンストラクタでエクスポートオブジェクトを埋めます。
* @private
*/
function populateConstructorExports (exports, codes, HttpError) {
codes.forEach(function forEachCode (code) {
var CodeError;
var name = toIdentifier(statuses$1.message[code]);
switch (codeClass(code)) {
case 400:
CodeError = createClientErrorConstructor(HttpError, name, code);
break
case 500:
CodeError = createServerErrorConstructor(HttpError, name, code);
break
}
if (CodeError) {
// コンストラクタをエクスポート
exports[code] = CodeError;
exports[name] = CodeError;
}
});
}
/**
* 名前識別子からクラス名を取得します。
* @private
*/
function toClassName (name) {
return name.substr(-5) !== 'Error'
? name + 'Error'
: name
}
} (httpErrors));
var createHttpError = httpErrors.exports;
class BareError extends Error {
status;
body;
constructor(status, body) {
super(body.message || body.code);
this.status = status;
this.body = body;
}
}
const project = {
name: 'bare-server-worker',
description: 'Cloudflare Bare Server',
repository: 'https://hideip.network',
version: '1.2.2'
};
function json(status, json) {
return new Response(JSON.stringify(json, null, '\t'), {
status,
headers: {
'content-type': 'application/json'
}
});
}
class Server extends EventTarget {
routes = new Map();
socketRoutes = new Map();
closed = false;
directory;
options;
/**
* @internal
*/
constructor(directory, options) {
super();
this.directory = directory;
this.options = options;
}
/**
* すべてのタイマーとリスナーを削除します
*/
close() {
this.closed = true;
this.dispatchEvent(new Event('close'));
}
shouldRoute(request) {
return !this.closed && new URL(request.url).pathname.startsWith(this.directory);
}
get instanceInfo() {
return {
versions: ['v1', 'v2'],
language: 'Cloudflare',
maintainer: this.options.maintainer,
project
};
}
async routeRequest(request) {
const service = new URL(request.url).pathname.slice(this.directory.length - 1);
let response;
const isSocket = request.headers.get('upgrade') === 'websocket';
try {
if (request.method === 'OPTIONS') {
response = new Response(undefined, {
status: 200
});
} else if (service === '/') {
response = json(200, this.instanceInfo);
} else if (!isSocket && this.routes.has(service)) {
const call = this.routes.get(service);
response = await call(request, this.options);
} else if (isSocket && this.socketRoutes.has(service)) {
const call = this.socketRoutes.get(service);
response = await call(request, this.options);
} else {
throw new createHttpError.NotFound();
}
} catch (error) {
if (this.options.logErrors) console.error(error);
if (createHttpError.isHttpError(error)) {
response = json(error.statusCode, {
code: 'UNKNOWN',
id: `error.${error.name}`,
message: error.message,
stack: error.stack
});
} else if (error instanceof Error) {
response = json(500, {
code: 'UNKNOWN',
id: `error.${error.name}`,
message: error.message,
stack: error.stack
});
} else {
response = json(500, {
code: 'UNKNOWN',
id: 'error.Exception',
message: error,
stack: new Error(error).stack
});
}
if (!(response instanceof Response)) {
if (this.options.logErrors) {
console.error('Cannot', request.method, new URL(request.url).pathname, ': ルートが応答を返しませんでした。');
}
throw new createHttpError.InternalServerError();
}
}
response.headers.set('x-robots-tag', 'noindex');
response.headers.set('access-control-allow-headers', '*');
response.headers.set('access-control-allow-origin', '*');
response.headers.set('access-control-allow-methods', '*');
response.headers.set('access-control-expose-headers', '*'); // すべてのリクエストでプレフライトを取得しない...
// 代わりに、10分ごとにプレフライトを取得します
response.headers.set('access-control-max-age', '7200');
return response;
}
}
const reserveChar = '%';
function decodeProtocol(protocol) {
let result = '';
for (let i = 0; i < protocol.length; i++) {
const char = protocol[i];
if (char === reserveChar) {
const code = parseInt(protocol.slice(i + 1, i + 3), 16);
const decoded = String.fromCharCode(code);
result += decoded;
i += 2;
} else {
result += char;
}
}
return result;
}
function randomHex(byteLength) {
const bytes = new Uint8Array(byteLength);
crypto.getRandomValues(bytes);
let hex = '';
for (const byte of bytes) hex += byte.toString(16).padStart(2, '0');
return hex;
}
const noBody = ['GET', 'HEAD'];
async function bareFetch(request, signal, requestHeaders, remote) {
return await fetch(`${remote.protocol}//${remote.host}:${remote.port}${remote.path}`, {
headers: requestHeaders,
method: request.method,
body: noBody.includes(request.method) ? undefined : await request.blob(),
signal,
redirect: 'manual'
});
}
async function upgradeBareFetch(request, signal, requestHeaders, remote) {
const res = await fetch(`${remote.protocol}//${remote.host}:${remote.port}${remote.path}`, {
headers: requestHeaders,
method: request.method,
signal
});
if (!res.webSocket) throw new Error("サーバーがWebSocketを受け入れませんでした");
return [res, res.webSocket];
}
const validProtocols$1 = ['http:', 'https:', 'ws:', 'wss:'];
function loadForwardedHeaders$1(forward, target, request) {
for (const header of forward) {
if (request.headers.has(header)) {
target[header] = request.headers.get(header);
}
}
}
function readHeaders$1(request) {
const remote = {};
const headers = {};
Reflect.setPrototypeOf(headers, null);
for (const remoteProp of ['host', 'port', 'protocol', 'path']) {
const header = `x-bare-${remoteProp}`;
if (request.headers.has(header)) {
const value = request.headers.get(header);
switch (remoteProp) {
case 'port':
if (isNaN(parseInt(value))) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `ヘッダーは有効な整数ではありません。`
});
}
break;
case 'protocol':
if (!validProtocols$1.includes(value)) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `ヘッダーが無効です`
});
}
break;
}
remote[remoteProp] = value;
} else {
throw new BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.${header}`,
message: `ヘッダーが指定されていません。`
});
}
}
if (request.headers.has('x-bare-headers')) {
try {
const json = JSON.parse(request.headers.get('x-bare-headers'));
for (const header in json) {
if (typeof json[header] !== 'string' && !Array.isArray(json[header])) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `ヘッダーはStringまたはArrayではありません。`
});
}
}
Object.assign(headers, json);
} catch (error) {
if (error instanceof SyntaxError) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `ヘッダーに無効なJSONが含まれています。 (${error.message})`
});
} else {
throw error;
}
}
} else {
throw new BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `ヘッダーが指定されていません。`
});
}
if (request.headers.has('x-bare-forward-headers')) {
let json;
try {
json = JSON.parse(request.headers.get('x-bare-forward-headers'));
} catch (error) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `ヘッダーに無効なJSONが含まれています。 (${error instanceof Error ? error.message : error})`
});
}
loadForwardedHeaders$1(json, headers, request);
} else {
throw new BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `ヘッダーが指定されていません。`
});
}
return {
remote: remote,
headers
};
}
const tunnelRequest$1 = async request => {
const {
remote,
headers
} = readHeaders$1(request);
const response = await bareFetch(request, request.signal, headers, remote);
const responseHeaders = new Headers();
for (const [header, value] of response.headers) {
if (header === 'content-encoding' || header === 'x-content-encoding') responseHeaders.set('content-encoding', value);else if (header === 'content-length') responseHeaders.set('content-length', value);
}
responseHeaders.set('x-bare-headers', JSON.stringify(Object.fromEntries(response.headers)));
responseHeaders.set('x-bare-status', response.status.toString());
responseHeaders.set('x-bare-status-text', response.statusText);
return new Response(response.body, {
status: 200,
headers: responseHeaders
});
};
const metaExpiration$1 = 30e3;
const wsMeta = async (request, options) => {
if (request.method === 'OPTIONS') {
return new Response(undefined, {
status: 200
});
}
if (!request.headers.has('x-bare-id')) {
throw new BareError(400, {
code: 'MISSING_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'ヘッダーが指定されていません'
});
}
const id = request.headers.get('x-bare-id');
const meta = await options.database.get(id); // メタが未定義でなく、バージョンが1であることを確認
if (meta?.value.v !== 1) throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: '未登録のID'
});
await options.database.delete(id);
return json(200, {
headers: meta.value.response?.headers
});
};
const wsNewMeta = async (request, options) => {
const id = randomHex(16);
await options.database.set(id, {
value: {
v: 1
},
expires: Date.now() + metaExpiration$1
});
return new Response(id);
};
const tunnelSocket$1 = async (request, options) => {
const [firstProtocol, data] = request.headers.get('sec-websocket-protocol')?.split(/,\s*/g) || [];
if (firstProtocol !== 'bare') throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.sec-websocket-protocol`,
message: `メタが指定されていません。`
});
const {
remote,
headers,
forward_headers: forwardHeaders,
id
} = JSON.parse(decodeProtocol(data));
loadForwardedHeaders$1(forwardHeaders, headers, request);
if (!id) throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.sec-websocket-protocol`,
message: `IDが必要です。`
});
const [remoteResponse, remoteSocket] = await upgradeBareFetch(request, request.signal, headers, remote);
const meta = await options.database.get(id);
if (meta?.value.v === 1) {
meta.value.response = {
headers: Object.fromEntries(remoteResponse.headers)
};
await options.database.set(id, meta);
}
return new Response(undefined, {
status: 101,
webSocket: remoteSocket
});
};
function registerV1(server) {
server.routes.set('/v1/', tunnelRequest$1);
server.routes.set('/v1/ws-new-meta', wsNewMeta);
server.routes.set('/v1/ws-meta', wsMeta);
server.socketRoutes.set('/v1/', tunnelSocket$1);
}
const MAX_HEADER_VALUE = 3072;
/**
*
* ヘッダーを仕様に従って分割します
* @param headers
* @returns 分割されたヘッダー
*/
function splitHeaders(headers) {
const output = new Headers(headers);
if (headers.has('x-bare-headers')) {
const value = headers.get('x-bare-headers');
if (value.length > MAX_HEADER_VALUE) {
output.delete('x-bare-headers');
let split = 0;
for (let i = 0; i < value.length; i += MAX_HEADER_VALUE) {
const part = value.slice(i, i + MAX_HEADER_VALUE);
const id = split++;
output.set(`x-bare-headers-${id}`, `;${part}`);
}
}
}
return output;
}
/**
* 仕様に従ってヘッダーを結合します
* @param headers
* @returns 結合されたヘッダー
*/
function joinHeaders(headers) {
const output = new Headers(headers);
const prefix = 'x-bare-headers';
if (headers.has(`${prefix}-0`)) {
const join = [];
for (const [header, value] of headers) {
if (!header.startsWith(prefix)) {
continue;
}
if (!value.startsWith(';')) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `値はセミコロンで始まる必要があります。`
});
}
const id = parseInt(header.slice(prefix.length + 1));
join[id] = value.slice(1);
output.delete(header);
}
output.set(prefix, join.join(''));
}
return output;
}
const validProtocols = ['http:', 'https:', 'ws:', 'wss:'];
const forbiddenForwardHeaders = ['connection', 'transfer-encoding', 'host', 'connection', 'origin', 'referer'];
const forbiddenPassHeaders = ['vary', 'connection', 'transfer-encoding', 'access-control-allow-headers', 'access-control-allow-methods', 'access-control-expose-headers', 'access-control-max-age', 'access-control-request-headers', 'access-control-request-method']; // 一般的なデフォルト
const defaultForwardHeaders = ['accept-encoding', 'accept-language', 'sec-websocket-extensions', 'sec-websocket-key', 'sec-websocket-version'];
const defaultPassHeaders = ['content-encoding', 'content-length', 'last-modified']; // クライアントがキャッシュキーを提供する場合のデフォルト
const defaultCacheForwardHeaders = ['if-modified-since', 'if-none-match', 'cache-control'];
const defaultCachePassHeaders = ['cache-control', 'etag'];
const cacheNotModified = 304;
function loadForwardedHeaders(forward, target, request) {
for (const header of forward) {
if (request.headers.has(header)) {
target[header] = request.headers.get(header);
}
}
}
const splitHeaderValue = /,\s*/g;
function readHeaders(request) {
const remote = Object.setPrototypeOf({}, null);
const sendHeaders = Object.setPrototypeOf({}, null);
const passHeaders = [...defaultPassHeaders];
const passStatus = [];
const forwardHeaders = [...defaultForwardHeaders]; // 一意であるべき
const cache = new URL(request.url).searchParams.has('cache');
if (cache) {
passHeaders.push(...defaultCachePassHeaders);
passStatus.push(cacheNotModified);
forwardHeaders.push(...defaultCacheForwardHeaders);
}
const headers = joinHeaders(request.headers);
for (const remoteProp of ['host', 'port', 'protocol', 'path']) {
const header = `x-bare-${remoteProp}`;
if (headers.has(header)) {
const value = headers.get(header);
switch (remoteProp) {
case 'port':
if (isNaN(parseInt(value))) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `ヘッダーは有効な整数ではありません。`
});
}
break;
case 'protocol':
if (!validProtocols.includes(value)) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.${header}`,
message: `ヘッダーが無効です`
});
}
break;
}
remote[remoteProp] = value;
} else {
throw new BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.${header}`,
message: `ヘッダーが指定されていません。`
});
}
}
if (headers.has('x-bare-headers')) {
try {
const json = JSON.parse(headers.get('x-bare-headers'));
for (const header in json) {
const value = json[header];
if (typeof value === 'string') {
sendHeaders[header] = value;
} else if (Array.isArray(value)) {
const array = [];
for (const val in value) {
if (typeof val !== 'string') {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `ヘッダーはStringではありません。`
});
}
array.push(val);
}
sendHeaders[header] = array;
} else {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `bare.headers.${header}`,
message: `ヘッダーはStringではありません。`
});
}
}
} catch (error) {
if (error instanceof SyntaxError) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `ヘッダーに無効なJSONが含まれています。 (${error.message})`
});
} else {
throw error;
}
}
} else {
throw new BareError(400, {
code: 'MISSING_BARE_HEADER',
id: `request.headers.x-bare-headers`,
message: `ヘッダーが指定されていません。`
});
}
if (headers.has('x-bare-pass-status')) {
const parsed = headers.get('x-bare-pass-status').split(splitHeaderValue);
for (const value of parsed) {
const number = parseInt(value);
if (isNaN(number)) {
throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.x-bare-pass-status`,
message: `配列に非数値の値が含まれています。`
});
} else {
passStatus.push(number);
}
}
}
if (headers.has('x-bare-pass-headers')) {
const parsed = headers.get('x-bare-pass-headers').split(splitHeaderValue);
for (let header of parsed) {
header = header.toLowerCase();
if (forbiddenPassHeaders.includes(header)) {
throw new BareError(400, {
code: 'FORBIDDEN_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `禁止されたヘッダーが渡されました。`
});
} else {
passHeaders.push(header);
}
}
}
if (headers.has('x-bare-forward-headers')) {
const parsed = headers.get('x-bare-forward-headers').split(splitHeaderValue);
for (let header of parsed) {
header = header.toLowerCase();
if (forbiddenForwardHeaders.includes(header)) {
throw new BareError(400, {
code: 'FORBIDDEN_BARE_HEADER',
id: `request.headers.x-bare-forward-headers`,
message: `禁止されたヘッダーが転送されました。`
});
} else {
forwardHeaders.push(header);
}
}
}
return {
remote,
sendHeaders,
passHeaders,
passStatus,
forwardHeaders
};
}
const tunnelRequest = async request => {
const {
remote,
sendHeaders,
passHeaders,
passStatus,
forwardHeaders
} = readHeaders(request);
loadForwardedHeaders(forwardHeaders, sendHeaders, request);
const response = await bareFetch(request, request.signal, sendHeaders, remote);
const responseHeaders = new Headers();
for (const [header, value] of passHeaders) {
if (!response.headers.has(header)) continue;
responseHeaders.set(header, value);
}
const status = passStatus.includes(response.status) ? response.status : 200;
if (status !== cacheNotModified) {
responseHeaders.set('x-bare-status', response.status.toString());
responseHeaders.set('x-bare-status-text', response.statusText);
responseHeaders.set('x-bare-headers', JSON.stringify(Object.fromEntries(response.headers)));
}
return new Response(response.body, {
status,
headers: splitHeaders(responseHeaders)
});
};
const metaExpiration = 30e3;
const getMeta = async (request, options) => {
if (request.method === 'OPTIONS') {
return new Response(undefined, {
status: 200
});
}
if (!request.headers.has('x-bare-id')) {
throw new BareError(400, {
code: 'MISSING_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'ヘッダーが指定されていません'
});
}
const id = request.headers.get('x-bare-id');
const meta = await options.database.get(id);
if (meta?.value.v !== 2) throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: '未登録のID'
});
if (!meta.value.response) throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: 'request.headers.x-bare-id',
message: 'メタが準備できていません'
});
await options.database.delete(id);
const responseHeaders = new Headers();
responseHeaders.set('x-bare-status', meta.value.response.status.toString());
responseHeaders.set('x-bare-status-text', meta.value.response.statusText);
responseHeaders.set('x-bare-headers', JSON.stringify(meta.value.response.headers));
return new Response(undefined, {
status: 200,
headers: splitHeaders(responseHeaders)
});
};
const newMeta = async (request, options) => {
const {
remote,
sendHeaders,
forwardHeaders
} = readHeaders(request);
const id = randomHex(16);
await options.database.set(id, {
expires: Date.now() + metaExpiration,
value: {
v: 2,
remote,
sendHeaders,
forwardHeaders
}
});
return new Response(id);
};
const tunnelSocket = async (request, options) => {
const id = request.headers.get('sec-websocket-protocol');
if (!id) throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.sec-websocket-protocol`,
message: `IDが必要です。`
});
const meta = await options.database.get(id);
if (meta?.value.v !== 2) throw new BareError(400, {
code: 'INVALID_BARE_HEADER',
id: `request.headers.sec-websocket-protocol`,
message: `無効なID。`
});
loadForwardedHeaders(meta.value.forwardHeaders, meta.value.sendHeaders, request);
const [remoteResponse, remoteSocket] = await upgradeBareFetch(request, request.signal, meta.value.sendHeaders, meta.value.remote); // https://developers.cloudflare.com/workers/learning/using-websockets
// クライアントに返します....
meta.value.response = {
headers: Object.fromEntries(remoteResponse.headers),
status: remoteResponse.status,
statusText: remoteResponse.statusText
};
await options.database.set(id, meta);
return new Response(undefined, {
status: 101,
webSocket: remoteSocket
});
};
function registerV2(server) {
server.routes.set('/v2/', tunnelRequest);
server.routes.set('/v2/ws-new-meta', newMeta);
server.routes.set('/v2/ws-meta', getMeta);
server.socketRoutes.set('/v2/', tunnelSocket);
}
/**
* Bareサーバーを作成します。
* これは指定されていないオプション(httpAgent、httpsAgent、metaMap)のすべてのライフサイクルを処理します。
*/
function createBareServer(directory, init = {}) {
if (typeof directory !== 'string') throw new Error('ディレクトリを指定する必要があります。');
if (!directory.startsWith('/') || !directory.endsWith('/')) throw new RangeError('ディレクトリは/で始まり、/で終わる必要があります。');
init.logErrors ??= false;
const cleanup = [];
if (!init.database) {
const database = new Map();
const interval = setInterval(() => cleanupDatabase(database), 1000);
init.database = database;
cleanup.push(() => clearInterval(interval));
}
const server = new Server(directory, { ...init,
database: new JSONDatabaseAdapter(init.database)
});
registerV1(server);
registerV2(server);
server.addEventListener('close', () => {
for (const cb of cleanup) cb();
});
return server;
}
const kvDB = new KVAdapter(BARE);
const bare = createBareServer(`/${bs}/`, {
logErrors: true,
database: kvDB
});
addEventListener('fetch', event => {
cleanupDatabase(kvDB);
if (bare.shouldRoute(event.request)) {
event.respondWith(bare.routeRequest(event.request));
} else {
event.respondWith(handleRequest(event.request));
}
});