fix: Use content headers when parsing HTTP requests/responses

Fixes #221

Signed-off-by: Jon Skeet <jonskeet@google.com>
This commit is contained in:
Jon Skeet 2022-07-11 10:04:41 +01:00 committed by Jon Skeet
parent 3ce4aa08f2
commit 39b691c24a
2 changed files with 49 additions and 8 deletions

View File

@ -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;
}
/// <summary>
@ -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;
}
/// <summary>
@ -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;
}
}

View File

@ -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<string, string>? source, HttpHeaders target)
{
if (source is null)