UNPKG

ufiber

Version:

Next-gen webserver for node-js developer

123 lines (121 loc) 3.91 kB
import { formatBytes } from "../utils/tools.js"; import { HIGH_WATER_MARK } from "../consts.js"; import { HttpError } from "../errors.js"; import { Readable } from "stream"; //#region src/http/readable.ts const kUwsRead = Symbol("uws-readable"); var UwsReadable = class extends Readable { #needsData = false; #completed = false; #usedStream = false; #usedBuffer = false; [kUwsRead] = { bytes: 0, chunks: [], isDone: false, buffer: null }; constructor(res, limit) { super({ highWaterMark: HIGH_WATER_MARK }); res.onData((chunk, isLast) => { if (this.destroyed) return; const buf = isLast ? Buffer.from(chunk) : Buffer.from(new Uint8Array(chunk)); this[kUwsRead].chunks.push(buf); this[kUwsRead].bytes += buf.length; if (typeof limit !== "undefined" && this[kUwsRead].bytes > limit) return this.destroy(new HttpError(413, { message: `Request body ${formatBytes(this[kUwsRead].bytes)} exceeds limit: ${formatBytes(limit)}` })); if (isLast) { this[kUwsRead].isDone = true; if (!this.#usedStream && !this.#completed && (this.#usedBuffer || this.listenerCount("complete") > 0)) this.#complete(); } if (this.#needsData) { this.#needsData = false; this._read(); } }); } _read() { if (this.#usedBuffer) { this.destroy(/* @__PURE__ */ new Error("Cannot read stream after getBuffer() has been called")); return; } this.#usedStream = true; while (this[kUwsRead].chunks.length > 0) { const chunk = this[kUwsRead].chunks.shift(); if (!this.push(chunk)) break; } if (this[kUwsRead].isDone && this[kUwsRead].chunks.length === 0) this.push(null); else if (this[kUwsRead].chunks.length === 0) this.#needsData = true; } _destroy(err, cb) { const r = this[kUwsRead]; r.isDone = true; r.chunks = []; r.buffer = null; this.#needsData = false; this.#completed = false; super._destroy(err, cb); } once(event, listener) { if (event === "complete") { if (this.#usedStream) throw new Error("Cannot use complete event after stream has been read"); if (this.#completed && this[kUwsRead].buffer) { queueMicrotask(() => listener(this[kUwsRead].buffer)); return this; } if (this[kUwsRead].isDone && !this.#completed) this.#complete(); } return super.once(event, listener); } #bodyBuf = () => { if (this[kUwsRead].buffer) return this[kUwsRead].buffer; const chunksLen = this[kUwsRead].chunks.length; let buffer; if (chunksLen === 0) buffer = Buffer.allocUnsafe(0); else if (chunksLen === 1) buffer = this[kUwsRead].chunks[0]; else buffer = Buffer.concat(this[kUwsRead].chunks, this[kUwsRead].bytes); this[kUwsRead].buffer = buffer; this[kUwsRead].chunks = []; return buffer; }; #complete = () => { if (this.#completed) return; this.#completed = true; queueMicrotask(() => { if (this.destroyed) return; const body = this.#bodyBuf(); this.emit("complete", body); }); }; getBuffer = () => new Promise((resolve, reject) => { if (this.destroyed) return reject(/* @__PURE__ */ new Error("Stream has been destroyed")); if (this.#usedStream) return reject(/* @__PURE__ */ new Error("Cannot get buffer after stream has been read")); this.#usedBuffer = true; if (this[kUwsRead].isDone) return resolve(this.#bodyBuf()); const onComplete = (buffer) => { cleanup(); resolve(buffer); }; const onError = (err) => { cleanup(); reject(err); }; const onClose = () => { cleanup(); reject(/* @__PURE__ */ new Error("Stream closed before completion")); }; const cleanup = () => { this.off("error", onError); this.off("close", onClose); this.off("complete", onComplete); }; this.once("error", onError); this.once("close", onClose); this.once("complete", onComplete); if (this.destroyed) { cleanup(); return reject(/* @__PURE__ */ new Error("Stream has been destroyed")); } }); }; //#endregion export { UwsReadable };