@mojojs/core
Version:
Real-time web framework
173 lines • 5.29 kB
JavaScript
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