Yurchiu's Blog

Gitalk Network Error 解决

Yurchiu,Internet 2022-12-31, 20:50:39 1.9k 隐藏左右两栏 展示左右两栏

我们都知道,Gitalk 现在必 Network Error。这篇文章详细描述如何解决问题。本文由 Yurchiu 从网上多篇文章集合而来。

打开个人博客页面,F12 看控制台,发现 https://cors-anywhere.azm.workers.dev/https://github.com/login/oauth/access_token 这个连接访问失败,这个就是 Github 获取 accesstoken 的代理链接,https://cors-anywhere.azm.workers.dev/ 被墙了,导致代理失效。

如何解决这个问题呢?本解决方案无任何花费,无需服务器等。

1. 申请一个域名

可以使用 Freenom 免费域名。但是它有很多坑。

如何避坑

2. Cloudflare 域名托管

进入 Cloudflare Dashboard,添加站点,选择 Free 免费套餐。域名解析记录类型选 A 记录即可,然后填入域名前缀(随便)、服务器 IP 地址(随便),选择 CDN 状态(使云朵为黄色),输入完毕后添加记录。

添加成功后会显示在下面,点击 Continue 继续。按说明修改自己的域名服务器为 Cloudflare 给出的地址。

3. Worker

利用 CloudFlare Worker 创建在线代理。创建一个 Worker,部署一个 JS 脚本。

查看 JS 代码
const exclude = [];     // Regexp for blacklisted urls
const include = [/^https?:\/\/.*yz-hs\.github\.io$/, /^https?:\/\/localhost/]; // Regexp for whitelisted origins e.g.
const apiKeys = {
	EZWTLwVEqFnaycMzdhBz: {
		name: 'Test App',
		expired: false,
		expiresAt: new Date('2023-01-01'),
		exclude: [], // Regexp for blacklisted urls
		include: ["^http.?://yz-hs.github.io$", "yz-hs.github.io$", "^https?://localhost/"], // Regexp for whitelisted origins
	},
};

// Config is all above this line.
// It should not be necessary to change anything below.

function verifyCredentials(request) {
	// Throws exception on verification failure.
	const requestApiKey = request.headers.get('x-cors-proxy-api-key');
	if (!Object.keys(apiKeys).includes(requestApiKey)) {
		throw new UnauthorizedException('Invalid authorization key.');
	}

	if (apiKeys[requestApiKey].expired) {
		throw new UnauthorizedException('Expired authorization key.');
	}

	if (apiKeys[requestApiKey].expiresAt && apiKeys[requestApiKey].expiresAt.getTime() < Date.now()) {
		throw new UnauthorizedException(`Expired authorization key.\nKey was valid until: ${apiKeys[requestApiKey].expiresAt}`);
	}

	return apiKeys[requestApiKey];
}

function checkRequiredHeadersPresent(request) {
	// Throws exception on verification failure.
	if (!request.headers.get('Origin') && !request.headers.get('x-requested-with')) {
		throw new BadRequestException('Missing required request header. Must specify one of: origin,x-requested-with');
	}
}

function UnauthorizedException(reason) {
	this.status = 401;
	this.statusText = 'Unauthorized';
	this.reason = reason;
}

function BadRequestException(reason) {
	this.status = 400;
	this.statusText = 'Bad Request';
	this.reason = reason;
}

function isListed(uri, listing) {
	let returnValue = false;
	console.log(uri);
	if (typeof uri === 'string') {
		for (const m of listing) {
			if (uri.match(m) !== null) {
				returnValue = true;
			}
		}
	} else { //   Decide what to do when Origin is null
		returnValue = true; // True accepts null origins false rejects them.
	}

	return returnValue;
}

function fix(myHeaders, request, isOPTIONS) {
	myHeaders.set('Access-Control-Allow-Origin', request.headers.get('Origin'));
	if (isOPTIONS) {
		myHeaders.set('Access-Control-Allow-Methods', request.headers.get('access-control-request-method'));
		const acrh = request.headers.get('access-control-request-headers');

		if (acrh) {
			myHeaders.set('Access-Control-Allow-Headers', acrh);
		}

		myHeaders.delete('X-Content-Type-Options');
	}

	return myHeaders;
}

function parseURL(requestUrl) {
	const match = requestUrl.match(/^(?:(https?:)?\/\/)?(([^/?]+?)(?::(\d{0,5})(?=[/?]|$))?)([/?][\S\s]*|$)/i);
	//                              ^^^^^^^          ^^^^^^^^      ^^^^^^^                ^^^^^^^^^^^^
	//                            1:protocol       3:hostname     4:port                 5:path + query string
	//                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	//                                            2:host

	if (!match) {
		console.log('no match');
		throw new BadRequestException('Invalid URL for proxy request.');
	}

	console.log('parseURL:match:', match);

	if (!match[1]) {
		console.log('nothing in match group 1');
		if (/^https?:/i.test(requestUrl)) {
			console.log('The pattern at top could mistakenly parse "http:///" as host="http:" and path=///.');
			throw new BadRequestException('Invalid URL for proxy request.');
		}

		// Scheme is omitted.
		if (requestUrl.lastIndexOf('//', 0) === -1) {
			console.log('"//" is omitted');
			requestUrl = '//' + requestUrl;
		}

		requestUrl = (match[4] === '443' ? 'https:' : 'http:') + requestUrl;
	}

	const parsed = new URL(requestUrl);
	if (!parsed.hostname) {
		console.log('"http://:1/" and "http:/notenoughslashes" could end up here.');
		throw new BadRequestException('Invalid URL for proxy request.');
	}

	return parsed;
}

async function proxyRequest(request, activeApiKey) {
	const isOPTIONS = (request.method === 'OPTIONS');
	const originUrl = new URL(request.url);
	const origin = request.headers.get('Origin');
	// ParseURL throws when the url is invalid
	const fetchUrl = parseURL(request.url.replace(originUrl.origin, '').slice(1));

	// Throws if it fails the check
	checkRequiredHeadersPresent(request);

	// Excluding urls which are not allowed as destination urls
	// Exclude origins which are not int he included ones
	if (isListed(fetchUrl.toString(), [...exclude, ...(activeApiKey?.exclude || [])]) || !isListed(origin, [...include, ...(activeApiKey?.include || [])])) {
		throw new BadRequestException('Origin or Destination URL is not allowed.');
	}

	let corsHeaders = request.headers.get('x-cors-headers');

	if (corsHeaders !== null) {
		try {
			corsHeaders = JSON.parse(corsHeaders);
		} catch {}
	}

	if (!originUrl.pathname.startsWith('/')) {
		throw new BadRequestException('Pathname does not start with "/"');
	}

	const recvHpaireaders = {};
	for (const pair of request.headers.entries()) {
		if ((pair[0].match('^origin') === null)
        && (pair[0].match('eferer') === null)
        && (pair[0].match('^cf-') === null)
        && (pair[0].match('^x-forw') === null)
        && (pair[0].match('^x-cors-headers') === null)
		) {
			recvHpaireaders[pair[0]] = pair[1];
		}
	}

	if (corsHeaders !== null) {
		for (const c of Object.entries(corsHeaders)) {
			recvHpaireaders[c[0]] = c[1];
		}
	}

	const newRequest = new Request(request, {
		headers: recvHpaireaders,
	});

	const response = await fetch(fetchUrl, newRequest);
	let myHeaders = new Headers(response.headers);
	const newCorsHeaders = [];
	const allh = {};
	for (const pair of response.headers.entries()) {
		newCorsHeaders.push(pair[0]);
		allh[pair[0]] = pair[1];
	}

	newCorsHeaders.push('cors-received-headers');
	myHeaders = fix(myHeaders, request, isOPTIONS);

	myHeaders.set('Access-Control-Expose-Headers', newCorsHeaders.join(','));

	myHeaders.set('cors-received-headers', JSON.stringify(allh));

	const body = isOPTIONS ? null : await response.arrayBuffer();

	return new Response(body, {
		headers: myHeaders,
		status: (isOPTIONS ? 200 : response.status),
		statusText: (isOPTIONS ? 'OK' : response.statusText),
	});
}

function homeRequest(request) {
	const isOPTIONS = (request.method === 'OPTIONS');
	const originUrl = new URL(request.url);
	const origin = request.headers.get('Origin');
	const remIp = request.headers.get('CF-Connecting-IP');
	const corsHeaders = request.headers.get('x-cors-headers');
	let myHeaders = new Headers();
	myHeaders = fix(myHeaders, request, isOPTIONS);

	let country = false;
	let colo = false;
	if (typeof request.cf !== 'undefined') {
		country = typeof request.cf.country === 'undefined' ? false : request.cf.country;
		colo = typeof request.cf.colo === 'undefined' ? false : request.cf.colo;
	}

	return new Response(
		'CLOUDFLARE-CORS-ANYWHERE\n\n'
        + 'Source:\nhttps://github.com/chrisspiegl/cloudflare-cors-anywhere\n\n'
        + 'Usage:\n'
        + originUrl.origin + '/{uri}\n'
        + 'Header x-cors-proxy-api-key must be set with valid api key\n'
        + 'Header origin or x-requested-with must be set\n\n'
        // + 'Limits: 100,000 requests/day\n'
        // + '          1,000 requests/10 minutes\n\n'
        + (origin === null ? '' : 'Origin: ' + origin + '\n')
        + 'Ip: ' + remIp + '\n'
        + (country ? 'Country: ' + country + '\n' : '')
        + (colo ? 'Datacenter: ' + colo + '\n' : '') + '\n'
        + ((corsHeaders === null) ? '' : '\nx-cors-headers: ' + JSON.stringify(corsHeaders)),
		{status: 200, headers: myHeaders},
	);
}

async function handleRequest(request) {
	const {protocol, pathname} = new URL(request.url);
	// In the case of a "Basic" authentication, the exchange MUST happen over an HTTPS (TLS) connection to be secure.
	if (protocol !== 'https:' || request.headers.get('x-forwarded-proto') !== 'https') {
		throw new BadRequestException('Must use a HTTPS connection.');
	}

	switch (pathname) {
		case '/favicon.ico':
		case '/robots.txt':
			return new Response(null, {status: 204});
		case '/':
			return homeRequest(request);
		default: {
			// Not 100% sure if this is a good idea…
			// Right now all OPTIONS requests are just simply replied to because otherwise they fail.
			// This is necessary because apparently, OPTIONS requests do not carry the `x-cors-proxy-api-key` header so this can not be authorized.
			if (request.method === 'OPTIONS') {
				return new Response(null, {
					headers: fix(new Headers(), request, true),
					status: 200,
					statusText: 'OK',
				});
			}

			// The "x-cors-proxy-api-key" header is sent when authenticated.
			//if (request.headers.has('x-cors-proxy-api-key')) {
				// Throws exception when authorization fails.
				//const activeApiKey = verifyCredentials(request);

				// Only returns this response when no exception is thrown.
				return proxyRequest(request);
			//}

			// Not authenticated.
			//throw new UnauthorizedException('Valid x-cors-proxy-api-key header has to be provided.');
		}
	}
}

addEventListener('fetch', async event => {
	event.respondWith(
		handleRequest(event.request).catch(error => {
			const message = error.reason || error.stack || 'Unknown Error';

			return new Response(message, {
				status: error.status || 500,
				statusText: error.statusText || null,
				headers: {
					'Content-Type': 'text/plain;charset=UTF-8',
					// Disables caching by default.
					'Cache-Control': 'no-store',
					// Returns the "Content-Length" header for HTTP HEAD requests.
					'Content-Length': message.length,
				},
			});
		}),
	);
});

4. 绑定域名

回到 Cloudflare 的域名管理面板,选择你刚刚托管的域名。

点击 Workers 进入域名中的 Workers 管理页面,然后点击 添加路由 设置新的路由。

路由 输入域名,加上子域名(你填的域名前缀),如 qwe.example.com/*。服务选择你的 Workers 名称,环境是 production。

5. 设置代理

对于 Cutie 主题,可将主题配置文件的 comments - gitalk - proxy 一项设置为:

proxy: "'https://qwe.example.com/https://github.com/login/oauth/access_token'"

qwe.example.com 改成你申请的域名。

这样,Gitalk 就可以正常使用了。





本文作者:Yurchiu,Internet

本文链接:https://yz-hs.github.io/ef06bab11ae2/

版权声明:本博客中所有原创文章除特别声明外,均允许规范转载,转载请注明出处。所有非原创文章,按照原作者要求转载。


By Yurchiu.
其他物件杂物收纳
Hitokoto

Yurchiu 说,除了她以外的人都很强!嘤嘤嘤~~
博客信息
文章数目
158
最近更新
08-21
本站字数
350.6k
文章目录