Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .autover/changes/f0d5a912-bcfa-4244-96cb-ac3c847f877c.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"Projects": [
{
"Name": "Amazon.Lambda.AspNetCoreServer",
"Type": "Major",
"ChangelogMessages": [
"[Breaking] Update build targets from .NET 6 and 8 to .NET 8 and 10",
"[Preview] Add support for Lambda Response Streaming enabled by setting the EnableResponseStreaming property from the base class AbstractAspNetCoreFunction"
]
},
{
"Name": "Amazon.Lambda.AspNetCoreServer.Hosting",
"Type": "Major",
"ChangelogMessages": [
"[Breaking] Update build targets from .NET 6 and 8 to .NET 8 and 10",
"[Preview] Add support for Lambda Response Streaming enabled by setting the EnableResponseStreaming property on the HostingOptions object passed into the AddAWSLambdaHosting method"
]
},
{
"Name": "Amazon.Lambda.Logging.AspNetCore",
"Type": "Major",
"ChangelogMessages": [
"[Breaking] Update build targets from .NET 6 and 8 to .NET 8 and 10"
]
}
]
}
17 changes: 16 additions & 1 deletion Libraries/Libraries.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.3.11512.155 d18.3
VisualStudioVersion = 18.3.11512.155
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12}"
EndProject
Expand Down Expand Up @@ -153,6 +153,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCustomAuthorizerApp", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ResponseStreamingFunctionHandlers", "test\Amazon.Lambda.RuntimeSupport.Tests\ResponseStreamingFunctionHandlers\ResponseStreamingFunctionHandlers.csproj", "{E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreStreamingApiGatewayTest", "test\Amazon.Lambda.RuntimeSupport.Tests\AspNetCoreStreamingApiGatewayTest\AspNetCoreStreamingApiGatewayTest.csproj", "{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -955,6 +957,18 @@ Global
{E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|x64.Build.0 = Release|Any CPU
{E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|x86.ActiveCfg = Release|Any CPU
{E404A7AC-812B-BC03-CA76-02C0BC2BA7F9}.Release|x86.Build.0 = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x64.ActiveCfg = Debug|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x64.Build.0 = Debug|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x86.ActiveCfg = Debug|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Debug|x86.Build.0 = Debug|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|Any CPU.Build.0 = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x64.ActiveCfg = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x64.Build.0 = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x86.ActiveCfg = Release|Any CPU
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1030,6 +1044,7 @@ Global
{8EEDD576-7FC4-4FAC-A5A2-F58562753A53} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
{3BFA4B73-BA61-4578-833B-C5B3A16EDA9E} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69}
{E404A7AC-812B-BC03-CA76-02C0BC2BA7F9} = {B5BD0336-7D08-492C-8489-42C987E29B39}
{0768FA72-CF49-2B59-BC4C-E4CE579E5D93} = {B5BD0336-7D08-492C-8489-42C987E29B39}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {503678A4-B8D1-4486-8915-405A3E9CF0EB}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<PropertyGroup>
<Description>Package for running ASP.NET Core applications using the Minimal API style as a AWS Lambda function.</Description>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<Version>1.10.0</Version>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ namespace Amazon.Lambda.AspNetCoreServer.Hosting;
/// </summary>
public class HostingOptions
{
internal const string ParameterizedPreviewMessage =
"Response streaming is in preview till a new version of .NET Lambda runtime client that supports response streaming " +
"has been deployed to the .NET Lambda managed runtime. Till deployment has been made the feature can be used by deploying as an " +
"executable including the latest version of Amazon.Lambda.RuntimeSupport and setting the \"EnablePreviewFeatures\" in the Lambda " +
"project file to \"true\"";

/// <summary>
/// The ILambdaSerializer used by Lambda to convert the incoming event JSON into the .NET event type and serialize the .NET response type
/// back to JSON to return to Lambda.
Expand All @@ -27,6 +33,15 @@ public class HostingOptions
/// </summary>
public bool IncludeUnhandledExceptionDetailInResponse { get; set; } = false;

/// <summary>
/// When true, the Lambda hosting server enables Lambda response streaming behavior
/// when invoking <c>FunctionHandlerAsync</c>. In streaming mode,
/// <c>FunctionHandlerAsync</c> writes directly to the Lambda response stream and
/// returns <c>null</c>. Requires net8.0 or later.
/// </summary>
[System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
public bool EnableResponseStreaming { get; set; } = false;

/// <summary>
/// Callback invoked after request marshalling to customize the HTTP request feature.
/// Receives the IHttpRequestFeature, Lambda request object, and ILambdaContext.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

namespace Amazon.Lambda.AspNetCoreServer.Hosting.Internal;

#if NET8_0_OR_GREATER
/// <summary>
/// Helper class for storing Requests for
/// <see cref="ServiceCollectionExtensions.AddAWSLambdaBeforeSnapshotRequest"/>
Expand All @@ -14,4 +13,3 @@ internal class GetBeforeSnapshotRequestsCollector
{
public HttpRequestMessage? Request { get; set; }
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,21 @@ public APIGatewayHttpApiV2LambdaRuntimeSupportServer(IServiceProvider servicePro
/// <returns></returns>
protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceProvider)
{
var handler = new APIGatewayHttpApiV2MinimalApi(serviceProvider).FunctionHandlerAsync;
return HandlerWrapper.GetHandlerWrapper(handler, this.Serializer);
var handler = new APIGatewayHttpApiV2MinimalApi(serviceProvider);
#pragma warning disable CA2252
var hostingOptions = serviceProvider.GetService<HostingOptions>();
handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false;
#pragma warning restore CA2252
Func<APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest, ILambdaContext, Task<APIGatewayEvents.APIGatewayHttpApiV2ProxyResponse>> bufferedHandler = handler.FunctionHandlerAsync;
return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer);
}

/// <summary>
/// Create the APIGatewayHttpApiV2ProxyFunction passing in the ASP.NET Core application's IServiceProvider
/// </summary>
public class APIGatewayHttpApiV2MinimalApi : APIGatewayHttpApiV2ProxyFunction
{
#if NET8_0_OR_GREATER
private readonly IEnumerable<GetBeforeSnapshotRequestsCollector> _beforeSnapshotRequestsCollectors;
#endif
private readonly HostingOptions? _hostingOptions;

/// <summary>
Expand All @@ -99,9 +102,7 @@ public class APIGatewayHttpApiV2MinimalApi : APIGatewayHttpApiV2ProxyFunction
public APIGatewayHttpApiV2MinimalApi(IServiceProvider serviceProvider)
: base(serviceProvider)
{
#if NET8_0_OR_GREATER
_beforeSnapshotRequestsCollectors = serviceProvider.GetServices<GetBeforeSnapshotRequestsCollector>();
#endif

// Retrieve HostingOptions from service provider (may be null for backward compatibility)
_hostingOptions = serviceProvider.GetService<HostingOptions>();
Expand All @@ -127,14 +128,12 @@ public APIGatewayHttpApiV2MinimalApi(IServiceProvider serviceProvider)
}
}

#if NET8_0_OR_GREATER
protected override IEnumerable<HttpRequestMessage> GetBeforeSnapshotRequests()
{
foreach (var collector in _beforeSnapshotRequestsCollectors)
if (collector.Request != null)
yield return collector.Request;
}
#endif

protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayHttpApiV2ProxyRequest lambdaRequest, ILambdaContext lambdaContext)
{
Expand Down Expand Up @@ -208,18 +207,21 @@ public APIGatewayRestApiLambdaRuntimeSupportServer(IServiceProvider serviceProvi
/// <returns></returns>
protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceProvider)
{
var handler = new APIGatewayRestApiMinimalApi(serviceProvider).FunctionHandlerAsync;
return HandlerWrapper.GetHandlerWrapper(handler, this.Serializer);
var handler = new APIGatewayRestApiMinimalApi(serviceProvider);
#pragma warning disable CA2252
var hostingOptions = serviceProvider.GetService<HostingOptions>();
handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false;
#pragma warning restore CA2252
Func<APIGatewayEvents.APIGatewayProxyRequest, ILambdaContext, Task<APIGatewayEvents.APIGatewayProxyResponse>> bufferedHandler = handler.FunctionHandlerAsync;
return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer);
}

/// <summary>
/// Create the APIGatewayProxyFunction passing in the ASP.NET Core application's IServiceProvider
/// </summary>
public class APIGatewayRestApiMinimalApi : APIGatewayProxyFunction
{
#if NET8_0_OR_GREATER
private readonly IEnumerable<GetBeforeSnapshotRequestsCollector> _beforeSnapshotRequestsCollectors;
#endif
private readonly HostingOptions? _hostingOptions;

/// <summary>
Expand All @@ -229,9 +231,7 @@ public class APIGatewayRestApiMinimalApi : APIGatewayProxyFunction
public APIGatewayRestApiMinimalApi(IServiceProvider serviceProvider)
: base(serviceProvider)
{
#if NET8_0_OR_GREATER
_beforeSnapshotRequestsCollectors = serviceProvider.GetServices<GetBeforeSnapshotRequestsCollector>();
#endif

// Retrieve HostingOptions from service provider (may be null for backward compatibility)
_hostingOptions = serviceProvider.GetService<HostingOptions>();
Expand All @@ -257,14 +257,12 @@ public APIGatewayRestApiMinimalApi(IServiceProvider serviceProvider)
}
}

#if NET8_0_OR_GREATER
protected override IEnumerable<HttpRequestMessage> GetBeforeSnapshotRequests()
{
foreach (var collector in _beforeSnapshotRequestsCollectors)
if (collector.Request != null)
yield return collector.Request;
}
#endif

protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, APIGatewayEvents.APIGatewayProxyRequest lambdaRequest, ILambdaContext lambdaContext)
{
Expand Down Expand Up @@ -338,18 +336,21 @@ public ApplicationLoadBalancerLambdaRuntimeSupportServer(IServiceProvider servic
/// <returns></returns>
protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceProvider)
{
var handler = new ApplicationLoadBalancerMinimalApi(serviceProvider).FunctionHandlerAsync;
return HandlerWrapper.GetHandlerWrapper(handler, this.Serializer);
var handler = new ApplicationLoadBalancerMinimalApi(serviceProvider);
#pragma warning disable CA2252
var hostingOptions = serviceProvider.GetService<HostingOptions>();
handler.EnableResponseStreaming = hostingOptions?.EnableResponseStreaming ?? false;
#pragma warning restore CA2252
Func<ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest, ILambdaContext, Task<ApplicationLoadBalancerEvents.ApplicationLoadBalancerResponse>> bufferedHandler = handler.FunctionHandlerAsync;
return HandlerWrapper.GetHandlerWrapper(bufferedHandler, this.Serializer);
}

/// <summary>
/// Create the ApplicationLoadBalancerFunction passing in the ASP.NET Core application's IServiceProvider
/// </summary>
public class ApplicationLoadBalancerMinimalApi : ApplicationLoadBalancerFunction
{
#if NET8_0_OR_GREATER
private readonly IEnumerable<GetBeforeSnapshotRequestsCollector> _beforeSnapshotRequestsCollectors;
#endif
private readonly HostingOptions? _hostingOptions;

/// <summary>
Expand All @@ -359,9 +360,7 @@ public class ApplicationLoadBalancerMinimalApi : ApplicationLoadBalancerFunction
public ApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider)
: base(serviceProvider)
{
#if NET8_0_OR_GREATER
_beforeSnapshotRequestsCollectors = serviceProvider.GetServices<GetBeforeSnapshotRequestsCollector>();
#endif

// Retrieve HostingOptions from service provider (may be null for backward compatibility)
_hostingOptions = serviceProvider.GetService<HostingOptions>();
Expand All @@ -387,14 +386,12 @@ public ApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider)
}
}

#if NET8_0_OR_GREATER
protected override IEnumerable<HttpRequestMessage> GetBeforeSnapshotRequests()
{
foreach (var collector in _beforeSnapshotRequestsCollectors)
if (collector.Request != null)
yield return collector.Request;
}
#endif

protected override void PostMarshallRequestFeature(IHttpRequestFeature aspNetCoreRequestFeature, ApplicationLoadBalancerEvents.ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ public static IServiceCollection AddAWSLambdaHosting(this IServiceCollection ser
return services;
}

#if NET8_0_OR_GREATER
/// <summary>
/// Adds a <see cref="HttpRequestMessage"/>> that will be used to invoke
/// Routes in your lambda function in order to initialize the ASP.NET Core and Lambda pipelines
Expand Down Expand Up @@ -142,7 +141,6 @@ public static IServiceCollection AddAWSLambdaBeforeSnapshotRequest(this IService

return services;
}
#endif

private static bool TryLambdaSetup(IServiceCollection services, LambdaEventSource eventSource, Action<HostingOptions>? configure, out HostingOptions? hostingOptions)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,46 @@ private protected override void InternalCustomResponseExceptionHandling(APIGatew
apiGatewayResponse.SetHeaderValues("ErrorType", ex.GetType().Name, false);
}

#if NET8_0_OR_GREATER
/// <summary>
/// Override for HTTP API v2 to use single-value <c>headers</c> in the streaming prelude
/// instead of <c>multiValueHeaders</c>. API Gateway HTTP API v2 expects the <c>headers</c>
/// format; using <c>multiValueHeaders</c> causes a 500 Internal Server Error.
/// </summary>
[System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
protected override Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature)
{
var prelude = new Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude
{
StatusCode = (System.Net.HttpStatusCode)(responseFeature.StatusCode != 0 ? responseFeature.StatusCode : 200)
};

foreach (var kvp in responseFeature.Headers)
{
if (string.Equals(kvp.Key, "Content-Length", StringComparison.OrdinalIgnoreCase) ||
string.Equals(kvp.Key, "Transfer-Encoding", StringComparison.OrdinalIgnoreCase))
{
continue;
}

if (string.Equals(kvp.Key, "Set-Cookie", StringComparison.OrdinalIgnoreCase))
{
foreach (var value in kvp.Value)
{
prelude.Cookies.Add(value);
}
}
else
{
// HTTP API v2 uses single-value headers. Join multiple values with ", ".
prelude.Headers[kvp.Key] = string.Join(", ", kvp.Value);
}
}

return prelude;
}
#endif

/// <summary>
/// Convert the JSON document received from API Gateway into the InvokeFeatures object.
/// InvokeFeatures is then passed into IHttpApplication to create the ASP.NET Core request objects.
Expand Down
Loading
Loading