Skip to content

Commit b476fcd

Browse files
committed
Add Snapstart helper to AbstractAspNetCoreFunction
1 parent bfa3ca9 commit b476fcd

10 files changed

Lines changed: 325 additions & 100 deletions

File tree

Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Amazon.Lambda.AspNetCoreServer.Hosting.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
<ProjectReference Include="..\Amazon.Lambda.APIGatewayEvents\Amazon.Lambda.APIGatewayEvents.csproj" />
2828
<ProjectReference Include="..\Amazon.Lambda.AspNetCoreServer\Amazon.Lambda.AspNetCoreServer.csproj" />
2929
<ProjectReference Include="..\Amazon.Lambda.RuntimeSupport\Amazon.Lambda.RuntimeSupport.csproj" />
30-
</ItemGroup>
30+
<ProjectReference Include="..\Amazon.Lambda.Serialization.SystemTextJson\Amazon.Lambda.Serialization.SystemTextJson.csproj" />
31+
32+
</ItemGroup>
3133

3234
</Project>

Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/Internal/LambdaSnapstartExecuteRequestsBeforeSnapshotHelper.cs

Lines changed: 6 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Text.Json.Serialization;
99
using Amazon.Lambda.APIGatewayEvents;
1010
using Amazon.Lambda.ApplicationLoadBalancerEvents;
11+
using Amazon.Lambda.AspNetCoreServer.Internal;
1112
using Amazon.Lambda.Core;
1213
using Amazon.Lambda.RuntimeSupport;
1314
using Amazon.Lambda.RuntimeSupport.Helpers;
@@ -101,8 +102,6 @@ private class HelperLambdaContext : ILambdaContext, ICognitoIdentity, IClientCon
101102

102103
private static class SnapstartHelperLambdaRequests
103104
{
104-
private static readonly Uri _baseUri = new Uri("http://localhost");
105-
106105
public static async Task ExecuteSnapstartInitRequests(string jsonRequest, int times, HandlerWrapper handlerWrapper)
107106
{
108107
var dummyRequest = new InvocationRequest(
@@ -124,108 +123,20 @@ public static async Task ExecuteSnapstartInitRequests(string jsonRequest, int ti
124123

125124
public static async Task<string> SerializeToJson(HttpRequestMessage request, LambdaEventSource lambdaType)
126125
{
127-
if (null == request.RequestUri)
128-
{
129-
throw new ArgumentException($"{nameof(HttpRequestMessage.RequestUri)} must be set.", nameof(request));
130-
}
131-
132-
if (request.RequestUri.IsAbsoluteUri)
133-
{
134-
throw new ArgumentException($"{nameof(HttpRequestMessage.RequestUri)} must be relative.", nameof(request));
135-
}
136-
137-
// make request absolut (relative to localhost) otherwise parsing the query will not work
138-
request.RequestUri = new Uri(_baseUri, request.RequestUri);
139-
140-
var duckRequest = new
141-
{
142-
Body = await ReadContent(request),
143-
Headers = request.Headers
144-
.ToDictionary(
145-
kvp => kvp.Key,
146-
kvp => kvp.Value.FirstOrDefault(),
147-
StringComparer.OrdinalIgnoreCase),
148-
HttpMethod = request.Method.ToString(),
149-
Path = "/" + _baseUri.MakeRelativeUri(request.RequestUri),
150-
RawQuery = request.RequestUri?.Query,
151-
Query = QueryHelpers.ParseNullableQuery(request.RequestUri?.Query)
152-
};
153-
154-
string translatedRequestJson = lambdaType switch
126+
var result = lambdaType switch
155127
{
156128
LambdaEventSource.ApplicationLoadBalancer =>
157-
JsonSerializer.Serialize(
158-
new ApplicationLoadBalancerRequest
159-
{
160-
Body = duckRequest.Body,
161-
Headers = duckRequest.Headers,
162-
Path = duckRequest.Path,
163-
HttpMethod = duckRequest.HttpMethod,
164-
QueryStringParameters = duckRequest.Query?.ToDictionary(k => k.Key, v => v.Value.ToString())
165-
},
166-
LambdaRequestTypeClasses.Default.ApplicationLoadBalancerRequest),
129+
await HttpRequestMessageSerializer.SerializeToJson<ApplicationLoadBalancerRequest>(request),
167130
LambdaEventSource.HttpApi =>
168-
JsonSerializer.Serialize(
169-
new APIGatewayHttpApiV2ProxyRequest
170-
{
171-
Body = duckRequest.Body,
172-
Headers = duckRequest.Headers,
173-
RawPath = duckRequest.Path,
174-
RequestContext = new APIGatewayHttpApiV2ProxyRequest.ProxyRequestContext
175-
{
176-
Http = new APIGatewayHttpApiV2ProxyRequest.HttpDescription
177-
{
178-
Method = duckRequest.HttpMethod,
179-
Path = duckRequest.Path
180-
}
181-
},
182-
QueryStringParameters = duckRequest.Query?.ToDictionary(k => k.Key, v => v.Value.ToString()),
183-
RawQueryString = duckRequest.RawQuery
184-
},
185-
LambdaRequestTypeClasses.Default.APIGatewayHttpApiV2ProxyRequest),
131+
await HttpRequestMessageSerializer.SerializeToJson<APIGatewayHttpApiV2ProxyRequest>(request),
186132
LambdaEventSource.RestApi =>
187-
JsonSerializer.Serialize(
188-
new APIGatewayProxyRequest
189-
{
190-
Body = duckRequest.Body,
191-
Headers = duckRequest.Headers,
192-
Path = duckRequest.Path,
193-
HttpMethod = duckRequest.HttpMethod,
194-
RequestContext = new APIGatewayProxyRequest.ProxyRequestContext
195-
{
196-
HttpMethod = duckRequest.HttpMethod
197-
},
198-
QueryStringParameters = duckRequest.Query?.ToDictionary(k => k.Key, v => v.Value.ToString())
199-
},
200-
LambdaRequestTypeClasses.Default.APIGatewayProxyRequest),
133+
await HttpRequestMessageSerializer.SerializeToJson<APIGatewayProxyRequest>(request),
201134
_ => throw new NotImplementedException(
202135
$"Unknown {nameof(LambdaEventSource)}: {Enum.GetName(lambdaType)}")
203136
};
204137

205-
return translatedRequestJson;
206-
}
207-
208-
private static async Task<string> ReadContent(HttpRequestMessage r)
209-
{
210-
if (r.Content == null)
211-
return string.Empty;
212-
213-
return await r.Content.ReadAsStringAsync();
138+
return result;
214139
}
215140
}
216141
}
217-
218-
219-
[JsonSourceGenerationOptions(WriteIndented = true)]
220-
[JsonSerializable(typeof(ApplicationLoadBalancerRequest))]
221-
[JsonSerializable(typeof(APIGatewayHttpApiV2ProxyRequest))]
222-
[JsonSerializable(typeof(APIGatewayProxyRequest))]
223-
[JsonSerializable(typeof(APIGatewayProxyRequest.ClientCertValidity))]
224-
[JsonSerializable(typeof(APIGatewayProxyRequest.ProxyRequestClientCert))]
225-
[JsonSerializable(typeof(APIGatewayProxyRequest.ProxyRequestContext))]
226-
[JsonSerializable(typeof(APIGatewayProxyRequest.RequestIdentity))]
227-
228-
internal partial class LambdaRequestTypeClasses : JsonSerializerContext
229-
{
230-
}
231142
#endif

Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public static IServiceCollection AddAWSLambdaHosting(this IServiceCollection ser
9898
/// <see cref="HttpRequestMessage.RequestUri"/>.
9999
/// <para />.
100100
/// Be aware that this will invoke your applications function handler code
101-
/// multiple times. Additionally, tt uses a mock <see cref="ILambdaContext"/>
101+
/// multiple times. Additionally, it uses a mock <see cref="ILambdaContext"/>
102102
/// which may not be fully populated.
103103
/// <para />
104104
/// This method automatically registers with <see cref="SnapshotRestore.RegisterBeforeSnapshot"/>.

Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
using System;
99
using System.Collections.Generic;
1010
using System.IO;
11+
using System.Linq;
12+
using System.Net.Http;
1113
using System.Reflection;
1214
using System.Text;
1315
using System.Threading.Tasks;
@@ -251,6 +253,46 @@ protected virtual IHostBuilder CreateHostBuilder()
251253
return builder;
252254
}
253255

256+
#if NET8_0_OR_GREATER
257+
/// <summary>
258+
/// Return one or more <see cref="HttpRequestMessage"/>s that will be used to invoke
259+
/// Routes in your lambda function in order to initialize the asp.net and lambda pipelines
260+
/// during <see cref="SnapshotRestore.RegisterBeforeSnapshot"/>,
261+
/// improving the performance gains offered by SnapStart.
262+
/// <para />
263+
/// The returned <see cref="HttpRequestMessage"/>s must have a relative
264+
/// <see cref="HttpRequestMessage.RequestUri"/>.
265+
/// <para />.
266+
/// Be aware that this will invoke your applications function handler code
267+
/// multiple times. Additionally, it uses a mock <see cref="ILambdaContext"/>
268+
/// which may not be fully populated.
269+
/// <para />
270+
/// This method automatically registers with <see cref="SnapshotRestore.RegisterBeforeSnapshot"/>.
271+
/// <para />
272+
/// If SnapStart is not enabled, then this method is never invoked.
273+
/// <para />
274+
/// Example:
275+
/// <para />
276+
/// <code>
277+
/// <![CDATA[
278+
/// public class HttpV2LambdaFunction : APIGatewayHttpApiV2ProxyFunction<Startup>
279+
/// {
280+
/// protected override IEnumerable<HttpRequestMessage> RegisterBeforeSnapshotRequest() =>
281+
/// [
282+
/// new HttpRequestMessage
283+
/// {
284+
/// RequestUri = new Uri("/api/ExampleSnapstartInit"),
285+
/// Method = HttpMethod.Get
286+
/// }
287+
/// ];
288+
/// }
289+
/// ]]>
290+
/// </code>
291+
/// </summary>
292+
protected virtual IEnumerable<HttpRequestMessage> RegisterBeforeSnapshotRequest() =>
293+
Enumerable.Empty<HttpRequestMessage>();
294+
#endif
295+
254296
private protected bool IsStarted
255297
{
256298
get
@@ -284,6 +326,37 @@ protected void Start()
284326
"instead of ConfigureWebHostDefaults to make sure the property Lambda services are registered.");
285327
}
286328
_logger = ActivatorUtilities.CreateInstance<Logger<AbstractAspNetCoreFunction<TREQUEST, TRESPONSE>>>(this._hostServices);
329+
330+
#if NET8_0_OR_GREATER
331+
332+
Amazon.Lambda.Core.SnapshotRestore.RegisterBeforeSnapshot(async () =>
333+
{
334+
var beforeSnapstartRequests = RegisterBeforeSnapshotRequest();
335+
336+
foreach (var httpRequest in beforeSnapstartRequests)
337+
{
338+
var invokeTimes = 5;
339+
340+
var json = await HttpRequestMessageSerializer.SerializeToJson<TREQUEST>(httpRequest);
341+
342+
var request = HttpRequestMessageSerializer.Deserialize<TREQUEST>(json);
343+
344+
InvokeFeatures features = new InvokeFeatures();
345+
MarshallRequest(features, request, new SnapStartEmptyLambdaContext());
346+
347+
var context = CreateContext(features);
348+
349+
var lambdaContext = new SnapStartEmptyLambdaContext();
350+
351+
for (var i = 0; i < invokeTimes; i++)
352+
{
353+
await ProcessRequest(lambdaContext, context, features);
354+
}
355+
}
356+
});
357+
358+
#endif
359+
287360
}
288361

289362
/// <summary>

Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
<ItemGroup>
3333
<FrameworkReference Include="Microsoft.AspNetCore.App" />
34+
<ProjectReference Include="..\Amazon.Lambda.RuntimeSupport\Amazon.Lambda.RuntimeSupport.csproj" />
3435
<ProjectReference Include="..\Amazon.Lambda.Serialization.SystemTextJson\Amazon.Lambda.Serialization.SystemTextJson.csproj" />
3536
</ItemGroup>
3637

0 commit comments

Comments
 (0)