diff --git a/.autover/changes/9e75413c-9cb6-4521-aaca-09b557fff2b3.json b/.autover/changes/9e75413c-9cb6-4521-aaca-09b557fff2b3.json
new file mode 100644
index 000000000..4b47d49fa
--- /dev/null
+++ b/.autover/changes/9e75413c-9cb6-4521-aaca-09b557fff2b3.json
@@ -0,0 +1,49 @@
+{
+ "Projects": [
+ {
+ "Name": "Amazon.Lambda.Core",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Lambda response streaming is now available as GA. The RequiresPreviewFeatures attribute has been removed",
+ "The LambdaLogger.ConfigureStructuredLogging API has been deployed to the managed runtime. The RequiresPreviewFeatures attribute has been removed"
+ ]
+ },
+ {
+ "Name": "Amazon.Lambda.Annotations",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Updated Amazon.Lambda.Core dependency to include Lambda Response Streaming support."
+ ]
+ },
+ {
+ "Name": "Amazon.Lambda.AspNetCoreServer",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Removed RequiresPreviewFeatures attribute for Lambda Response Streaming.",
+ "Updated Amazon.Lambda.Core dependency to include Lambda Response Streaming support."
+ ]
+ },
+ {
+ "Name": "Amazon.Lambda.AspNetCoreServer.Hosting",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Removed RequiresPreviewFeatures attribute for Lambda Response Streaming.",
+ "Updated Amazon.Lambda.Core dependency to include Lambda Response Streaming support."
+ ]
+ },
+ {
+ "Name": "Amazon.Lambda.PowerShellHost",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Updated Amazon.Lambda.Core dependency to include Lambda Response Streaming support."
+ ]
+ },
+ {
+ "Name": "Amazon.Lambda.RuntimeSupport",
+ "Type": "Minor",
+ "ChangelogMessages": [
+ "Updated Amazon.Lambda.Core dependency to include Lambda Response Streaming support."
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs
index cf7ef34ba..3389368c7 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer.Hosting/HostingOptions.cs
@@ -39,7 +39,6 @@ public class HostingOptions
/// FunctionHandlerAsync writes directly to the Lambda response stream and
/// returns null. Requires net8.0 or later.
///
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
public bool EnableResponseStreaming { get; set; } = false;
///
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs
index 819306c30..e9578068c 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayHttpApiV2ProxyFunction.cs
@@ -59,7 +59,6 @@ private protected override void InternalCustomResponseExceptionHandling(APIGatew
/// instead of multiValueHeaders. API Gateway HTTP API v2 expects the headers
/// format; using multiValueHeaders causes a 500 Internal Server Error.
///
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
protected override Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature)
{
var prelude = new Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs
index 1398239de..4e3e7a35d 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs
@@ -113,7 +113,6 @@ private protected override void InternalCustomResponseExceptionHandling(APIGatew
///
/// The ASP.NET Core response feature for the current invocation.
/// A populated .
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
protected override Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature)
{
var prelude = new Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs
index 51c930333..55b69f67f 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs
@@ -24,12 +24,6 @@ namespace Amazon.Lambda.AspNetCoreServer
///
public abstract class AbstractAspNetCoreFunction
{
- 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\"";
-
///
/// Key to access the ILambdaContext object from the HttpContext.Items collection.
///
@@ -206,7 +200,6 @@ public void RegisterResponseContentEncodingForContentEncoding(string contentEnco
/// buffering it and returning a typed response object (which will be null).
/// Requires net8.0 or later.
///
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
public virtual bool EnableResponseStreaming { get; set; } = false;
@@ -678,7 +671,6 @@ protected virtual void PostMarshallResponseFeature(IHttpResponseFeature aspNetCo
///
/// The ASP.NET Core response feature for the current invocation.
/// A populated .
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
protected abstract Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature);
///
@@ -689,7 +681,6 @@ protected virtual void PostMarshallResponseFeature(IHttpResponseFeature aspNetCo
///
/// The HTTP response prelude containing status code and headers.
/// A writable for the response body.
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
protected virtual System.IO.Stream CreateLambdaResponseStream(
Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude)
{
@@ -701,7 +692,6 @@ protected virtual System.IO.Stream CreateLambdaResponseStream(
/// is true. Writes the response directly to a
/// .
///
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
private async Task ExecuteStreamingRequestAsync(InvokeFeatures features)
{
var responseFeature = (IHttpResponseFeature)features;
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs
index 2bb08c711..6a2805088 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs
@@ -225,7 +225,6 @@ private protected override void InternalCustomResponseExceptionHandling(Applicat
}
///
- [System.Runtime.Versioning.RequiresPreviewFeatures(ParameterizedPreviewMessage)]
protected override HttpResponseStreamPrelude BuildStreamingPrelude(IHttpResponseFeature responseFeature) => throw new NotImplementedException();
private string GetSingleHeaderValue(ApplicationLoadBalancerRequest request, string headerName)
diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs
index 03ec929a1..86678b50e 100644
--- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs
+++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/StreamingResponseBodyFeature.cs
@@ -3,7 +3,6 @@
using System;
using System.IO;
using System.IO.Pipelines;
-using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
@@ -20,7 +19,6 @@ namespace Amazon.Lambda.AspNetCoreServer.Internal
/// ; after all writes go directly to the
/// obtained from the stream opener delegate.
///
- [RequiresPreviewFeatures(AbstractAspNetCoreFunction.ParameterizedPreviewMessage)]
internal class StreamingResponseBodyFeature : IHttpResponseBodyFeature
{
private readonly ILogger _logger;
diff --git a/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs b/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs
index 38afe7371..bba419e0f 100644
--- a/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs
+++ b/Libraries/src/Amazon.Lambda.Core/LambdaLogger.cs
@@ -1,6 +1,7 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
using System;
-using System.Reflection.Emit;
-using System.Runtime.Versioning;
using System.Text;
namespace Amazon.Lambda.Core
@@ -139,7 +140,6 @@ public static void Log(string level, Exception exception, string message, params
/// When structured logging is enabled this method will allow overriding the default configuration the Lambda runtime uses for structured logging.
///
/// The options to use for configuring structured logging.
- [RequiresPreviewFeatures("This method is in preview until the latest changes of the .NET Lambda runtime client have been deployed to the Lambda managed runtimes")]
public static void ConfigureStructuredLogging(StructuredLoggingOptions options) => _configureStructuredLoggingAction(options);
internal static void SetConfigureStructuredLoggingAction(Action configureStructuredLoggingAction)
diff --git a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs
index 1a10aa2dc..6f8bf2a4a 100644
--- a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs
+++ b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/HttpResponseStreamPrelude.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Net;
-using System.Runtime.Versioning;
using System.Text.Json;
namespace Amazon.Lambda.Core.ResponseStreaming
@@ -15,7 +14,6 @@ namespace Amazon.Lambda.Core.ResponseStreaming
/// headers, and cookies for the response. The prelude must be sent as the first chunk of the response stream, followed by the response body chunks.
/// This allows you to set the status code and headers for the response before sending any of the response body.
///
- [RequiresPreviewFeatures(LambdaResponseStreamFactory.PreviewMessage)]
public class HttpResponseStreamPrelude
{
///
diff --git a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs
index 83ac446a4..38b95c129 100644
--- a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs
+++ b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStream.cs
@@ -4,7 +4,6 @@
using System;
using System.IO;
-using System.Runtime.Versioning;
using System.Threading;
using System.Threading.Tasks;
@@ -15,7 +14,6 @@ namespace Amazon.Lambda.Core.ResponseStreaming
/// to the Lambda Runtime API. Returned by .
/// Integrates with standard .NET stream consumers such as .
///
- [RequiresPreviewFeatures(LambdaResponseStreamFactory.PreviewMessage)]
public class LambdaResponseStream : Stream
{
private readonly ILambdaResponseStream _responseStream;
diff --git a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs
index 1b9e6d3b6..1c76e837e 100644
--- a/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs
+++ b/Libraries/src/Amazon.Lambda.Core/ResponseStreaming/LambdaResponseStreamFactory.cs
@@ -3,22 +3,14 @@
#if NET8_0_OR_GREATER
using System;
using System.IO;
-using System.Runtime.Versioning;
namespace Amazon.Lambda.Core.ResponseStreaming
{
///
/// Factory to create Lambda response streams for writing streaming responses in AWS Lambda functions. The created streams are write-only and non-seekable.
///
- [RequiresPreviewFeatures(LambdaResponseStreamFactory.PreviewMessage)]
public class LambdaResponseStreamFactory
{
- internal const string PreviewMessage =
- "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\"";
-
internal const string UninitializedFactoryMessage =
"LambdaResponseStreamFactory is not initialized. This is caused by mismatch versions of Amazon.Lambda.Core and Amazon.Lambda.RuntimeSupport. " +
"Update both packages to the current version to address the issue.";
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingHostingTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingHostingTests.cs
index f70f91629..8d0717928 100644
--- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingHostingTests.cs
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingHostingTests.cs
@@ -1,8 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-
-using System.Runtime.Versioning;
using Amazon.Lambda.AspNetCoreServer.Hosting.Internal;
using Amazon.Lambda.AspNetCoreServer.Test;
using Amazon.Lambda.Core;
@@ -16,7 +14,6 @@ namespace Amazon.Lambda.AspNetCoreServer.Hosting.Tests;
///
/// Tests for response streaming integration in hosting (Requirement 10).
///
-[RequiresPreviewFeatures]
public class ResponseStreamingHostingTests
{
[Fact]
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingPropertyTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingPropertyTests.cs
index 43ebc4dd4..853f0a0dc 100644
--- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingPropertyTests.cs
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Hosting.Tests/ResponseStreamingPropertyTests.cs
@@ -1,8 +1,6 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
-using System.Runtime.Versioning;
-
using Amazon.Lambda.AspNetCoreServer.Hosting.Internal;
using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
@@ -14,7 +12,6 @@
namespace Amazon.Lambda.AspNetCoreServer.Hosting.Tests;
-[RequiresPreviewFeatures]
public class ResponseStreamingPropertyTests
{
private static IServiceProvider BuildServiceProvider(HostingOptions hostingOptions)
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/BuildStreamingPreludeTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/BuildStreamingPreludeTests.cs
index c2971d0ab..7bca22fa4 100644
--- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/BuildStreamingPreludeTests.cs
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/BuildStreamingPreludeTests.cs
@@ -1,7 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
+using System.Collections.Generic;
+using System.Linq;
using System.Net;
-using System.Runtime.Versioning;
using Amazon.Lambda.AspNetCoreServer.Internal;
using Microsoft.AspNetCore.Http.Features;
@@ -9,15 +10,29 @@
namespace Amazon.Lambda.AspNetCoreServer.Test
{
- [RequiresPreviewFeatures]
public class BuildStreamingPreludeTests
{
+ ///
+ /// Selects which proxy function implementation of BuildStreamingPrelude to exercise.
+ /// The two implementations differ in how non-cookie headers are represented on the prelude:
+ /// the HTTP API v2 function uses the single-value Headers collection while the
+ /// REST API function uses the multi-value MultiValueHeaders collection.
+ ///
+ public enum ProxyFunctionType
+ {
+ /// APIGatewayProxyFunction (REST API) - populates MultiValueHeaders.
+ RestApi,
+
+ /// APIGatewayHttpApiV2ProxyFunction (HTTP API v2) - populates Headers.
+ HttpApiV2
+ }
+
// Subclass that skips host startup entirely and
// just exposes BuildStreamingPrelude directly without needing a running host.
- private class StandalonePreludeBuilder : APIGatewayHttpApiV2ProxyFunction
+ private class HttpApiV2PreludeBuilder : APIGatewayHttpApiV2ProxyFunction
{
// Use the StartupMode.FirstRequest constructor so no host is started eagerly.
- public StandalonePreludeBuilder()
+ public HttpApiV2PreludeBuilder()
: base(StartupMode.FirstRequest) { }
public Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude
@@ -25,10 +40,36 @@ public Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude
=> BuildStreamingPrelude(responseFeature);
}
- private static StandalonePreludeBuilder CreateBuilder() => new StandalonePreludeBuilder();
+ // Subclass that skips host startup entirely and
+ // just exposes BuildStreamingPrelude directly without needing a running host.
+ private class RestApiPreludeBuilder : APIGatewayProxyFunction
+ {
+ // Use the StartupMode.FirstRequest constructor so no host is started eagerly.
+ public RestApiPreludeBuilder()
+ : base(StartupMode.FirstRequest) { }
+
+ public Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude
+ InvokeBuildStreamingPrelude(IHttpResponseFeature responseFeature)
+ => BuildStreamingPrelude(responseFeature);
+ }
+
+ // Helper: invoke BuildStreamingPrelude on the requested proxy function implementation.
+ private static Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude BuildPrelude(
+ ProxyFunctionType functionType, IHttpResponseFeature responseFeature)
+ {
+ switch (functionType)
+ {
+ case ProxyFunctionType.RestApi:
+ return new RestApiPreludeBuilder().InvokeBuildStreamingPrelude(responseFeature);
+ case ProxyFunctionType.HttpApiV2:
+ return new HttpApiV2PreludeBuilder().InvokeBuildStreamingPrelude(responseFeature);
+ default:
+ throw new System.ArgumentOutOfRangeException(nameof(functionType));
+ }
+ }
// Helper: create an InvokeFeatures, set StatusCode and Headers, return as IHttpResponseFeature.
- private static IHttpResponseFeature MakeResponseFeature(int statusCode, System.Collections.Generic.Dictionary headers = null)
+ private static IHttpResponseFeature MakeResponseFeature(int statusCode, Dictionary headers = null)
{
var features = new InvokeFeatures();
var rf = (IHttpResponseFeature)features;
@@ -41,29 +82,100 @@ private static IHttpResponseFeature MakeResponseFeature(int statusCode, System.C
return rf;
}
+ // -----------------------------------------------------------------------
+ // Assertion helpers that check the correct header collection depending on
+ // the proxy function type: MultiValueHeaders for REST API, Headers for HTTP API v2.
+ // -----------------------------------------------------------------------
+
+ // Assert a non-cookie header is present with the given values.
+ private static void AssertHeaderPresent(
+ Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude,
+ ProxyFunctionType functionType, string key, params string[] expectedValues)
+ {
+ if (functionType == ProxyFunctionType.RestApi)
+ {
+ Assert.True(prelude.MultiValueHeaders.ContainsKey(key));
+ Assert.Equal(expectedValues, prelude.MultiValueHeaders[key]);
+
+ // The single-value Headers collection is not used by the REST API implementation.
+ Assert.False(prelude.Headers.ContainsKey(key));
+ }
+ else
+ {
+ Assert.True(prelude.Headers.ContainsKey(key));
+ // HTTP API v2 uses single-value headers, so multiple values are joined with ", ".
+ Assert.Equal(string.Join(", ", expectedValues), prelude.Headers[key]);
+
+ // The multi-value MultiValueHeaders collection is not used by the HTTP API v2 implementation.
+ Assert.False(prelude.MultiValueHeaders.ContainsKey(key));
+ }
+ }
+
+ // Assert a header is absent from whichever collection the implementation uses.
+ private static void AssertHeaderAbsent(
+ Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude,
+ ProxyFunctionType functionType, string key)
+ {
+ if (functionType == ProxyFunctionType.RestApi)
+ {
+ Assert.False(prelude.MultiValueHeaders.ContainsKey(key));
+ }
+ else
+ {
+ Assert.False(prelude.Headers.ContainsKey(key));
+ }
+ }
+
+ // Assert the header collection used by the implementation is empty.
+ private static void AssertHeadersEmpty(
+ Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude,
+ ProxyFunctionType functionType)
+ {
+ if (functionType == ProxyFunctionType.RestApi)
+ {
+ Assert.Empty(prelude.MultiValueHeaders);
+ }
+ else
+ {
+ Assert.Empty(prelude.Headers);
+ }
+ }
+
// -----------------------------------------------------------------------
// 6.1 Status code is copied correctly for values 100–599
// -----------------------------------------------------------------------
[Theory]
- [InlineData(100)]
- [InlineData(200)]
- [InlineData(201)]
- [InlineData(204)]
- [InlineData(301)]
- [InlineData(302)]
- [InlineData(400)]
- [InlineData(401)]
- [InlineData(403)]
- [InlineData(404)]
- [InlineData(500)]
- [InlineData(503)]
- [InlineData(599)]
- public void StatusCode_IsCopiedCorrectly(int statusCode)
+ [InlineData(ProxyFunctionType.RestApi, 100)]
+ [InlineData(ProxyFunctionType.RestApi, 200)]
+ [InlineData(ProxyFunctionType.RestApi, 201)]
+ [InlineData(ProxyFunctionType.RestApi, 204)]
+ [InlineData(ProxyFunctionType.RestApi, 301)]
+ [InlineData(ProxyFunctionType.RestApi, 302)]
+ [InlineData(ProxyFunctionType.RestApi, 400)]
+ [InlineData(ProxyFunctionType.RestApi, 401)]
+ [InlineData(ProxyFunctionType.RestApi, 403)]
+ [InlineData(ProxyFunctionType.RestApi, 404)]
+ [InlineData(ProxyFunctionType.RestApi, 500)]
+ [InlineData(ProxyFunctionType.RestApi, 503)]
+ [InlineData(ProxyFunctionType.RestApi, 599)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 100)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 200)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 201)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 204)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 301)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 302)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 400)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 401)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 403)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 404)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 500)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 503)]
+ [InlineData(ProxyFunctionType.HttpApiV2, 599)]
+ public void StatusCode_IsCopiedCorrectly(ProxyFunctionType functionType, int statusCode)
{
- var builder = CreateBuilder();
var rf = MakeResponseFeature(statusCode);
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
Assert.Equal((HttpStatusCode)statusCode, prelude.StatusCode);
}
@@ -71,111 +183,114 @@ public void StatusCode_IsCopiedCorrectly(int statusCode)
// -----------------------------------------------------------------------
// 6.2 Status code defaults to 200 when IHttpResponseFeature.StatusCode is 0
// -----------------------------------------------------------------------
- [Fact]
- public void StatusCode_DefaultsTo200_WhenFeatureStatusCodeIsZero()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void StatusCode_DefaultsTo200_WhenFeatureStatusCodeIsZero(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
var rf = MakeResponseFeature(0);
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
Assert.Equal(HttpStatusCode.OK, prelude.StatusCode);
}
// -----------------------------------------------------------------------
- // 6.3 Non-Set-Cookie headers appear in MultiValueHeaders with all values preserved
+ // 6.3 Non-Set-Cookie headers appear in the header collection with all values preserved.
+ // HTTP API v2 uses the single-value "headers" collection (joining multiple values with ", "),
+ // while the REST API uses the multi-value "multiValueHeaders" collection.
// -----------------------------------------------------------------------
- [Fact]
- public void NonSetCookieHeaders_AppearInMultiValueHeaders_WithAllValuesPreserved()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void NonSetCookieHeaders_AppearInHeaders_WithAllValuesPreserved(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
- var rf = MakeResponseFeature(200, new System.Collections.Generic.Dictionary
+ var rf = MakeResponseFeature(200, new Dictionary
{
["Content-Type"] = new[] { "application/json" },
["X-Custom"] = new[] { "val1", "val2" },
["Cache-Control"] = new[] { "no-cache", "no-store" }
});
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
- Assert.True(prelude.MultiValueHeaders.ContainsKey("Content-Type"));
- Assert.Equal(new[] { "application/json" }, prelude.MultiValueHeaders["Content-Type"]);
-
- Assert.True(prelude.MultiValueHeaders.ContainsKey("X-Custom"));
- Assert.Equal(new[] { "val1", "val2" }, prelude.MultiValueHeaders["X-Custom"]);
-
- Assert.True(prelude.MultiValueHeaders.ContainsKey("Cache-Control"));
- Assert.Equal(new[] { "no-cache", "no-store" }, prelude.MultiValueHeaders["Cache-Control"]);
+ AssertHeaderPresent(prelude, functionType, "Content-Type", "application/json");
+ AssertHeaderPresent(prelude, functionType, "X-Custom", "val1", "val2");
+ AssertHeaderPresent(prelude, functionType, "Cache-Control", "no-cache", "no-store");
}
- [Fact]
- public void NonSetCookieHeaders_MultiValueHeaders_PreservesMultipleValues()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void NonSetCookieHeaders_JoinsMultipleValues(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
- var rf = MakeResponseFeature(200, new System.Collections.Generic.Dictionary
+ var rf = MakeResponseFeature(200, new Dictionary
{
["Accept"] = new[] { "text/html", "application/xhtml+xml", "application/xml" }
});
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
- Assert.Equal(new[] { "text/html", "application/xhtml+xml", "application/xml" },
- prelude.MultiValueHeaders["Accept"]);
+ AssertHeaderPresent(prelude, functionType, "Accept",
+ "text/html", "application/xhtml+xml", "application/xml");
}
// -----------------------------------------------------------------------
- // 6.4 Set-Cookie header values are moved to Cookies and absent from MultiValueHeaders
+ // 6.4 Set-Cookie header values are moved to Cookies and absent from the header collection
// -----------------------------------------------------------------------
- [Fact]
- public void SetCookieHeader_MovedToCookies_AbsentFromMultiValueHeaders()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void SetCookieHeader_MovedToCookies_AbsentFromHeaders(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
- var rf = MakeResponseFeature(200, new System.Collections.Generic.Dictionary
+ var rf = MakeResponseFeature(200, new Dictionary
{
["Set-Cookie"] = new[] { "session=abc123; Path=/; HttpOnly" },
["Content-Type"] = new[] { "text/html" }
});
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
// Cookie value is in Cookies
Assert.Contains("session=abc123; Path=/; HttpOnly", prelude.Cookies);
- // Set-Cookie is NOT in MultiValueHeaders
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Set-Cookie"));
- Assert.False(prelude.MultiValueHeaders.ContainsKey("set-cookie"));
+ // Set-Cookie is NOT in the header collection
+ AssertHeaderAbsent(prelude, functionType, "Set-Cookie");
+ AssertHeaderAbsent(prelude, functionType, "set-cookie");
// Other headers are still present
- Assert.True(prelude.MultiValueHeaders.ContainsKey("Content-Type"));
+ AssertHeaderPresent(prelude, functionType, "Content-Type", "text/html");
}
- [Fact]
- public void SetCookieHeader_IsCaseInsensitive()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void SetCookieHeader_IsCaseInsensitive(ProxyFunctionType functionType)
{
// The implementation uses StringComparison.OrdinalIgnoreCase, so
// "set-cookie" (lowercase) should also be routed to Cookies.
- var builder = CreateBuilder();
var features = new InvokeFeatures();
var rf = (IHttpResponseFeature)features;
rf.StatusCode = 200;
// HeaderDictionary is case-insensitive, so "set-cookie" and "Set-Cookie" are the same key.
rf.Headers["set-cookie"] = new Microsoft.Extensions.Primitives.StringValues("id=xyz; Path=/");
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
Assert.Contains("id=xyz; Path=/", prelude.Cookies);
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Set-Cookie"));
- Assert.False(prelude.MultiValueHeaders.ContainsKey("set-cookie"));
+ AssertHeaderAbsent(prelude, functionType, "Set-Cookie");
+ AssertHeaderAbsent(prelude, functionType, "set-cookie");
}
// -----------------------------------------------------------------------
// 6.5 Multiple Set-Cookie values all appear in Cookies
// -----------------------------------------------------------------------
- [Fact]
- public void MultipleSetCookieValues_AllAppearInCookies()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void MultipleSetCookieValues_AllAppearInCookies(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
- var rf = MakeResponseFeature(200, new System.Collections.Generic.Dictionary
+ var rf = MakeResponseFeature(200, new Dictionary
{
["Set-Cookie"] = new[]
{
@@ -185,83 +300,87 @@ public void MultipleSetCookieValues_AllAppearInCookies()
}
});
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
Assert.Equal(3, prelude.Cookies.Count);
Assert.Contains("session=abc; Path=/; HttpOnly", prelude.Cookies);
Assert.Contains("theme=dark; Path=/", prelude.Cookies);
Assert.Contains("lang=en; Path=/; SameSite=Strict", prelude.Cookies);
- // None of them should be in MultiValueHeaders
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Set-Cookie"));
+ // None of them should be in the header collection
+ AssertHeaderAbsent(prelude, functionType, "Set-Cookie");
}
- [Fact]
- public void MultipleSetCookieValues_WithOtherHeaders_CookiesAndHeadersAreSeparated()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void MultipleSetCookieValues_WithOtherHeaders_CookiesAndHeadersAreSeparated(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
- var rf = MakeResponseFeature(201, new System.Collections.Generic.Dictionary
+ var rf = MakeResponseFeature(201, new Dictionary
{
["Set-Cookie"] = new[] { "a=1", "b=2" },
["Location"] = new[] { "/new-resource" }
});
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
Assert.Equal((HttpStatusCode)201, prelude.StatusCode);
Assert.Equal(2, prelude.Cookies.Count);
Assert.Contains("a=1", prelude.Cookies);
Assert.Contains("b=2", prelude.Cookies);
- Assert.True(prelude.MultiValueHeaders.ContainsKey("Location"));
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Set-Cookie"));
+ AssertHeaderPresent(prelude, functionType, "Location", "/new-resource");
+ AssertHeaderAbsent(prelude, functionType, "Set-Cookie");
}
- [Fact]
- public void EmptyHeaders_ProducesEmptyMultiValueHeadersAndCookies()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void EmptyHeaders_ProducesEmptyHeadersAndCookies(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
var rf = MakeResponseFeature(204);
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
Assert.Equal(HttpStatusCode.NoContent, prelude.StatusCode);
- Assert.Empty(prelude.MultiValueHeaders);
+ AssertHeadersEmpty(prelude, functionType);
Assert.Empty(prelude.Cookies);
}
// -----------------------------------------------------------------------
// Content-Length and Transfer-Encoding are excluded from the prelude
// -----------------------------------------------------------------------
- [Fact]
- public void ContentLengthHeader_ExcludedFromPrelude()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void ContentLengthHeader_ExcludedFromPrelude(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
- var rf = MakeResponseFeature(200, new System.Collections.Generic.Dictionary
+ var rf = MakeResponseFeature(200, new Dictionary
{
["Content-Type"] = new[] { "application/json" },
["Content-Length"] = new[] { "42" }
});
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
- Assert.True(prelude.MultiValueHeaders.ContainsKey("Content-Type"));
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Content-Length"));
+ AssertHeaderPresent(prelude, functionType, "Content-Type", "application/json");
+ AssertHeaderAbsent(prelude, functionType, "Content-Length");
}
- [Fact]
- public void TransferEncodingHeader_ExcludedFromPrelude()
+ [Theory]
+ [InlineData(ProxyFunctionType.RestApi)]
+ [InlineData(ProxyFunctionType.HttpApiV2)]
+ public void TransferEncodingHeader_ExcludedFromPrelude(ProxyFunctionType functionType)
{
- var builder = CreateBuilder();
- var rf = MakeResponseFeature(200, new System.Collections.Generic.Dictionary
+ var rf = MakeResponseFeature(200, new Dictionary
{
["Content-Type"] = new[] { "text/plain" },
["Transfer-Encoding"] = new[] { "chunked" }
});
- var prelude = builder.InvokeBuildStreamingPrelude(rf);
+ var prelude = BuildPrelude(functionType, rf);
- Assert.True(prelude.MultiValueHeaders.ContainsKey("Content-Type"));
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Transfer-Encoding"));
+ AssertHeaderPresent(prelude, functionType, "Content-Type", "text/plain");
+ AssertHeaderAbsent(prelude, functionType, "Transfer-Encoding");
}
}
}
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs
index 05c6bed87..b1dbf95cb 100644
--- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/ResponseStreamingPropertyTests.cs
@@ -4,7 +4,6 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
-using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -20,7 +19,6 @@
namespace Amazon.Lambda.AspNetCoreServer.Test
{
- [RequiresPreviewFeatures]
public class ResponseStreamingPropertyTests
{
// -----------------------------------------------------------------------
@@ -52,7 +50,6 @@ protected override void PostMarshallItemsFeatureFeature(
base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
}
- [RequiresPreviewFeatures]
protected override Stream CreateLambdaResponseStream(
Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude)
{
@@ -195,12 +192,13 @@ public void Property3_Prelude_StatusCodeAndNonCookieHeaders_Correct(
int expectedStatus = statusCode == 0 ? 200 : statusCode;
Assert.Equal((System.Net.HttpStatusCode)expectedStatus, prelude.StatusCode);
- Assert.True(prelude.MultiValueHeaders.ContainsKey(headerKey),
- $"Header '{headerKey}' missing from MultiValueHeaders");
- Assert.Equal(headerValues, prelude.MultiValueHeaders[headerKey].ToArray());
+ // HTTP API v2 uses the single-value "headers" collection; multiple values are joined with ", ".
+ Assert.True(prelude.Headers.ContainsKey(headerKey),
+ $"Header '{headerKey}' missing from Headers");
+ Assert.Equal(string.Join(", ", headerValues), prelude.Headers[headerKey]);
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Set-Cookie"));
- Assert.False(prelude.MultiValueHeaders.ContainsKey("set-cookie"));
+ Assert.False(prelude.Headers.ContainsKey("Set-Cookie"));
+ Assert.False(prelude.Headers.ContainsKey("set-cookie"));
}
@@ -227,12 +225,12 @@ public void Property4_SetCookieHeaders_MovedToCookies_AbsentFromMultiValueHeader
foreach (var cookie in cookies)
Assert.Contains(cookie, prelude.Cookies);
- Assert.False(prelude.MultiValueHeaders.ContainsKey("Set-Cookie"),
- "Set-Cookie must not appear in MultiValueHeaders");
- Assert.False(prelude.MultiValueHeaders.ContainsKey("set-cookie"),
- "set-cookie must not appear in MultiValueHeaders");
+ Assert.False(prelude.Headers.ContainsKey("Set-Cookie"),
+ "Set-Cookie must not appear in Headers");
+ Assert.False(prelude.Headers.ContainsKey("set-cookie"),
+ "set-cookie must not appear in Headers");
- Assert.True(prelude.MultiValueHeaders.ContainsKey("content-type"));
+ Assert.True(prelude.Headers.ContainsKey("content-type"));
}
@@ -446,7 +444,6 @@ protected override void PostMarshallItemsFeatureFeature(
base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
}
- [RequiresPreviewFeatures]
protected override Stream CreateLambdaResponseStream(
Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude)
{
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs
index 6ba2a6291..3bf4e2073 100644
--- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingFunctionHandlerAsyncTests.cs
@@ -4,9 +4,7 @@
using System.Collections.Generic;
using System.IO;
using System.Reflection;
-using System.Runtime.Versioning;
using System.Text;
-using System.Threading;
using System.Threading.Tasks;
using Amazon.Lambda.APIGatewayEvents;
@@ -30,7 +28,6 @@ namespace Amazon.Lambda.AspNetCoreServer.Test
/// a instead of calling LambdaResponseStreamFactory.CreateHttpStream,
/// allowing tests to run without the Lambda runtime.
///
- [RequiresPreviewFeatures]
public class StreamingFunctionHandlerAsyncTests
{
// -----------------------------------------------------------------------
@@ -75,7 +72,6 @@ protected override void PostMarshallItemsFeatureFeature(
base.PostMarshallItemsFeatureFeature(aspNetCoreItemFeature, lambdaRequest, lambdaContext);
}
- [RequiresPreviewFeatures]
protected override Stream CreateLambdaResponseStream(
Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude)
{
@@ -467,18 +463,6 @@ public void FunctionHandlerAsync_HasLambdaSerializerAttribute()
attr.SerializerType);
}
- [Fact]
- public void EnableResponseStreaming_Property_HasRequiresPreviewFeaturesAttribute()
- {
- var prop = typeof(APIGatewayHttpApiV2ProxyFunction)
- .GetProperty(nameof(APIGatewayHttpApiV2ProxyFunction.EnableResponseStreaming));
-
- Assert.NotNull(prop);
-
- var attr = prop.GetCustomAttribute();
- Assert.NotNull(attr);
- }
-
[Fact]
public void EnableResponseStreaming_Property_DefaultsToFalse()
{
@@ -531,7 +515,6 @@ protected CustomPipelineStreamingFunction()
EnableResponseStreaming = true;
}
- [RequiresPreviewFeatures]
protected override Stream CreateLambdaResponseStream(
Amazon.Lambda.Core.ResponseStreaming.HttpResponseStreamPrelude prelude)
{
diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingResponseBodyFeatureTests.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingResponseBodyFeatureTests.cs
index cdbd403e4..669529f9a 100644
--- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingResponseBodyFeatureTests.cs
+++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/StreamingResponseBodyFeatureTests.cs
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
using System;
using System.IO;
-using System.Runtime.Versioning;
using System.Threading.Tasks;
using Amazon.Lambda.AspNetCoreServer.Internal;
@@ -11,7 +10,6 @@
namespace Amazon.Lambda.AspNetCoreServer.Test
{
- [RequiresPreviewFeatures]
public class StreamingResponseBodyFeatureTests
{
// Helper: creates a StreamingResponseBodyFeature backed by a MemoryStream stand-in.