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,
40 * @param {Object} logger object which implements logging methods
41 * @param {Object} options
42 * @param {Boolean} options.ignoreTrailingSlash requests for '/foo/' will match a '/foo' route
43 * @param {string} options.proxyPrefix leading part of url path to strip
44 * @param {Boolean} options.strictAccept whether to error on unsupported Accept type
45 * @param {string} options.selfBaseUrl for constructing links
46 * @param {Boolean} options.staticMetadata serve static headers with static files
47 * @param {Boolean} options.trustProxy trust some header data to be provided by proxy
48 * @param {Boolean} options.intrinsicHeadMethod handle HEAD requests automatically if not specified as a route method
49 * @param {Boolean} options.intrinsicHeadPersistBody include un-sent body on ctx for automatic HEAD requests
50 * @param {Object} options.querystring alternate qs parser to use
52 constructor(logger
= console
, options
= {}) {
53 common
.setOptions(this, defaultOptions
, options
);
55 this.router
= new Router(options
);
57 if (!this.proxyPrefix
) {
58 this._stripPrefix
= (p
) => p
;
61 this.responseTypes
= [
62 Enum
.ContentType
.TextHTML
,
63 Enum
.ContentType
.TextPlain
,
64 Enum
.ContentType
.ApplicationJson
,
72 * Resolve relative and empty paths in url
73 * @param {string} p path
76 const pathNorm
= path
.normalize(p
); // This isn't perfectly correct, but it's easy...
77 return this._stripPrefix(pathNorm
);
82 * Remove a leading portion of url path
83 * N.B. This method gets obliterated if there is no prefix defined at construction
84 * @param {string} p path
87 if (p
.startsWith(this.proxyPrefix
)) {
88 return p
.slice(this.proxyPrefix
.length
);
95 * Returns the path part, and querystring object, from a request url.
99 const [ p
, qs
] = common
.splitFirst(url
, '?');
101 pathPart: this._normalizePath(p
),
102 queryParams: this.querystring
.parse(qs
),
108 * Insert a new path handler
109 * @param {string} method
110 * @param {string} urlPath
111 * @param {fn} handler
113 on(method
, urlPath
, handler
, ...handlerArgs
) {
114 this.router
.on(method
, urlPath
, handler
, handlerArgs
);
119 * Common header tagging for all requests.
120 * Add our own identifier, and persist any external transit identifiers.
121 * Sets requestId on ctx to a new uuid.
122 * If X-Request-Id or X-Correlation-Id exist on incoming headers, sets them
123 * on outgoing headers and sets on ctx.
124 * @param {http.ClientRequest} req
125 * @param {http.ServerResponse} res
126 * @param {object} ctx
128 static tagContext(req
, res
, ctx
) {
129 const requestId
= common
.requestId();
130 ctx
.requestId
= requestId
;
131 res
.setHeader(Enum
.Header
.RequestId
, requestId
);
132 [Enum
.Header
.XRequestId
, Enum
.Header
.XCorrelationId
].forEach((h
) => {
133 const v
= req
.getHeader(h
);
135 ctx
[h
.replace(/-/g
, '')] = v
;
145 * @param {http.ClientRequest} req
148 // TODO: RFC7239 Forwarded support
149 const address
= (this.trustProxy
&& req
?.getHeader(Enum
.Header
.XForwardedFor
)) ||
150 (this.trustProxy
&& req
?.getHeader(Enum
.Header
.XRealIP
)) ||
151 (req
?.connection
?.remoteAddress
) ||
153 return address
.split(/\s*,\s*/u)[0];
159 * @param {http.ClientRequest} req
162 // TODO: RFC7239 Forwarded support
163 const protocol
= (this.trustProxy
&& req
?.getHeader(Enum
.Header
.XForwardedProto
)) ||
164 ((req
?.connection
?.encrypted
) ? 'https' : 'http');
165 return protocol
.split(/\s*,\s*/u)[0];
170 * Sets ctx.clientAddress and ctx.clientProtocol.
171 * @param {http.ClientRequest} req
172 * @param {http.ServerResponse} res
173 * @param {object} ctx
175 clientAddressContext(req
, res
, ctx
) {
176 ctx
.clientAddress
= this._getAddress(req
);
177 ctx
.clientProtocol
= this._getProtocol(req
);
182 * Called before every request handler.
183 * Sets tracking identifiers and client information on ctx.
184 * @param {http.ClientRequest} req
185 * @param {http.ServerResponse} res
186 * @param {object} ctx
188 async
preHandler(req
, res
, ctx
) {
189 Dingus
.tagContext(req
, res
, ctx
);
190 this.clientAddressContext(req
, res
, ctx
);
195 * Helper for collecting chunks as array of buffers.
196 * @param {Buffer[]} chunks
197 * @param {string|Buffer} chunk
198 * @param {string} encoding
200 static pushBufChunk(chunks
, chunk
, encoding
= 'utf8') {
202 if (typeof chunk
=== 'string') {
203 chunk
= Buffer
.from(chunk
, encoding
);
211 * Sets ctx.responseBody and calls handler upon res.end().
212 * @param {http.ClientRequest} req
213 * @param {http.ServerResponse} res
214 * @param {object} ctx
215 * @param {*} handler fn(req, res, ctx)
217 static setEndBodyHandler(req
, res
, ctx
, handler
) {
218 const origWrite
= res
.write
.bind(res
);
219 const origEnd
= res
.end
.bind(res
);
221 res
.write = function (chunk
, encoding
, ...rest
) {
222 Dingus
.pushBufChunk(chunks
, chunk
, encoding
);
223 return origWrite(chunk
, encoding
, ...rest
);
225 res
.end = function (data
, encoding
, ...rest
) {
226 Dingus
.pushBufChunk(chunks
, data
, encoding
);
227 ctx
.responseBody
= Buffer
.concat(chunks
);
228 handler(req
, res
, ctx
);
229 return origEnd(data
, encoding
, ...rest
);
235 * Intercept writes for head requests, do not send to client,
236 * but send length, and make body available in context.
237 * N.B. If persisted, ctx.responseBody will be a raw buffer, be aware when logging.
238 * @param {http.ClientRequest} req
239 * @param {http.ServerResponse} res
240 * @param {object} ctx
241 * @param {Boolean} persistResponseBody
243 static setHeadHandler(req
, res
, ctx
, persistResponseBody
= false) {
244 if (req
.method
=== 'HEAD') {
245 const origEnd
= res
.end
.bind(res
);
247 res
.write = function (chunk
, encoding
) {
248 Dingus
.pushBufChunk(chunks
, chunk
, encoding
);
249 // No call to original res.write.
251 res
.end = function (data
, encoding
, ...rest
) {
252 Dingus
.pushBufChunk(chunks
, data
, encoding
);
253 const responseBody
= Buffer
.concat(chunks
);
254 res
.setHeader(Enum
.Header
.ContentLength
, Buffer
.byteLength(responseBody
));
255 if (persistResponseBody
) {
256 ctx
.responseBody
= responseBody
;
258 return origEnd(undefined, encoding
, ...rest
);
265 * Resolve the handler to invoke for a request.
266 * @param {http.ClientRequest} req
267 * @param {http.ServerResponse} res
268 * @param {object} ctx
271 _determineHandler(req
, res
, ctx
) {
272 const _scope
= _fileScope('_determineHandler');
274 const { pathPart
, queryParams
} = this._splitUrl(req
.url
);
275 ctx
.queryParams
= queryParams
;
277 let handler
, handlerArgs
= [];
279 ({ handler
, handlerArgs
} = this.router
.lookup(req
.method
, pathPart
, ctx
));
281 if (e
instanceof URIError
) {
282 handler
= this.handlerBadRequest
.bind(this);
283 } else if (e
instanceof RouterNoPathError
) {
284 handler
= this.handlerNotFound
.bind(this);
285 } else if (e
instanceof RouterNoMethodError
) {
286 if (this.intrinsicHeadMethod
&& req
.method
=== 'HEAD') {
287 ({ handler
, handlerArgs
} = this._determineHeadHandler(req
, res
, ctx
, pathPart
));
289 handler
= this.handlerMethodNotAllowed
.bind(this);
292 this.logger
.error(_scope
, 'unexpected error', { error: e
});
293 handler
= this.handlerInternalServerError
.bind(this);
296 return { handler
, handlerArgs
};
301 * For intrinsic HEAD requests, resolve the handler to invoke.
302 * @param {http.ClientRequest} req
303 * @param {http.ServerResponse} res
304 * @param {object} ctx
305 * @param {string} pathPart
308 _determineHeadHandler(req
, res
, ctx
, pathPart
) {
309 const _scope
= _fileScope('_determineHeadHandler');
310 let handler
, handlerArgs
= [];
312 ({ handler
, handlerArgs
} = this.router
.lookup('GET', pathPart
, ctx
));
313 Dingus
.setHeadHandler(req
, res
, ctx
, this.intrinsicHeadPersistBody
);
315 if (e
instanceof RouterNoMethodError
) {
316 handler
= this.handlerMethodNotAllowed
.bind(this);
318 this.logger
.error(_scope
, 'unexpected error', { error: e
});
319 handler
= this.handlerInternalServerError
.bind(this);
322 return { handler
, handlerArgs
};
327 * Dispatch the handler for a request
328 * @param {http.ClientRequest} req
329 * @param {http.ServerResponse} res
330 * @param {object} ctx
332 async
dispatch(req
, res
, ctx
= {}) {
333 const { handler
, handlerArgs
} = this._determineHandler(req
, res
, ctx
);
335 await
this.preHandler(req
, res
, ctx
);
336 return await
handler(req
, res
, ctx
, ...handlerArgs
);
339 this.sendErrorResponse(e
, req
, res
, ctx
);
345 * Return normalized type, without any parameters.
346 * @param {http.ClientRequest} req
349 static getRequestContentType(req
) {
350 const contentType
= req
.getHeader(Enum
.Header
.ContentType
);
351 return (contentType
|| '').split(';')[0].trim().toLowerCase();
356 * Parse rawBody as contentType into ctx.parsedBody.
357 * @param {string} contentType
358 * @param {object} ctx
359 * @param {string|buffer} rawBody
361 parseBody(contentType
, ctx
, rawBody
) {
362 const _scope
= _fileScope('parseBody');
364 switch (contentType
) {
365 case Enum
.ContentType
.ApplicationForm:
366 ctx
.parsedBody
= this.querystring
.parse(rawBody
);
369 case Enum
.ContentType
.ApplicationJson:
371 ctx
.parsedBody
= JSON
.parse(rawBody
);
373 this.logger
.debug(_scope
, 'JSON parse failed', { requestId: ctx
.requestId
, error: e
});
374 throw new ResponseError(Enum
.ErrorResponse
.BadRequest
, e
.message
);
379 this.logger
.debug(_scope
, 'unhandled content-type', { requestId: ctx
.requestId
, contentType
});
380 throw new ResponseError(Enum
.ErrorResponse
.UnsupportedMediaType
);
386 * Return all body data from a request.
387 * @param {http.ClientRequest} req
388 * @param {Number=} maximumBodySize
389 * @param {Boolean=} toString
391 async
bodyData(req
, maximumBodySize
, toString
= true) {
392 const _scope
= _fileScope('bodyData');
393 return new Promise((resolve
, reject
) => {
396 req
.on('data', (chunk
) => {
398 length
+= Buffer
.byteLength(chunk
);
399 if (maximumBodySize
&& length
> maximumBodySize
) {
400 this.logger
.debug(_scope
, 'body data exceeded limit', { length
, maximumBodySize
});
401 reject(new ResponseError(Enum
.ErrorResponse
.RequestEntityTooLarge
));
404 req
.on('end', () => {
405 const bodyBuffer
= Buffer
.concat(body
);
406 resolve(toString
? bodyBuffer
.toString() : bodyBuffer
);
408 req
.on('error', (e
) => {
409 this.logger
.error(_scope
, 'failed', { error: e
});
417 * Read and parse request body data.
418 * Sets ctx.parsedBody, and optionally ctx.rawBody.
419 * @param {http.ClientRequest} req
420 * @param {http.ServerResponse} res
421 * @param {object} ctx
423 * @param {Boolean} .parseEmptyBody
424 * @param {Boolean} .persistRawBody
426 async
ingestBody(req
, res
, ctx
, { parseEmptyBody
= true, persistRawBody
= false, maximumBodySize
} = {}) {
427 const rawBody
= await
this.bodyData(req
, maximumBodySize
);
428 if (persistRawBody
) {
429 ctx
.rawBody
= rawBody
;
431 if (rawBody
|| parseEmptyBody
) {
432 const contentType
= Dingus
.getRequestContentType(req
);
433 this.parseBody(contentType
, ctx
, rawBody
);
439 * Return the best matching response type.
440 * @param {string[]} responseTypes
441 * @param {http.ClientRequest} req
443 static getResponseContentType(responseTypes
, req
) {
444 const acceptHeader
= req
.getHeader(Enum
.Header
.Accept
);
445 return ContentNegotiation
.accept(responseTypes
, acceptHeader
);
450 * Returns a list of the most-preferred content encodings for the response.
451 * @param {string[]} responseEncodings
452 * @param {http.ClientRequest} req
454 static getResponseEncoding(responseEncodings
, req
) {
455 const acceptEncodingHeader
= req
.getHeader(Enum
.Header
.AcceptEncoding
);
456 return ContentNegotiation
.preferred(responseEncodings
, acceptEncodingHeader
);
461 * Set the best content type for the response.
462 * Sets ctx.responseType, and Content-Type header.
463 * @param {string[]} responseTypes default first
464 * @param {http.ClientRequest} req
465 * @param {http.ServerResponse} res
466 * @param {object} ctx
468 setResponseType(responseTypes
, req
, res
, ctx
) {
469 const _scope
= _fileScope('setResponseType');
470 ctx
.responseType
= Dingus
.getResponseContentType(responseTypes
, req
);
471 if (!ctx
.responseType
) {
472 if (this.strictAccept
) {
473 this.logger
.debug(_scope
, 'unhandled strict accept', { requestId: req
.requestId
});
474 throw new ResponseError(Enum
.ErrorResponse
.NotAcceptable
);
476 ctx
.responseType
= responseTypes
[0];
479 res
.setHeader(Enum
.Header
.ContentType
, ctx
.responseType
);
484 * Inserts an encoding into Content-Encoding header.
485 * @param {http.ServerResponse} res
486 * @param {string} encoding
488 static addEncodingHeader(res
, encoding
) {
489 const existingEncodings
= res
.getHeader(Enum
.Header
.ContentEncoding
);
490 if (existingEncodings
) {
491 encoding
= `${encoding}, ${existingEncodings}`;
493 res
.setHeader(Enum
.Header
.ContentEncoding
, encoding
);
498 * Attempt to fetch both data and metadata for a file.
499 * @param {string} filePath
501 async
_readFileInfo(filePath
) {
502 const _scope
= _fileScope('_readFileInfo');
505 // eslint-disable-next-line security/detect-non-literal-fs-filename
506 const stat
= fsPromises
.stat(filePath
);
507 // eslint-disable-next-line security/detect-non-literal-fs-filename
508 const data
= fsPromises
.readFile(filePath
);
509 result
= await Promise
.all([stat
, data
]);
511 if (['ENOENT', 'EACCES', 'EISDIR', 'ENAMETOOLONG', 'EINVAL'].includes(e
.code
)) {
514 this.logger
.error(_scope
, 'fs error', { error: e
, filePath
});
522 * Potentially add additional headers from static file meta-file.
523 * @param {http.ServerResponse} res
524 * @param {string} directory
525 * @param {string} fileName - already normalized and filtered
527 async
_serveFileMetaHeaders(res
, directory
, fileName
) {
528 const _scope
= _fileScope('_serveFileMetaHeaders');
531 const metaPrefix
= '.';
532 const metaSuffix
= '.meta';
533 const metaFileName
= `${metaPrefix}${fileName}${metaSuffix}`;
534 const metaFilePath
= path
.join(directory
, metaFileName
);
536 const [stat
, data
] = await
this._readFileInfo(metaFilePath
);
541 const lineBreakRE
= /\r\n|\n|\r/;
542 const lines
= data
.toString().split(lineBreakRE
);
543 common
.unfoldHeaderLines(lines
);
545 const headerParseRE
= /^(?<name
>[^:]+): +(?<value
>.*)$/;
546 lines
.forEach((line
) => {
548 const result
= headerParseRE
.exec(line
);
549 const { groups: header
} = result
;
550 res
.setHeader(header
.name
, header
.value
);
559 * Serve a file from a directory, with rudimentary cache awareness.
560 * This will also serve pre-encoded variations if available and requested.
561 * @param {http.ClientRequest} req
562 * @param {http.ServerResponse} res
563 * @param {object} ctx
564 * @param {string} directory
565 * @param {string} fileName
567 async
serveFile(req
, res
, ctx
, directory
, fileName
) {
568 const _scope
= _fileScope('serveFile');
569 this.logger
.debug(_scope
, 'called', { req
, ctx
});
571 // Require a directory field.
573 this.logger
.debug(_scope
, 'rejected unset directory', { fileName
});
574 return this.handlerNotFound(req
, res
, ctx
);
577 // Normalize the supplied path, as encoded path-navigation may have been (maliciously) present.
578 fileName
= path
.normalize(fileName
);
580 // We will not deal with any subdirs, nor any dot-files.
581 // (Note that we could not deal with subdirs even if we wanted, due to simple router matching scheme.)
582 if (fileName
.indexOf(path
.sep
) >= 0
583 || fileName
.startsWith('.')) {
584 this.logger
.debug(_scope
, 'rejected filename', { fileName
});
585 return this.handlerNotFound(req
, res
, ctx
);
588 const filePath
= path
.join(directory
, fileName
);
590 // File must exist, before any alternate static encodings will be considered.
591 let [stat
, data
] = await
this._readFileInfo(filePath
);
593 return this.handlerNotFound(req
, res
, ctx
);
596 // If encodings were requested, check for static versions to serve.
597 // Update stat and data if matching version is found.
598 ctx
.availableEncodings
= Dingus
.getResponseEncoding(Object
.values(Enum
.EncodingType
), req
);
599 if (ctx
.availableEncodings
.length
=== 0) {
600 // Identity encoding was specifically denied, and nothing else available.
601 this.logger
.debug(_scope
, 'no suitable encodings', { ctx
});
602 return this.handlerMethodNotAllowed(req
, res
, ctx
);
604 for (const encoding
of ctx
.availableEncodings
) {
605 if (encoding
=== Enum
.EncodingType
.Identity
) {
608 const suffix
= Enum
.EncodingTypeSuffix
[encoding
];
610 this.logger
.error(_scope
, 'supported encoding missing mapped suffix', { ctx
, encoding
});
613 const encodedFilePath
= `${filePath}${suffix}`;
614 const [ encodedStat
, encodedData
] = await
this._readFileInfo(encodedFilePath
);
616 ([ stat
, data
] = [ encodedStat
, encodedData
]);
617 ctx
.selectedEncoding
= encoding
;
618 Dingus
.addEncodingHeader(res
, encoding
);
619 res
.setHeader(Enum
.Header
.Vary
, Enum
.Header
.AcceptEncoding
);
620 this.logger
.debug(_scope
, 'serving encoded version', { ctx
, encodedFilePath
});
625 const lastModifiedDate
= new Date(stat
.mtimeMs
);
626 res
.setHeader(Enum
.Header
.LastModified
, lastModifiedDate
.toGMTString());
628 const eTag
= common
.generateETag(filePath
, stat
, data
);
629 res
.setHeader(Enum
.Header
.ETag
, eTag
);
631 if (common
.isClientCached(req
, stat
.mtimeMs
, eTag
)) {
632 this.logger
.debug(_scope
, 'client cached file', { filePath
});
633 res
.statusCode
= 304; // Not Modified
638 // Set the type based on extension of un-encoded filename.
639 const ext
= path
.extname(filePath
).slice(1); // Drop the dot
640 const contentType
= extensionToMime(ext
);
641 res
.setHeader(Enum
.Header
.ContentType
, contentType
);
643 // We presume static files are relatively cacheable.
644 res
.setHeader(Enum
.Header
.CacheControl
, 'public');
646 if (this.staticMetadata
) {
647 ctx
.metaHeaders
= await
this._serveFileMetaHeaders(res
, directory
, fileName
);
650 this.logger
.debug(_scope
, 'serving file', { filePath
, contentType
});
656 * Return a content-type appropriate rendering of an errorResponse object.
657 * @param {string} type content-type of response
658 * @param {object} err either an Error object, or an error response
659 * @param {number} err.statusCode
660 * @param {string} err.errorMessage
661 * @param {string|string[]} err.details
663 // eslint-disable-next-line class-methods-use-this
664 renderError(contentType
, err
) {
665 switch (contentType
) {
666 case Enum
.ContentType
.ApplicationJson:
667 return JSON
.stringify(err
);
669 case Enum
.ContentType
.TextHTML:
670 return Template
.errorHTML(err
);
672 case Enum
.ContentType
.TextPlain:
674 return [err
.errorMessage
, err
.details
].join('\r\n');
680 * Send an error response. Terminal.
681 * Logs any non-error-response errors as such.
682 * @param {object} err either an Error object, or an error response
683 * @param {http.ClientRequest} req
684 * @param {http.ServerResponse} res
685 * @param {object} ctx
687 sendErrorResponse(err
, req
, res
, ctx
) {
688 const _scope
= _fileScope('sendErrorResponse');
691 // Default to a content type if one is not yet present
692 if (!res
.hasHeader(Enum
.Header
.ContentType
)) {
693 res
.setHeader(Enum
.Header
.ContentType
, Enum
.ContentType
.TextPlain
);
696 if (err
?.statusCode
) {
697 res
.statusCode
= err
.statusCode
;
698 body
= this.renderError(res
.getHeader(Enum
.Header
.ContentType
), err
);
699 this.logger
.debug(_scope
, 'handler error', { err
, req
, res
, ctx
});
701 res
.statusCode
= 500;
702 body
= this.renderError(res
.getHeader(Enum
.Header
.ContentType
), Enum
.ErrorResponse
.InternalServerError
);
703 this.logger
.error(_scope
, 'handler exception', { err
, req
, res
, ctx
});
710 * @param {http.ClientRequest} req
711 * @param {http.ServerResponse} res
712 * @param {object} ctx
713 * @param {String} file - override ctx.params.file
715 async
handlerGetStaticFile(req
, res
, ctx
, file
) {
716 Dingus
.setHeadHandler(req
, res
, ctx
);
718 // Set a default response type to handle any errors; will be re-set to serve actual static content type.
719 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
721 await
this.serveFile(req
, res
, ctx
, this.staticPath
, file
|| ctx
.params
.file
);
726 * @param {http.ClientRequest} req
727 * @param {http.ServerResponse} res
728 * @param {Object} ctx
729 * @param {String} newPath
730 * @param {Number} statusCode
732 async
handlerRedirect(req
, res
, ctx
, newPath
, statusCode
= 307) {
733 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
734 res
.setHeader(Enum
.Header
.Location
, newPath
);
735 res
.statusCode
= statusCode
;
741 * @param {http.ClientRequest} req
742 * @param {http.ServerResponse} res
743 * @param {object} ctx
745 async
handlerMethodNotAllowed(req
, res
, ctx
) {
746 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
747 throw new ResponseError(Enum
.ErrorResponse
.MethodNotAllowed
);
752 * @param {http.ClientRequest} req
753 * @param {http.ServerResponse} res
754 * @param {object} ctx
756 async
handlerNotFound(req
, res
, ctx
) {
757 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
758 throw new ResponseError(Enum
.ErrorResponse
.NotFound
);
763 * @param {http.ClientRequest} req
764 * @param {http.ServerResponse} res
765 * @param {object} ctx
767 async
handlerBadRequest(req
, res
, ctx
) {
768 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
769 throw new ResponseError(Enum
.ErrorResponse
.BadRequest
);
774 * @param {http.ClientRequest} req
775 * @param {http.ServerResponse} res
776 * @param {object} ctx
778 async
handlerInternalServerError(req
, res
, ctx
) {
779 this.setResponseType(this.responseTypes
, req
, res
, ctx
);
780 throw new ResponseError(Enum
.ErrorResponse
.InternalServerError
);
785 module
.exports
= Dingus
;