Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs (94 lines of code) (raw):
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
using Amazon.Lambda.RuntimeSupport.Helpers;
using System;
using System.IO;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Lambda.RuntimeSupport.Bootstrap;
namespace Amazon.Lambda.RuntimeSupport
{
/// <summary>
/// Client to call the AWS Lambda Runtime API.
/// </summary>
public class RuntimeApiClient : IRuntimeApiClient
{
private readonly HttpClient _httpClient;
private readonly IInternalRuntimeApiClient _internalClient;
#if NET6_0_OR_GREATER
private readonly IConsoleLoggerWriter _consoleLoggerRedirector = new LogLevelLoggerWriter();
#else
private readonly IConsoleLoggerWriter _consoleLoggerRedirector = new SimpleLoggerWriter();
#endif
internal Func<Exception, ExceptionInfo> ExceptionConverter { get; set; }
internal LambdaEnvironment LambdaEnvironment { get; set; }
/// <inheritdoc/>
public IConsoleLoggerWriter ConsoleLogger => _consoleLoggerRedirector;
/// <summary>
/// Create a new RuntimeApiClient
/// </summary>
/// <param name="httpClient">The HttpClient to use to communicate with the Runtime API.</param>
public RuntimeApiClient(HttpClient httpClient)
: this(new SystemEnvironmentVariables(), httpClient)
{
}
internal RuntimeApiClient(IEnvironmentVariables environmentVariables, HttpClient httpClient, LambdaBootstrapOptions lambdaBootstrapOptions = null)
{
ExceptionConverter = ExceptionInfo.GetExceptionInfo;
_httpClient = httpClient;
LambdaEnvironment = new LambdaEnvironment(environmentVariables, lambdaBootstrapOptions);
var internalClient = new InternalRuntimeApiClient(httpClient);
internalClient.BaseUrl = "http://" + LambdaEnvironment.RuntimeServerHostAndPort + internalClient.BaseUrl;
_internalClient = internalClient;
}
internal RuntimeApiClient(IEnvironmentVariables environmentVariables, IInternalRuntimeApiClient internalClient, LambdaBootstrapOptions lambdaBootstrapOptions = null)
{
LambdaEnvironment = new LambdaEnvironment(environmentVariables, lambdaBootstrapOptions);
_internalClient = internalClient;
ExceptionConverter = ExceptionInfo.GetExceptionInfo;
}
/// <summary>
/// Report an initialization error as an asynchronous operation.
/// </summary>
/// <param name="exception">The exception to report.</param>
/// <param name="errorType">An optional errorType string that can be used to log higher-context error to customer instead of generic Runtime.Unknown by the Lambda Sandbox. </param>
/// <param name="cancellationToken">The optional cancellation token to use.</param>
/// <returns>A Task representing the asynchronous operation.</returns>
public Task ReportInitializationErrorAsync(Exception exception, String errorType = null, CancellationToken cancellationToken = default)
{
if (exception == null)
throw new ArgumentNullException(nameof(exception));
return _internalClient.ErrorAsync(errorType, LambdaJsonExceptionWriter.WriteJson(ExceptionInfo.GetExceptionInfo(exception)), cancellationToken);
}
/// <summary>
/// Send an initialization error with a type string but no other information as an asynchronous operation.
/// This can be used to directly control flow in Step Functions without creating an Exception class and throwing it.
/// </summary>
/// <param name="errorType">The type of the error to report to Lambda. This does not need to be a .NET type name.</param>
/// <param name="cancellationToken">The optional cancellation token to use.</param>
/// <returns>A Task representing the asynchronous operation.</returns>
public Task ReportInitializationErrorAsync(string errorType, CancellationToken cancellationToken = default)
{
if (errorType == null)
throw new ArgumentNullException(nameof(errorType));
return _internalClient.ErrorAsync(errorType, null, cancellationToken);
}
/// <summary>
/// Get the next function invocation from the Runtime API as an asynchronous operation.
/// Completes when the next invocation is received.
/// </summary>
/// <param name="cancellationToken">The optional cancellation token to use to stop listening for the next invocation.</param>
/// <returns>A Task representing the asynchronous operation.</returns>
public async Task<InvocationRequest> GetNextInvocationAsync(CancellationToken cancellationToken = default)
{
SwaggerResponse<Stream> response = await _internalClient.NextAsync(cancellationToken);
var headers = new RuntimeApiHeaders(response.Headers);
_consoleLoggerRedirector.SetCurrentAwsRequestId(headers.AwsRequestId);
var lambdaContext = new LambdaContext(headers, LambdaEnvironment, _consoleLoggerRedirector);
return new InvocationRequest
{
InputStream = response.Result,
LambdaContext = lambdaContext,
};
}
/// <summary>
/// Report an invocation error as an asynchronous operation.
/// </summary>
/// <param name="awsRequestId">The ID of the function request that caused the error.</param>
/// <param name="exception">The exception to report.</param>
/// <param name="cancellationToken">The optional cancellation token to use.</param>
/// <returns>A Task representing the asynchronous operation.</returns>
public Task ReportInvocationErrorAsync(string awsRequestId, Exception exception, CancellationToken cancellationToken = default)
{
if (awsRequestId == null)
throw new ArgumentNullException(nameof(awsRequestId));
if (exception == null)
throw new ArgumentNullException(nameof(exception));
var exceptionInfo = ExceptionInfo.GetExceptionInfo(exception);
var exceptionInfoJson = LambdaJsonExceptionWriter.WriteJson(exceptionInfo);
var exceptionInfoXRayJson = LambdaXRayExceptionWriter.WriteJson(exceptionInfo);
return _internalClient.ErrorWithXRayCauseAsync(awsRequestId, exceptionInfo.ErrorType, exceptionInfoJson, exceptionInfoXRayJson, cancellationToken);
}
#if NET8_0_OR_GREATER
/// <summary>
/// Triggers the snapshot to be taken, and then after resume, restores the lambda
/// context from the Runtime API as an asynchronous operation when SnapStart is enabled.
/// </summary>
/// <param name="cancellationToken">The optional cancellation token to use.</param>
/// <returns>A Task representing the asynchronous operation.</returns>
public async Task RestoreNextInvocationAsync(CancellationToken cancellationToken = default)
{
await _internalClient.RestoreNextAsync(cancellationToken);
}
/// <summary>
/// Report a restore error as an asynchronous operation when SnapStart is enabled.
/// </summary>
/// <param name="exception">The exception to report.</param>
/// <param name="errorType">An optional errorType string that can be used to log higher-context error to customer instead of generic Runtime.Unknown by the Lambda Sandbox. </param>
/// <param name="cancellationToken">The optional cancellation token to use.</param>
/// <returns>A Task representing the asynchronous operation.</returns>
public Task ReportRestoreErrorAsync(Exception exception, String errorType = null, CancellationToken cancellationToken = default)
{
if (exception == null)
throw new ArgumentNullException(nameof(exception));
return _internalClient.RestoreErrorAsync(errorType, LambdaJsonExceptionWriter.WriteJson(ExceptionInfo.GetExceptionInfo(exception)), cancellationToken);
}
#endif
/// <summary>
/// Send a response to a function invocation to the Runtime API as an asynchronous operation.
/// </summary>
/// <param name="awsRequestId">The ID of the function request being responded to.</param>
/// <param name="outputStream">The content of the response to the function invocation.</param>
/// <param name="cancellationToken">The optional cancellation token to use.</param>
/// <returns></returns>
public async Task SendResponseAsync(string awsRequestId, Stream outputStream, CancellationToken cancellationToken = default)
{
await _internalClient.ResponseAsync(awsRequestId, outputStream, cancellationToken);
}
}
}