|
8 | 8 | import {CONTENT_TYPE, X_ORIGINAL_CONTENT_TYPE} from '../../ssr/server/constants' |
9 | 9 | import {getFlattenedHeadersMap} from '@h4ad/serverless-adapter' |
10 | 10 |
|
| 11 | +/** |
| 12 | + * Processes multi-value headers by flattening them into a single headers object, |
| 13 | + * while preserving cookies in multiValueHeaders format. |
| 14 | + * Cookies are extracted from multiValueHeaders and kept separate, as they need |
| 15 | + * to remain in multiValueHeaders format for AWS Lambda responses. |
| 16 | + * Also restores the original content type if it was temporarily replaced |
| 17 | + * (e.g., when binary content encoding is used). |
| 18 | + * |
| 19 | + * @private |
| 20 | + * @param {Object} multiValueHeaders - An object containing multi-value headers, |
| 21 | + * where each key maps to an array of header values (e.g., {'set-cookie': ['cookie1', 'cookie2']}) |
| 22 | + * @returns {Object} An object containing: |
| 23 | + * - headers: A flattened headers object with all headers joined by commas, |
| 24 | + * excluding set-cookie headers, with a 'date' header added and content-type |
| 25 | + * restored from x-original-content-type if present |
| 26 | + * - multiValueHeaders: An object containing only the 'set-cookie' header if cookies were present, |
| 27 | + * otherwise an empty object |
| 28 | + */ |
| 29 | +export const processHeaders = (multiValueHeaders) => { |
| 30 | + const cookies = multiValueHeaders?.['set-cookie'] |
| 31 | + let headers = getFlattenedHeadersMap(multiValueHeaders || {}, ',', true) |
| 32 | + headers['date'] = new Date().toUTCString() |
| 33 | + let newMultiValueHeaders = {} |
| 34 | + |
| 35 | + // Only allow set-cookie headers to be in multiValueHeaders |
| 36 | + // to return multiple set-cookie headers instead of a single set-cookie header with multiple values |
| 37 | + if (cookies) { |
| 38 | + delete headers['set-cookie'] |
| 39 | + newMultiValueHeaders = {'set-cookie': cookies} |
| 40 | + } |
| 41 | + |
| 42 | + // If the response contains an X_ORIGINAL_CONTENT_TYPE header, |
| 43 | + // then replace the current CONTENT_TYPE header with it. |
| 44 | + const originalContentType = headers[X_ORIGINAL_CONTENT_TYPE] |
| 45 | + if (originalContentType) { |
| 46 | + headers[CONTENT_TYPE] = originalContentType |
| 47 | + delete headers[X_ORIGINAL_CONTENT_TYPE] |
| 48 | + } |
| 49 | + |
| 50 | + return {headers, multiValueHeaders: newMultiValueHeaders} |
| 51 | +} |
| 52 | + |
| 53 | +/** |
| 54 | + * Processes a Lambda response by converting multi-value headers to a flattened format, |
| 55 | + * preserving cookies in multiValueHeaders, and adding correlation ID from the event. |
| 56 | + * |
| 57 | + * This function is used to transform Express response headers into the format |
| 58 | + * expected by AWS Lambda/API Gateway, ensuring that: |
| 59 | + * - Multi-value headers are properly flattened (except for set-cookie) |
| 60 | + * - Cookies remain in multiValueHeaders format for proper handling |
| 61 | + * - Correlation IDs are propagated from the request to the response |
| 62 | + * - Original content types are restored when they were temporarily replaced |
| 63 | + * (handled by processHeaders) |
| 64 | + * |
| 65 | + * @param {Object} response - The Lambda response object containing multiValueHeaders |
| 66 | + * @param {Object} event - The Lambda event object containing request headers |
| 67 | + * @param {Object} [event.headers] - Request headers from the Lambda event |
| 68 | + * @param {string} [event.headers['x-correlation-id']] - Correlation ID to add to response headers |
| 69 | + * @returns {Object} The processed response object with: |
| 70 | + * - headers: Flattened headers object (all headers joined by commas, except set-cookie) |
| 71 | + * - multiValueHeaders: Object containing set-cookie headers if present |
| 72 | + * - All other properties from the original response object |
| 73 | + */ |
11 | 74 | export const processLambdaResponse = (response, event) => { |
12 | 75 | if (!response) return response |
13 | 76 |
|
14 | 77 | // Retrieve the correlation ID from the event headers |
15 | 78 | const correlationId = event.headers?.['x-correlation-id'] |
16 | 79 |
|
17 | | - let joinedHeaders = getFlattenedHeadersMap(response.multiValueHeaders || {}, ',', true) |
18 | | - joinedHeaders['date'] = new Date().toUTCString() |
19 | | - delete response['multiValueHeaders'] |
| 80 | + // The response only has multiValueHeaders but the updated response needs joined single value headers |
| 81 | + // except for the set-cookie headers |
| 82 | + let {headers, multiValueHeaders} = processHeaders(response.multiValueHeaders) |
20 | 83 |
|
21 | 84 | // Add the correlation ID to the response headers if it exists |
22 | 85 | if (correlationId) { |
23 | | - joinedHeaders['x-correlation-id'] = correlationId |
24 | | - } |
25 | | - |
26 | | - // If the response contains an X_ORIGINAL_CONTENT_TYPE header, |
27 | | - // then replace the current CONTENT_TYPE header with it. |
28 | | - const originalContentType = joinedHeaders[X_ORIGINAL_CONTENT_TYPE] |
29 | | - if (originalContentType) { |
30 | | - joinedHeaders[CONTENT_TYPE] = originalContentType |
31 | | - delete joinedHeaders[X_ORIGINAL_CONTENT_TYPE] |
| 86 | + headers['x-correlation-id'] = correlationId |
32 | 87 | } |
33 | 88 |
|
34 | 89 | const result = { |
35 | 90 | ...response, |
36 | | - headers: joinedHeaders |
| 91 | + headers, |
| 92 | + multiValueHeaders |
37 | 93 | } |
38 | 94 | return result |
39 | 95 | } |
0 commit comments