[Fixes #1713]WebApiCompatShim doesn't work with Transfer Encoding: chunked
This commit is contained in:
parent
af9e9f9113
commit
391816eb71
|
|
@ -51,6 +51,15 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||||
}
|
}
|
||||||
|
|
||||||
var responseHeaders = responseMessage.Headers;
|
var responseHeaders = responseMessage.Headers;
|
||||||
|
|
||||||
|
// Ignore the Transfer-Encoding header if it is just "chunked".
|
||||||
|
// We let the host decide about whether the response should be chunked or not.
|
||||||
|
if (responseHeaders.TransferEncodingChunked == true &&
|
||||||
|
responseHeaders.TransferEncoding.Count == 1)
|
||||||
|
{
|
||||||
|
responseHeaders.TransferEncoding.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var header in responseHeaders)
|
foreach (var header in responseHeaders)
|
||||||
{
|
{
|
||||||
response.Headers.AppendValues(header.Key, header.Value.ToArray());
|
response.Headers.AppendValues(header.Key, header.Value.ToArray());
|
||||||
|
|
@ -59,6 +68,12 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim
|
||||||
if (responseMessage.Content != null)
|
if (responseMessage.Content != null)
|
||||||
{
|
{
|
||||||
var contentHeaders = responseMessage.Content.Headers;
|
var contentHeaders = responseMessage.Content.Headers;
|
||||||
|
|
||||||
|
// Copy the response content headers only after ensuring they are complete.
|
||||||
|
// We ask for Content-Length first because HttpContent lazily computes this
|
||||||
|
// and only afterwards writes the value into the content headers.
|
||||||
|
var unused = contentHeaders.ContentLength;
|
||||||
|
|
||||||
foreach (var header in contentHeaders)
|
foreach (var header in contentHeaders)
|
||||||
{
|
{
|
||||||
response.Headers.AppendValues(header.Key, header.Value.ToArray());
|
response.Headers.AppendValues(header.Key, header.Value.ToArray());
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Formatting;
|
using System.Net.Http.Formatting;
|
||||||
|
|
@ -276,7 +277,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ApiController_ResponseReturned_Chunked()
|
public async Task ApiController_ExplicitChunkedEncoding_IsIgnored()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var server = TestServer.Create(_provider, _app);
|
var server = TestServer.Create(_provider, _app);
|
||||||
|
|
@ -286,19 +287,34 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests
|
||||||
"POST Hello, HttpResponseMessage world!";
|
"POST Hello, HttpResponseMessage world!";
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
var response = await client.PostAsync(
|
var request = new HttpRequestMessage();
|
||||||
"http://localhost/api/Blog/HttpRequestMessage/EchoWithResponseMessageChunked",
|
request.Method = HttpMethod.Post;
|
||||||
new StringContent("Hello, HttpResponseMessage world!"));
|
request.RequestUri = new Uri("http://localhost/api/Blog/HttpRequestMessage/EchoWithResponseMessageChunked");
|
||||||
var content = await response.Content.ReadAsStringAsync();
|
request.Content = new StringContent("Hello, HttpResponseMessage world!");
|
||||||
|
|
||||||
|
// HttpClient buffers the response by default and this would set the Content-Length header and so
|
||||||
|
// this will not provide us accurate information as to whether the server set the header or
|
||||||
|
// the client. So here we explicitly mention to only read the headers and not the body.
|
||||||
|
var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||||
Assert.Equal(expected, content);
|
Assert.NotNull(response.Content);
|
||||||
|
Assert.NotNull(response.Content.Headers.ContentLength);
|
||||||
|
Assert.Null(response.Headers.TransferEncodingChunked);
|
||||||
|
|
||||||
|
// When HttpClient by default reads and buffers the resposne body, it diposes the
|
||||||
|
// response stream for us. But since we are reading the content explicitly, we need
|
||||||
|
// to close it.
|
||||||
|
var responseStream = await response.Content.ReadAsStreamAsync();
|
||||||
|
using (var streamReader = new StreamReader(responseStream))
|
||||||
|
{
|
||||||
|
Assert.Equal(expected, streamReader.ReadToEnd());
|
||||||
|
}
|
||||||
|
|
||||||
IEnumerable<string> values;
|
IEnumerable<string> values;
|
||||||
Assert.True(response.Headers.TryGetValues("X-Test", out values));
|
Assert.True(response.Headers.TryGetValues("X-Test", out values));
|
||||||
Assert.Equal(new string[] { "Hello!" }, values);
|
Assert.Equal(new string[] { "Hello!" }, values);
|
||||||
Assert.Equal(true, response.Headers.TransferEncodingChunked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,12 @@
|
||||||
#if !ASPNETCORE50
|
#if !ASPNETCORE50
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNet.Http;
|
||||||
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
using Microsoft.AspNet.Mvc.WebApiCompatShim;
|
||||||
using Microsoft.AspNet.PipelineCore;
|
using Microsoft.AspNet.PipelineCore;
|
||||||
using Moq;
|
using Moq;
|
||||||
|
|
@ -26,7 +29,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShimTest
|
||||||
streamContent.Protected().Setup("Dispose", true).Verifiable();
|
streamContent.Protected().Setup("Dispose", true).Verifiable();
|
||||||
var httpResponseMessage = new HttpResponseMessage();
|
var httpResponseMessage = new HttpResponseMessage();
|
||||||
httpResponseMessage.Content = streamContent.Object;
|
httpResponseMessage.Content = streamContent.Object;
|
||||||
var outputFormatterContext = GetOutputFormatterContext(httpResponseMessage, typeof(HttpResponseMessage));
|
var outputFormatterContext = GetOutputFormatterContext(
|
||||||
|
httpResponseMessage,
|
||||||
|
typeof(HttpResponseMessage),
|
||||||
|
new DefaultHttpContext());
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await formatter.WriteAsync(outputFormatterContext);
|
await formatter.WriteAsync(outputFormatterContext);
|
||||||
|
|
@ -35,13 +41,110 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShimTest
|
||||||
streamContent.Protected().Verify("Dispose", Times.Once(), true);
|
streamContent.Protected().Verify("Dispose", Times.Once(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType)
|
[Fact]
|
||||||
|
public async Task ExplicitlySet_ChunkedEncodingFlag_IsIgnored()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var httpResponseMessage = new HttpResponseMessage();
|
||||||
|
httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World")));
|
||||||
|
httpResponseMessage.Headers.TransferEncodingChunked = true;
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var formatter = new HttpResponseMessageOutputFormatter();
|
||||||
|
var outputFormatterContext = GetOutputFormatterContext(
|
||||||
|
httpResponseMessage,
|
||||||
|
typeof(HttpResponseMessage),
|
||||||
|
httpContext);
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(outputFormatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(httpContext.Response.Headers.ContainsKey("Transfer-Encoding"));
|
||||||
|
Assert.NotNull(httpContext.Response.ContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExplicitlySet_ChunkedEncodingHeader_IsIgnored()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var transferEncodingHeaderKey = "Transfer-Encoding";
|
||||||
|
var httpResponseMessage = new HttpResponseMessage();
|
||||||
|
httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World")));
|
||||||
|
httpResponseMessage.Headers.Add(transferEncodingHeaderKey, "chunked");
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var formatter = new HttpResponseMessageOutputFormatter();
|
||||||
|
var outputFormatterContext = GetOutputFormatterContext(
|
||||||
|
httpResponseMessage,
|
||||||
|
typeof(HttpResponseMessage),
|
||||||
|
httpContext);
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(outputFormatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.False(httpContext.Response.Headers.ContainsKey(transferEncodingHeaderKey));
|
||||||
|
Assert.NotNull(httpContext.Response.ContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExplicitlySet_MultipleEncodings_ChunkedNotIgnored()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var transferEncodingHeaderKey = "Transfer-Encoding";
|
||||||
|
var httpResponseMessage = new HttpResponseMessage();
|
||||||
|
httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World")));
|
||||||
|
httpResponseMessage.Headers.Add(transferEncodingHeaderKey, new[] { "identity", "chunked" });
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var formatter = new HttpResponseMessageOutputFormatter();
|
||||||
|
var outputFormatterContext = GetOutputFormatterContext(
|
||||||
|
httpResponseMessage,
|
||||||
|
typeof(HttpResponseMessage),
|
||||||
|
httpContext);
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(outputFormatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(httpContext.Response.Headers.ContainsKey(transferEncodingHeaderKey));
|
||||||
|
Assert.Equal(new string[] { "identity", "chunked" },
|
||||||
|
httpContext.Response.Headers.GetValues(transferEncodingHeaderKey));
|
||||||
|
Assert.NotNull(httpContext.Response.ContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task ExplicitlySet_MultipleEncodingsUsingChunkedFlag_ChunkedNotIgnored()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var transferEncodingHeaderKey = "Transfer-Encoding";
|
||||||
|
var httpResponseMessage = new HttpResponseMessage();
|
||||||
|
httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World")));
|
||||||
|
httpResponseMessage.Headers.Add(transferEncodingHeaderKey, new[] { "identity" });
|
||||||
|
httpResponseMessage.Headers.TransferEncodingChunked = true;
|
||||||
|
|
||||||
|
var httpContext = new DefaultHttpContext();
|
||||||
|
var formatter = new HttpResponseMessageOutputFormatter();
|
||||||
|
var outputFormatterContext = GetOutputFormatterContext(
|
||||||
|
httpResponseMessage,
|
||||||
|
typeof(HttpResponseMessage),
|
||||||
|
httpContext);
|
||||||
|
// Act
|
||||||
|
await formatter.WriteAsync(outputFormatterContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.True(httpContext.Response.Headers.ContainsKey(transferEncodingHeaderKey));
|
||||||
|
Assert.Equal(new string[] { "identity", "chunked" },
|
||||||
|
httpContext.Response.Headers.GetValues(transferEncodingHeaderKey));
|
||||||
|
Assert.NotNull(httpContext.Response.ContentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType,
|
||||||
|
HttpContext httpContext)
|
||||||
{
|
{
|
||||||
return new OutputFormatterContext
|
return new OutputFormatterContext
|
||||||
{
|
{
|
||||||
Object = outputValue,
|
Object = outputValue,
|
||||||
DeclaredType = outputType,
|
DeclaredType = outputType,
|
||||||
ActionContext = new ActionContext(new DefaultHttpContext(), routeData: null, actionDescriptor: null)
|
ActionContext = new ActionContext(httpContext, routeData: null, actionDescriptor: null)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue