| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163 |
- import HttpErrors from 'http-errors';
- import {Errorf} from '@e22m4u/js-format';
- import {RouterOptions} from '../router-options.js';
- import {DebuggableService} from '../debuggable-service.js';
- import {
- createError,
- parseContentType,
- fetchRequestBody,
- } from '../utils/index.js';
- /**
- * Method names to be parsed.
- *
- * @type {string[]}
- */
- export const METHODS_WITH_BODY = ['POST', 'PUT', 'PATCH', 'DELETE'];
- /**
- * Unparsable media types.
- *
- * @type {string[]}
- */
- export const UNPARSABLE_MEDIA_TYPES = ['multipart/form-data'];
- /**
- * Body parser.
- */
- export class BodyParser extends DebuggableService {
- /**
- * Parsers.
- *
- * @type {{[mime: string]: Function}}
- */
- _parsers = {
- 'text/plain': v => String(v),
- 'application/json': parseJsonBody,
- };
- /**
- * Set parser.
- *
- * @param {string} mediaType
- * @param {Function} parser
- * @returns {this}
- */
- defineParser(mediaType, parser) {
- if (!mediaType || typeof mediaType !== 'string')
- throw new Errorf(
- 'The parameter "mediaType" of BodyParser.defineParser ' +
- 'should be a non-empty String, but %v was given.',
- mediaType,
- );
- if (!parser || typeof parser !== 'function')
- throw new Errorf(
- 'The parameter "parser" of BodyParser.defineParser ' +
- 'should be a Function, but %v was given.',
- parser,
- );
- this._parsers[mediaType] = parser;
- return this;
- }
- /**
- * Has parser.
- *
- * @param {string} mediaType
- * @returns {boolean}
- */
- hasParser(mediaType) {
- if (!mediaType || typeof mediaType !== 'string')
- throw new Errorf(
- 'The parameter "mediaType" of BodyParser.hasParser ' +
- 'should be a non-empty String, but %v was given.',
- mediaType,
- );
- return Boolean(this._parsers[mediaType]);
- }
- /**
- * Delete parser.
- *
- * @param {string} mediaType
- * @returns {this}
- */
- deleteParser(mediaType) {
- if (!mediaType || typeof mediaType !== 'string')
- throw new Errorf(
- 'The parameter "mediaType" of BodyParser.deleteParser ' +
- 'should be a non-empty String, but %v was given.',
- mediaType,
- );
- const parser = this._parsers[mediaType];
- if (!parser) throw new Errorf('The parser of %v is not found.', mediaType);
- delete this._parsers[mediaType];
- return this;
- }
- /**
- * Parse.
- *
- * @param {import('http').IncomingMessage} request
- * @returns {Promise<*>|undefined}
- */
- parse(request) {
- const debug = this.getDebuggerFor(this.parse);
- if (!METHODS_WITH_BODY.includes(request.method.toUpperCase())) {
- debug(
- 'Body parsing was skipped for the %s request.',
- request.method.toUpperCase(),
- );
- return;
- }
- const contentType = (request.headers['content-type'] || '').replace(
- /^([^;]+);.*$/,
- '$1',
- );
- if (!contentType) {
- debug(
- 'Body parsing was skipped because the request had no content type.',
- );
- return;
- }
- const {mediaType} = parseContentType(contentType);
- if (!mediaType)
- throw createError(
- HttpErrors.BadRequest,
- 'Unable to parse the "content-type" header.',
- );
- const parser = this._parsers[mediaType];
- if (!parser) {
- if (UNPARSABLE_MEDIA_TYPES.includes(mediaType)) {
- debug('Body parsing was skipped for %v.', mediaType);
- return;
- }
- throw createError(
- HttpErrors.UnsupportedMediaType,
- 'Media type %v is not supported.',
- mediaType,
- );
- }
- const bodyBytesLimit = this.getService(RouterOptions).requestBodyBytesLimit;
- return fetchRequestBody(request, bodyBytesLimit).then(rawBody => {
- if (rawBody != null) return parser(rawBody);
- return rawBody;
- });
- }
- }
- /**
- * Parse json body.
- *
- * @param {string} input
- * @returns {*|undefined}
- */
- export function parseJsonBody(input) {
- if (typeof input !== 'string') return undefined;
- try {
- return JSON.parse(input);
- } catch (error) {
- throw createError(HttpErrors.BadRequest, error.message);
- }
- }
|