From 39b691c24a409d88dd0f453fc0b56b44db1224b7 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Mon, 11 Jul 2022 10:04:41 +0100 Subject: [PATCH] fix: Use content headers when parsing HTTP requests/responses Fixes #221 Signed-off-by: Jon Skeet --- .../Http/HttpClientExtensions.cs | 16 ++++---- .../Http/HttpClientExtensionsTest.cs | 41 ++++++++++++++++++- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs b/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs index 374bd96..c1965d7 100644 --- a/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs +++ b/src/CloudNative.CloudEvents/Http/HttpClientExtensions.cs @@ -6,7 +6,6 @@ using CloudNative.CloudEvents.Core; using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; @@ -36,7 +35,7 @@ namespace CloudNative.CloudEvents.Http { Validation.CheckNotNull(httpRequestMessage, nameof(httpRequestMessage)); return HasCloudEventsContentType(httpRequestMessage.Content) || - httpRequestMessage.Headers.Contains(HttpUtilities.SpecVersionHttpHeader); + (MaybeGetVersionId(httpRequestMessage.Headers) ?? MaybeGetVersionId(httpRequestMessage.Content?.Headers)) is not null; } /// @@ -48,7 +47,7 @@ namespace CloudNative.CloudEvents.Http { Validation.CheckNotNull(httpResponseMessage, nameof(httpResponseMessage)); return HasCloudEventsContentType(httpResponseMessage.Content) || - httpResponseMessage.Headers.Contains(HttpUtilities.SpecVersionHttpHeader); + (MaybeGetVersionId(httpResponseMessage.Headers) ?? MaybeGetVersionId(httpResponseMessage.Content?.Headers)) is not null; } /// @@ -143,9 +142,7 @@ namespace CloudNative.CloudEvents.Http } else { - string? versionId = headers.Contains(HttpUtilities.SpecVersionHttpHeader) - ? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First() - : null; + string? versionId = MaybeGetVersionId(headers) ?? MaybeGetVersionId(content.Headers); if (versionId is null) { throw new ArgumentException($"Request does not represent a CloudEvent. It has neither a {HttpUtilities.SpecVersionHttpHeader} header, nor a suitable content type.", nameof(paramName)); @@ -154,7 +151,7 @@ namespace CloudNative.CloudEvents.Http ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", paramName); var cloudEvent = new CloudEvent(version, extensionAttributes); - foreach (var header in headers) + foreach (var header in headers.Concat(content.Headers)) { string? attributeName = HttpUtilities.GetAttributeNameFromHeaderName(header.Key); if (attributeName is null || attributeName == CloudEventsSpecVersion.SpecVersionAttribute.Name) @@ -345,5 +342,10 @@ namespace CloudNative.CloudEvents.Http private static bool HasCloudEventsBatchContentType(HttpContent content) => MimeUtilities.IsCloudEventsBatchContentType(content?.Headers?.ContentType?.MediaType); + + private static string? MaybeGetVersionId(HttpHeaders? headers) => + headers is not null && headers.Contains(HttpUtilities.SpecVersionHttpHeader) + ? headers.GetValues(HttpUtilities.SpecVersionHttpHeader).First() + : null; } } \ No newline at end of file diff --git a/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs b/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs index 3707306..8b50729 100644 --- a/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs +++ b/test/CloudNative.CloudEvents.UnitTests/Http/HttpClientExtensionsTest.cs @@ -8,7 +8,6 @@ using CloudNative.CloudEvents.UnitTests; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; @@ -40,6 +39,20 @@ namespace CloudNative.CloudEvents.Http.UnitTests "Structured", new StringContent("content is ignored", Encoding.UTF8, "application/cloudevents+json"), null + }, + { + "Binary with header in content", + new StringContent("header is in the content", Encoding.UTF8, "application/json") + { + Headers = + { + { "ce-specversion", "1.0" }, + { "ce-type", "test-type" }, + { "ce-id", "test-id" }, + { "ce-source", "//test" } + } + }, + null } }; @@ -438,6 +451,32 @@ namespace CloudNative.CloudEvents.Http.UnitTests AssertBatchesEqual(batch, parsedBatch); } + [Theory] + [InlineData(ContentMode.Binary)] + [InlineData(ContentMode.Structured)] + public async Task RoundtripRequest(ContentMode contentMode) + { + var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); + var formatter = new JsonEventFormatter(); + var content = cloudEvent.ToHttpContent(contentMode, formatter); + var request = new HttpRequestMessage { Content = content }; + var parsed = await request.ToCloudEventAsync(formatter); + AssertCloudEventsEqual(cloudEvent, parsed); + } + + [Theory] + [InlineData(ContentMode.Binary)] + [InlineData(ContentMode.Structured)] + public async Task RoundtripResponse(ContentMode contentMode) + { + var cloudEvent = new CloudEvent().PopulateRequiredAttributes(); + var formatter = new JsonEventFormatter(); + var content = cloudEvent.ToHttpContent(contentMode, formatter); + var request = new HttpResponseMessage { Content = content }; + var parsed = await request.ToCloudEventAsync(formatter); + AssertCloudEventsEqual(cloudEvent, parsed); + } + internal static void CopyHeaders(IDictionary? source, HttpHeaders target) { if (source is null)