From 209247e75f7b120c045a5e4a6092a34a6b936384 Mon Sep 17 00:00:00 2001 From: Michael Friis Date: Tue, 26 Feb 2019 15:56:53 -0800 Subject: [PATCH] add support for parsing ASP.NET Core HTTP requests into CloudEvents Signed-off-by: Michael Friis --- CloudEvents.sln | 17 +++ CloudEvents.v3.ncrunchsolution | 6 + .../CloudNative.CloudEvents.AspNetCore.csproj | 15 +++ .../HttpRequestExtension.cs | 120 ++++++++++++++++++ 4 files changed, 158 insertions(+) create mode 100644 CloudEvents.v3.ncrunchsolution create mode 100644 src/CloudNative.CloudEvents.AspNetCore/CloudNative.CloudEvents.AspNetCore.csproj create mode 100644 src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtension.cs diff --git a/CloudEvents.sln b/CloudEvents.sln index 1d9d4fc..c60967c 100644 --- a/CloudEvents.sln +++ b/CloudEvents.sln @@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudNative.CloudEvents.Amq EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CloudNative.CloudEvents.Kafka", "src\CloudNative.CloudEvents.Kafka\CloudNative.CloudEvents.Kafka.csproj", "{193D6D9D-C1A0-459E-86CF-F207CDF0FC73}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CloudNative.CloudEvents.AspNetCore", "src\CloudNative.CloudEvents.AspNetCore\CloudNative.CloudEvents.AspNetCore.csproj", "{C726DD78-2D56-48D3-928A-D10226E3750B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -93,6 +95,7 @@ Global {39EF4DB0-9890-4CAD-A36E-F7E25D2E72EF}.Release|x64.Build.0 = Release|Any CPU {39EF4DB0-9890-4CAD-A36E-F7E25D2E72EF}.Release|x86.ActiveCfg = Release|Any CPU {39EF4DB0-9890-4CAD-A36E-F7E25D2E72EF}.Release|x86.Build.0 = Release|Any CPU +<<<<<<< HEAD {193D6D9D-C1A0-459E-86CF-F207CDF0FC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {193D6D9D-C1A0-459E-86CF-F207CDF0FC73}.Debug|Any CPU.Build.0 = Debug|Any CPU {193D6D9D-C1A0-459E-86CF-F207CDF0FC73}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -105,6 +108,20 @@ Global {193D6D9D-C1A0-459E-86CF-F207CDF0FC73}.Release|x64.Build.0 = Release|Any CPU {193D6D9D-C1A0-459E-86CF-F207CDF0FC73}.Release|x86.ActiveCfg = Release|Any CPU {193D6D9D-C1A0-459E-86CF-F207CDF0FC73}.Release|x86.Build.0 = Release|Any CPU +======= + {C726DD78-2D56-48D3-928A-D10226E3750B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Debug|x64.ActiveCfg = Debug|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Debug|x64.Build.0 = Debug|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Debug|x86.ActiveCfg = Debug|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Debug|x86.Build.0 = Debug|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Release|Any CPU.Build.0 = Release|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Release|x64.ActiveCfg = Release|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Release|x64.Build.0 = Release|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Release|x86.ActiveCfg = Release|Any CPU + {C726DD78-2D56-48D3-928A-D10226E3750B}.Release|x86.Build.0 = Release|Any CPU +>>>>>>> dde2c54... add support for parsing ASP.NET Core HTTP requests into CloudEvents EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/CloudEvents.v3.ncrunchsolution b/CloudEvents.v3.ncrunchsolution new file mode 100644 index 0000000..f774ab9 --- /dev/null +++ b/CloudEvents.v3.ncrunchsolution @@ -0,0 +1,6 @@ + + + False + True + + \ No newline at end of file diff --git a/src/CloudNative.CloudEvents.AspNetCore/CloudNative.CloudEvents.AspNetCore.csproj b/src/CloudNative.CloudEvents.AspNetCore/CloudNative.CloudEvents.AspNetCore.csproj new file mode 100644 index 0000000..3aa0ce2 --- /dev/null +++ b/src/CloudNative.CloudEvents.AspNetCore/CloudNative.CloudEvents.AspNetCore.csproj @@ -0,0 +1,15 @@ + + + + netstandard2.0 + + + + + + + + + + + diff --git a/src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtension.cs b/src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtension.cs new file mode 100644 index 0000000..8647078 --- /dev/null +++ b/src/CloudNative.CloudEvents.AspNetCore/HttpRequestExtension.cs @@ -0,0 +1,120 @@ +// Copyright (c) Cloud Native Foundation. +// Licensed under the Apache 2.0 license. +// See LICENSE file in the project root for full license information. + +namespace CloudNative.CloudEvents +{ + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Primitives; + using Newtonsoft.Json; + using System; + using System.Net.Mime; + + public static class HttpRequestExtension + { + const string HttpHeaderPrefix = "ce-"; + + const string SpecVersionHttpHeader1 = HttpHeaderPrefix + "cloudEventsVersion"; + + const string SpecVersionHttpHeader2 = HttpHeaderPrefix + "specversion"; + + static JsonEventFormatter jsonFormatter = new JsonEventFormatter(); + + /// + /// Converts this HTTP request into a CloudEvent object, with the given extensions. + /// + /// HTTP request + /// List of extension instances + /// A CloudEvent instance or 'null' if the request message doesn't hold a CloudEvent + public static CloudEvent ToCloudEvent(this HttpRequest httpRequest, + params ICloudEventExtension[] extensions) + { + return ToCloudEvent(httpRequest, null, extensions); + } + + /// + /// Converts this HTTP request into a CloudEvent object, with the given extensions, + /// overriding the formatter. + /// + /// HTTP request + /// + /// List of extension instances + /// A CloudEvent instance or 'null' if the request message doesn't hold a CloudEvent + public static CloudEvent ToCloudEvent(this HttpRequest httpRequest, + ICloudEventFormatter formatter = null, + params ICloudEventExtension[] extensions) + { + if (httpRequest.ContentType != null && + httpRequest.ContentType.StartsWith(CloudEvent.MediaType, + StringComparison.InvariantCultureIgnoreCase)) + { + // handle structured mode + if (formatter == null) + { + // if we didn't get a formatter, pick one + if (httpRequest.ContentType.EndsWith(JsonEventFormatter.MediaTypeSuffix, + StringComparison.InvariantCultureIgnoreCase)) + { + formatter = jsonFormatter; + } + else + { + throw new InvalidOperationException("Unsupported CloudEvents encoding"); + } + } + + return formatter.DecodeStructuredEvent(httpRequest.Body, extensions); + } + else + { + CloudEventsSpecVersion version = CloudEventsSpecVersion.Default; + if (httpRequest.Headers[SpecVersionHttpHeader1] != StringValues.Empty) + { + version = CloudEventsSpecVersion.V0_1; + } + + if (httpRequest.Headers[SpecVersionHttpHeader2] != StringValues.Empty) + { + version = httpRequest.Headers[SpecVersionHttpHeader2] == "0.2" + ? CloudEventsSpecVersion.V0_2 + : CloudEventsSpecVersion.Default; + } + + var cloudEvent = new CloudEvent(version, extensions); + var attributes = cloudEvent.GetAttributes(); + foreach (var httpRequestHeader in httpRequest.Headers.Keys) + { + if (httpRequestHeader.Equals(SpecVersionHttpHeader1, + StringComparison.InvariantCultureIgnoreCase) || + httpRequestHeader.Equals(SpecVersionHttpHeader2, StringComparison.InvariantCultureIgnoreCase)) + { + continue; + } + + if (httpRequestHeader.StartsWith(HttpHeaderPrefix, StringComparison.InvariantCultureIgnoreCase)) + { + string headerValue = httpRequest.Headers[httpRequestHeader]; + if (headerValue.StartsWith("{") && headerValue.EndsWith("}") || + headerValue.StartsWith("[") && headerValue.EndsWith("]")) + { + attributes[httpRequestHeader.Substring(3)] = + JsonConvert.DeserializeObject(headerValue); + } + else + { + attributes[httpRequestHeader.Substring(3)] = headerValue; + attributes[httpRequestHeader.Substring(3)] = headerValue; + } + } + } + + cloudEvent.ContentType = httpRequest.ContentType != null + ? new ContentType(httpRequest.ContentType) + : null; + cloudEvent.Data = httpRequest.Body; + return cloudEvent; + } + } + + } +}