diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index d51e23920..07404edf9 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -15,7 +15,7 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/appsettings.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/appsettings.json index 09cf536c1..5ec73ff5b 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/appsettings.json +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI-FSharp/template/src/BlueprintBaseName.1/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Warning" + "Default": "Information" } } } diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 1273012bf..7b6581189 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,8 +6,8 @@ - - - + + + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/appsettings.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/appsettings.json index 2699e2957..6d0536ac4 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/appsettings.json +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/appsettings.json @@ -1,8 +1,7 @@ { - "Lambda.Logging": { + "Logging": { "LogLevel": { - "Default": "Debug", - "Microsoft": "Information" + "Default": "Information" } }, "AppS3Bucket": "" diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/serverless.template b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/serverless.template index c5f404021..bae6a35ab 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/serverless.template +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/src/BlueprintBaseName.1/serverless.template @@ -1,78 +1,127 @@ { - "AWSTemplateFormatVersion" : "2010-09-09", - "Transform" : "AWS::Serverless-2016-10-31", - "Description" : "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.", + "AWSTemplateFormatVersion": "2010-09-09", + "Transform": "AWS::Serverless-2016-10-31", + "Description": "An AWS Serverless Application that uses the ASP.NET Core framework running in Amazon Lambda.", - "Parameters" : { - "ShouldCreateBucket" : { - "Type" : "String", - "AllowedValues" : ["true", "false"], - "Description" : "If true then the S3 bucket that will be proxied will be created with the CloudFormation stack." - }, - "BucketName" : { - "Type" : "String", - "Description" : "Name of S3 bucket that will be proxied. If left blank a name will be generated.", - "MinLength" : "0" - } - }, + "Parameters": { + "ShouldCreateBucket": { + "Type": "String", + "AllowedValues": [ + "true", + "false" + ], + "Description": "If true then the S3 bucket that will be proxied will be created with the CloudFormation stack." + }, + "BucketName": { + "Type": "String", + "Description": "Name of S3 bucket that will be proxied. If left blank a name will be generated.", + "MinLength": "0" + } + }, + "Conditions": { + "CreateS3Bucket": { + "Fn::Equals": [ + { + "Ref": "ShouldCreateBucket" + }, + "true" + ] + }, + "BucketNameGenerated": { + "Fn::Equals": [ + { + "Ref": "BucketName" + }, + "" + ] + } + }, - "Conditions" : { - "CreateS3Bucket" : {"Fn::Equals" : [{"Ref" : "ShouldCreateBucket"}, "true"]}, - "BucketNameGenerated" : {"Fn::Equals" : [{"Ref" : "BucketName"}, ""]} - }, + "Resources": { + "AspNetCoreFunction": { + "Type": "AWS::Serverless::Function", + "Properties": { + "Handler": "BlueprintBaseName.1::BlueprintBaseName._1.LambdaEntryPoint::FunctionHandlerAsync", + "Runtime": "dotnetcore2.1", + "CodeUri": "", + "MemorySize": 256, + "Timeout": 30, + "Role": null, + "Policies": [ + "AWSLambdaFullAccess" + ], + "Environment": { + "Variables": { + "AppS3Bucket": { + "Fn::If": [ + "CreateS3Bucket", + { + "Ref": "Bucket" + }, + { + "Ref": "BucketName" + } + ] + } + } + }, + "Events": { + "ProxyResource": { + "Type": "Api", + "Properties": { + "Path": "/{proxy+}", + "Method": "ANY" + } + }, + "RootResource": { + "Type": "Api", + "Properties": { + "Path": "/", + "Method": "ANY" + } + } + } + } + }, - "Resources" : { + "Bucket": { + "Type": "AWS::S3::Bucket", + "Condition": "CreateS3Bucket", + "Properties": { + "BucketName": { + "Fn::If": [ + "BucketNameGenerated", + { + "Ref": "AWS::NoValue" + }, + { + "Ref": "BucketName" + } + ] + } + } + } + }, - "AspNetCoreFunction" : { - "Type" : "AWS::Serverless::Function", - "Properties": { - "Handler": "BlueprintBaseName.1::BlueprintBaseName._1.LambdaEntryPoint::FunctionHandlerAsync", - "Runtime": "dotnetcore2.1", - "CodeUri": "", - "MemorySize": 256, - "Timeout": 30, - "Role": null, - "Policies": [ "AWSLambdaFullAccess" ], - "Environment" : { - "Variables" : { - "AppS3Bucket" : { "Fn::If" : ["CreateS3Bucket", {"Ref":"Bucket"}, { "Ref" : "BucketName" } ] } - } - }, - "Events": { - "ProxyResource": { - "Type": "Api", - "Properties": { - "Path": "/{proxy+}", - "Method": "ANY" - } - }, - "RootResource": { - "Type": "Api", - "Properties": { - "Path": "/", - "Method": "ANY" - } - } - } - } - }, - - "Bucket" : { - "Type" : "AWS::S3::Bucket", - "Condition" : "CreateS3Bucket", - "Properties" : { - "BucketName" : { "Fn::If" : ["BucketNameGenerated", {"Ref" : "AWS::NoValue" }, { "Ref" : "BucketName" } ] } - } - } - }, - - "Outputs" : { - "ApiURL" : { - "Description" : "API endpoint URL for Prod environment", - "Value" : { "Fn::Sub" : "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" } - }, - "S3ProxyBucket" : { - "Value" : { "Fn::If" : ["CreateS3Bucket", {"Ref":"Bucket"}, { "Ref" : "BucketName" } ] } - } - } + "Outputs": { + "ApiURL": { + "Description": "API endpoint URL for Prod environment", + "Value": { + "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/" + } + }, + "S3ProxyBucket": { + "Value": { + "Fn::If": [ + "CreateS3Bucket", + { + "Ref": "Bucket" + }, + { + "Ref": "BucketName" + } + ] + } + } + } } \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj index 66dcf573e..2236eba1b 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebAPI/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj @@ -18,7 +18,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index e3a5f754d..32a9772b1 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -5,7 +5,7 @@ Lambda - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/appsettings.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/appsettings.json index 5fff67bac..ef6dc62e9 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/appsettings.json +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/AspNetCoreWebApp/template/src/BlueprintBaseName.1/appsettings.json @@ -2,7 +2,7 @@ "Logging": { "IncludeScopes": false, "LogLevel": { - "Default": "Warning" + "Default": "Information" } } } diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/ChatBotTutorial/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/ChatBotTutorial/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 1e66fef6f..96a76e009 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/ChatBotTutorial/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/ChatBotTutorial/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/blueprint-manifest.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/blueprint-manifest.json new file mode 100644 index 000000000..b72be01e0 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/blueprint-manifest.json @@ -0,0 +1,8 @@ +{ + "display-name":"Custom Runtime Function", + "system-name":"CustomRuntimeFunction", + "description": "Use Lambda Custom Runtime feature to build Lambda functions using .NET Core 2.2 or 3.0.", + "sort-order" : 101, + "hidden-tags" : ["F#","LambdaProject"], + "tags":["Custom"] +} \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/.template.config/template.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/.template.config/template.json new file mode 100644 index 000000000..c2a9f961b --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/.template.config/template.json @@ -0,0 +1,40 @@ +{ + "author": "AWS", + "classifications": ["AWS", "Lambda", "Function"], + "name": "Lambda Custom Runtime Function", + "identity": "AWS.Lambda.Function.CustomRuntimeFunction.FSharp", + "groupIdentity": "AWS.Lambda.Function.CustomRuntimFunction", + "shortName": "lambda.CustomRuntimeFunction", + "tags": { + "language": "F#", + "type": "project" + }, + "sourceName": "BlueprintBaseName.1", + "preferNameDirectory": true, + "symbols": { + "profile": { + "type": "parameter", + "description" : "The AWS credentials profile set in aws-lambda-tools-defaults.json and used as the default profile when interacting with AWS.", + "datatype": "string", + "replaces" : "DefaultProfile", + "defaultValue": "" + }, + "region": { + "type": "parameter", + "description" : "The AWS region set in aws-lambda-tools-defaults.json and used as the default region when interacting with AWS.", + "datatype": "string", + "replaces" : "DefaultRegion", + "defaultValue": "" + }, + "safe-sourcename": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "safe_namespace", + "fallbackVariableName": "safe_name" + }, + "replaces": "BlueprintBaseName._1" + } + }, + "primaryOutputs": [ { "path": "./src/BlueprintBaseName.1/BlueprintBaseName.1.csproj" } ] +} diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj new file mode 100644 index 000000000..2ea716786 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -0,0 +1,27 @@ + + + Exe + netcoreapp2.2 + Lambda + + + + Always + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/Function.fs b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/Function.fs new file mode 100644 index 000000000..cb6bd0e3c --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/Function.fs @@ -0,0 +1,36 @@ +namespace BlueprintBaseName._1 + +open Amazon.Lambda.Core +open Amazon.Lambda.RuntimeSupport +open Amazon.Lambda.Serialization.Json + +open System + +module Function = + + + /// + /// A simple function that takes a string and does a ToUpper + /// + /// To use this handler to respond to an AWS event, reference the appropriate package from + /// https://github.com/aws/aws-lambda-dotnet#events + /// and change the string input parameter to the desired event type. + /// + /// + /// + /// + let functionHandler (input: string) (_: ILambdaContext) = + match input with + | null -> String.Empty + | _ -> input.ToUpper() + + + [] + let main _args = + + let handler = Func(functionHandler) + use handlerWrapper = HandlerWrapper.GetHandlerWrapper(handler, new JsonSerializer()) + use bootstrap = new LambdaBootstrap(handlerWrapper) + + bootstrap.RunAsync().GetAwaiter().GetResult() + 0 \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/Readme.md b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/Readme.md new file mode 100644 index 000000000..3ee77acdb --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/Readme.md @@ -0,0 +1,43 @@ +# AWS Lambda Custom Runtime Function Project + +This starter project consists of: +* Function.fs - contains a main function that starts the bootstrap, and a single function handler +* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS +* bootstrap - a Linux bash script that is invoked by the AWS Lambda infrastructure to start the function + +You may also have a test project depending on the options selected. + +The generated main function is the entry point for the function's process. The main function wraps the function handler in a wrapper that the bootstrap can work with. Then it instantiates the bootstrap and sets it up to call the function handler each time the AWS Lambda function is invoked. After the set up the bootstrap is started. + +The generated function handler is a simple function accepting a string argument that returns the uppercase equivalent of the input string. Replace the body of this function, and parameters, to suit your needs. + +## Here are some steps to follow from Visual Studio: + +(Deploying and invoking custom runtime functions is not yet available in Visual Studio) + +## Here are some steps to follow to get started from the command line: + +Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line. Version 3.1.4 +or later is required to deploy this project. + +Install Amazon.Lambda.Tools Global Tools if not already installed. +``` + dotnet tool install -g Amazon.Lambda.Tools +``` + +If already installed check if new version is available. +``` + dotnet tool update -g Amazon.Lambda.Tools +``` + +Execute unit tests +``` + cd "BlueprintBaseName/test/BlueprintBaseName.Tests" + dotnet test +``` + +Deploy function to AWS Lambda +``` + cd "BlueprintBaseName/src/BlueprintBaseName" + dotnet lambda deploy-function +``` diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/aws-lambda-tools-defaults.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..f5d12b8b9 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/aws-lambda-tools-defaults.json @@ -0,0 +1,20 @@ +{ + "Information" : [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + + "dotnet lambda help", + + "All the command line options for the Lambda command can be specified in this file." + ], + + "profile":"DefaultProfile", + "region" : "DefaultRegion", + "configuration" : "Release", + "function-runtime":"provided", + "function-memory-size" : 256, + "function-timeout" : 30, + "function-handler" : "not_required_for_custom_runtime", + "function-name" : "BlueprintBaseName.1", + "msbuild-parameters": "--self-contained true" + } diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/bootstrap b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/bootstrap new file mode 100644 index 000000000..e16942ea7 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/src/BlueprintBaseName.1/bootstrap @@ -0,0 +1,4 @@ +#!/bin/sh +# This is the script that the Lambda host calls to start the custom runtime. + +/var/task/BlueprintBaseName.1 \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj new file mode 100644 index 000000000..939a7be84 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj @@ -0,0 +1,21 @@ + + + Library + false + netcoreapp2.2 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/test/BlueprintBaseName.1.Tests/FunctionTest.fs b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/test/BlueprintBaseName.1.Tests/FunctionTest.fs new file mode 100644 index 000000000..47e05ddbd --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction-FSharp/template/test/BlueprintBaseName.1.Tests/FunctionTest.fs @@ -0,0 +1,20 @@ +namespace BlueprintBaseName._1.Tests + + +open Xunit +open Amazon.Lambda.TestUtilities +open BlueprintBaseName._1 + + +module FunctionTest = + + [] + let ``Invoke ToUpper Lambda Function``() = + // Invoke the lambda function and confirm the string was upper cased. + let context = TestLambdaContext() + let upperCase = Function.functionHandler "hello world" context + + Assert.Equal("HELLO WORLD", upperCase) + + [] + let main _ = 0 diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/blueprint-manifest.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/blueprint-manifest.json new file mode 100644 index 000000000..339841380 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/blueprint-manifest.json @@ -0,0 +1,8 @@ +{ + "display-name":"Custom Runtime Function", + "system-name":"CustomRuntimeFunction", + "description": "Use Lambda Custom Runtime feature to build Lambda functions using .NET Core 2.2 or 3.0.", + "sort-order" : 101, + "hidden-tags" : ["C#","LambdaProject"], + "tags":["Custom"] +} \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/.template.config/template.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/.template.config/template.json new file mode 100644 index 000000000..f1a7b09c5 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/.template.config/template.json @@ -0,0 +1,40 @@ +{ + "author": "AWS", + "classifications": ["AWS", "Lambda", "Function"], + "name": "Lambda Custom Runtime Function", + "identity": "AWS.Lambda.Function.CustomRuntimeFunction.CSharp", + "groupIdentity": "AWS.Lambda.Function.CustomRuntimFunction", + "shortName": "lambda.CustomRuntimeFunction", + "tags": { + "language": "C#", + "type": "project" + }, + "sourceName": "BlueprintBaseName.1", + "preferNameDirectory": true, + "symbols": { + "profile": { + "type": "parameter", + "description" : "The AWS credentials profile set in aws-lambda-tools-defaults.json and used as the default profile when interacting with AWS.", + "datatype": "string", + "replaces" : "DefaultProfile", + "defaultValue": "" + }, + "region": { + "type": "parameter", + "description" : "The AWS region set in aws-lambda-tools-defaults.json and used as the default region when interacting with AWS.", + "datatype": "string", + "replaces" : "DefaultRegion", + "defaultValue": "" + }, + "safe-sourcename": { + "type": "generated", + "generator": "coalesce", + "parameters": { + "sourceVariableName": "safe_namespace", + "fallbackVariableName": "safe_name" + }, + "replaces": "BlueprintBaseName._1" + } + }, + "primaryOutputs": [ { "path": "./src/BlueprintBaseName.1/BlueprintBaseName.1.csproj" } ] +} diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj new file mode 100644 index 000000000..3268a670a --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -0,0 +1,18 @@ + + + Exe + netcoreapp2.2 + latest + Lambda + + + + Always + + + + + + + + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/Function.cs b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/Function.cs new file mode 100644 index 000000000..c10244c40 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/Function.cs @@ -0,0 +1,40 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.Json; +using System; +using System.Threading.Tasks; + +namespace BlueprintBaseName._1 +{ + public class Function + { + /// + /// The main entry point for the custom runtime. + /// + /// + private static async Task Main(string[] args) + { + Func func = FunctionHandler; + using(var handlerWrapper = HandlerWrapper.GetHandlerWrapper(func, new JsonSerializer())) + using(var bootstrap = new LambdaBootstrap(handlerWrapper)) + { + await bootstrap.RunAsync(); + } + } + + /// + /// A simple function that takes a string and does a ToUpper + /// + /// To use this handler to respond to an AWS event, reference the appropriate package from + /// https://github.com/aws/aws-lambda-dotnet#events + /// and change the string input parameter to the desired event type. + /// + /// + /// + /// + public static string FunctionHandler(string input, ILambdaContext context) + { + return input?.ToUpper(); + } + } +} diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/Readme.md b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/Readme.md new file mode 100644 index 000000000..ea66fee1a --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/Readme.md @@ -0,0 +1,43 @@ +# AWS Lambda Custom Runtime Function Project + +This starter project consists of: +* Function.cs - contains a class with a Main method that starts the bootstrap, and a single function handler method +* aws-lambda-tools-defaults.json - default argument settings for use with Visual Studio and command line deployment tools for AWS +* bootstrap - a Linux bash script that is invoked by the AWS Lambda infrastructure to start the function + +You may also have a test project depending on the options selected. + +The generated Main method is the entry point for the function's process. The main method wraps the function handler in a wrapper that the bootstrap can work with. Then it instantiates the bootstrap and sets it up to call the function handler each time the AWS Lambda function is invoked. After the set up the bootstrap is started. + +The generated function handler is a simple method accepting a string argument that returns the uppercase equivalent of the input string. Replace the body of this method, and parameters, to suit your needs. + +## Here are some steps to follow from Visual Studio: + +(Deploying and invoking custom runtime functions is not yet available in Visual Studio) + +## Here are some steps to follow to get started from the command line: + +Once you have edited your template and code you can deploy your application using the [Amazon.Lambda.Tools Global Tool](https://github.com/aws/aws-extensions-for-dotnet-cli#aws-lambda-amazonlambdatools) from the command line. Version 3.1.4 +or later is required to deploy this project. + +Install Amazon.Lambda.Tools Global Tools if not already installed. +``` + dotnet tool install -g Amazon.Lambda.Tools +``` + +If already installed check if new version is available. +``` + dotnet tool update -g Amazon.Lambda.Tools +``` + +Execute unit tests +``` + cd "BlueprintBaseName/test/BlueprintBaseName.Tests" + dotnet test +``` + +Deploy function to AWS Lambda +``` + cd "BlueprintBaseName/src/BlueprintBaseName" + dotnet lambda deploy-function +``` diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/aws-lambda-tools-defaults.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..fdec294d9 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/aws-lambda-tools-defaults.json @@ -0,0 +1,20 @@ +{ + "Information" : [ + "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.", + "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.", + + "dotnet lambda help", + + "All the command line options for the Lambda command can be specified in this file." + ], + + "profile":"DefaultProfile", + "region" : "DefaultRegion", + "configuration" : "Release", + "framework" : "netcoreapp2.2", + "function-runtime":"provided", + "function-memory-size" : 256, + "function-timeout" : 30, + "function-handler" : "not_required_for_custom_runtime", + "msbuild-parameters": "--self-contained true" + } diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/bootstrap b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/bootstrap new file mode 100644 index 000000000..e16942ea7 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/src/BlueprintBaseName.1/bootstrap @@ -0,0 +1,4 @@ +#!/bin/sh +# This is the script that the Lambda host calls to start the custom runtime. + +/var/task/BlueprintBaseName.1 \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj new file mode 100644 index 000000000..84e96b144 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj @@ -0,0 +1,15 @@ + + + netcoreapp2.2 + + + + + + + + + + + + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/test/BlueprintBaseName.1.Tests/FunctionTest.cs b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/test/BlueprintBaseName.1.Tests/FunctionTest.cs new file mode 100644 index 000000000..e08cf47d5 --- /dev/null +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/CustomRuntimeFunction/template/test/BlueprintBaseName.1.Tests/FunctionTest.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Xunit; +using Amazon.Lambda.Core; +using Amazon.Lambda.TestUtilities; + +using BlueprintBaseName._1; + +namespace BlueprintBaseName._1.Tests +{ + public class FunctionTest + { + [Fact] + public void TestToUpperFunction() + { + + // Invoke the lambda function and confirm the string was upper cased. + var context = new TestLambdaContext(); + var upperCase = Function.FunctionHandler("hello world", context); + + Assert.Equal("HELLO WORLD", upperCase); + } + } +} diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index 91983d04f..44c6530d6 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -5,10 +5,10 @@ Lambda - - + + - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj index e94a45fa4..1dc328f09 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj @@ -12,7 +12,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 1acc0c498..16d7082a6 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -5,10 +5,10 @@ Lambda - - + + - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj index 3e5fdf144..3875b2bc8 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabels/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index cb0101e1d..fccbaeedb 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -5,10 +5,10 @@ Lambda - - + + - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj index e94a45fa4..1dc328f09 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj @@ -12,7 +12,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 1acc0c498..16d7082a6 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -5,10 +5,10 @@ Lambda - - + + - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj index 3e5fdf144..3875b2bc8 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DetectImageLabelsServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DynamoDBBlogAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DynamoDBBlogAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 9390e45db..19e993058 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DynamoDBBlogAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/DynamoDBBlogAPI/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -5,9 +5,9 @@ Lambda - + - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index 4d60ddd94..b6f945f56 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -12,7 +12,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 45c85fd29..48b66be16 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index 78cf305d1..63834bb40 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -12,7 +12,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/serverless.template b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/serverless.template index 32c03d685..1a8933ad0 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/serverless.template +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless-FSharp/template/src/BlueprintBaseName.1/serverless.template @@ -16,7 +16,7 @@ "Role": null, "Policies": [ "AWSLambdaBasicExecutionRole" ], "Events": { - "PutResource": { + "RootGet": { "Type": "Api", "Properties": { "Path": "/", diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 7814ca17b..e826b7874 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,7 +6,7 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/serverless.template b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/serverless.template index be979b521..1a8933ad0 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/serverless.template +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/EmptyServerless/template/src/BlueprintBaseName.1/serverless.template @@ -6,7 +6,7 @@ "Resources" : { "Get" : { - "Type" : "AWS::Serverless::Function", + "Type" : "AWS::Serverless::Function", "Properties": { "Handler": "BlueprintBaseName.1::BlueprintBaseName._1.Functions::Get", "Runtime": "dotnetcore2.1", @@ -16,7 +16,7 @@ "Role": null, "Policies": [ "AWSLambdaBasicExecutionRole" ], "Events": { - "PutResource": { + "RootGet": { "Type": "Api", "Properties": { "Path": "/", diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index cdc0bb1d0..de83f0a80 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -10,7 +10,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/appsettings.json b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/appsettings.json index 09cf536c1..5ec73ff5b 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/appsettings.json +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/GiraffeWebApp-FSharp/template/src/BlueprintBaseName.1/appsettings.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Warning" + "Default": "Information" } } } diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/LexBookTripSample/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/LexBookTripSample/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 1e66fef6f..96a76e009 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/LexBookTripSample/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/LexBookTripSample/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -7,6 +7,6 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleApplicationLoadBalancer/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleApplicationLoadBalancer/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 8f6de297a..f1d4f404a 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleApplicationLoadBalancer/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleApplicationLoadBalancer/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,7 +6,7 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index fc6b4fd61..64a852330 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -6,9 +6,9 @@ - + - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj index 9ff865b5b..fb3bd0e8d 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj @@ -8,7 +8,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index a199d280e..4187639c1 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,8 +6,8 @@ - + - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj index 86a1eafb0..7c85a19ed 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleDynamoDBFunction/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFirehoseFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFirehoseFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 5363cbe54..4ca182823 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFirehoseFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFirehoseFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,7 +6,7 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index 5740d536c..966ebd033 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -6,7 +6,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index ef52a310a..82275f3ae 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleKinesisFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,7 +6,7 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index d9aa5f4e5..141c89458 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -6,9 +6,9 @@ - + - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj index 14f794c17..6907c5e73 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj @@ -8,7 +8,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 039865da0..4c4b24f53 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,8 +6,8 @@ - + - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj index 24818a6bf..c47517978 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3Function/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index 40bde0540..c5a2fc5f7 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -6,9 +6,9 @@ - + - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj index 14f794c17..6907c5e73 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless-FSharp/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.fsproj @@ -8,7 +8,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 039865da0..4c4b24f53 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,8 +6,8 @@ - + - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj index 24818a6bf..c47517978 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleS3FunctionServerless/template/test/BlueprintBaseName.1.Tests/BlueprintBaseName.1.Tests.csproj @@ -6,7 +6,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleSQSFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleSQSFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 310fc5464..ea94125c8 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleSQSFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/SimpleSQSFunction/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,7 +6,7 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj index 004dee088..3bd9b3458 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld-FSharp/template/src/BlueprintBaseName.1/BlueprintBaseName.1.fsproj @@ -6,7 +6,7 @@ - + diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj index 45c85fd29..48b66be16 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/StepFunctionsHelloWorld/template/src/BlueprintBaseName.1/BlueprintBaseName.1.csproj @@ -6,6 +6,6 @@ - + \ No newline at end of file diff --git a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/template.nuspec b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/template.nuspec index c047909ce..18238e675 100644 --- a/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/template.nuspec +++ b/Blueprints/BlueprintDefinitions/Msbuild-NETCore_2_1/template.nuspec @@ -2,9 +2,9 @@ Amazon.Lambda.Templates - 3.5.1 + 3.7.1 Amazon Web Services - AWS Amazon Lambda + AWS Amazon Lambda AWS Lambda templates for Microsoft Template Engine accessible with the dotnet CLI's new command en-US https://github.com/aws/aws-lambda-dotnet diff --git a/Blueprints/BlueprintPackager/BaseBlueprintPackager.cs b/Blueprints/BlueprintPackager/BaseBlueprintPackager.cs index 4881db323..4ed9a956f 100644 --- a/Blueprints/BlueprintPackager/BaseBlueprintPackager.cs +++ b/Blueprints/BlueprintPackager/BaseBlueprintPackager.cs @@ -14,15 +14,46 @@ namespace Packager public abstract class BaseBlueprintPackager { protected string _blueprintRoot; + protected IList _excludeBlueprints; - public BaseBlueprintPackager(string blueprintRoot) + /// + /// Construct a new BaseBlueprintPackager. + /// + /// root to look for blueprint-manifest.json in + /// names of blueprints to exclude from this BaseBlueprintPackager + public BaseBlueprintPackager(string blueprintRoot, IList excludeBlueprints = null) { + if (excludeBlueprints == null) + { + excludeBlueprints = new List(); + } + this._blueprintRoot = blueprintRoot; + this._excludeBlueprints = excludeBlueprints; } protected IList SearchForblueprintManifests() { - return Directory.GetFiles(_blueprintRoot, "blueprint-manifest.json", SearchOption.AllDirectories).ToList(); - } + var temp = Directory.GetFiles(_blueprintRoot, "blueprint-manifest.json", SearchOption.AllDirectories); + var result = new List(); + foreach(string possible in temp) + { + var include = true; + foreach (var excludeBlueprint in _excludeBlueprints) + { + if (Path.GetFileName(Path.GetDirectoryName(possible)) == excludeBlueprint) + { + include = false; + break; + } + } + if (include) + { + result.Add(possible); + } + } + + return result; + } } } \ No newline at end of file diff --git a/Blueprints/BlueprintPackager/Program.cs b/Blueprints/BlueprintPackager/Program.cs index 6b5318914..5830872c9 100644 --- a/Blueprints/BlueprintPackager/Program.cs +++ b/Blueprints/BlueprintPackager/Program.cs @@ -7,14 +7,19 @@ public class Program { public static void Main(string[] args) { + ProcessArgs(args, out var updateVersions); + var outputDirectory = GetFullPath(@"../../Deployment/Blueprints"); var msbuildBased_2_1_Blueprints = GetFullPath(@"../BlueprintDefinitions/Msbuild-NETCore_2_1"); try { Init(outputDirectory); - var versionUpdater = new UpdatePackageReferenceVersions(msbuildBased_2_1_Blueprints); - versionUpdater.Execute(); + if (updateVersions) + { + var versionUpdater = new UpdatePackageReferenceVersions(msbuildBased_2_1_Blueprints); + versionUpdater.Execute(); + } var vsMsbuildPackager_2_1 = new VSMsbuildBlueprintPackager(msbuildBased_2_1_Blueprints, Path.Combine(outputDirectory, "VisualStudioBlueprintsMsbuild_2_1")); vsMsbuildPackager_2_1.Execute(); @@ -27,6 +32,21 @@ public static void Main(string[] args) } } + private static void ProcessArgs(string[] args, out bool updateVersions) + { + updateVersions = false; + if (args.Length == 1 && args[0] == "--updateVersions") + { + updateVersions = true; + } + else if (args.Length != 0) + { + Console.Error.WriteLine("usage: BlueprintPackager [--updateVersions]"); + Console.Error.WriteLine("--updateVersions Run job to automatically update nuget package versions for template projects."); + Environment.Exit(-1); + } + } + public static string GetFullPath(string relativePath) { if (Directory.GetCurrentDirectory().Contains("Debug") || Directory.GetCurrentDirectory().Contains("Release")) diff --git a/Blueprints/BlueprintPackager/VSMsbuildBlueprintPackager.cs b/Blueprints/BlueprintPackager/VSMsbuildBlueprintPackager.cs index 13e130af0..5df2e92fc 100644 --- a/Blueprints/BlueprintPackager/VSMsbuildBlueprintPackager.cs +++ b/Blueprints/BlueprintPackager/VSMsbuildBlueprintPackager.cs @@ -12,12 +12,13 @@ namespace Packager { public class VSMsbuildBlueprintPackager : BaseBlueprintPackager - { - + { + private static IList ExcludeBlueprints = new List() { }; + string _outputDirectory; public VSMsbuildBlueprintPackager(string blueprintRoot, string outputDirectory) - : base(blueprintRoot) + : base(blueprintRoot, ExcludeBlueprints) { _outputDirectory = outputDirectory; // Path.Combine(outputDirectory, "VisualStudioBlueprintsMsbuild"); if(!Directory.Exists(_outputDirectory)) diff --git a/Libraries/Libraries.sln b/Libraries/Libraries.sln index d1f6cee10..2c5333698 100644 --- a/Libraries/Libraries.sln +++ b/Libraries/Libraries.sln @@ -79,7 +79,17 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellScriptsAsFunction EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.CloudWatchEvents", "src\Amazon.Lambda.CloudWatchEvents\Amazon.Lambda.CloudWatchEvents.csproj", "{AD96AA48-2E1A-4BBB-9329-E1E484172FE3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.Lambda.ApplicationLoadBalancerEvents", "src\Amazon.Lambda.ApplicationLoadBalancerEvents\Amazon.Lambda.ApplicationLoadBalancerEvents.csproj", "{0E743512-2FE4-40AD-935A-17B40ACF82C1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.ApplicationLoadBalancerEvents", "src\Amazon.Lambda.ApplicationLoadBalancerEvents\Amazon.Lambda.ApplicationLoadBalancerEvents.csproj", "{0E743512-2FE4-40AD-935A-17B40ACF82C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.RuntimeSupport", "src\Amazon.Lambda.RuntimeSupport\Amazon.Lambda.RuntimeSupport.csproj", "{0046EAB1-FA73-4D7A-A7CD-936E943F775E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Amazon.Lambda.RuntimeSupport.Tests", "Amazon.Lambda.RuntimeSupport.Tests", "{B5BD0336-7D08-492C-8489-42C987E29B39}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomRuntimeFunctionTest", "test\Amazon.Lambda.RuntimeSupport.Tests\CustomRuntimeFunctionTest\CustomRuntimeFunctionTest.csproj", "{61934DD2-CFBB-48E1-947C-75E8F736734C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.RuntimeSupport.IntegrationTests", "test\Amazon.Lambda.RuntimeSupport.Tests\Amazon.Lambda.RuntimeSupport.IntegrationTests\Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj", "{39FD7632-73D0-4BB3-AE99-DC7B8B251E29}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.Lambda.RuntimeSupport.UnitTests", "test\Amazon.Lambda.RuntimeSupport.Tests\Amazon.Lambda.RuntimeSupport.UnitTests\Amazon.Lambda.RuntimeSupport.UnitTests.csproj", "{10E47FE4-8620-4933-A14D-E33F25CA557A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -215,6 +225,22 @@ Global {0E743512-2FE4-40AD-935A-17B40ACF82C1}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E743512-2FE4-40AD-935A-17B40ACF82C1}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E743512-2FE4-40AD-935A-17B40ACF82C1}.Release|Any CPU.Build.0 = Release|Any CPU + {0046EAB1-FA73-4D7A-A7CD-936E943F775E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0046EAB1-FA73-4D7A-A7CD-936E943F775E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0046EAB1-FA73-4D7A-A7CD-936E943F775E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0046EAB1-FA73-4D7A-A7CD-936E943F775E}.Release|Any CPU.Build.0 = Release|Any CPU + {61934DD2-CFBB-48E1-947C-75E8F736734C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61934DD2-CFBB-48E1-947C-75E8F736734C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61934DD2-CFBB-48E1-947C-75E8F736734C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61934DD2-CFBB-48E1-947C-75E8F736734C}.Release|Any CPU.Build.0 = Release|Any CPU + {39FD7632-73D0-4BB3-AE99-DC7B8B251E29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39FD7632-73D0-4BB3-AE99-DC7B8B251E29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39FD7632-73D0-4BB3-AE99-DC7B8B251E29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39FD7632-73D0-4BB3-AE99-DC7B8B251E29}.Release|Any CPU.Build.0 = Release|Any CPU + {10E47FE4-8620-4933-A14D-E33F25CA557A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10E47FE4-8620-4933-A14D-E33F25CA557A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10E47FE4-8620-4933-A14D-E33F25CA557A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10E47FE4-8620-4933-A14D-E33F25CA557A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -254,6 +280,11 @@ Global {0AD1E5D6-AC23-47C1-97BF-227007021B6F} = {ADEC039D-0C34-4DA7-802B-6204FFE3F1F5} {AD96AA48-2E1A-4BBB-9329-E1E484172FE3} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12} {0E743512-2FE4-40AD-935A-17B40ACF82C1} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12} + {0046EAB1-FA73-4D7A-A7CD-936E943F775E} = {AAB54E74-20B1-42ED-BC3D-CE9F7BC7FD12} + {B5BD0336-7D08-492C-8489-42C987E29B39} = {1DE4EE60-45BA-4EF7-BE00-B9EB861E4C69} + {61934DD2-CFBB-48E1-947C-75E8F736734C} = {B5BD0336-7D08-492C-8489-42C987E29B39} + {39FD7632-73D0-4BB3-AE99-DC7B8B251E29} = {B5BD0336-7D08-492C-8489-42C987E29B39} + {10E47FE4-8620-4933-A14D-E33F25CA557A} = {B5BD0336-7D08-492C-8489-42C987E29B39} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {503678A4-B8D1-4486-8915-405A3E9CF0EB} diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs index 9be308984..357dd72ef 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction.cs @@ -76,6 +76,7 @@ protected APIGatewayProxyFunction(AspNetCoreStartupMode startupMode) } + /// /// /// @@ -86,16 +87,36 @@ protected APIGatewayProxyFunction(StartupMode startupMode) } - - private protected override void InternalPostCreateContext(HostingApplication.Context context, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) + private protected override void InternalPostCreateContext( + HostingApplication.Context context, + APIGatewayProxyRequest apiGatewayRequest, + ILambdaContext lambdaContext) { - if (apiGatewayRequest?.RequestContext?.Authorizer?.Claims != null) + var authorizer = apiGatewayRequest?.RequestContext?.Authorizer; + + if (authorizer != null) { - var identity = new ClaimsIdentity(apiGatewayRequest.RequestContext.Authorizer.Claims.Select( - entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); + // 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 {apiGatewayRequest.RequestContext.Authorizer.Claims.Count} claims coming from API Gateway's Request Context"); - context.HttpContext.User = new ClaimsPrincipal(identity); + lambdaContext.Logger.LogLine( + $"Configuring HttpContext.User with {authorizer.Claims.Count} claims coming from API Gateway's Request Context"); + context.HttpContext.User = new ClaimsPrincipal(identity); + } + else + { + // handling claims output from custom lambda authorizer + var identity = new ClaimsIdentity( + authorizer.Where(x => !string.Equals(x.Key, "claims", StringComparison.OrdinalIgnoreCase)) + .Select(entry => new Claim(entry.Key, entry.Value.ToString())), "AuthorizerIdentity"); + + lambdaContext.Logger.LogLine( + $"Configuring HttpContext.User with {authorizer.Count} claims coming from API Gateway's Request Context"); + context.HttpContext.User = new ClaimsPrincipal(identity); + } } } @@ -115,12 +136,13 @@ private protected override void InternalCustomResponseExceptionHandling(HostingA protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxyRequest apiGatewayRequest, ILambdaContext lambdaContext) { { - var requestFeatures = (IHttpRequestFeature)features; + 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)) + if (apiGatewayRequest.PathParameters != null && apiGatewayRequest.PathParameters.ContainsKey("proxy") && + !string.IsNullOrEmpty(apiGatewayRequest.Resource)) { var proxyPath = apiGatewayRequest.PathParameters["proxy"]; path = apiGatewayRequest.Resource.Replace("{proxy+}", proxyPath); @@ -157,14 +179,15 @@ protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxy if (requestContextPath.EndsWith(path)) { - requestFeatures.PathBase = requestContextPath.Substring(0, requestContextPath.Length - requestFeatures.Path.Length); + requestFeatures.PathBase = requestContextPath.Substring(0, + requestContextPath.Length - requestFeatures.Path.Length); } } requestFeatures.Path = Utilities.DecodeResourcePath(requestFeatures.Path); - requestFeatures.QueryString = Utilities.CreateQueryStringParamaters( - apiGatewayRequest.QueryStringParameters, apiGatewayRequest.MultiValueQueryStringParameters); + requestFeatures.QueryString = Utilities.CreateQueryStringParameters( + apiGatewayRequest.QueryStringParameters, apiGatewayRequest.MultiValueQueryStringParameters, true); Utilities.SetHeadersCollection(requestFeatures.Headers, apiGatewayRequest.Headers, apiGatewayRequest.MultiValueHeaders); @@ -190,7 +213,7 @@ protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxy { // set up connection features - var connectionFeatures = (IHttpConnectionFeature)features; + var connectionFeatures = (IHttpConnectionFeature) features; if (!string.IsNullOrEmpty(apiGatewayRequest?.RequestContext?.Identity?.SourceIp) && IPAddress.TryParse(apiGatewayRequest.RequestContext.Identity.SourceIp, out var remoteIpAddress)) @@ -207,7 +230,6 @@ protected override void MarshallRequest(InvokeFeatures features, APIGatewayProxy // was marshalled into ASP.NET Core request. PostMarshallConnectionFeature(connectionFeatures, apiGatewayRequest, lambdaContext); } - } /// @@ -261,4 +283,4 @@ protected override APIGatewayProxyResponse MarshallResponse(IHttpResponseFeature return response; } } -} +} \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction{TStartup}.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction{TStartup}.cs index 7758104be..7a2e3b93b 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction{TStartup}.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/APIGatewayProxyFunction{TStartup}.cs @@ -10,6 +10,26 @@ namespace Amazon.Lambda.AspNetCoreServer /// The type containing the startup methods for the application. public abstract class APIGatewayProxyFunction : APIGatewayProxyFunction where TStartup : class { + /// + /// Default Constructor. The ASP.NET Core Framework will be initialized as part of the construction. + /// + protected APIGatewayProxyFunction() + : base() + { + + } + + + /// + /// + /// + /// Configure when the ASP.NET Core framework will be initialized + protected APIGatewayProxyFunction(StartupMode startupMode) + : base(startupMode) + { + + } + /// protected override IWebHostBuilder CreateWebHostBuilder() => base.CreateWebHostBuilder().UseStartup(); diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs index 787be9916..b77c39ecc 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/AbstractAspNetCoreFunction.cs @@ -168,9 +168,10 @@ protected virtual IWebHostBuilder CreateWebHostBuilder() }) .ConfigureLogging((hostingContext, logging) => { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("LAMBDA_TASK_ROOT"))) { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); } @@ -207,6 +208,8 @@ protected void Start() _host = builder.Build(); + PostCreateWebHost(_host); + _host.Start(); _server = _host.Services.GetService(typeof(Microsoft.AspNetCore.Hosting.Server.IServer)) as LambdaServer; @@ -397,6 +400,16 @@ private protected virtual void InternalCustomResponseExceptionHandling(HostingAp } + /// + /// This methid is called after the IWebHost is created from the IWebHostBuilder and the services have been configured. The + /// WebHost hasn't been started yet. + /// + /// + protected virtual void PostCreateWebHost(IWebHost webHost) + { + + } + /// /// This method is called after the HostingApplication.Context has been created. Derived classes can overwrite this method to alter /// the context before passing the request to ASP.NET Core to process the request. diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj index d78811d3e..eb8539c8e 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Amazon.Lambda.AspNetCoreServer.csproj @@ -6,7 +6,7 @@ Amazon.Lambda.AspNetCoreServer makes it easy to run ASP.NET Core Web API applications as AWS Lambda functions. netstandard2.0 Amazon.Lambda.AspNetCoreServer - 3.0.1 + 3.0.4 Amazon.Lambda.AspNetCoreServer Amazon.Lambda.AspNetCoreServer AWS;Amazon;Lambda;aspnetcore diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs index ec830e5f0..91b4f1ac8 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction.cs @@ -64,8 +64,8 @@ protected override void MarshallRequest(InvokeFeatures features, ApplicationLoad requestFeatures.Method = lambdaRequest.HttpMethod; requestFeatures.Path = Utilities.DecodeResourcePath(lambdaRequest.Path); - requestFeatures.QueryString = Utilities.CreateQueryStringParamaters( - lambdaRequest.QueryStringParameters, lambdaRequest.MultiValueQueryStringParameters); + requestFeatures.QueryString = Utilities.CreateQueryStringParameters( + lambdaRequest.QueryStringParameters, lambdaRequest.MultiValueQueryStringParameters, false); Utilities.SetHeadersCollection(requestFeatures.Headers, lambdaRequest.Headers, lambdaRequest.MultiValueHeaders); @@ -74,6 +74,15 @@ protected override void MarshallRequest(InvokeFeatures features, ApplicationLoad 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); diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction{TStartup}.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction{TStartup}.cs index 4939ed1d9..75e199e34 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction{TStartup}.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/ApplicationLoadBalancerFunction{TStartup}.cs @@ -10,6 +10,26 @@ namespace Amazon.Lambda.AspNetCoreServer /// The type containing the startup methods for the application. public abstract class ApplicationLoadBalancerFunction : ApplicationLoadBalancerFunction where TStartup : class { + /// + /// Default Constructor. The ASP.NET Core Framework will be initialized as part of the construction. + /// + protected ApplicationLoadBalancerFunction() + : base() + { + + } + + + /// + /// + /// + /// Configure when the ASP.NET Core framework will be initialized + protected ApplicationLoadBalancerFunction(StartupMode startupMode) + : base(startupMode) + { + + } + /// protected override IWebHostBuilder CreateWebHostBuilder() => base.CreateWebHostBuilder().UseStartup(); diff --git a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs index 26bdd4574..648c6c777 100644 --- a/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs +++ b/Libraries/src/Amazon.Lambda.AspNetCoreServer/Internal/Utilities.cs @@ -65,7 +65,7 @@ internal static (string body, bool isBase64Encoded) ConvertAspNetCoreBodyToLambd } } - internal static string CreateQueryStringParamaters(IDictionary singleValues, IDictionary> multiValues) + internal static string CreateQueryStringParameters(IDictionary singleValues, IDictionary> multiValues, bool urlEncodeValue) { if (multiValues?.Count > 0) { @@ -78,7 +78,7 @@ internal static string CreateQueryStringParamaters(IDictionary s { sb.Append("&"); } - sb.Append($"{kvp.Key}={value}"); + sb.Append($"{kvp.Key}={(urlEncodeValue ? WebUtility.UrlEncode(value) : value)}"); } } return sb.ToString(); @@ -95,7 +95,7 @@ internal static string CreateQueryStringParamaters(IDictionary s { sb.Append("&"); } - sb.Append($"{kvp.Key}={kvp.Value}"); + sb.Append($"{kvp.Key}={(urlEncodeValue ? WebUtility.UrlEncode(kvp.Value) : kvp.Value)}"); } return sb.ToString(); } @@ -120,16 +120,13 @@ internal static void SetHeadersCollection(IHeaderDictionary headers, IDictionary headers[kvp.Key] = new StringValues(kvp.Value); } } - } - internal static string DecodeResourcePath(string resourcePath) - { - // Convert any + signs to percent encoding before url decoding the path. - resourcePath = resourcePath.Replace("+", "%2B"); - resourcePath = resourcePath = WebUtility.UrlDecode(resourcePath); - - return resourcePath; - } + internal static string DecodeResourcePath(string resourcePath) => WebUtility.UrlDecode(resourcePath + // Convert any + signs to percent encoding before URL decoding the path. + .Replace("+", "%2B") + // Double-escape any %2F (encoded / characters) so that they survive URL decoding the path. + .Replace("%2F", "%252F") + .Replace("%2f", "%252f")); } } diff --git a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj index 12274e644..8e046d555 100644 --- a/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj +++ b/Libraries/src/Amazon.Lambda.Logging.AspNetCore/Amazon.Lambda.Logging.AspNetCore.csproj @@ -6,7 +6,7 @@ Amazon Lambda .NET Core support - Logging ASP.NET Core package. netstandard2.0 Amazon.Lambda.Logging.AspNetCore - 2.1.0 + 2.2.0 Amazon.Lambda.Logging.AspNetCore Amazon.Lambda.Logging.AspNetCore AWS;Amazon;Lambda;Logging diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj new file mode 100644 index 000000000..df69819f0 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Amazon.Lambda.RuntimeSupport.csproj @@ -0,0 +1,26 @@ + + + + + + netstandard2.0 + 1.0.0 + Provides a bootstrap and Lambda Runtime API Client to help you to develop custom .NET Core Lambda Runtimes. + Amazon.Lambda.RuntimeSupport + Amazon.Lambda.RuntimeSupport + Amazon.Lambda.RuntimeSupport + AWS;Amazon;Lambda + + + + + + + + + + + + + + diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs new file mode 100644 index 000000000..1da2ebe18 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/HandlerWrapper.cs @@ -0,0 +1,722 @@ +/* + * 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.Core; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// This class provides methods that help you wrap existing C# Lambda implementations with LambdaBootstrapHandler delegates. + /// This makes serialization and deserialization simpler and allows you to use existing functions them with an instance of LambdaBootstrap. + /// + public class HandlerWrapper : IDisposable + { + private static readonly InvocationResponse EmptyInvocationResponse = + new InvocationResponse(new MemoryStream(0), false); + + private MemoryStream OutputStream = new MemoryStream(); + + public LambdaBootstrapHandler Handler { get; private set; } + + private HandlerWrapper(LambdaBootstrapHandler handler) + { + Handler = handler; + } + + private HandlerWrapper() { } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task Handler(); + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper(async (invocation) => + { + await handler(); + return EmptyInvocationResponse; + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task Handler(Stream) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper(async (invocation) => + { + await handler(invocation.InputStream); + return EmptyInvocationResponse; + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task Handler(PocoIn) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + return new HandlerWrapper(async (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + await handler(input); + return EmptyInvocationResponse; + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task Handler(ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper(async (invocation) => + { + await handler(invocation.LambdaContext); + return EmptyInvocationResponse; + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task Handler(Stream, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper(async (invocation) => + { + await handler(invocation.InputStream, invocation.LambdaContext); + return EmptyInvocationResponse; + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task Handler(PocoIn, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + return new HandlerWrapper(async (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + await handler(input, invocation.LambdaContext); + return EmptyInvocationResponse; + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<Stream> Handler() + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler) + { + return new HandlerWrapper(async (invocation) => + { + return new InvocationResponse(await handler()); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<Stream> Handler(Stream) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler) + { + return new HandlerWrapper(async (invocation) => + { + return new InvocationResponse(await handler(invocation.InputStream)); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<Stream> Handler(PocoIn) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + return new HandlerWrapper(async (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + return new InvocationResponse(await handler(input)); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<Stream> Handler(ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler) + { + return new HandlerWrapper(async (invocation) => + { + return new InvocationResponse(await handler(invocation.LambdaContext)); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<Stream> Handler(Stream, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler) + { + return new HandlerWrapper(async (invocation) => + { + return new InvocationResponse(await handler(invocation.InputStream, invocation.LambdaContext)); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<Stream> Handler(PocoIn, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + return new HandlerWrapper(async (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + return new InvocationResponse(await handler(input, invocation.LambdaContext)); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<PocoOut> Handler() + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = async (invocation) => + { + TOutput output = await handler(); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return new InvocationResponse(handlerWrapper.OutputStream, false); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<PocoOut> Handler(Stream) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = async (invocation) => + { + TOutput output = await handler(invocation.InputStream); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return new InvocationResponse(handlerWrapper.OutputStream, false); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<PocoOut> Handler(PocoIn) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = async (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + TOutput output = await handler(input); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return new InvocationResponse(handlerWrapper.OutputStream, false); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<PocoOut> Handler(ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = async (invocation) => + { + TOutput output = await handler(invocation.LambdaContext); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; ; + return new InvocationResponse(handlerWrapper.OutputStream, false); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<PocoOut> Handler(Stream, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = async (invocation) => + { + TOutput output = await handler(invocation.InputStream, invocation.LambdaContext); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return new InvocationResponse(handlerWrapper.OutputStream, false); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Task<PocoOut> Handler(PocoIn, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func> handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = async (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + TOutput output = await handler(input, invocation.LambdaContext); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return new InvocationResponse(handlerWrapper.OutputStream, false); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: void Handler() + /// + /// Action called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Action handler) + { + return new HandlerWrapper((invocation) => + { + handler(); + return Task.FromResult(EmptyInvocationResponse); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: void Handler(Stream) + /// + /// Action called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Action handler) + { + return new HandlerWrapper((invocation) => + { + handler(invocation.InputStream); + return Task.FromResult(EmptyInvocationResponse); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: void Handler(PocoIn) + /// + /// Action called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Action handler, ILambdaSerializer serializer) + { + return new HandlerWrapper((invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + handler(input); + return Task.FromResult(EmptyInvocationResponse); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: void Handler(ILambdaContext) + /// + /// Action called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Action handler) + { + return new HandlerWrapper((invocation) => + { + handler(invocation.LambdaContext); + return Task.FromResult(EmptyInvocationResponse); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: void Handler(Stream, ILambdaContext) + /// + /// Action called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Action handler) + { + return new HandlerWrapper((invocation) => + { + handler(invocation.InputStream, invocation.LambdaContext); + return Task.FromResult(EmptyInvocationResponse); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: void Handler(PocoIn, ILambdaContext) + /// + /// Action called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Action handler, ILambdaSerializer serializer) + { + return new HandlerWrapper((invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + handler(input, invocation.LambdaContext); + return Task.FromResult(EmptyInvocationResponse); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Stream Handler() + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper((invocation) => + { + return Task.FromResult(new InvocationResponse(handler())); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Stream Handler(Stream) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper((invocation) => + { + return Task.FromResult(new InvocationResponse(handler(invocation.InputStream))); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Stream Handler(PocoIn) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + return new HandlerWrapper((invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + return Task.FromResult(new InvocationResponse(handler(input))); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Stream Handler(ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper((invocation) => + { + return Task.FromResult(new InvocationResponse(handler(invocation.LambdaContext))); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Stream Handler(PocoIn, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler) + { + return new HandlerWrapper((invocation) => + { + return Task.FromResult(new InvocationResponse(handler(invocation.InputStream, invocation.LambdaContext))); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: Stream Handler(PocoIn, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + return new HandlerWrapper((invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + return Task.FromResult(new InvocationResponse(handler(input, invocation.LambdaContext))); + }); + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: PocoOut Handler() + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = (invocation) => + { + TOutput output = handler(); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return Task.FromResult(new InvocationResponse(handlerWrapper.OutputStream, false)); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: PocoOut Handler(Stream) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = (invocation) => + { + TOutput output = handler(invocation.InputStream); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return Task.FromResult(new InvocationResponse(handlerWrapper.OutputStream, false)); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: PocoOut Handler(PocoIn) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + TOutput output = handler(input); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return Task.FromResult(new InvocationResponse(handlerWrapper.OutputStream, false)); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: PocoOut Handler(ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = (invocation) => + { + TOutput output = handler(invocation.LambdaContext); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; ; + return Task.FromResult(new InvocationResponse(handlerWrapper.OutputStream, false)); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: PocoOut Handler(Stream, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = (invocation) => + { + TOutput output = handler(invocation.InputStream, invocation.LambdaContext); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return Task.FromResult(new InvocationResponse(handlerWrapper.OutputStream, false)); + }; + return handlerWrapper; + } + + /// + /// Get a HandlerWrapper that will call the given method on function invocation. + /// Note that you may have to cast your handler to its specific type to help the compiler. + /// Example handler signature: PocoOut Handler(PocoIn, ILambdaContext) + /// + /// Func called for each invocation of the Lambda function. + /// ILambdaSerializer to use when calling the handler + /// A HandlerWrapper + public static HandlerWrapper GetHandlerWrapper(Func handler, ILambdaSerializer serializer) + { + var handlerWrapper = new HandlerWrapper(); + handlerWrapper.Handler = (invocation) => + { + TInput input = serializer.Deserialize(invocation.InputStream); + TOutput output = handler(input, invocation.LambdaContext); + handlerWrapper.OutputStream.SetLength(0); + serializer.Serialize(output, handlerWrapper.OutputStream); + handlerWrapper.OutputStream.Position = 0; + return Task.FromResult(new InvocationResponse(handlerWrapper.OutputStream, false)); + }; + return handlerWrapper; + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + OutputStream.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(true); + } + #endregion + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs new file mode 100644 index 000000000..655d1170c --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Bootstrap/LambdaBootstrap.cs @@ -0,0 +1,156 @@ +/* + * 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 System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport +{ + public delegate Task LambdaBootstrapHandler(InvocationRequest invocation); + public delegate Task LambdaBootstrapInitializer(); + + /// + /// Class to communicate with the Lambda Runtime API, handle initialization, + /// and run the invoke loop for an AWS Lambda function + /// + public class LambdaBootstrap : IDisposable + { + /// + /// The Lambda container freezes the process at a point where an HTTP request is in progress. + /// We need to make sure we don't timeout waiting for the next invocation. + /// + private static readonly TimeSpan RuntimeApiHttpTimeout = TimeSpan.FromHours(12); + + private LambdaBootstrapInitializer _initializer; + private LambdaBootstrapHandler _handler; + + private HttpClient _httpClient; + internal IRuntimeApiClient Client { get; set; } + + /// + /// Create a LambdaBootstrap that will call the given initializer and handler. + /// + /// Delegate called for each invocation of the Lambda function. + /// Delegate called to initialize the Lambda function. If not provided the initialization step is skipped. + /// + public LambdaBootstrap(LambdaBootstrapHandler handler, LambdaBootstrapInitializer initializer = null) + { + _handler = handler ?? throw new ArgumentNullException(nameof(handler)); + _initializer = initializer; + _httpClient = new HttpClient + { + Timeout = RuntimeApiHttpTimeout + }; + Client = new RuntimeApiClient(new SystemEnvironmentVariables(), _httpClient); + } + + /// + /// Create a LambdaBootstrap that will call the given initializer and handler. + /// + /// The HandlerWrapper to call for each invocation of the Lambda function. + /// Delegate called to initialize the Lambda function. If not provided the initialization step is skipped. + /// + public LambdaBootstrap(HandlerWrapper handlerWrapper, LambdaBootstrapInitializer initializer = null) + : this(handlerWrapper.Handler, initializer) + { } + + /// + /// Run the initialization Func if provided. + /// Then run the invoke loop, calling the handler for each invocation. + /// + /// + /// A Task that represents the operation. + public async Task RunAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + bool doStartInvokeLoop = _initializer == null || await InitializeAsync(); + + while (doStartInvokeLoop && !cancellationToken.IsCancellationRequested) + { + await InvokeOnceAsync(); + } + } + + internal async Task InitializeAsync() + { + try + { + return await _initializer(); + } + catch (Exception exception) + { + await Client.ReportInitializationErrorAsync(exception); + throw; + } + } + + internal async Task InvokeOnceAsync() + { + using (var invocation = await Client.GetNextInvocationAsync()) + { + InvocationResponse response = null; + bool invokeSucceeded = false; + + try + { + response = await _handler(invocation); + invokeSucceeded = true; + } + catch (Exception exception) + { + await Client.ReportInvocationErrorAsync(invocation.LambdaContext.AwsRequestId, exception); + } + + if (invokeSucceeded) + { + try + { + await Client.SendResponseAsync(invocation.LambdaContext.AwsRequestId, response?.OutputStream); + } + finally + { + if (response != null && response.DisposeOutputStream) + { + response.OutputStream?.Dispose(); + } + } + } + } + } + + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _httpClient?.Dispose(); + } + disposedValue = true; + } + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() + { + // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + Dispose(true); + } + #endregion + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IRuntimeApiClient.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IRuntimeApiClient.cs new file mode 100644 index 000000000..f2579f97b --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/IRuntimeApiClient.cs @@ -0,0 +1,79 @@ +/* + * 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.Core; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Client to call the AWS Lambda Runtime API. + /// + public interface IRuntimeApiClient + { + /// + /// Report an initialization error as an asynchronous operation. + /// + /// The exception to report. + /// A Task representing the asynchronous operation. + Task ReportInitializationErrorAsync(Exception exception); + + /// + /// 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. + /// + /// The type of the error to report to Lambda. This does not need to be a .NET type name. + /// A Task representing the asynchronous operation. + Task ReportInitializationErrorAsync(string errorType); + + /// + /// Get the next function invocation from the Runtime API as an asynchronous operation. + /// Completes when the next invocation is received. + /// + /// A Task representing the asynchronous operation. + Task GetNextInvocationAsync(); + + /// + /// Report an invocation error as an asynchronous operation. + /// + /// The ID of the function request that caused the error. + /// The exception to report. + /// A Task representing the asynchronous operation. + Task ReportInvocationErrorAsync(string awsRequestId, Exception exception); + + /// + /// 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. + /// + /// The ID of the function request that caused the error. + /// The type of the error to report to Lambda. This does not need to be a .NET type name. + /// A Task representing the asynchronous operation. + Task ReportInvocationErrorAsync(string awsRequestId, string errorType); + + /// + /// Send a response to a function invocation to the Runtime API as an asynchronous operation. + /// + /// The ID of the function request being responded to. + /// The content of the response to the function invocation. + /// + Task SendResponseAsync(string awsRequestId, Stream outputStream); + } +} \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientAdapted.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientAdapted.cs new file mode 100644 index 000000000..7645e49bd --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientAdapted.cs @@ -0,0 +1,669 @@ +/* + * 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. + */ +/* + * This file was originally generated using NSwag. + * The original generated file is InternalClientGenerated.txt. + * This code has been adapted to: + * 1. Use an internal copy of LitJson to avoid dependency collision with consumers. + * 2. Allow streams to be sent directly as a response, instead of assuming the response is JSON. + * 3. Customize based on AWS Lambda specific nuances. (See comments in runtime-api.yaml) + */ + +namespace Amazon.Lambda.RuntimeSupport +{ + + internal partial interface IInternalRuntimeApiClient + { + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, string errorJson); + + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, string errorJson, System.Threading.CancellationToken cancellationToken); + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + System.Threading.Tasks.Task> NextAsync(); + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> NextAsync(System.Threading.CancellationToken cancellationToken); + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, System.IO.Stream outputStream); + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, System.IO.Stream outputStream, System.Threading.CancellationToken cancellationToken); + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, string errorJson); + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, string errorJson, System.Threading.CancellationToken cancellationToken); + + } + + internal partial class InternalRuntimeApiClient : IInternalRuntimeApiClient + { + private const string ErrorContentType = "application/vnd.aws.lambda.error+json"; + + private string _baseUrl = "/2018-06-01"; + private System.Net.Http.HttpClient _httpClient; + + public InternalRuntimeApiClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + } + + public string BaseUrl + { + get { return _baseUrl; } + set { _baseUrl = value; } + } + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + public System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, string errorJson) + { + return ErrorAsync(lambda_Runtime_Function_Error_Type, errorJson, System.Threading.CancellationToken.None); + } + + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, string errorJson, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/init/error"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + if (lambda_Runtime_Function_Error_Type != null) + request_.Headers.TryAddWithoutValidation("Lambda-Runtime-Function-Error-Type", ConvertToString(lambda_Runtime_Function_Error_Type, System.Globalization.CultureInfo.InvariantCulture)); + using (var content_ = new System.Net.Http.StringContent(errorJson)) + { + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(ErrorContentType); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "202") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(StatusResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + return new SwaggerResponse((int)response_.StatusCode, headers_, result_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, default(StatusResponse)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + } + finally + { + } + } + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + public System.Threading.Tasks.Task> NextAsync() + { + return NextAsync(System.Threading.CancellationToken.None); + } + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> NextAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/invocation/next"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var inputBuffer = response_.Content == null ? null : await response_.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + return new SwaggerResponse((int)response_.StatusCode, headers_, new System.IO.MemoryStream(inputBuffer)); + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, new System.IO.MemoryStream(0)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + public System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, System.IO.Stream outputStream) + { + return ResponseAsync(awsRequestId, outputStream, System.Threading.CancellationToken.None); + } + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, System.IO.Stream outputStream, System.Threading.CancellationToken cancellationToken) + { + if (awsRequestId == null) + throw new System.ArgumentNullException("awsRequestId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/invocation/{AwsRequestId}/response"); + urlBuilder_.Replace("{AwsRequestId}", System.Uri.EscapeDataString(ConvertToString(awsRequestId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + var request_ = new System.Net.Http.HttpRequestMessage(); + { + var content_ = outputStream == null ? + (System.Net.Http.HttpContent)new System.Net.Http.StringContent(string.Empty) : + (System.Net.Http.HttpContent)new System.Net.Http.StreamContent(new NonDisposingStreamWrapper(outputStream)); + + try + { + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "202") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(StatusResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + return new SwaggerResponse((int)response_.StatusCode, headers_, result_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + } + else + if (status_ == "400") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Bad Request", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "413") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Payload Too Large", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, default(StatusResponse)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + finally + { + content_?.Dispose(); + } + } + } + finally + { + } + } + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + public System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, string errorJson) + { + return Error2Async(awsRequestId, lambda_Runtime_Function_Error_Type, errorJson, System.Threading.CancellationToken.None); + } + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, string errorJson, System.Threading.CancellationToken cancellationToken) + { + if (awsRequestId == null) + throw new System.ArgumentNullException("awsRequestId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/invocation/{AwsRequestId}/error"); + urlBuilder_.Replace("{AwsRequestId}", System.Uri.EscapeDataString(ConvertToString(awsRequestId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + if (lambda_Runtime_Function_Error_Type != null) + request_.Headers.TryAddWithoutValidation("Lambda-Runtime-Function-Error-Type", ConvertToString(lambda_Runtime_Function_Error_Type, System.Globalization.CultureInfo.InvariantCulture)); + using (var content_ = new System.Net.Http.StringContent(errorJson)) + { + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse(ErrorContentType); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "202") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(StatusResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + return new SwaggerResponse((int)response_.StatusCode, headers_, result_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + } + else + if (status_ == "400") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Bad Request", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = ThirdParty.Json.LitJson.JsonMapper.ToObject(responseData_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, default(StatusResponse)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + } + finally + { + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value is System.Enum) + { + string name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value; + } + } + } + } + else if (value is bool) + { + return System.Convert.ToString(value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[])value); + } + else if (value != null && value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array)value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + return System.Convert.ToString(value, cultureInfo); + } + } + + internal partial class StatusResponse + { + public string status { get; set; } + } + + internal partial class ErrorResponse + { + public string errorMessage { get; set; } + public string errorType { get; set; } + } + + internal partial class SwaggerResponse + { + public int StatusCode { get; private set; } + + public System.Collections.Generic.Dictionary> Headers { get; private set; } + + public SwaggerResponse(int statusCode, System.Collections.Generic.Dictionary> headers) + { + StatusCode = statusCode; + Headers = headers; + } + } + + internal partial class SwaggerResponse : SwaggerResponse + { + public TResult Result { get; private set; } + + public SwaggerResponse(int statusCode, System.Collections.Generic.Dictionary> headers, TResult result) + : base(statusCode, headers) + { + Result = result; + } + } + + public partial class RuntimeApiClientException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.Dictionary> Headers { get; private set; } + + public RuntimeApiClientException(string message, int statusCode, string response, System.Collections.Generic.Dictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + response.Substring(0, response.Length >= 512 ? 512 : response.Length), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + public partial class RuntimeApiClientException : RuntimeApiClientException + { + public TResult Result { get; private set; } + + public RuntimeApiClientException(string message, int statusCode, string response, System.Collections.Generic.Dictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientGenerated.txt b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientGenerated.txt new file mode 100644 index 000000000..f4e33eab4 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InternalClientGenerated.txt @@ -0,0 +1,672 @@ +//---------------------- +// +// Generated using the NSwag toolchain v12.0.4.0 (NJsonSchema v9.12.7.0 (Newtonsoft.Json v11.0.0.0)) (http://NSwag.org) +// +//---------------------- + +namespace Amazon.Lambda.RuntimeSupport +{ + #pragma warning disable // Disable all warnings + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "12.0.4.0 (NJsonSchema v9.12.7.0 (Newtonsoft.Json v11.0.0.0))")] + public partial interface IInternalRuntimeApiClient + { + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, object body); + + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, object body, System.Threading.CancellationToken cancellationToken); + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + System.Threading.Tasks.Task> NextAsync(); + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> NextAsync(System.Threading.CancellationToken cancellationToken); + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, object body); + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, object body, System.Threading.CancellationToken cancellationToken); + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, object body); + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, object body, System.Threading.CancellationToken cancellationToken); + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "12.0.4.0 (NJsonSchema v9.12.7.0 (Newtonsoft.Json v11.0.0.0))")] + internal partial class InternalRuntimeApiClient : IInternalRuntimeApiClient + { + private string _baseUrl = "/2018-06-01"; + private System.Net.Http.HttpClient _httpClient; + private System.Lazy _settings; + + public InternalRuntimeApiClient(System.Net.Http.HttpClient httpClient) + { + _httpClient = httpClient; + _settings = new System.Lazy(() => + { + var settings = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(settings); + return settings; + }); + } + + public string BaseUrl + { + get { return _baseUrl; } + set { _baseUrl = value; } + } + + protected Newtonsoft.Json.JsonSerializerSettings JsonSerializerSettings { get { return _settings.Value; } } + + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + public System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, object body) + { + return ErrorAsync(lambda_Runtime_Function_Error_Type, body, System.Threading.CancellationToken.None); + } + + /// Non-recoverable initialization error. Runtime should exit after reporting the error. Error will be served in response to the first invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> ErrorAsync(string lambda_Runtime_Function_Error_Type, object body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/init/error"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + if (lambda_Runtime_Function_Error_Type != null) + request_.Headers.TryAddWithoutValidation("Lambda-Runtime-Function-Error-Type", ConvertToString(lambda_Runtime_Function_Error_Type, System.Globalization.CultureInfo.InvariantCulture)); + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "202") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(StatusResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return new SwaggerResponse((int)response_.StatusCode, headers_, result_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, default(StatusResponse)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + public System.Threading.Tasks.Task> NextAsync() + { + return NextAsync(System.Threading.CancellationToken.None); + } + + /// Runtime makes this HTTP request when it is ready to receive and process a new invoke. + /// This is an iterator-style blocking API call. Response contains event JSON document, specific to the invoking service. + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> NextAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/invocation/next"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(object); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return new SwaggerResponse((int)response_.StatusCode, headers_, result_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, default(object)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + public System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, object body) + { + return ResponseAsync(awsRequestId, body, System.Threading.CancellationToken.None); + } + + /// Runtime makes this request in order to submit a response. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> ResponseAsync(string awsRequestId, object body, System.Threading.CancellationToken cancellationToken) + { + if (awsRequestId == null) + throw new System.ArgumentNullException("awsRequestId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/invocation/{AwsRequestId}/response"); + urlBuilder_.Replace("{AwsRequestId}", System.Uri.EscapeDataString(ConvertToString(awsRequestId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "202") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(StatusResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return new SwaggerResponse((int)response_.StatusCode, headers_, result_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + } + else + if (status_ == "400") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Bad Request", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "413") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Payload Too Large", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, default(StatusResponse)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + public System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, object body) + { + return Error2Async(awsRequestId, lambda_Runtime_Function_Error_Type, body, System.Threading.CancellationToken.None); + } + + /// Runtime makes this request in order to submit an error response. It can be either a function error, or a runtime error. Error will be served in response to the invoke. + /// Accepted + /// A server side error occurred. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + public async System.Threading.Tasks.Task> Error2Async(string awsRequestId, string lambda_Runtime_Function_Error_Type, object body, System.Threading.CancellationToken cancellationToken) + { + if (awsRequestId == null) + throw new System.ArgumentNullException("awsRequestId"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/runtime/invocation/{AwsRequestId}/error"); + urlBuilder_.Replace("{AwsRequestId}", System.Uri.EscapeDataString(ConvertToString(awsRequestId, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + if (lambda_Runtime_Function_Error_Type != null) + request_.Headers.TryAddWithoutValidation("Lambda-Runtime-Function-Error-Type", ConvertToString(lambda_Runtime_Function_Error_Type, System.Globalization.CultureInfo.InvariantCulture)); + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json"); + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + if (response_.Content != null && response_.Content.Headers != null) + { + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + } + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "202") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(StatusResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return new SwaggerResponse((int)response_.StatusCode, headers_, result_); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + } + else + if (status_ == "400") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Bad Request", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "403") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(ErrorResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + } + catch (System.Exception exception_) + { + throw new RuntimeApiClientException("Could not deserialize the response body.", (int)response_.StatusCode, responseData_, headers_, exception_); + } + throw new RuntimeApiClientException("Forbidden", (int)response_.StatusCode, responseData_, headers_, result_, null); + } + else + if (status_ == "500") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("Container error. Non-recoverable state. Runtime should exit promptly.\n", (int)response_.StatusCode, responseData_, headers_, null); + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new RuntimeApiClientException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", (int)response_.StatusCode, responseData_, headers_, null); + } + + return new SwaggerResponse((int)response_.StatusCode, headers_, default(StatusResponse)); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo) + { + if (value is System.Enum) + { + string name = System.Enum.GetName(value.GetType(), value); + if (name != null) + { + var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name); + if (field != null) + { + var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute)) + as System.Runtime.Serialization.EnumMemberAttribute; + if (attribute != null) + { + return attribute.Value; + } + } + } + } + else if (value is bool) { + return System.Convert.ToString(value, cultureInfo).ToLowerInvariant(); + } + else if (value is byte[]) + { + return System.Convert.ToBase64String((byte[]) value); + } + else if (value != null && value.GetType().IsArray) + { + var array = System.Linq.Enumerable.OfType((System.Array) value); + return string.Join(",", System.Linq.Enumerable.Select(array, o => ConvertToString(o, cultureInfo))); + } + + return System.Convert.ToString(value, cultureInfo); + } + } + + + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.12.7.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class StatusResponse + { + [Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Status { get; set; } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.12.7.0 (Newtonsoft.Json v11.0.0.0)")] + public partial class ErrorResponse + { + [Newtonsoft.Json.JsonProperty("errorMessage", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ErrorMessage { get; set; } + + [Newtonsoft.Json.JsonProperty("errorType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string ErrorType { get; set; } + + + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "12.0.4.0 (NJsonSchema v9.12.7.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class SwaggerResponse + { + public int StatusCode { get; private set; } + + public System.Collections.Generic.Dictionary> Headers { get; private set; } + + public SwaggerResponse(int statusCode, System.Collections.Generic.Dictionary> headers) + { + StatusCode = statusCode; + Headers = headers; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "12.0.4.0 (NJsonSchema v9.12.7.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class SwaggerResponse : SwaggerResponse + { + public TResult Result { get; private set; } + + public SwaggerResponse(int statusCode, System.Collections.Generic.Dictionary> headers, TResult result) + : base(statusCode, headers) + { + Result = result; + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "12.0.4.0 (NJsonSchema v9.12.7.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class RuntimeApiClientException : System.Exception + { + public int StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.Dictionary> Headers { get; private set; } + + public RuntimeApiClientException(string message, int statusCode, string response, System.Collections.Generic.Dictionary> headers, System.Exception innerException) + : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + response.Substring(0, response.Length >= 512 ? 512 : response.Length), innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "12.0.4.0 (NJsonSchema v9.12.7.0 (Newtonsoft.Json v11.0.0.0))")] + public partial class RuntimeApiClientException : RuntimeApiClientException + { + public TResult Result { get; private set; } + + public RuntimeApiClientException(string message, int statusCode, string response, System.Collections.Generic.Dictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InvocationRequest.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InvocationRequest.cs new file mode 100644 index 000000000..93d5bf151 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InvocationRequest.cs @@ -0,0 +1,43 @@ +/* + * 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.Core; +using System; +using System.IO; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Class that contains all the information necessary to handle an invocation of an AWS Lambda function. + /// + public class InvocationRequest : IDisposable + { + /// + /// Input to the function invocation. + /// + public Stream InputStream { get; internal set; } + + /// + /// Context for the invocation. + /// + public ILambdaContext LambdaContext { get; internal set; } + + internal InvocationRequest() { } + + public void Dispose() + { + InputStream?.Dispose(); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InvocationResponse.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InvocationResponse.cs new file mode 100644 index 000000000..815fd4b13 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/InvocationResponse.cs @@ -0,0 +1,47 @@ +/* + * 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.Core; +using System; +using System.IO; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Class that contains the response for an invocation of an AWS Lambda function. + /// + public class InvocationResponse + { + /// + /// Output from the function invocation. + /// + public Stream OutputStream { get; set; } + + /// + /// True if the LambdaBootstrap should dispose the stream after it's read, false otherwise. + /// Set this to false if you plan to reuse the same output stream for multiple invocations of the function. + /// + public bool DisposeOutputStream { get; private set; } = true; + + public InvocationResponse(Stream outputStream) + : this(outputStream, true) + { } + + public InvocationResponse(Stream outputStream, bool disposeOutputStream) + { + OutputStream = outputStream ?? throw new ArgumentNullException(nameof(outputStream)); + DisposeOutputStream = disposeOutputStream; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/NonDisposingStreamWrapper.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/NonDisposingStreamWrapper.cs new file mode 100644 index 000000000..a1eeda61c --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/NonDisposingStreamWrapper.cs @@ -0,0 +1,102 @@ +/* + * 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 System.IO; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// This class is used to wrap the function response stream. + /// It allows the wrapped stream to be reused. + /// + internal class NonDisposingStreamWrapper : Stream + { + Stream _wrappedStream; + + public NonDisposingStreamWrapper(Stream wrappedStream) + { + _wrappedStream = wrappedStream; + } + + public override bool CanRead + { + get + { + return _wrappedStream.CanRead; + } + } + + public override bool CanSeek + { + get + { + return _wrappedStream.CanSeek; + } + } + + public override bool CanWrite + { + get + { + return _wrappedStream.CanWrite; + } + } + + public override long Length + { + get + { + return _wrappedStream.Length; + } + } + + public override long Position + { + get + { + return _wrappedStream.Position; + } + + set + { + _wrappedStream.Position = value; + } + } + + public override void Flush() + { + _wrappedStream.Flush(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _wrappedStream.Read(buffer, offset, count); + } + + public override long Seek(long offset, SeekOrigin origin) + { + return _wrappedStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + _wrappedStream.SetLength(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + _wrappedStream.Write(buffer, offset, count); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs new file mode 100644 index 000000000..c9ccaf75f --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiClient.cs @@ -0,0 +1,149 @@ +/* + * 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.Core; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Client to call the AWS Lambda Runtime API. + /// + public class RuntimeApiClient : IRuntimeApiClient + { + private readonly HttpClient _httpClient; + private readonly IInternalRuntimeApiClient _internalClient; + + internal Func ExceptionConverter { get; set; } + internal LambdaEnvironment LambdaEnvironment { get; set; } + + /// + /// Create a new RuntimeApiClient + /// + /// The HttpClient to use to communicate with the Runtime API. + public RuntimeApiClient(HttpClient httpClient) + : this(new SystemEnvironmentVariables(), httpClient) + { + } + + internal RuntimeApiClient(IEnvironmentVariables environmentVariables, HttpClient httpClient) + { + ExceptionConverter = ExceptionInfo.GetExceptionInfo; + _httpClient = httpClient; + LambdaEnvironment = new LambdaEnvironment(environmentVariables); + var internalClient = new InternalRuntimeApiClient(httpClient); + internalClient.BaseUrl = "http://" + LambdaEnvironment.RuntimeServerHostAndPort + internalClient.BaseUrl; + _internalClient = internalClient; + } + + internal RuntimeApiClient(IEnvironmentVariables environmentVariables, IInternalRuntimeApiClient internalClient) + { + LambdaEnvironment = new LambdaEnvironment(environmentVariables); + _internalClient = internalClient; + ExceptionConverter = ExceptionInfo.GetExceptionInfo; + } + + /// + /// Report an initialization error as an asynchronous operation. + /// + /// The exception to report. + /// A Task representing the asynchronous operation. + public Task ReportInitializationErrorAsync(Exception exception) + { + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + return _internalClient.ErrorAsync(null, LambdaJsonExceptionWriter.WriteJson(ExceptionInfo.GetExceptionInfo(exception))); + } + + /// + /// 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. + /// + /// The type of the error to report to Lambda. This does not need to be a .NET type name. + /// A Task representing the asynchronous operation. + public Task ReportInitializationErrorAsync(string errorType) + { + if (errorType == null) + throw new ArgumentNullException(nameof(errorType)); + + return _internalClient.ErrorAsync(errorType, null); + } + + /// + /// Get the next function invocation from the Runtime API as an asynchronous operation. + /// Completes when the next invocation is received. + /// + /// A Task representing the asynchronous operation. + public async Task GetNextInvocationAsync() + { + SwaggerResponse response = await _internalClient.NextAsync(System.Threading.CancellationToken.None); + + var lambdaContext = new LambdaContext(new RuntimeApiHeaders(response.Headers), LambdaEnvironment); + return new InvocationRequest + { + InputStream = response.Result, + LambdaContext = lambdaContext, + }; + } + + /// + /// Report an invocation error as an asynchronous operation. + /// + /// The ID of the function request that caused the error. + /// The exception to report. + /// A Task representing the asynchronous operation. + public Task ReportInvocationErrorAsync(string awsRequestId, Exception exception) + { + if (awsRequestId == null) + throw new ArgumentNullException(nameof(awsRequestId)); + + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + var exceptionInfo = ExceptionInfo.GetExceptionInfo(exception); + return _internalClient.Error2Async(awsRequestId, exceptionInfo.ErrorType, LambdaJsonExceptionWriter.WriteJson(exceptionInfo)); + } + + /// + /// 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. + /// + /// The ID of the function request that caused the error. + /// The type of the error to report to Lambda. This does not need to be a .NET type name. + /// A Task representing the asynchronous operation. + public Task ReportInvocationErrorAsync(string awsRequestId, string errorType) + { + return _internalClient.Error2Async(awsRequestId, errorType, null); + } + + /// + /// Send a response to a function invocation to the Runtime API as an asynchronous operation. + /// + /// The ID of the function request being responded to. + /// The content of the response to the function invocation. + /// + public async Task SendResponseAsync(string awsRequestId, Stream outputStream) + { + await _internalClient.ResponseAsync(awsRequestId, outputStream, CancellationToken.None); + } + } +} \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiHeaders.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiHeaders.cs new file mode 100644 index 000000000..5a7176af5 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/RuntimeApiHeaders.cs @@ -0,0 +1,62 @@ +/* + * 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 System.Collections.Generic; +using System.Linq; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class RuntimeApiHeaders + { + internal const string HeaderAwsRequestId = "Lambda-Runtime-Aws-Request-Id"; + internal const string HeaderTraceId = "Lambda-Runtime-Trace-Id"; + internal const string HeaderClientContext = "Lambda-Runtime-Client-Context"; + internal const string HeaderCognitoIdentity = "Lambda-Runtime-Cognito-Identity"; + internal const string HeaderDeadlineMs = "Lambda-Runtime-Deadline-Ms"; + internal const string HeaderInvokedFunctionArn = "Lambda-Runtime-Invoked-Function-Arn"; + + public RuntimeApiHeaders(Dictionary> headers) + { + DeadlineMs = GetHeaderValueOrNull(headers, HeaderDeadlineMs); + AwsRequestId = GetHeaderValueRequired(headers, HeaderAwsRequestId); + ClientContextJson = GetHeaderValueOrNull(headers, HeaderClientContext); + CognitoIdentityJson = GetHeaderValueOrNull(headers, HeaderCognitoIdentity); + InvokedFunctionArn = GetHeaderValueRequired(headers, HeaderInvokedFunctionArn); + TraceId = GetHeaderValueOrNull(headers, HeaderTraceId); + } + + public string AwsRequestId { get; private set; } + public string InvokedFunctionArn { get; private set; } + public string TraceId { get; private set; } + public string ClientContextJson { get; private set; } + public string CognitoIdentityJson { get; private set; } + public string DeadlineMs { get; private set; } + + private string GetHeaderValueRequired(Dictionary> headers, string header) + { + return headers[header].FirstOrDefault(); + } + + private string GetHeaderValueOrNull(Dictionary> headers, string header) + { + if (headers.ContainsKey(header)) + { + return headers[header].FirstOrDefault(); + } + + return null; + } + } + +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/runtime-api.yaml b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/runtime-api.yaml new file mode 100644 index 000000000..05c66927b --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Client/runtime-api.yaml @@ -0,0 +1,250 @@ +# +# 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. +# +# This file was copied from https://docs.aws.amazon.com/lambda/latest/dg/samples/runtime-api.zip on 12/1/2018 +# +# This file documents BYOL (RAPID) API using Open API 3 specification format. +# +# +# A note on error reporting in BYOL. +# +# Runtimes should report all errors using Lambda standard error format, in order to integrate with other AWS services: +# +# Content-Type: application/vnd.aws.lambda.error+json: +# { +# "errorMessage": "...", +# "errorType": "...", +# "stackTrace": [], +# } +# +# Corresponding Open API 3 schema: +# +# ErrorRequest: +# type: object +# properties: +# errorMessage: +# type: string +# errorType: +# type: string +# stackTrace: +# type: array +# items: +# type: string +# +# Lambda's default behavior is to use Lambda-Runtime-Function-Error-Type header value to construct an error response +# when error payload is not provided or can not be read. + + +openapi: 3.0.0 +info: + title: BYOL API + description: Native Runtime API. + version: 1.0.2 + +servers: + - url: /2018-06-01 + +paths: + + /runtime/init/error: + post: + summary: > + Non-recoverable initialization error. Runtime should exit after reporting + the error. Error will be served in response to the first invoke. + parameters: + - in: header + name: Lambda-Runtime-Function-Error-Type + schema: + type: string + requestBody: + content: + '*/*': + schema: {} + responses: + '202': + description: Accepted + content: + application/json: + schema: + $ref: '#/components/schemas/StatusResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: > + Container error. Non-recoverable state. Runtime should exit promptly. + + /runtime/invocation/next: + get: + summary: > + Runtime makes this HTTP request when it is ready to receive and process a + new invoke. + responses: + '200': + description: > + This is an iterator-style blocking API call. Response contains + event JSON document, specific to the invoking service. + headers: + Lambda-Runtime-Aws-Request-Id: + description: AWS request ID associated with the request. + schema: + type: string + Lambda-Runtime-Trace-Id: + description: X-Ray tracing header. + schema: + type: string + Lambda-Runtime-Client-Context: + description: > + Information about the client application and device when invoked + through the AWS Mobile SDK. + schema: + type: string + Lambda-Runtime-Cognito-Identity: + description: > + Information about the Amazon Cognito identity provider when invoked + through the AWS Mobile SDK. + schema: + type: string + Lambda-Runtime-Deadline-Ms: + description: > + Function execution deadline counted in milliseconds since the Unix epoch. + schema: + type: string + Lambda-Runtime-Invoked-Function-Arn: + description: > + The ARN requested. This can be different in each invoke that + executes the same version. + schema: + type: string + content: + application/json: + schema: + $ref: '#/components/schemas/EventResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: > + Container error. Non-recoverable state. Runtime should exit promptly. + + /runtime/invocation/{AwsRequestId}/response: + post: + summary: Runtime makes this request in order to submit a response. + parameters: + - in: path + name: AwsRequestId + schema: + type: string + required: true + requestBody: + content: + '*/*': + schema: {} + responses: + '202': + description: Accepted + content: + application/json: + schema: + $ref: '#/components/schemas/StatusResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '413': + description: Payload Too Large + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: > + Container error. Non-recoverable state. Runtime should exit promptly. + + /runtime/invocation/{AwsRequestId}/error: + post: + summary: > + Runtime makes this request in order to submit an error response. It can + be either a function error, or a runtime error. Error will be served in + response to the invoke. + parameters: + - in: path + name: AwsRequestId + schema: + type: string + required: true + - in: header + name: Lambda-Runtime-Function-Error-Type + schema: + type: string + requestBody: + content: + '*/*': + schema: {} + responses: + '202': + description: Accepted + content: + application/json: + schema: + $ref: '#/components/schemas/StatusResponse' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '403': + description: Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: > + Container error. Non-recoverable state. Runtime should exit promptly. + +components: + schemas: + + StatusResponse: + type: object + properties: + status: + type: string + + ErrorResponse: + type: object + properties: + errorMessage: + type: string + errorType: + type: string + + EventResponse: + type: object \ No newline at end of file diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoClientApplication.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoClientApplication.cs new file mode 100644 index 000000000..5e23113ba --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoClientApplication.cs @@ -0,0 +1,54 @@ +/* + * 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.Core; +using System; +using ThirdParty.Json.LitJson; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class CognitoClientApplication : IClientApplication + { + public string AppPackageName { get; internal set; } + + public string AppTitle { get; internal set; } + + public string AppVersionCode { get; internal set; } + + public string AppVersionName { get; internal set; } + + public string InstallationId { get; internal set; } + + internal static CognitoClientApplication FromJsonData(JsonData jsonData) + { + var result = new CognitoClientApplication(); + + if (jsonData != null) + { + if (jsonData["app_package_name"] != null) + result.AppPackageName = jsonData["app_package_name"].ToString(); + if (jsonData["app_title"] != null) + result.AppTitle = jsonData["app_title"].ToString(); + if (jsonData["app_version_code"] != null) + result.AppVersionCode = jsonData["app_version_code"].ToString(); + if (jsonData["app_version_name"] != null) + result.AppVersionName = jsonData["app_version_name"].ToString(); + if (jsonData["installation_id"] != null) + result.InstallationId = jsonData["installation_id"].ToString(); + } + + return result; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoClientContext.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoClientContext.cs new file mode 100644 index 000000000..208223768 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoClientContext.cs @@ -0,0 +1,60 @@ +/* + * 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.Core; +using System; +using System.Collections.Generic; +using ThirdParty.Json.LitJson; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class CognitoClientContext : IClientContext + { + public IDictionary Environment { get; internal set; } + + public IClientApplication Client { get; internal set; } + + public IDictionary Custom { get; internal set; } + + internal static CognitoClientContext FromJson(string json) + { + var result = new CognitoClientContext(); + + if (!string.IsNullOrWhiteSpace(json)) + { + var jsonData = JsonMapper.ToObject(json); + + if (jsonData["client"] != null) + result.Client = CognitoClientApplication.FromJsonData(jsonData["client"]); + if (jsonData["custom"] != null) + result.Custom = GetDictionaryFromJsonData(jsonData["custom"]); + if (jsonData["env"] != null) + result.Environment = GetDictionaryFromJsonData(jsonData["env"]); + } + return result; + } + + private static IDictionary GetDictionaryFromJsonData(JsonData jsonData) + { + var result = new Dictionary(); + + foreach (var key in jsonData.PropertyNames) + { + result.Add(key, jsonData[key].ToString()); + } + + return result; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoIdentity.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoIdentity.cs new file mode 100644 index 000000000..2849d5b75 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/CognitoIdentity.cs @@ -0,0 +1,43 @@ +/* + * 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.Core; +using System; +using ThirdParty.Json.LitJson; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class CognitoIdentity : ICognitoIdentity + { + public string IdentityId { get; internal set; } + + public string IdentityPoolId { get; internal set; } + + internal static CognitoIdentity FromJson(string json) + { + var result = new CognitoIdentity(); + + if (!string.IsNullOrWhiteSpace(json)) + { + var jsonData = JsonMapper.ToObject(json); + if (jsonData["identityId"] != null) + result.IdentityId = jsonData["identityId"].ToString(); + if (jsonData["identityPoolId"] != null) + result.IdentityPoolId = jsonData["identityPoolId"].ToString(); + } + + return result; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/IEnvironmentVariables.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/IEnvironmentVariables.cs new file mode 100644 index 000000000..4d97576d1 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/IEnvironmentVariables.cs @@ -0,0 +1,29 @@ +/* + * 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 System.Collections; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Interface to access environment variables. + /// Allows for unit testing without changing the real System environment variables. + /// + internal interface IEnvironmentVariables + { + void SetEnvironmentVariable(string variable, string value); + string GetEnvironmentVariable(string variable); + IDictionary GetEnvironmentVariables(); + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaConsoleLogger.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaConsoleLogger.cs new file mode 100644 index 000000000..828e18707 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaConsoleLogger.cs @@ -0,0 +1,32 @@ +/* + * 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.Core; +using System; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class LambdaConsoleLogger : ILambdaLogger + { + public void Log(string message) + { + Console.Write(message); + } + + public void LogLine(string message) + { + Console.WriteLine(message); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs new file mode 100644 index 000000000..26ba8aab4 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaContext.cs @@ -0,0 +1,74 @@ +/* + * 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.Core; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class LambdaContext : ILambdaContext + { + private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1); + + private LambdaEnvironment _lambdaEnvironment; + private RuntimeApiHeaders _runtimeApiHeaders; + + private long _deadlineMs; + private int _memoryLimitInMB; + private Lazy _cognitoIdentityLazy; + private Lazy _cognitoClientContextLazy; + + public LambdaContext(RuntimeApiHeaders runtimeApiHeaders, LambdaEnvironment lambdaEnvironment) + { + _lambdaEnvironment = lambdaEnvironment; + _runtimeApiHeaders = runtimeApiHeaders; + + int.TryParse(_lambdaEnvironment.FunctionMemorySize, out _memoryLimitInMB); + long.TryParse(_runtimeApiHeaders.DeadlineMs, out _deadlineMs); + _cognitoIdentityLazy = new Lazy(() => CognitoIdentity.FromJson(runtimeApiHeaders.CognitoIdentityJson)); + _cognitoClientContextLazy = new Lazy(() => CognitoClientContext.FromJson(runtimeApiHeaders.ClientContextJson)); + + // set environment variable so that if the function uses the XRay client it will work correctly + _lambdaEnvironment.SetXAmznTraceId(_runtimeApiHeaders.TraceId); + } + + // TODO If/When Amazon.Lambda.Core is major versioned, add this to ILambdaContext. + // Until then function code can access it via the _X_AMZN_TRACE_ID environment variable set by LambdaBootstrap. + public string TraceId => _runtimeApiHeaders.TraceId; + + public string AwsRequestId => _runtimeApiHeaders.AwsRequestId; + + public IClientContext ClientContext => _cognitoClientContextLazy.Value; + + public string FunctionName => _lambdaEnvironment.FunctionName; + + public string FunctionVersion => _lambdaEnvironment.FunctionVersion; + + public ICognitoIdentity Identity => _cognitoIdentityLazy.Value; + + public string InvokedFunctionArn => _runtimeApiHeaders.InvokedFunctionArn; + + public ILambdaLogger Logger => new LambdaConsoleLogger(); + + public string LogGroupName => _lambdaEnvironment.LogGroupName; + + public string LogStreamName => _lambdaEnvironment.LogStreamName; + + public int MemoryLimitInMB => _memoryLimitInMB; + + public TimeSpan RemainingTime => TimeSpan.FromMilliseconds(_deadlineMs - (DateTime.Now - UnixEpoch).TotalMilliseconds); + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaEnvironment.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaEnvironment.cs new file mode 100644 index 000000000..7d7497163 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/LambdaEnvironment.cs @@ -0,0 +1,100 @@ +/* + * 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 System; +using System.Linq; +using System.Reflection; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Provides access to Environment Variables set by the Lambda runtime environment. + /// + public class LambdaEnvironment + { + internal const string EnvVarExecutionEnvironment = "AWS_EXECUTION_ENV"; + internal const string EnvVarFunctionMemorySize = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"; + internal const string EnvVarFunctionName = "AWS_LAMBDA_FUNCTION_NAME"; + internal const string EnvVarFunctionVersion = "AWS_LAMBDA_FUNCTION_VERSION"; + internal const string EnvVarHandler = "_HANDLER"; + internal const string EnvVarLogGroupName = "AWS_LAMBDA_LOG_GROUP_NAME"; + internal const string EnvVarLogStreamName = "AWS_LAMBDA_LOG_STREAM_NAME"; + internal const string EnvVarServerHostAndPort = "AWS_LAMBDA_RUNTIME_API"; + internal const string EnvVarTraceId = "_X_AMZN_TRACE_ID"; + + internal const string AmazonLambdaRuntimeSupportMarker = "amazonlambdaruntimesupport"; + + private IEnvironmentVariables _environmentVariables; + + public LambdaEnvironment() : this(new SystemEnvironmentVariables()) { } + + internal LambdaEnvironment(IEnvironmentVariables environmentVariables) + { + _environmentVariables = environmentVariables; + + FunctionMemorySize = environmentVariables.GetEnvironmentVariable(EnvVarFunctionMemorySize) as string; + FunctionName = environmentVariables.GetEnvironmentVariable(EnvVarFunctionName) as string; + FunctionVersion = environmentVariables.GetEnvironmentVariable(EnvVarFunctionVersion) as string; + LogGroupName = environmentVariables.GetEnvironmentVariable(EnvVarLogGroupName) as string; + LogStreamName = environmentVariables.GetEnvironmentVariable(EnvVarLogStreamName) as string; + RuntimeServerHostAndPort = environmentVariables.GetEnvironmentVariable(EnvVarServerHostAndPort) as string; + Handler = environmentVariables.GetEnvironmentVariable(EnvVarHandler) as string; + + SetExecutionEnvironment(); + } + + private void SetExecutionEnvironment() + { + + var envValue = _environmentVariables.GetEnvironmentVariable(EnvVarExecutionEnvironment); + if (!string.IsNullOrEmpty(envValue) && !envValue.Contains(AmazonLambdaRuntimeSupportMarker)) + { + var assemblyVersion = typeof(LambdaBootstrap).Assembly + .GetCustomAttributes(typeof(AssemblyInformationalVersionAttribute), false) + .FirstOrDefault() + as AssemblyInformationalVersionAttribute; + + _environmentVariables.SetEnvironmentVariable(EnvVarExecutionEnvironment, + $"{envValue}_{AmazonLambdaRuntimeSupportMarker}_{assemblyVersion?.InformationalVersion}"); + } + } + + internal void SetXAmznTraceId(string xAmznTraceId) + { + _environmentVariables.SetEnvironmentVariable(EnvVarTraceId, xAmznTraceId); + } + + public string FunctionMemorySize { get; private set; } + public string FunctionName { get; private set; } + public string FunctionVersion { get; private set; } + public string LogGroupName { get; private set; } + public string LogStreamName { get; private set; } + public string RuntimeServerHostAndPort { get; private set; } + public string Handler { get; private set; } + public string XAmznTraceId + { + get + { + return _environmentVariables.GetEnvironmentVariable(EnvVarTraceId); + } + } + public string ExecutionEnvironment + { + get + { + return _environmentVariables.GetEnvironmentVariable(EnvVarExecutionEnvironment); + } + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/SystemEnvironmentVariables.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/SystemEnvironmentVariables.cs new file mode 100644 index 000000000..c4d306db2 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/Context/SystemEnvironmentVariables.cs @@ -0,0 +1,40 @@ +/* + * 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 System; +using System.Collections; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Implementation of IEnvironmentVariables that accesses the real System.Environment + /// + internal class SystemEnvironmentVariables : IEnvironmentVariables + { + public string GetEnvironmentVariable(string variable) + { + return Environment.GetEnvironmentVariable(variable); + } + + public IDictionary GetEnvironmentVariables() + { + return Environment.GetEnvironmentVariables(); + } + + public void SetEnvironmentVariable(string variable, string value) + { + Environment.SetEnvironmentVariable(variable, value); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/ExceptionInfo.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/ExceptionInfo.cs new file mode 100644 index 000000000..b72f3d586 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/ExceptionInfo.cs @@ -0,0 +1,92 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Amazon.Lambda.RuntimeSupport +{ + /// + /// Class to hold basic raw information extracted from Exceptions. + /// The raw information will be formatted as JSON to be reported to the Lambda Runtime API. + /// + internal class ExceptionInfo + { + public string ErrorMessage { get; set; } + public string ErrorType { get; set; } + public StackFrameInfo[] StackFrames { get; set; } + public string StackTrace { get; set; } + + public ExceptionInfo InnerException { get; set; } + public List InnerExceptions { get; internal set; } = new List(); + + public Exception OriginalException { get; set; } + + public ExceptionInfo() { } + + public ExceptionInfo(Exception exception, bool isNestedException = false) + { + if (exception == null) + throw new ArgumentNullException(nameof(exception)); + + ErrorType = exception.GetType().Name; + ErrorMessage = exception.Message; + + if (!string.IsNullOrEmpty(exception.StackTrace)) + { + StackTrace stackTrace = new StackTrace(exception, true); + StackTrace = stackTrace.ToString(); + + // Only extract the stack frames like this for the top-level exception + // This is used for Xray Exception serialization + if (isNestedException || stackTrace?.GetFrames() == null) + { + StackFrames = new StackFrameInfo[0]; + } + else + { + StackFrames = ( + from sf in stackTrace.GetFrames() + where sf != null + select new StackFrameInfo(sf) + ).ToArray(); + } + } + + if (exception.InnerException != null) + { + InnerException = new ExceptionInfo(exception.InnerException, true); + } + + AggregateException aggregateException = exception as AggregateException; + + if (aggregateException != null && aggregateException.InnerExceptions != null) + { + foreach (var innerEx in aggregateException.InnerExceptions) + { + InnerExceptions.Add(new ExceptionInfo(innerEx, true)); + } + } + } + + public static ExceptionInfo GetExceptionInfo(Exception exception) + { + return new ExceptionInfo(exception); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/JsonExceptionWriterHelpers.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/JsonExceptionWriterHelpers.cs new file mode 100644 index 000000000..284f5a04a --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/JsonExceptionWriterHelpers.cs @@ -0,0 +1,118 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Text; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class JsonExceptionWriterHelpers + { + /// + /// This method escapes a string for use as a JSON string value. + /// It was adapted from the PutString method in the ThirdParty.Json.LitJson.JsonWriter class. + /// + /// TODO: rewrite the *JsonExceptionWriter classes to use a JSON library instead of building strings directly. + /// + /// + public static string EscapeStringForJson(string str) + { + if (str == null) + return null; + + int n = str.Length; + var sb = new StringBuilder(n * 2); + for (int i = 0; i < n; i++) + { + char c = str[i]; + switch (c) + { + case '\n': + sb.Append(@"\n"); + break; + + case '\r': + sb.Append(@"\r"); + break; + + case '\t': + sb.Append(@"\t"); + break; + + case '"': + sb.Append(@"\"""); + break; + + case '\\': + sb.Append(@"\\"); + break; + + case '\f': + sb.Append(@"\f"); + break; + + case '\b': + sb.Append(@"\b"); + break; + + case '\u0085': // Next Line + sb.Append(@"\u0085"); + break; + + case '\u2028': // Line Separator + sb.Append(@"\u2028"); + break; + + case '\u2029': // Paragraph Separator + sb.Append(@"\u2029"); + break; + + default: + if (c < ' ') + { + // Turn into a \uXXXX sequence + sb.Append(@"\u"); + sb.Append(IntToHex((int)c)); + } + else + { + sb.Append(c); + } + break; + } + } + return sb.ToString().Trim(); + } + + private static char[] IntToHex(int n) + { + int num; + char[] hex = new char[4]; + + for (int i = 0; i < 4; i++) + { + num = n % 16; + + if (num < 10) + hex[3 - i] = (char)('0' + num); + else + hex[3 - i] = (char)('A' + (num - 10)); + + n >>= 4; + } + return hex; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/LambdaJsonExceptionWriter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/LambdaJsonExceptionWriter.cs new file mode 100644 index 000000000..6394ce874 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/LambdaJsonExceptionWriter.cs @@ -0,0 +1,197 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Amazon.Lambda.RuntimeSupport +{ + // TODO rewrite using a JSON library + internal class LambdaJsonExceptionWriter + { + private static readonly Encoding TEXT_ENCODING = Encoding.UTF8; + private const int INDENT_SIZE = 2; + private const int MAX_PAYLOAD_SIZE = 256 * 1024; // 256KB + private const string ERROR_MESSAGE = "errorMessage"; + private const string ERROR_TYPE = "errorType"; + private const string STACK_TRACE = "stackTrace"; + private const string INNER_EXCEPTION = "cause"; + private const string INNER_EXCEPTIONS = "causes"; + private const string TRUNCATED_MESSAGE = + "{\"" + ERROR_MESSAGE + "\": \"Exception exceeded maximum payload size of 256KB.\"}"; + + /// + /// Write the formatted JSON response for this exception, and all inner exceptions. + /// + /// The exception response object to serialize. + /// The serialized JSON string. + public static string WriteJson(ExceptionInfo ex) + { + if (ex == null) + throw new ArgumentNullException("ex"); + + MeteredStringBuilder jsonBuilder = new MeteredStringBuilder(TEXT_ENCODING, MAX_PAYLOAD_SIZE); + string json = AppendJson(ex, 0, false, MAX_PAYLOAD_SIZE - jsonBuilder.SizeInBytes); + if (json != null && jsonBuilder.HasRoomForString(json)) + { + jsonBuilder.Append(json); + } + else + { + jsonBuilder.Append(TRUNCATED_MESSAGE); + } + return jsonBuilder.ToString(); + } + + private static string AppendJson(ExceptionInfo ex, int tab, bool appendComma, int remainingRoom) + { + if (remainingRoom <= 0) + return null; + + MeteredStringBuilder jsonBuilder = new MeteredStringBuilder(TEXT_ENCODING, remainingRoom); + int nextTabDepth = tab + 1; + int nextNextTabDepth = nextTabDepth + 1; + + List jsonElements = new List(); + + // Grab the elements we want to capture + string message = JsonExceptionWriterHelpers.EscapeStringForJson(ex.ErrorMessage); + string type = JsonExceptionWriterHelpers.EscapeStringForJson(ex.ErrorType); + string stackTrace = ex.StackTrace; + ExceptionInfo innerException = ex.InnerException; + List innerExceptions = ex.InnerExceptions; + + // Create the JSON lines for each non-null element + string messageJson = null; + if (message != null) + { + // Trim important for Aggregate Exceptions, whose + // message contains multiple lines by default + messageJson = TabString($"\"{ERROR_MESSAGE}\": \"{message}\"", nextTabDepth); + } + + string typeJson = TabString($"\"{ERROR_TYPE}\": \"{type}\"", nextTabDepth); + string stackTraceJson = GetStackTraceJson(stackTrace, nextTabDepth); + + + // Add each non-null element to the json elements list + if (typeJson != null) jsonElements.Add(typeJson); + if (messageJson != null) jsonElements.Add(messageJson); + if (stackTraceJson != null) jsonElements.Add(stackTraceJson); + + // Exception JSON body, comma delimited + string exceptionJsonBody = string.Join("," + Environment.NewLine, jsonElements); + + jsonBuilder.AppendLine(TabString("{", tab)); + jsonBuilder.Append(exceptionJsonBody); + + bool hasInnerException = innerException != null; + bool hasInnerExceptionList = innerExceptions != null && innerExceptions.Count > 0; + + // Before we close, check for inner exception(s) + if (hasInnerException) + { + // We have to add the inner exception, which means we need + // another comma after the exception json body + jsonBuilder.AppendLine(","); + + jsonBuilder.Append(TabString($"\"{INNER_EXCEPTION}\": ", nextTabDepth)); + + string innerJson = AppendJson(innerException, nextTabDepth, hasInnerExceptionList, remainingRoom - jsonBuilder.SizeInBytes); + if (innerJson != null && jsonBuilder.HasRoomForString(innerJson)) + { + jsonBuilder.Append(innerJson); + } + else + { + jsonBuilder.AppendLine(TRUNCATED_MESSAGE); + } + } + + if (hasInnerExceptionList) + { + jsonBuilder.Append(TabString($"\"{INNER_EXCEPTIONS}\": [", nextTabDepth)); + + for (int i = 0; i < innerExceptions.Count; i++) + { + var isLastOne = i == innerExceptions.Count - 1; + var innerException2 = innerExceptions[i]; + string innerJson = AppendJson(innerException2, nextNextTabDepth, !isLastOne, remainingRoom - jsonBuilder.SizeInBytes); + if (innerJson != null && jsonBuilder.HasRoomForString(innerJson)) + { + jsonBuilder.Append(innerJson); + } + else + { + jsonBuilder.AppendLine(TabString(TRUNCATED_MESSAGE, nextNextTabDepth)); + break; + } + } + + jsonBuilder.AppendLine(TabString($"]", nextTabDepth)); + } + + if (!hasInnerException && !hasInnerExceptionList) + { + // No inner exceptions = no trailing comma needed + jsonBuilder.AppendLine(); + } + + jsonBuilder.AppendLine(TabString("}" + (appendComma ? "," : ""), tab)); + return jsonBuilder.ToString(); + } + + private static string GetStackTraceJson(string stackTrace, int tab) + { + if (stackTrace == null) + { + return null; + } + + string[] stackTraceElements = stackTrace.Split(new[] { Environment.NewLine }, StringSplitOptions.None) + .Select(s => s.Trim()) + .Where(s => !String.IsNullOrWhiteSpace(s)) + .Select(s => TabString(($"\"{JsonExceptionWriterHelpers.EscapeStringForJson(s)}\""), tab + 1)) + .ToArray(); + + if (stackTraceElements.Length == 0) + { + return null; + } + + StringBuilder stackTraceBuilder = new StringBuilder(); + stackTraceBuilder.AppendLine(TabString($"\"{STACK_TRACE}\": [", tab)); + stackTraceBuilder.AppendLine(string.Join("," + Environment.NewLine, stackTraceElements)); + stackTraceBuilder.Append(TabString("]", tab)); + return stackTraceBuilder.ToString(); + } + + private static string TabString(string str, int tabDepth) + { + if (tabDepth == 0) return str; + + StringBuilder stringBuilder = new StringBuilder(); + for (int x = 0; x < tabDepth * INDENT_SIZE; x++) + { + stringBuilder.Append(" "); + } + stringBuilder.Append(str); + + return stringBuilder.ToString(); + } + + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/MeteredStringBuilder.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/MeteredStringBuilder.cs new file mode 100644 index 000000000..f68faac8d --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/MeteredStringBuilder.cs @@ -0,0 +1,72 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Text; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class MeteredStringBuilder + { + private readonly int _maxSize; + private readonly Encoding _encoding; + private readonly StringBuilder _stringBuilder; + + public int SizeInBytes { get; private set; } + + public MeteredStringBuilder(Encoding encoding, int maxSize) + { + if (maxSize <= 0) + { + throw new ArgumentOutOfRangeException(nameof(maxSize)); + } + + _stringBuilder = new StringBuilder(); + SizeInBytes = 0; + _encoding = encoding ?? throw new ArgumentNullException(nameof(encoding)); + _maxSize = maxSize; + } + + public void Append(string str) + { + int strSizeInBytes = _encoding.GetByteCount(str); + _stringBuilder.Append(str); + SizeInBytes += strSizeInBytes; + } + + public void AppendLine(string str) + { + string strWithLine = str + Environment.NewLine; + int strSizeInBytes = _encoding.GetByteCount(strWithLine); + _stringBuilder.Append(strWithLine); + SizeInBytes += strSizeInBytes; + } + + public void AppendLine() + { + AppendLine(""); + } + + public bool HasRoomForString(string str) + { + return SizeInBytes + _encoding.GetByteCount(str) < _maxSize; + } + + public override string ToString() + { + return _stringBuilder.ToString(); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/StackFrameInfo.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/StackFrameInfo.cs new file mode 100644 index 000000000..6652b8d2c --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ExceptionHandling/StackFrameInfo.cs @@ -0,0 +1,55 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; + +namespace Amazon.Lambda.RuntimeSupport +{ + internal class StackFrameInfo + { + public StackFrameInfo(string path, int line, string label) + { + Path = path; + Line = line; + Label = label; + } + + public StackFrameInfo(StackFrame stackFrame) + { + Path = stackFrame.GetFileName(); + Line = stackFrame.GetFileLineNumber(); + + var method = stackFrame.GetMethod(); + if (method != null) + { + var methodTypeName = method.DeclaringType?.Name; + if (methodTypeName == null) + { + Label = method.Name; + } + else + { + Label = methodTypeName + "." + method.Name; + } + } + } + + public string Path { get; } + public int Line { get; } + public string Label { get; } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/README.md b/Libraries/src/Amazon.Lambda.RuntimeSupport/README.md new file mode 100644 index 000000000..c17770f3a --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/README.md @@ -0,0 +1,97 @@ +# Amazon.Lambda.RuntimeSupport + +This package contains classes that can be used to create .NET Core custom runtimes in AWS Lambda. +You can learn more about AWS Lambda custom runtimes [here](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html). + +## LambdaBootstrap + +The [Amazon.Lambda.RuntimeSupport.LambdaBootstrap](./Bootstrap/LambdaBootstrap.cs) class handles initialization of the function and runs the loop that receives and handles invocations from the AWS Lambda service. +Take a look at the signature of the ToUpperAsync method in the example below. This signature is the default for function handlers when using the Amazon.Lambda.RuntimeSupport.LambdaBootstrap class. + +```csharp +private static MemoryStream ResponseStream = new MemoryStream(); +private static JsonSerializer JsonSerializer = new JsonSerializer(); + +private static async Task Main(string[] args) +{ + using(var bootstrap = new LambdaBootstrap(ToUpperAsync)) + { + await bootstrap.RunAsync(); + } +} + +private static Task ToUpperAsync(InvocationRequest invocation) +{ + var input = JsonSerializer.Deserialize(invocation.InputStream); + + ResponseStream.SetLength(0); + JsonSerializer.Serialize(input.ToUpper(), ResponseStream); + ResponseStream.Position = 0; + + return Task.FromResult(new InvocationResponse(responseStream, false)); +} +``` + +The [Amazon.Lambda.RuntimeSupport.HandlerWrapper](./Bootstrap/HandlerWrapper.cs) class allows you to use existing handlers with LambdaBootstrap. +The Amazon.Lambda.RuntimeSupport.HandlerWrapper class also takes care of deserialization and serialization for you. + +```csharp +private static async Task Main(string[] args) +{ + using(var handlerWrapper = HandlerWrapper.GetHandlerWrapper((Func)ToUpper, new JsonSerializer())) + using(var bootstrap = new LambdaBootstrap(handlerWrapper)) + { + await bootstrap.RunAsync(); + } +} + +// existing handler doesn't conform to the new Amazon.Lambda.RuntimeSupport default signature +public static string ToUpper(string input, ILambdaContext context) +{ + return input?.ToUpper(); +} +``` + +The [Amazon.Lambda.RuntimeSupport.RuntimeApiClient](./Client/RuntimeApiClient.cs) class handles interaction with the [AWS Lambda Runtime Interface](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). +This class is meant for advanced use cases. Under most circumstances you won't use it directly. + +Below is an excerpt from the [Amazon.Lambda.RuntimeSupport.LambdaBootstrap](./Bootstrap/LambdaBootstrap.cs) class that demonstrates how Amazon.Lambda.RuntimeSupport.RuntimeApiClient is used. +Read the full source for Amazon.Lambda.RuntimeSupport.LambdaBootstrap to learn more. + +```csharp +internal async Task InvokeOnceAsync() +{ + using (var invocation = await Client.GetNextInvocationAsync()) + { + InvocationResponse response = null; + bool invokeSucceeded = false; + + try + { + response = await _handler(invocation); + invokeSucceeded = true; + } + catch (Exception exception) + { + await Client.ReportInvocationErrorAsync(invocation.LambdaContext.AwsRequestId, exception); + } + + if (invokeSucceeded) + { + try + { + await Client.SendResponseAsync(invocation.LambdaContext.AwsRequestId, response?.OutputStream); + } + finally + { + if (response != null && response.DisposeOutputStream) + { + response.OutputStream?.Dispose(); + } + } + } + } +} +``` + + diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/AmazonLitJsonHelpers.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/AmazonLitJsonHelpers.cs new file mode 100644 index 000000000..6cb173cee --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/AmazonLitJsonHelpers.cs @@ -0,0 +1,369 @@ +/******************************************************************************* + * Copyright 2008-2017 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. + * ***************************************************************************** + * __ _ _ ___ + * ( )( \/\/ )/ __) + * /__\ \ / \__ \ + * (_)(_) \/\/ (___/ + * + * AWS SDK for .NET + * + */ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Amazon.Util.Internal +{ + internal class AmazonUtils + { + public static readonly DateTime EPOCH_START = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + public static double ConvertToUnixEpochMilliSeconds(DateTime dateTime) + { + TimeSpan ts = new TimeSpan(dateTime.ToUniversalTime().Ticks - EPOCH_START.Ticks); + double milli = Math.Round(ts.TotalMilliseconds, 0) / 1000.0; + return milli; + } + } + internal interface ITypeInfo + { + Type BaseType { get; } + + Type Type { get; } + + Assembly Assembly { get; } + bool IsArray { get; } + + Array ArrayCreateInstance(int length); + + Type GetInterface(string name); + Type[] GetInterfaces(); + + IEnumerable GetProperties(); + + IEnumerable GetFields(); + FieldInfo GetField(string name); + + MethodInfo GetMethod(string name); + MethodInfo GetMethod(string name, ITypeInfo[] paramTypes); + + MemberInfo[] GetMembers(); + + + ConstructorInfo GetConstructor(ITypeInfo[] paramTypes); + + PropertyInfo GetProperty(string name); + + bool IsAssignableFrom(ITypeInfo typeInfo); + + bool IsEnum {get;} + + bool IsClass { get; } + bool IsValueType { get; } + + bool IsInterface { get; } + bool IsAbstract { get; } + bool IsSealed { get; } + + object EnumToObject(object value); + + ITypeInfo EnumGetUnderlyingType(); + + object CreateInstance(); + + ITypeInfo GetElementType(); + + bool IsType(Type type); + + string FullName { get; } + string Name { get; } + + bool IsGenericTypeDefinition { get; } + bool IsGenericType { get; } + bool ContainsGenericParameters { get; } + Type GetGenericTypeDefinition(); + Type[] GetGenericArguments(); + + object[] GetCustomAttributes(bool inherit); + object[] GetCustomAttributes(ITypeInfo attributeType, bool inherit); + + } + + internal static class TypeFactory + { + public static readonly ITypeInfo[] EmptyTypes = new ITypeInfo[] { }; + public static ITypeInfo GetTypeInfo(Type type) + { + if (type == null) + return null; + + return new TypeInfoWrapper(type); + } + + abstract class AbstractTypeInfo : ITypeInfo + { + protected Type _type; + + internal AbstractTypeInfo(Type type) + { + this._type = type; + } + + public Type Type + { + get { return this._type; } + } + + public override int GetHashCode() + { + return this._type.GetHashCode(); + } + + public override bool Equals(object obj) + { + var typeWrapper = obj as AbstractTypeInfo; + if (typeWrapper == null) + return false; + + return this._type.Equals(typeWrapper._type); + } + + public bool IsType(Type type) + { + return this._type == type; + } + + public abstract Type BaseType { get; } + public abstract Assembly Assembly { get; } + public abstract Type GetInterface(string name); + public abstract Type[] GetInterfaces(); + public abstract IEnumerable GetProperties(); + public abstract IEnumerable GetFields(); + public abstract FieldInfo GetField(string name); + public abstract MethodInfo GetMethod(string name); + public abstract MethodInfo GetMethod(string name, ITypeInfo[] paramTypes); + public abstract MemberInfo[] GetMembers(); + public abstract PropertyInfo GetProperty(string name); + public abstract bool IsAssignableFrom(ITypeInfo typeInfo); + public abstract bool IsClass { get; } + public abstract bool IsInterface { get; } + public abstract bool IsAbstract { get; } + public abstract bool IsSealed { get; } + public abstract bool IsEnum { get; } + public abstract bool IsValueType { get; } + public abstract ConstructorInfo GetConstructor(ITypeInfo[] paramTypes); + + public abstract object[] GetCustomAttributes(bool inherit); + public abstract object[] GetCustomAttributes(ITypeInfo attributeType, bool inherit); + + public abstract bool ContainsGenericParameters { get; } + public abstract bool IsGenericTypeDefinition { get; } + public abstract bool IsGenericType { get; } + public abstract Type GetGenericTypeDefinition(); + public abstract Type[] GetGenericArguments(); + + public bool IsArray + { + get { return this._type.IsArray; } + } + + + public object EnumToObject(object value) + { + return Enum.ToObject(this._type, value); + } + + public ITypeInfo EnumGetUnderlyingType() + { + return TypeFactory.GetTypeInfo(Enum.GetUnderlyingType(this._type)); + } + + public object CreateInstance() + { + return Activator.CreateInstance(this._type); + } + + public Array ArrayCreateInstance(int length) + { + return Array.CreateInstance(this._type, length); + } + + public ITypeInfo GetElementType() + { + return TypeFactory.GetTypeInfo(this._type.GetElementType()); + } + + public string FullName + { + get + { + return this._type.FullName; + } + } + + public string Name + { + get + { + return this._type.Name; + } + } + } + class TypeInfoWrapper : AbstractTypeInfo + { + + internal TypeInfoWrapper(Type type) + : base(type) + { + } + + public override Type BaseType + { + get { return this._type.BaseType; } + } + + public override FieldInfo GetField(string name) + { + return this._type.GetField(name); + } + + public override Type GetInterface(string name) + { + return this._type.GetInterfaces().FirstOrDefault(x => (x.Namespace + "." + x.Name) == name); + } + + public override Type[] GetInterfaces() + { + return this._type.GetInterfaces(); + } + + public override IEnumerable GetProperties() + { + return this._type.GetProperties(); + } + + public override IEnumerable GetFields() + { + return this._type.GetFields(); + } + + public override MemberInfo[] GetMembers() + { + return this._type.GetMembers(); + } + + public override bool IsClass + { + get { return this._type.IsClass; } + } + + public override bool IsValueType + { + get { return this._type.IsValueType; } + } + + public override bool IsInterface + { + get { return this._type.IsInterface; } + } + + public override bool IsAbstract + { + get { return this._type.IsAbstract; } + } + + public override bool IsSealed + { + get { return this._type.IsSealed; } + } + + public override bool IsEnum + { + get { return this._type.IsEnum; } + } + + public override MethodInfo GetMethod(string name) + { + return this._type.GetMethod(name); + } + + public override MethodInfo GetMethod(string name, ITypeInfo[] paramTypes) + { + Type[] types = new Type[paramTypes.Length]; + for (int i = 0; i < paramTypes.Length; i++) + types[i] = ((AbstractTypeInfo)paramTypes[i]).Type; + return this._type.GetMethod(name, types); + } + + public override ConstructorInfo GetConstructor(ITypeInfo[] paramTypes) + { + Type[] types = new Type[paramTypes.Length]; + for (int i = 0; i < paramTypes.Length; i++) + types[i] = ((AbstractTypeInfo)paramTypes[i]).Type; + var constructor = this._type.GetConstructor(types); + return constructor; + } + + public override PropertyInfo GetProperty(string name) + { + return this._type.GetProperty(name); + } + + public override bool IsAssignableFrom(ITypeInfo typeInfo) + { + return this._type.IsAssignableFrom(((AbstractTypeInfo)typeInfo).Type); + } + + public override bool ContainsGenericParameters + { + get { return this._type.ContainsGenericParameters; } + } + + public override bool IsGenericTypeDefinition + { + get { return this._type.IsGenericTypeDefinition; } + } + + public override bool IsGenericType + { + get { return this._type.IsGenericType; } + } + + public override Type GetGenericTypeDefinition() + { + return this._type.GetGenericTypeDefinition(); + } + + public override Type[] GetGenericArguments() + { + return this._type.GetGenericArguments(); + } + + public override object[] GetCustomAttributes(bool inherit) + { + return this._type.GetCustomAttributes(inherit); + } + + public override object[] GetCustomAttributes(ITypeInfo attributeType, bool inherit) + { + return this._type.GetCustomAttributes(((TypeInfoWrapper)attributeType)._type, inherit); + } + + public override Assembly Assembly + { + get { return this._type.Assembly; } + } + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/IJsonWrapper.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/IJsonWrapper.cs new file mode 100644 index 000000000..e8f84983c --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/IJsonWrapper.cs @@ -0,0 +1,68 @@ +#pragma warning disable 1587 +#region Header +/// +/// IJsonWrapper.cs +/// Interface that represents a type capable of handling all kinds of JSON +/// data. This is mainly used when mapping objects through JsonMapper, and +/// it's implemented by JsonData. +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +using System.Collections; +using System.Collections.Specialized; + +namespace ThirdParty.Json.LitJson +{ + internal enum JsonType + { + None, + + Object, + Array, + String, + Int, + UInt, + Long, + ULong, + Double, + Boolean + } + + internal interface IJsonWrapper : IList, IOrderedDictionary + { + bool IsArray { get; } + bool IsBoolean { get; } + bool IsDouble { get; } + bool IsInt { get; } + bool IsUInt { get; } + bool IsLong { get; } + bool IsULong { get; } + bool IsObject { get; } + bool IsString { get; } + + bool GetBoolean (); + double GetDouble (); + int GetInt (); + uint GetUInt(); + JsonType GetJsonType (); + long GetLong (); + ulong GetULong(); + string GetString (); + + void SetBoolean (bool val); + void SetDouble (double val); + void SetInt (int val); + void SetUInt (uint val); + void SetJsonType (JsonType type); + void SetLong (long val); + void SetULong (ulong val); + void SetString (string val); + + string ToJson (); + void ToJson (JsonWriter writer); + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonData.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonData.cs new file mode 100644 index 000000000..c277a1f7c --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonData.cs @@ -0,0 +1,1143 @@ +#pragma warning disable 1587 +#region Header +/// +/// JsonData.cs +/// Generic type to hold JSON data (objects, arrays, and so on). This is +/// the default type returned by JsonMapper.ToObject(). +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; + +namespace ThirdParty.Json.LitJson +{ + internal class JsonData : IJsonWrapper, IEquatable + { + #region Fields + private IList inst_array; + private bool inst_boolean; + private double inst_double; + + // number datatype that holds int, uint, long, and ulong values. + // type field keeps track of the actual type of the value + // and casted before returning the value to the client code. + private ulong inst_number; + + private IDictionary inst_object; + private string inst_string; + private string json; + private JsonType type; + + // Used to implement the IOrderedDictionary interface + private IList> object_list; + #endregion + + + #region Properties + public int Count { + get { return EnsureCollection ().Count; } + } + + public bool IsArray { + get { return type == JsonType.Array; } + } + + public bool IsBoolean { + get { return type == JsonType.Boolean; } + } + + public bool IsDouble { + get { return type == JsonType.Double; } + } + + public bool IsInt { + get { return type == JsonType.Int; } + } + + public bool IsUInt { + get { return type == JsonType.UInt; } + } + + public bool IsLong { + get { return type == JsonType.Long; } + } + + public bool IsULong { + get { return type == JsonType.ULong; } + } + + public bool IsObject { + get { return type == JsonType.Object; } + } + + public bool IsString { + get { return type == JsonType.String; } + } + #endregion + + + #region ICollection Properties + int ICollection.Count { + get { + return Count; + } + } + + bool ICollection.IsSynchronized { + get { + return EnsureCollection ().IsSynchronized; + } + } + + object ICollection.SyncRoot { + get { + return EnsureCollection ().SyncRoot; + } + } + #endregion + + + #region IDictionary Properties + bool IDictionary.IsFixedSize { + get { + return EnsureDictionary ().IsFixedSize; + } + } + + bool IDictionary.IsReadOnly { + get { + return EnsureDictionary ().IsReadOnly; + } + } + + ICollection IDictionary.Keys { + get { + EnsureDictionary (); + IList keys = new List (); + + foreach (KeyValuePair entry in + object_list) { + keys.Add (entry.Key); + } + + return (ICollection) keys; + } + } + + ICollection IDictionary.Values { + get { + EnsureDictionary (); + IList values = new List (); + + foreach (KeyValuePair entry in + object_list) { + values.Add (entry.Value); + } + + return (ICollection) values; + } + } + #endregion + + + + #region IJsonWrapper Properties + bool IJsonWrapper.IsArray { + get { return IsArray; } + } + + bool IJsonWrapper.IsBoolean { + get { return IsBoolean; } + } + + bool IJsonWrapper.IsDouble { + get { return IsDouble; } + } + + bool IJsonWrapper.IsInt { + get { return IsInt; } + } + + bool IJsonWrapper.IsLong { + get { return IsLong; } + } + + bool IJsonWrapper.IsObject { + get { return IsObject; } + } + + bool IJsonWrapper.IsString { + get { return IsString; } + } + #endregion + + + #region IList Properties + bool IList.IsFixedSize { + get { + return EnsureList ().IsFixedSize; + } + } + + bool IList.IsReadOnly { + get { + return EnsureList ().IsReadOnly; + } + } + #endregion + + + #region IDictionary Indexer + object IDictionary.this[object key] { + get { + return EnsureDictionary ()[key]; + } + + set { + if (! (key is String)) + throw new ArgumentException ( + "The key has to be a string"); + + JsonData data = ToJsonData (value); + + this[(string) key] = data; + } + } + #endregion + + + #region IOrderedDictionary Indexer + object IOrderedDictionary.this[int idx] { + get { + EnsureDictionary (); + return object_list[idx].Value; + } + + set { + EnsureDictionary (); + JsonData data = ToJsonData (value); + + KeyValuePair old_entry = object_list[idx]; + + inst_object[old_entry.Key] = data; + + KeyValuePair entry = + new KeyValuePair (old_entry.Key, data); + + object_list[idx] = entry; + } + } + #endregion + + + #region IList Indexer + object IList.this[int index] { + get { + return EnsureList ()[index]; + } + + set { + EnsureList (); + JsonData data = ToJsonData (value); + + this[index] = data; + } + } + #endregion + + + #region Public Indexers + + public IEnumerable PropertyNames + { + get + { + EnsureDictionary(); + return inst_object.Keys; + } + } + + public JsonData this[string prop_name] { + get { + EnsureDictionary (); + JsonData data = null; + inst_object.TryGetValue(prop_name, out data); + return data; + } + + set { + EnsureDictionary (); + + KeyValuePair entry = + new KeyValuePair (prop_name, value); + + if (inst_object.ContainsKey (prop_name)) { + for (int i = 0; i < object_list.Count; i++) { + if (object_list[i].Key == prop_name) { + object_list[i] = entry; + break; + } + } + } else + object_list.Add (entry); + + inst_object[prop_name] = value; + + json = null; + } + } + + public JsonData this[int index] { + get { + EnsureCollection (); + + if (type == JsonType.Array) + return inst_array[index]; + + return object_list[index].Value; + } + + set { + EnsureCollection (); + + if (type == JsonType.Array) + inst_array[index] = value; + else { + KeyValuePair entry = object_list[index]; + KeyValuePair new_entry = + new KeyValuePair (entry.Key, value); + + object_list[index] = new_entry; + inst_object[entry.Key] = value; + } + + json = null; + } + } + #endregion + + + #region Constructors + public JsonData () + { + } + + public JsonData (bool boolean) + { + type = JsonType.Boolean; + inst_boolean = boolean; + } + + public JsonData (double number) + { + type = JsonType.Double; + inst_double = number; + } + + public JsonData (int number) + { + type = JsonType.Int; + inst_number = (ulong)number; + } + + public JsonData(uint number) + { + type = JsonType.UInt; + inst_number = (ulong)number; + } + + public JsonData (long number) + { + type = JsonType.Long; + inst_number = (ulong)number; + } + + public JsonData(ulong number) + { + type = JsonType.ULong; + inst_number = number; + } + + public JsonData (object obj) + { + if (obj is Boolean) { + type = JsonType.Boolean; + inst_boolean = (bool) obj; + return; + } + + if (obj is Double) { + type = JsonType.Double; + inst_double = (double) obj; + return; + } + + if (obj is Int32) { + type = JsonType.Int; + inst_number = (ulong)obj; + return; + } + + if (obj is UInt32) + { + type = JsonType.UInt; + inst_number = (ulong)obj; + return; + } + + if (obj is Int64) { + type = JsonType.Long; + inst_number = (ulong)obj; + return; + } + + if (obj is UInt64) + { + type = JsonType.ULong; + inst_number = (ulong)obj; + return; + } + + if (obj is String) { + type = JsonType.String; + inst_string = (string) obj; + return; + } + + throw new ArgumentException ( + "Unable to wrap the given object with JsonData"); + } + + public JsonData (string str) + { + type = JsonType.String; + inst_string = str; + } + #endregion + + + #region Implicit Conversions + public static implicit operator JsonData (Boolean data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (Double data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (Int32 data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (Int64 data) + { + return new JsonData (data); + } + + public static implicit operator JsonData (String data) + { + return new JsonData (data); + } + #endregion + + + #region Explicit Conversions + public static explicit operator Boolean (JsonData data) + { + if (data.type != JsonType.Boolean) + throw new InvalidCastException ( + "Instance of JsonData doesn't hold a double"); + + return data.inst_boolean; + } + + public static explicit operator Double (JsonData data) + { + if (data.type != JsonType.Double) + throw new InvalidCastException ( + "Instance of JsonData doesn't hold a double"); + + return data.inst_double; + } + + public static explicit operator Int32 (JsonData data) + { + if (data.type != JsonType.Int) + { + throw new InvalidCastException ( + "Instance of JsonData doesn't hold an int"); + } + + return unchecked((int)data.inst_number); + } + + public static explicit operator UInt32(JsonData data) + { + if (data.type != JsonType.UInt) + { + throw new InvalidCastException( + "Instance of JsonData doesn't hold an int"); + } + + return unchecked((uint)data.inst_number); + } + + public static explicit operator Int64 (JsonData data) + { + if (data.type != JsonType.Int && data.type != JsonType.Long) + { + throw new InvalidCastException ( + "Instance of JsonData doesn't hold an long"); + } + + return unchecked((long)data.inst_number); + } + + public static explicit operator UInt64(JsonData data) + { + if (data.type != JsonType.UInt && data.type != JsonType.ULong) + { + throw new InvalidCastException( + "Instance of JsonData doesn't hold an long"); + } + + return (ulong)data.inst_number; + } + + public static explicit operator String (JsonData data) + { + if (data.type != JsonType.String) + throw new InvalidCastException ( + "Instance of JsonData doesn't hold a string"); + + return data.inst_string; + } + #endregion + + + #region ICollection Methods + void ICollection.CopyTo (Array array, int index) + { + EnsureCollection ().CopyTo (array, index); + } + #endregion + + + #region IDictionary Methods + void IDictionary.Add (object key, object value) + { + JsonData data = ToJsonData (value); + + EnsureDictionary ().Add (key, data); + + KeyValuePair entry = + new KeyValuePair ((string) key, data); + object_list.Add (entry); + + json = null; + } + + void IDictionary.Clear () + { + EnsureDictionary ().Clear (); + object_list.Clear (); + json = null; + } + + bool IDictionary.Contains (object key) + { + return EnsureDictionary ().Contains (key); + } + + IDictionaryEnumerator IDictionary.GetEnumerator () + { + return ((IOrderedDictionary) this).GetEnumerator (); + } + + void IDictionary.Remove (object key) + { + EnsureDictionary ().Remove (key); + + for (int i = 0; i < object_list.Count; i++) { + if (object_list[i].Key == (string) key) { + object_list.RemoveAt (i); + break; + } + } + + json = null; + } + #endregion + + + #region IEnumerable Methods + IEnumerator IEnumerable.GetEnumerator () + { + return EnsureCollection ().GetEnumerator (); + } + #endregion + + + #region IJsonWrapper Methods + bool IJsonWrapper.GetBoolean () + { + if (type != JsonType.Boolean) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a boolean"); + + return inst_boolean; + } + + double IJsonWrapper.GetDouble () + { + if (type != JsonType.Double) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a double"); + + return inst_double; + } + + int IJsonWrapper.GetInt () + { + if (type != JsonType.Int) + throw new InvalidOperationException ( + "JsonData instance doesn't hold an int"); + + return unchecked((int)inst_number); + } + + uint IJsonWrapper.GetUInt() + { + if (type != JsonType.UInt) + throw new InvalidOperationException( + "JsonData instance doesn't hold an int"); + + return unchecked((uint)inst_number); + } + + long IJsonWrapper.GetLong () + { + if (type != JsonType.Long) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a long"); + + return unchecked((long)inst_number); + } + + ulong IJsonWrapper.GetULong() + { + if (type != JsonType.ULong) + throw new InvalidOperationException( + "JsonData instance doesn't hold a long"); + + return inst_number; + } + + string IJsonWrapper.GetString () + { + if (type != JsonType.String) + throw new InvalidOperationException ( + "JsonData instance doesn't hold a string"); + + return inst_string; + } + + void IJsonWrapper.SetBoolean (bool val) + { + type = JsonType.Boolean; + inst_boolean = val; + json = null; + } + + void IJsonWrapper.SetDouble (double val) + { + type = JsonType.Double; + inst_double = val; + json = null; + } + + void IJsonWrapper.SetInt (int val) + { + type = JsonType.Int; + inst_number = unchecked((ulong)val); + json = null; + } + + void IJsonWrapper.SetUInt(uint val) + { + type = JsonType.UInt; + inst_number = unchecked((ulong)val); + json = null; + } + + void IJsonWrapper.SetLong (long val) + { + type = JsonType.Long; + inst_number = unchecked((ulong)val); + json = null; + } + + void IJsonWrapper.SetULong(ulong val) + { + type = JsonType.ULong; + inst_number = val; + json = null; + } + + void IJsonWrapper.SetString (string val) + { + type = JsonType.String; + inst_string = val; + json = null; + } + + string IJsonWrapper.ToJson () + { + return ToJson (); + } + + void IJsonWrapper.ToJson (JsonWriter writer) + { + ToJson (writer); + } + #endregion + + + #region IList Methods + int IList.Add (object value) + { + return Add (value); + } + + void IList.Clear () + { + EnsureList ().Clear (); + json = null; + } + + bool IList.Contains (object value) + { + return EnsureList ().Contains (value); + } + + int IList.IndexOf (object value) + { + return EnsureList ().IndexOf (value); + } + + void IList.Insert (int index, object value) + { + EnsureList ().Insert (index, value); + json = null; + } + + void IList.Remove (object value) + { + EnsureList ().Remove (value); + json = null; + } + + void IList.RemoveAt (int index) + { + EnsureList ().RemoveAt (index); + json = null; + } + #endregion + + + #region IOrderedDictionary Methods + IDictionaryEnumerator IOrderedDictionary.GetEnumerator () + { + EnsureDictionary (); + + return new OrderedDictionaryEnumerator ( + object_list.GetEnumerator ()); + } + + void IOrderedDictionary.Insert (int idx, object key, object value) + { + string property = (string) key; + JsonData data = ToJsonData (value); + + this[property] = data; + + KeyValuePair entry = + new KeyValuePair (property, data); + + object_list.Insert (idx, entry); + } + + void IOrderedDictionary.RemoveAt (int idx) + { + EnsureDictionary (); + + inst_object.Remove (object_list[idx].Key); + object_list.RemoveAt (idx); + } + #endregion + + + #region Private Methods + private ICollection EnsureCollection () + { + if (type == JsonType.Array) + return (ICollection) inst_array; + + if (type == JsonType.Object) + return (ICollection) inst_object; + + throw new InvalidOperationException ( + "The JsonData instance has to be initialized first"); + } + + private IDictionary EnsureDictionary () + { + if (type == JsonType.Object) + return (IDictionary) inst_object; + + if (type != JsonType.None) + throw new InvalidOperationException ( + "Instance of JsonData is not a dictionary"); + + type = JsonType.Object; + inst_object = new Dictionary (); + object_list = new List> (); + + return (IDictionary) inst_object; + } + + private IList EnsureList () + { + if (type == JsonType.Array) + return (IList) inst_array; + + if (type != JsonType.None) + throw new InvalidOperationException ( + "Instance of JsonData is not a list"); + + type = JsonType.Array; + inst_array = new List (); + + return (IList) inst_array; + } + + private JsonData ToJsonData (object obj) + { + if (obj == null) + return null; + + if (obj is JsonData) + return (JsonData) obj; + + return new JsonData (obj); + } + + private static void WriteJson (IJsonWrapper obj, JsonWriter writer) + { + if (obj == null) { + writer.Write(null); + return; + } + + if (obj.IsString) { + writer.Write (obj.GetString ()); + return; + } + + if (obj.IsBoolean) { + writer.Write (obj.GetBoolean ()); + return; + } + + if (obj.IsDouble) { + writer.Write (obj.GetDouble ()); + return; + } + + if (obj.IsInt) { + writer.Write (obj.GetInt ()); + return; + } + + if (obj.IsUInt) + { + writer.Write(obj.GetUInt()); + return; + } + + if (obj.IsLong) { + writer.Write (obj.GetLong ()); + return; + } + + if (obj.IsULong) + { + writer.Write(obj.GetULong()); + return; + } + + if (obj.IsArray) { + writer.WriteArrayStart (); + foreach (object elem in (IList) obj) + WriteJson ((JsonData) elem, writer); + writer.WriteArrayEnd (); + + return; + } + + if (obj.IsObject) { + writer.WriteObjectStart (); + + foreach (DictionaryEntry entry in ((IDictionary) obj)) { + writer.WritePropertyName ((string) entry.Key); + WriteJson ((JsonData) entry.Value, writer); + } + writer.WriteObjectEnd (); + + return; + } + } + #endregion + + + public int Add (object value) + { + JsonData data = ToJsonData (value); + + json = null; + + return EnsureList ().Add (data); + } + + public void Clear () + { + if (IsObject) { + ((IDictionary) this).Clear (); + return; + } + + if (IsArray) { + ((IList) this).Clear (); + return; + } + } + + public bool Equals (JsonData x) + { + if (x == null) + return false; + + if (x.type != this.type) + { + bool thisIsSigned = (this.type == JsonType.Int || this.type == JsonType.Long); + bool thisIsUnsigned = (this.type == JsonType.UInt || this.type == JsonType.ULong); + bool xIsSigned = (x.type == JsonType.Int || x.type == JsonType.Long); + bool xIsUnsigned = (x.type == JsonType.UInt || x.type == JsonType.ULong); + + if (thisIsSigned == xIsSigned || thisIsUnsigned == xIsUnsigned) + { + // only allow types between signed numbers and between unsigned numbers to be actually compared + } + else + { + return false; + } + } + + switch (this.type) { + case JsonType.None: + return true; + + case JsonType.Object: + return this.inst_object.Equals (x.inst_object); + + case JsonType.Array: + return this.inst_array.Equals (x.inst_array); + + case JsonType.String: + return this.inst_string.Equals (x.inst_string); + + case JsonType.Int: + case JsonType.UInt: + case JsonType.Long: + case JsonType.ULong: + return this.inst_number.Equals (x.inst_number); + + case JsonType.Double: + return this.inst_double.Equals (x.inst_double); + + case JsonType.Boolean: + return this.inst_boolean.Equals (x.inst_boolean); + } + + return false; + } + + public JsonType GetJsonType () + { + return type; + } + + public void SetJsonType (JsonType type) + { + if (this.type == type) + return; + + switch (type) { + case JsonType.None: + break; + + case JsonType.Object: + inst_object = new Dictionary (); + object_list = new List> (); + break; + + case JsonType.Array: + inst_array = new List (); + break; + + case JsonType.String: + inst_string = default (String); + break; + + case JsonType.Int: + inst_number = default (Int32); + break; + + case JsonType.UInt: + inst_number = default(UInt32); + break; + + case JsonType.Long: + inst_number = default (Int64); + break; + + case JsonType.ULong: + inst_number = default(UInt64); + break; + + case JsonType.Double: + inst_double = default (Double); + break; + + case JsonType.Boolean: + inst_boolean = default (Boolean); + break; + } + + this.type = type; + } + + public string ToJson () + { + if (json != null) + return json; + + StringWriter sw = new StringWriter (); + JsonWriter writer = new JsonWriter (sw); + writer.Validate = false; + + WriteJson (this, writer); + json = sw.ToString (); + + return json; + } + + public void ToJson (JsonWriter writer) + { + bool old_validate = writer.Validate; + + writer.Validate = false; + + WriteJson (this, writer); + + writer.Validate = old_validate; + } + + public override string ToString () + { + switch (type) { + case JsonType.Array: + return "JsonData array"; + + case JsonType.Boolean: + return inst_boolean.ToString (); + + case JsonType.Double: + return inst_double.ToString (); + + case JsonType.Int: + return unchecked((int)inst_number).ToString(); + case JsonType.UInt: + return unchecked((uint)inst_number).ToString(); + case JsonType.Long: + return unchecked((long)inst_number).ToString(); + case JsonType.ULong: + return inst_number.ToString (); + + case JsonType.Object: + return "JsonData object"; + + case JsonType.String: + return inst_string; + } + + return "Uninitialized JsonData"; + } + } + + + internal class OrderedDictionaryEnumerator : IDictionaryEnumerator + { + IEnumerator> list_enumerator; + + + public object Current { + get { return Entry; } + } + + public DictionaryEntry Entry { + get { + KeyValuePair curr = list_enumerator.Current; + return new DictionaryEntry (curr.Key, curr.Value); + } + } + + public object Key { + get { return list_enumerator.Current.Key; } + } + + public object Value { + get { return list_enumerator.Current.Value; } + } + + + public OrderedDictionaryEnumerator ( + IEnumerator> enumerator) + { + list_enumerator = enumerator; + } + + + public bool MoveNext () + { + return list_enumerator.MoveNext (); + } + + public void Reset () + { + list_enumerator.Reset (); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonException.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonException.cs new file mode 100644 index 000000000..31c088c09 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonException.cs @@ -0,0 +1,61 @@ +#pragma warning disable 1587 +#region Header +/// +/// JsonException.cs +/// Base class throwed by LitJSON when a parsing error occurs. +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +using System; + + +namespace ThirdParty.Json.LitJson +{ + internal class JsonException : Exception + { + public JsonException () : base () + { + } + + internal JsonException (ParserToken token) : + base (String.Format ( + "Invalid token '{0}' in input string", token)) + { + } + + internal JsonException (ParserToken token, + Exception inner_exception) : + base (String.Format ( + "Invalid token '{0}' in input string", token), + inner_exception) + { + } + + internal JsonException (int c) : + base (String.Format ( + "Invalid character '{0}' in input string", (char) c)) + { + } + + internal JsonException (int c, Exception inner_exception) : + base (String.Format ( + "Invalid character '{0}' in input string", (char) c), + inner_exception) + { + } + + + public JsonException (string message) : base (message) + { + } + + public JsonException (string message, Exception inner_exception) : + base (message, inner_exception) + { + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonMapper.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonMapper.cs new file mode 100644 index 000000000..af3e9eda6 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonMapper.cs @@ -0,0 +1,988 @@ +#pragma warning disable 1587 +#region Header +/// +/// JsonMapper.cs +/// JSON to .Net object and object to JSON conversions. +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Reflection; + +using Amazon.Util; +using Amazon.Util.Internal; + +namespace ThirdParty.Json.LitJson +{ + internal struct PropertyMetadata + { + public MemberInfo Info; + public bool IsField; + public Type Type; + } + + + internal struct ArrayMetadata + { + private Type element_type; + private bool is_array; + private bool is_list; + + + public Type ElementType { + get { + if (element_type == null) + return typeof (JsonData); + + return element_type; + } + + set { element_type = value; } + } + + public bool IsArray { + get { return is_array; } + set { is_array = value; } + } + + public bool IsList { + get { return is_list; } + set { is_list = value; } + } + } + + + internal struct ObjectMetadata + { + private Type element_type; + private bool is_dictionary; + + private IDictionary properties; + + + public Type ElementType { + get { + if (element_type == null) + return typeof (JsonData); + + return element_type; + } + + set { element_type = value; } + } + + public bool IsDictionary { + get { return is_dictionary; } + set { is_dictionary = value; } + } + + public IDictionary Properties { + get { return properties; } + set { properties = value; } + } + } + + + internal delegate void ExporterFunc (object obj, JsonWriter writer); + internal delegate void ExporterFunc (T obj, JsonWriter writer); + + internal delegate object ImporterFunc (object input); + internal delegate TValue ImporterFunc (TJson input); + + internal delegate IJsonWrapper WrapperFactory (); + + + internal class JsonMapper + { + #region Fields + private static int max_nesting_depth; + + private static IFormatProvider datetime_format; + + private static IDictionary base_exporters_table; + private static IDictionary custom_exporters_table; + + private static IDictionary> base_importers_table; + private static IDictionary> custom_importers_table; + + private static IDictionary array_metadata; + private static readonly object array_metadata_lock = new Object (); + + private static IDictionary> conv_ops; + private static readonly object conv_ops_lock = new Object (); + + private static IDictionary object_metadata; + private static readonly object object_metadata_lock = new Object (); + + private static IDictionary> type_properties; + private static readonly object type_properties_lock = new Object (); + + private static JsonWriter static_writer; + private static readonly object static_writer_lock = new Object (); + + private static readonly HashSet dictionary_properties_to_ignore = new HashSet(StringComparer.Ordinal) + { + "Comparer", "Count", "Keys", "Values" + }; + #endregion + + + #region Constructors + static JsonMapper () + { + max_nesting_depth = 100; + + array_metadata = new Dictionary (); + conv_ops = new Dictionary> (); + object_metadata = new Dictionary (); + type_properties = new Dictionary> (); + + static_writer = new JsonWriter (); + + datetime_format = DateTimeFormatInfo.InvariantInfo; + + base_exporters_table = new Dictionary (); + custom_exporters_table = new Dictionary (); + + base_importers_table = new Dictionary> (); + custom_importers_table = new Dictionary> (); + + RegisterBaseExporters (); + RegisterBaseImporters (); + } + #endregion + + + #region Private Methods + private static void AddArrayMetadata (Type type) + { + if (array_metadata.ContainsKey (type)) + return; + + ArrayMetadata data = new ArrayMetadata (); + + data.IsArray = type.IsArray; + + var typeInfo = TypeFactory.GetTypeInfo(type); + if (typeInfo.GetInterface("System.Collections.IList") != null) + data.IsList = true; + + foreach (PropertyInfo p_info in typeInfo.GetProperties()) + { + if (p_info.Name != "Item") + continue; + + ParameterInfo[] parameters = p_info.GetIndexParameters (); + + if (parameters.Length != 1) + continue; + + if (parameters[0].ParameterType == typeof (int)) + data.ElementType = p_info.PropertyType; + } + + lock (array_metadata_lock) { + try { + array_metadata.Add (type, data); + } catch (ArgumentException) { + return; + } + } + } + + private static void AddObjectMetadata (Type type) + { + if (object_metadata.ContainsKey (type)) + return; + + ObjectMetadata data = new ObjectMetadata (); + + var typeInfo = TypeFactory.GetTypeInfo(type); + if (typeInfo.GetInterface("System.Collections.IDictionary") != null) + data.IsDictionary = true; + + data.Properties = new Dictionary (); + + foreach (PropertyInfo p_info in typeInfo.GetProperties()) + { + if (p_info.Name == "Item") { + ParameterInfo[] parameters = p_info.GetIndexParameters (); + + if (parameters.Length != 1) + continue; + + if (parameters[0].ParameterType == typeof (string)) + data.ElementType = p_info.PropertyType; + + continue; + } + + if (data.IsDictionary && dictionary_properties_to_ignore.Contains(p_info.Name)) + continue; + + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = p_info; + p_data.Type = p_info.PropertyType; + + data.Properties.Add (p_info.Name, p_data); + } + + foreach (FieldInfo f_info in typeInfo.GetFields()) + { + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = f_info; + p_data.IsField = true; + p_data.Type = f_info.FieldType; + + data.Properties.Add (f_info.Name, p_data); + } + + lock (object_metadata_lock) { + try { + object_metadata.Add (type, data); + } catch (ArgumentException) { + return; + } + } + } + + private static void AddTypeProperties (Type type) + { + if (type_properties.ContainsKey (type)) + return; + var typeInfo = TypeFactory.GetTypeInfo(type); + IList props = new List (); + + foreach (PropertyInfo p_info in typeInfo.GetProperties()) + { + if (p_info.Name == "Item") + continue; + + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = p_info; + p_data.IsField = false; + props.Add (p_data); + } + + foreach (FieldInfo f_info in typeInfo.GetFields()) + { + PropertyMetadata p_data = new PropertyMetadata (); + p_data.Info = f_info; + p_data.IsField = true; + + props.Add (p_data); + } + + lock (type_properties_lock) { + try { + type_properties.Add (type, props); + } catch (ArgumentException) { + return; + } + } + } + + private static MethodInfo GetConvOp (Type t1, Type t2) + { + lock (conv_ops_lock) { + if (! conv_ops.ContainsKey (t1)) + conv_ops.Add (t1, new Dictionary ()); + } + + var typeInfoT1 = TypeFactory.GetTypeInfo(t1); + var typeInfoT2 = TypeFactory.GetTypeInfo(t2); + + if (conv_ops[t1].ContainsKey (t2)) + return conv_ops[t1][t2]; + + MethodInfo op = typeInfoT1.GetMethod( + "op_Implicit", new ITypeInfo[] { typeInfoT2 }); + + lock (conv_ops_lock) { + try { + conv_ops[t1].Add (t2, op); + } catch (ArgumentException) { + return conv_ops[t1][t2]; + } + } + + return op; + } + + private static object ReadValue (Type inst_type, JsonReader reader) + { + reader.Read (); + var inst_typeInfo = TypeFactory.GetTypeInfo(inst_type); + + if (reader.Token == JsonToken.ArrayEnd) + return null; + + //support for nullable types + Type underlying_type = Nullable.GetUnderlyingType(inst_type); + Type value_type = underlying_type ?? inst_type; + + if (reader.Token == JsonToken.Null) { + + if (inst_typeInfo.IsClass || underlying_type != null) + { + return null; + } + + throw new JsonException (String.Format ( + "Can't assign null to an instance of type {0}", + inst_type)); + } + + if (reader.Token == JsonToken.Double || + reader.Token == JsonToken.Int || + reader.Token == JsonToken.UInt || + reader.Token == JsonToken.Long || + reader.Token == JsonToken.ULong || + reader.Token == JsonToken.String || + reader.Token == JsonToken.Boolean) { + + Type json_type = reader.Value.GetType (); + var json_typeInfo = TypeFactory.GetTypeInfo(json_type); + if (inst_typeInfo.IsAssignableFrom(json_typeInfo)) + return reader.Value; + + // If there's a custom importer that fits, use it + if (custom_importers_table.ContainsKey (json_type) && + custom_importers_table[json_type].ContainsKey ( + value_type)) { + + ImporterFunc importer = + custom_importers_table[json_type][value_type]; + + return importer (reader.Value); + } + + // Maybe there's a base importer that works + if (base_importers_table.ContainsKey (json_type) && + base_importers_table[json_type].ContainsKey ( + value_type)) { + + ImporterFunc importer = + base_importers_table[json_type][value_type]; + + return importer (reader.Value); + } + + // Maybe it's an enum + if (inst_typeInfo.IsEnum) + return Enum.ToObject (value_type, reader.Value); + + // Try using an implicit conversion operator + MethodInfo conv_op = GetConvOp (value_type, json_type); + + if (conv_op != null) + return conv_op.Invoke (null, + new object[] { reader.Value }); + + // No luck + throw new JsonException (String.Format ( + "Can't assign value '{0}' (type {1}) to type {2}", + reader.Value, json_type, inst_type)); + } + + object instance = null; + + if (reader.Token == JsonToken.ArrayStart) { + + AddArrayMetadata (inst_type); + ArrayMetadata t_data = array_metadata[inst_type]; + + if (! t_data.IsArray && ! t_data.IsList) + throw new JsonException (String.Format ( + "Type {0} can't act as an array", + inst_type)); + + IList list; + Type elem_type; + + if (! t_data.IsArray) { + list = (IList) Activator.CreateInstance (inst_type); + elem_type = t_data.ElementType; + } else { + list = new List (); + elem_type = inst_type.GetElementType (); + } + + while (true) { + object item = ReadValue (elem_type, reader); + if (reader.Token == JsonToken.ArrayEnd) + break; + + list.Add (item); + } + + if (t_data.IsArray) { + int n = list.Count; + instance = Array.CreateInstance (elem_type, n); + + for (int i = 0; i < n; i++) + ((Array) instance).SetValue (list[i], i); + } else + instance = list; + + } else if (reader.Token == JsonToken.ObjectStart) { + + AddObjectMetadata (value_type); + ObjectMetadata t_data = object_metadata[value_type]; + + instance = Activator.CreateInstance (value_type); + + while (true) { + reader.Read (); + + if (reader.Token == JsonToken.ObjectEnd) + break; + + string property = (string) reader.Value; + + if (t_data.Properties.ContainsKey (property)) { + PropertyMetadata prop_data = + t_data.Properties[property]; + + if (prop_data.IsField) { + ((FieldInfo) prop_data.Info).SetValue ( + instance, ReadValue (prop_data.Type, reader)); + } else { + PropertyInfo p_info = + (PropertyInfo) prop_data.Info; + + if (p_info.CanWrite) + p_info.SetValue ( + instance, + ReadValue (prop_data.Type, reader), + null); + else + ReadValue (prop_data.Type, reader); + } + + } else { + if (! t_data.IsDictionary) + throw new JsonException (String.Format ( + "The type {0} doesn't have the " + + "property '{1}'", inst_type, property)); + + ((IDictionary) instance).Add ( + property, ReadValue ( + t_data.ElementType, reader)); + } + + } + + } + + return instance; + } + + private static IJsonWrapper ReadValue (WrapperFactory factory, + JsonReader reader) + { + reader.Read (); + + if (reader.Token == JsonToken.ArrayEnd || + reader.Token == JsonToken.Null) + return null; + + IJsonWrapper instance = factory (); + + if (reader.Token == JsonToken.String) { + instance.SetString ((string) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Double) { + instance.SetDouble ((double) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Int) { + instance.SetInt ((int) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.UInt) { + instance.SetUInt((uint)reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Long) { + instance.SetLong ((long) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.ULong) { + instance.SetULong((ulong)reader.Value); + return instance; + } + + if (reader.Token == JsonToken.Boolean) { + instance.SetBoolean ((bool) reader.Value); + return instance; + } + + if (reader.Token == JsonToken.ArrayStart) { + instance.SetJsonType (JsonType.Array); + + while (true) { + IJsonWrapper item = ReadValue (factory, reader); + // nij - added check to see if the item is not null. This is to handle arrays within arrays. + // In those cases when the outer array read the inner array an item was returned back the current + // reader.Token is at the ArrayEnd for the inner array. + if (item == null && reader.Token == JsonToken.ArrayEnd) + break; + + ((IList) instance).Add (item); + } + } + else if (reader.Token == JsonToken.ObjectStart) { + instance.SetJsonType (JsonType.Object); + + while (true) { + reader.Read (); + + if (reader.Token == JsonToken.ObjectEnd) + break; + + string property = (string) reader.Value; + + ((IDictionary) instance)[property] = ReadValue ( + factory, reader); + } + + } + + return instance; + } + + private static void RegisterBaseExporters () + { + base_exporters_table[typeof (byte)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((byte) obj)); + }; + + base_exporters_table[typeof (char)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToString ((char) obj)); + }; + + base_exporters_table[typeof (DateTime)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToString ((DateTime) obj, + datetime_format)); + }; + + base_exporters_table[typeof (decimal)] = + delegate (object obj, JsonWriter writer) { + writer.Write ((decimal) obj); + }; + + base_exporters_table[typeof (sbyte)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((sbyte) obj)); + }; + + base_exporters_table[typeof (short)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((short) obj)); + }; + + base_exporters_table[typeof (ushort)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToInt32 ((ushort) obj)); + }; + + base_exporters_table[typeof (uint)] = + delegate (object obj, JsonWriter writer) { + writer.Write (Convert.ToUInt64 ((uint) obj)); + }; + + base_exporters_table[typeof (ulong)] = + delegate (object obj, JsonWriter writer) { + writer.Write ((ulong) obj); + }; + + base_exporters_table[typeof(float)] = + delegate (object obj,JsonWriter writer){ + writer.Write(Convert.ToDouble((float) obj)); + }; + + base_exporters_table[typeof(Int64)] = + delegate (object obj,JsonWriter writer){ + writer.Write(Convert.ToDouble((Int64) obj)); + }; + } + + private static void RegisterBaseImporters () + { + ImporterFunc importer; + + importer = delegate (object input) { + return Convert.ToByte ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (byte), importer); + + importer = delegate (object input) { + return Convert.ToUInt64 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (ulong), importer); + + importer = delegate (object input) { + return Convert.ToSByte ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (sbyte), importer); + + importer = delegate (object input) { + return Convert.ToInt16 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (short), importer); + + importer = delegate (object input) { + return Convert.ToUInt16 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (ushort), importer); + + importer = delegate (object input) { + return Convert.ToUInt32 ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (uint), importer); + + importer = delegate (object input) { + return Convert.ToSingle ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (float), importer); + + importer = delegate (object input) { + return Convert.ToDouble ((int) input); + }; + RegisterImporter (base_importers_table, typeof (int), + typeof (double), importer); + + importer = delegate (object input) { + return Convert.ToDecimal ((double) input); + }; + RegisterImporter (base_importers_table, typeof (double), + typeof (decimal), importer); + + importer = delegate(object input) + { + return Convert.ToSingle((float)(double)input); + }; + RegisterImporter(base_importers_table,typeof(double), + typeof(float),importer); + + importer = delegate (object input) { + return Convert.ToUInt32 ((long) input); + }; + RegisterImporter (base_importers_table, typeof (long), + typeof (uint), importer); + + importer = delegate (object input) { + return Convert.ToChar ((string) input); + }; + RegisterImporter (base_importers_table, typeof (string), + typeof (char), importer); + + importer = delegate (object input) { + return Convert.ToDateTime ((string) input, datetime_format); + }; + RegisterImporter (base_importers_table, typeof (string), + typeof (DateTime), importer); + + importer = delegate(object input) { + return Convert.ToInt64 ((Int32)input); + }; + RegisterImporter (base_importers_table, typeof (Int32), + typeof(Int64), importer); + } + + private static void RegisterImporter ( + IDictionary> table, + Type json_type, Type value_type, ImporterFunc importer) + { + if (! table.ContainsKey (json_type)) + table.Add (json_type, new Dictionary ()); + + table[json_type][value_type] = importer; + } + + private static void WriteValue (object obj, JsonWriter writer, + bool writer_is_private, + int depth) + { + if (depth > max_nesting_depth) + throw new JsonException ( + String.Format ("Max allowed object depth reached while " + + "trying to export from type {0}", + obj.GetType ())); + + if (obj == null) { + writer.Write (null); + return; + } + + if (obj is IJsonWrapper) { + if (writer_is_private) + writer.TextWriter.Write (((IJsonWrapper) obj).ToJson ()); + else + ((IJsonWrapper) obj).ToJson (writer); + + return; + } + + if (obj is String) { + writer.Write ((string) obj); + return; + } + + if (obj is Double) { + writer.Write ((double) obj); + return; + } + + if (obj is Int32) { + writer.Write ((int) obj); + return; + } + + if (obj is UInt32) + { + writer.Write((uint)obj); + return; + } + + if (obj is Boolean) { + writer.Write ((bool) obj); + return; + } + + if (obj is Int64) { + writer.Write ((long) obj); + return; + } + + if (obj is UInt64) + { + writer.Write((ulong)obj); + } + + if (obj is Array) { + writer.WriteArrayStart (); + + foreach (object elem in (Array) obj) + WriteValue (elem, writer, writer_is_private, depth + 1); + + writer.WriteArrayEnd (); + + return; + } + + if (obj is IList) { + writer.WriteArrayStart (); + foreach (object elem in (IList) obj) + WriteValue (elem, writer, writer_is_private, depth + 1); + writer.WriteArrayEnd (); + + return; + } + + if (obj is IDictionary) { + writer.WriteObjectStart (); + foreach (DictionaryEntry entry in (IDictionary) obj) { + writer.WritePropertyName ((string) entry.Key); + WriteValue (entry.Value, writer, writer_is_private, + depth + 1); + } + writer.WriteObjectEnd (); + + return; + } + + Type obj_type = obj.GetType (); + + // See if there's a custom exporter for the object + if (custom_exporters_table.ContainsKey (obj_type)) { + ExporterFunc exporter = custom_exporters_table[obj_type]; + exporter (obj, writer); + + return; + } + + // If not, maybe there's a base exporter + if (base_exporters_table.ContainsKey (obj_type)) { + ExporterFunc exporter = base_exporters_table[obj_type]; + exporter (obj, writer); + + return; + } + + // Last option, let's see if it's an enum + if (obj is Enum) { + Type e_type = Enum.GetUnderlyingType (obj_type); + + if (e_type == typeof (long) + || e_type == typeof (uint) + || e_type == typeof (ulong)) + writer.Write ((ulong) obj); + else + writer.Write ((int) obj); + + return; + } + + // Okay, so it looks like the input should be exported as an + // object + AddTypeProperties (obj_type); + IList props = type_properties[obj_type]; + + writer.WriteObjectStart (); + foreach (PropertyMetadata p_data in props) { + if (p_data.IsField) { + writer.WritePropertyName (p_data.Info.Name); + WriteValue (((FieldInfo) p_data.Info).GetValue (obj), + writer, writer_is_private, depth + 1); + } + else { + PropertyInfo p_info = (PropertyInfo) p_data.Info; + + if (p_info.CanRead) + { + writer.WritePropertyName(p_data.Info.Name); + WriteValue(p_info.GetGetMethod().Invoke(obj, null), + writer, writer_is_private, depth + 1); + } + } + } + writer.WriteObjectEnd (); + } + #endregion + + + public static string ToJson (object obj) + { + lock (static_writer_lock) { + static_writer.Reset (); + + WriteValue (obj, static_writer, true, 0); + + return static_writer.ToString (); + } + } + + public static void ToJson (object obj, JsonWriter writer) + { + WriteValue (obj, writer, false, 0); + } + + public static JsonData ToObject (JsonReader reader) + { + return (JsonData) ToWrapper ( + delegate { return new JsonData (); }, reader); + } + + public static JsonData ToObject (TextReader reader) + { + JsonReader json_reader = new JsonReader (reader); + + return (JsonData) ToWrapper ( + delegate { return new JsonData (); }, json_reader); + } + + public static JsonData ToObject (string json) + { + return (JsonData) ToWrapper ( + delegate { return new JsonData (); }, json); + } + + public static T ToObject (JsonReader reader) + { + return (T) ReadValue (typeof (T), reader); + } + + public static T ToObject (TextReader reader) + { + JsonReader json_reader = new JsonReader (reader); + + return (T) ReadValue (typeof (T), json_reader); + } + + public static T ToObject (string json) + { + JsonReader reader = new JsonReader (json); + + return (T) ReadValue (typeof (T), reader); + } + + public static IJsonWrapper ToWrapper (WrapperFactory factory, + JsonReader reader) + { + return ReadValue (factory, reader); + } + + public static IJsonWrapper ToWrapper (WrapperFactory factory, + string json) + { + JsonReader reader = new JsonReader (json); + + return ReadValue (factory, reader); + } + + public static void RegisterExporter (ExporterFunc exporter) + { + ExporterFunc exporter_wrapper = + delegate (object obj, JsonWriter writer) { + exporter ((T) obj, writer); + }; + + custom_exporters_table[typeof (T)] = exporter_wrapper; + } + + public static void RegisterImporter ( + ImporterFunc importer) + { + ImporterFunc importer_wrapper = + delegate (object input) { + return importer ((TJson) input); + }; + + RegisterImporter (custom_importers_table, typeof (TJson), + typeof (TValue), importer_wrapper); + } + + public static void UnregisterExporters () + { + custom_exporters_table.Clear (); + } + + public static void UnregisterImporters () + { + custom_importers_table.Clear (); + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonReader.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonReader.cs new file mode 100644 index 000000000..da7e87c34 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonReader.cs @@ -0,0 +1,388 @@ +#pragma warning disable 1587 +#region Header +/// +/// JsonReader.cs +/// Stream-like access to JSON text. +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + + +namespace ThirdParty.Json.LitJson +{ + internal enum JsonToken + { + None, + + ObjectStart, + PropertyName, + ObjectEnd, + + ArrayStart, + ArrayEnd, + + Int, + UInt, + Long, + ULong, + Double, + + String, + + Boolean, + Null + } + + + internal class JsonReader + { + #region Fields + + private Stack depth = new Stack(); + private int current_input; + private int current_symbol; + private bool end_of_json; + private bool end_of_input; + private Lexer lexer; + private bool parser_in_string; + private bool parser_return; + private bool read_started; + private TextReader reader; + private bool reader_is_owned; + private object token_value; + private JsonToken token; + #endregion + + + #region Public Properties + public bool AllowComments { + get { return lexer.AllowComments; } + set { lexer.AllowComments = value; } + } + + public bool AllowSingleQuotedStrings { + get { return lexer.AllowSingleQuotedStrings; } + set { lexer.AllowSingleQuotedStrings = value; } + } + + public bool EndOfInput { + get { return end_of_input; } + } + + public bool EndOfJson { + get { return end_of_json; } + } + + public JsonToken Token { + get { return token; } + } + + public object Value { + get { return token_value; } + } + #endregion + + + #region Constructors + + public JsonReader (string json_text) : + this (new StringReader (json_text), true) + { + } + + public JsonReader (TextReader reader) : + this (reader, false) + { + } + + private JsonReader (TextReader reader, bool owned) + { + if (reader == null) + throw new ArgumentNullException ("reader"); + + parser_in_string = false; + parser_return = false; + + read_started = false; + + lexer = new Lexer (reader); + + end_of_input = false; + end_of_json = false; + + this.reader = reader; + reader_is_owned = owned; + } + #endregion + + + + #region Private Methods + private void ProcessNumber (string number) + { + if (number.IndexOf ('.') != -1 || + number.IndexOf ('e') != -1 || + number.IndexOf ('E') != -1) { + + double n_double; + if (Double.TryParse(number, NumberStyles.Any, CultureInfo.InvariantCulture, out n_double)) + { + token = JsonToken.Double; + token_value = n_double; + + return; + } + } + + int n_int32; + if (Int32.TryParse(number, NumberStyles.Any, CultureInfo.InvariantCulture, out n_int32)) + { + token = JsonToken.Int; + token_value = n_int32; + + return; + } + + uint n_uint32; + if (UInt32.TryParse(number, NumberStyles.Any, CultureInfo.InvariantCulture, out n_uint32)) + { + token = JsonToken.UInt; + token_value = n_uint32; + + return; + } + + long n_int64; + if (Int64.TryParse(number, NumberStyles.Any, CultureInfo.InvariantCulture, out n_int64)) + { + token = JsonToken.Long; + token_value = n_int64; + + return; + } + + ulong n_uint64; + if (UInt64.TryParse(number, NumberStyles.Any, CultureInfo.InvariantCulture, out n_uint64)) + { + token = JsonToken.ULong; + token_value = n_uint64; + + return; + } + + // Shouldn't happen, but just in case, return something + token = JsonToken.ULong; + token_value = default(ulong); + } + + private void ProcessSymbol () + { + if (current_symbol == '[') { + token = JsonToken.ArrayStart; + parser_return = true; + + } else if (current_symbol == ']') { + token = JsonToken.ArrayEnd; + parser_return = true; + + } else if (current_symbol == '{') { + token = JsonToken.ObjectStart; + parser_return = true; + + } else if (current_symbol == '}') { + token = JsonToken.ObjectEnd; + parser_return = true; + + } else if (current_symbol == '"') { + if (parser_in_string) { + parser_in_string = false; + + parser_return = true; + + } else { + if (token == JsonToken.None) + token = JsonToken.String; + + parser_in_string = true; + } + + } else if (current_symbol == (int) ParserToken.CharSeq) { + token_value = lexer.StringValue; + + } else if (current_symbol == (int) ParserToken.False) { + token = JsonToken.Boolean; + token_value = false; + parser_return = true; + + } else if (current_symbol == (int) ParserToken.Null) { + token = JsonToken.Null; + parser_return = true; + + } else if (current_symbol == (int) ParserToken.Number) { + ProcessNumber (lexer.StringValue); + + parser_return = true; + + } else if (current_symbol == (int) ParserToken.Pair) { + token = JsonToken.PropertyName; + + } else if (current_symbol == (int) ParserToken.True) { + token = JsonToken.Boolean; + token_value = true; + parser_return = true; + + } + } + + private bool ReadToken () + { + if (end_of_input) + return false; + + lexer.NextToken (); + + if (lexer.EndOfInput) { + Close (); + + return false; + } + + current_input = lexer.Token; + + return true; + } + #endregion + + + public void Close () + { + if (end_of_input) + return; + + end_of_input = true; + end_of_json = true; + + reader = null; + } + + public bool Read() + { + if (end_of_input) + return false; + + if (end_of_json) + { + end_of_json = false; + } + + token = JsonToken.None; + parser_in_string = false; + parser_return = false; + + // Check if the first read call. If so then do an extra ReadToken because Read assumes that the previous + // call to Read has already called ReadToken. + if (!read_started) + { + read_started = true; + + if (!ReadToken()) + return false; + } + + do + { + current_symbol = current_input; + ProcessSymbol(); + if (parser_return) + { + if (this.token == JsonToken.ObjectStart || this.token == JsonToken.ArrayStart) + { + depth.Push(this.token); + } + else if (this.token == JsonToken.ObjectEnd || this.token == JsonToken.ArrayEnd) + { + // Clear out property name if is on top. This could happen if the value for the property was null. + if (depth.Peek() == JsonToken.PropertyName) + depth.Pop(); + + // Pop the opening token for this closing token. Make sure it is of the right type otherwise + // the document is invalid. + var opening = depth.Pop(); + if (this.token == JsonToken.ObjectEnd && opening != JsonToken.ObjectStart) + throw new JsonException("Error: Current token is ObjectEnd which does not match the opening " + opening.ToString()); + if (this.token == JsonToken.ArrayEnd && opening != JsonToken.ArrayStart) + throw new JsonException("Error: Current token is ArrayEnd which does not match the opening " + opening.ToString()); + + // If that last element is popped then we reached the end of the JSON object. + if (depth.Count == 0) + { + end_of_json = true; + } + } + // If the top of the stack is an object start and the next read is a string then it must be a property name + // to add to the stack. + else if (depth.Count > 0 && depth.Peek() != JsonToken.PropertyName && + this.token == JsonToken.String && depth.Peek() == JsonToken.ObjectStart) + { + this.token = JsonToken.PropertyName; + depth.Push(this.token); + } + + if ( + (this.token == JsonToken.ObjectEnd || + this.token == JsonToken.ArrayEnd || + this.token == JsonToken.String || + this.token == JsonToken.Boolean || + this.token == JsonToken.Double || + this.token == JsonToken.Int || + this.token == JsonToken.UInt || + this.token == JsonToken.Long || + this.token == JsonToken.ULong || + this.token == JsonToken.Null || + this.token == JsonToken.String + )) + { + // If we found a value but we are not in an array or object then the document is an invalid json document. + if (depth.Count == 0) + { + if (this.token != JsonToken.ArrayEnd && this.token != JsonToken.ObjectEnd) + { + throw new JsonException("Value without enclosing object or array"); + } + } + // The valud of the property has been processed so pop the property name from the stack. + else if (depth.Peek() == JsonToken.PropertyName) + { + depth.Pop(); + } + } + + // Read the next token that will be processed the next time the Read method is called. + // This is done ahead of the next read so we can detect if we are at the end of the json document. + // Otherwise EndOfInput would not return true until an attempt to read was made. + if (!ReadToken() && depth.Count != 0) + throw new JsonException("Incomplete JSON Document"); + return true; + } + } while (ReadToken()); + + // If we reached the end of the document but there is still elements left in the depth stack then + // the document is invalid JSON. + if (depth.Count != 0) + throw new JsonException("Incomplete JSON Document"); + + end_of_input = true; + return false; + } + + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonWriter.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonWriter.cs new file mode 100644 index 000000000..603fbc7cc --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/JsonWriter.cs @@ -0,0 +1,509 @@ +#pragma warning disable 1587 +#region Header +/// +/// JsonWriter.cs +/// Stream-like facility to output JSON text. +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +using Amazon.Util.Internal; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; + + +namespace ThirdParty.Json.LitJson +{ + internal enum Condition + { + InArray, + InObject, + NotAProperty, + Property, + Value + } + + internal class WriterContext + { + public int Count; + public bool InArray; + public bool InObject; + public bool ExpectingValue; + public int Padding; + } + + internal class JsonWriter + { + #region Fields + private static NumberFormatInfo number_format; + + private WriterContext context; + private Stack ctx_stack; + private bool has_reached_end; + private char[] hex_seq; + private int indentation; + private int indent_value; + private StringBuilder inst_string_builder; + private bool pretty_print; + private bool validate; + private TextWriter writer; + #endregion + + + #region Properties + public int IndentValue { + get { return indent_value; } + set { + indentation = (indentation / indent_value) * value; + indent_value = value; + } + } + + public bool PrettyPrint { + get { return pretty_print; } + set { pretty_print = value; } + } + + public TextWriter TextWriter { + get { return writer; } + } + + public bool Validate { + get { return validate; } + set { validate = value; } + } + #endregion + + + #region Constructors + static JsonWriter () + { + number_format = NumberFormatInfo.InvariantInfo; + } + + public JsonWriter () + { + inst_string_builder = new StringBuilder (); + writer = new StringWriter (inst_string_builder); + + Init (); + } + + public JsonWriter (StringBuilder sb) : + this (new StringWriter (sb)) + { + } + + public JsonWriter (TextWriter writer) + { + if (writer == null) + throw new ArgumentNullException ("writer"); + + this.writer = writer; + + Init (); + } + #endregion + + + #region Private Methods + private void DoValidation (Condition cond) + { + if (! context.ExpectingValue) + context.Count++; + + if (! validate) + return; + + if (has_reached_end) + throw new JsonException ( + "A complete JSON symbol has already been written"); + + switch (cond) { + case Condition.InArray: + if (! context.InArray) + throw new JsonException ( + "Can't close an array here"); + break; + + case Condition.InObject: + if (! context.InObject || context.ExpectingValue) + throw new JsonException ( + "Can't close an object here"); + break; + + case Condition.NotAProperty: + if (context.InObject && ! context.ExpectingValue) + throw new JsonException ( + "Expected a property"); + break; + + case Condition.Property: + if (! context.InObject || context.ExpectingValue) + throw new JsonException ( + "Can't add a property here"); + break; + + case Condition.Value: + if (! context.InArray && + (! context.InObject || ! context.ExpectingValue)) + throw new JsonException ( + "Can't add a value here"); + + break; + } + } + + private void Init () + { + has_reached_end = false; + hex_seq = new char[4]; + indentation = 0; + indent_value = 4; + pretty_print = false; + validate = true; + + ctx_stack = new Stack (); + context = new WriterContext (); + ctx_stack.Push (context); + } + + private static void IntToHex (int n, char[] hex) + { + int num; + + for (int i = 0; i < 4; i++) { + num = n % 16; + + if (num < 10) + hex[3 - i] = (char) ('0' + num); + else + hex[3 - i] = (char) ('A' + (num - 10)); + + n >>= 4; + } + } + + private void Indent () + { + if (pretty_print) + indentation += indent_value; + } + + + private void Put (string str) + { + if (pretty_print && ! context.ExpectingValue) + for (int i = 0; i < indentation; i++) + writer.Write (' '); + + writer.Write (str); + } + + private void PutNewline () + { + PutNewline (true); + } + + private void PutNewline (bool add_comma) + { + if (add_comma && ! context.ExpectingValue && + context.Count > 1) + writer.Write (','); + + if (pretty_print && ! context.ExpectingValue) + writer.Write ("\r\n"); + } + + private void PutString (string str) + { + Put (String.Empty); + + writer.Write ('"'); + + int n = str.Length; + for (int i = 0; i < n; i++) + { + char c = str[i]; + switch (c) { + case '\n': + writer.Write ("\\n"); + continue; + + case '\r': + writer.Write ("\\r"); + continue; + + case '\t': + writer.Write ("\\t"); + continue; + + case '"': + case '\\': + writer.Write ('\\'); + writer.Write (c); + continue; + + case '\f': + writer.Write ("\\f"); + continue; + + case '\b': + writer.Write ("\\b"); + continue; + } + + if ((int) c >= 32 && (int) c <= 126) { + writer.Write (c); + continue; + } + + if (c < ' ' || (c >= '\u0080' && c < '\u00a0')) + { + // Turn into a \uXXXX sequence + IntToHex((int)c, hex_seq); + writer.Write("\\u"); + writer.Write(hex_seq); + } + else + { + writer.Write(c); + } + } + + writer.Write ('"'); + } + + private void Unindent () + { + if (pretty_print) + indentation -= indent_value; + } + #endregion + + + public override string ToString () + { + if (inst_string_builder == null) + return String.Empty; + + return inst_string_builder.ToString (); + } + + public void Reset () + { + has_reached_end = false; + + ctx_stack.Clear (); + context = new WriterContext (); + ctx_stack.Push (context); + + if (inst_string_builder != null) + inst_string_builder.Remove (0, inst_string_builder.Length); + } + + public void Write (bool boolean) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (boolean ? "true" : "false"); + + context.ExpectingValue = false; + } + + public void Write (decimal number) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (Convert.ToString (number, number_format)); + + context.ExpectingValue = false; + } + + public void Write (double number) + { + DoValidation (Condition.Value); + PutNewline (); + + // Modified to support roundtripping of double.MaxValue + string str = number.ToString("R", CultureInfo.InvariantCulture); + Put (str); + + if (str.IndexOf ('.') == -1 && + str.IndexOf ('E') == -1) + writer.Write (".0"); + + context.ExpectingValue = false; + } + + public void Write (int number) + { + DoValidation(Condition.Value); + PutNewline (); + + Put(Convert.ToString(number, number_format)); + + context.ExpectingValue = false; + } + + public void Write(uint number) + { + DoValidation(Condition.Value); + PutNewline(); + + Put(Convert.ToString(number, number_format)); + + context.ExpectingValue = false; + } + + public void Write(long number) + { + DoValidation(Condition.Value); + PutNewline(); + + Put(Convert.ToString(number, number_format)); + + context.ExpectingValue = false; + } + + public void Write (string str) + { + DoValidation (Condition.Value); + PutNewline (); + + if (str == null) + Put ("null"); + else + PutString (str); + + context.ExpectingValue = false; + } + + public void WriteRaw(string str) + { + DoValidation(Condition.Value); + PutNewline(); + + if (str == null) + Put("null"); + else + Put(str); + + context.ExpectingValue = false; + } + + public void Write (ulong number) + { + DoValidation (Condition.Value); + PutNewline (); + + Put (Convert.ToString (number, number_format)); + + context.ExpectingValue = false; + } + + public void Write(DateTime date) + { + DoValidation(Condition.Value); + PutNewline(); + + + Put(AmazonUtils.ConvertToUnixEpochMilliSeconds(date).ToString(CultureInfo.InvariantCulture)); + + context.ExpectingValue = false; + } + + + public void WriteArrayEnd () + { + DoValidation (Condition.InArray); + PutNewline (false); + + ctx_stack.Pop (); + if (ctx_stack.Count == 1) + has_reached_end = true; + else { + context = ctx_stack.Peek (); + context.ExpectingValue = false; + } + + Unindent (); + Put ("]"); + } + + public void WriteArrayStart () + { + DoValidation (Condition.NotAProperty); + PutNewline (); + + Put ("["); + + context = new WriterContext (); + context.InArray = true; + ctx_stack.Push (context); + + Indent (); + } + + public void WriteObjectEnd () + { + DoValidation (Condition.InObject); + PutNewline (false); + + ctx_stack.Pop (); + if (ctx_stack.Count == 1) + has_reached_end = true; + else { + context = ctx_stack.Peek (); + context.ExpectingValue = false; + } + + Unindent (); + Put ("}"); + } + + public void WriteObjectStart () + { + DoValidation (Condition.NotAProperty); + PutNewline (); + + Put ("{"); + + context = new WriterContext (); + context.InObject = true; + ctx_stack.Push (context); + + Indent (); + } + + public void WritePropertyName (string property_name) + { + DoValidation (Condition.Property); + PutNewline (); + + PutString (property_name); + + if (pretty_print) { + if (property_name.Length > context.Padding) + context.Padding = property_name.Length; + + for (int i = context.Padding - property_name.Length; + i >= 0; i--) + writer.Write (' '); + + writer.Write (": "); + } else + writer.Write (':'); + + context.ExpectingValue = true; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/Lexer.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/Lexer.cs new file mode 100644 index 000000000..ff7f4dad7 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/Lexer.cs @@ -0,0 +1,911 @@ +#pragma warning disable 1587 +#region Header +/// +/// Lexer.cs +/// JSON lexer implementation based on a finite state machine. +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + + +namespace ThirdParty.Json.LitJson +{ + internal class FsmContext + { + public bool Return; + public int NextState; + public Lexer L; + public int StateStack; + } + + + internal class Lexer + { + #region Fields + private delegate bool StateHandler (FsmContext ctx); + + private static int[] fsm_return_table; + private static StateHandler[] fsm_handler_table; + + private bool allow_comments; + private bool allow_single_quoted_strings; + private bool end_of_input; + private FsmContext fsm_context; + private int input_buffer; + private int input_char; + private TextReader reader; + private int state; + private StringBuilder string_buffer; + private string string_value; + private int token; + private int unichar; + #endregion + + + #region Properties + public bool AllowComments { + get { return allow_comments; } + set { allow_comments = value; } + } + + public bool AllowSingleQuotedStrings { + get { return allow_single_quoted_strings; } + set { allow_single_quoted_strings = value; } + } + + public bool EndOfInput { + get { return end_of_input; } + } + + public int Token { + get { return token; } + } + + public string StringValue { + get { return string_value; } + } + #endregion + + + #region Constructors + static Lexer () + { + PopulateFsmTables (); + } + + public Lexer (TextReader reader) + { + allow_comments = true; + allow_single_quoted_strings = true; + + input_buffer = 0; + string_buffer = new StringBuilder (128); + state = 1; + end_of_input = false; + this.reader = reader; + + fsm_context = new FsmContext (); + fsm_context.L = this; + } + #endregion + + + #region Static Methods + private static int HexValue (int digit) + { + switch (digit) { + case 'a': + case 'A': + return 10; + + case 'b': + case 'B': + return 11; + + case 'c': + case 'C': + return 12; + + case 'd': + case 'D': + return 13; + + case 'e': + case 'E': + return 14; + + case 'f': + case 'F': + return 15; + + default: + return digit - '0'; + } + } + + private static void PopulateFsmTables () + { + fsm_handler_table = new StateHandler[28] { + State1, + State2, + State3, + State4, + State5, + State6, + State7, + State8, + State9, + State10, + State11, + State12, + State13, + State14, + State15, + State16, + State17, + State18, + State19, + State20, + State21, + State22, + State23, + State24, + State25, + State26, + State27, + State28 + }; + + fsm_return_table = new int[28] { + (int) ParserToken.Char, + 0, + (int) ParserToken.Number, + (int) ParserToken.Number, + 0, + (int) ParserToken.Number, + 0, + (int) ParserToken.Number, + 0, + 0, + (int) ParserToken.True, + 0, + 0, + 0, + (int) ParserToken.False, + 0, + 0, + (int) ParserToken.Null, + (int) ParserToken.CharSeq, + (int) ParserToken.Char, + 0, + 0, + (int) ParserToken.CharSeq, + (int) ParserToken.Char, + 0, + 0, + 0, + 0 + }; + } + + private static char ProcessEscChar (int esc_char) + { + switch (esc_char) { + case '"': + case '\'': + case '\\': + case '/': + return Convert.ToChar (esc_char); + + case 'n': + return '\n'; + + case 't': + return '\t'; + + case 'r': + return '\r'; + + case 'b': + return '\b'; + + case 'f': + return '\f'; + + default: + // Unreachable + return '?'; + } + } + + private static bool State1 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') + continue; + + if (ctx.L.input_char >= '1' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 3; + return true; + } + + switch (ctx.L.input_char) { + case '"': + ctx.NextState = 19; + ctx.Return = true; + return true; + + case ',': + case ':': + case '[': + case ']': + case '{': + case '}': + ctx.NextState = 1; + ctx.Return = true; + return true; + + case '-': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 2; + return true; + + case '0': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 4; + return true; + + case 'f': + ctx.NextState = 12; + return true; + + case 'n': + ctx.NextState = 16; + return true; + + case 't': + ctx.NextState = 9; + return true; + + case '\'': + if (! ctx.L.allow_single_quoted_strings) + return false; + + ctx.L.input_char = '"'; + ctx.NextState = 23; + ctx.Return = true; + return true; + + case '/': + if (! ctx.L.allow_comments) + return false; + + ctx.NextState = 25; + return true; + + default: + return false; + } + } + + return true; + } + + private static bool State2 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char >= '1' && ctx.L.input_char<= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 3; + return true; + } + + switch (ctx.L.input_char) { + case '0': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 4; + return true; + + default: + return false; + } + } + + private static bool State3 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + case '.': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 5; + return true; + + case 'e': + case 'E': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 7; + return true; + + default: + return false; + } + } + return true; + } + + private static bool State4 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + case '.': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 5; + return true; + + case 'e': + case 'E': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 7; + return true; + + default: + return false; + } + } + + private static bool State5 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 6; + return true; + } + + return false; + } + + private static bool State6 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + case 'e': + case 'E': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 7; + return true; + + default: + return false; + } + } + + return true; + } + + private static bool State7 (FsmContext ctx) + { + ctx.L.GetChar (); + + if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 8; + return true; + } + + switch (ctx.L.input_char) { + case '+': + case '-': + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + ctx.NextState = 8; + return true; + + default: + return false; + } + } + + private static bool State8 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') { + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + + if (ctx.L.input_char == ' ' || + ctx.L.input_char >= '\t' && ctx.L.input_char<= '\r') { + ctx.Return = true; + ctx.NextState = 1; + return true; + } + + switch (ctx.L.input_char) { + case ',': + case ']': + case '}': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + return true; + } + + private static bool State9 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'r': + ctx.NextState = 10; + return true; + + default: + return false; + } + } + + private static bool State10 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'u': + ctx.NextState = 11; + return true; + + default: + return false; + } + } + + private static bool State11 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'e': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State12 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'a': + ctx.NextState = 13; + return true; + + default: + return false; + } + } + + private static bool State13 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'l': + ctx.NextState = 14; + return true; + + default: + return false; + } + } + + private static bool State14 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 's': + ctx.NextState = 15; + return true; + + default: + return false; + } + } + + private static bool State15 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'e': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State16 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'u': + ctx.NextState = 17; + return true; + + default: + return false; + } + } + + private static bool State17 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'l': + ctx.NextState = 18; + return true; + + default: + return false; + } + } + + private static bool State18 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'l': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State19 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + switch (ctx.L.input_char) { + case '"': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 20; + return true; + + case '\\': + ctx.StateStack = 19; + ctx.NextState = 21; + return true; + + default: + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + } + + return true; + } + + private static bool State20 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case '"': + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State21 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case 'u': + ctx.NextState = 22; + return true; + + case '"': + case '\'': + case '/': + case '\\': + case 'b': + case 'f': + case 'n': + case 'r': + case 't': + ctx.L.string_buffer.Append ( + ProcessEscChar (ctx.L.input_char)); + ctx.NextState = ctx.StateStack; + return true; + + default: + return false; + } + } + + private static bool State22 (FsmContext ctx) + { + int counter = 0; + int mult = 4096; + + ctx.L.unichar = 0; + + while (ctx.L.GetChar ()) { + + if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9' || + ctx.L.input_char >= 'A' && ctx.L.input_char <= 'F' || + ctx.L.input_char >= 'a' && ctx.L.input_char <= 'f') { + + ctx.L.unichar += HexValue (ctx.L.input_char) * mult; + + counter++; + mult /= 16; + + if (counter == 4) { + ctx.L.string_buffer.Append ( + Convert.ToChar (ctx.L.unichar)); + ctx.NextState = ctx.StateStack; + return true; + } + + continue; + } + + return false; + } + + return true; + } + + private static bool State23 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + switch (ctx.L.input_char) { + case '\'': + ctx.L.UngetChar (); + ctx.Return = true; + ctx.NextState = 24; + return true; + + case '\\': + ctx.StateStack = 23; + ctx.NextState = 21; + return true; + + default: + ctx.L.string_buffer.Append ((char) ctx.L.input_char); + continue; + } + } + + return true; + } + + private static bool State24 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case '\'': + ctx.L.input_char = '"'; + ctx.Return = true; + ctx.NextState = 1; + return true; + + default: + return false; + } + } + + private static bool State25 (FsmContext ctx) + { + ctx.L.GetChar (); + + switch (ctx.L.input_char) { + case '*': + ctx.NextState = 27; + return true; + + case '/': + ctx.NextState = 26; + return true; + + default: + return false; + } + } + + private static bool State26 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == '\n') { + ctx.NextState = 1; + return true; + } + } + + return true; + } + + private static bool State27 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == '*') { + ctx.NextState = 28; + return true; + } + } + + return true; + } + + private static bool State28 (FsmContext ctx) + { + while (ctx.L.GetChar ()) { + if (ctx.L.input_char == '*') + continue; + + if (ctx.L.input_char == '/') { + ctx.NextState = 1; + return true; + } + + ctx.NextState = 27; + return true; + } + + return true; + } + #endregion + + + private bool GetChar () + { + if ((input_char = NextChar ()) != -1) + return true; + + end_of_input = true; + return false; + } + + private int NextChar () + { + if (input_buffer != 0) { + int tmp = input_buffer; + input_buffer = 0; + + return tmp; + } + + return reader.Read (); + } + + public bool NextToken () + { + StateHandler handler; + fsm_context.Return = false; + + while (true) { + handler = fsm_handler_table[state - 1]; + + if (! handler (fsm_context)) + throw new JsonException (input_char); + + if (end_of_input) + return false; + + if (fsm_context.Return) { + string_value = string_buffer.ToString (); + string_buffer.Length = 0; + token = fsm_return_table[state - 1]; + + if (token == (int) ParserToken.Char) + token = input_char; + + state = fsm_context.NextState; + + return true; + } + + state = fsm_context.NextState; + } + } + + private void UngetChar () + { + input_buffer = input_char; + } + } +} diff --git a/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/ParserToken.cs b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/ParserToken.cs new file mode 100644 index 000000000..3cabedfd8 --- /dev/null +++ b/Libraries/src/Amazon.Lambda.RuntimeSupport/ThirdParty/Json/ParserToken.cs @@ -0,0 +1,45 @@ +#pragma warning disable 1587 +#region Header +/// +/// ParserToken.cs +/// Internal representation of the tokens used by the lexer and the parser. +/// +/// The authors disclaim copyright to this source code. For more details, see +/// the COPYING file included with this distribution. +/// +#endregion + + +namespace ThirdParty.Json.LitJson +{ + internal enum ParserToken + { + // Lexer tokens + None = System.Char.MaxValue + 1, + Number, + True, + False, + Null, + CharSeq, + // Single char + Char, + + // Parser Rules + Text, + Object, + ObjectPrime, + Pair, + PairRest, + Array, + ArrayPrime, + Value, + ValueRest, + String, + + // End of input + End, + + // The empty rule + Epsilon + } +} diff --git a/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj b/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj index 8833c5052..216ce39fb 100644 --- a/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj +++ b/Libraries/src/Amazon.Lambda.Serialization.Json/Amazon.Lambda.Serialization.Json.csproj @@ -9,7 +9,7 @@ Amazon.Lambda.Serialization.Json Amazon.Lambda.Serialization.Json AWS;Amazon;Lambda - 1.5.0 + 1.5.0 diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs index e0e9ccc2f..bcc2f8ab6 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestApplicationLoadBalancerCalls.cs @@ -63,14 +63,24 @@ public async Task TestGetNoQueryStringAlbMv() public async Task TestGetEncodingQueryStringAlb() { var response = await this.InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-encoding-request.json"); - Assert.Equal("?url=http://www.gooogle.com", response.Body); + var results = JsonConvert.DeserializeObject(response.Body); + Assert.Equal("http://www.gooogle.com", results.Url); + Assert.Equal(DateTimeOffset.Parse("2019-03-12T16:06:06.549817+00:00"), results.TestDateTimeOffset); + + Assert.True(response.Headers.ContainsKey("Content-Type")); + Assert.Equal("application/json; charset=utf-8", response.Headers["Content-Type"]); } [Fact] public async Task TestGetEncodingQueryStringAlbMv() { var response = await this.InvokeApplicationLoadBalancerRequest("values-get-querystring-alb-mv-encoding-request.json"); - Assert.Equal("?url=http://www.gooogle.com", response.Body); + var results = JsonConvert.DeserializeObject(response.Body); + Assert.Equal("http://www.gooogle.com", results.Url); + Assert.Equal(DateTimeOffset.Parse("2019-03-12T16:06:06.549817+00:00"), results.TestDateTimeOffset); + + Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); + Assert.Equal("application/json; charset=utf-8", response.MultiValueHeaders["Content-Type"][0]); } [Fact] @@ -151,6 +161,17 @@ public async Task TestPutBinaryContent() Assert.False(response.IsBase64Encoded, "Response IsBase64Encoded"); } + [Fact] + public async Task TestHealthCheck() + { + var response = await this.InvokeApplicationLoadBalancerRequest("alb-healthcheck.json"); + + Assert.Equal(200, response.StatusCode); + Assert.Equal("[\"value1\",\"value2\"]", response.Body); + Assert.True(response.Headers.ContainsKey("Content-Type")); + Assert.Equal("application/json; charset=utf-8", response.Headers["Content-Type"]); + } + private async Task InvokeApplicationLoadBalancerRequest(string fileName) { return await InvokeApplicationLoadBalancerRequest(new TestLambdaContext(), fileName); diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs index 3d8668521..c9dacc034 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/TestCallingWebAPI.cs @@ -9,7 +9,6 @@ using System.Linq; using System.Net; using System.Reflection; - using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -26,20 +25,20 @@ public async Task TestGetAllValues() { var context = new TestLambdaContext(); - var response = await this.InvokeAPIGatewayRequest(context, "values-get-all-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest(context, "values-get-all-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); Assert.Equal("application/json; charset=utf-8", response.MultiValueHeaders["Content-Type"][0]); - Assert.Contains("OnStarting Called", ((TestLambdaLogger)context.Logger).Buffer.ToString()); + Assert.Contains("OnStarting Called", ((TestLambdaLogger) context.Logger).Buffer.ToString()); } [Fact] public async Task TestGetAllValuesWithCustomPath() { - var response = await this.InvokeAPIGatewayRequest("values-get-different-proxypath-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-get-different-proxypath-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("[\"value1\",\"value2\"]", response.Body); @@ -50,7 +49,7 @@ public async Task TestGetAllValuesWithCustomPath() [Fact] public async Task TestGetSingleValue() { - var response = await this.InvokeAPIGatewayRequest("values-get-single-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-get-single-apigateway-request.json"); Assert.Equal("value=5", response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); @@ -60,7 +59,7 @@ public async Task TestGetSingleValue() [Fact] public async Task TestGetQueryStringValue() { - var response = await this.InvokeAPIGatewayRequest("values-get-querystring-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-get-querystring-apigateway-request.json"); Assert.Equal("Lewis, Meriwether", response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); @@ -70,7 +69,7 @@ public async Task TestGetQueryStringValue() [Fact] public async Task TestGetNoQueryStringApiGateway() { - var response = await this.InvokeAPIGatewayRequest("values-get-no-querystring-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-get-no-querystring-apigateway-request.json"); Assert.Equal(string.Empty, response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); @@ -80,17 +79,19 @@ public async Task TestGetNoQueryStringApiGateway() [Fact] public async Task TestGetEncodingQueryStringGateway() { - var response = await this.InvokeAPIGatewayRequest("values-get-querystring-apigatway-encoding-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-get-querystring-apigateway-encoding-request.json"); + var results = JsonConvert.DeserializeObject(response.Body); + Assert.Equal("http://www.gooogle.com", results.Url); + Assert.Equal(DateTimeOffset.Parse("2019-03-12T16:06:06.549817+00:00"), results.TestDateTimeOffset); - Assert.Equal("?url=http://www.gooogle.com", response.Body); Assert.True(response.MultiValueHeaders.ContainsKey("Content-Type")); - Assert.Equal("text/plain; charset=utf-8", response.MultiValueHeaders["Content-Type"][0]); + Assert.Equal("application/json; charset=utf-8", response.MultiValueHeaders["Content-Type"][0]); } [Fact] public async Task TestPutWithBody() { - var response = await this.InvokeAPIGatewayRequest("values-put-withbody-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-put-withbody-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("Agent, Smith", response.Body); @@ -101,15 +102,15 @@ public async Task TestPutWithBody() [Fact] public async Task TestDefaultResponseErrorCode() { - var response = await this.InvokeAPIGatewayRequest("values-get-error-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-get-error-apigateway-request.json"); Assert.Equal(500, response.StatusCode); Assert.Equal(string.Empty, response.Body); } [Theory] - [InlineData("values-get-aggregateerror-apigatway-request.json", "AggregateException")] - [InlineData("values-get-typeloaderror-apigatway-request.json", "ReflectionTypeLoadException")] + [InlineData("values-get-aggregateerror-apigateway-request.json", "AggregateException")] + [InlineData("values-get-typeloaderror-apigateway-request.json", "ReflectionTypeLoadException")] public async Task TestEnhancedExceptions(string requestFileName, string expectedExceptionType) { var response = await this.InvokeAPIGatewayRequest(requestFileName); @@ -123,7 +124,7 @@ public async Task TestEnhancedExceptions(string requestFileName, string expected [Fact] public async Task TestGettingSwaggerDefinition() { - var response = await this.InvokeAPIGatewayRequest("swagger-get-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("swagger-get-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.True(response.Body.Length > 0); @@ -133,7 +134,7 @@ public async Task TestGettingSwaggerDefinition() [Fact] public void TestGetCustomAuthorizerValue() { - var requestStr = File.ReadAllText("values-get-customauthorizer-apigatway-request.json"); + var requestStr = File.ReadAllText("values-get-customauthorizer-apigateway-request.json"); var request = JsonConvert.DeserializeObject(requestStr); Assert.NotNull(request.RequestContext.Authorizer); Assert.NotNull(request.RequestContext.Authorizer.StringKey); @@ -160,8 +161,9 @@ public void TestCustomAuthorizerSerialization() new APIGatewayCustomAuthorizerPolicy.IAMPolicyStatement { Effect = "Allow", - Action = new HashSet { "execute-api:Invoke" }, - Resource = new HashSet { "arn:aws:execute-api:us-west-2:1234567890:apit123d45/Prod/GET/*" } + Action = new HashSet {"execute-api:Invoke"}, + Resource = new HashSet + {"arn:aws:execute-api:us-west-2:1234567890:apit123d45/Prod/GET/*"} } } } @@ -176,9 +178,9 @@ public void TestCustomAuthorizerSerialization() [Fact] public async Task TestGetBinaryContent() { - var response = await this.InvokeAPIGatewayRequest("values-get-binary-apigatway-request.json"); - - Assert.Equal((int)HttpStatusCode.OK, response.StatusCode); + var response = await this.InvokeAPIGatewayRequest("values-get-binary-apigateway-request.json"); + + Assert.Equal((int) HttpStatusCode.OK, response.StatusCode); IList contentType; Assert.True(response.MultiValueHeaders.TryGetValue("Content-Type", out contentType), @@ -186,12 +188,12 @@ public async Task TestGetBinaryContent() Assert.Equal("application/octet-stream", contentType[0]); Assert.NotNull(response.Body); Assert.True(response.Body.Length > 0, - "Body content is not empty"); + "Body content is not empty"); Assert.True(response.IsBase64Encoded, "Response IsBase64Encoded"); // Compute a 256-byte array, with values 0-255 - var binExpected = new byte[byte.MaxValue].Select((val, index) => (byte)index).ToArray(); + var binExpected = new byte[byte.MaxValue].Select((val, index) => (byte) index).ToArray(); var binActual = Convert.FromBase64String(response.Body); Assert.Equal(binExpected, binActual); } @@ -210,10 +212,27 @@ public async Task TestEncodePlusInResourcePath() [Fact] public async Task TestEncodeSpaceInResourcePath() { + var requestStr = GetRequestContent("encode-space-in-resource-path.json"); var response = await this.InvokeAPIGatewayRequest("encode-space-in-resource-path.json"); Assert.Equal(200, response.StatusCode); Assert.Equal("value=tmh/file name.xml", response.Body); + + } + + [Fact] + public async Task TestEncodeSlashInResourcePath() + { + var requestStr = GetRequestContent("encode-slash-in-resource-path.json"); + var response = await this.InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr); + + Assert.Equal(200, response.StatusCode); + Assert.Equal("{\"only\":\"a%2Fb\"}", response.Body); + + response = await this.InvokeAPIGatewayRequestWithContent(new TestLambdaContext(), requestStr.Replace("a%2Fb", "a/b")); + + Assert.Equal(200, response.StatusCode); + Assert.Equal("{\"first\":\"a\",\"second\":\"b\"}", response.Body); } [Fact] @@ -251,6 +270,15 @@ public async Task TestAuthTestAccess() Assert.Equal("You Have Access", response.Body); } + [Fact] + public async Task TestAuthTestAccess_CustomLambdaAuthorizerClaims() + { + var response = + await this.InvokeAPIGatewayRequest("authtest-access-request-custom-lambda-authorizer-output.json"); + + Assert.Equal(200, response.StatusCode); + Assert.Equal("You Have Access", response.Body); + } [Fact] public async Task TestAuthTestNoAccess() @@ -275,7 +303,7 @@ public async Task TestMissingResourceInRequest() [Fact] public async Task TestDeleteNoContentContentType() { - var response = await this.InvokeAPIGatewayRequest("values-delete-no-content-type-apigatway-request.json"); + var response = await this.InvokeAPIGatewayRequest("values-delete-no-content-type-apigateway-request.json"); Assert.Equal(200, response.StatusCode); Assert.True(response.Body.Length == 0); @@ -302,12 +330,22 @@ private async Task InvokeAPIGatewayRequest(string fileN } private async Task InvokeAPIGatewayRequest(TestLambdaContext context, string fileName) + { + return await InvokeAPIGatewayRequestWithContent(context, GetRequestContent(fileName)); + } + + private async Task InvokeAPIGatewayRequestWithContent(TestLambdaContext context, string requestContent) { var lambdaFunction = new ApiGatewayLambdaFunction(); + var request = JsonConvert.DeserializeObject(requestContent); + return await lambdaFunction.FunctionHandlerAsync(request, context); + } + + private string GetRequestContent(string fileName) + { var filePath = Path.Combine(Path.GetDirectoryName(this.GetType().GetTypeInfo().Assembly.Location), fileName); var requestStr = File.ReadAllText(filePath); - var request = JsonConvert.DeserializeObject(requestStr); - return await lambdaFunction.FunctionHandlerAsync(request, context); + return requestStr; } } -} +} \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/alb-healthcheck.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/alb-healthcheck.json new file mode 100644 index 000000000..2cbfb5fe3 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/alb-healthcheck.json @@ -0,0 +1,15 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/api/values", + "queryStringParameters": {}, + "headers": { + "user-agent": "ELB-HealthChecker/2.0" + }, + "body": "", + "isBase64Encoded": false +} \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/authtest-access-request-custom-lambda-authorizer-output.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/authtest-access-request-custom-lambda-authorizer-output.json new file mode 100644 index 000000000..f8b920cb8 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/authtest-access-request-custom-lambda-authorizer-output.json @@ -0,0 +1,64 @@ +{ + "resource": "/{proxy+}", + "path": "/api/authtest", + "httpMethod": "GET", + "headers": { + "Authorization": "eyJraWQiOiJLdXprWCtcL0E4MWlVZGU5QSt2SFBMdzZCTUFmQzVqUGJ1NTZiT0VGNXdkaz0iLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI4ZWYzZTQ1NS0zODY5LTQwMmUtYWM5ZS1mODhlODZjMzIwYjMiLCJjb2duaXRvOmdyb3VwcyI6WyJBZG1pbiJdLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwiaXNzIjoiaHR0cHM6XC9cL2NvZ25pdG8taWRwLnVzLXdlc3QtMi5hbWF6b25hd3MuY29tXC91cy13ZXN0LTJfUExpM2NmMHUwIiwicGhvbmVfbnVtYmVyX3ZlcmlmaWVkIjp0cnVlLCJjb2duaXRvOnVzZXJuYW1lIjoibm9ybWoiLCJhdWQiOiIzbXVzaGZjOHNnbTh1b2FjdmlmNXZoa3Q0OSIsImV2ZW50X2lkIjoiNzU3NjBmNTgtZjk4NC0xMWU3LThkNGEtMjM4OWVmYzUwZDY4IiwidG9rZW5fdXNlIjoiaWQiLCJhdXRoX3RpbWUiOjE1MTU5NzMyOTYsInBob25lX251bWJlciI6IisxNDI1NzM2NzM0OSIsImV4cCI6MTUxNTk3Njg5NiwiaWF0IjoxNTE1OTczMjk2LCJlbWFpbCI6ImpvaGFuc28yQGdtYWlsLmNvbSJ9.v1zIeTutUMzgXGoQy5dpOXUW6km9Ye-X-iLehgn1fO4HeJPZ9rOY9jDRMyRjyF5I57sAsN1v9uB3f98gEdStzO4qpASlYK7F7CtcenJ2lZYNU4gJPDQqEDdoMvE5Nir89RL09PYFceKaZw2Qr53VTLBfwaNGJYeSYeiZN09RL6fXwciH5h15rGwgNpl3kvzZOTLCqHNv3J7Y5POr8BjT2DvqUVJv7X1M5pOzHxWIqtBfAfyhEzZftIDqNKsI_aKZolkRd_UI77dSMNuZokJSAKlEuXc6fNJ556R3xSAhEli5DeZUIMdQLQVB7pIQzRlXvt2M0MMK-uC2d7_kUWAkVg", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "zzqupfvhrk.execute-api.us-west-2.amazonaws.com", + "Via": "1.1 ca79756ec49e2babf1b916300304b2fb.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "3OhHlF5fim8xxjG1-RZEFK4nos0t-JtPu6vvpNkUg9NOcl-Os53gBg==", + "X-Amzn-Trace-Id": "Root=1-5a5beab2-7cd6a52c614f43910ab9be30", + "X-Forwarded-For": "50.35.77.7, 52.46.17.45", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "queryStringParameters": null, + "pathParameters": { + "proxy": "api/authtest" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "8gffya", + "authorizer": { + "cognito:groups": "Admin", + "phone_number_verified": "true", + "cognito:username": "normj", + "aud": "3mushfc8sgm8uoacvif5vhkt49", + "event_id": "75760f58-f984-11e7-8d4a-2389efc50d68", + "token_use": "id", + "auth_time": "1515973296", + "you_are_special": "true" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "GET", + "requestTime": "14/Jan/2018:23:41:38 +0000", + "path": "/Prod/api/authtest", + "accountId": "626492997873", + "protocol": "HTTP/1.1", + "stage": "Prod", + "requestTimeEpoch": 1515973298687, + "requestId": "7713b963-f984-11e7-b02a-f346964d4540", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "50.35.77.7", + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": null, + "user": null + }, + "apiId": "zzqupfvhrk" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/encode-slash-in-resource-path.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/encode-slash-in-resource-path.json new file mode 100644 index 000000000..d0e68ac2e --- /dev/null +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/encode-slash-in-resource-path.json @@ -0,0 +1,119 @@ +{ + "resource": "/{proxy+}", + "path": "/api/resourcepath/encoding/a%2Fb", + "httpMethod": "GET", + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-US,en;q=0.9", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "uy8hsm9y23.execute-api.us-west-2.amazonaws.com", + "upgrade-insecure-requests": "1", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36", + "Via": "2.0 078ca3a7cfdee29c8e3514176205c50a.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "5uiz6HWWKWR_HQCLcvX2GXppwJP5FJGXcr_OUs31D6H_JV9uioyyMg==", + "X-Amzn-Trace-Id": "Root=1-5cc7de85-72b4887ed3a6d79ba6a5388a", + "X-Forwarded-For": "50.35.64.182, 52.46.17.56", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3" + ], + "Accept-Encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-US,en;q=0.9" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Host": [ + "uy8hsm9y23.execute-api.us-west-2.amazonaws.com" + ], + "upgrade-insecure-requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36" + ], + "Via": [ + "2.0 078ca3a7cfdee29c8e3514176205c50a.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "5uiz6HWWKWR_HQCLcvX2GXppwJP5FJGXcr_OUs31D6H_JV9uioyyMg==" + ], + "X-Amzn-Trace-Id": [ + "Root=1-5cc7de85-72b4887ed3a6d79ba6a5388a" + ], + "X-Forwarded-For": [ + "50.35.64.182, 52.46.17.56" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": null, + "multiValueQueryStringParameters": null, + "pathParameters": { + "proxy": "api/resourcepath/encoding/a%2Fb" + }, + "stageVariables": null, + "requestContext": { + "resourceId": "kaaa27", + "resourcePath": "/{proxy+}", + "httpMethod": "GET", + "extendedRequestId": "Y7-05GJCPHcF7pA=", + "requestTime": "30/Apr/2019:05:35:01 +0000", + "path": "/Prod/api/resourcepath/encoding/a%2Fb", + "accountId": "626492997873", + "protocol": "HTTP/1.1", + "stage": "Prod", + "domainPrefix": "uy8hsm9y23", + "requestTimeEpoch": 1556602501793, + "requestId": "b34371ef-6b09-11e9-86d0-b596427e6920", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "sourceIp": "50.35.64.182", + "principalOrgId": null, + "accessKey": null, + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36", + "user": null + }, + "domainName": "uy8hsm9y23.execute-api.us-west-2.amazonaws.com", + "apiId": "uy8hsm9y23" + }, + "body": null, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/swagger-get-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/swagger-get-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/swagger-get-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/swagger-get-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-delete-no-content-type-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-delete-no-content-type-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-delete-no-content-type-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-delete-no-content-type-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-aggregateerror-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-aggregateerror-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-aggregateerror-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-aggregateerror-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-all-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-all-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-all-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-all-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-binary-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-binary-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-binary-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-binary-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-customauthorizer-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-customauthorizer-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-customauthorizer-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-customauthorizer-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-different-proxypath-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-different-proxypath-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-different-proxypath-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-different-proxypath-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-error-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-error-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-error-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-error-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-no-querystring-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-no-querystring-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-no-querystring-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-no-querystring-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-encoding-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-encoding-request.json index 6c2f055f4..d91c4a03a 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-encoding-request.json +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-encoding-request.json @@ -5,9 +5,10 @@ } }, "httpMethod": "GET", - "path": "/api/rawquerystring", + "path": "/api/rawquerystring/json", "queryStringParameters": { - "url": "http://www.gooogle.com" + "url": "http://www.gooogle.com", + "testDateTimeOffset": "2019-03-12T16%3A06%3A06.5498170%2B00%3A00" }, "headers": { "accept": "text/html,application/xhtml+xml", diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-mv-encoding-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-mv-encoding-request.json index 17bde6ecb..6fb5dea92 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-mv-encoding-request.json +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-alb-mv-encoding-request.json @@ -5,9 +5,10 @@ } }, "httpMethod": "GET", - "path": "/api/rawquerystring", + "path": "/api/rawquerystring/json", "multiValueQueryStringParameters": { - "url": [ "http://www.gooogle.com"] + "url": [ "http://www.gooogle.com" ], + "testDateTimeOffset": [ "2019-03-12T16%3A06%3A06.5498170%2B00%3A00" ] }, "multiValueHeaders": { "accept": [ "text/html", "application/xhtml+xml" ], diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigatway-encoding-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigateway-encoding-request.json similarity index 83% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigatway-encoding-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigateway-encoding-request.json index 2a9c8b732..403db5a86 100644 --- a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigatway-encoding-request.json +++ b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigateway-encoding-request.json @@ -1,13 +1,14 @@ { "resource": "/{proxy+}", - "path": "/api/rawquerystring", + "path": "/api/rawquerystring/json", "httpMethod": "GET", "headers": null, "queryStringParameters": { - "url": "http://www.gooogle.com" + "url": "http://www.gooogle.com", + "testDateTimeOffset": "2019-03-12T16:06:06.549817+00:00" }, "pathParameters": { - "proxy": "api/rawquerystring" + "proxy": "api/rawquerystring/json" }, "stageVariables": null, "requestContext": { diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-querystring-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-single-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-single-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-single-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-single-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-typeloaderror-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-typeloaderror-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-typeloaderror-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-get-typeloaderror-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-put-withbody-apigatway-request.json b/Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-put-withbody-apigateway-request.json similarity index 100% rename from Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-put-withbody-apigatway-request.json rename to Libraries/test/Amazon.Lambda.AspNetCoreServer.Test/values-put-withbody-apigateway-request.json diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj new file mode 100644 index 000000000..903468061 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/Amazon.Lambda.RuntimeSupport.IntegrationTests.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp2.1 + SKIP_RUNTIME_SUPPORT_INTEG_TESTS + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs new file mode 100644 index 000000000..e434f8547 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.IntegrationTests/CustomRuntimeTests.cs @@ -0,0 +1,389 @@ +/* + * 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.IdentityManagement; +using Amazon.IdentityManagement.Model; +using Amazon.Lambda.Model; +using Amazon.S3; +using Amazon.S3.Model; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.IntegrationTests +{ + public class CustomRuntimeTests + { + private static readonly RegionEndpoint TestRegion = RegionEndpoint.USWest2; + private static readonly string LAMBDA_ASSUME_ROLE_POLICY = + @" + { + ""Version"": ""2012-10-17"", + ""Statement"": [ + { + ""Sid"": """", + ""Effect"": ""Allow"", + ""Principal"": { + ""Service"": ""lambda.amazonaws.com"" + }, + ""Action"": ""sts:AssumeRole"" + } + ] + } + ".Trim(); + + private const string ExecutionRoleName = "runtimesupporttestingrole"; + private const string TestBucketRoot = "runtimesupporttesting-"; + private const string FunctionName = "CustomRuntimeFunctionTest"; + private const string DeploymentZipKey = "CustomRuntimeFunctionTest.zip"; + private const string DeploymentPackageZipRelativePath = @"CustomRuntimeFunctionTest\bin\Release\netcoreapp2.2\CustomRuntimeFunctionTest.zip"; + private const string TestsProjectDirectoryName = "Amazon.Lambda.RuntimeSupport.Tests"; + + private static string ExecutionRoleArn { get; set; } + +#if SKIP_RUNTIME_SUPPORT_INTEG_TESTS + [Fact(Skip = "Skipped intentionally by setting the SkipRuntimeSupportIntegTests build parameter.")] +#else + [Fact] +#endif + public async Task TestAllHandlersAsync() + { + // run all test cases in one test to ensure they run serially + using (var lambdaClient = new AmazonLambdaClient(TestRegion)) + using (var s3Client = new AmazonS3Client(TestRegion)) + using (var iamClient = new AmazonIdentityManagementServiceClient(TestRegion)) + { + var roleAlreadyExisted = false; + + try + { + roleAlreadyExisted = await PrepareTestResources(s3Client, lambdaClient, iamClient); + + await RunTestSuccessAsync(lambdaClient, "ToUpperAsync", "message", "ToUpperAsync-MESSAGE"); + await RunTestSuccessAsync(lambdaClient, "PingAsync", "ping", "PingAsync-pong"); + await RunTestSuccessAsync(lambdaClient, "HttpsWorksAsync", "", "HttpsWorksAsync-SUCCESS"); + await RunTestSuccessAsync(lambdaClient, "CertificateCallbackWorksAsync", "", "CertificateCallbackWorksAsync-SUCCESS"); + await RunTestSuccessAsync(lambdaClient, "NetworkingProtocolsAsync", "", "NetworkingProtocolsAsync-SUCCESS"); + await RunTestSuccessAsync(lambdaClient, "HandlerEnvVarAsync", "", "HandlerEnvVarAsync-HandlerEnvVarAsync"); + await RunTestExceptionAsync(lambdaClient, "AggregateExceptionUnwrappedAsync", "", "Exception", "Exception thrown from an async handler."); + await RunTestExceptionAsync(lambdaClient, "AggregateExceptionUnwrapped", "", "Exception", "Exception thrown from a synchronous handler."); + await RunTestExceptionAsync(lambdaClient, "AggregateExceptionNotUnwrappedAsync", "", "AggregateException", "AggregateException thrown from an async handler."); + await RunTestExceptionAsync(lambdaClient, "AggregateExceptionNotUnwrapped", "", "AggregateException", "AggregateException thrown from a synchronous handler."); + await RunTestExceptionAsync(lambdaClient, "TooLargeResponseBodyAsync", "", "Function.ResponseSizeTooLarge", "Response payload size (7340060 bytes) exceeded maximum allowed payload size (6291556 bytes)."); + await RunTestSuccessAsync(lambdaClient, "LambdaEnvironmentAsync", "", "LambdaEnvironmentAsync-SUCCESS"); + await RunTestSuccessAsync(lambdaClient, "LambdaContextBasicAsync", "", "LambdaContextBasicAsync-SUCCESS"); + await RunTestSuccessAsync(lambdaClient, "GetPidDllImportAsync", "", "GetPidDllImportAsync-SUCCESS"); + await RunTestSuccessAsync(lambdaClient, "GetTimezoneNameAsync", "", "GetTimezoneNameAsync-UTC"); + } + finally + { + await CleanUpTestResources(s3Client, lambdaClient, iamClient, roleAlreadyExisted); + } + } + } + + private async Task RunTestExceptionAsync(AmazonLambdaClient lambdaClient, string handler, string input, + string expectedErrorType, string expectedErrorMessage) + { + await UpdateHandlerAsync(lambdaClient, handler); + + var invokeResponse = await InvokeFunctionAsync(lambdaClient, JsonConvert.SerializeObject(input)); + Assert.True(invokeResponse.HttpStatusCode == System.Net.HttpStatusCode.OK); + Assert.True(invokeResponse.FunctionError != null); + using (var responseStream = invokeResponse.Payload) + using (var sr = new StreamReader(responseStream)) + { + JObject exception = (JObject)JsonConvert.DeserializeObject(await sr.ReadToEndAsync()); + Assert.Equal(expectedErrorType, exception["errorType"].ToString()); + Assert.Equal(expectedErrorMessage, exception["errorMessage"].ToString()); + } + } + + private async Task RunTestSuccessAsync(AmazonLambdaClient lambdaClient, string handler, string input, string expectedResponse) + { + await UpdateHandlerAsync(lambdaClient, handler); + + var invokeResponse = await InvokeFunctionAsync(lambdaClient, JsonConvert.SerializeObject(input)); + Assert.True(invokeResponse.HttpStatusCode == System.Net.HttpStatusCode.OK); + Assert.True(invokeResponse.FunctionError == null); + using (var responseStream = invokeResponse.Payload) + using (var sr = new StreamReader(responseStream)) + { + var responseString = JsonConvert.DeserializeObject(await sr.ReadToEndAsync()); + Assert.Equal(expectedResponse, responseString); + } + } + + /// + /// Clean up all test resources. + /// Also cleans up any resources that might be left from previous failed/interrupted tests. + /// + /// + /// + /// + private async Task CleanUpTestResources(AmazonS3Client s3Client, AmazonLambdaClient lambdaClient, + AmazonIdentityManagementServiceClient iamClient, bool roleAlreadyExisted) + { + await DeleteFunctionIfExistsAsync(lambdaClient); + + var listBucketsResponse = await s3Client.ListBucketsAsync(); + foreach (var bucket in listBucketsResponse.Buckets) + { + if (bucket.BucketName.StartsWith(TestBucketRoot)) + { + await DeleteDeploymentZipAndBucketAsync(s3Client, bucket.BucketName); + } + } + + if (!roleAlreadyExisted) + { + try + { + var deleteRoleRequest = new DeleteRoleRequest + { + RoleName = ExecutionRoleName + }; + await iamClient.DeleteRoleAsync(deleteRoleRequest); + } + catch (Exception) + { + // no problem - it's best effort + } + } + } + + private async Task PrepareTestResources(AmazonS3Client s3Client, AmazonLambdaClient lambdaClient, + AmazonIdentityManagementServiceClient iamClient) + { + var roleAlreadyExisted = await ValidateAndSetIamRoleArn(iamClient); + + var testBucketName = TestBucketRoot + Guid.NewGuid().ToString(); + await CreateBucketWithDeploymentZipAsync(s3Client, testBucketName); + await CreateFunctionAsync(lambdaClient, testBucketName); + + return roleAlreadyExisted; + } + + /// + /// Create the role if it's not there already. + /// Return true if it already existed. + /// + /// + private static async Task ValidateAndSetIamRoleArn(AmazonIdentityManagementServiceClient iamClient) + { + var getRoleRequest = new GetRoleRequest + { + RoleName = ExecutionRoleName + }; + try + { + ExecutionRoleArn = (await iamClient.GetRoleAsync(getRoleRequest)).Role.Arn; + return true; + } + catch (NoSuchEntityException) + { + // create the role + var createRoleRequest = new CreateRoleRequest + { + RoleName = ExecutionRoleName, + Description = "Test role for CustomRuntimeTests.", + AssumeRolePolicyDocument = LAMBDA_ASSUME_ROLE_POLICY + }; + ExecutionRoleArn = (await iamClient.CreateRoleAsync(createRoleRequest)).Role.Arn; + return false; + } + } + + private async Task CreateBucketWithDeploymentZipAsync(AmazonS3Client s3Client, string bucketName) + { + // create bucket if it doesn't exist + var listBucketsResponse = await s3Client.ListBucketsAsync(); + if (listBucketsResponse.Buckets.Find((bucket) => bucket.BucketName == bucketName) == null) + { + var putBucketRequest = new PutBucketRequest + { + BucketName = bucketName + }; + await s3Client.PutBucketAsync(putBucketRequest); + } + + // write or overwrite deployment package + var putObjectRequest = new PutObjectRequest + { + BucketName = bucketName, + Key = DeploymentZipKey, + FilePath = GetDeploymentZipPath() + }; + await s3Client.PutObjectAsync(putObjectRequest); + } + + private async Task DeleteDeploymentZipAndBucketAsync(AmazonS3Client s3Client, string bucketName) + { + // Delete the deployment zip. + // This is idempotent - it works even if the object is not there. + var deleteObjectRequest = new DeleteObjectRequest + { + BucketName = bucketName, + Key = DeploymentZipKey + }; + await s3Client.DeleteObjectAsync(deleteObjectRequest); + + // Delete the bucket. + // Make idempotent by checking exception. + var deleteBucketRequest = new DeleteBucketRequest + { + BucketName = bucketName + }; + try + { + await s3Client.DeleteBucketAsync(deleteBucketRequest); + } + catch (AmazonS3Exception e) + { + // If it's just telling us the bucket's not there then continue, otherwise throw. + if (!e.Message.Contains("The specified bucket does not exist")) + { + throw; + } + } + } + + private async Task InvokeFunctionAsync(AmazonLambdaClient lambdaClient, string payload) + { + var request = new InvokeRequest + { + FunctionName = FunctionName, + Payload = payload + }; + return await lambdaClient.InvokeAsync(request); + } + + private static async Task UpdateHandlerAsync(AmazonLambdaClient lambdaClient, string handler) + { + var updateFunctionConfigurationRequest = new UpdateFunctionConfigurationRequest + { + FunctionName = FunctionName, + Handler = handler + }; + await lambdaClient.UpdateFunctionConfigurationAsync(updateFunctionConfigurationRequest); + } + + private static async Task CreateFunctionAsync(AmazonLambdaClient lambdaClient, string bucketName) + { + await DeleteFunctionIfExistsAsync(lambdaClient); + + var createRequest = new CreateFunctionRequest + { + FunctionName = FunctionName, + Code = new FunctionCode + { + S3Bucket = bucketName, + S3Key = DeploymentZipKey + }, + Handler = "PingAsync", + MemorySize = 512, + Runtime = Runtime.Provided, + Role = ExecutionRoleArn + }; + + var startTime = DateTime.Now; + var created = false; + while (DateTime.Now < startTime.AddSeconds(30)) + { + try + { + await lambdaClient.CreateFunctionAsync(createRequest); + created = true; + break; + } + catch (InvalidParameterValueException ipve) + { + // Wait for the role to be fully propagated through AWS + if (ipve.Message == "The role defined for the function cannot be assumed by Lambda.") + { + await Task.Delay(2000); + } + else + { + throw; + } + } + } + + if (!created) + { + throw new Exception($"Timed out trying to create Lambda function {FunctionName}"); + } + } + + private static async Task DeleteFunctionIfExistsAsync(AmazonLambdaClient lambdaClient) + { + var request = new DeleteFunctionRequest + { + FunctionName = FunctionName + }; + + try + { + var response = await lambdaClient.DeleteFunctionAsync(request); + } + catch (ResourceNotFoundException) + { + // no problem + } + } + + /// + /// Get the path of the deployment package for testing the custom runtime. + /// This assumes that the 'dotnet lambda package -c Release' command was run as part of the pre-build of this csproj. + /// + /// + private static string GetDeploymentZipPath() + { + var testsProjectDirectory = FindUp(System.Environment.CurrentDirectory, TestsProjectDirectoryName, true); + Assert.NotNull(testsProjectDirectory); + + var deploymentZipFile = Path.Combine(testsProjectDirectory, DeploymentPackageZipRelativePath); + + Assert.True(File.Exists(deploymentZipFile)); + + return deploymentZipFile; + } + + private static string FindUp(string path, string fileOrDirectoryName, bool combine) + { + var fullPath = Path.Combine(path, fileOrDirectoryName); + if (File.Exists(fullPath) || Directory.Exists(fullPath)) + { + return combine ? fullPath : path; + } + else + { + var upDirectory = Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(upDirectory)) + { + return null; + } + else + { + return FindUp(upDirectory, fileOrDirectoryName, combine); + } + } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Amazon.Lambda.RuntimeSupport.UnitTests.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Amazon.Lambda.RuntimeSupport.UnitTests.csproj new file mode 100644 index 000000000..32966cb3d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/Amazon.Lambda.RuntimeSupport.UnitTests.csproj @@ -0,0 +1,29 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs new file mode 100644 index 000000000..b35949633 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/HandlerWrapperTests.cs @@ -0,0 +1,685 @@ +/* + * 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.Core; +using Amazon.Lambda.Serialization.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class HandlerWrapperTests + { + private static readonly JsonSerializer Serializer = new JsonSerializer(); + + private static readonly byte[] EmptyBytes = null; + + private static readonly byte[] InputBytes = new byte[5] { 0, 1, 2, 3, 4 }; + private static readonly byte[] OutputBytes = null; + + private const string StringInput = "mIxEd CaSe StRiNg"; + private static readonly byte[] StringInputBytes = null; + private const string StringOutput = "MIXED CASE STRING"; + private static readonly byte[] StringOutputBytes = null; + + private static readonly PocoInput PocoInput = new PocoInput + { + InputInt = 0, + InputString = "xyz" + }; + private static readonly byte[] PocoInputBytes = null; + private static readonly PocoOutput PocoOutput = new PocoOutput + { + OutputInt = 10, + OutputString = "XYZ" + }; + private static readonly byte[] PocoOutputBytes = null; + + static HandlerWrapperTests() + { + EmptyBytes = new byte[0]; + + OutputBytes = new byte[InputBytes.Length]; + for (int i = 0; i < InputBytes.Length; i++) + { + OutputBytes[i] = (byte)(InputBytes[i] + 10); + } + + MemoryStream tempStream; + + tempStream = new MemoryStream(); + Serializer.Serialize(StringInput, tempStream); + StringInputBytes = new byte[tempStream.Length]; + tempStream.Position = 0; + tempStream.Read(StringInputBytes, 0, StringInputBytes.Length); + + tempStream = new MemoryStream(); + Serializer.Serialize(StringOutput, tempStream); + StringOutputBytes = new byte[tempStream.Length]; + tempStream.Position = 0; + tempStream.Read(StringOutputBytes, 0, StringOutputBytes.Length); + + tempStream = new MemoryStream(); + Serializer.Serialize(PocoInput, tempStream); + PocoInputBytes = new byte[tempStream.Length]; + tempStream.Position = 0; + tempStream.Read(PocoInputBytes, 0, PocoInputBytes.Length); + + tempStream = new MemoryStream(); + Serializer.Serialize(PocoOutput, tempStream); + PocoOutputBytes = new byte[tempStream.Length]; + tempStream.Position = 0; + tempStream.Read(PocoOutputBytes, 0, PocoOutputBytes.Length); + } + + private LambdaEnvironment _lambdaEnvironment; + private RuntimeApiHeaders _runtimeApiHeaders; + private Checkpoint _checkpoint; + + public HandlerWrapperTests() + { + var environmentVariables = new TestEnvironmentVariables(); + _lambdaEnvironment = new LambdaEnvironment(environmentVariables); + + var headers = new Dictionary>(); + headers.Add(RuntimeApiHeaders.HeaderAwsRequestId, new List() { "request_id" }); + headers.Add(RuntimeApiHeaders.HeaderInvokedFunctionArn, new List() { "invoked_function_arn" }); + _runtimeApiHeaders = new RuntimeApiHeaders(headers); + _checkpoint = new Checkpoint(); + } + + [Fact] + public async Task TestTask() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async () => + { + await Task.Delay(0); + _checkpoint.Check(); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestStreamTask() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input) => + { + await Task.Delay(0); + _checkpoint.Check(); + AssertEqual(InputBytes, input); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestPocoInputTask() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestILambdaContextTask() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (context) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.NotNull(context.AwsRequestId); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestStreamILambdaContextTask() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input, context) => + { + await Task.Delay(0); + _checkpoint.Check(); + AssertEqual(InputBytes, input); + Assert.NotNull(context.AwsRequestId); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestPocoInputILambdaContextTask() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input, context) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + Assert.NotNull(context.AwsRequestId); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestTaskOfStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async () => + { + await Task.Delay(0); + _checkpoint.Check(); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestStreamTaskOfStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input) => + { + await Task.Delay(0); + _checkpoint.Check(); + AssertEqual(InputBytes, input); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestPocoInputTaskOfStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + return new MemoryStream(OutputBytes); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestContextTaskOfStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (context) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.NotNull(context.AwsRequestId); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestStreamContextTaskOfStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input, context) => + { + await Task.Delay(0); + _checkpoint.Check(); + AssertEqual(InputBytes, input); + Assert.NotNull(context.AwsRequestId); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestPocoInputContextTaskOfStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input, context) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + Assert.NotNull(context.AwsRequestId); + return new MemoryStream(OutputBytes); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestTaskOfPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async () => + { + await Task.Delay(0); + _checkpoint.Check(); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestStreamTaskOfPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input) => + { + await Task.Delay(0); + _checkpoint.Check(); + AssertEqual(InputBytes, input); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestPocoInputTaskOfPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestILambdaContextTaskOfPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (context) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.NotNull(context.AwsRequestId); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestStreamILambdaContextTaskOfPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input, context) => + { + await Task.Delay(0); + _checkpoint.Check(); + AssertEqual(InputBytes, input); + Assert.NotNull(context.AwsRequestId); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestPocoInputILambdaContextTaskOfPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(async (input, context) => + { + await Task.Delay(0); + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + Assert.NotNull(context.AwsRequestId); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestVoid() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(() => + { + _checkpoint.Check(); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestStreamVoid() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input) => + { + _checkpoint.Check(); + AssertEqual(InputBytes, input); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestPocoInputVoid() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input) => + { + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestILambdaContextVoid() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((context) => + { + _checkpoint.Check(); + Assert.NotNull(context.AwsRequestId); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestStreamILambdaContextVoid() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input, context) => + { + _checkpoint.Check(); + AssertEqual(InputBytes, input); + Assert.NotNull(context.AwsRequestId); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestPocoInputILambdaContextVoid() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input, context) => + { + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + Assert.NotNull(context.AwsRequestId); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, EmptyBytes, false); + } + } + + [Fact] + public async Task TestVoidStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(() => + { + _checkpoint.Check(); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestStreamStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input) => + { + _checkpoint.Check(); + AssertEqual(InputBytes, input); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestPocoInputStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input) => + { + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + return new MemoryStream(OutputBytes); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestILambdaContextStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((context) => + { + _checkpoint.Check(); + Assert.NotNull(context.AwsRequestId); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestStreamILambdaContextStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input, context) => + { + _checkpoint.Check(); + AssertEqual(InputBytes, input); + Assert.NotNull(context.AwsRequestId); + return new MemoryStream(OutputBytes); + })) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestPocoInputILambdaContextStream() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input, context) => + { + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + Assert.NotNull(context.AwsRequestId); + return new MemoryStream(OutputBytes); + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, OutputBytes, true); + } + } + + [Fact] + public async Task TestVoidPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper(() => + { + _checkpoint.Check(); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestStreamPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input) => + { + _checkpoint.Check(); + AssertEqual(InputBytes, input); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestPocoInputPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input) => + { + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestILambdaContextPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((context) => + { + _checkpoint.Check(); + Assert.NotNull(context.AwsRequestId); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, EmptyBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestStreamILambdaContextPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input, context) => + { + _checkpoint.Check(); + AssertEqual(InputBytes, input); + Assert.NotNull(context.AwsRequestId); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, InputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestPocoInputILambdaContextPocoOutput() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input, context) => + { + _checkpoint.Check(); + Assert.Equal(PocoInput, input); + Assert.NotNull(context.AwsRequestId); + return PocoOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, PocoInputBytes, PocoOutputBytes, false); + } + } + + [Fact] + public async Task TestSerializtionOfString() + { + using (var handlerWrapper = HandlerWrapper.GetHandlerWrapper((input) => + { + _checkpoint.Check(); + Assert.Equal(StringInput, input); + return StringOutput; + }, Serializer)) + { + await TestHandlerWrapper(handlerWrapper, StringInputBytes, StringOutputBytes, false); + } + } + + private async Task TestHandlerWrapper(HandlerWrapper handlerWrapper, byte[] input, byte[] expectedOutput, bool expectedDisposeOutputStream) + { + // run twice to make sure wrappers that reuse the output stream work correctly + for (int i = 0; i < 2; i++) + { + var invocation = new InvocationRequest + { + InputStream = new MemoryStream(input ?? new byte[0]), + LambdaContext = new LambdaContext(_runtimeApiHeaders, _lambdaEnvironment) + }; + + var invocationResponse = await handlerWrapper.Handler(invocation); + + Assert.True(_checkpoint.IsChecked); + Assert.Equal(expectedDisposeOutputStream, invocationResponse.DisposeOutputStream); + AssertEqual(expectedOutput, invocationResponse.OutputStream); + } + } + + private void AssertEqual(byte[] expected, Stream actual) + { + Assert.NotNull(actual); + var actualBytes = new byte[actual.Length]; + actual.Read(actualBytes, 0, actualBytes.Length); + AssertEqual(expected, actualBytes); + } + + private void AssertEqual(byte[] expected, byte[] actual) + { + Assert.NotNull(expected); + Assert.NotNull(actual); + Assert.True(expected != null && actual != null); + Assert.Equal(expected.Length, actual.Length); + for (int i = 0; i < expected.Length; i++) + { + Assert.Equal(expected[i], actual[i]); + } + } + + public class Checkpoint + { + public bool IsChecked { get; set; } + + public void Check() + { + IsChecked = true; + } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs new file mode 100644 index 000000000..19a44a18e --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaBootstrapTests.cs @@ -0,0 +1,172 @@ +/* + * 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 System; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + /// + /// Tests to test LambdaBootstrap when it's constructed using its actual constructor. + /// Tests of the static GetLambdaBootstrap methods can be found in LambdaBootstrapWrapperTests. + /// + public class LambdaBootstrapTests + { + TestHandler _testFunction; + TestInitializer _testInitializer; + TestRuntimeApiClient _testRuntimeApiClient; + TestEnvironmentVariables _environmentVariables; + + public LambdaBootstrapTests() + { + _environmentVariables = new TestEnvironmentVariables(); + _testRuntimeApiClient = new TestRuntimeApiClient(_environmentVariables); + _testInitializer = new TestInitializer(); + _testFunction = new TestHandler(); + } + + [Fact] + public void ThrowsExceptionForNullHandler() + { + Assert.Throws("handler", () => { new LambdaBootstrap((LambdaBootstrapHandler)null); }); + } + + [Fact] + public async Task NoInitializer() + { + _testFunction.CancellationSource.Cancel(); + + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, null)) + { + bootstrap.Client = _testRuntimeApiClient; + await bootstrap.RunAsync(_testFunction.CancellationSource.Token); + } + Assert.False(_testInitializer.InitializerWasCalled); + Assert.False(_testFunction.HandlerWasCalled); + } + + [Fact] + public async Task InitializerThrowsException() + { + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, _testInitializer.InitializeThrowAsync)) + { + bootstrap.Client = _testRuntimeApiClient; + var exception = await Assert.ThrowsAsync(async () => { await bootstrap.RunAsync(); }); + Assert.Equal(TestInitializer.InitializeExceptionMessage, exception.Message); + } + + Assert.True(_testRuntimeApiClient.ReportInitializationErrorAsyncExceptionCalled); + Assert.True(_testInitializer.InitializerWasCalled); + Assert.False(_testFunction.HandlerWasCalled); + } + + [Fact] + public async Task InitializerReturnsFalse() + { + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, _testInitializer.InitializeFalseAsync)) + { + await bootstrap.RunAsync(); + } + Assert.True(_testInitializer.InitializerWasCalled); + Assert.False(_testFunction.HandlerWasCalled); + } + + [Fact] + public async Task InitializerReturnsTrueAndHandlerLoopRuns() + { + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, _testInitializer.InitializeTrueAsync)) + { + bootstrap.Client = _testRuntimeApiClient; + await bootstrap.RunAsync(_testFunction.CancellationSource.Token); + } + + Assert.True(_testInitializer.InitializerWasCalled); + Assert.True(_testFunction.HandlerWasCalled); + } + + [Fact] + public async Task TraceIdEnvironmentVariableIsSet() + { + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerAsync, null)) + { + bootstrap.Client = _testRuntimeApiClient; + Assert.Null(_environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarTraceId)); + + await bootstrap.InvokeOnceAsync(); + + Assert.NotNull(_testRuntimeApiClient.LastTraceId); + Assert.Equal(_testRuntimeApiClient.LastTraceId, _environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarTraceId)); + } + + Assert.False(_testInitializer.InitializerWasCalled); + Assert.True(_testFunction.HandlerWasCalled); + } + + [Fact] + public async Task HandlerThrowsException() + { + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerThrowsAsync, null)) + { + bootstrap.Client = _testRuntimeApiClient; + Assert.Null(_environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarTraceId)); + + await bootstrap.InvokeOnceAsync(); + } + + Assert.True(_testRuntimeApiClient.ReportInvocationErrorAsyncExceptionCalled); + Assert.False(_testInitializer.InitializerWasCalled); + Assert.True(_testFunction.HandlerWasCalled); + } + + [Fact] + public async Task HandlerInputAndOutputWork() + { + const string testInput = "a MiXeD cAsE sTrInG"; + + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerToUpperAsync, null)) + { + _testRuntimeApiClient.FunctionInput = Encoding.UTF8.GetBytes(testInput); + bootstrap.Client = _testRuntimeApiClient; + Assert.Null(_environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarTraceId)); + + await bootstrap.InvokeOnceAsync(); + } + + _testRuntimeApiClient.VerifyOutput(testInput.ToUpper()); + + Assert.False(_testInitializer.InitializerWasCalled); + Assert.True(_testFunction.HandlerWasCalled); + } + + [Fact] + public async Task HandlerReturnsNull() + { + using (var bootstrap = new LambdaBootstrap(_testFunction.BaseHandlerReturnsNullAsync, null)) + { + _testRuntimeApiClient.FunctionInput = new byte[0]; + bootstrap.Client = _testRuntimeApiClient; + Assert.Null(_environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarTraceId)); + + await bootstrap.InvokeOnceAsync(); + } + + _testRuntimeApiClient.VerifyOutput((byte[])null); + + Assert.False(_testInitializer.InitializerWasCalled); + Assert.True(_testFunction.HandlerWasCalled); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaEnvironmentTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaEnvironmentTests.cs new file mode 100644 index 000000000..369e7aeef --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/LambdaEnvironmentTests.cs @@ -0,0 +1,48 @@ +/* + * 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 System.Text.RegularExpressions; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class LambdaEnvironmentTests + { + private const string LambdaExecutionEnvironment = "part-set-by-lambda"; + TestEnvironmentVariables _environmentVariables; + + public LambdaEnvironmentTests() + { + _environmentVariables = new TestEnvironmentVariables(); + } + + [Fact] + public void SetsExecutionEnvironmentButNotTwice() + { + var expectedValueRegex = new Regex($"{LambdaExecutionEnvironment}_amazonlambdaruntimesupport_[0-9]+\\.[0-9]+\\.[0-9]+"); + _environmentVariables.SetEnvironmentVariable(LambdaEnvironment.EnvVarExecutionEnvironment, LambdaExecutionEnvironment); + + var lambdaEnvironment = new LambdaEnvironment(_environmentVariables); + Assert.True(expectedValueRegex.IsMatch(lambdaEnvironment.ExecutionEnvironment)); + Assert.True(expectedValueRegex.IsMatch(_environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarExecutionEnvironment))); + + // Make sure that creating another LambdaEnvironment instance won't change the value. + lambdaEnvironment = new LambdaEnvironment(_environmentVariables); + Assert.True(expectedValueRegex.IsMatch(lambdaEnvironment.ExecutionEnvironment)); + Assert.True(expectedValueRegex.IsMatch(_environmentVariables.GetEnvironmentVariable(LambdaEnvironment.EnvVarExecutionEnvironment))); + + } + + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/NonDisposingStreamWrapperTests.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/NonDisposingStreamWrapperTests.cs new file mode 100644 index 000000000..e5703bf4d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/NonDisposingStreamWrapperTests.cs @@ -0,0 +1,55 @@ +/* + * 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 System; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class NonDisposingStreamWrapperTests + { + private const string TestString = "testing123"; + [Fact] + public async Task MakeSureDisposeWorks() + { + var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(TestString)); + using (var streamWrapper = new NonDisposingStreamWrapper(memoryStream)) + { + var buffer = new byte[memoryStream.Length]; + await streamWrapper.ReadAsync(buffer); + Assert.Equal(TestString, Encoding.UTF8.GetString(buffer)); + } + + // show that it's not disposed + memoryStream.Position = 0; + + memoryStream.Dispose(); + + // show that it's disposed now + var caughtException = false; + try + { + memoryStream.Position = 0; + } + catch (ObjectDisposedException) + { + caughtException = true; + } + Assert.True(caughtException); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/PocoInput.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/PocoInput.cs new file mode 100644 index 000000000..3ac0c0afe --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/PocoInput.cs @@ -0,0 +1,49 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Text; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class PocoInput + { + public String InputString { get; set; } + public int InputInt { get; set; } + + public override bool Equals(object value) + { + PocoInput pocoInput = value as PocoInput; + + return !Object.ReferenceEquals(null, pocoInput) + && String.Equals(InputString, pocoInput.InputString) + && int.Equals(InputInt, pocoInput.InputInt); + } + + public override int GetHashCode() + { + unchecked + { + int hashBase = (int)2166136261; + int hashMultiplier = 16777619; + + int hash = hashBase; + hash = (hash * hashMultiplier) ^ (!Object.ReferenceEquals(null, InputString) ? InputString.GetHashCode() : 0); + hash = (hash * hashMultiplier) ^ (!Object.ReferenceEquals(null, InputInt) ? InputInt.GetHashCode() : 0); + return hash; + } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/PocoOutput.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/PocoOutput.cs new file mode 100644 index 000000000..8d0b26e31 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/PocoOutput.cs @@ -0,0 +1,49 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.Text; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class PocoOutput + { + public String OutputString { get; set; } + public int OutputInt { get; set; } + + public override bool Equals(object value) + { + PocoOutput pocoOutput = value as PocoOutput; + + return !Object.ReferenceEquals(null, pocoOutput) + && String.Equals(OutputString, pocoOutput.OutputString) + && int.Equals(OutputInt, pocoOutput.OutputInt); + } + + public override int GetHashCode() + { + unchecked + { + int hashBase = (int)2166136261; + int hashMultiplier = 16777619; + + int hash = hashBase; + hash = (hash * hashMultiplier) ^ (!Object.ReferenceEquals(null, OutputString) ? OutputString.GetHashCode() : 0); + hash = (hash * hashMultiplier) ^ (!Object.ReferenceEquals(null, OutputInt) ? OutputInt.GetHashCode() : 0); + return hash; + } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestEnvironmentVariables.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestEnvironmentVariables.cs new file mode 100644 index 000000000..828a0a787 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestEnvironmentVariables.cs @@ -0,0 +1,48 @@ +/* + * 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 System; +using System.Collections; +using System.Collections.Generic; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + class TestEnvironmentVariables : IEnvironmentVariables + { + Dictionary environmentVariables = new Dictionary(); + + public TestEnvironmentVariables(IDictionary initialValues = null) + { + environmentVariables = initialValues == null ? + new Dictionary() : + new Dictionary(initialValues); + } + + public string GetEnvironmentVariable(string variable) + { + environmentVariables.TryGetValue(variable, out var value); + return value; + } + + public IDictionary GetEnvironmentVariables() + { + return new Dictionary(environmentVariables); + } + + public void SetEnvironmentVariable(string variable, string value) + { + environmentVariables[variable] = value; + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestHandler.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestHandler.cs new file mode 100644 index 000000000..31b3f5934 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestHandler.cs @@ -0,0 +1,115 @@ +/* + * 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.Serialization.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class TestHandler + { + public const string InvokeExceptionMessage = "Invoke Exception"; + + protected JsonSerializer _jsonSerializer = new JsonSerializer(); + + public bool HandlerWasCalled { get; protected set; } + public byte[] LastInputReceived { get; private set; } + public byte[] LastOutputSent { get; private set; } + public CancellationTokenSource CancellationSource { get; } = new CancellationTokenSource(); + + public TestHandler() + { + // In case something goes wrong make sure tests won't hang forever in the invoke loop. + CancellationSource.CancelAfter(30000); + } + + public async Task BaseHandlerAsync(InvocationRequest invocation) + { + var outputStream = new MemoryStream(); + await DoHandlerCommonTasksAsync(invocation.InputStream, outputStream); + return new InvocationResponse(outputStream); + } + + public async Task BaseHandlerThrowsAsync(InvocationRequest invocation) + { + var outputStream = new MemoryStream(); + await DoHandlerCommonTasksAsync(invocation.InputStream, null); + throw new Exception(InvokeExceptionMessage); + } + + public async Task BaseHandlerToUpperAsync(InvocationRequest invocation) + { + using (var sr = new StreamReader(invocation.InputStream)) + { + Stream outputStream = new MemoryStream(Encoding.UTF8.GetBytes(sr.ReadToEnd().ToUpper())); + outputStream.Position = 0; + await DoHandlerCommonTasksAsync(invocation.InputStream, outputStream); + return new InvocationResponse(outputStream); + } + } + + public async Task BaseHandlerReturnsNullAsync(InvocationRequest invocation) + { + using (var sr = new StreamReader(invocation.InputStream)) + { + await DoHandlerCommonTasksAsync(invocation.InputStream, null); + return null; + } + } + + public void HandlerVoidVoidSync() + { + DoHandlerTasks(null, null); + } + + private async Task DoHandlerCommonTasksAsync(Stream input, Stream output) + { + await Task.Delay(0); + DoHandlerTasks(input, output); + } + + private void DoHandlerTasks(Stream input, Stream output) + { + CancellationSource.Cancel(); + HandlerWasCalled = true; + + if (input == null) + { + LastInputReceived = null; + } + else + { + input.Position = 0; + LastInputReceived = new byte[input.Length]; + input.Read(LastInputReceived); + } + + if (output == null) + { + LastOutputSent = null; + } + else + { + LastOutputSent = new byte[output.Length]; + output.Read(LastOutputSent); + output.Position = 0; + } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestInitializer.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestInitializer.cs new file mode 100644 index 000000000..4455c7f6d --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestInitializer.cs @@ -0,0 +1,70 @@ +/* + * 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.Serialization.Json; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + public class TestInitializer + { + public const string InitializeExceptionMessage = "Initialize Exception"; + + protected JsonSerializer _jsonSerializer = new JsonSerializer(); + + public bool InitializerWasCalled { get; protected set; } + + public bool InitializeTrue() + { + return Initialize(true); + } + public bool InitializeFalse() + { + return Initialize(false); + } + public bool InitializeThrow() + { + return Initialize(null); + } + + public Task InitializeTrueAsync() + { + return Task.FromResult(Initialize(true)); + } + public Task InitializeFalseAsync() + { + return Task.FromResult(Initialize(false)); + } + public Task InitializeThrowAsync() + { + return Task.FromResult(Initialize(null)); + } + + private bool Initialize(bool? result) + { + InitializerWasCalled = true; + if (result.HasValue) + { + return result.Value; + } + else + { + throw new Exception(InitializeExceptionMessage); + } + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs new file mode 100644 index 000000000..66c645c19 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/Amazon.Lambda.RuntimeSupport.UnitTests/TestHelpers/TestRuntimeApiClient.cs @@ -0,0 +1,135 @@ +/* + * 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 System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Amazon.Lambda.RuntimeSupport.UnitTests +{ + internal class TestRuntimeApiClient : IRuntimeApiClient + { + private IEnvironmentVariables _environmentVariables; + private Dictionary> _headers; + + public TestRuntimeApiClient(IEnvironmentVariables environmentVariables) + { + _environmentVariables = environmentVariables; + _headers = new Dictionary>(); + _headers.Add(RuntimeApiHeaders.HeaderAwsRequestId, new List() { "request_id" }); + _headers.Add(RuntimeApiHeaders.HeaderInvokedFunctionArn, new List() { "invoked_function_arn" }); + } + + public bool GetNextInvocationAsyncCalled { get; private set; } + public bool ReportInitializationErrorAsyncExceptionCalled { get; private set; } + public bool ReportInitializationErrorAsyncTypeCalled { get; private set; } + public bool ReportInvocationErrorAsyncExceptionCalled { get; private set; } + public bool ReportInvocationErrorAsyncTypeCalled { get; private set; } + public bool SendResponseAsyncCalled { get; private set; } + + public string LastTraceId { get; private set; } + public byte[] FunctionInput { get; set; } + public Stream LastOutputStream { get; private set; } + + public void VerifyOutput(string expectedOutput) + { + VerifyOutput(Encoding.UTF8.GetBytes(expectedOutput)); + } + + public void VerifyOutput(byte[] expectedOutput) + { + if (expectedOutput == null && LastOutputStream == null) + { + return; + } + else if (expectedOutput != null && LastOutputStream != null) + { + int nextByte = 0; + int count = 0; + while ((nextByte = LastOutputStream.ReadByte()) != -1) + { + Assert.Equal(expectedOutput[count++], nextByte); + } + if (count == 0) + { + Assert.Null(expectedOutput); + } + } + else + { + throw new Exception("expectedOutput and LastOutputStream must both be null or both be non-null."); + } + } + + public Task GetNextInvocationAsync() + { + GetNextInvocationAsyncCalled = true; + + LastTraceId = Guid.NewGuid().ToString(); + _headers[RuntimeApiHeaders.HeaderTraceId] = new List() { LastTraceId }; + + var inputStream = new MemoryStream(FunctionInput == null ? new byte[0] : FunctionInput); + inputStream.Position = 0; + + return Task.FromResult(new InvocationRequest() + { + InputStream = inputStream, + LambdaContext = new LambdaContext( + new RuntimeApiHeaders(_headers), + new LambdaEnvironment(_environmentVariables)) + }); + } + + public Task ReportInitializationErrorAsync(Exception exception) + { + ReportInitializationErrorAsyncExceptionCalled = true; + return Task.Run(() => { }); + } + + public Task ReportInitializationErrorAsync(string errorType) + { + ReportInitializationErrorAsyncTypeCalled = true; + return Task.Run(() => { }); + } + + public Task ReportInvocationErrorAsync(string awsRequestId, Exception exception) + { + ReportInvocationErrorAsyncExceptionCalled = true; + return Task.Run(() => { }); + } + + public Task ReportInvocationErrorAsync(string awsRequestId, string errorType) + { + ReportInvocationErrorAsyncTypeCalled = true; + return Task.Run(() => { }); + } + + public Task SendResponseAsync(string awsRequestId, Stream outputStream) + { + if (outputStream != null) + { + // copy the stream because it gets disposed by the bootstrap + LastOutputStream = new MemoryStream((int)outputStream.Length); + outputStream.CopyTo(LastOutputStream); + LastOutputStream.Position = 0; + } + + SendResponseAsyncCalled = true; + return Task.Run(() => { }); + } + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunction.cs b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunction.cs new file mode 100644 index 000000000..ba6b92279 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunction.cs @@ -0,0 +1,339 @@ +using Amazon.Lambda.Core; +using Amazon.Lambda.RuntimeSupport; +using Amazon.Lambda.Serialization.Json; +using System; +using System.IO; +using System.Net.Http; +using System.Net.Sockets; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace CustomRuntimeFunctionTest +{ + class CustomRuntimeFunction + { + private const string FailureResult = "FAILURE"; + private const string SuccessResult = "SUCCESS"; + private const string TestUrl = "https://www.amazon.com"; + + private static readonly Lazy SixMBString = new Lazy(() => { return new string('X', 1024 * 1024 * 6); }); + private static readonly Lazy SevenMBString = new Lazy(() => { return new string('X', 1024 * 1024 * 7); }); + + private static MemoryStream ResponseStream = new MemoryStream(); + private static JsonSerializer JsonSerializer = new JsonSerializer(); + private static LambdaEnvironment LambdaEnvironment = new LambdaEnvironment(); + + private static async Task Main(string[] args) + { + var handler = LambdaEnvironment.Handler; + LambdaBootstrap bootstrap = null; + HandlerWrapper handlerWrapper = null; + + try + { + switch (handler) + { + case nameof(ToUpperAsync): + bootstrap = new LambdaBootstrap(ToUpperAsync); + break; + case nameof(PingAsync): + bootstrap = new LambdaBootstrap(PingAsync); + break; + case nameof(HttpsWorksAsync): + bootstrap = new LambdaBootstrap(HttpsWorksAsync); + break; + case nameof(CertificateCallbackWorksAsync): + bootstrap = new LambdaBootstrap(CertificateCallbackWorksAsync); + break; + case nameof(NetworkingProtocolsAsync): + bootstrap = new LambdaBootstrap(NetworkingProtocolsAsync); + break; + case nameof(HandlerEnvVarAsync): + bootstrap = new LambdaBootstrap(HandlerEnvVarAsync); + break; + case nameof(AggregateExceptionUnwrappedAsync): + bootstrap = new LambdaBootstrap(AggregateExceptionUnwrappedAsync); + break; + case nameof(AggregateExceptionUnwrapped): + handlerWrapper = HandlerWrapper.GetHandlerWrapper((Action)AggregateExceptionUnwrapped); + bootstrap = new LambdaBootstrap(handlerWrapper); + break; + case nameof(AggregateExceptionNotUnwrappedAsync): + bootstrap = new LambdaBootstrap(AggregateExceptionNotUnwrappedAsync); + break; + case nameof(AggregateExceptionNotUnwrapped): + handlerWrapper = HandlerWrapper.GetHandlerWrapper((Action)AggregateExceptionNotUnwrapped); + bootstrap = new LambdaBootstrap(handlerWrapper); + break; + case nameof(TooLargeResponseBodyAsync): + bootstrap = new LambdaBootstrap(TooLargeResponseBodyAsync); + break; + case nameof(LambdaEnvironmentAsync): + bootstrap = new LambdaBootstrap(LambdaEnvironmentAsync); + break; + case nameof(LambdaContextBasicAsync): + bootstrap = new LambdaBootstrap(LambdaContextBasicAsync); + break; + case nameof(GetPidDllImportAsync): + bootstrap = new LambdaBootstrap(GetPidDllImportAsync); + break; + case nameof(GetTimezoneNameAsync): + bootstrap = new LambdaBootstrap(GetTimezoneNameAsync); + break; + default: + throw new Exception($"Handler {handler} is not supported."); + } + await bootstrap.RunAsync(); + } + finally + { + handlerWrapper?.Dispose(); + bootstrap?.Dispose(); + } + } + + private static Task ToUpperAsync(InvocationRequest invocation) + { + var input = JsonSerializer.Deserialize(invocation.InputStream); + return Task.FromResult(GetInvocationResponse(nameof(ToUpperAsync), input.ToUpper())); + } + + private static Task PingAsync(InvocationRequest invocation) + { + var input = JsonSerializer.Deserialize(invocation.InputStream); + + if (input == "ping") + { + return Task.FromResult(GetInvocationResponse(nameof(PingAsync), "pong")); + } + else + { + throw new Exception("Expected input: ping"); + } + } + + private static async Task HttpsWorksAsync(InvocationRequest invocation) + { + var isSuccess = false; + + using (var httpClient = new HttpClient()) + { + var response = await httpClient.GetAsync(TestUrl); + if (response.IsSuccessStatusCode) + { + isSuccess = true; + } + Console.WriteLine($"Response from HTTP get: {response}"); + } + + return GetInvocationResponse(nameof(HttpsWorksAsync), isSuccess); + } + + private static async Task CertificateCallbackWorksAsync(InvocationRequest invocation) + { + var isSuccess = false; + + using (var httpClientHandler = new HttpClientHandler()) + { + httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }; + using (var client = new HttpClient(httpClientHandler)) + { + var response = await client.GetAsync(TestUrl); + if (response.IsSuccessStatusCode) + { + isSuccess = true; + } + Console.WriteLine($"Response from HTTP get: {response}"); + } + } + + return GetInvocationResponse(nameof(CertificateCallbackWorksAsync), isSuccess); + } + + private static Task NetworkingProtocolsAsync(InvocationRequest invocation) + { + var type = typeof(Socket).GetTypeInfo().Assembly.GetType("System.Net.SocketProtocolSupportPal"); + var method = type.GetMethod("IsProtocolSupported", BindingFlags.NonPublic | BindingFlags.Static); + var ipv4Supported = method.Invoke(null, new object[] { AddressFamily.InterNetwork }); + var ipv6Supported = method.Invoke(null, new object[] { AddressFamily.InterNetworkV6 }); + + Exception ipv4SocketCreateException = null; + try + { + using (Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { } + } + catch (Exception e) + { + ipv4SocketCreateException = e; + } + + Exception ipv6SocketCreateException = null; + try + { + using (Socket s = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp)) { } + } + catch (Exception e) + { + ipv6SocketCreateException = e; + } + + string returnValue = ""; + if (!(bool)ipv4Supported) + { + returnValue += "For System.Net.SocketProtocolSupportPal.IsProtocolSupported(AddressFamily.InterNetwork) Expected true, Actual false" + Environment.NewLine; + } + + if ((bool)ipv6Supported) + { + returnValue += "For System.Net.SocketProtocolSupportPal.IsProtocolSupported(AddressFamily.InterNetworkV6) Expected false, Actual true" + Environment.NewLine; + } + + if (ipv4SocketCreateException != null) + { + returnValue += "Error creating IPV4 Socket: " + ipv4SocketCreateException + Environment.NewLine; + } + + if (ipv6SocketCreateException == null) + { + returnValue += "When creating IPV6 Socket expected exception, got none." + Environment.NewLine; + } + + if (ipv6SocketCreateException != null && ipv6SocketCreateException.Message != "Address family not supported by protocol") + { + returnValue += "When creating IPV6 Socket expected exception 'Address family not supported by protocol', actual '" + ipv6SocketCreateException.Message + "'" + Environment.NewLine; + } + + if (String.IsNullOrEmpty(returnValue)) + { + returnValue = "SUCCESS"; + } + + return Task.FromResult(GetInvocationResponse(nameof(NetworkingProtocolsAsync), returnValue)); + } + + private static Task HandlerEnvVarAsync(InvocationRequest invocation) + { + return Task.FromResult(GetInvocationResponse(nameof(HandlerEnvVarAsync), LambdaEnvironment.Handler)); + } + + private static async Task AggregateExceptionUnwrappedAsync(InvocationRequest invocation) + { + // do something async so this function is compiled as async + var dummy = await Task.FromResult("xyz"); + throw new Exception("Exception thrown from an async handler."); + } + + private static void AggregateExceptionUnwrapped() + { + throw new Exception("Exception thrown from a synchronous handler."); + } + + private static async Task AggregateExceptionNotUnwrappedAsync(InvocationRequest invocation) + { + // do something async so this function is compiled as async + var dummy = await Task.FromResult("xyz"); + throw new AggregateException("AggregateException thrown from an async handler."); + } + + private static void AggregateExceptionNotUnwrapped() + { + throw new AggregateException("AggregateException thrown from a synchronous handler."); + } + + private static Task TooLargeResponseBodyAsync(InvocationRequest invocation) + { + return Task.FromResult(GetInvocationResponse(nameof(TooLargeResponseBodyAsync), SevenMBString.Value)); + } + + private static Task LambdaEnvironmentAsync(InvocationRequest invocation) + { + AssertNotNull(LambdaEnvironment.FunctionMemorySize, nameof(LambdaEnvironment.FunctionMemorySize)); + AssertNotNull(LambdaEnvironment.FunctionName, nameof(LambdaEnvironment.FunctionName)); + AssertNotNull(LambdaEnvironment.FunctionVersion, nameof(LambdaEnvironment.FunctionVersion)); + AssertNotNull(LambdaEnvironment.Handler, nameof(LambdaEnvironment.Handler)); + AssertNotNull(LambdaEnvironment.LogGroupName, nameof(LambdaEnvironment.LogGroupName)); + AssertNotNull(LambdaEnvironment.LogStreamName, nameof(LambdaEnvironment.LogStreamName)); + AssertNotNull(LambdaEnvironment.RuntimeServerHostAndPort, nameof(LambdaEnvironment.RuntimeServerHostAndPort)); + AssertNotNull(LambdaEnvironment.XAmznTraceId, nameof(LambdaEnvironment.XAmznTraceId)); + + return Task.FromResult(GetInvocationResponse(nameof(LambdaEnvironmentAsync), true)); + } + + private static Task LambdaContextBasicAsync(InvocationRequest invocation) + { + AssertNotNull(invocation.LambdaContext.AwsRequestId, nameof(invocation.LambdaContext.AwsRequestId)); + AssertNotNull(invocation.LambdaContext.ClientContext, nameof(invocation.LambdaContext.ClientContext)); + AssertNotNull(invocation.LambdaContext.FunctionName, nameof(invocation.LambdaContext.FunctionName)); + AssertNotNull(invocation.LambdaContext.FunctionVersion, nameof(invocation.LambdaContext.FunctionVersion)); + AssertNotNull(invocation.LambdaContext.Identity, nameof(invocation.LambdaContext.Identity)); + AssertNotNull(invocation.LambdaContext.InvokedFunctionArn, nameof(invocation.LambdaContext.InvokedFunctionArn)); + AssertNotNull(invocation.LambdaContext.Logger, nameof(invocation.LambdaContext.Logger)); + AssertNotNull(invocation.LambdaContext.LogGroupName, nameof(invocation.LambdaContext.LogGroupName)); + AssertNotNull(invocation.LambdaContext.LogStreamName, nameof(invocation.LambdaContext.LogStreamName)); + + AssertTrue(invocation.LambdaContext.MemoryLimitInMB >= 128, + $"{nameof(invocation.LambdaContext.MemoryLimitInMB)}={invocation.LambdaContext.MemoryLimitInMB} is not >= 128"); + AssertTrue(invocation.LambdaContext.RemainingTime > TimeSpan.Zero, + $"{nameof(invocation.LambdaContext.RemainingTime)}={invocation.LambdaContext.RemainingTime} is not >= 0"); + + return Task.FromResult(GetInvocationResponse(nameof(LambdaContextBasicAsync), true)); + } + + #region GetPidDllImportAsync + [DllImport("libc", EntryPoint = "getpid", CallingConvention = CallingConvention.Cdecl)] + private static extern int getpid(); + + private static Task GetPidDllImportAsync(InvocationRequest invocation) + { + var isSuccess = getpid() > 0; + return Task.FromResult(GetInvocationResponse(nameof(GetPidDllImportAsync), isSuccess)); + } + #endregion + + private static Task GetTimezoneNameAsync(InvocationRequest invocation) + { + return Task.FromResult(GetInvocationResponse(nameof(GetTimezoneNameAsync), TimeZoneInfo.Local.Id)); + } + + #region Helpers + private static void AssertNotNull(object value, string valueName) + { + if (value == null) + { + throw new Exception($"{valueName} cannot be null."); + } + } + + private static void AssertTrue(bool value, string errorMessage) + { + if (!value) + { + throw new Exception(errorMessage); + } + } + + private static InvocationResponse GetInvocationResponse(string testName, bool isSuccess) + { + return GetInvocationResponse($"{testName}-{(isSuccess ? SuccessResult : FailureResult)}"); + } + + private static InvocationResponse GetInvocationResponse(string testName, string result) + { + return GetInvocationResponse($"{testName}-{result}"); + } + + private static InvocationResponse GetInvocationResponse(string result) + { + + ResponseStream.SetLength(0); + JsonSerializer.Serialize(result, ResponseStream); + ResponseStream.Position = 0; + + return new InvocationResponse(ResponseStream, false); + } + #endregion + + } +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunctionTest.csproj b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunctionTest.csproj new file mode 100644 index 000000000..d5409451f --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/CustomRuntimeFunctionTest.csproj @@ -0,0 +1,20 @@ + + + + Exe + netcoreapp2.2 + 7.1 + + + + + Always + + + + + + + + + diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/aws-lambda-tools-defaults.json b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/aws-lambda-tools-defaults.json new file mode 100644 index 000000000..5432e10bd --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/aws-lambda-tools-defaults.json @@ -0,0 +1,12 @@ +{ + "profile": "default", + "region": "us-west-2", + "configuration": "Release", + "framework": "netcoreapp2.2", + "msbuild-parameters": "--self-contained true", + "function-runtime": "provided", + "function-memory-size": 256, + "function-timeout": 30, + "function-handler": "PingAsync", + "function-name": "CustomRuntimeFunctionTest" +} diff --git a/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/bootstrap b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/bootstrap new file mode 100644 index 000000000..73ff1fc53 --- /dev/null +++ b/Libraries/test/Amazon.Lambda.RuntimeSupport.Tests/CustomRuntimeFunctionTest/bootstrap @@ -0,0 +1,3 @@ +#!/bin/sh + +/var/task/CustomRuntimeFunctionTest \ No newline at end of file diff --git a/Libraries/test/TestFunctionFSharp/TestFunctionCSharp/TestFunctionCSharp.csproj b/Libraries/test/TestFunctionFSharp/TestFunctionCSharp/TestFunctionCSharp.csproj index 5ecd86883..7affeddee 100644 --- a/Libraries/test/TestFunctionFSharp/TestFunctionCSharp/TestFunctionCSharp.csproj +++ b/Libraries/test/TestFunctionFSharp/TestFunctionCSharp/TestFunctionCSharp.csproj @@ -14,7 +14,7 @@ - + diff --git a/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs b/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs index 9837b932c..52e083bf9 100644 --- a/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs +++ b/Libraries/test/TestWebApp/Controllers/RawQueryStringController.cs @@ -14,5 +14,22 @@ public string Get() { return this.Request.QueryString.ToString(); } + + [HttpGet] + [Route("json")] + public Results Get([FromQuery] string url, [FromQuery] DateTimeOffset testDateTimeOffset) + { + return new Results + { + Url = url, + TestDateTimeOffset = testDateTimeOffset + }; + } + + public class Results + { + public string Url { get; set; } + public DateTimeOffset TestDateTimeOffset { get;set;} + } } } diff --git a/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs b/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs index 8ff34139d..082f14863 100644 --- a/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs +++ b/Libraries/test/TestWebApp/Controllers/ResourcePathController.cs @@ -30,5 +30,12 @@ public string GetString(string value) var path = this.HttpContext.Request.Path; return "value=" + value; } + + [HttpGet("/api/[controller]/encoding/{first}/{second}", Name = "Multi")] + public ActionResult Multi(string first, string second) => Ok(new { first, second }); + + [HttpGet("/api/[controller]/encoding/{only}", Name = "Single")] + public ActionResult Single(string only) => Ok(new { only }); + } } diff --git a/PowerShell/README.md b/PowerShell/README.md index ea478dfdc..454b2a21a 100644 --- a/PowerShell/README.md +++ b/PowerShell/README.md @@ -71,3 +71,9 @@ New-AWSPowerShellLambdaPackage|Creates the Lambda deployment package that can be * Part 5 concludes the series with the packaging and publishing of the PowerShell Lambda function to AWS and shows it in operation from Facebook. * [AWS Lambda and PowerShell](https://4sysops.com/archives/aws-lambda-with-powershell/) - By Graham Beer * Building Environment to create PowerShell AWS lambda's. Example of shutting down instances via tagging. +* [Automate the posts on Twitter using a AWS Lambda function and PowerShell](https://blog.victorsilva.com.uy/aws-lambda-powershell-twitter/) - By Victor Silva + * A way to send automated blog post on Twitter without “human” interaction using PowerShell AWS Lambda´s. + +### AWS Recorded Talks +* [Unleash your PowerShell with AWS Lambda and Serverless Computing](https://www.youtube.com/watch?v=-CmIrrEYtLA) - PowerShell and DevOps Global Summit 2019 by Andrew Pearce + * Introduces PowerShell language support in AWS Lambda, introduces event driven design patterns and demonstrates a PowerShell Serverless application using Amazon Simple Notification Service (SNS), Amazon Simple Queue Service (SQS) and Amazon API Gateway. diff --git a/README.md b/README.md index 5c73822ac..715718805 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ The table below shows the currently supported patch of each major version of .NE |Version|Currently Supported Patch|In Development Queue|Latest Microsoft Release| |-------|-------------------------|--------------------|------------------------| -|1.0|1.0.13|1.0.14|1.0.14| +|1.0|1.0.13|1.0.16|1.0.16| |2.0|2.0.9||2.0.9| -|2.1|2.1.6|2.1.7, 2.1.8|2.1.8| +|2.1|2.1.10|2.1.11|2.1.11| ## Learning Resources diff --git a/RELEASE.CHANGELOG.md b/RELEASE.CHANGELOG.md index e1fcff66e..99399746b 100644 --- a/RELEASE.CHANGELOG.md +++ b/RELEASE.CHANGELOG.md @@ -1,3 +1,42 @@ +### Release 2019-05-01 +* **Amazon.Lambda.AspNetCoreServer (3.0.4)** + * Pull Request [#449](https://github.com/aws/aws-lambda-dotnet/pull/449) fixing routing with escape characters in resource path. Thanks [Chris/0](https://github.com/chrisoverzero) + * Fixed url encoding issue with query string values when called by API Gateway. [#451](https://github.com/aws/aws-lambda-dotnet/pull/451) + * Fixed issue handling ELB Health Checks when Lambda function placed behind an Application Load Balancer. [#452](https://github.com/aws/aws-lambda-dotnet/pull/452) +* **Amazon.Lambda.Templates (3.7.1)** + * Updated dependencies for AWS SDK for .NET and the Amazon Lambda packages to the latest version. + +### Release 2019-03-18 +* **Amazon.Lambda.TestTool-2.1 (0.9.2)** (Preview) + * Fixed issue loading dependent assemblies when the name differs from the NuGet package. + + +### Release 2019-03-18 +* **Amazon.Lambda.RuntimeSupport (1.0.0)** + * New package to support running custom .NET Core Lambda runtimes like .NET Core 2.2. Read the following blog for more information. [https://aws.amazon.com/blogs/developer/announcing-amazon-lambda-runtimesupport/](https://aws.amazon.com/blogs/developer/announcing-amazon-lambda-runtimesupport/) +* **Blueprints** + * New Custom Runtime blueprint for both C# and F# + * **Amazon.Lambda.Templates (3.7.0)** released with latest blueprints. + + +### Release 2019-02-21 +* **Amazon.Lambda.AspNetCoreServer (3.0.3)** + * Pull Request [#409](https://github.com/aws/aws-lambda-dotnet/pull/409) allowing claims from custom authorizer to be passed into ASP.NET Core. Thanks [Lukas Sinkus](https://github.com/LUS1N) + +### Release 2019-02-21 +* **Amazon.Lambda.AspNetCoreServer (3.0.2)** + * Fixed bug with Amazon.Lambda.Logging.AspNetCoreServer not reading logging settings from configuration like appsettings.json. + * Added PostCreateWebHost virtual method to run code after the IWebHost has been created but not started. +* **Amazon.Lambda.Logging.AspNetCore (2.2.0)** + * Pull Request [#401](https://github.com/aws/aws-lambda-dotnet/pull/401) adds ability to log EventId and Exception. Thanks [Piotr Karpala](https://github.com/aws/aws-lambda-dotnet/pull/401) +* **Amazon.Lambda.TestTool-2.1 (0.9.1)** (Preview) + * Pull Request [#403](https://github.com/aws/aws-lambda-dotnet/pull/403) added `--path` command line argument. Thanks [Aidan Ryan](https://github.com/aidanjryan) + * Fixed bug when searching for default config files during startup. +* **Blueprints** + * Updated logging section in appsettings.json to Informational to match before the logging fix in Amazon.Lambda.AspNetCoreServer + * Updated NuGet dependencies. + * **Amazon.Lambda.Templates (3.6.0)** released with latest blueprints. + ### Release 2019-02-08 * **Amazon.Lambda.AspNetCoreServer (3.0.1)** * Fixed issue with content-type being incorrectly set by API Gateway when ASP.NET Core does not return a content-type. diff --git a/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/DlqMonitorTests.cs b/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/DlqMonitorTests.cs index f37c55837..7f55f2a65 100644 --- a/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/DlqMonitorTests.cs +++ b/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/DlqMonitorTests.cs @@ -15,7 +15,7 @@ namespace Amazon.Lambda.TestTool.Tests { public class DlqMonitorTests { - [Fact] + [Fact(Skip = "Integration test being disabled temporarily. Enable test once a container with a profile is created")] public async Task DlqIntegTest() { const int WAIT_TIME = 5000; diff --git a/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/ExternalCommandsTests.cs b/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/ExternalCommandsTests.cs index 4baa89483..7dd6fd914 100644 --- a/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/ExternalCommandsTests.cs +++ b/Tools/LambdaTestTool/Amazon.Lambda.TestTool.Tests/ExternalCommandsTests.cs @@ -14,7 +14,7 @@ namespace Amazon.Lambda.TestTool.Tests { public class ExternalCommandsTests { - [Fact] + [Fact(Skip = "Integration test being disabled temporarily. Enable test once a container with a profile is created")] public void ListProfiles() { var manager = new ExternalCommandManager(); @@ -30,7 +30,7 @@ public void ListProfiles() Assert.True(profiles.Contains(TestUtils.TestProfile)); } - [Fact] + [Fact(Skip = "Integration test being disabled temporarily. Enable test once a container with a profile is created")] public async Task ListQueuesAsync() { var queueName = "local-reader-list-queue-test-" + DateTime.Now.Ticks; @@ -52,7 +52,7 @@ public async Task ListQueuesAsync() } } - [Fact] + [Fact(Skip = "Integration test being disabled temporarily. Enable test once a container with a profile is created")] public async Task ReadMessageAsync() { var queueName = "local-reader-read-message-test-" + DateTime.Now.Ticks; diff --git a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj index b4f3b6f78..1e2ab0072 100644 --- a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj +++ b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Amazon.Lambda.TestTool.csproj @@ -10,13 +10,14 @@ true A tool to help debug and test your .NET Core AWS Lambda functions locally. Latest - 0.9.0 + 0.9.2 AWS .NET Lambda Test Tool Apache 2 false Amazon.Lambda.TestTool-2.1 AWS;Amazon;Lambda 1701;1702;1591;1587;3021;NU5100 + true diff --git a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/CommandLineOptions.cs b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/CommandLineOptions.cs index 7cc2725c8..3ba9eb09e 100644 --- a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/CommandLineOptions.cs +++ b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/CommandLineOptions.cs @@ -10,6 +10,7 @@ public class CommandLineOptions public bool NoLaunchWindow { get; set; } + public string Path { get; set; } public static CommandLineOptions Parse(string[] args) { @@ -27,6 +28,10 @@ public static CommandLineOptions Parse(string[] args) options.NoLaunchWindow = GetNextBoolValue(i); i++; break; + case "--path": + options.Path = GetNextValue(i); + i++; + break; } } diff --git a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Program.cs b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Program.cs index b02c28ead..8444453a6 100644 --- a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Program.cs +++ b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Program.cs @@ -29,14 +29,13 @@ static void Main(string[] args) Port = commandOptions.Port }; - var path = Directory.GetCurrentDirectory(); + var path = commandOptions.Path ?? Directory.GetCurrentDirectory(); - // Check to see if running in debug mode from this project's directory which means the test tool is being debugged. + // Check to see if running in debug mode from this project's directory which means the test tool is being debugged. // To make debugging easier pick one of the test Lambda projects. - if (Directory.GetCurrentDirectory().EndsWith("Amazon.Lambda.TestTool")) + if (path.EndsWith("Amazon.Lambda.TestTool")) { - path = Path.Combine(Directory.GetCurrentDirectory(), - "../LambdaFunctions/S3EventFunction/bin/Debug/netcoreapp2.1"); + path = Path.Combine(path, "../LambdaFunctions/S3EventFunction/bin/Debug/netcoreapp2.1"); } // If running in the project directory select the build directory so the deps.json file can be found. else if (Utils.IsProjectDirectory(path)) @@ -87,13 +86,21 @@ static IList SearchForConfigFiles(string lambdaFunctionDirectory) { foreach (var file in Directory.GetFiles(lambdaFunctionDirectory, "*.json", SearchOption.TopDirectoryOnly)) { - var data = JsonMapper.ToObject(File.ReadAllText(file)); - - if(data.ContainsKey("framework") && data["framework"].ToString().StartsWith("netcoreapp") && - (data.ContainsKey("function-handler") || data.ContainsKey("template"))) + try + { + var data = JsonMapper.ToObject(File.ReadAllText(file)); + + if (data.IsObject && + data.ContainsKey("framework") && data["framework"].ToString().StartsWith("netcoreapp") && + (data.ContainsKey("function-handler") || data.ContainsKey("template"))) + { + Console.WriteLine($"Found Lambda config file {file}"); + configFiles.Add(file); + } + } + catch { - Console.WriteLine($"Found Lambda config file {file}"); - configFiles.Add(file); + Console.WriteLine($"Error parsing JSON file: {file}"); } } diff --git a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaAssemblyResolver.cs b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaAssemblyResolver.cs index 5677bbfd7..da9042e0e 100644 --- a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaAssemblyResolver.cs +++ b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaAssemblyResolver.cs @@ -53,9 +53,28 @@ bool NamesMatch(RuntimeLibrary runtime) { return string.Equals(runtime.Name, name.Name, StringComparison.OrdinalIgnoreCase); } + + bool ResourceAssetPathMatch(RuntimeLibrary runtime) + { + foreach(var group in runtime.RuntimeAssemblyGroups) + { + foreach(var path in group.AssetPaths) + { + if(path.EndsWith("/" + name.Name + ".dll")) + { + return true; + } + } + } + return false; + } RuntimeLibrary library = this.dependencyContext.RuntimeLibraries.FirstOrDefault(NamesMatch); + + if(library == null) + library = this.dependencyContext.RuntimeLibraries.FirstOrDefault(ResourceAssetPathMatch); + if (library != null) { var wrapper = new CompilationLibrary( diff --git a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaExecutor.cs b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaExecutor.cs index e9e599903..eb06b0a18 100644 --- a/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaExecutor.cs +++ b/Tools/LambdaTestTool/Amazon.Lambda.TestTool/Runtime/LambdaExecutor.cs @@ -18,6 +18,13 @@ public ExecutionResponse Execute(ExecutionRequest request) { var logger = new LocalLambdaLogger(); var response = new ExecutionResponse(); + + if (!string.IsNullOrEmpty(request.Function.ErrorMessage)) + { + response.Error = request.Function.ErrorMessage; + return response; + } + try { if (!string.IsNullOrEmpty(request.AWSRegion)) diff --git a/buildtools/build.proj b/buildtools/build.proj index 821abba42..de50af041 100644 --- a/buildtools/build.proj +++ b/buildtools/build.proj @@ -1,299 +1,139 @@ - - - - - - - - Release - dotnet pack --no-build -c $(Configuration) -o ../../../Deployment/nuget-packages - - true - $(MSBuildThisFileDirectory)local-development.snk - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dotnet test -c $(Configuration) + Release + dotnet pack --no-build -c $(Configuration) -o ../../../Deployment/nuget-packages + true + $(MSBuildThisFileDirectory)local-development.snk + --updateVersions + false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - dotnet test - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + dotnet test -c $(Configuration) + + + + + + + + + + + + + + + + + dotnet test + + + + + + dotnet test + + + - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/buildtools/common.props b/buildtools/common.props index 10b7c4fa7..813af9823 100644 --- a/buildtools/common.props +++ b/buildtools/common.props @@ -7,7 +7,7 @@ Amazon Web Services Library - true + false true