public virtual async Task <APIGatewayProxyResponse> FunctionHandlerAsync(APIGatewayProxyRequest request, ILambdaContext lambdaContext) { lambdaContext.Logger.LogLine($"Incoming {request.HttpMethod} requests to {request.Path}"); InvokeFeatures features = new InvokeFeatures(); MarshallRequest(features, request, lambdaContext); lambdaContext.Logger.LogLine($"ASP.NET Core Request PathBase: {((IHttpRequestFeature)features).PathBase}, Path: {((IHttpRequestFeature)features).Path}"); var context = this.CreateContext(features); if (request?.RequestContext?.Authorizer?.Claims != null) { var identity = new ClaimsIdentity(request.RequestContext.Authorizer.Claims.Select( entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); lambdaContext.Logger.LogLine($"Configuring HttpContext.User with {request.RequestContext.Authorizer.Claims.Count} claims coming from API Gateway's Request Context"); context.HttpContext.User = new ClaimsPrincipal(identity); } // Add along the Lambda objects to the HttpContext to give access to Lambda to them in the ASP.NET Core application context.HttpContext.Items[LAMBDA_CONTEXT] = lambdaContext; context.HttpContext.Items[APIGATEWAY_REQUEST] = request; // Allow the context to be customized before passing the request to ASP.NET Core. PostCreateContext(context, request, lambdaContext); var response = await this.ProcessRequest(lambdaContext, context, features); return(response); }
public async Task <APIGatewayProxyResponse> FunctionHandlerAsync(APIGatewayAuthorizerProxyRequest request, ILambdaContext lambdaContext) { Console.WriteLine(JsonConvert.SerializeObject(request)); lambdaContext.Logger.LogLine($"Incoming {request.HttpMethod} requests to {request.Path}"); var features = new InvokeFeatures(); MarshallRequest(features, request); var context = CreateContext(features); if (request.Headers.ContainsKey("Authorization")) { var token = new JwtSecurityToken(request.Headers["Authorization"]); var identity = new ClaimsIdentity(token.Claims, "AuthorizerIdentity"); context.HttpContext.User = new ClaimsPrincipal(identity); } // Add along the Lambda objects to the HttpContext to give access to Lambda to them in the ASP.NET Core application context.HttpContext.Items["LAMBDA_CONTEXT"] = lambdaContext; context.HttpContext.Items["APIGATEWAY_REQUEST"] = request; var response = await ProcessRequest(lambdaContext, context, features); return(response); }
public virtual async Task <APIGatewayProxyResponse> FunctionHandlerAsync(APIGatewayProxyRequest request, ILambdaContext lambdaContext) { lambdaContext?.Logger.Log($"Incoming {request.HttpMethod} requests to {request.Path}"); InvokeFeatures features = new InvokeFeatures(); MarshallRequest(features, request); var context = _server.Application.CreateContext(features); try { await this._server.Application.ProcessRequestAsync(context); this._server.Application.DisposeContext(context, null); } catch (Exception e) { lambdaContext?.Logger.Log($"Unknown error responding to request: {ErrorReport(e)}"); this._server.Application.DisposeContext(context, e); } var response = MarshallResponse(features); // ASP.NET Core Web API does not always set the status code if the request was // successful if (response.StatusCode == 0) { response.StatusCode = 200; } return(response); }
/// <inheritdoc/> protected override void MarshallRequest(InvokeFeatures features, ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { // Request coming from Application Load Balancer will always send the headers X-Amzn-Trace-Id, X-Forwarded-For, X-Forwarded-Port, and X-Forwarded-Proto. // So this will only happen when writing tests with incomplete sample requests. if (lambdaRequest.Headers == null && lambdaRequest.MultiValueHeaders == null) { throw new Exception("Unable to determine header mode, single or multi value, because both Headers and MultiValueHeaders are null."); } // Look to see if the request is using mutli value headers or not. This is important when // marshalling the response to know whether to fill in the the Headers or MultiValueHeaders collection. // Since a Lambda function compute environment is only one processing one event at a time it is safe to store // this as a member variable. this._multiHeaderValuesEnabled = lambdaRequest.MultiValueHeaders != null; { var requestFeatures = (IHttpRequestFeature)features; requestFeatures.Scheme = GetSingleHeaderValue(lambdaRequest, "x-forwarded-proto"); requestFeatures.Method = lambdaRequest.HttpMethod; requestFeatures.Path = Utilities.DecodeResourcePath(lambdaRequest.Path); requestFeatures.QueryString = Utilities.CreateQueryStringParamaters( lambdaRequest.QueryStringParameters, lambdaRequest.MultiValueQueryStringParameters); Utilities.SetHeadersCollection(requestFeatures.Headers, lambdaRequest.Headers, lambdaRequest.MultiValueHeaders); if (!string.IsNullOrEmpty(lambdaRequest.Body)) { requestFeatures.Body = Utilities.ConvertLambdaRequestBodyToAspNetCoreBody(lambdaRequest.Body, lambdaRequest.IsBase64Encoded); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallRequestFeature(requestFeatures, lambdaRequest, lambdaContext); } { // set up connection features var connectionFeatures = (IHttpConnectionFeature)features; var remoteIpAddressStr = GetSingleHeaderValue(lambdaRequest, "x-forwarded-for"); if (!string.IsNullOrEmpty(remoteIpAddressStr) && IPAddress.TryParse(remoteIpAddressStr, out var remoteIpAddress)) { connectionFeatures.RemoteIpAddress = remoteIpAddress; } var remotePort = GetSingleHeaderValue(lambdaRequest, "x-forwarded-port"); if (!string.IsNullOrEmpty(remotePort)) { connectionFeatures.RemotePort = int.Parse(remotePort); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallConnectionFeature(connectionFeatures, lambdaRequest, lambdaContext); } }
public void EnsureTraceIdStaysTheSame() { var features = new InvokeFeatures() as IHttpRequestIdentifierFeature; var traceId1 = features.TraceIdentifier; var traceId2 = features.TraceIdentifier; Assert.Equal(traceId1, traceId2); }
public virtual async Task <APIGatewayProxyResponse> FunctionHandlerAsync(APIGatewayProxyRequest request, ILambdaContext lambdaContext) { lambdaContext.Logger.Log($"Incoming {request.HttpMethod} requests to {request.Path}"); InvokeFeatures features = new InvokeFeatures(); MarshallRequest(features, request); var context = this.CreateContext(features); return(await this.ProcessRequest(lambdaContext, context, features)); }
public async Task <APIGatewayProxyResponse> FunctionHandlerAsync(APIGatewayAuthorizerProxyRequest request, ILambdaContext lambdaContext) { Console.WriteLine(JsonConvert.SerializeObject(request)); if (string.IsNullOrEmpty(request.HttpMethod)) { request.HttpMethod = "GET"; request.Path = "/ping"; request.Headers = new Dictionary <string, string> { { "Host", "localhost" } }; request.RequestContext = new AuthorizerRequestContext(); lambdaContext.Logger.LogLine("Keep-alive invokation"); lambdaContext = new DummyContext(lambdaContext); } lambdaContext.Logger.LogLine($"Incoming {request.HttpMethod} requests to {request.Path}"); var features = new InvokeFeatures(); MarshallRequest(features, request); var context = this.CreateContext(features); if (request.RequestContext.Authorizer != null) { var claims = request.RequestContext.Authorizer.Claims.ToObject <Dictionary <string, object> >(); var identity = new ClaimsIdentity(claims.Select(entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); context.HttpContext.User = new ClaimsPrincipal(identity); } // Add along the Lambda objects to the HttpContext to give access to Lambda to them in the ASP.NET Core application context.HttpContext.Items[LAMBDA_CONTEXT] = lambdaContext; context.HttpContext.Items[APIGATEWAY_REQUEST] = request; var response = await this.ProcessRequest(lambdaContext, context, features); var useGzip = false; if (request.Headers != null && request.Headers.TryGetValue("accept-encoding", out var headerValue)) { useGzip = headerValue.Contains("gzip"); } if (useGzip && context.HttpContext.Request.Method != HttpMethod.Options.Method) { response.IsBase64Encoded = true; var buffer = Zip(response.Body); response.Body = Convert.ToBase64String(buffer); response.Headers.Add(HeaderNames.ContentEncoding, "gzip"); } return(response); }
protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) { //string serializedRequest = JsonConvert.SerializeObject(apiGatewayRequest); lambdaContext.Logger.LogLine($"REQUEST: {JsonConvert.SerializeObject(apiGatewayRequest)}"); lambdaContext.Logger.LogLine($"REQUEST Context: {JsonConvert.SerializeObject(apiGatewayRequest.RequestContext)}"); if (apiGatewayRequest.RequestContext == null) { CallHealthCheck(lambdaContext); } else { base.MarshallRequest(features, apiGatewayRequest, lambdaContext); } }
public virtual async Task <APIGatewayProxyResponse> FunctionHandlerAsync(APIGatewayProxyRequest request, ILambdaContext lambdaContext) { lambdaContext.Logger.LogLine($"Incoming {request.HttpMethod} requests to {request.Path}"); InvokeFeatures features = new InvokeFeatures(); MarshallRequest(features, request); var context = this.CreateContext(features); // Add along the Lambda objects to the HttpContext to give access to Lambda to them in the ASP.NET Core application context.HttpContext.Items[LAMBDA_CONTEXT] = lambdaContext; context.HttpContext.Items[APIGATEWAY_REQUEST] = request; var response = await this.ProcessRequest(lambdaContext, context, features); return(response); }
/// <summary> /// This method is what the Lambda function handler points to. /// </summary> /// <param name="requestStream">Takes in a Stream instead of APIGatewayProxyRequest to get access to the raw event for logging purposes.</param> /// <param name="lambdaContext"></param> /// <returns></returns> public virtual async Task <Stream> FunctionHandlerAsync(Stream requestStream, ILambdaContext lambdaContext) { if (this.EnableRequestLogging) { StreamReader reader = new StreamReader(requestStream); string json = reader.ReadToEnd(); lambdaContext.Logger.LogLine(json); requestStream.Position = 0; } var request = this._serializer.Deserialize <APIGatewayProxyRequest>(requestStream); lambdaContext.Logger.Log($"Incoming {request.HttpMethod} requests to {request.Path}"); InvokeFeatures features = new InvokeFeatures(); MarshallRequest(features, request); var context = this.CreateContext(features); var response = await this.ProcessRequest(lambdaContext, context, features); var responseStream = new MemoryStream(); this._serializer.Serialize <APIGatewayProxyResponse>(response, responseStream); responseStream.Position = 0; if (this.EnableResponseLogging) { StreamReader reader = new StreamReader(responseStream); string json = reader.ReadToEnd(); lambdaContext.Logger.LogLine(json); responseStream.Position = 0; } return(responseStream); }
/// <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. /// </summary> /// <param name="features"></param> /// <param name="apiGatewayRequest"></param> /// <param name="lambdaContext"></param> protected override void MarshallRequest(InvokeFeatures features, APIGatewayHttpApiV2ProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) { { var authFeatures = (IHttpAuthenticationFeature)features; var authorizer = apiGatewayRequest?.RequestContext?.Authorizer; if (authorizer != null) { // handling claims output from cognito user pool authorizer if (authorizer.Jwt?.Claims != null && authorizer.Jwt.Claims.Count != 0) { var identity = new ClaimsIdentity(authorizer.Jwt.Claims.Select( entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); _logger.LogDebug( $"Configuring HttpContext.User with {authorizer.Jwt.Claims.Count} claims coming from API Gateway's Request Context"); authFeatures.User = new ClaimsPrincipal(identity); } else { // handling claims output from custom lambda authorizer var identity = new ClaimsIdentity(authorizer.Jwt?.Claims.Select(entry => new Claim(entry.Key, entry.Value)), "AuthorizerIdentity"); _logger.LogDebug( $"Configuring HttpContext.User with {identity.Claims.Count()} claims coming from API Gateway's Request Context"); authFeatures.User = new ClaimsPrincipal(identity); } } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallHttpAuthenticationFeature(authFeatures, apiGatewayRequest, lambdaContext); } { var httpInfo = apiGatewayRequest.RequestContext.Http; var requestFeatures = (IHttpRequestFeature)features; requestFeatures.Scheme = "https"; requestFeatures.Method = httpInfo.Method; if (string.IsNullOrWhiteSpace(apiGatewayRequest.RequestContext?.DomainName)) { _logger.LogWarning($"Request does not contain domain name information but is derived from {nameof(APIGatewayProxyFunction)}."); } requestFeatures.Path = Utilities.DecodeResourcePath(httpInfo.Path); if (!requestFeatures.Path.StartsWith("/")) { requestFeatures.Path = "/" + requestFeatures.Path; } // If there is a stage name in the resource path strip it out and set the stage name as the base path. // This is required so that ASP.NET Core will route request based on the resource path without the stage name. var stageName = apiGatewayRequest.RequestContext.Stage; if (!string.IsNullOrWhiteSpace(stageName)) { if (requestFeatures.Path.StartsWith($"/{stageName}")) { requestFeatures.Path = requestFeatures.Path.Substring(stageName.Length + 1); requestFeatures.PathBase = $"/{stageName}"; } } requestFeatures.QueryString = Utilities.CreateQueryStringParametersFromHttpApiV2(apiGatewayRequest.RawQueryString); // API Gateway HTTP API V2 format supports multiple values for headers by comma separating the values. if (apiGatewayRequest.Headers != null) { foreach (var kvp in apiGatewayRequest.Headers) { requestFeatures.Headers[kvp.Key] = new StringValues(kvp.Value?.Split(',')); } } if (!requestFeatures.Headers.ContainsKey("Host")) { requestFeatures.Headers["Host"] = apiGatewayRequest.RequestContext.DomainName; } if (apiGatewayRequest.Cookies != null) { // Add Cookies from the event requestFeatures.Headers["Cookie"] = String.Join("; ", apiGatewayRequest.Cookies); } if (!string.IsNullOrEmpty(apiGatewayRequest.Body)) { requestFeatures.Body = Utilities.ConvertLambdaRequestBodyToAspNetCoreBody(apiGatewayRequest.Body, apiGatewayRequest.IsBase64Encoded); } // Make sure the content-length header is set if header was not present. const string contentLengthHeaderName = "Content-Length"; if (!requestFeatures.Headers.ContainsKey(contentLengthHeaderName)) { requestFeatures.Headers[contentLengthHeaderName] = requestFeatures.Body == null ? "0" : requestFeatures.Body.Length.ToString(CultureInfo.InvariantCulture); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallRequestFeature(requestFeatures, apiGatewayRequest, lambdaContext); } { // set up connection features var connectionFeatures = (IHttpConnectionFeature)features; if (!string.IsNullOrEmpty(apiGatewayRequest.RequestContext?.Http?.SourceIp) && IPAddress.TryParse(apiGatewayRequest.RequestContext.Http.SourceIp, out var remoteIpAddress)) { connectionFeatures.RemoteIpAddress = remoteIpAddress; } if (apiGatewayRequest?.Headers?.TryGetValue("X-Forwarded-Port", out var port) == true) { connectionFeatures.RemotePort = int.Parse(port, CultureInfo.InvariantCulture); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallConnectionFeature(connectionFeatures, apiGatewayRequest, lambdaContext); } { var tlsConnectionFeature = (ITlsConnectionFeature)features; var clientCertPem = apiGatewayRequest?.RequestContext?.Authentication?.ClientCert?.ClientCertPem; if (clientCertPem != null) { tlsConnectionFeature.ClientCertificate = Utilities.GetX509Certificate2FromPem(clientCertPem); } PostMarshallTlsConnectionFeature(tlsConnectionFeature, apiGatewayRequest, lambdaContext); } }
/// <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. /// </summary> /// <param name="features"></param> /// <param name="apiGatewayRequest"></param> /// <param name="lambdaContext"></param> protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) { { var authFeatures = (IHttpAuthenticationFeature)features; var authorizer = apiGatewayRequest?.RequestContext?.Authorizer; if (authorizer != null) { // handling claims output from cognito user pool authorizer if (authorizer.Claims != null && authorizer.Claims.Count != 0) { var identity = new ClaimsIdentity(authorizer.Claims.Select( entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); _logger.LogDebug( $"Configuring HttpContext.User with {authorizer.Claims.Count} claims coming from API Gateway's Request Context"); authFeatures.User = new ClaimsPrincipal(identity); } else { // handling claims output from custom lambda authorizer var identity = new ClaimsIdentity( authorizer.Where(x => x.Value != null && !string.Equals(x.Key, "claims", StringComparison.OrdinalIgnoreCase)) .Select(entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); _logger.LogDebug( $"Configuring HttpContext.User with {authorizer.Count} claims coming from API Gateway's Request Context"); authFeatures.User = new ClaimsPrincipal(identity); } } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallHttpAuthenticationFeature(authFeatures, apiGatewayRequest, lambdaContext); } { var requestFeatures = (IHttpRequestFeature)features; requestFeatures.Scheme = "https"; requestFeatures.Method = apiGatewayRequest.HttpMethod; if (string.IsNullOrWhiteSpace(apiGatewayRequest?.RequestContext?.DomainName)) { _logger.LogWarning($"Request does not contain domain name information but is derived from {nameof(APIGatewayProxyFunction)}."); } string path = null; if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.ContainsKey("proxy") && !string.IsNullOrEmpty(apiGatewayRequest.Resource)) { var proxyPath = apiGatewayRequest.PathParameters["proxy"]; path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath); } if (string.IsNullOrEmpty(path)) { path = apiGatewayRequest.Path; } if (!path.StartsWith("/")) { path = "/" + path; } requestFeatures.Path = path; requestFeatures.PathBase = string.Empty; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Path)) { // This is to cover the case where the request coming in is https://myapigatewayid.execute-api.us-west-2.amazonaws.com/Prod where // Prod is the stage name and there is no ending '/'. Path will be set to '/' so to make sure we detect the correct base path // append '/' on the end to make the later EndsWith and substring work correctly. var requestContextPath = apiGatewayRequest.RequestContext.Path; if (path.EndsWith("/") && !requestContextPath.EndsWith("/")) { requestContextPath += "/"; } else if (!path.EndsWith("/") && requestContextPath.EndsWith("/")) { // Handle a trailing slash in the request path: e.g. https://myapigatewayid.execute-api.us-west-2.amazonaws.com/Prod/foo/ requestFeatures.Path = path += "/"; } if (requestContextPath.EndsWith(path)) { requestFeatures.PathBase = requestContextPath.Substring(0, requestContextPath.Length - requestFeatures.Path.Length); } } requestFeatures.Path = Utilities.DecodeResourcePath(requestFeatures.Path); requestFeatures.QueryString = Utilities.CreateQueryStringParameters( apiGatewayRequest.QueryStringParameters, apiGatewayRequest.MultiValueQueryStringParameters, true); 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}"; } if (!string.IsNullOrEmpty(apiGatewayRequest.Body)) { requestFeatures.Body = Utilities.ConvertLambdaRequestBodyToAspNetCoreBody(apiGatewayRequest.Body, apiGatewayRequest.IsBase64Encoded); } // Make sure the content-length header is set if header was not present. const string contentLengthHeaderName = "Content-Length"; if (!requestFeatures.Headers.ContainsKey(contentLengthHeaderName)) { requestFeatures.Headers[contentLengthHeaderName] = requestFeatures.Body == null ? "0" : requestFeatures.Body.Length.ToString(CultureInfo.InvariantCulture); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallRequestFeature(requestFeatures, apiGatewayRequest, lambdaContext); } { // set up connection features var connectionFeatures = (IHttpConnectionFeature)features; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Identity?.SourceIp) && IPAddress.TryParse(apiGatewayRequest.RequestContext.Identity.SourceIp, out var remoteIpAddress)) { connectionFeatures.RemoteIpAddress = remoteIpAddress; } if (apiGatewayRequest?.Headers?.ContainsKey("X-Forwarded-Port") == true) { connectionFeatures.RemotePort = int.Parse(apiGatewayRequest.Headers["X-Forwarded-Port"]); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallConnectionFeature(connectionFeatures, apiGatewayRequest, lambdaContext); } { var tlsConnectionFeature = (ITlsConnectionFeature)features; var clientCertPem = apiGatewayRequest?.RequestContext?.Identity?.ClientCert?.ClientCertPem; if (clientCertPem != null) { tlsConnectionFeature.ClientCertificate = Utilities.GetX509Certificate2FromPem(clientCertPem); } PostMarshallTlsConnectionFeature(tlsConnectionFeature, apiGatewayRequest, lambdaContext); } }
/// <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. /// </summary> /// <param name="features"></param> /// <param name="apiGatewayRequest"></param> /// <param name="lambdaContext"></param> protected void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) { { var requestFeatures = (IHttpRequestFeature)features; requestFeatures.Scheme = "https"; requestFeatures.Method = apiGatewayRequest.HttpMethod; string path = null; if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.ContainsKey("proxy") && !string.IsNullOrEmpty(apiGatewayRequest.Resource)) { var proxyPath = apiGatewayRequest.PathParameters["proxy"]; path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath); } if (string.IsNullOrEmpty(path)) { path = apiGatewayRequest.Path; } if (!path.StartsWith("/")) { path = "/" + path; } requestFeatures.Path = path; requestFeatures.PathBase = string.Empty; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Path)) { // This is to cover the case where the request coming in is https://myapigatewayid.execute-api.us-west-2.amazonaws.com/Prod where // Prod is the stage name and there is no ending '/'. Path will be set to '/' so to make sure we detect the correct base path // append '/' on the end to make the later EndsWith and substring work correctly. var requestContextPath = apiGatewayRequest.RequestContext.Path; if (path.EndsWith("/") && !requestContextPath.EndsWith("/")) { requestContextPath += "/"; } if (requestContextPath.EndsWith(path)) { requestFeatures.PathBase = requestContextPath.Substring(0, requestContextPath.Length - requestFeatures.Path.Length); } } // API Gateway delivers the query string in a dictionary but must be reconstructed into the full query string // before passing into ASP.NET Core framework. var queryStringParameters = apiGatewayRequest.QueryStringParameters; if (queryStringParameters != null) { StringBuilder sb = new StringBuilder("?"); foreach (var kvp in queryStringParameters) { if (sb.Length > 1) { sb.Append("&"); } sb.Append($"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value.ToString())}"); } requestFeatures.QueryString = sb.ToString(); } else { requestFeatures.QueryString = string.Empty; } var headers = apiGatewayRequest.Headers; if (headers != null) { foreach (var kvp in headers) { requestFeatures.Headers[kvp.Key] = kvp.Value?.ToString(); } } if (!requestFeatures.Headers.ContainsKey("Host")) { var apiId = apiGatewayRequest.RequestContext?.ApiId ?? ""; var stage = apiGatewayRequest.RequestContext?.Stage ?? ""; requestFeatures.Headers["Host"] = $"apigateway-{apiId}-{stage}"; } if (!string.IsNullOrEmpty(apiGatewayRequest.Body)) { Byte[] binaryBody; if (apiGatewayRequest.IsBase64Encoded) { binaryBody = Convert.FromBase64String(apiGatewayRequest.Body); } else { binaryBody = UTF8Encoding.UTF8.GetBytes(apiGatewayRequest.Body); } requestFeatures.Body = new MemoryStream(binaryBody); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallRequestFeature(requestFeatures, apiGatewayRequest, lambdaContext); } { // set up connection features var connectionFeatures = (IHttpConnectionFeature)features; IPAddress remoteIpAddress; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Identity?.SourceIp) && IPAddress.TryParse(apiGatewayRequest.RequestContext.Identity.SourceIp, out remoteIpAddress)) { connectionFeatures.RemoteIpAddress = remoteIpAddress; } if (apiGatewayRequest?.Headers?.ContainsKey("X-Forwarded-Port") == true) { connectionFeatures.RemotePort = int.Parse(apiGatewayRequest.Headers["X-Forwarded-Port"]); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallConnectionFeature(connectionFeatures, apiGatewayRequest, lambdaContext); } }
/// <summary> /// Processes the current request. /// </summary> /// <param name="lambdaContext"><see cref="ILambdaContext"/> implementation.</param> /// <param name="context">The hosting application request context object.</param> /// <param name="features">An <see cref="InvokeFeatures"/> instance.</param> /// <param name="rethrowUnhandledError"> /// If specified, an unhandled exception will be rethrown for custom error handling. /// Ensure that the error handling code calls 'this.MarshallResponse(features, 500);' after handling the error to return a <see cref="APIGatewayProxyResponse"/> to the user. /// </param> protected async Task <APIGatewayProxyResponse> ProcessRequest(ILambdaContext lambdaContext, HostingApplication.Context context, InvokeFeatures features, bool rethrowUnhandledError = false) { var defaultStatusCode = 200; Exception ex = null; try { await this._server.Application.ProcessRequestAsync(context); } catch (AggregateException agex) { ex = agex; lambdaContext.Logger.LogLine($"Caught AggregateException: '{agex}'"); var sb = new StringBuilder(); foreach (var newEx in agex.InnerExceptions) { sb.AppendLine(this.ErrorReport(newEx)); } lambdaContext.Logger.LogLine(sb.ToString()); defaultStatusCode = 500; } catch (ReflectionTypeLoadException rex) { ex = rex; lambdaContext.Logger.LogLine($"Caught ReflectionTypeLoadException: '{rex}'"); var sb = new StringBuilder(); foreach (var loaderException in rex.LoaderExceptions) { var fileNotFoundException = loaderException as FileNotFoundException; if (fileNotFoundException != null && !string.IsNullOrEmpty(fileNotFoundException.FileName)) { sb.AppendLine($"Missing file: {fileNotFoundException.FileName}"); } else { sb.AppendLine(this.ErrorReport(loaderException)); } } lambdaContext.Logger.LogLine(sb.ToString()); defaultStatusCode = 500; } catch (Exception e) { ex = e; if (rethrowUnhandledError) { throw; } lambdaContext.Logger.LogLine($"Unknown error responding to request: {this.ErrorReport(e)}"); defaultStatusCode = 500; } finally { this._server.Application.DisposeContext(context, ex); } if (features.ResponseStartingEvents != null) { await features.ResponseStartingEvents.ExecuteAsync(); } var response = this.MarshallResponse(features, lambdaContext, defaultStatusCode); lambdaContext.Logger.LogLine($"Response Base 64 Encoded: {response.IsBase64Encoded}"); if (ex != null) { response.Headers.Add(new KeyValuePair <string, string>("ErrorType", ex.GetType().Name)); } if (features.ResponseCompletedEvents != null) { await features.ResponseCompletedEvents.ExecuteAsync(); } return(response); }
protected async Task <APIGatewayProxyResponse> MyProcessRequest(ILambdaContext lambdaContext, HostingApplication.Context context, InvokeFeatures features, bool rethrowUnhandledError = false) { _logger.LogInformation("MY PROCESS REQUEST!!!"); var resp = await base.ProcessRequest(lambdaContext, context, features, rethrowUnhandledError); if (resp.Body != null && resp.Headers.ContainsKey("Content-Type")) { if ("application/octet-stream" == resp.Headers["Content-Type"]) { _logger.LogInformation("SETTING B64!!!"); resp = new MyAPIGatewayProxyResponse { Body = resp.Body, Headers = resp.Headers, StatusCode = resp.StatusCode, IsBase64Encoded = true, }; } } return(resp); }
/// <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. /// </summary> /// <param name="features"></param> /// <param name="apiGatewayRequest"></param> /// <param name="lambdaContext"></param> protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) { { var requestFeatures = (IHttpRequestFeature)features; requestFeatures.Scheme = "https"; requestFeatures.Method = apiGatewayRequest.HttpMethod; string path = null; if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.ContainsKey("proxy") && !string.IsNullOrEmpty(apiGatewayRequest.Resource)) { var proxyPath = apiGatewayRequest.PathParameters["proxy"]; path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath); } if (string.IsNullOrEmpty(path)) { path = apiGatewayRequest.Path; } if (!path.StartsWith("/")) { path = "/" + path; } requestFeatures.Path = path; requestFeatures.PathBase = string.Empty; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Path)) { // This is to cover the case where the request coming in is https://myapigatewayid.execute-api.us-west-2.amazonaws.com/Prod where // Prod is the stage name and there is no ending '/'. Path will be set to '/' so to make sure we detect the correct base path // append '/' on the end to make the later EndsWith and substring work correctly. var requestContextPath = apiGatewayRequest.RequestContext.Path; if (path.EndsWith("/") && !requestContextPath.EndsWith("/")) { requestContextPath += "/"; } else if (!path.EndsWith("/") && requestContextPath.EndsWith("/")) { // Handle a trailing slash in the request path: e.g. https://myapigatewayid.execute-api.us-west-2.amazonaws.com/Prod/foo/ requestFeatures.Path = path += "/"; } if (requestContextPath.EndsWith(path)) { requestFeatures.PathBase = requestContextPath.Substring(0, requestContextPath.Length - requestFeatures.Path.Length); } } requestFeatures.Path = Utilities.DecodeResourcePath(requestFeatures.Path); requestFeatures.QueryString = Utilities.CreateQueryStringParameters( apiGatewayRequest.QueryStringParameters, apiGatewayRequest.MultiValueQueryStringParameters, true); 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}"; } if (!string.IsNullOrEmpty(apiGatewayRequest.Body)) { requestFeatures.Body = Utilities.ConvertLambdaRequestBodyToAspNetCoreBody(apiGatewayRequest.Body, apiGatewayRequest.IsBase64Encoded); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallRequestFeature(requestFeatures, apiGatewayRequest, lambdaContext); } { // set up connection features var connectionFeatures = (IHttpConnectionFeature)features; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Identity?.SourceIp) && IPAddress.TryParse(apiGatewayRequest.RequestContext.Identity.SourceIp, out var remoteIpAddress)) { connectionFeatures.RemoteIpAddress = remoteIpAddress; } if (apiGatewayRequest?.Headers?.ContainsKey("X-Forwarded-Port") == true) { connectionFeatures.RemotePort = int.Parse(apiGatewayRequest.Headers["X-Forwarded-Port"]); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallConnectionFeature(connectionFeatures, apiGatewayRequest, lambdaContext); } }
/// <inheritdoc/> protected override void MarshallRequest(InvokeFeatures features, ApplicationLoadBalancerRequest lambdaRequest, ILambdaContext lambdaContext) { // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallHttpAuthenticationFeature(features, lambdaRequest, lambdaContext); // Request coming from Application Load Balancer will always send the headers X-Amzn-Trace-Id, X-Forwarded-For, X-Forwarded-Port, and X-Forwarded-Proto. // So this will only happen when writing tests with incomplete sample requests. if (lambdaRequest.Headers == null && lambdaRequest.MultiValueHeaders == null) { throw new Exception("Unable to determine header mode, single or multi value, because both Headers and MultiValueHeaders are null."); } if (lambdaRequest.RequestContext?.Elb?.TargetGroupArn == null) { _logger.LogWarning($"Request does not contain ELB information but is derived from {nameof(ApplicationLoadBalancerFunction)}."); } // Look to see if the request is using mutli value headers or not. This is important when // marshalling the response to know whether to fill in the the Headers or MultiValueHeaders collection. // Since a Lambda function compute environment is only one processing one event at a time it is safe to store // this as a member variable. this._multiHeaderValuesEnabled = lambdaRequest.MultiValueHeaders != null; { var requestFeatures = (IHttpRequestFeature)features; requestFeatures.Scheme = GetSingleHeaderValue(lambdaRequest, "x-forwarded-proto"); requestFeatures.Method = lambdaRequest.HttpMethod; requestFeatures.Path = Utilities.DecodeResourcePath(lambdaRequest.Path); requestFeatures.QueryString = Utilities.CreateQueryStringParameters( lambdaRequest.QueryStringParameters, lambdaRequest.MultiValueQueryStringParameters, false); Utilities.SetHeadersCollection(requestFeatures.Headers, lambdaRequest.Headers, lambdaRequest.MultiValueHeaders); if (!string.IsNullOrEmpty(lambdaRequest.Body)) { requestFeatures.Body = Utilities.ConvertLambdaRequestBodyToAspNetCoreBody(lambdaRequest.Body, lambdaRequest.IsBase64Encoded); } var userAgent = GetSingleHeaderValue(lambdaRequest, "user-agent"); if (userAgent != null && userAgent.StartsWith("ELB-HealthChecker/", StringComparison.OrdinalIgnoreCase)) { requestFeatures.Scheme = "https"; requestFeatures.Headers["host"] = "localhost"; requestFeatures.Headers["x-forwarded-port"] = "443"; requestFeatures.Headers["x-forwarded-for"] = "127.0.0.1"; } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallRequestFeature(requestFeatures, lambdaRequest, lambdaContext); } { // set up connection features var connectionFeatures = (IHttpConnectionFeature)features; var remoteIpAddressStr = GetSingleHeaderValue(lambdaRequest, "x-forwarded-for"); if (!string.IsNullOrEmpty(remoteIpAddressStr) && IPAddress.TryParse(remoteIpAddressStr, out var remoteIpAddress)) { connectionFeatures.RemoteIpAddress = remoteIpAddress; } var remotePort = GetSingleHeaderValue(lambdaRequest, "x-forwarded-port"); if (!string.IsNullOrEmpty(remotePort)) { connectionFeatures.RemotePort = int.Parse(remotePort); } // Call consumers customize method in case they want to change how API Gateway's request // was marshalled into ASP.NET Core request. PostMarshallConnectionFeature(connectionFeatures, lambdaRequest, lambdaContext); } }
/// <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. /// </summary> /// <param name="features"></param> /// <param name="apiGatewayRequest"></param> protected void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest) { var requestFeatures = (IHttpRequestFeature)features; requestFeatures.Scheme = "https"; requestFeatures.Method = apiGatewayRequest.HttpMethod; string path = null; if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.ContainsKey("proxy")) { var proxyPath = apiGatewayRequest.PathParameters["proxy"]; path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath); } if (string.IsNullOrEmpty(path)) { path = apiGatewayRequest.Path; } if (!path.StartsWith("/")) { path = "/" + path; } requestFeatures.Path = WebUtility.UrlDecode(path); // API Gateway delivers the query string in a dictionary but must be reconstructed into the full query string // before passing into ASP.NET Core framework. var queryStringParameters = apiGatewayRequest.QueryStringParameters; if (queryStringParameters != null) { StringBuilder sb = new StringBuilder("?"); foreach (var kvp in queryStringParameters) { if (sb.Length > 1) { sb.Append("&"); } sb.Append($"{WebUtility.UrlEncode(kvp.Key)}={WebUtility.UrlEncode(kvp.Value.ToString())}"); } requestFeatures.QueryString = sb.ToString(); } var headers = apiGatewayRequest.Headers; if (headers != null) { foreach (var kvp in headers) { requestFeatures.Headers[kvp.Key] = kvp.Value?.ToString(); } } if (!requestFeatures.Headers.ContainsKey("Host")) { var apiId = apiGatewayRequest.RequestContext?.ApiId ?? ""; var stage = apiGatewayRequest.RequestContext?.Stage ?? ""; requestFeatures.Headers["Host"] = $"apigateway-{apiId}-{stage}"; } if (!string.IsNullOrEmpty(apiGatewayRequest.Body)) { Byte[] binaryBody; if (apiGatewayRequest.IsBase64Encoded) { binaryBody = Convert.FromBase64String(apiGatewayRequest.Body); } else { binaryBody = UTF8Encoding.UTF8.GetBytes(apiGatewayRequest.Body); } requestFeatures.Body = new MemoryStream(binaryBody); } // set up connection features var connectionFeatures = (IHttpConnectionFeature)features; IPAddress remoteIpAddress; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Identity?.SourceIp) && IPAddress.TryParse(apiGatewayRequest.RequestContext.Identity.SourceIp, out remoteIpAddress)) { connectionFeatures.RemoteIpAddress = remoteIpAddress; } if (apiGatewayRequest?.Headers?.ContainsKey("X-Forwarded-Port") == true) { connectionFeatures.RemotePort = int.Parse(apiGatewayRequest.Headers["X-Forwarded-Port"]); } }
public void EnsureStatusCodeStartsAtIs200() { var feature = new InvokeFeatures() as IHttpResponseFeature; Assert.Equal(200, feature.StatusCode); }
public virtual async Task <APIGatewayProxyResponse> FunctionHandlerAsync(Stream input, ILambdaContext lambdaContext) { if (!IsStarted) { Start(); } try { string jsonInput = null; using (var reader = new StreamReader(input, Encoding.UTF8)) { jsonInput = reader.ReadToEnd(); } if (jsonInput == null) { throw new ArgumentNullException(nameof(input)); } var zeitRequest = JsonConvert.DeserializeObject <ZeitRequest> (jsonInput); ZeitBody zeitBody = null; if (zeitRequest.Body != null) { zeitBody = JsonConvert.DeserializeObject <ZeitBody> (zeitRequest.Body); } var request = new APIGatewayProxyRequest() { Body = zeitBody.Body, Headers = zeitBody.Headers, Path = zeitBody.Path, HttpMethod = zeitBody.Method, IsBase64Encoded = zeitBody.Enconding?.Equals("base64") ?? false }; _logger.LogDebug($"Incoming {request.HttpMethod} requests to {request.Path}"); InvokeFeatures features = new InvokeFeatures(); MarshallRequest(features, request, lambdaContext); _logger.LogDebug($"ASP.NET Core Request PathBase: {((IHttpRequestFeature)features).PathBase}, Path: {((IHttpRequestFeature)features).Path}"); var context = this.CreateContext(features); if (request?.RequestContext?.Authorizer?.Claims != null) { var identity = new ClaimsIdentity(request.RequestContext.Authorizer.Claims.Select( entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); _logger.LogDebug($"Configuring HttpContext.User with {request.RequestContext.Authorizer.Claims.Count} claims coming from API Gateway's Request Context"); context.HttpContext.User = new ClaimsPrincipal(identity); } // Add along the Lambda objects to the HttpContext to give access to Lambda to them in the ASP.NET Core application context.HttpContext.Items[LAMBDA_CONTEXT] = lambdaContext; context.HttpContext.Items[APIGATEWAY_REQUEST] = request; // Allow the context to be customized before passing the request to ASP.NET Core. PostCreateContext(context, request, lambdaContext); var response = await this.ProcessRequest(lambdaContext, context, features); return(response); } catch (Exception e) { throw new Exception(e.Message + e.InnerException + e.StackTrace); } }