UNPKG

@mojojs/core

Version:

Real-time web framework

173 lines 5.29 kB
import { on } from 'node:events'; import zlib from 'node:zlib'; import { Params } from './body/params.js'; import { Headers } from './headers.js'; import DOM from '@mojojs/dom'; import busboy from 'busboy'; import yaml from 'js-yaml'; /** * HTTP message body base class. */ export class Body { constructor(headers, stream) { /** * Automatically decompress message body if necessary. */ this.autoDecompress = true; this._form = undefined; this._stream = stream; this.headers = new Headers(headers); } async *[Symbol.asyncIterator]() { yield* this.createReadStream(); } /** * Get message body as `Buffer` object. */ async buffer() { return Buffer.concat(await this._consumeBody()); } /** * Get message body as a readable stream. */ createReadStream() { const stream = this._stream; if (stream.readableEnded === true) throw new Error('Request body has already been consumed'); if (this.autoDecompress !== true || this.get('content-encoding') !== 'gzip') return stream; const gunzip = zlib.createGunzip(); stream.pipe(gunzip); return gunzip; } /** * Get async iterator for uploaded files from message body. * @example * // Iterate over uploaded files * for await (const {fieldname, file, filename} of body.files()) { * const parts = []; * for await (const chunk of file) { * parts.push(chunk); * } * const content = Buffer.concat(parts).toString(); * console.write(`${fieldname}: ${content}`); * } */ async *files(options) { if (this._isForm() === false) return; try { for await (const [fieldname, file, name, encoding, mimetype] of this._formIterator(options)) { const filename = name.filename; yield { fieldname, file, filename, encoding, mimetype }; } } catch (error) { if (!(error instanceof Error) || error.name !== 'AbortError') throw error; } } /** * Get form parameters from message body. * @example * // Get a specific parameter * const params = await body.form(); * const foo = params.get('foo'); */ async form(options) { if (this._form === undefined) { // eslint-disable-next-line @typescript-eslint/no-unused-vars for await (const _ of this.files(options)) ; } return this._params; } /** * Get HTTP header from message. * @example * // Get User-Agent header * const agent = body.get('User-Agent'); */ get(name) { return this.headers.get(name); } /** * Get HTML message body as `@mojojs/dom` object. */ async html() { return new DOM(await this.text()); } /** * Get JSON message body as parsed data structure. */ async json() { return JSON.parse((await this.buffer()).toString()); } /** * Pipe message body to writable stream. */ async pipe(writer) { return await new Promise((resolve, reject) => { this.createReadStream().on('error', reject).pipe(writer).on('unpipe', resolve); }); } /** * Set HTTP header for message. * // Set Server header * body.set('Server', 'mojo.js'); */ set(name, value) { this.headers.set(name, value); return this; } /** * Get message body as string. */ async text(charset = 'utf8') { return (await this.buffer()).toString(charset); } /** * Get XML message body as `@mojojs/dom` object. */ async xml() { return new DOM(await this.text(), { xml: true }); } /** * Get YAML message body as parsed data structure. */ async yaml() { return yaml.load((await this.buffer()).toString()); } async _consumeBody() { const chunks = []; return await new Promise((resolve, reject) => { this.createReadStream() .on('data', chunk => chunks.push(Buffer.from(chunk))) .on('error', reject) .on('end', () => resolve(chunks)); }); } _formIterator(options) { const ac = new AbortController(); const type = this.get('Content-Type') ?? ''; const params = this._params; const bb = busboy({ headers: { 'content-type': type, ...this.headers.toObject() }, ...options }); bb.on('field', (fieldname, val) => params.append(fieldname, val)); bb.on('end', () => ac.abort()).on('close', () => ac.abort()); const files = on(bb, 'file', { signal: ac.signal }); this._stream.pipe(bb); return files; } _isForm() { const type = this.get('Content-Type'); if (type === null) return false; return type.startsWith('application/x-www-form-urlencoded') || type.startsWith('multipart/form-data'); } get _params() { if (this._form === undefined) this._form = new Params(); return this._form; } } //# sourceMappingURL=body.js.map