Skip to content

Commit 700568a

Browse files
Add 6MB request and response size check (#1990)
1 parent 4e50b80 commit 700568a

File tree

17 files changed

+618
-62
lines changed

17 files changed

+618
-62
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"Projects": [
3+
{
4+
"Name": "Amazon.Lambda.TestTool",
5+
"Type": "Patch",
6+
"ChangelogMessages": [
7+
"Add 6MB request and response size validation."
8+
]
9+
}
10+
]
11+
}

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Components/Pages/Home.razor

+20-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,26 @@ else
7777
<label for="sample-requests">Example Requests</label>
7878
</div>
7979
<div class="mt-1 flex-grow-1 flex-fill">
80-
<label class="form-label" for="function-payload">Function Input</label>
81-
<StandaloneCodeEditor Id="function-payload" @ref="_editor" ConstructionOptions="EditorConstructionOptions" CssClass="rounded-4 overflow-hidden border"/>
80+
<div class="d-flex justify-content-start align-items-center">
81+
<label class="form-label mb-0" for="function-payload">Function Input</label>
82+
@if (!string.IsNullOrEmpty(_errorMessage))
83+
{
84+
<div class="text-danger ms-auto">
85+
<i class="bi bi-exclamation-triangle-fill"></i>
86+
</div>
87+
}
88+
</div>
89+
<StandaloneCodeEditor
90+
Id="function-payload"
91+
@ref="_editor"
92+
ConstructionOptions="EditorConstructionOptions"
93+
CssClass="@($"rounded-4 overflow-hidden border {(!string.IsNullOrEmpty(_errorMessage) ? "border-danger border-2" : "")}")"/>
94+
@if (!string.IsNullOrEmpty(_errorMessage))
95+
{
96+
<div class="text-danger">
97+
@_errorMessage
98+
</div>
99+
}
82100
</div>
83101
</div>
84102
<div class="col-lg-6 d-flex flex-column">

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Components/Pages/Home.razor.cs

+42-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
using Amazon.Lambda.Model;
45
using Microsoft.AspNetCore.Components;
56
using Amazon.Lambda.TestTool.Services;
67
using Amazon.Lambda.TestTool.Models;
@@ -9,6 +10,7 @@
910
using BlazorMonaco.Editor;
1011
using Microsoft.JSInterop;
1112
using Microsoft.AspNetCore.WebUtilities;
13+
using Microsoft.Extensions.Options;
1214

1315
namespace Amazon.Lambda.TestTool.Components.Pages;
1416

@@ -21,6 +23,8 @@ public partial class Home : ComponentBase, IDisposable
2123
[Inject] public required IDirectoryManager DirectoryManager { get; set; }
2224
[Inject] public required IThemeService ThemeService { get; set; }
2325
[Inject] public required IJSRuntime JsRuntime { get; set; }
26+
[Inject] public required ILambdaClient LambdaClient { get; set; }
27+
[Inject] public IOptions<LambdaOptions> LambdaOptions { get; set; }
2428

2529
private StandaloneCodeEditor? _editor;
2630
private StandaloneCodeEditor? _activeEditor;
@@ -33,6 +37,8 @@ public partial class Home : ComponentBase, IDisposable
3337

3438
private const string NoSampleSelectedId = "void-select-request";
3539

40+
private string _errorMessage = string.Empty;
41+
3642
private IDictionary<string, IList<LambdaRequest>> SampleRequests { get; set; } = new Dictionary<string, IList<LambdaRequest>>();
3743

3844
private IRuntimeApiDataStore? DataStore { get; set; }
@@ -184,9 +190,12 @@ async Task OnAddEventClick()
184190
DataStore is null)
185191
return;
186192
var editorValue = await _editor.GetValue();
187-
DataStore.QueueEvent(editorValue, false);
188-
await _editor.SetValue(string.Empty);
189-
SelectedSampleRequestName = NoSampleSelectedId;
193+
var success = await InvokeLambdaFunction(editorValue);
194+
if (success)
195+
{
196+
await _editor.SetValue(string.Empty);
197+
SelectedSampleRequestName = NoSampleSelectedId;
198+
}
190199
StateHasChanged();
191200
}
192201

@@ -202,7 +211,7 @@ void OnClearExecuted()
202211
StateHasChanged();
203212
}
204213

205-
void OnRequeue(string awsRequestId)
214+
async Task OnRequeue(string awsRequestId)
206215
{
207216
if (DataStore is null)
208217
return;
@@ -218,8 +227,7 @@ void OnRequeue(string awsRequestId)
218227

219228
if (evnt == null)
220229
return;
221-
222-
DataStore.QueueEvent(evnt.EventJson, false);
230+
await InvokeLambdaFunction(evnt.EventJson);
223231
StateHasChanged();
224232
}
225233

@@ -326,4 +334,32 @@ private StandaloneEditorConstructionOptions ActiveErrorEditorConstructionOptions
326334
}
327335
};
328336
}
337+
338+
private async Task<bool> InvokeLambdaFunction(string payload)
339+
{
340+
var invokeRequest = new InvokeRequest
341+
{
342+
FunctionName = SelectedFunctionName,
343+
Payload = payload,
344+
InvocationType = InvocationType.Event
345+
};
346+
347+
try
348+
{
349+
await LambdaClient.InvokeAsync(invokeRequest, LambdaOptions.Value.Endpoint);
350+
_errorMessage = string.Empty;
351+
return true;
352+
}
353+
catch (AmazonLambdaException e)
354+
{
355+
Logger.LogInformation(e.Message);
356+
357+
// lambda client automatically adds some extra verbiage: "The service returned an error with Error Code xxxx and HTTP Body: <bodyhere>".
358+
// removing the extra verbiage to make the error message smaller and look better on the ui.
359+
_errorMessage = e.Message.Contains("HTTP Body: ")
360+
? e.Message.Split("HTTP Body: ")[1]
361+
: e.Message;
362+
}
363+
return false;
364+
}
329365
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
namespace Amazon.Lambda.TestTool;
5+
6+
/// <summary>
7+
/// Contains constant string values for AWS Lambda exception types.
8+
/// </summary>
9+
public class Exceptions
10+
{
11+
/// <summary>
12+
/// Exception thrown when the request payload size exceeds AWS Lambda's limits.
13+
/// This occurs when the request payload is larger than 6 MB for synchronous invocations.
14+
/// </summary>
15+
public const string RequestEntityTooLargeException = "RequestEntityTooLargeException";
16+
}

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Extensions/InvokeResponseExtensions.cs

+69
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
using Amazon.Lambda.Model;
77
using Amazon.Lambda.TestTool.Models;
88

9+
namespace Amazon.Lambda.TestTool.Extensions;
10+
911
/// <summary>
1012
/// Provides extension methods for converting Lambda InvokeResponse to API Gateway response types.
1113
/// </summary>
@@ -83,6 +85,52 @@ public static APIGatewayProxyResponse ToApiGatewayErrorResponse(ApiGatewayEmulat
8385
}
8486
}
8587

88+
/// <summary>
89+
/// Creates a standard API Gateway response for a "Request Entity Too Large" (413) error.
90+
/// Not compatible with HTTP V2 API Gateway mode.
91+
/// </summary>
92+
/// <param name="emulatorMode">The API Gateway emulator mode (Rest or HttpV1 only).</param>
93+
/// <returns>An APIGatewayProxyResponse configured with:
94+
/// - Status code 413
95+
/// - JSON error message ("Request Too Long" for REST, "Request Entity Too Large" for HTTP V1)
96+
/// - Content-Type header set to application/json
97+
/// </returns>
98+
/// <exception cref="InvalidOperationException">Thrown when emulatorMode is HttpV2 or invalid value</exception>
99+
/// <remarks>
100+
/// This method only supports REST and HTTP V1 API Gateway modes. For HTTP V2,
101+
/// use <seealso cref="ToHttpApiV2RequestTooLargeResponse"/>.
102+
/// </remarks>
103+
public static APIGatewayProxyResponse ToHttpApiRequestTooLargeResponse(ApiGatewayEmulatorMode emulatorMode)
104+
{
105+
if (emulatorMode == ApiGatewayEmulatorMode.Rest)
106+
{
107+
return new APIGatewayProxyResponse
108+
{
109+
StatusCode = 413,
110+
Body = "{\"message\":\"Request Too Long\"}",
111+
Headers = new Dictionary<string, string>
112+
{
113+
{ "Content-Type", "application/json" }
114+
},
115+
IsBase64Encoded = false
116+
};
117+
}
118+
if (emulatorMode == ApiGatewayEmulatorMode.HttpV1)
119+
{
120+
return new APIGatewayProxyResponse
121+
{
122+
StatusCode = 413,
123+
Body = "{\"message\":\"Request Entity Too Large\"}",
124+
Headers = new Dictionary<string, string>
125+
{
126+
{ "Content-Type", "application/json" }
127+
},
128+
IsBase64Encoded = false
129+
};
130+
}
131+
throw new ArgumentException($"Unsupported API Gateway emulator mode: {emulatorMode}. Only Rest and HttpV1 modes are supported.");
132+
}
133+
86134
/// <summary>
87135
/// Converts an Amazon Lambda InvokeResponse to an APIGatewayHttpApiV2ProxyResponse.
88136
/// </summary>
@@ -209,4 +257,25 @@ public static APIGatewayHttpApiV2ProxyResponse ToHttpApiV2ErrorResponse()
209257
};
210258
}
211259

260+
/// <summary>
261+
/// Creates a standard HTTP API v2 response for a "Request Entity Too Large" (413) error.
262+
/// </summary>
263+
/// <returns>An APIGatewayHttpApiV2ProxyResponse configured with:
264+
/// - Status code 413
265+
/// - JSON error message
266+
/// - Content-Type header set to application/json
267+
/// </returns>
268+
public static APIGatewayHttpApiV2ProxyResponse ToHttpApiV2RequestTooLargeResponse()
269+
{
270+
return new APIGatewayHttpApiV2ProxyResponse
271+
{
272+
StatusCode = 413,
273+
Body = "{\"message\":\"Request Entity Too Large\"}",
274+
Headers = new Dictionary<string, string>
275+
{
276+
{ "Content-Type", "application/json" }
277+
},
278+
IsBase64Encoded = false
279+
};
280+
}
212281
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
namespace Amazon.Lambda.TestTool.Models;
5+
6+
/// <summary>
7+
/// Configuration options for invoking lambda functions.
8+
/// </summary>
9+
public class LambdaOptions
10+
{
11+
/// <summary>
12+
/// Gets or sets the endpoint URL for Lambda function invocations.
13+
/// </summary>
14+
/// <value>
15+
/// A string containing the endpoint URL. Defaults to an empty string.
16+
/// </value>
17+
public string Endpoint { get; set; } = string.Empty;
18+
}

Tools/LambdaTestTool-v2/src/Amazon.Lambda.TestTool/Processes/ApiGatewayEmulatorProcess.cs

+41-48
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static ApiGatewayEmulatorProcess Startup(RunCommandSettings settings, Can
5252
Utils.ConfigureWebApplicationBuilder(builder);
5353

5454
builder.Services.AddApiGatewayEmulatorServices();
55+
builder.Services.AddSingleton<ILambdaClient, LambdaClient>();
5556

5657
var serviceUrl = $"http://{settings.LambdaEmulatorHost}:{settings.ApiGatewayEmulatorPort}";
5758
builder.WebHost.UseUrls(serviceUrl);
@@ -68,7 +69,7 @@ public static ApiGatewayEmulatorProcess Startup(RunCommandSettings settings, Can
6869
app.Logger.LogInformation("The API Gateway Emulator is available at: {ServiceUrl}", serviceUrl);
6970
});
7071

71-
app.Map("/{**catchAll}", async (HttpContext context, IApiGatewayRouteConfigService routeConfigService) =>
72+
app.Map("/{**catchAll}", async (HttpContext context, IApiGatewayRouteConfigService routeConfigService, ILambdaClient lambdaClient) =>
7273
{
7374
var routeConfig = routeConfigService.GetRouteConfig(context.Request.Method, context.Request.Path);
7475
if (routeConfig == null)
@@ -101,38 +102,56 @@ public static ApiGatewayEmulatorProcess Startup(RunCommandSettings settings, Can
101102
PayloadStream = lambdaRequestStream
102103
};
103104

104-
using var lambdaClient = CreateLambdaServiceClient(routeConfig, settings);
105-
var response = await lambdaClient.InvokeAsync(invokeRequest);
106-
107-
if (response.FunctionError == null) // response is successful
105+
try
108106
{
109-
if (settings.ApiGatewayEmulatorMode.Equals(ApiGatewayEmulatorMode.HttpV2))
107+
var endpoint = routeConfig.Endpoint ?? $"http://{settings.LambdaEmulatorHost}:{settings.LambdaEmulatorPort}";
108+
var response = await lambdaClient.InvokeAsync(invokeRequest, endpoint);
109+
110+
if (response.FunctionError == null) // response is successful
110111
{
111-
var lambdaResponse = response.ToApiGatewayHttpApiV2ProxyResponse();
112-
await lambdaResponse.ToHttpResponseAsync(context);
112+
if (settings.ApiGatewayEmulatorMode.Equals(ApiGatewayEmulatorMode.HttpV2))
113+
{
114+
var lambdaResponse = response.ToApiGatewayHttpApiV2ProxyResponse();
115+
await lambdaResponse.ToHttpResponseAsync(context);
116+
}
117+
else
118+
{
119+
var lambdaResponse = response.ToApiGatewayProxyResponse(settings.ApiGatewayEmulatorMode.Value);
120+
await lambdaResponse.ToHttpResponseAsync(context, settings.ApiGatewayEmulatorMode.Value);
121+
}
113122
}
114123
else
115124
{
116-
var lambdaResponse = response.ToApiGatewayProxyResponse(settings.ApiGatewayEmulatorMode.Value);
117-
await lambdaResponse.ToHttpResponseAsync(context, settings.ApiGatewayEmulatorMode.Value);
125+
// For errors that happen within the function they still come back as 200 status code (they dont throw exception) but have FunctionError populated.
126+
// Api gateway just displays them as an internal server error, so we convert them to the correct error response here.
127+
if (settings.ApiGatewayEmulatorMode.Equals(ApiGatewayEmulatorMode.HttpV2))
128+
{
129+
var lambdaResponse = InvokeResponseExtensions.ToHttpApiV2ErrorResponse();
130+
await lambdaResponse.ToHttpResponseAsync(context);
131+
}
132+
else
133+
{
134+
var lambdaResponse = InvokeResponseExtensions.ToApiGatewayErrorResponse(settings.ApiGatewayEmulatorMode.Value);
135+
await lambdaResponse.ToHttpResponseAsync(context, settings.ApiGatewayEmulatorMode.Value);
136+
}
118137
}
119138
}
120-
else
139+
catch (AmazonLambdaException e)
121140
{
122-
// For function errors, api gateway just displays them as an internal server error, so we convert them to the correct error response here.
123-
124-
if (settings.ApiGatewayEmulatorMode.Equals(ApiGatewayEmulatorMode.HttpV2))
141+
if (e.ErrorCode == Exceptions.RequestEntityTooLargeException)
125142
{
126-
var lambdaResponse = InvokeResponseExtensions.ToHttpApiV2ErrorResponse();
127-
await lambdaResponse.ToHttpResponseAsync(context);
128-
}
129-
else
130-
{
131-
var lambdaResponse = InvokeResponseExtensions.ToApiGatewayErrorResponse(settings.ApiGatewayEmulatorMode.Value);
132-
await lambdaResponse.ToHttpResponseAsync(context, settings.ApiGatewayEmulatorMode.Value);
143+
if (settings.ApiGatewayEmulatorMode.Equals(ApiGatewayEmulatorMode.HttpV2))
144+
{
145+
var lambdaResponse = InvokeResponseExtensions.ToHttpApiV2RequestTooLargeResponse();
146+
await lambdaResponse.ToHttpResponseAsync(context);
147+
}
148+
else
149+
{
150+
var lambdaResponse = InvokeResponseExtensions.ToHttpApiRequestTooLargeResponse(settings.ApiGatewayEmulatorMode.Value);
151+
await lambdaResponse.ToHttpResponseAsync(context, settings.ApiGatewayEmulatorMode.Value);
152+
}
133153
}
134154
}
135-
136155
});
137156

138157
var runTask = app.RunAsync(cancellationToken);
@@ -144,30 +163,4 @@ public static ApiGatewayEmulatorProcess Startup(RunCommandSettings settings, Can
144163
ServiceUrl = serviceUrl
145164
};
146165
}
147-
148-
/// <summary>
149-
/// Creates an Amazon Lambda service client with the specified configuration.
150-
/// </summary>
151-
/// <param name="routeConfig">The API Gateway route configuration containing the endpoint information.
152-
/// If the endpoint is specified in routeConfig, it will be used as the service URL.</param>
153-
/// <param name="settings">The run command settings containing host and port information.
154-
/// If routeConfig endpoint is null, the service URL will be constructed using settings.Host and settings.Port.</param>
155-
/// <returns>An instance of IAmazonLambda configured with the specified endpoint and credentials.</returns>
156-
/// <remarks>
157-
/// The function uses hard-coded AWS credentials ("accessKey", "secretKey") for authentication since they are not actually being used.
158-
/// The service URL is determined by either:
159-
/// - Using routeConfig.Endpoint if it's not null
160-
/// - Combining settings.Host and settings.Port if routeConfig.Endpoint is null
161-
/// </remarks>
162-
private static IAmazonLambda CreateLambdaServiceClient(ApiGatewayRouteConfig routeConfig, RunCommandSettings settings)
163-
{
164-
var endpoint = routeConfig.Endpoint ?? $"http://{settings.LambdaEmulatorHost}:{settings.LambdaEmulatorPort}";
165-
166-
var lambdaConfig = new AmazonLambdaConfig
167-
{
168-
ServiceURL = endpoint
169-
};
170-
171-
return new AmazonLambdaClient(new Amazon.Runtime.BasicAWSCredentials("accessKey", "secretKey"), lambdaConfig);
172-
}
173166
}

0 commit comments

Comments
 (0)