mirror of
https://github.com/MercuryWorkshop/adrift.git
synced 2025-05-16 15:40:01 -04:00
break up main.ts
This commit is contained in:
parent
7315350858
commit
7ec14853f2
3 changed files with 384 additions and 366 deletions
195
server/http.ts
Normal file
195
server/http.ts
Normal file
|
@ -0,0 +1,195 @@
|
|||
import { HTTPRequestPayload } from "../protocol/index.js";
|
||||
import { LookupAddress, LookupAllOptions, lookup } from "dns";
|
||||
import {
|
||||
ClientRequest,
|
||||
Agent as HTTPAgent,
|
||||
IncomingMessage,
|
||||
RequestOptions,
|
||||
STATUS_CODES,
|
||||
request as httpRequest,
|
||||
} from "http";
|
||||
import { Agent as HTTPSAgent, request as httpsRequest } from "https";
|
||||
import fuck from "ipaddr.js";
|
||||
const { isValid, parse } = fuck;
|
||||
import { Readable } from "stream";
|
||||
|
||||
|
||||
export interface BareErrorBody {
|
||||
code: string;
|
||||
id: string;
|
||||
message?: string;
|
||||
stack?: string;
|
||||
}
|
||||
|
||||
export class BareError extends Error {
|
||||
status: number;
|
||||
body: BareErrorBody;
|
||||
constructor(status: number, body: BareErrorBody) {
|
||||
super(body.message || body.code);
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
export const options: BareServerOptions = {
|
||||
logErrors: true,
|
||||
filterRemote: (url) => {
|
||||
// if the remote is an IP then it didn't go through the init.lookup hook
|
||||
// isValid determines if this is so
|
||||
if (isValid(url.hostname) && parse(url.hostname).range() !== "unicast")
|
||||
throw new RangeError("Forbidden IP");
|
||||
},
|
||||
lookup: (hostname, options, callback) =>
|
||||
lookup(hostname, options, (err, address, family) => {
|
||||
if (
|
||||
address &&
|
||||
toAddressArray(address, family).some(
|
||||
({ address }) => parse(address).range() !== "unicast"
|
||||
)
|
||||
)
|
||||
callback(new RangeError("Forbidden IP"), [], -1);
|
||||
else callback(err, address, family);
|
||||
}),
|
||||
httpAgent: new HTTPAgent({
|
||||
keepAlive: true,
|
||||
}),
|
||||
httpsAgent: new HTTPSAgent({
|
||||
keepAlive: true,
|
||||
}),
|
||||
database: new Map<string, string>(),
|
||||
};
|
||||
|
||||
export interface BareServerOptions {
|
||||
logErrors: boolean;
|
||||
/**
|
||||
* Callback for filtering the remote URL.
|
||||
* @returns Nothing
|
||||
* @throws An error if the remote is bad.
|
||||
*/
|
||||
filterRemote?: (remote: Readonly<URL>) => Promise<void> | void;
|
||||
/**
|
||||
* DNS lookup
|
||||
* May not get called when remote.host is an IP
|
||||
* Use in combination with filterRemote to block IPs
|
||||
*/
|
||||
lookup: (
|
||||
hostname: string,
|
||||
options: LookupAllOptions,
|
||||
callback: (
|
||||
err: NodeJS.ErrnoException | null,
|
||||
addresses: LookupAddress[],
|
||||
family: number
|
||||
) => void
|
||||
) => void;
|
||||
localAddress?: string;
|
||||
family?: number;
|
||||
httpAgent: HTTPAgent;
|
||||
httpsAgent: HTTPSAgent;
|
||||
database: Map<string, string>;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
address: string;
|
||||
family: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the address and family of a DNS lookup callback into an array if it wasn't already
|
||||
*/
|
||||
export function toAddressArray(address: string | Address[], family?: number) {
|
||||
if (typeof address === "string")
|
||||
return [
|
||||
{
|
||||
address,
|
||||
family,
|
||||
},
|
||||
] as Address[];
|
||||
else return address;
|
||||
}
|
||||
|
||||
|
||||
function outgoingError<T>(error: T): T | BareError {
|
||||
if (error instanceof Error) {
|
||||
switch ((<Error & { code?: string }>error).code) {
|
||||
case "ENOTFOUND":
|
||||
return new BareError(500, {
|
||||
code: "HOST_NOT_FOUND",
|
||||
id: "request",
|
||||
message: "The specified host could not be resolved.",
|
||||
});
|
||||
case "ECONNREFUSED":
|
||||
return new BareError(500, {
|
||||
code: "CONNECTION_REFUSED",
|
||||
id: "response",
|
||||
message: "The remote rejected the request.",
|
||||
});
|
||||
case "ECONNRESET":
|
||||
return new BareError(500, {
|
||||
code: "CONNECTION_RESET",
|
||||
id: "response",
|
||||
message: "The request was forcibly closed.",
|
||||
});
|
||||
case "ETIMEOUT":
|
||||
return new BareError(500, {
|
||||
code: "CONNECTION_TIMEOUT",
|
||||
id: "response",
|
||||
message: "The response timed out.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
export async function bareFetch(
|
||||
request: HTTPRequestPayload,
|
||||
signal: AbortSignal,
|
||||
remote: URL,
|
||||
options: BareServerOptions
|
||||
): Promise<IncomingMessage> {
|
||||
if (options.filterRemote) await options.filterRemote(remote);
|
||||
|
||||
const req: RequestOptions = {
|
||||
method: request.method,
|
||||
headers: request.requestHeaders,
|
||||
setHost: false,
|
||||
signal,
|
||||
localAddress: options.localAddress,
|
||||
family: options.family,
|
||||
lookup: options.lookup,
|
||||
};
|
||||
|
||||
let outgoing: ClientRequest;
|
||||
|
||||
// NodeJS will convert the URL into HTTP options automatically
|
||||
// see https://github.com/nodejs/node/blob/e30e71665cab94118833cc536a43750703b19633/lib/internal/url.js#L1277
|
||||
|
||||
if (remote.protocol === "https:")
|
||||
outgoing = httpsRequest(remote, {
|
||||
...req,
|
||||
agent: options.httpsAgent,
|
||||
});
|
||||
else if (remote.protocol === "http:")
|
||||
outgoing = httpRequest(remote, {
|
||||
...req,
|
||||
agent: options.httpAgent,
|
||||
});
|
||||
else throw new RangeError(`Unsupported protocol: '${remote.protocol}'`);
|
||||
|
||||
if (request.body) Readable.from([request.body]).pipe(outgoing);
|
||||
else outgoing.end();
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
outgoing.on("response", (response: IncomingMessage) => {
|
||||
resolve(response);
|
||||
});
|
||||
|
||||
outgoing.on("upgrade", (req, socket) => {
|
||||
reject("Remote did not send a response");
|
||||
socket.destroy();
|
||||
});
|
||||
|
||||
outgoing.on("error", (error: Error) => {
|
||||
reject(outgoingError(error));
|
||||
});
|
||||
});
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue