Skip to content

Commit 0fa1c76

Browse files
authored
Merge pull request #2 from epilot-dev/chore/force-custom-error-message
chore: introduce custom error/message when header not present
2 parents ae1d8d5 + f1edefa commit 0fa1c76

File tree

3 files changed

+113
-9
lines changed

3 files changed

+113
-9
lines changed

packages/large-response-middleware/README.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Enables Lambdas to return responses larger than 6MB by offloading the content to
77
- This implementation currently provides support for API Gateway with Lambda Proxy Integration only.
88
- There are plans to extend this work as described here [#issue-1](https://github.com/epilot-dev/aws-lambda-utility-middlewares/issues/1)
99

10-
----
10+
---
1111

1212
<p align="center">
1313
<img src="https://raw.githubusercontent.com/epilot-dev/aws-lambda-utility-middlewares/main/packages/large-response-middleware/docs/out/architecture-1/Architecture%20-%20Sequence%20Diagram.svg" />
@@ -23,18 +23,19 @@ When a client can handle a Large Response, it must send a request with the HTTP
2323

2424
If the client provides the large response MIME type, the Lambda will not log an error using `Log.error`. Instead, it will rewrite the original response with a reference to the offloaded large payload. Furthermore, the rewritten response will include the HTTP header `Content-Type` with the value `application/large-response.vnd+json`.
2525

26-
If the client does not provide the large response MIME type, the Lambda will log an error with `Log.error`, and the response will fail due to an excessively large response body.
26+
If the client does not provide the large response MIME type, the Lambda will log an error with `Log.error` and rewrite the original response with a custom message (can be configured) and HTTP status code 413 (Payload Too Large).
2727

2828
### Middleware Configuration:
2929

3030
Supported Parameters:
3131

32-
| Parameter | Type | Description |
33-
| --------------- | ------------------- | ---------------------------------------------------------------------------- |
34-
| thresholdWarn | `number` | Warning threshold level (percentage of `sizeLimitInMB`), e.g: 0.80 |
35-
| thresholdError | `number` | Error threshold level (percentage of `sizeLimitInMB`), e.g: 0.90 |
36-
| sizeLimitInMB | `number` | Maximum allowed size limit in MB, e.g 6 |
37-
| outputBucket | `string` | Identifier or name of the output S3 bucket |
32+
| Parameter | Type | Description |
33+
| --- | --- | --- |
34+
| thresholdWarn | `number` | Warning threshold level (percentage of `sizeLimitInMB`), e.g: 0.80 |
35+
| thresholdError | `number` | Error threshold level (percentage of `sizeLimitInMB`), e.g: 0.90 |
36+
| sizeLimitInMB | `number` | Maximum allowed size limit in MB, e.g 6 |
37+
| outputBucket | `string` | Identifier or name of the output S3 bucket |
38+
| customErrorMessage | `string \| (event:APIGatewayProxyEventV2) => string ` | Custom error message to be returned when the response is too large and the client does not support large responses (no accept header) |
3839
| groupRequestsBy | `function - mapper` | Function to group requests, based on API Gateway event V2. Defaults to 'all' |
3940

4041
Example Usage:

packages/large-response-middleware/src/index.test.ts

Lines changed: 89 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import * as Lambda from 'aws-lambda';
44

55
import { getOrgIdFromContext } from './__tests__/util';
66

7-
import { LARGE_RESPONSE_MIME_TYPE, withLargeResponseHandler } from './';
87
import * as middleware from './';
8+
import { LARGE_RESPONSE_MIME_TYPE, withLargeResponseHandler } from './';
99

1010
const uploadFileSpy = jest.spyOn(middleware, 'uploadFile').mockResolvedValue({
1111
filename: 'red-redington/2023-12-13/la-caballa',
@@ -123,6 +123,94 @@ describe('withLargeResponseHandler', () => {
123123
});
124124
});
125125

126+
it('should overwrite response with default ERROR message + correct status when content length is over ERROR threshold', async () => {
127+
const middleware = withLargeResponseHandler({
128+
thresholdWarn: 0.5,
129+
thresholdError: 0.9,
130+
sizeLimitInMB: 1,
131+
outputBucket: 'the-bucket-list',
132+
groupRequestsBy: getOrgIdFromContext,
133+
});
134+
const content = Buffer.alloc(1024 * 1024, 'a').toString();
135+
const requestResponseContext = {
136+
event: {
137+
requestContext: {},
138+
},
139+
response: {
140+
headers: {
141+
random: Buffer.alloc(0.85 * 1024 * 1024, 'a').toString(), // 0.85MB
142+
},
143+
body: content,
144+
},
145+
} as any;
146+
147+
await middleware.after(requestResponseContext);
148+
149+
expect(JSON.parse(requestResponseContext.response?.body)?.message).toBe(
150+
"Call the API with the HTTP header 'Accept: application/large-response.vnd+json' to receive the payload through an S3 ref and avoid HTTP 500 errors.",
151+
);
152+
expect(requestResponseContext?.response?.statusCode).toBe(413);
153+
});
154+
155+
it('should overwrite response with ERROR message (string) + correct status when content length is over ERROR threshold', async () => {
156+
const middleware = withLargeResponseHandler({
157+
thresholdWarn: 0.5,
158+
thresholdError: 0.9,
159+
customErrorMessage: 'Custom error message',
160+
sizeLimitInMB: 1,
161+
outputBucket: 'the-bucket-list',
162+
groupRequestsBy: getOrgIdFromContext,
163+
});
164+
const content = Buffer.alloc(1024 * 1024, 'a').toString();
165+
const requestResponseContext = {
166+
event: {
167+
requestContext: {},
168+
},
169+
response: {
170+
headers: {
171+
random: Buffer.alloc(0.85 * 1024 * 1024, 'a').toString(), // 0.85MB
172+
},
173+
body: content,
174+
},
175+
} as any;
176+
177+
await middleware.after(requestResponseContext);
178+
179+
expect(JSON.parse(requestResponseContext.response?.body)?.message).toBe('Custom error message');
180+
expect(requestResponseContext?.response?.statusCode).toBe(413);
181+
});
182+
183+
it('should overwrite response with custom ERROR message (callback function) + correct status when content length is over ERROR threshold', async () => {
184+
const middleware = withLargeResponseHandler({
185+
thresholdWarn: 0.5,
186+
thresholdError: 0.9,
187+
customErrorMessage: (event: Lambda.APIGatewayProxyEventV2) =>
188+
`Custom error message for ${event.requestContext?.requestId}`,
189+
sizeLimitInMB: 1,
190+
outputBucket: 'the-bucket-list',
191+
groupRequestsBy: getOrgIdFromContext,
192+
});
193+
const content = Buffer.alloc(1024 * 1024, 'a').toString();
194+
const requestResponseContext = {
195+
event: {
196+
requestContext: {
197+
requestId: 'request-id-123',
198+
},
199+
},
200+
response: {
201+
headers: {
202+
random: Buffer.alloc(0.85 * 1024 * 1024, 'a').toString(), // 0.85MB
203+
},
204+
body: content,
205+
},
206+
} as any;
207+
208+
await middleware.after(requestResponseContext);
209+
210+
expect(JSON.parse(requestResponseContext.response?.body)?.message).toBe('Custom error message for request-id-123');
211+
expect(requestResponseContext?.response?.statusCode).toBe(413);
212+
});
213+
126214
describe('when request header "Accept":"application/large-response.vnd+json" is given', () => {
127215
it('should not log ERROR with "Large response detected (limit exceeded)" when content length is over ERROR threshold', async () => {
128216
const middleware = withLargeResponseHandler({

packages/large-response-middleware/src/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,21 @@ export type FileUploadContext = {
2626
fileName: string;
2727
};
2828

29+
export type CustomErrorMessage = string | ((event: APIGatewayProxyEventV2) => string);
30+
2931
export const withLargeResponseHandler = ({
3032
thresholdWarn,
3133
thresholdError,
3234
sizeLimitInMB: _sizeLimitInMB,
3335
outputBucket,
36+
customErrorMessage,
3437
groupRequestsBy,
3538
}: {
3639
thresholdWarn: number;
3740
thresholdError: number;
3841
sizeLimitInMB: number;
3942
outputBucket: string;
43+
customErrorMessage?: CustomErrorMessage;
4044
groupRequestsBy?: (event: APIGatewayProxyEventV2) => string;
4145
}) => {
4246
return {
@@ -101,6 +105,11 @@ export const withLargeResponseHandler = ({
101105
response_size_mb: contentLengthMB.toFixed(2),
102106
$payload_ref,
103107
});
108+
response.isBase64Encoded = false;
109+
response.statusCode = 413;
110+
response.body = JSON.stringify({
111+
message: getCustomErrorMessage(customErrorMessage, event),
112+
});
104113
}
105114
} else if (contentLengthMB > thresholdWarnInMB) {
106115
Log.warn(`Large response detected. ${LARGE_RESPONSE_USER_INFO}`, {
@@ -189,3 +198,9 @@ function getFormattedDate() {
189198

190199
return date.toISOString().split('T')[0];
191200
}
201+
202+
function getCustomErrorMessage(customErrorMessage: CustomErrorMessage | undefined, event: APIGatewayProxyEventV2) {
203+
return typeof customErrorMessage === 'function'
204+
? customErrorMessage(event)
205+
: customErrorMessage ?? LARGE_RESPONSE_USER_INFO;
206+
}

0 commit comments

Comments
 (0)