Allow setting Content-Length header on the response (#1158)

* Allow setting Content-Length header on the response

* fix?
This commit is contained in:
Stef Heyenrath
2024-08-16 12:14:42 +02:00
committed by GitHub
parent 7e162a00ab
commit 088444024f
5 changed files with 275 additions and 253 deletions
@@ -137,7 +137,6 @@ message HelloReply {
public static void Run() public static void Run()
{ {
RunOnLocal(); RunOnLocal();
return;
var mappingBuilder = new MappingBuilder(); var mappingBuilder = new MappingBuilder();
mappingBuilder mappingBuilder
@@ -308,17 +307,6 @@ message HelloReply {
.RespondWith(Response.Create() .RespondWith(Response.Create()
.WithBody("GraphQL is ok") .WithBody("GraphQL is ok")
); );
//server
// .AddGraphQLSchema("my-graphql", TestSchema, customScalars)
// .Given(Request.Create()
// .WithPath("/graphql2")
// .UsingPost()
// )
// .WithGraphQLSchema("my-graphql")
// .RespondWith(Response.Create()
// .WithBody("GraphQL is ok")
// );
#endif #endif
#if MIMEKIT #if MIMEKIT
@@ -377,6 +365,15 @@ message HelloReply {
.WithHeader("Content-Type", "text/plain") .WithHeader("Content-Type", "text/plain")
); );
server
.Given(Request.Create()
.UsingHead()
.WithPath("/cl")
)
.RespondWith(Response.Create()
.WithHeader("Content-Length", "42")
);
server server
.Given(Request.Create() .Given(Request.Create()
.UsingMethod("GET") .UsingMethod("GET")
@@ -14,12 +14,12 @@ namespace WireMock.Http;
/// </summary> /// </summary>
internal static class HttpKnownHeaderNames internal static class HttpKnownHeaderNames
{ {
// https://docs.microsoft.com/en-us/dotnet/api/system.net.webheadercollection.isrestricted // - https://docs.microsoft.com/en-us/dotnet/api/system.net.webheadercollection.isrestricted
// - ContentLength is allowed per #720
private static readonly string[] RestrictedResponseHeaders = private static readonly string[] RestrictedResponseHeaders =
{ {
Accept, Accept,
Connection, Connection,
ContentLength,
ContentType, ContentType,
Date, // RFC1123Pattern Date, // RFC1123Pattern
Expect, Expect,
@@ -37,12 +37,19 @@ namespace WireMock.Owin.Mappers
private readonly Encoding _utf8NoBom = new UTF8Encoding(false); private readonly Encoding _utf8NoBom = new UTF8Encoding(false);
// https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx // https://msdn.microsoft.com/en-us/library/78h415ay(v=vs.110).aspx
#if !USE_ASPNETCORE private static readonly IDictionary<string, Action<IResponse, bool, WireMockList<string>>> ResponseHeadersToFix =
private static readonly IDictionary<string, Action<IResponse, WireMockList<string>>> ResponseHeadersToFix = new Dictionary<string, Action<IResponse, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase) { new Dictionary<string, Action<IResponse, bool, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase)
#else {
private static readonly IDictionary<string, Action<IResponse, WireMockList<string>>> ResponseHeadersToFix = new Dictionary<string, Action<IResponse, WireMockList<string>>>(StringComparer.OrdinalIgnoreCase) { { HttpKnownHeaderNames.ContentType, (r, _, v) => r.ContentType = v.FirstOrDefault() },
#endif { HttpKnownHeaderNames.ContentLength, (r, hasBody, v) =>
{ HttpKnownHeaderNames.ContentType, (r, v) => r.ContentType = v.FirstOrDefault() } {
// Only set the Content-Length header if the response does not have a body
if (!hasBody && long.TryParse(v.FirstOrDefault(), out var contentLength))
{
r.ContentLength = contentLength;
}
}
}
}; };
/// <summary> /// <summary>
@@ -83,23 +90,21 @@ namespace WireMock.Owin.Mappers
} }
var statusCodeType = responseMessage.StatusCode?.GetType(); var statusCodeType = responseMessage.StatusCode?.GetType();
switch (statusCodeType) if (statusCodeType != null)
{
if (statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum)
{ {
case { } when statusCodeType == typeof(int) || statusCodeType == typeof(int?) || statusCodeType.GetTypeInfo().IsEnum:
response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!); response.StatusCode = MapStatusCode((int)responseMessage.StatusCode!);
break; }
else if (statusCodeType == typeof(string))
case { } when statusCodeType == typeof(string): {
// Note: this case will also match on null // Note: this case will also match on null
int.TryParse(responseMessage.StatusCode as string, out var result); int.TryParse(responseMessage.StatusCode as string, out var statusCodeTypeAsInt);
response.StatusCode = MapStatusCode(result); response.StatusCode = MapStatusCode(statusCodeTypeAsInt);
break; }
default:
break;
} }
SetResponseHeaders(responseMessage, response); SetResponseHeaders(responseMessage, bytes, response);
if (bytes != null) if (bytes != null)
{ {
@@ -160,7 +165,7 @@ namespace WireMock.Owin.Mappers
return null; return null;
} }
private static void SetResponseHeaders(IResponseMessage responseMessage, IResponse response) private static void SetResponseHeaders(IResponseMessage responseMessage, byte[]? bytes, IResponse response)
{ {
// Force setting the Date header (#577) // Force setting the Date header (#577)
AppendResponseHeader( AppendResponseHeader(
@@ -179,11 +184,11 @@ namespace WireMock.Owin.Mappers
var value = item.Value; var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action)) if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{ {
action?.Invoke(response, value); action?.Invoke(response, bytes != null, value);
} }
else else
{ {
// Check if this response header can be added (#148 and #227) // Check if this response header can be added (#148, #227 and #720)
if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName)) if (!HttpKnownHeaderNames.IsRestrictedResponseHeader(headerName))
{ {
AppendResponseHeader(response, headerName, value.ToArray()); AppendResponseHeader(response, headerName, value.ToArray());
@@ -206,7 +211,7 @@ namespace WireMock.Owin.Mappers
var value = item.Value; var value = item.Value;
if (ResponseHeadersToFix.TryGetValue(headerName, out var action)) if (ResponseHeadersToFix.TryGetValue(headerName, out var action))
{ {
action?.Invoke(response, value); action?.Invoke(response, false, value);
} }
else else
{ {
@@ -25,8 +25,8 @@ using Response = Microsoft.AspNetCore.Http.HttpResponse;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
#endif #endif
namespace WireMock.Net.Tests.Owin.Mappers namespace WireMock.Net.Tests.Owin.Mappers;
{
public class OwinResponseMapperTests public class OwinResponseMapperTests
{ {
private static readonly Task CompletedTask = Task.FromResult(true); private static readonly Task CompletedTask = Task.FromResult(true);
@@ -299,4 +299,3 @@ namespace WireMock.Net.Tests.Owin.Mappers
_stream.Verify(s => s.WriteAsync(It.IsAny<byte[]>(), 0, It.Is<int>(count => count >= 0), It.IsAny<CancellationToken>()), Times.Once); _stream.Verify(s => s.WriteAsync(It.IsAny<byte[]>(), 0, It.Is<int>(count => count >= 0), It.IsAny<CancellationToken>()), Times.Once);
} }
} }
}
+22 -1
View File
@@ -14,6 +14,7 @@ using FluentAssertions;
using Newtonsoft.Json; using Newtonsoft.Json;
using NFluent; using NFluent;
using WireMock.Admin.Mappings; using WireMock.Admin.Mappings;
using WireMock.Http;
using WireMock.Matchers; using WireMock.Matchers;
using WireMock.Net.Tests.Serialization; using WireMock.Net.Tests.Serialization;
using WireMock.Net.Xunit; using WireMock.Net.Xunit;
@@ -351,7 +352,7 @@ public partial class WireMockServerTests
//} //}
[Fact] [Fact]
public async Task WireMockServer_Should_exclude_restrictedResponseHeader() public async Task WireMockServer_Should_Exclude_RestrictedResponseHeader()
{ {
// Assign // Assign
string path = $"/foo_{Guid.NewGuid()}"; string path = $"/foo_{Guid.NewGuid()}";
@@ -371,6 +372,26 @@ public partial class WireMockServerTests
server.Stop(); server.Stop();
} }
[Fact] // #720
public async Task WireMockServer_Should_AllowResponseHeaderContentLength_For_HEAD()
{
// Assign
const string length = "42";
var path = $"/cl_{Guid.NewGuid()}";
using var server = WireMockServer.Start();
server
.Given(Request.Create().WithPath(path).UsingHead())
.RespondWith(Response.Create().WithHeader(HttpKnownHeaderNames.ContentLength, length));
// Act
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, path);
var response = await server.CreateClient().SendAsync(httpRequestMessage).ConfigureAwait(false);
// Assert
response.Content.Headers.GetValues(HttpKnownHeaderNames.ContentLength).Should().Contain(length);
}
#if !NET452 && !NET461 #if !NET452 && !NET461
[Theory] [Theory]
[InlineData("TRACE")] [InlineData("TRACE")]