mirror of
https://github.com/MercuryWorkshop/adrift.git
synced 2025-05-13 06:10:01 -04:00
serverside client chunking impl (CURSED)
This commit is contained in:
parent
a839d0fe51
commit
b73a561844
5 changed files with 51 additions and 20 deletions
|
@ -7,7 +7,6 @@ import {
|
|||
ReadyStateCallback,
|
||||
WebSocketImpl,
|
||||
} from "bare-client-custom";
|
||||
import { ReadableStream, TransformStream } from "node:stream/web";
|
||||
import { MAX_CHUNK_SIZE } from "protocol";
|
||||
import { Connection } from "./Connection";
|
||||
|
||||
|
@ -23,6 +22,17 @@ function createBodyStream(
|
|||
): ReadableStream<ArrayBuffer | Uint8Array> | null {
|
||||
if (body === null) return null;
|
||||
|
||||
if (typeof body === "string") {
|
||||
body = new TextEncoder().encode(body);
|
||||
}
|
||||
|
||||
if (ArrayBuffer.isView(body)) {
|
||||
body = body.buffer.slice(
|
||||
body.byteOffset,
|
||||
body.byteOffset + body.byteLength
|
||||
);
|
||||
}
|
||||
|
||||
const transformer = () =>
|
||||
new TransformStream({
|
||||
transform: async (
|
||||
|
@ -82,7 +92,6 @@ function createBodyStream(
|
|||
}
|
||||
|
||||
if (body instanceof Blob) {
|
||||
// @ts-expect-error
|
||||
return body.stream().pipeThrough(transformer());
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { ReadableStream } from "node:stream/web";
|
||||
import {
|
||||
C2SRequestType,
|
||||
C2SRequestTypes,
|
||||
|
@ -164,11 +163,11 @@ export class Connection {
|
|||
await this.send(seq, C2SRequestTypes.HTTPRequestStart, new Blob([json]));
|
||||
|
||||
if (body) {
|
||||
for await (const chunk of body) {
|
||||
for await (const chunk of body as unknown as NodeJS.ReadableStream) {
|
||||
await this.send(
|
||||
seq,
|
||||
C2SRequestTypes.HTTPRequestChunk,
|
||||
new Uint8Array(chunk)
|
||||
new Uint8Array(chunk as Uint8Array | ArrayBuffer)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
|
@ -85,10 +85,6 @@ importers:
|
|||
version: 4.9.4
|
||||
|
||||
Ultraviolet:
|
||||
dependencies:
|
||||
bare-client-custom:
|
||||
specifier: workspace:2.2.0-alpha
|
||||
version: link:../bare-client-custom
|
||||
devDependencies:
|
||||
'@tomphttp/bare-client':
|
||||
specifier: ^2.2.0-alpha
|
||||
|
@ -178,6 +174,10 @@ importers:
|
|||
protocol:
|
||||
specifier: workspace:*
|
||||
version: link:../protocol
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^20.4.10
|
||||
version: 20.4.10
|
||||
|
||||
corium:
|
||||
dependencies:
|
||||
|
@ -3779,10 +3779,6 @@ packages:
|
|||
resolution: {integrity: sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==}
|
||||
dev: true
|
||||
|
||||
/@types/crypto-js@4.1.1:
|
||||
resolution: {integrity: sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==}
|
||||
dev: true
|
||||
|
||||
/@types/css-tree@2.0.0:
|
||||
resolution: {integrity: sha512-mY2sXRLBnUPMYw6mkOT+6dABeaNxAEKZz6scE9kQPNJx8fKe1fOsm8Honl7+xFYe6TKX8WNk2+7oMp2vBArJ9Q==}
|
||||
dev: true
|
||||
|
|
|
@ -9,7 +9,7 @@ import {
|
|||
import { Agent as HTTPSAgent, request as httpsRequest } from "https";
|
||||
import fuck from "ipaddr.js";
|
||||
import { HTTPRequestPayload } from "protocol";
|
||||
import { Readable } from "stream";
|
||||
import { Writable } from "stream";
|
||||
const { isValid, parse } = fuck;
|
||||
|
||||
export interface BareErrorBody {
|
||||
|
@ -139,6 +139,7 @@ function outgoingError<T>(error: T): T | BareError {
|
|||
|
||||
export async function bareFetch(
|
||||
request: HTTPRequestPayload,
|
||||
pipeOutgoing: (s: Writable) => void,
|
||||
signal: AbortSignal,
|
||||
remote: URL,
|
||||
options: BareServerOptions
|
||||
|
@ -172,8 +173,7 @@ export async function bareFetch(
|
|||
});
|
||||
else throw new RangeError(`Unsupported protocol: '${remote.protocol}'`);
|
||||
|
||||
if (request.body) Readable.from([request.body]).pipe(outgoing);
|
||||
else outgoing.end();
|
||||
pipeOutgoing(outgoing);
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
outgoing.on("response", (response: IncomingMessage) => {
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
WSClosePayload,
|
||||
WSErrorPayload,
|
||||
} from "protocol";
|
||||
import { Readable } from "stream";
|
||||
import { Readable, Writable } from "stream";
|
||||
import { BareError, bareFetch, options } from "./http";
|
||||
|
||||
function bareErrorToResponse(e: BareError): {
|
||||
|
@ -25,12 +25,14 @@ function bareErrorToResponse(e: BareError): {
|
|||
statusText: STATUS_CODES[e.status] || "",
|
||||
headers: {},
|
||||
},
|
||||
// TODO: this is node specific. for web we might have to go through Blob here
|
||||
body: Readable.from(JSON.stringify(e.body)),
|
||||
};
|
||||
}
|
||||
|
||||
export class AdriftServer {
|
||||
send: (msg: ArrayBuffer) => void;
|
||||
requestStreams: Record<number, Writable> = {};
|
||||
sockets: Record<number, WebSocket> = {};
|
||||
events: EventEmitter;
|
||||
|
||||
|
@ -74,7 +76,10 @@ export class AdriftServer {
|
|||
return payload;
|
||||
}
|
||||
|
||||
async handleHTTPRequest(payload: HTTPRequestPayload): Promise<{
|
||||
async handleHTTPRequest(
|
||||
payload: HTTPRequestPayload,
|
||||
pipeOutgoing: (s: Writable) => void
|
||||
): Promise<{
|
||||
payload: HTTPResponsePayload;
|
||||
body: AsyncIterable<ArrayBuffer>;
|
||||
}> {
|
||||
|
@ -89,6 +94,7 @@ export class AdriftServer {
|
|||
try {
|
||||
resp = await bareFetch(
|
||||
payload,
|
||||
pipeOutgoing,
|
||||
abort.signal,
|
||||
new URL(payload.remote),
|
||||
options
|
||||
|
@ -199,16 +205,20 @@ export class AdriftServer {
|
|||
if (!init) return;
|
||||
const { cursor, seq, op } = init;
|
||||
switch (op) {
|
||||
case C2SRequestTypes.HTTPRequest: {
|
||||
case C2SRequestTypes.HTTPRequestStart: {
|
||||
let resp: {
|
||||
payload: HTTPResponsePayload;
|
||||
body: AsyncIterable<ArrayBuffer>;
|
||||
};
|
||||
const reqPayload = AdriftServer.tryParseJSONPayload(msg.slice(cursor));
|
||||
if (!reqPayload) return;
|
||||
|
||||
try {
|
||||
resp = await this.handleHTTPRequest(reqPayload);
|
||||
resp = await this.handleHTTPRequest(reqPayload, (outgoingStream) => {
|
||||
this.requestStreams[seq] = outgoingStream;
|
||||
});
|
||||
} catch (e) {
|
||||
delete this.requestStreams[seq];
|
||||
if (options.logErrors) console.error(e);
|
||||
|
||||
let bareError;
|
||||
|
@ -233,8 +243,10 @@ export class AdriftServer {
|
|||
resp = bareErrorToResponse(bareError);
|
||||
}
|
||||
|
||||
delete this.requestStreams[seq];
|
||||
const { payload, body } = resp;
|
||||
this.sendHTTPResponseStart(seq, payload);
|
||||
|
||||
for await (const chunk of body) {
|
||||
let chunkPart = null;
|
||||
let chunkRest = chunk;
|
||||
|
@ -248,6 +260,21 @@ export class AdriftServer {
|
|||
break;
|
||||
}
|
||||
|
||||
case C2SRequestTypes.HTTPRequestChunk: {
|
||||
const stream = this.requestStreams[seq];
|
||||
if (!stream) return;
|
||||
stream.write(new Uint8Array(msg.slice(cursor)));
|
||||
break;
|
||||
}
|
||||
|
||||
case C2SRequestTypes.HTTPRequestEnd: {
|
||||
const stream = this.requestStreams[seq];
|
||||
if (!stream) return;
|
||||
stream.end();
|
||||
delete this.requestStreams[seq];
|
||||
break;
|
||||
}
|
||||
|
||||
case C2SRequestTypes.WSOpen: {
|
||||
const payload = AdriftServer.tryParseJSONPayload(msg.slice(cursor));
|
||||
const ws = (this.sockets[seq] = new WebSocket(payload.url));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue