Spaces:
Sleeping
Sleeping
| ; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.ExpressAdapter = void 0; | |
| const common_1 = require("@nestjs/common"); | |
| const shared_utils_1 = require("@nestjs/common/utils/shared.utils"); | |
| const http_adapter_1 = require("@nestjs/core/adapters/http-adapter"); | |
| const router_method_factory_1 = require("@nestjs/core/helpers/router-method-factory"); | |
| const legacy_route_converter_1 = require("@nestjs/core/router/legacy-route-converter"); | |
| const cors = require("cors"); | |
| const express = require("express"); | |
| const http = require("http"); | |
| const https = require("https"); | |
| const path_to_regexp_1 = require("path-to-regexp"); | |
| const get_body_parser_options_util_1 = require("./utils/get-body-parser-options.util"); | |
| /** | |
| * @publicApi | |
| */ | |
| class ExpressAdapter extends http_adapter_1.AbstractHttpAdapter { | |
| constructor(instance) { | |
| super(instance || express()); | |
| this.routerMethodFactory = new router_method_factory_1.RouterMethodFactory(); | |
| this.logger = new common_1.Logger(ExpressAdapter.name); | |
| this.openConnections = new Set(); | |
| this.instance.use((req, res, next) => { | |
| if (this.onResponseHook) { | |
| res.on('finish', () => { | |
| void this.onResponseHook.apply(this, [req, res]); | |
| }); | |
| } | |
| if (this.onRequestHook) { | |
| void this.onRequestHook.apply(this, [req, res, next]); | |
| } | |
| else { | |
| next(); | |
| } | |
| }); | |
| } | |
| setOnRequestHook(onRequestHook) { | |
| this.onRequestHook = onRequestHook; | |
| } | |
| setOnResponseHook(onResponseHook) { | |
| this.onResponseHook = onResponseHook; | |
| } | |
| reply(response, body, statusCode) { | |
| if (statusCode) { | |
| response.status(statusCode); | |
| } | |
| if ((0, shared_utils_1.isNil)(body)) { | |
| return response.send(); | |
| } | |
| if (body instanceof common_1.StreamableFile) { | |
| this.applyStreamHeaders(response, body); | |
| const stream = body.getStream(); | |
| stream.once('error', err => { | |
| body.errorHandler(err, response); | |
| }); | |
| return stream | |
| .pipe(response) | |
| .on('error', (err) => body.errorLogger(err)); | |
| } | |
| const responseContentType = response.getHeader('Content-Type'); | |
| if (typeof responseContentType === 'string' && | |
| !responseContentType.startsWith('application/json') && | |
| body?.statusCode >= common_1.HttpStatus.BAD_REQUEST) { | |
| this.logger.warn("Content-Type doesn't match Reply body, you might need a custom ExceptionFilter for non-JSON responses"); | |
| response.setHeader('Content-Type', 'application/json'); | |
| } | |
| return (0, shared_utils_1.isObject)(body) ? response.json(body) : response.send(String(body)); | |
| } | |
| status(response, statusCode) { | |
| return response.status(statusCode); | |
| } | |
| end(response, message) { | |
| return response.end(message); | |
| } | |
| render(response, view, options) { | |
| return response.render(view, options); | |
| } | |
| redirect(response, statusCode, url) { | |
| return response.redirect(statusCode, url); | |
| } | |
| setErrorHandler(handler, prefix) { | |
| return this.use(handler); | |
| } | |
| setNotFoundHandler(handler, prefix) { | |
| return this.use(handler); | |
| } | |
| isHeadersSent(response) { | |
| return response.headersSent; | |
| } | |
| getHeader(response, name) { | |
| return response.get(name); | |
| } | |
| setHeader(response, name, value) { | |
| return response.set(name, value); | |
| } | |
| appendHeader(response, name, value) { | |
| return response.append(name, value); | |
| } | |
| normalizePath(path) { | |
| try { | |
| const convertedPath = legacy_route_converter_1.LegacyRouteConverter.tryConvert(path); | |
| // Call "pathToRegexp" to trigger a TypeError if the path is invalid | |
| (0, path_to_regexp_1.pathToRegexp)(convertedPath); | |
| return convertedPath; | |
| } | |
| catch (e) { | |
| if (e instanceof TypeError) { | |
| legacy_route_converter_1.LegacyRouteConverter.printError(path); | |
| } | |
| throw e; | |
| } | |
| } | |
| listen(port, ...args) { | |
| return this.httpServer.listen(port, ...args); | |
| } | |
| close() { | |
| this.closeOpenConnections(); | |
| if (!this.httpServer) { | |
| return undefined; | |
| } | |
| return new Promise(resolve => this.httpServer.close(resolve)); | |
| } | |
| set(...args) { | |
| return this.instance.set(...args); | |
| } | |
| enable(...args) { | |
| return this.instance.enable(...args); | |
| } | |
| disable(...args) { | |
| return this.instance.disable(...args); | |
| } | |
| engine(...args) { | |
| return this.instance.engine(...args); | |
| } | |
| useStaticAssets(path, options) { | |
| if (options && options.prefix) { | |
| return this.use(options.prefix, express.static(path, options)); | |
| } | |
| return this.use(express.static(path, options)); | |
| } | |
| setBaseViewsDir(path) { | |
| return this.set('views', path); | |
| } | |
| setViewEngine(engine) { | |
| return this.set('view engine', engine); | |
| } | |
| getRequestHostname(request) { | |
| return request.hostname; | |
| } | |
| getRequestMethod(request) { | |
| return request.method; | |
| } | |
| getRequestUrl(request) { | |
| return request.originalUrl; | |
| } | |
| enableCors(options) { | |
| return this.use(cors(options)); | |
| } | |
| createMiddlewareFactory(requestMethod) { | |
| return (path, callback) => { | |
| try { | |
| const convertedPath = legacy_route_converter_1.LegacyRouteConverter.tryConvert(path); | |
| return this.routerMethodFactory | |
| .get(this.instance, requestMethod) | |
| .call(this.instance, convertedPath, callback); | |
| } | |
| catch (e) { | |
| if (e instanceof TypeError) { | |
| legacy_route_converter_1.LegacyRouteConverter.printError(path); | |
| } | |
| throw e; | |
| } | |
| }; | |
| } | |
| initHttpServer(options) { | |
| const isHttpsEnabled = options && options.httpsOptions; | |
| if (isHttpsEnabled) { | |
| this.httpServer = https.createServer(options.httpsOptions, this.getInstance()); | |
| } | |
| else { | |
| this.httpServer = http.createServer(this.getInstance()); | |
| } | |
| if (options?.forceCloseConnections) { | |
| this.trackOpenConnections(); | |
| } | |
| } | |
| registerParserMiddleware(prefix, rawBody) { | |
| const bodyParserJsonOptions = (0, get_body_parser_options_util_1.getBodyParserOptions)(rawBody); | |
| const bodyParserUrlencodedOptions = (0, get_body_parser_options_util_1.getBodyParserOptions)(rawBody, { | |
| extended: true, | |
| }); | |
| const parserMiddleware = { | |
| jsonParser: express.json(bodyParserJsonOptions), | |
| urlencodedParser: express.urlencoded(bodyParserUrlencodedOptions), | |
| }; | |
| Object.keys(parserMiddleware) | |
| .filter(parser => !this.isMiddlewareApplied(parser)) | |
| .forEach(parserKey => this.use(parserMiddleware[parserKey])); | |
| } | |
| useBodyParser(type, rawBody, options) { | |
| const parserOptions = (0, get_body_parser_options_util_1.getBodyParserOptions)(rawBody, options); | |
| const parser = express[type](parserOptions); | |
| this.use(parser); | |
| return this; | |
| } | |
| setLocal(key, value) { | |
| this.instance.locals[key] = value; | |
| return this; | |
| } | |
| getType() { | |
| return 'express'; | |
| } | |
| applyVersionFilter(handler, version, versioningOptions) { | |
| const callNextHandler = (req, res, next) => { | |
| if (!next) { | |
| throw new common_1.InternalServerErrorException('HTTP adapter does not support filtering on version'); | |
| } | |
| return next(); | |
| }; | |
| if (version === common_1.VERSION_NEUTRAL || | |
| // URL Versioning is done via the path, so the filter continues forward | |
| versioningOptions.type === common_1.VersioningType.URI) { | |
| const handlerForNoVersioning = (req, res, next) => handler(req, res, next); | |
| return handlerForNoVersioning; | |
| } | |
| // Custom Extractor Versioning Handler | |
| if (versioningOptions.type === common_1.VersioningType.CUSTOM) { | |
| const handlerForCustomVersioning = (req, res, next) => { | |
| const extractedVersion = versioningOptions.extractor(req); | |
| if (Array.isArray(version)) { | |
| if (Array.isArray(extractedVersion) && | |
| version.filter(v => extractedVersion.includes(v)).length) { | |
| return handler(req, res, next); | |
| } | |
| if ((0, shared_utils_1.isString)(extractedVersion) && | |
| version.includes(extractedVersion)) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| else if ((0, shared_utils_1.isString)(version)) { | |
| // Known bug here - if there are multiple versions supported across separate | |
| // handlers/controllers, we can't select the highest matching handler. | |
| // Since this code is evaluated per-handler, then we can't see if the highest | |
| // specified version exists in a different handler. | |
| if (Array.isArray(extractedVersion) && | |
| extractedVersion.includes(version)) { | |
| return handler(req, res, next); | |
| } | |
| if ((0, shared_utils_1.isString)(extractedVersion) && version === extractedVersion) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| return callNextHandler(req, res, next); | |
| }; | |
| return handlerForCustomVersioning; | |
| } | |
| // Media Type (Accept Header) Versioning Handler | |
| if (versioningOptions.type === common_1.VersioningType.MEDIA_TYPE) { | |
| const handlerForMediaTypeVersioning = (req, res, next) => { | |
| const MEDIA_TYPE_HEADER = 'Accept'; | |
| const acceptHeaderValue = req.headers?.[MEDIA_TYPE_HEADER] || | |
| req.headers?.[MEDIA_TYPE_HEADER.toLowerCase()]; | |
| const acceptHeaderVersionParameter = acceptHeaderValue | |
| ? acceptHeaderValue.split(';')[1] | |
| : undefined; | |
| // No version was supplied | |
| if ((0, shared_utils_1.isUndefined)(acceptHeaderVersionParameter)) { | |
| if (Array.isArray(version)) { | |
| if (version.includes(common_1.VERSION_NEUTRAL)) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| } | |
| else { | |
| const headerVersion = acceptHeaderVersionParameter.split(versioningOptions.key)[1]; | |
| if (Array.isArray(version)) { | |
| if (version.includes(headerVersion)) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| else if ((0, shared_utils_1.isString)(version)) { | |
| if (version === headerVersion) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| } | |
| return callNextHandler(req, res, next); | |
| }; | |
| return handlerForMediaTypeVersioning; | |
| } | |
| // Header Versioning Handler | |
| if (versioningOptions.type === common_1.VersioningType.HEADER) { | |
| const handlerForHeaderVersioning = (req, res, next) => { | |
| const customHeaderVersionParameter = req.headers?.[versioningOptions.header] || | |
| req.headers?.[versioningOptions.header.toLowerCase()]; | |
| // No version was supplied | |
| if ((0, shared_utils_1.isUndefined)(customHeaderVersionParameter)) { | |
| if (Array.isArray(version)) { | |
| if (version.includes(common_1.VERSION_NEUTRAL)) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| } | |
| else { | |
| if (Array.isArray(version)) { | |
| if (version.includes(customHeaderVersionParameter)) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| else if ((0, shared_utils_1.isString)(version)) { | |
| if (version === customHeaderVersionParameter) { | |
| return handler(req, res, next); | |
| } | |
| } | |
| } | |
| return callNextHandler(req, res, next); | |
| }; | |
| return handlerForHeaderVersioning; | |
| } | |
| throw new Error('Unsupported versioning options'); | |
| } | |
| trackOpenConnections() { | |
| this.httpServer.on('connection', (socket) => { | |
| this.openConnections.add(socket); | |
| socket.on('close', () => this.openConnections.delete(socket)); | |
| }); | |
| } | |
| closeOpenConnections() { | |
| for (const socket of this.openConnections) { | |
| socket.destroy(); | |
| this.openConnections.delete(socket); | |
| } | |
| } | |
| isMiddlewareApplied(name) { | |
| const app = this.getInstance(); | |
| return (!!app.router && | |
| !!app.router.stack && | |
| (0, shared_utils_1.isFunction)(app.router.stack.filter) && | |
| app.router.stack.some((layer) => layer && layer.handle && layer.handle.name === name)); | |
| } | |
| setHeaderIfNotExists(response, name, value) { | |
| if (value !== undefined && response.getHeader(name) === undefined) { | |
| const headerValue = Array.isArray(value) ? value.join(',') : value; | |
| response.setHeader(name, headerValue); | |
| } | |
| } | |
| applyStreamHeaders(response, streamable) { | |
| const headers = streamable.getHeaders(); | |
| this.setHeaderIfNotExists(response, 'Content-Type', headers.type); | |
| this.setHeaderIfNotExists(response, 'Content-Disposition', headers.disposition); | |
| this.setHeaderIfNotExists(response, 'Content-Length', headers.length); | |
| } | |
| } | |
| exports.ExpressAdapter = ExpressAdapter; | |