feat: request rewriter
This commit is contained in:
parent
176e773013
commit
a828b3dd1c
|
|
@ -1,4 +1,4 @@
|
|||
import '@/utils/request-interceptor';
|
||||
import '@/utils/request-rewriter';
|
||||
|
||||
import { Hono } from 'hono';
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ if (proxyIsPAC) {
|
|||
dispatcher = new ProxyAgent({
|
||||
uri: proxyUri,
|
||||
token: proxyObj?.auth ? `Basic ${proxyObj.auth}` : undefined,
|
||||
requestTls: {
|
||||
rejectUnauthorized: process.env.NODE_TLS_REJECT_UNAUTHORIZED !== '0',
|
||||
},
|
||||
});
|
||||
} else if (proxyUri.startsWith('socks')) {
|
||||
agent = new SocksProxyAgent(proxyUri);
|
||||
|
|
|
|||
|
|
@ -1,19 +1,14 @@
|
|||
import { setupServer } from 'msw/node';
|
||||
import { http } from 'msw';
|
||||
import logger from '@/utils/logger';
|
||||
import { config } from '@/config';
|
||||
import { fetch, Headers, FormData, Request, Response } from 'undici';
|
||||
import { fetch, Request, RequestInfo, RequestInit } from 'undici';
|
||||
import proxy from '@/utils/proxy';
|
||||
|
||||
Object.defineProperties(globalThis, {
|
||||
fetch: { value: fetch, writable: true },
|
||||
Headers: { value: Headers },
|
||||
FormData: { value: FormData },
|
||||
Request: { value: Request },
|
||||
Response: { value: Response },
|
||||
});
|
||||
const wrappedFetch: typeof fetch = (input: RequestInfo, init?: RequestInit) => {
|
||||
const request = new Request(input, init);
|
||||
const options: RequestInit = {};
|
||||
|
||||
logger.debug(`Outgoing request: ${request.method} ${request.url}`);
|
||||
|
||||
const handler = (request: globalThis.Request, options: NonNullable<Parameters<typeof fetch>[1]>) => {
|
||||
// ua
|
||||
if (!request.headers.get('user-agent')) {
|
||||
request.headers.set('user-agent', config.ua);
|
||||
|
|
@ -45,21 +40,11 @@ const handler = (request: globalThis.Request, options: NonNullable<Parameters<ty
|
|||
}
|
||||
|
||||
if (proxyRegex.test(request.url) && request.url.startsWith('http') && !(urlHandler && urlHandler.host === proxy.proxyUrlHandler?.host)) {
|
||||
// fetch
|
||||
options.dispatcher = proxy.dispatcher;
|
||||
}
|
||||
}
|
||||
|
||||
return fetch(request, options);
|
||||
};
|
||||
|
||||
const server = setupServer(
|
||||
// @ts-expect-error
|
||||
http.all('*', ({ request }) => {
|
||||
logger.debug(`Outgoing request: ${request.method} ${request.url}`);
|
||||
const requestClone = request.clone();
|
||||
const options = {};
|
||||
handler(requestClone, options);
|
||||
// @ts-expect-error
|
||||
return fetch(requestClone, options);
|
||||
})
|
||||
);
|
||||
server.listen();
|
||||
export default wrappedFetch;
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
import logger from '@/utils/logger';
|
||||
import { config } from '@/config';
|
||||
import proxy from '@/utils/proxy';
|
||||
|
||||
type Get = typeof http.get | typeof https.get | typeof http.request | typeof https.request;
|
||||
|
||||
const getWrappedGet: <T extends Get>(origin: T) => T = (origin) =>
|
||||
function (this: any, ...args: Parameters<typeof origin>) {
|
||||
let url: URL;
|
||||
let options: http.RequestOptions = {};
|
||||
let callback: ((res: http.IncomingMessage) => void) | undefined;
|
||||
if (typeof args[0] === 'string' || args[0] instanceof URL) {
|
||||
url = new URL(args[0]);
|
||||
if (typeof args[1] === 'object') {
|
||||
options = args[1];
|
||||
callback = args[2];
|
||||
} else if (typeof args[1] === 'function') {
|
||||
options = {};
|
||||
callback = args[1];
|
||||
}
|
||||
} else {
|
||||
options = args[0];
|
||||
url = new URL(options.href || `${options.protocol}//${options.hostname || options.host}${options.path}${options.search || (options.query ? `?${options.query}` : '')}`);
|
||||
if (typeof args[1] === 'function') {
|
||||
callback = args[1];
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Outgoing request: ${options.method || 'GET'} ${url}`);
|
||||
|
||||
options.headers = options.headers || {};
|
||||
const headersLowerCaseKeys = new Set(Object.keys(options.headers).map((key) => key.toLowerCase()));
|
||||
|
||||
// ua
|
||||
if (!headersLowerCaseKeys.has('user-agent')) {
|
||||
options.headers['user-agent'] = config.ua;
|
||||
}
|
||||
|
||||
// Accept
|
||||
if (!headersLowerCaseKeys.has('accept')) {
|
||||
options.headers.accept = '*/*';
|
||||
}
|
||||
|
||||
// referer
|
||||
if (!headersLowerCaseKeys.has('referer')) {
|
||||
options.headers.referer = url.origin;
|
||||
}
|
||||
|
||||
// proxy
|
||||
if (!options.agent && proxy.agent) {
|
||||
const proxyRegex = new RegExp(proxy.proxyObj.url_regex);
|
||||
|
||||
if (proxyRegex.test(url.toString()) && url.protocol.startsWith('http') && url.host !== proxy.proxyUrlHandler?.host) {
|
||||
options.agent = proxy.agent;
|
||||
if (proxy.proxyObj.auth) {
|
||||
options.headers['Proxy-Authorization'] = `Basic ${proxy.proxyObj.auth}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Reflect.apply(origin, this, [url, options, callback]) as ReturnType<typeof origin>;
|
||||
};
|
||||
|
||||
export default getWrappedGet;
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { Headers, FormData, Request, Response } from 'undici';
|
||||
import http from 'node:http';
|
||||
import https from 'node:https';
|
||||
|
||||
import fetch from '@/utils/request-rewriter/fetch';
|
||||
import getWrappedGet from '@/utils/request-rewriter/get';
|
||||
|
||||
Object.defineProperties(globalThis, {
|
||||
fetch: { value: fetch },
|
||||
Headers: { value: Headers },
|
||||
FormData: { value: FormData },
|
||||
Request: { value: Request },
|
||||
Response: { value: Response },
|
||||
});
|
||||
|
||||
http.get = getWrappedGet(http.get);
|
||||
http.request = getWrappedGet(http.request);
|
||||
https.get = getWrappedGet(https.get);
|
||||
https.request = getWrappedGet(https.request);
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
import { config } from '@/config';
|
||||
import logger from '@/utils/logger';
|
||||
import http, { type RequestOptions } from 'node:http';
|
||||
import https from 'node:https';
|
||||
import proxy from '@/utils/proxy';
|
||||
|
||||
let proxyWrapper: (
|
||||
url: string,
|
||||
options: RequestOptions & {
|
||||
headers: Record<string, string>;
|
||||
}
|
||||
) => boolean = () => false;
|
||||
|
||||
if (proxy.agent) {
|
||||
const proxyRegex = new RegExp(proxy.proxyObj.url_regex);
|
||||
const protocolMatch = (protocolLike?: string | null) => protocolLike?.toLowerCase().startsWith('http');
|
||||
|
||||
proxyWrapper = (url, options) => {
|
||||
let urlHandler;
|
||||
try {
|
||||
urlHandler = new URL(url);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
if (proxyRegex.test(url) && (protocolMatch(options.protocol) || protocolMatch(url)) && (!urlHandler || urlHandler.host !== proxy.proxyUrlHandler?.host)) {
|
||||
options.agent = proxy.agent || false;
|
||||
if (proxy.proxyObj.auth) {
|
||||
options.headers['Proxy-Authorization'] = `Basic ${proxy.proxyObj.auth}`;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
const requestWrapper = (url: string, options: http.RequestOptions = {}) => {
|
||||
options.headers = options.headers || {};
|
||||
|
||||
const optionsWithHeaders = options as http.RequestOptions & {
|
||||
headers: Record<string, string>;
|
||||
};
|
||||
const headersLowerCaseKeys = new Set(Object.keys(optionsWithHeaders.headers).map((key) => key.toLowerCase()));
|
||||
|
||||
let prxied = false;
|
||||
if (config.proxyStrategy === 'all') {
|
||||
prxied = proxyWrapper(url, optionsWithHeaders);
|
||||
} else if (config.proxyStrategy === 'on_retry' && (optionsWithHeaders as any).retryCount) {
|
||||
// TODO
|
||||
prxied = proxyWrapper(url, optionsWithHeaders);
|
||||
}
|
||||
if (prxied) {
|
||||
logger.debug(`Proxy for ${url}`);
|
||||
} else {
|
||||
logger.debug(`Requesting ${url}`);
|
||||
}
|
||||
|
||||
// ua
|
||||
if (!headersLowerCaseKeys.has('user-agent')) {
|
||||
options.headers['user-agent'] = config.ua;
|
||||
}
|
||||
|
||||
// Accept
|
||||
if (!headersLowerCaseKeys.has('accept')) {
|
||||
options.headers.Accept = '*/*';
|
||||
}
|
||||
|
||||
let urlHandler;
|
||||
try {
|
||||
urlHandler = new URL(url);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (
|
||||
urlHandler && // referer
|
||||
!headersLowerCaseKeys.has('referer')
|
||||
) {
|
||||
options.headers.referer = urlHandler.origin;
|
||||
}
|
||||
};
|
||||
|
||||
const httpWrap = (func: typeof http.request) => {
|
||||
const origin = func;
|
||||
const warpped: typeof http.request = function (...args) {
|
||||
let url: string;
|
||||
let options: http.RequestOptions;
|
||||
if (args[0] instanceof URL || typeof args[0] === 'string') {
|
||||
url = args[0].toString();
|
||||
options = args[1] as http.RequestOptions;
|
||||
} else {
|
||||
options = args[0] as http.RequestOptions;
|
||||
url = `${options.protocol}//${options.hostname || options.host}${options.path}`;
|
||||
}
|
||||
requestWrapper(url, options);
|
||||
|
||||
// @ts-expect-error apply
|
||||
return origin.apply(this, args);
|
||||
};
|
||||
return warpped;
|
||||
};
|
||||
|
||||
http.get = httpWrap(http.get);
|
||||
https.get = httpWrap(https.get);
|
||||
http.request = httpWrap(http.request);
|
||||
https.request = httpWrap(https.request);
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "hono/jsx",
|
||||
|
|
|
|||
Loading…
Reference in New Issue