UNPKG

@h4ad/serverless-adapter

Version:

Run REST APIs and other web applications using your existing Node.js application framework (NestJS, Express, Koa, Hapi, Fastify and many others), on top of AWS, Azure, Digital Ocean and many other clouds.

1 lines 10.2 kB
{"version":3,"sources":["../../../src/adapters/azure/http-trigger-v4.adapter.ts"],"sourcesContent":["//#region Imports\n\nimport { URL } from 'node:url';\nimport type {\n Context,\n Cookie,\n HttpRequest,\n HttpResponseSimple,\n} from '@azure/functions';\nimport type { BothValueHeaders } from '../../@types';\nimport type {\n AdapterContract,\n AdapterRequest,\n GetResponseAdapterProps,\n OnErrorProps,\n} from '../../contracts';\nimport {\n getDefaultIfUndefined,\n getEventBodyAsBuffer,\n getFlattenedHeadersMap,\n getPathWithQueryStringParams,\n} from '../../core';\n\n//#endregion\n\n/**\n * The options to customize the {@link HttpTriggerV4Adapter}\n *\n * @breadcrumb Adapters / Azure / HttpTriggerV4Adapter\n * @public\n */\nexport interface HttpTriggerV4AdapterOptions {\n /**\n * Strip base path for custom domains\n *\n * @defaultValue ''\n */\n stripBasePath?: string;\n}\n\n/**\n * The adapter to handle requests from Http Trigger on Azure Function V4.\n *\n * @example\n * ```typescript\n * const stripBasePath = '/any/custom/base/path'; // default ''\n * const adapter = new HttpTriggerV4Adapter({ stripBasePath });\n * ```\n *\n * @see {@link https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node | Reference}\n *\n * @breadcrumb Adapters / Azure / HttpTriggerV4Adapter\n * @public\n */\nexport class HttpTriggerV4Adapter\n implements AdapterContract<HttpRequest, Context, HttpResponseSimple>\n{\n //#region Constructor\n\n /**\n * Default constructor\n *\n * @param options - The options to customize the {@link HttpTriggerV4Adapter}\n */\n constructor(protected readonly options?: HttpTriggerV4AdapterOptions) {}\n\n //#endregion\n\n //#region Public Methods\n\n /**\n * {@inheritDoc}\n */\n public getAdapterName(): string {\n return HttpTriggerV4Adapter.name;\n }\n\n /**\n * {@inheritDoc}\n */\n public canHandle(event: unknown, context: unknown): boolean {\n const maybeEvent = event as Partial<HttpRequest> | undefined;\n const maybeContext = context as Partial<Context> | undefined;\n\n return !!(\n maybeEvent &&\n maybeEvent.method &&\n maybeEvent.headers &&\n maybeEvent.url &&\n maybeEvent.query &&\n maybeContext &&\n maybeContext.traceContext &&\n maybeContext.bindingDefinitions &&\n maybeContext.log &&\n !!maybeContext.log.info &&\n maybeContext.bindingData\n );\n }\n\n /**\n * {@inheritDoc}\n */\n public getRequest(event: HttpRequest): AdapterRequest {\n const path = this.getPathFromEvent(event);\n\n const method = event.method!;\n const headers = getFlattenedHeadersMap(event.headers, ',', true);\n\n let body: Buffer | undefined;\n\n if (event.body) {\n const [bufferBody, contentLength] = getEventBodyAsBuffer(\n event.rawBody,\n false,\n );\n\n body = bufferBody;\n headers['content-length'] = String(contentLength);\n }\n\n const remoteAddress = headers['x-forwarded-for'];\n\n return {\n method,\n path,\n headers,\n remoteAddress,\n body,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public getResponse({\n body,\n statusCode,\n headers: originalHeaders,\n }: GetResponseAdapterProps<HttpRequest>): HttpResponseSimple {\n const headers = getFlattenedHeadersMap(originalHeaders, ',', true);\n const cookies = this.getAzureCookiesFromHeaders(originalHeaders);\n\n if (headers['set-cookie']) delete headers['set-cookie'];\n\n return {\n body,\n statusCode,\n headers,\n // I tried to understand this property with\n // https://docs.microsoft.com/en-us/aspnet/web-api/overview/formats-and-model-binding/content-negotiation\n // but I don't know if it's worth implementing this guy as an option\n // I found out when this guy is set to true and the framework sets content-type, azure returns 500\n // So I'll leave it as is and hope no one has any problems.\n enableContentNegotiation: false,\n cookies,\n };\n }\n\n /**\n * {@inheritDoc}\n */\n public onErrorWhileForwarding({\n error,\n respondWithErrors,\n event,\n delegatedResolver,\n log,\n }: OnErrorProps<HttpRequest, HttpResponseSimple>): void {\n const body = respondWithErrors ? error.stack : '';\n const errorResponse = this.getResponse({\n event,\n statusCode: 500,\n body: body || '',\n headers: {},\n isBase64Encoded: false,\n log,\n });\n\n delegatedResolver.succeed(errorResponse);\n }\n\n //#endregion\n\n //#region Protected Methods\n\n /**\n * Get path from event with query strings\n *\n * @param event - The event sent by serverless\n */\n protected getPathFromEvent(event: HttpRequest): string {\n const stripBasePath = getDefaultIfUndefined(\n this.options?.stripBasePath,\n '',\n );\n\n const url = new URL(event.url);\n const originalPath = url.pathname;\n\n const replaceRegex = new RegExp(`^${stripBasePath}`);\n const path = originalPath.replace(replaceRegex, '');\n\n const queryParams = event.query;\n\n return getPathWithQueryStringParams(path, queryParams);\n }\n\n /**\n * Get the Azure Cookie list parsed from set-cookie header.\n *\n * @param headers - The headers object\n */\n protected getAzureCookiesFromHeaders(headers: BothValueHeaders): Cookie[] {\n const setCookie = headers['set-cookie'];\n\n const headerCookies = Array.isArray(setCookie)\n ? setCookie\n : setCookie\n ? [setCookie]\n : [];\n\n return headerCookies.map(cookie => this.parseCookie(cookie));\n }\n\n /**\n * Parse the string cookie to the Azure Cookie Object.\n * This code was written by {@link https://github.com/zachabney | @zachabney}\n * on {@link https://github.com/zachabney/azure-aws-serverless-express/blob/241d2d5c4d5906e4817662cad6426ec2cbbf9ca7/src/index.js#L4-L49 | this library}.\n *\n * @param cookie - The cookie\n */\n protected parseCookie(cookie: string): Cookie {\n return cookie.split(';').reduce(\n (azureCookieObject, cookieProperty, index) => {\n const [key, value] = cookieProperty.split('=');\n\n const sanitizedKey = key.toLowerCase().trim();\n const sanitizedValue = value && value.trim();\n\n if (index === 0) {\n azureCookieObject.name = key;\n azureCookieObject.value = sanitizedValue;\n\n return azureCookieObject;\n }\n\n switch (sanitizedKey) {\n case 'domain':\n azureCookieObject.domain = sanitizedValue;\n break;\n case 'path':\n azureCookieObject.path = sanitizedValue;\n break;\n case 'expires':\n azureCookieObject.expires = new Date(sanitizedValue);\n break;\n case 'secure':\n azureCookieObject.secure = true;\n break;\n case 'httponly':\n azureCookieObject.httpOnly = true;\n break;\n case 'samesite':\n azureCookieObject.sameSite = sanitizedValue as Cookie['sameSite'];\n break;\n case 'max-age':\n azureCookieObject.maxAge = Number(sanitizedValue);\n break;\n }\n\n return azureCookieObject;\n },\n { name: '', value: '' } as Cookie,\n );\n }\n\n //#endregion\n}\n"],"mappings":"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","names":["URL","HttpTriggerV4Adapter","_HttpTriggerV4Adapter","options","__name","event","context","maybeEvent","maybeContext","path","method","headers","getFlattenedHeadersMap","body","bufferBody","contentLength","getEventBodyAsBuffer","remoteAddress","statusCode","originalHeaders","cookies","error","respondWithErrors","delegatedResolver","log","errorResponse","stripBasePath","getDefaultIfUndefined","originalPath","URL","replaceRegex","queryParams","getPathWithQueryStringParams","setCookie","cookie","azureCookieObject","cookieProperty","index","key","value","sanitizedKey","sanitizedValue"]}