Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
25c0c33
Support API Gateway websocket api via LambdaServer
wiowou Sep 5, 2024
1f5717a
Merge pull request #2229 from aws/dev
philasmar Dec 15, 2025
ad0ebab
update permissions (#2231) (#2232)
GarrettBeatty Dec 16, 2025
1dbfbf2
Merge branch 'dev'
aws-sdk-dotnet-automation Dec 17, 2025
a4310dc
Merge branch 'dev'
aws-sdk-dotnet-automation Jan 8, 2026
0548eb3
Dev (#2248)
GarrettBeatty Jan 9, 2026
3f6de4a
Merge branch 'dev'
aws-sdk-dotnet-automation Jan 12, 2026
fd18ef2
Merge pull request #2258 from aws/dev
philasmar Jan 14, 2026
6e7c60b
Merge branch 'dev'
aws-sdk-dotnet-automation Jan 15, 2026
15145bf
Merge branch 'dev'
aws-sdk-dotnet-automation Jan 16, 2026
bc0c745
Merge branch 'dev'
aws-sdk-dotnet-automation Jan 30, 2026
281133d
Merge branch 'dev'
aws-sdk-dotnet-automation Feb 4, 2026
e61e9a5
Merge branch 'dev'
aws-sdk-dotnet-automation Feb 10, 2026
3af4abf
Merge branch 'dev'
aws-sdk-dotnet-automation Feb 19, 2026
c28fcfa
Merge branch 'dev'
aws-sdk-dotnet-automation Feb 19, 2026
f3cf8cc
Merge pull request #2296 from aws/dev
philasmar Mar 12, 2026
dc91547
Merge pull request #2307 from aws/dev
philasmar Mar 18, 2026
a5b74f6
Merge branch 'dev'
aws-sdk-dotnet-automation Mar 19, 2026
52d0de4
Merge branch 'dev'
aws-sdk-dotnet-automation Mar 27, 2026
cb78042
Merge branch 'dev'
aws-sdk-dotnet-automation Apr 9, 2026
64a47bb
Merge branch 'dev'
aws-sdk-dotnet-automation Apr 14, 2026
e9d0d74
Merge branch 'dev'
aws-sdk-dotnet-automation Apr 14, 2026
caea55b
Merge pull request #2334 from aws/dev
philasmar Apr 15, 2026
5820339
Merge branch 'dev'
aws-sdk-dotnet-automation Apr 16, 2026
ffa4585
Merge branch 'dev'
aws-sdk-dotnet-automation Apr 22, 2026
35b2e8f
Merge branch 'dev'
aws-sdk-dotnet-automation Apr 22, 2026
c63d743
Merge branch 'dev'
aws-sdk-dotnet-automation Apr 29, 2026
dfbb7ac
Merge branch 'dev'
aws-sdk-dotnet-automation May 7, 2026
5de51e0
Merge branch 'dev'
aws-sdk-dotnet-automation May 12, 2026
40cdd7f
pr fixes: api gateway websocket api
May 13, 2026
4c22317
fixed merge conflict. Logical changes remain
May 13, 2026
0fc8136
fix lambda runtime support server
May 13, 2026
5ad44ab
simplify calls
May 13, 2026
acd15d7
pr fixes: api gateway websocket api
May 13, 2026
89a6b33
Merge branch 'dev' into websocket
GarrettBeatty May 15, 2026
26b9cd0
Fix websocket Hosting wiring and simplify MinimalApi
GarrettBeatty May 15, 2026
05ae11c
Add autover change file for websocket support
GarrettBeatty May 15, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,45 @@ public ApplicationLoadBalancerMinimalApi(IServiceProvider serviceProvider)
}
}
}

/// <summary>
/// IServer for handlying Lambda events from an API Gateway Websocket API.
/// </summary>
Comment thread
wiowou marked this conversation as resolved.
public class APIGatewayWebsocketApiLambdaRuntimeSupportServer : LambdaRuntimeSupportServer
{
/// <summary>
/// Create instances
/// </summary>
/// <param name="serviceProvider">The IServiceProvider created for the ASP.NET Core application</param>
public APIGatewayWebsocketApiLambdaRuntimeSupportServer(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}

/// <summary>
/// Creates HandlerWrapper for processing events from API Gateway Websocket API
/// </summary>
/// <param name="serviceProvider"></param>
/// <returns></returns>
protected override HandlerWrapper CreateHandlerWrapper(IServiceProvider serviceProvider)
{
var handler = new APIGatewayWebsocketApiMinimalApi(serviceProvider).FunctionHandlerAsync;
return HandlerWrapper.GetHandlerWrapper(handler, this.Serializer);
}

/// <summary>
/// Create the APIGatewayWebsocketApiV2ProxyFunction passing in the ASP.NET Core application's IServiceProvider
/// </summary>
public class APIGatewayWebsocketApiMinimalApi : APIGatewayWebsocketApiProxyFunction
{
/// <summary>
/// Create instances
/// </summary>
/// <param name="serviceProvider">The IServiceProvider created for the ASP.NET Core application</param>
public APIGatewayWebsocketApiMinimalApi(IServiceProvider serviceProvider)
: base(serviceProvider)
{
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,12 @@ public enum LambdaEventSource
/// <summary>
/// ELB Application Load Balancer
/// </summary>
ApplicationLoadBalancer
ApplicationLoadBalancer,

/// <summary>
/// API Gateway HTTP API
Comment thread
wiowou marked this conversation as resolved.
Outdated
/// </summary>
WebsocketApi
}

/// <summary>
Expand Down Expand Up @@ -106,6 +111,7 @@ private static bool TryLambdaSetup(IServiceCollection services, LambdaEventSourc
LambdaEventSource.HttpApi => typeof(APIGatewayHttpApiV2LambdaRuntimeSupportServer),
LambdaEventSource.RestApi => typeof(APIGatewayRestApiLambdaRuntimeSupportServer),
LambdaEventSource.ApplicationLoadBalancer => typeof(ApplicationLoadBalancerLambdaRuntimeSupportServer),
LambdaEventSource.WebsocketApi => typeof(APIGatewayWebsocketApiLambdaRuntimeSupportServer),
_ => throw new ArgumentException($"Event source type {eventSource} unknown")
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Security.Claims;
using System.Text;

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
Expand Down Expand Up @@ -151,33 +152,9 @@ protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxy
{
var requestFeatures = (IHttpRequestFeature)features;
requestFeatures.Scheme = "https";
requestFeatures.Method = apiGatewayRequest.HttpMethod;
requestFeatures.Method = this.ParseHttpMethod(apiGatewayRequest);

string path = null;

// Replaces {proxy+} in path, if exists
if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.TryGetValue("proxy", out var proxy) &&
!string.IsNullOrEmpty(apiGatewayRequest.Resource))
{
var proxyPath = proxy;
path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath);

// Adds all the rest of non greedy parameters in apiGateway.Resource to the path
foreach (var pathParameter in apiGatewayRequest.PathParameters.Where(pp => pp.Key != "proxy"))
{
path = path.Replace($"{{{pathParameter.Key}}}", pathParameter.Value);
}
}

if (string.IsNullOrEmpty(path))
{
path = apiGatewayRequest.Path;
}

if (!path.StartsWith("/"))
{
path = "/" + path;
}
string path = this.ParseHttpPath(apiGatewayRequest);

var rawQueryString = Utilities.CreateQueryStringParameters(
apiGatewayRequest.QueryStringParameters, apiGatewayRequest.MultiValueQueryStringParameters, true);
Expand Down Expand Up @@ -214,13 +191,7 @@ protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxy

Utilities.SetHeadersCollection(requestFeatures.Headers, apiGatewayRequest.Headers, apiGatewayRequest.MultiValueHeaders);

if (!requestFeatures.Headers.ContainsKey("Host"))
{
var apiId = apiGatewayRequest?.RequestContext?.ApiId ?? "";
var stage = apiGatewayRequest?.RequestContext?.Stage ?? "";

requestFeatures.Headers["Host"] = $"apigateway-{apiId}-{stage}";
}
requestFeatures.Headers = this.AddMissingRequestHeaders(apiGatewayRequest, requestFeatures.Headers);


if (!string.IsNullOrEmpty(apiGatewayRequest.Body))
Expand Down Expand Up @@ -256,6 +227,7 @@ protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxy
{
connectionFeatures.RemotePort = int.Parse(forwardedPort, CultureInfo.InvariantCulture);
}
connectionFeatures.ConnectionId = apiGatewayRequest.RequestContext?.ConnectionId;

// Call consumers customize method in case they want to change how API Gateway's request
// was marshalled into ASP.NET Core request.
Expand Down Expand Up @@ -335,5 +307,66 @@ protected override APIGatewayProxyResponse MarshallResponse(IHttpResponseFeature

return response;
}

/// <summary>
/// Get the http path from the request.
/// </summary>
/// <param name="apiGatewayRequest"></param>
/// <returns>string</returns>
protected virtual string ParseHttpPath(APIGatewayProxyRequest apiGatewayRequest)
{
string path = null;

// Replaces {proxy+} in path, if exists
if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.TryGetValue("proxy", out var proxy) &&
!string.IsNullOrEmpty(apiGatewayRequest.Resource))
{
var proxyPath = proxy;
path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath);

// Adds all the rest of non greedy parameters in apiGateway.Resource to the path
foreach (var pathParameter in apiGatewayRequest.PathParameters.Where(pp => pp.Key != "proxy"))
{
path = path.Replace($"{{{pathParameter.Key}}}", pathParameter.Value);
}
}

if (string.IsNullOrEmpty(path))
{
path = apiGatewayRequest.Path;
}

if (!path.StartsWith("/"))
{
path = "/" + path;
}
return path;
}

/// <summary>
/// Get the http method from the request.
/// </summary>
/// <param name="apiGatewayRequest"></param>
/// <returns>string</returns>
protected virtual string ParseHttpMethod(APIGatewayProxyRequest apiGatewayRequest)
{
return apiGatewayRequest.HttpMethod;
}

/// <summary>
/// Add missing headers to request.
/// </summary>
/// <returns>IHeaderDictionary</returns>
protected virtual IHeaderDictionary AddMissingRequestHeaders(APIGatewayProxyRequest apiGatewayRequest, IHeaderDictionary headers)
{
if (!headers.ContainsKey("Host"))
{
var apiId = apiGatewayRequest?.RequestContext?.ApiId ?? "";
var stage = apiGatewayRequest?.RequestContext?.Stage ?? "";

headers["Host"] = $"apigateway-{apiId}-{stage}";
}
return headers;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;

using Microsoft.AspNetCore.Http;

using Amazon.Lambda.APIGatewayEvents;
using Amazon.Lambda.AspNetCoreServer.Internal;

namespace Amazon.Lambda.AspNetCoreServer
{
/// <summary>
/// Base class for ASP.NET Core Lambda functions that are getting request from API Gateway Websocket API V2 payload format.
///
/// The http method is fixed as POST. Requests are handled using the RouteKey, so the same lambda should be referenced by multiple API Gateway routes for the ASP.NET Core IServer to successfully route requests.
/// </summary>
public abstract class APIGatewayWebsocketApiProxyFunction : APIGatewayProxyFunction
{
/// <summary>
/// Default constructor
/// </summary>
protected APIGatewayWebsocketApiProxyFunction()
: base()
{

}

/// <inheritdoc/>
/// <param name="startupMode">Configure when the ASP.NET Core framework will be initialized</param>
protected APIGatewayWebsocketApiProxyFunction(StartupMode startupMode)
: base(startupMode)
{

}

/// <summary>
/// Constructor used by Amazon.Lambda.AspNetCoreServer.Hosting to support ASP.NET Core projects using the Minimal API style.
/// </summary>
/// <param name="hostedServices"></param>
protected APIGatewayWebsocketApiProxyFunction(IServiceProvider hostedServices)
: base(hostedServices)
{
_hostServices = hostedServices;
}

/// <inheritdoc/>
/// <param name="apiGatewayRequest"></param>
/// <returns>string</returns>
protected override string ParseHttpPath(APIGatewayProxyRequest apiGatewayRequest)
{
var path = "/" + Utilities.DecodeResourcePath(apiGatewayRequest.RequestContext.RouteKey);
return path;
}

/// <inheritdoc/>
/// <param name="apiGatewayRequest"></param>
/// <returns>string</returns>
protected override string ParseHttpMethod(APIGatewayProxyRequest apiGatewayRequest)
{
return "POST";
}

/// <inheritdoc/>
/// <returns>IHeaderDictionary</returns>
protected override IHeaderDictionary AddMissingRequestHeaders(APIGatewayProxyRequest apiGatewayRequest, IHeaderDictionary headers)
{
headers = base.AddMissingRequestHeaders(apiGatewayRequest, headers);
headers["Content-Type"] = "application/json";
Comment thread
wiowou marked this conversation as resolved.
Outdated
return headers;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using Microsoft.AspNetCore.Hosting;
using System.Diagnostics.CodeAnalysis;

namespace Amazon.Lambda.AspNetCoreServer
{
/// <summary>
/// APIGatewayWebsocketApiV2ProxyFunction is the base class that is implemented in a ASP.NET Core Web API. The derived class implements
/// the Init method similar to Main function in the ASP.NET Core and provides typed Startup. The function handler for
/// the Lambda function will point to this base class FunctionHandlerAsync method.
/// </summary>
Comment thread
wiowou marked this conversation as resolved.
/// <typeparam name ="TStartup">The type containing the startup methods for the application.</typeparam>
public abstract class APIGatewayWebsocketApiProxyFunction<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] TStartup> : APIGatewayWebsocketApiProxyFunction where TStartup : class
{
/// <summary>
/// Default Constructor. The ASP.NET Core Framework will be initialized as part of the construction.
/// </summary>
protected APIGatewayWebsocketApiProxyFunction()
: base()
{

}


/// <summary>
///
/// </summary>
Comment thread
GarrettBeatty marked this conversation as resolved.
/// <param name="startupMode">Configure when the ASP.NET Core framework will be initialized</param>
protected APIGatewayWebsocketApiProxyFunction(StartupMode startupMode)
: base(startupMode)
{

}

/// <inheritdoc/>
protected override void Init(IWebHostBuilder builder)
{
builder.UseStartup<TStartup>();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.002.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.AspNetCoreServer.Test", "Amazon.Lambda.AspNetCoreServer.Test.csproj", "{AE614E81-1148-41E0-9CA7-B1F3EB34B65E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{AE614E81-1148-41E0-9CA7-B1F3EB34B65E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AE614E81-1148-41E0-9CA7-B1F3EB34B65E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AE614E81-1148-41E0-9CA7-B1F3EB34B65E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AE614E81-1148-41E0-9CA7-B1F3EB34B65E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FDFB946B-997E-4736-A826-7461752C8DF4}
EndGlobalSection
EndGlobal
Loading