Skip to content
This repository was archived by the owner on Oct 14, 2024. It is now read-only.

Commit

Permalink
await
Browse files Browse the repository at this point in the history
  • Loading branch information
so1ve committed Feb 26, 2024
1 parent 276467a commit 889e281
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 11 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"eslint": "^8.51.0",
"pkgroll": "^2.0.1",
"prettier": "^3.0.3",
"tiny-request-router": "^1.2.2",
"tsx": "^3.13.0",
"typescript": "^5.2.2",
"vite": "^4.4.11",
Expand Down
13 changes: 13 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions src/cgi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { Method, Params } from "tiny-request-router";
import { Router } from "tiny-request-router";

import { CacheDB } from "./cache-db";
import { CW_CGI_PREFIX } from "./constants";

const router = new Router<(params: Params) => Promise<Response>>();

export async function handleRoutes(request: Request): Promise<Response> {
const db = new CacheDB();
const url = new URL(request.url.slice(CW_CGI_PREFIX.length));
const match = router.match(request.method as Method, url.pathname);

if (match) {
const response = await match.handler(Object.fromEntries(url.searchParams));

return response;
} else {
return new Response("Not Found!, Client Worker!");
}
}
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const CW_CGI_PREFIX = "/cw-cgi";
254 changes: 244 additions & 10 deletions src/engine.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
/* eslint-disable ts/no-throw-literal */
import * as logger from "./logger";
import { rebuildRequest } from "./rebuild";
import type { FetchEngineFunction } from "./types";
import { rebuildRequest, rebuildResponse } from "./rebuild";
import type {
FetchEngineConfig,
FetchEngineFunction,
ListFetchEngineFunction,
} from "./types";

const create504Response = (engine: string) =>
new Response(
Expand All @@ -11,6 +16,14 @@ const create504Response = (engine: string) =>
},
);

const DEFAULT_CONFIG = Object.freeze({
threads: 4,
trylimit: 10,
status: 200,
timeout: 5000,
redirect: "follow",
} satisfies FetchEngineConfig);

export const engineFetch: FetchEngineFunction = async (request, config) => {
config = {
status: 200,
Expand All @@ -20,7 +33,6 @@ export const engineFetch: FetchEngineFunction = async (request, config) => {
};

setTimeout(() => {
// eslint-disable-next-line ts/no-throw-literal
throw create504Response("Fetch");
}, config.timeout);

Expand All @@ -33,11 +45,7 @@ export const engineFetch: FetchEngineFunction = async (request, config) => {

export const engineCrazy: FetchEngineFunction = async (request, config) => {
config = {
threads: 4,
trylimit: 10,
status: 200,
timeout: 5000,
redirect: "follow",
...DEFAULT_CONFIG,
...config,
};

Expand All @@ -59,7 +67,7 @@ export const engineCrazy: FetchEngineFunction = async (request, config) => {

if (!contentLength || contentLength < config.threads!) {
logger.error(
`engineCrazy: The Origin does not support Crazy Mode, or the size of the file is less than ${config.threads} bytes, downgrade to normal fetch.`,
`Engine Crazy: The Origin does not support Crazy Mode, or the size of the file is less than ${config.threads} bytes, downgrade to normal fetch.`,
);

return engineFetch(request, config);
Expand Down Expand Up @@ -105,7 +113,6 @@ export const engineCrazy: FetchEngineFunction = async (request, config) => {
const responses = await Promise.all(chunks);

setTimeout(() => {
// eslint-disable-next-line ts/no-throw-literal
throw create504Response("Crazy");
}, config.timeout);

Expand All @@ -115,3 +122,230 @@ export const engineCrazy: FetchEngineFunction = async (request, config) => {
statusText: "OK",
});
};

export const engineClassic: ListFetchEngineFunction = async (
requests,
config,
) => {
config = {
...DEFAULT_CONFIG,
...config,
};

if (requests.length === 0) {
throw new Error("Engine Classic: No request to fetch.");
} else if (requests.length === 1) {
logger.warning(
"Engine Classic: only one request, this request will downgrade to normal fetch.",
);

return engineFetch(requests[0], config);
}

const controller = new AbortController();

setTimeout(() => {
throw create504Response("Classic");
}, config.timeout);

const fetchRequests = requests.map(async (req) => {
try {
const response = await fetch(req, {
signal: controller.signal,
mode: config!.mode,
credentials: config!.credentials,
redirect: config!.redirect,
});

const responseData = await response.arrayBuffer();

const modifiedResponse = new Response(responseData, {
status: response.status,
headers: response.headers,
statusText: response.statusText,
});

if (modifiedResponse.status === config!.status) {
controller.abort();

return modifiedResponse;
}
} catch (err) {
if (err === "DOMException: The user aborted a request.") {
// eslint-disable-next-line no-console
console.log(); // To disable the warning:DOMException: The user aborted a request.
}
}
});

return (await Promise.any(fetchRequests))!;
};

export const engineParallel: ListFetchEngineFunction = async (
requests,
config,
) => {
config = {
...DEFAULT_CONFIG,
...config,
};

if (requests.length === 0) {
throw new Error("Engine Parallel: No request to fetch.");
} else if (requests.length === 1) {
logger.warning(
"Engine Parallel: only one request, this request will downgrade to normal fetch.",
);

return engineFetch(requests[0], config);
}

const abortEvent = new Event("abortOtherInstance");
const eventTarget = new EventTarget();

const fetchRequests = requests.map(async (req) => {
const controller = new AbortController();
let tagged = false;

eventTarget.addEventListener(abortEvent.type, () => {
if (!tagged) {
controller.abort();
}
});

try {
const res = await fetch(req, {
signal: controller.signal,
mode: config!.mode,
credentials: config!.credentials,
redirect: config!.redirect,
});

if (config!.status && res.status === config!.status) {
tagged = true;
eventTarget.dispatchEvent(abortEvent);

return rebuildResponse(res);
}
} catch (err) {
if (err === "DOMException: The user aborted a request.") {
// eslint-disable-next-line no-console
console.log(); // To disable the warning:DOMException: The user aborted a request.
}
}
});

setTimeout(() => {
throw create504Response("Parallel");
}, config.timeout);

return (await Promise.any(fetchRequests))!;
};

export const engineKFCThursdayVW50: ListFetchEngineFunction = async (
requests,
config,
) => {
config = {
...DEFAULT_CONFIG,
timeout: 30_000,
...config,
};

if (requests.length === 0) {
throw new Error("Engine KFCThursdayVW50: No request to fetch.");
} else if (requests.length === 1) {
logger.warning(
"Engine KFCThursdayVW50: only one request, this request will downgrade to engine crazy.",
);

return engineCrazy(requests[0], config);
}

const controller = new AbortController();
const preFetch = await engineParallel(requests, {
signal: controller.signal,
mode: config.mode,
credentials: config.credentials,
redirect: config.redirect,
timeout: config.timeout,
});
const preHeaders = preFetch.headers;
const contentLength = Number.parseInt(preHeaders.get("Content-Length")!);

if (preFetch.status === config.status) {
throw create504Response("KFCThursdayVW50");
}

controller.abort();

if (!contentLength || contentLength < config.threads!) {
logger.warning(
`Engine KFCThursdayVW50: The Origin does not support KFCThursdayVW50 Mode, or the size of the file is less than ${config.threads} bytes, downgrade to parallel.`,
);

return engineParallel(requests, config);
}

const chunkSize = contentLength / config.threads!;
const chunks: Promise<ArrayBuffer>[] = [];

for (let i = 0; i < config.threads!; i++) {
chunks.push(
new Promise((resolve, reject) => {
let trycount = 1;

async function instance(): Promise<ArrayBuffer> {
trycount += 1;

const newRequests = [];

for (const request of requests) {
newRequests.push(
rebuildRequest(request, {
headers: {
Range: `bytes=${i * chunkSize}-${(i + 1) * chunkSize - 1}`,
},
url: request.url,
}),
);
}

return engineParallel(newRequests, {
mode: config!.mode,
credentials: config!.credentials,
redirect: config!.redirect,
timeout: config!.timeout,
status: 206,
})
.then((response) => response.arrayBuffer())
.catch(async (err) => {
logger.error(`Engine KFCThursdayVW50: ${await err.text()}`);
if (trycount >= config!.trylimit!) {
reject(err);
}

return instance();
});
}
resolve(instance());
}),
);
}

setTimeout(() => {
throw create504Response("KFCThursdayVW50");
}, config.timeout);

try {
const responses = await Promise.all(chunks);

return new Response(new Blob(responses), {
headers: preHeaders,
status: 200,
statusText: "OK",
});
} catch {
throw create504Response("KFCThursdayVW50");
}
};
2 changes: 1 addition & 1 deletion src/rebuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function rebuildRequest(request: Request, init: CwRequestInit) {
return newRequest;
}

export function rebuildResponse(response: Response, init: CwResponseInit) {
export function rebuildResponse(response: Response, init: CwResponseInit = {}) {
if (response.type === "opaque") {
logger.error(
`You can't rebuild a opaque response.ClientWorker will ignore this build`,
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,8 @@ export type FetchEngineFunction = (
request: Request,
config?: FetchEngineConfig,
) => Promise<Response>;

export type ListFetchEngineFunction = (
request: Request[],
config?: FetchEngineConfig,
) => Promise<Response>;

0 comments on commit 889e281

Please sign in to comment.