1 /* eslint-disable security/detect-object-injection */
5 * A very minimal API server framework.
6 * Just a self-contained router and some request glue.
10 const { promises: fsPromises
} = require('fs');
11 const path
= require('path');
12 const querystring
= require('querystring');
13 const common
= require('./common');
14 const ContentNegotiation
= require('./content-negotiation');
15 const Enum
= require('./enum');
16 const { ResponseError
, RouterNoPathError
, RouterNoMethodError
} = require('./errors');
17 const { extensionToMime
} = require('./mime-helper');
18 const Router
= require('./router');
19 const Template
= require('./template');
22 const { fileScope
} = require('@squeep/log-helper');
23 const _fileScope
= fileScope(__filename
);
25 const defaultOptions
= {
26 ignoreTrailingSlash: true,
31 staticPath: undefined, // No reasonable default
33 intrinsicHeadMethod: true,
34 intrinsicHeadPersistBody: false,
38 const cookieSplitRE
= /; */
;
42 * @param {Object} logger object which implements logging methods
43 * @param {Object} options
44 * @param {Boolean} options.ignoreTrailingSlash requests for '/foo/' will match a '/foo' route
45 * @param {string} options.proxyPrefix leading part of url path to strip
46 * @param {Boolean} options.strictAccept whether to error on unsupported Accept type
47 * @param {string} options.selfBaseUrl for constructing links
48 * @param {Boolean} options.staticMetadata serve static headers with static files
49 * @param {Boolean} options.trustProxy trust some header data to be provided by proxy
50 * @param {Boolean} options.intrinsicHeadMethod handle HEAD requests automatically if not specified as a route method
51 * @param {Boolean} options.intrinsicHeadPersistBody include un-sent body on ctx for automatic HEAD requests
52 * @param {Object} options.querystring alternate qs parser to use
54 constructor(logger
= console
, options
= {}) {
55 common
.setOptions(this, defaultOptions
, options
);
57 this.router
= new Router(options
);
59 if (!this.proxyPrefix
) {
60 this._stripPrefix
= (p
) => p
;
63 this.responseTypes
= [
64 Enum
.ContentType
.TextHTML
,
65 Enum
.ContentType
.TextPlain
,
66 Enum
.ContentType
.ApplicationJson
,
74 * Resolve relative and empty paths in url
75 * @param {string} p path
78 const pathNorm
= path
.normalize(p
); // This isn't perfectly correct, but it's easy...
79 return this._stripPrefix(pathNorm
);
84 * Remove a leading portion of url path
85 * N.B. This method gets obliterated if there is no prefix defined at construction
86 * @param {string} p path
89 if (p
.startsWith(this.proxyPrefix
)) {
90 return p
.slice(this.proxyPrefix
.length
);
97 * Returns the path part, and querystring object, from a request url.
101 const [ p
, qs
] = common
.splitFirst(url
, '?');
103 pathPart: this._normalizePath(p
),
104 queryParams: this.querystring
.parse(qs
),
110 * Insert a new path handler
111 * @param {string} method
112 * @param {string} urlPath
113 * @param {fn} handler
115 on(method
, urlPath
, handler
, ...handlerArgs
) {
116 this.router
.on(method
, urlPath
, handler
, handlerArgs
);
121 * Common header tagging for all requests.
122 * Add our own identifier, and persist any external transit identifiers.
123 * Sets requestId on ctx to a new uuid.
124 * If X-Request-Id or X-Correlation-Id exist on incoming headers, sets them
125 * on outgoing headers and sets on ctx.
126 * @param {http.ClientRequest} req
127 * @param {http.ServerResponse} res
128 * @param {object} ctx
130 static tagContext(req
, res
, ctx
) {
131 const requestId
= common
.requestId();
132 ctx
.requestId
= requestId
;
133 res
.setHeader(Enum
.Header
.RequestId
, requestId
);
134 [Enum
.Header
.XRequestId
, Enum
.Header
.XCorrelationId
].forEach((h
) => {
135 const v
= req
.getHeader(h
);
137 ctx
[h
.replace(/-/g
, '')] = v
;
147 * @param {http.ClientRequest} req
150 // TODO: RFC7239 Forwarded support
151 const address
= (this.trustProxy
&& req
?.getHeader(Enum
.Header
.XForwardedFor
)) ||
152 (this.trustProxy
&& req
?.getHeader(Enum
.Header
.XRealIP
)) ||
153 (req
?.connection
?.remoteAddress
) ||
155 return address
.split(/\s*,\s*/u)[0];
161 * @param {http.ClientRequest} req
164 // TODO: RFC7239 Forwarded support
165 const protocol
= (this.trustProxy
&& req
?.getHeader(Enum
.Header
.XForwardedProto
)) ||
166 ((req
?.connection
?.encrypted
) ? 'https' : 'http');
167 return protocol
.split(/\s*,\s*/u)[0];
172 * Sets ctx.clientAddress and ctx.clientProtocol.
173 * @param {http.ClientRequest} req
174 * @param {http.ServerResponse} res
175 * @param {object} ctx
177 clientAddressContext(req
, res
, ctx
) {
178 ctx
.clientAddress
= this._getAddress(req
);
179 ctx
.clientProtocol
= this._getProtocol(req
);
184 * Sets ctx.cookie from Cookie header.
185 * @param {http.ClientRequest} req
186 * @param {http.ServerResponse} res
187 * @param {object} ctx
189 static ingestCookie(req
, res
, ctx
) {
191 req
.getHeader(Enum
.Header
.Cookie
)?.split(cookieSplitRE
).forEach((cookie
) => {
192 const [ name
, value
] = common
.splitFirst(cookie
, '=', null).map((x
) => {
194 return decodeURIComponent(x
.trim());
199 if (name
&& !(name
in ctx
.cookie
)) {
200 const isQuoted
= value
?.startsWith('"') && value
.endsWith('"');
201 ctx
.cookie
[name
] = isQuoted
? value
.slice(1, -1) : value
; // eslint-disable-line security/detect-object-injection
208 * Called before every request handler.
209 * Sets tracking identifiers and client information on ctx.
210 * @param {http.ClientRequest} req
211 * @param {http.ServerResponse} res
212 * @param {object} ctx
214 async
preHandler(req
, res
, ctx
) {
215 this.constructor.tagContext(req
, res
, ctx
);
216 this.clientAddressContext(req
, res
, ctx
);
217 this.constructor.ingestCookie(req
, res
, ctx
);
222 * Helper for collecting chunks as array of buffers.
223 * @param {Buffer[]} chunks
224 * @param {string|Buffer} chunk
225 * @param {string} encoding
227 static pushBufChunk(chunks
, chunk
, encoding
= 'utf8') {
229 if (typeof chunk
=== 'string') {
230 chunk
= Buffer
.from(chunk
, encoding
);
238 * Sets ctx.responseBody and calls handler upon res.end().
239 * @param {http.ClientRequest} req
240 * @param {http.ServerResponse} res
241 * @param {object} ctx
242 * @param {*} handler fn(req, res, ctx)
244 static setEndBodyHandler(req
, res
, ctx
, handler
) {
245 const origWrite
= res
.write
.bind(res
);
246 const origEnd
= res
.end
.bind(res
);
248 res
.write = function (chunk
, encoding
, ...rest
) {
249 Dingus
.pushBufChunk(chunks
, chunk
, encoding
);
250 return origWrite(chunk
, encoding
, ...rest
);
252 res
.end = function (data
, encoding
, ...rest
) {
253 Dingus
.pushBufChunk(chunks
, data
, encoding
);
254 ctx
.responseBody
= Buffer
.concat(chunks
);
255 handler(req
, res
, ctx
);
256 return origEnd(data
, encoding
, ...rest
);
262 * Intercept writes for head requests, do not send to client,
263 * but send length, and make body available in context.
264 * N.B. If persisted, ctx.responseBody will be a raw buffer, be aware when logging.
265 * @param {http.ClientRequest} req
266 * @param {http.ServerResponse} res
267 * @param {object} ctx
268 * @param {Boolean} persistResponseBody
270 static setHeadHandler(req
, res
, ctx
, persistResponseBody
= false) {
271 if (req
.method
=== 'HEAD') {
272 const origEnd
= res
.end
.bind(res
);
274 res
.write = function (chunk
, encoding
) {
275 Dingus
.pushBufChunk(chunks
, chunk
, encoding
);
276 // No call to original res.write.
278 res
.end = function (data
, encoding
, ...rest
) {
279 Dingus
.pushBufChunk(chunks
, data
, encoding
);
280 const responseBody
= Buffer
.concat(chunks
);
281 res
.setHeader(Enum
.Header
.ContentLength
, Buffer
.byteLength(responseBody
));
282 if (persistResponseBody
) {
283 ctx
.responseBody
= responseBody
;
285 return origEnd(undefined, encoding
, ...rest
);
292 * Resolve the handler to invoke for a request.
293 * @param {http.ClientRequest} req
294 * @param {http.ServerResponse} res
295 * @param {object} ctx
298 _determineHandler(req
, res
, ctx
) {
299 const _scope
= _fileScope('_determineHandler');
301 const { pathPart
, queryParams
} = this._splitUrl(req
.url
);
302 ctx
.queryParams
= queryParams
;
304 let handler
, handlerArgs
= [];
306 ({ handler
, handlerArgs
} = this.router
.lookup(req
.method
, pathPart
, ctx
));
308 if (e
instanceof URIError
) {
309 handler
= this.handlerBadRequest
.bind(this);
310 } else if (e
instanceof RouterNoPathError
) {
311 handler
= this.handlerNotFound
.bind(this);
312 } else if (e
instanceof RouterNoMethodError
) {
313 if (this.intrinsicHeadMethod
&& req
.method
=== 'HEAD') {
314 ({ handler
, handlerArgs
} = this._determineHeadHandler(req
, res
, ctx
, pathPart
));
316 handler
= this.handlerMethodNotAllowed
.bind(this);
319 this.logger
.error(_scope
, 'unexpected error', { error: e
});
320 handler
= this.handlerInternalServerError
.bind(this);
323 return { handler
, handlerArgs
};
328 * For intrinsic HEAD requests, resolve the handler to invoke.
329 * @param {http.ClientRequest} req
330 * @param {http.ServerResponse} res
331 * @param {object} ctx
332 * @param {string} pathPart
335 _determineHeadHandler(req
, res
, ctx
, pathPart
) {
336 const _scope
= _fileScope('_determineHeadHandler');
337 let handler
, handlerArgs
= [];
339 ({ handler
, handlerArgs
} = this.router
.lookup('GET', pathPart
, ctx
));
340 Dingus
.setHeadHandler(req
, res
, ctx
, this.intrinsicHeadPersistBody
);
342 if (e
instanceof RouterNoMethodError
) {
343 handler
= this.handlerMethodNotAllowed
.bind(this);
345 this.logger
.error(_scope
, 'unexpected error', { error: e
});
346 handler
= this.handlerInternalServerError
.bind(this);
349 return { handler
, handlerArgs
};
354 * Dispatch the handler for a request
355 * @param {http.ClientRequest} req
356 * @param {http.ServerResponse} res
357 * @param {object} ctx
359 async
dispatch(req
, res
, ctx
= {}) {
360 const { handler
, handlerArgs
} = this._determineHandler(req
, res
, ctx
);
362 await
this.preHandler(req
, res
, ctx
);
363 return await
handler(req
, res
, ctx
, ...handlerArgs
);
366 this.sendErrorResponse(e
, req
, res
, ctx
);
372 * Return normalized type, without any parameters.
373 * @param {http.ClientRequest} req
376 static getRequestContentType(req
) {
377 const contentType
= req
.getHeader(Enum
.Header
.ContentType
);
378 return (contentType
|| '').split(';')[0].trim().toLowerCase();
383 * Parse rawBody as contentType into ctx.parsedBody.
384 * @param {string} contentType
385 * @param {object} ctx
386 * @param {string|buffer} rawBody
388 parseBody(contentType
, ctx
, rawBody
) {
389 const _scope
= _fileScope('parseBody');
391 switch (contentType
) {
392 case Enum
.ContentType
.ApplicationForm:
393 ctx
.parsedBody
= this.querystring
.parse(rawBody
);
396 case Enum
.ContentType
.ApplicationJson:
398 ctx
.parsedBody
= JSON
.parse(rawBody
);
400 this.logger
.debug(_scope
, 'JSON parse failed', { requestId: ctx
.requestId
, error: e
});
401 throw new ResponseError(Enum
.ErrorResponse
.BadRequest
, e
.message
);
406 this.logger
.debug(_scope
, 'unhandled content-type', { requestId: ctx
.requestId
, contentType
});
407 throw new ResponseError(Enum
.ErrorResponse
.UnsupportedMediaType
);
413 * Return all body data from a request.
414 * @param {http.ClientRequest} req
415 * @param {Number=} maximumBodySize
416 * @param {Boolean=} toString
418 async
bodyData(req
, maximumBodySize
, toString
= true) {
419 const _scope
= _fileScope('bodyData');
420 return new Promise((resolve
, reject
) => {
423 req
.on('data', (chunk
) => {
425 length
+= Buffer
.byteLength(chunk
);
426 if (maximumBodySize
&& length
> maximumBodySize
) {
427 this.logger
.debug(_scope
, 'body data exceeded limit', { length
, maximumBodySize
});
428 reject(new ResponseError(Enum
.ErrorResponse
.RequestEntityTooLarge
));
431 req
.on('end', () => {
432 const bodyBuffer
= Buffer
.concat(body
);
433 resolve(toString
? bodyBuffer
.toString() : bodyBuffer
);
435 req
.on('error', (e
) => {
436 this.logger
.error(_scope
, 'failed', { error: e
});
444 * Read and parse request body data.
445 * Sets ctx.parsedBody, and optionally ctx.rawBody.
446 * @param {http.ClientRequest} req
447 * @param {http.ServerResponse} res
448 * @param {object} ctx
450 * @param {Boolean} .parseEmptyBody
451 * @param {Boolean} .persistRawBody
453 async
ingestBody(req
, res
, ctx
, { parseEmptyBody
= true, persistRawBody
= false, maximumBodySize
} = {}) {
454 const rawBody
= await
this.bodyData(req
, maximumBodySize
);
455 if (persistRawBody
) {
456 ctx
.rawBody
= rawBody
;
458 if (rawBody
|| parseEmptyBody
) {
459 const contentType
= Dingus
.getRequestContentType(req
);
460 this.parseBody(contentType
, ctx
, rawBody
);
466 * Return the best matching response type.
467 * @param {string[]} responseTypes
468 * @param {http.ClientRequest} req
470 static getResponseContentType(responseTypes
, req
) {
471 const acceptHeader
= req
.getHeader(Enum
.Header
.Accept
);
472 return ContentNegotiation
.accept(responseTypes
, acceptHeader
);
477 * Returns a list of the most-preferred content encodings for the response.
478 * @param {string[]} responseEncodings
479 * @param {http.ClientRequest} req
481 static getResponseEncoding(responseEncodings
, req
) {
482 const acceptEncodingHeader
= req
.getHeader(Enum
.Header
.AcceptEncoding
);
483 return ContentNegotiation
.preferred(responseEncodings
, acceptEncodingHeader
);
488 * Set the best content type for the response.
489 * Sets ctx.responseType, and Content-Type header.
490 * @param {string[]} responseTypes default first
491 * @param {http.ClientRequest} req
492 * @param {http.ServerResponse} res
493 * @param {object} ctx
495 setResponseType(responseTypes
, req
, res
, ctx
) {
496 const _scope
= _fileScope('setResponseType');
497 ctx
.responseType
= Dingus
.getResponseContentType(responseTypes
, req
);
498 if (!ctx
.responseType
) {
499 if (this.strictAccept
) {
500 this.logger
.debug(_scope
, 'unhandled strict accept', { requestId: req
.requestId
});
501 throw new ResponseError(Enum
.ErrorResponse
.NotAcceptable
);
503 ctx
.responseType
= responseTypes
[0];
506 res
.setHeader(Enum
.Header
.ContentType
, ctx
.responseType
);
511 * Inserts an encoding into Content-Encoding header.
512 * @param {http.ServerResponse} res
513 * @param {string} encoding
515 static addEncodingHeader(res
, encoding
) {
516 const existingEncodings
= res
.getHeader(Enum
.Header
.ContentEncoding
);
517 if (existingEncodings
) {
518 encoding
= `${encoding}, ${existingEncodings}`;
520 res
.setHeader(Enum
.Header
.ContentEncoding
, encoding
);
525 * Attempt to fetch both data and metadata for a file.
526 * @param {string} filePath
528 async
_readFileInfo(filePath
) {
529 const _scope
= _fileScope('_readFileInfo');
532 // eslint-disable-next-line security/detect-non-literal-fs-filename
533 const stat
= fsPromises
.stat(filePath
);
534 // eslint-disable-next-line security/detect-non-literal-fs-filename
535 const data
= fsPromises
.readFile(filePath
);
536 result
= await Promise
.all([stat
, data
]);
538 if (['ENOENT', 'EACCES', 'EISDIR', 'ENAMETOOLONG', 'EINVAL'].includes(e
.code
)) {
541 this.logger
.error(_scope
, 'fs error', { error: e
, filePath
});
549 * Potentially add additional headers from static file meta-file.
550 * @param {http.ServerResponse} res
551 * @param {string} directory
552 * @param {string} fileName - already normalized and filtered
554 async
_serveFileMetaHeaders(res
, directory
, fileName
) {
555 const _scope
= _fileScope('_serveFileMetaHeaders');
558 const metaPrefix
= '.';
559 const metaSuffix
= '.meta';
560 const metaFileName
= `${metaPrefix}${fileName}${metaSuffix}`;
561 const metaFilePath
= path
.join(directory
, metaFileName
);
563 const [stat
, data
] = await
this._readFileInfo(metaFilePath
);
568 const lineBreakRE
= /\r\n|\n|\r/;
569 const lines
= data
.toString().split(lineBreakRE
);
570 common
.unfoldHeaderLines(lines
);
572 const headerParseRE
= /^(?<name
>[^:]+): +(?<value
>.*)$/;
573 lines
.forEach((line
) => {
575 const result
= headerParseRE
.exec(line
);
576 const { groups: header
} = result
;
577 res
.setHeader(header
.name
, header
.value
);
586 * Serve a file from a directory, with rudimentary cache awareness.
587 * This will also serve pre-encoded variations if available and requested.
588 * @param {http.ClientRequest} req
589 * @param {http.ServerResponse} res
590 * @param {object} ctx
591 * @param {string} directory
592 * @param {string} fileName
594 async
serveFile(req
, res
, ctx
, directory
, fileName
) {
595 const _scope
= _fileScope('serveFile');
596 this.logger
.debug(_scope
, 'called', { req
, ctx
});
598 // Require a directory field.
600 this.logger
.debug(_scope
, 'rejected unset directory', { fileName
});
601 return this.handlerNotFound(req
, res
, ctx
);
604 // Normalize the supplied path, as encoded path-navigation may have been (maliciously) present.
605 fileName
= path
.normalize(fileName
);
607 // We will not deal with any subdirs, nor any dot-files.
608 // (Note that we could not deal with subdirs even if we wanted, due to simple router matching scheme.)
609 if (fileName
.indexOf(path
.sep
) >= 0
610 || fileName
.startsWith('.')) {
611 this.logger
.debug(_scope
, 'rejected filename', { fileName
});
612 return this.handlerNotFound(req
, res
, ctx
);
615 const filePath
= path
.join(directory
, fileName
);
617 // File must exist, before any alternate static encodings will be considered.
618 let [stat
, data
] = await
this._readFileInfo(filePath
);
620 return this.handlerNotFound(req
, res
, ctx
);
623 // If encodings were requested, check for static versions to serve.
624 // Update stat and data if matching version is found.
625 ctx
.availableEncodings
= Dingus
.getResponseEncoding(Object
.values(Enum
.EncodingType
), req
);
626 if (ctx
.availableEncodings
.length
=== 0) {
627 // Identity encoding was specifically denied, and nothing else available.
628 this.logger
.debug(_scope
, 'no suitable encodings', { ctx
});
629 return this.handlerMethodNotAllowed(req
, res
, ctx
);
631 for (const encoding
of ctx
.availableEncodings
) {
632 if (encoding
=== Enum
.EncodingType
.Identity
) {
635 const suffix
= Enum
.EncodingTypeSuffix
[encoding
];
637 this.logger
.error(_scope
, 'supported encoding missing mapped suffix', { ctx
, encoding
});
640 const encodedFilePath
= `${filePath}${suffix}`;
641 const [ encodedStat
, encodedData
] = await
this._readFileInfo(encodedFilePath
);
643 ([ stat
, data
] = [ encodedStat
, encodedData
]);
644 ctx
.selectedEncoding
= encoding
;
645 Dingus
.addEncodingHeader(res
, encoding
);
646 res
.setHeader(Enum
.Header
.Vary
, Enum
.Header
.AcceptEncoding
);
647 this.logger
.debug(_scope
, 'serving encoded version', { ctx
, encodedFilePath
});
652 const lastModifiedDate
= new Date(stat
.mtimeMs
);
653 res
.setHeader(Enum
.Header
.LastModified
, lastModifiedDate
.toGMTString());
655 const eTag
= common
.generateETag(filePath
, stat
, data
);
656 res
.setHeader(Enum
.Header
.ETag
, eTag
);
658 if (common
.isClientCached(req
, stat
.mtimeMs
, eTag
)) {
659 this.logger
.debug(_scope
, 'client cached file', { filePath
});
660 res
.statusCode
= 304; // Not Modified
665 // Set the type based on extension of un-encoded filename.
666 const ext
= path
.extname(filePath
).slice(1); // Drop the dot
667 const contentType
= extensionToMime(ext
);
668 res
.setHeader(Enum
.Header
.ContentType
, contentType
);
670 // We presume static files are relatively cacheable.
671 res
.setHeader(Enum
.Header
.CacheControl
, 'public');
673 if (this.staticMetadata
) {
674 ctx
.metaHeaders
= await
this._serveFileMetaHeaders(res
, directory
, fileName
);
677 this.logger
.debug(_scope
, 'serving file', { filePath
, contentType
});
683 * Return a content-type appropriate rendering of an errorResponse object.
684 * @param {string} type content-type of response
685 * @param {object} err either an Error object, or an error response
686 * @param {number} err.statusCode
687 * @param {string} err.errorMessage
688 * @param {string|string[]} err.details
690 // eslint-disable-next-line class-methods-use-this
691 renderError(contentType
, err
) {
692 switch (contentType
) {
693 case Enum
.ContentType
.ApplicationJson:
694 return JSON
.stringify(err
);
696 case Enum
.ContentType
.TextHTML:
697 return Template
.errorHTML(err
);
699 case Enum
.ContentType
.TextPlain:
701 return [err
.errorMessage
, err
.details
].join('\r\n');
707 * Send an error response. Terminal.
708 * Logs any non-error-response errors as such.
709 * @param {object} err either an Error object, or an error response
710 * @param {http.ClientRequest} req
711 * @param {http.ServerResponse} res
712 * @param {object} ctx
714 sendErrorResponse(err
, req
, res
, ctx
) {
715 const _scope
= _fileScope('sendErrorResponse');
718 // Default to a content type if one is not yet present
719 if (!res
.hasHeader(Enum
.Header
.ContentType
)) {
720 res
.setHeader(Enum
.Header
.ContentType
, Enum
.ContentType
.TextPlain
);
723 if (err
?.statusCode
) {
724 res
.statusCode
= err
.statusCode
;
725 body
= this.renderError(res
.getHeader(Enum
.Header
.ContentType
), err
);
726 this.logger
.debug(_scope
, 'handler error', { err
, req
, res
, ctx
});
728 res
.statusCode
= 500;
729 body
= this.renderError(res
.getHeader(Enum
.Header
.ContentType
), Enum
.ErrorResponse
.InternalServerError
);
730 this.logger
.error(_scope
, 'handler exception', { err
, req
, res
, ctx
});
737 * @param {http.ClientRequest} req
738 * @param {http.ServerResponse} res
739 * @param {object} ctx
740 * @param {String} file - override ctx.params.file
742 async
handlerGetStaticFile(req
, res
, ctx
, file
) {
743 Dingus
.setHeadHandler(req
, res
, ctx
);
745 // Set a default response type to handle any errors; will be re-set to serve actual static content type.
746 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
748 await
this.serveFile(req
, res
, ctx
, this.staticPath
, file
|| ctx
.params
.file
);
753 * @param {http.ClientRequest} req
754 * @param {http.ServerResponse} res
755 * @param {Object} ctx
756 * @param {String} newPath
757 * @param {Number} statusCode
759 async
handlerRedirect(req
, res
, ctx
, newPath
, statusCode
= 307) {
760 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
761 res
.setHeader(Enum
.Header
.Location
, newPath
);
762 res
.statusCode
= statusCode
;
768 * @param {http.ClientRequest} req
769 * @param {http.ServerResponse} res
770 * @param {object} ctx
772 async
handlerMethodNotAllowed(req
, res
, ctx
) {
773 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
774 throw new ResponseError(Enum
.ErrorResponse
.MethodNotAllowed
);
779 * @param {http.ClientRequest} req
780 * @param {http.ServerResponse} res
781 * @param {object} ctx
783 async
handlerNotFound(req
, res
, ctx
) {
784 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
785 throw new ResponseError(Enum
.ErrorResponse
.NotFound
);
790 * @param {http.ClientRequest} req
791 * @param {http.ServerResponse} res
792 * @param {object} ctx
794 async
handlerBadRequest(req
, res
, ctx
) {
795 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
796 throw new ResponseError(Enum
.ErrorResponse
.BadRequest
);
801 * @param {http.ClientRequest} req
802 * @param {http.ServerResponse} res
803 * @param {object} ctx
805 async
handlerInternalServerError(req
, res
, ctx
) {
806 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
807 throw new ResponseError(Enum
.ErrorResponse
.InternalServerError
);
812 module
.exports
= Dingus
;