parent
df37d00307
commit
11bae8a112
|
|
@ -66,32 +66,35 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
|
|
||||||
var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO, PreserveExecutionContext);
|
var contextBuilder = new HttpContextBuilder(_application, AllowSynchronousIO, PreserveExecutionContext);
|
||||||
|
|
||||||
var requestContent = request.Content ?? new StreamContent(Stream.Null);
|
var requestContent = request.Content;
|
||||||
|
|
||||||
// Read content from the request HttpContent into a pipe in a background task. This will allow the request
|
if (requestContent != null)
|
||||||
// delegate to start before the request HttpContent is complete. A background task allows duplex streaming scenarios.
|
|
||||||
contextBuilder.SendRequestStream(async writer =>
|
|
||||||
{
|
{
|
||||||
if (requestContent is StreamContent)
|
// Read content from the request HttpContent into a pipe in a background task. This will allow the request
|
||||||
|
// delegate to start before the request HttpContent is complete. A background task allows duplex streaming scenarios.
|
||||||
|
contextBuilder.SendRequestStream(async writer =>
|
||||||
{
|
{
|
||||||
|
if (requestContent is StreamContent)
|
||||||
|
{
|
||||||
// This is odd but required for backwards compat. If StreamContent is passed in then seek to beginning.
|
// This is odd but required for backwards compat. If StreamContent is passed in then seek to beginning.
|
||||||
// This is safe because StreamContent.ReadAsStreamAsync doesn't block. It will return the inner stream.
|
// This is safe because StreamContent.ReadAsStreamAsync doesn't block. It will return the inner stream.
|
||||||
var body = await requestContent.ReadAsStreamAsync();
|
var body = await requestContent.ReadAsStreamAsync();
|
||||||
if (body.CanSeek)
|
if (body.CanSeek)
|
||||||
{
|
{
|
||||||
// This body may have been consumed before, rewind it.
|
// This body may have been consumed before, rewind it.
|
||||||
body.Seek(0, SeekOrigin.Begin);
|
body.Seek(0, SeekOrigin.Begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
await body.CopyToAsync(writer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await requestContent.CopyToAsync(writer.AsStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
await body.CopyToAsync(writer);
|
await writer.CompleteAsync();
|
||||||
}
|
});
|
||||||
else
|
}
|
||||||
{
|
|
||||||
await requestContent.CopyToAsync(writer.AsStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
await writer.CompleteAsync();
|
|
||||||
});
|
|
||||||
|
|
||||||
contextBuilder.Configure((context, reader) =>
|
contextBuilder.Configure((context, reader) =>
|
||||||
{
|
{
|
||||||
|
|
@ -110,6 +113,39 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
|
|
||||||
req.Scheme = request.RequestUri.Scheme;
|
req.Scheme = request.RequestUri.Scheme;
|
||||||
|
|
||||||
|
var canHaveBody = false;
|
||||||
|
if (requestContent != null)
|
||||||
|
{
|
||||||
|
canHaveBody = true;
|
||||||
|
// Chunked takes precedence over Content-Length, don't create a request with both Content-Length and chunked.
|
||||||
|
if (request.Headers.TransferEncodingChunked != true)
|
||||||
|
{
|
||||||
|
// Reading the ContentLength will add it to the Headers‼
|
||||||
|
// https://github.com/dotnet/runtime/blob/874399ab15e47c2b4b7c6533cc37d27d47cb5242/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpContentHeaders.cs#L68-L87
|
||||||
|
var contentLength = requestContent.Headers.ContentLength;
|
||||||
|
if (!contentLength.HasValue && request.Version == HttpVersion.Version11)
|
||||||
|
{
|
||||||
|
// HTTP/1.1 requests with a body require either Content-Length or Transfer-Encoding: chunked.
|
||||||
|
request.Headers.TransferEncodingChunked = true;
|
||||||
|
}
|
||||||
|
else if (contentLength == 0)
|
||||||
|
{
|
||||||
|
canHaveBody = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var header in requestContent.Headers)
|
||||||
|
{
|
||||||
|
req.Headers.Append(header.Key, header.Value.ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canHaveBody)
|
||||||
|
{
|
||||||
|
req.Body = new AsyncStreamWrapper(reader.AsStream(), () => contextBuilder.AllowSynchronousIO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.Features.Set<IHttpRequestBodyDetectionFeature>(new RequestBodyDetectionFeature(canHaveBody));
|
||||||
|
|
||||||
foreach (var header in request.Headers)
|
foreach (var header in request.Headers)
|
||||||
{
|
{
|
||||||
// User-Agent is a space delineated single line header but HttpRequestHeaders parses it as multiple elements.
|
// User-Agent is a space delineated single line header but HttpRequestHeaders parses it as multiple elements.
|
||||||
|
|
@ -141,17 +177,6 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
req.PathBase = _pathBase;
|
req.PathBase = _pathBase;
|
||||||
}
|
}
|
||||||
req.QueryString = QueryString.FromUriComponent(request.RequestUri);
|
req.QueryString = QueryString.FromUriComponent(request.RequestUri);
|
||||||
|
|
||||||
// Reading the ContentLength will add it to the Headers‼
|
|
||||||
// https://github.com/dotnet/runtime/blob/874399ab15e47c2b4b7c6533cc37d27d47cb5242/src/libraries/System.Net.Http/src/System/Net/Http/Headers/HttpContentHeaders.cs#L68-L87
|
|
||||||
_ = requestContent.Headers.ContentLength;
|
|
||||||
|
|
||||||
foreach (var header in requestContent.Headers)
|
|
||||||
{
|
|
||||||
req.Headers.Append(header.Key, header.Value.ToArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Body = new AsyncStreamWrapper(reader.AsStream(), () => contextBuilder.AllowSynchronousIO);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var response = new HttpResponseMessage();
|
var response = new HttpResponseMessage();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.TestHost
|
||||||
|
{
|
||||||
|
internal class RequestBodyDetectionFeature : IHttpRequestBodyDetectionFeature
|
||||||
|
{
|
||||||
|
public RequestBodyDetectionFeature(bool canHaveBody)
|
||||||
|
{
|
||||||
|
CanHaveBody = canHaveBody;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanHaveBody { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -111,7 +111,9 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
var contentBytes = Encoding.UTF8.GetBytes("This is a content!");
|
var contentBytes = Encoding.UTF8.GetBytes("This is a content!");
|
||||||
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
||||||
{
|
{
|
||||||
|
Assert.True(context.Request.CanHaveBody());
|
||||||
Assert.Equal(contentBytes.LongLength, context.Request.ContentLength);
|
Assert.Equal(contentBytes.LongLength, context.Request.ContentLength);
|
||||||
|
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}));
|
}));
|
||||||
|
|
@ -122,11 +124,13 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task ContentLengthWithNoBodyWorks()
|
public Task ContentLengthNotPresentWithNoBody()
|
||||||
{
|
{
|
||||||
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
||||||
{
|
{
|
||||||
Assert.Equal(0, context.Request.ContentLength);
|
Assert.False(context.Request.CanHaveBody());
|
||||||
|
Assert.Null(context.Request.ContentLength);
|
||||||
|
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}));
|
}));
|
||||||
|
|
@ -136,11 +140,13 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public Task ContentLengthWithChunkedTransferEncodingWorks()
|
public Task ContentLengthWithImplicitChunkedTransferEncodingWorks()
|
||||||
{
|
{
|
||||||
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
||||||
{
|
{
|
||||||
|
Assert.True(context.Request.CanHaveBody());
|
||||||
Assert.Null(context.Request.ContentLength);
|
Assert.Null(context.Request.ContentLength);
|
||||||
|
Assert.Equal("chunked", context.Request.Headers[HeaderNames.TransferEncoding]);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}));
|
}));
|
||||||
|
|
@ -150,6 +156,26 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
return httpClient.PostAsync("http://example.com", new UnlimitedContent());
|
return httpClient.PostAsync("http://example.com", new UnlimitedContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public Task ContentLengthWithExplicitChunkedTransferEncodingWorks()
|
||||||
|
{
|
||||||
|
var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
|
||||||
|
{
|
||||||
|
Assert.True(context.Request.CanHaveBody());
|
||||||
|
Assert.Null(context.Request.ContentLength);
|
||||||
|
Assert.Equal("chunked", context.Request.Headers[HeaderNames.TransferEncoding]);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}));
|
||||||
|
|
||||||
|
var httpClient = new HttpClient(handler);
|
||||||
|
httpClient.DefaultRequestHeaders.TransferEncodingChunked = true;
|
||||||
|
var contentBytes = Encoding.UTF8.GetBytes("This is a content!");
|
||||||
|
var content = new ByteArrayContent(contentBytes);
|
||||||
|
|
||||||
|
return httpClient.PostAsync("http://example.com", content);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ServerTrailersSetOnResponseAfterContentRead()
|
public async Task ServerTrailersSetOnResponseAfterContentRead()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
Assert.Equal("/A/Path", context.Request.PathBase.Value);
|
Assert.Equal("/A/Path", context.Request.PathBase.Value);
|
||||||
Assert.Equal("/and/file.txt", context.Request.Path.Value);
|
Assert.Equal("/and/file.txt", context.Request.Path.Value);
|
||||||
Assert.Equal("?and=query", context.Request.QueryString.Value);
|
Assert.Equal("?and=query", context.Request.QueryString.Value);
|
||||||
|
Assert.Null(context.Request.CanHaveBody());
|
||||||
Assert.NotNull(context.Request.Body);
|
Assert.NotNull(context.Request.Body);
|
||||||
Assert.NotNull(context.Request.Headers);
|
Assert.NotNull(context.Request.Headers);
|
||||||
Assert.NotNull(context.Response.Headers);
|
Assert.NotNull(context.Response.Headers);
|
||||||
|
|
|
||||||
|
|
@ -228,6 +228,7 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
|
|
||||||
var stream = new ThrowOnDisposeStream();
|
var stream = new ThrowOnDisposeStream();
|
||||||
stream.Write(Encoding.ASCII.GetBytes("Hello World"));
|
stream.Write(Encoding.ASCII.GetBytes("Hello World"));
|
||||||
|
stream.Seek(0, SeekOrigin.Begin);
|
||||||
var response = await server.CreateClient().PostAsync("/", new StreamContent(stream));
|
var response = await server.CreateClient().PostAsync("/", new StreamContent(stream));
|
||||||
Assert.True(response.IsSuccessStatusCode);
|
Assert.True(response.IsSuccessStatusCode);
|
||||||
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
|
Assert.Equal("Hello World", await response.Content.ReadAsStringAsync());
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.TestHost
|
namespace Microsoft.AspNetCore.TestHost
|
||||||
|
|
@ -14,5 +16,10 @@ namespace Microsoft.AspNetCore.TestHost
|
||||||
internal static Task<T> WithTimeout<T>(this Task<T> task) => task.TimeoutAfter(DefaultTimeout);
|
internal static Task<T> WithTimeout<T>(this Task<T> task) => task.TimeoutAfter(DefaultTimeout);
|
||||||
|
|
||||||
internal static Task WithTimeout(this Task task) => task.TimeoutAfter(DefaultTimeout);
|
internal static Task WithTimeout(this Task task) => task.TimeoutAfter(DefaultTimeout);
|
||||||
|
|
||||||
|
internal static bool? CanHaveBody(this HttpRequest request)
|
||||||
|
{
|
||||||
|
return request.HttpContext.Features.Get<IHttpRequestBodyDetectionFeature>()?.CanHaveBody;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http.Features
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Used to indicate if the request can have a body.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHttpRequestBodyDetectionFeature
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates if the request can have a body.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This returns true when:
|
||||||
|
/// - It's an HTTP/1.x request with a non-zero Content-Length or a 'Transfer-Encoding: chunked' header.
|
||||||
|
/// - It's an HTTP/2 request that did not set the END_STREAM flag on the initial headers frame.
|
||||||
|
/// The final request body length may still be zero for the chunked or HTTP/2 scenarios.
|
||||||
|
///
|
||||||
|
/// This returns false when:
|
||||||
|
/// - It's an HTTP/1.x request with no Content-Length or 'Transfer-Encoding: chunked' header, or the Content-Length is 0.
|
||||||
|
/// - It's an HTTP/1.x request with Connection: Upgrade (e.g. WebSockets). There is no HTTP request body for these requests and
|
||||||
|
/// no data should be received until after the upgrade.
|
||||||
|
/// - It's an HTTP/2 request that set END_STREAM on the initial headers frame.
|
||||||
|
/// When false, the request body should never return data.
|
||||||
|
/// </remarks>
|
||||||
|
bool CanHaveBody { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
internal class FeatureContext :
|
internal class FeatureContext :
|
||||||
IHttpRequestFeature,
|
IHttpRequestFeature,
|
||||||
|
IHttpRequestBodyDetectionFeature,
|
||||||
IHttpConnectionFeature,
|
IHttpConnectionFeature,
|
||||||
IHttpResponseFeature,
|
IHttpResponseFeature,
|
||||||
IHttpResponseBodyFeature,
|
IHttpResponseBodyFeature,
|
||||||
|
|
@ -212,6 +213,8 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
set { _scheme = value; }
|
set { _scheme = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IHttpRequestBodyDetectionFeature.CanHaveBody => Request.HasEntityBody;
|
||||||
|
|
||||||
IPAddress IHttpConnectionFeature.LocalIpAddress
|
IPAddress IHttpConnectionFeature.LocalIpAddress
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
{
|
{
|
||||||
if (_contentBoundaryType == BoundaryType.None)
|
if (_contentBoundaryType == BoundaryType.None)
|
||||||
{
|
{
|
||||||
|
// Note Http.Sys adds the Transfer-Encoding: chunked header to HTTP/2 requests with bodies for back compat.
|
||||||
string transferEncoding = Headers[HttpKnownHeaderNames.TransferEncoding];
|
string transferEncoding = Headers[HttpKnownHeaderNames.TransferEncoding];
|
||||||
if (string.Equals("chunked", transferEncoding?.Trim(), StringComparison.OrdinalIgnoreCase))
|
if (string.Equals("chunked", transferEncoding?.Trim(), StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
private static readonly Dictionary<Type, Func<FeatureContext, object>> _featureFuncLookup = new Dictionary<Type, Func<FeatureContext, object>>()
|
private static readonly Dictionary<Type, Func<FeatureContext, object>> _featureFuncLookup = new Dictionary<Type, Func<FeatureContext, object>>()
|
||||||
{
|
{
|
||||||
{ typeof(IHttpRequestFeature), _identityFunc },
|
{ typeof(IHttpRequestFeature), _identityFunc },
|
||||||
|
{ typeof(IHttpRequestBodyDetectionFeature), _identityFunc },
|
||||||
{ typeof(IHttpConnectionFeature), _identityFunc },
|
{ typeof(IHttpConnectionFeature), _identityFunc },
|
||||||
{ typeof(IHttpResponseFeature), _identityFunc },
|
{ typeof(IHttpResponseFeature), _identityFunc },
|
||||||
{ typeof(IHttpResponseBodyFeature), _identityFunc },
|
{ typeof(IHttpResponseBodyFeature), _identityFunc },
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,12 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
@ -31,6 +34,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
|
||||||
using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext =>
|
using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext =>
|
||||||
{
|
{
|
||||||
// Default 200
|
// Default 200
|
||||||
|
Assert.False(httpContext.Request.CanHaveBody());
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -64,6 +68,233 @@ namespace Microsoft.AspNetCore.Server.HttpSys.FunctionalTests
|
||||||
.Build().RunAsync();
|
.Build().RunAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
[InlineData("POST")]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")]
|
||||||
|
public async Task RequestWithoutData_LengthRequired_Rejected(string method)
|
||||||
|
{
|
||||||
|
using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext =>
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
});
|
||||||
|
|
||||||
|
await new HostBuilder()
|
||||||
|
.UseHttp2Cat(address, async h2Connection =>
|
||||||
|
{
|
||||||
|
await h2Connection.InitializeConnectionAsync();
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1.");
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, method),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "https"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
|
||||||
|
await h2Connection.StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
|
await h2Connection.ReceiveHeadersAsync(1, decodedHeaders =>
|
||||||
|
{
|
||||||
|
Assert.Equal("411", decodedHeaders[HeaderNames.Status]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 344);
|
||||||
|
dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0);
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Connection stopped.");
|
||||||
|
})
|
||||||
|
.Build().RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
[InlineData("GET")]
|
||||||
|
[InlineData("HEAD")]
|
||||||
|
[InlineData("PATCH")]
|
||||||
|
[InlineData("DELETE")]
|
||||||
|
[InlineData("CUSTOM")]
|
||||||
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")]
|
||||||
|
public async Task RequestWithoutData_Success(string method)
|
||||||
|
{
|
||||||
|
using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext =>
|
||||||
|
{
|
||||||
|
Assert.True(HttpMethods.Equals(method, httpContext.Request.Method));
|
||||||
|
Assert.False(httpContext.Request.CanHaveBody());
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
Assert.False(httpContext.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
});
|
||||||
|
|
||||||
|
await new HostBuilder()
|
||||||
|
.UseHttp2Cat(address, async h2Connection =>
|
||||||
|
{
|
||||||
|
await h2Connection.InitializeConnectionAsync();
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1.");
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, method),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "https"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
|
||||||
|
await h2Connection.StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
|
await h2Connection.ReceiveHeadersAsync(1, decodedHeaders =>
|
||||||
|
{
|
||||||
|
Assert.Equal("200", decodedHeaders[HeaderNames.Status]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
if (Environment.OSVersion.Version >= Win10_Regressed_DataFrame)
|
||||||
|
{
|
||||||
|
// TODO: Remove when the regression is fixed.
|
||||||
|
// https://github.com/dotnet/aspnetcore/issues/23164#issuecomment-652646163
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 0);
|
||||||
|
|
||||||
|
dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
}
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0);
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Connection stopped.");
|
||||||
|
})
|
||||||
|
.Build().RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
[InlineData("GET")]
|
||||||
|
// [InlineData("HEAD")] Reset with code HTTP_1_1_REQUIRED
|
||||||
|
[InlineData("POST")]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[InlineData("PATCH")]
|
||||||
|
[InlineData("DELETE")]
|
||||||
|
[InlineData("CUSTOM")]
|
||||||
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")]
|
||||||
|
public async Task RequestWithDataAndContentLength_Success(string method)
|
||||||
|
{
|
||||||
|
using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext =>
|
||||||
|
{
|
||||||
|
Assert.True(HttpMethods.Equals(method, httpContext.Request.Method));
|
||||||
|
Assert.True(httpContext.Request.CanHaveBody());
|
||||||
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
|
Assert.False(httpContext.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
|
||||||
|
return httpContext.Request.Body.CopyToAsync(httpContext.Response.Body);
|
||||||
|
});
|
||||||
|
|
||||||
|
await new HostBuilder()
|
||||||
|
.UseHttp2Cat(address, async h2Connection =>
|
||||||
|
{
|
||||||
|
await h2Connection.InitializeConnectionAsync();
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1.");
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, method),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "https"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.ContentLength, "11"),
|
||||||
|
};
|
||||||
|
|
||||||
|
await h2Connection.StartStreamAsync(1, headers, endStream: false);
|
||||||
|
|
||||||
|
await h2Connection.SendDataAsync(1, Encoding.UTF8.GetBytes("Hello World"), endStream: true);
|
||||||
|
|
||||||
|
// Http.Sys no longer sends a window update here on later versions.
|
||||||
|
if (Environment.OSVersion.Version < new Version(10, 0, 19041, 0))
|
||||||
|
{
|
||||||
|
var windowUpdate = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Assert.Equal(Http2FrameType.WINDOW_UPDATE, windowUpdate.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
await h2Connection.ReceiveHeadersAsync(1, decodedHeaders =>
|
||||||
|
{
|
||||||
|
Assert.Equal("200", decodedHeaders[HeaderNames.Status]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 11);
|
||||||
|
Assert.Equal("Hello World", Encoding.UTF8.GetString(dataFrame.Payload.Span));
|
||||||
|
|
||||||
|
dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0);
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Connection stopped.");
|
||||||
|
})
|
||||||
|
.Build().RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ConditionalTheory]
|
||||||
|
[InlineData("GET")]
|
||||||
|
// [InlineData("HEAD")] Reset with code HTTP_1_1_REQUIRED
|
||||||
|
[InlineData("POST")]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[InlineData("PATCH")]
|
||||||
|
[InlineData("DELETE")]
|
||||||
|
[InlineData("CUSTOM")]
|
||||||
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")]
|
||||||
|
public async Task RequestWithDataAndNoContentLength_Success(string method)
|
||||||
|
{
|
||||||
|
using var server = Utilities.CreateDynamicHttpsServer(out var address, httpContext =>
|
||||||
|
{
|
||||||
|
Assert.True(HttpMethods.Equals(method, httpContext.Request.Method));
|
||||||
|
Assert.True(httpContext.Request.CanHaveBody());
|
||||||
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
|
// The client didn't send this header, Http.Sys added it for back compat with HTTP/1.1.
|
||||||
|
Assert.Equal("chunked", httpContext.Request.Headers[HeaderNames.TransferEncoding]);
|
||||||
|
return httpContext.Request.Body.CopyToAsync(httpContext.Response.Body);
|
||||||
|
});
|
||||||
|
|
||||||
|
await new HostBuilder()
|
||||||
|
.UseHttp2Cat(address, async h2Connection =>
|
||||||
|
{
|
||||||
|
await h2Connection.InitializeConnectionAsync();
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Initialized http2 connection. Starting stream 1.");
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, method),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "https"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
|
||||||
|
await h2Connection.StartStreamAsync(1, headers, endStream: false);
|
||||||
|
|
||||||
|
await h2Connection.SendDataAsync(1, Encoding.UTF8.GetBytes("Hello World"), endStream: true);
|
||||||
|
|
||||||
|
// Http.Sys no longer sends a window update here on later versions.
|
||||||
|
if (Environment.OSVersion.Version < new Version(10, 0, 19041, 0))
|
||||||
|
{
|
||||||
|
var windowUpdate = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Assert.Equal(Http2FrameType.WINDOW_UPDATE, windowUpdate.Type);
|
||||||
|
}
|
||||||
|
|
||||||
|
await h2Connection.ReceiveHeadersAsync(1, decodedHeaders =>
|
||||||
|
{
|
||||||
|
Assert.Equal("200", decodedHeaders[HeaderNames.Status]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: false, length: 11);
|
||||||
|
Assert.Equal("Hello World", Encoding.UTF8.GetString(dataFrame.Payload.Span));
|
||||||
|
|
||||||
|
dataFrame = await h2Connection.ReceiveFrameAsync();
|
||||||
|
Http2Utilities.VerifyDataFrame(dataFrame, 1, endOfStream: true, length: 0);
|
||||||
|
|
||||||
|
h2Connection.Logger.LogInformation("Connection stopped.");
|
||||||
|
})
|
||||||
|
.Build().RunAsync();
|
||||||
|
}
|
||||||
|
|
||||||
[ConditionalFact]
|
[ConditionalFact]
|
||||||
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")]
|
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win10, SkipReason = "Http2 requires Win10")]
|
||||||
public async Task ResponseWithData_Success()
|
public async Task ResponseWithData_Success()
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
Assert.NotNull(feature);
|
Assert.NotNull(feature);
|
||||||
Assert.False(feature.IsReadOnly);
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.True(httpContext.Request.CanHaveBody());
|
||||||
Assert.Equal(11, httpContext.Request.ContentLength);
|
Assert.Equal(11, httpContext.Request.ContentLength);
|
||||||
byte[] input = new byte[100];
|
byte[] input = new byte[100];
|
||||||
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
|
|
@ -114,6 +115,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
var feature = httpContext.Features.Get<IHttpMaxRequestBodySizeFeature>();
|
||||||
Assert.NotNull(feature);
|
Assert.NotNull(feature);
|
||||||
Assert.False(feature.IsReadOnly);
|
Assert.False(feature.IsReadOnly);
|
||||||
|
Assert.True(httpContext.Request.CanHaveBody());
|
||||||
Assert.Null(httpContext.Request.ContentLength);
|
Assert.Null(httpContext.Request.ContentLength);
|
||||||
byte[] input = new byte[100];
|
byte[] input = new byte[100];
|
||||||
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using System.Net.Sockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Http.Features;
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -23,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
string address;
|
string address;
|
||||||
using (Utilities.CreateHttpServer(out address, httpContext =>
|
using (Utilities.CreateHttpServer(out address, httpContext =>
|
||||||
{
|
{
|
||||||
|
Assert.True(httpContext.Request.CanHaveBody());
|
||||||
byte[] input = new byte[100];
|
byte[] input = new byte[100];
|
||||||
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
|
||||||
int read = httpContext.Request.Body.Read(input, 0, input.Length);
|
int read = httpContext.Request.Body.Read(input, 0, input.Length);
|
||||||
|
|
@ -42,6 +44,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
string address;
|
string address;
|
||||||
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
using (Utilities.CreateHttpServer(out address, async httpContext =>
|
||||||
{
|
{
|
||||||
|
Assert.True(httpContext.Request.CanHaveBody());
|
||||||
byte[] input = new byte[100];
|
byte[] input = new byte[100];
|
||||||
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
int read = await httpContext.Request.Body.ReadAsync(input, 0, input.Length);
|
||||||
httpContext.Response.ContentLength = read;
|
httpContext.Response.ContentLength = read;
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
Assert.Equal("/basepath/SomePath?SomeQuery", requestInfo.RawTarget);
|
Assert.Equal("/basepath/SomePath?SomeQuery", requestInfo.RawTarget);
|
||||||
Assert.Equal("HTTP/1.1", requestInfo.Protocol);
|
Assert.Equal("HTTP/1.1", requestInfo.Protocol);
|
||||||
|
|
||||||
|
Assert.False(httpContext.Request.CanHaveBody());
|
||||||
var connectionInfo = httpContext.Features.Get<IHttpConnectionFeature>();
|
var connectionInfo = httpContext.Features.Get<IHttpConnectionFeature>();
|
||||||
Assert.Equal("::1", connectionInfo.RemoteIpAddress.ToString());
|
Assert.Equal("::1", connectionInfo.RemoteIpAddress.ToString());
|
||||||
Assert.NotEqual(0, connectionInfo.RemotePort);
|
Assert.NotEqual(0, connectionInfo.RemotePort);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Server;
|
using Microsoft.AspNetCore.Hosting.Server;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
using Microsoft.AspNetCore.Hosting.Server.Features;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
using Microsoft.AspNetCore.Testing;
|
using Microsoft.AspNetCore.Testing;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
|
|
@ -170,5 +171,10 @@ namespace Microsoft.AspNetCore.Server.HttpSys
|
||||||
internal static Task WithTimeout(this Task task) => task.TimeoutAfter(DefaultTimeout);
|
internal static Task WithTimeout(this Task task) => task.TimeoutAfter(DefaultTimeout);
|
||||||
|
|
||||||
internal static Task<T> WithTimeout<T>(this Task<T> task) => task.TimeoutAfter(DefaultTimeout);
|
internal static Task<T> WithTimeout<T>(this Task<T> task) => task.TimeoutAfter(DefaultTimeout);
|
||||||
|
|
||||||
|
internal static bool? CanHaveBody(this HttpRequest request)
|
||||||
|
{
|
||||||
|
return request.HttpContext.Features.Get<IHttpRequestBodyDetectionFeature>()?.CanHaveBody;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ using Microsoft.Net.Http.Headers;
|
||||||
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
internal partial class HttpProtocol : IHttpRequestFeature,
|
internal partial class HttpProtocol : IHttpRequestFeature,
|
||||||
|
IHttpRequestBodyDetectionFeature,
|
||||||
IHttpResponseFeature,
|
IHttpResponseFeature,
|
||||||
IHttpResponseBodyFeature,
|
IHttpResponseBodyFeature,
|
||||||
IRequestBodyPipeFeature,
|
IRequestBodyPipeFeature,
|
||||||
|
|
@ -121,6 +122,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IHttpRequestBodyDetectionFeature.CanHaveBody => _bodyControl.CanHaveBody;
|
||||||
|
|
||||||
bool IHttpRequestTrailersFeature.Available => RequestTrailersAvailable;
|
bool IHttpRequestTrailersFeature.Available => RequestTrailersAvailable;
|
||||||
|
|
||||||
IHeaderDictionary IHttpRequestTrailersFeature.Trailers
|
IHeaderDictionary IHttpRequestTrailersFeature.Trailers
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
internal partial class HttpProtocol : IFeatureCollection
|
internal partial class HttpProtocol : IFeatureCollection
|
||||||
{
|
{
|
||||||
private object _currentIHttpRequestFeature;
|
private object _currentIHttpRequestFeature;
|
||||||
|
private object _currentIHttpRequestBodyDetectionFeature;
|
||||||
private object _currentIHttpResponseFeature;
|
private object _currentIHttpResponseFeature;
|
||||||
private object _currentIHttpResponseBodyFeature;
|
private object _currentIHttpResponseBodyFeature;
|
||||||
private object _currentIRequestBodyPipeFeature;
|
private object _currentIRequestBodyPipeFeature;
|
||||||
|
|
@ -48,6 +49,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
private void FastReset()
|
private void FastReset()
|
||||||
{
|
{
|
||||||
_currentIHttpRequestFeature = this;
|
_currentIHttpRequestFeature = this;
|
||||||
|
_currentIHttpRequestBodyDetectionFeature = this;
|
||||||
_currentIHttpResponseFeature = this;
|
_currentIHttpResponseFeature = this;
|
||||||
_currentIHttpResponseBodyFeature = this;
|
_currentIHttpResponseBodyFeature = this;
|
||||||
_currentIRequestBodyPipeFeature = this;
|
_currentIRequestBodyPipeFeature = this;
|
||||||
|
|
@ -133,6 +135,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
feature = _currentIHttpRequestFeature;
|
feature = _currentIHttpRequestFeature;
|
||||||
}
|
}
|
||||||
|
else if (key == typeof(IHttpRequestBodyDetectionFeature))
|
||||||
|
{
|
||||||
|
feature = _currentIHttpRequestBodyDetectionFeature;
|
||||||
|
}
|
||||||
else if (key == typeof(IHttpResponseFeature))
|
else if (key == typeof(IHttpResponseFeature))
|
||||||
{
|
{
|
||||||
feature = _currentIHttpResponseFeature;
|
feature = _currentIHttpResponseFeature;
|
||||||
|
|
@ -253,6 +259,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
_currentIHttpRequestFeature = value;
|
_currentIHttpRequestFeature = value;
|
||||||
}
|
}
|
||||||
|
else if (key == typeof(IHttpRequestBodyDetectionFeature))
|
||||||
|
{
|
||||||
|
_currentIHttpRequestBodyDetectionFeature = value;
|
||||||
|
}
|
||||||
else if (key == typeof(IHttpResponseFeature))
|
else if (key == typeof(IHttpResponseFeature))
|
||||||
{
|
{
|
||||||
_currentIHttpResponseFeature = value;
|
_currentIHttpResponseFeature = value;
|
||||||
|
|
@ -371,6 +381,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
feature = (TFeature)_currentIHttpRequestFeature;
|
feature = (TFeature)_currentIHttpRequestFeature;
|
||||||
}
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IHttpRequestBodyDetectionFeature))
|
||||||
|
{
|
||||||
|
feature = (TFeature)_currentIHttpRequestBodyDetectionFeature;
|
||||||
|
}
|
||||||
else if (typeof(TFeature) == typeof(IHttpResponseFeature))
|
else if (typeof(TFeature) == typeof(IHttpResponseFeature))
|
||||||
{
|
{
|
||||||
feature = (TFeature)_currentIHttpResponseFeature;
|
feature = (TFeature)_currentIHttpResponseFeature;
|
||||||
|
|
@ -495,6 +509,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
_currentIHttpRequestFeature = feature;
|
_currentIHttpRequestFeature = feature;
|
||||||
}
|
}
|
||||||
|
else if (typeof(TFeature) == typeof(IHttpRequestBodyDetectionFeature))
|
||||||
|
{
|
||||||
|
_currentIHttpRequestBodyDetectionFeature = feature;
|
||||||
|
}
|
||||||
else if (typeof(TFeature) == typeof(IHttpResponseFeature))
|
else if (typeof(TFeature) == typeof(IHttpResponseFeature))
|
||||||
{
|
{
|
||||||
_currentIHttpResponseFeature = feature;
|
_currentIHttpResponseFeature = feature;
|
||||||
|
|
@ -611,6 +629,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestFeature), _currentIHttpRequestFeature);
|
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestFeature), _currentIHttpRequestFeature);
|
||||||
}
|
}
|
||||||
|
if (_currentIHttpRequestBodyDetectionFeature != null)
|
||||||
|
{
|
||||||
|
yield return new KeyValuePair<Type, object>(typeof(IHttpRequestBodyDetectionFeature), _currentIHttpRequestBodyDetectionFeature);
|
||||||
|
}
|
||||||
if (_currentIHttpResponseFeature != null)
|
if (_currentIHttpResponseFeature != null)
|
||||||
{
|
{
|
||||||
yield return new KeyValuePair<Type, object>(typeof(IHttpResponseFeature), _currentIHttpResponseFeature);
|
yield return new KeyValuePair<Type, object>(typeof(IHttpResponseFeature), _currentIHttpResponseFeature);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
_upgradeStream = new HttpUpgradeStream(_request, _response);
|
_upgradeStream = new HttpUpgradeStream(_request, _response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanHaveBody { get; private set; }
|
||||||
|
|
||||||
public Stream Upgrade()
|
public Stream Upgrade()
|
||||||
{
|
{
|
||||||
// causes writes to context.Response.Body to throw
|
// causes writes to context.Response.Body to throw
|
||||||
|
|
@ -46,6 +48,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
|
||||||
|
|
||||||
public (Stream request, Stream response, PipeReader reader, PipeWriter writer) Start(MessageBody body)
|
public (Stream request, Stream response, PipeReader reader, PipeWriter writer) Start(MessageBody body)
|
||||||
{
|
{
|
||||||
|
CanHaveBody = !body.IsEmpty;
|
||||||
_requestReader.StartAcceptingReads(body);
|
_requestReader.StartAcceptingReads(body);
|
||||||
_emptyRequestReader.StartAcceptingReads(MessageBody.ZeroContentLengthClose);
|
_emptyRequestReader.StartAcceptingReads(MessageBody.ZeroContentLengthClose);
|
||||||
_responseWriter.StartAcceptingWrites();
|
_responseWriter.StartAcceptingWrites();
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public void FeaturesSetByTypeSameAsGeneric()
|
public void FeaturesSetByTypeSameAsGeneric()
|
||||||
{
|
{
|
||||||
_collection[typeof(IHttpRequestFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpRequestFeature)] = CreateHttp1Connection();
|
||||||
|
_collection[typeof(IHttpRequestBodyDetectionFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IHttpResponseFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpResponseFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IHttpResponseBodyFeature)] = CreateHttp1Connection();
|
_collection[typeof(IHttpResponseBodyFeature)] = CreateHttp1Connection();
|
||||||
_collection[typeof(IRequestBodyPipeFeature)] = CreateHttp1Connection();
|
_collection[typeof(IRequestBodyPipeFeature)] = CreateHttp1Connection();
|
||||||
|
|
@ -139,6 +140,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
public void FeaturesSetByGenericSameAsByType()
|
public void FeaturesSetByGenericSameAsByType()
|
||||||
{
|
{
|
||||||
_collection.Set<IHttpRequestFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpRequestFeature>(CreateHttp1Connection());
|
||||||
|
_collection.Set<IHttpRequestBodyDetectionFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IHttpResponseFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpResponseFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IHttpResponseBodyFeature>(CreateHttp1Connection());
|
_collection.Set<IHttpResponseBodyFeature>(CreateHttp1Connection());
|
||||||
_collection.Set<IRequestBodyPipeFeature>(CreateHttp1Connection());
|
_collection.Set<IRequestBodyPipeFeature>(CreateHttp1Connection());
|
||||||
|
|
@ -194,6 +196,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
private void CompareGenericGetterToIndexer()
|
private void CompareGenericGetterToIndexer()
|
||||||
{
|
{
|
||||||
Assert.Same(_collection.Get<IHttpRequestFeature>(), _collection[typeof(IHttpRequestFeature)]);
|
Assert.Same(_collection.Get<IHttpRequestFeature>(), _collection[typeof(IHttpRequestFeature)]);
|
||||||
|
Assert.Same(_collection.Get<IHttpRequestBodyDetectionFeature>(), _collection[typeof(IHttpRequestBodyDetectionFeature)]);
|
||||||
Assert.Same(_collection.Get<IHttpResponseFeature>(), _collection[typeof(IHttpResponseFeature)]);
|
Assert.Same(_collection.Get<IHttpResponseFeature>(), _collection[typeof(IHttpResponseFeature)]);
|
||||||
Assert.Same(_collection.Get<IHttpResponseBodyFeature>(), _collection[typeof(IHttpResponseBodyFeature)]);
|
Assert.Same(_collection.Get<IHttpResponseBodyFeature>(), _collection[typeof(IHttpResponseBodyFeature)]);
|
||||||
Assert.Same(_collection.Get<IRequestBodyPipeFeature>(), _collection[typeof(IRequestBodyPipeFeature)]);
|
Assert.Same(_collection.Get<IRequestBodyPipeFeature>(), _collection[typeof(IRequestBodyPipeFeature)]);
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var buffer = new byte[8192];
|
var buffer = new byte[8192];
|
||||||
|
|
@ -40,6 +41,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var readResult = await request.BodyReader.ReadAsync();
|
var readResult = await request.BodyReader.ReadAsync();
|
||||||
|
|
@ -58,6 +60,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
var data = new MemoryStream();
|
var data = new MemoryStream();
|
||||||
await request.Body.CopyToAsync(data);
|
await request.Body.CopyToAsync(data);
|
||||||
var bytes = data.ToArray();
|
var bytes = data.ToArray();
|
||||||
|
|
@ -174,6 +177,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
|
|
||||||
Assert.Equal("POST", request.Method);
|
Assert.Equal("POST", request.Method);
|
||||||
|
|
||||||
|
|
@ -229,6 +233,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
|
|
||||||
var buffer = new byte[200];
|
var buffer = new byte[200];
|
||||||
|
|
||||||
|
|
@ -356,6 +361,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
|
|
||||||
// The first request is chunked with no trailers.
|
// The first request is chunked with no trailers.
|
||||||
if (requestsReceived == 0)
|
if (requestsReceived == 0)
|
||||||
|
|
@ -652,6 +658,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
|
|
||||||
var buffer = new byte[200];
|
var buffer = new byte[200];
|
||||||
|
|
||||||
|
|
@ -695,6 +702,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
{
|
{
|
||||||
var response = httpContext.Response;
|
var response = httpContext.Response;
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
|
|
||||||
var buffer = new byte[200];
|
var buffer = new byte[200];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,22 +64,28 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Theory]
|
||||||
public async Task HEADERS_Received_CustomMethod_Accepted()
|
[InlineData("GET")]
|
||||||
|
[InlineData("POST")]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[InlineData("PATCH")]
|
||||||
|
[InlineData("DELETE")]
|
||||||
|
[InlineData("CUSTOM")]
|
||||||
|
public async Task HEADERS_Received_KnownOrCustomMethods_Accepted(string method)
|
||||||
{
|
{
|
||||||
var headers = new[]
|
var headers = new[]
|
||||||
{
|
{
|
||||||
new KeyValuePair<string, string>(HeaderNames.Method, "Custom"),
|
new KeyValuePair<string, string>(HeaderNames.Method, method),
|
||||||
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
};
|
};
|
||||||
await InitializeConnectionAsync(_echoMethod);
|
await InitializeConnectionAsync(_echoMethodNoBody);
|
||||||
|
|
||||||
await StartStreamAsync(1, headers, endStream: true);
|
await StartStreamAsync(1, headers, endStream: true);
|
||||||
|
|
||||||
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
withLength: 51,
|
withLength: 45 + method.Length,
|
||||||
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
|
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
|
||||||
withStreamId: 1);
|
withStreamId: 1);
|
||||||
|
|
||||||
|
|
@ -90,14 +96,146 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
Assert.Equal(4, _decodedHeaders.Count);
|
Assert.Equal(4, _decodedHeaders.Count);
|
||||||
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
Assert.Equal("Custom", _decodedHeaders["Method"]);
|
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
|
||||||
Assert.Equal("0", _decodedHeaders["content-length"]);
|
Assert.Equal(method, _decodedHeaders["Method"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task HEADERS_Received_HEADMethod_Accepted()
|
||||||
|
{
|
||||||
|
await InitializeConnectionAsync(_echoMethodNoBody);
|
||||||
|
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, "HEAD"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 45,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(3, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("HEAD", _decodedHeaders["Method"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GET")]
|
||||||
|
[InlineData("POST")]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[InlineData("PATCH")]
|
||||||
|
[InlineData("DELETE")]
|
||||||
|
[InlineData("CUSTOM")]
|
||||||
|
public async Task HEADERS_Received_MethodsWithContentLength_Accepted(string method)
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, method),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.ContentLength, "11"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(context =>
|
||||||
|
{
|
||||||
|
Assert.True(HttpMethods.Equals(method, context.Request.Method));
|
||||||
|
Assert.True(context.Request.CanHaveBody());
|
||||||
|
Assert.Equal(11, context.Request.ContentLength);
|
||||||
|
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
|
||||||
|
return context.Request.BodyReader.CopyToAsync(context.Response.BodyWriter);
|
||||||
|
});
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: false);
|
||||||
|
await SendDataAsync(1, Encoding.UTF8.GetBytes("Hello World"), endStream: true);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 32,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
|
||||||
|
withStreamId: 1);
|
||||||
|
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 11,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(2, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("Hello World", Encoding.UTF8.GetString(dataFrame.Payload.Span));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("GET")]
|
||||||
|
[InlineData("POST")]
|
||||||
|
[InlineData("PUT")]
|
||||||
|
[InlineData("PATCH")]
|
||||||
|
[InlineData("DELETE")]
|
||||||
|
[InlineData("CUSTOM")]
|
||||||
|
public async Task HEADERS_Received_MethodsWithoutContentLength_Accepted(string method)
|
||||||
|
{
|
||||||
|
var headers = new[]
|
||||||
|
{
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Method, method),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Path, "/"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Scheme, "http"),
|
||||||
|
new KeyValuePair<string, string>(HeaderNames.Authority, "localhost:80"),
|
||||||
|
};
|
||||||
|
await InitializeConnectionAsync(context =>
|
||||||
|
{
|
||||||
|
Assert.True(HttpMethods.Equals(method, context.Request.Method));
|
||||||
|
Assert.True(context.Request.CanHaveBody());
|
||||||
|
Assert.Null(context.Request.ContentLength);
|
||||||
|
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
|
||||||
|
return context.Request.BodyReader.CopyToAsync(context.Response.BodyWriter);
|
||||||
|
});
|
||||||
|
|
||||||
|
await StartStreamAsync(1, headers, endStream: false);
|
||||||
|
await SendDataAsync(1, Encoding.UTF8.GetBytes("Hello World"), endStream: true);
|
||||||
|
|
||||||
|
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
|
||||||
|
withLength: 32,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
|
||||||
|
withStreamId: 1);
|
||||||
|
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 11,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
|
||||||
|
withStreamId: 1);
|
||||||
|
await ExpectAsync(Http2FrameType.DATA,
|
||||||
|
withLength: 0,
|
||||||
|
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
|
||||||
|
withStreamId: 1);
|
||||||
|
|
||||||
|
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
|
||||||
|
|
||||||
|
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
|
||||||
|
|
||||||
|
Assert.Equal(2, _decodedHeaders.Count);
|
||||||
|
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
|
||||||
|
Assert.Equal("200", _decodedHeaders[HeaderNames.Status]);
|
||||||
|
Assert.Equal("Hello World", Encoding.UTF8.GetString(dataFrame.Payload.Span));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task HEADERS_Received_CONNECTMethod_Accepted()
|
public async Task HEADERS_Received_CONNECTMethod_Accepted()
|
||||||
{
|
{
|
||||||
await InitializeConnectionAsync(_echoMethod);
|
await InitializeConnectionAsync(_echoMethodNoBody);
|
||||||
|
|
||||||
// :path and :scheme are not allowed, :authority is optional
|
// :path and :scheme are not allowed, :authority is optional
|
||||||
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "CONNECT") };
|
var headers = new[] { new KeyValuePair<string, string>(HeaderNames.Method, "CONNECT") };
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
protected readonly RequestDelegate _waitForAbortApplication;
|
protected readonly RequestDelegate _waitForAbortApplication;
|
||||||
protected readonly RequestDelegate _waitForAbortFlushingApplication;
|
protected readonly RequestDelegate _waitForAbortFlushingApplication;
|
||||||
protected readonly RequestDelegate _readRateApplication;
|
protected readonly RequestDelegate _readRateApplication;
|
||||||
protected readonly RequestDelegate _echoMethod;
|
protected readonly RequestDelegate _echoMethodNoBody;
|
||||||
protected readonly RequestDelegate _echoHost;
|
protected readonly RequestDelegate _echoHost;
|
||||||
protected readonly RequestDelegate _echoPath;
|
protected readonly RequestDelegate _echoPath;
|
||||||
protected readonly RequestDelegate _appAbort;
|
protected readonly RequestDelegate _appAbort;
|
||||||
|
|
@ -346,8 +346,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
await stalledReadTask;
|
await stalledReadTask;
|
||||||
};
|
};
|
||||||
|
|
||||||
_echoMethod = context =>
|
_echoMethodNoBody = context =>
|
||||||
{
|
{
|
||||||
|
Assert.False(context.Request.CanHaveBody());
|
||||||
context.Response.Headers["Method"] = context.Request.Method;
|
context.Response.Headers["Method"] = context.Request.Method;
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
|
|
||||||
await using (var server = new TestServer(async context =>
|
await using (var server = new TestServer(async context =>
|
||||||
{
|
{
|
||||||
|
Assert.True(context.Request.CanHaveBody());
|
||||||
var buffer = new byte[1];
|
var buffer = new byte[1];
|
||||||
#pragma warning disable CS0618 // Type or member is obsolete
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
requestRejectedEx = await Assert.ThrowsAsync<BadHttpRequestException>(
|
requestRejectedEx = await Assert.ThrowsAsync<BadHttpRequestException>(
|
||||||
|
|
|
||||||
|
|
@ -1933,7 +1933,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.InMemory.FunctionalTests
|
||||||
var request = httpContext.Request;
|
var request = httpContext.Request;
|
||||||
|
|
||||||
Assert.Equal("POST", request.Method);
|
Assert.Equal("POST", request.Method);
|
||||||
|
Assert.True(request.CanHaveBody());
|
||||||
var readResult = await request.BodyReader.ReadAsync();
|
var readResult = await request.BodyReader.ReadAsync();
|
||||||
request.BodyReader.AdvanceTo(readResult.Buffer.End);
|
request.BodyReader.AdvanceTo(readResult.Buffer.End);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http.Features;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Http
|
||||||
|
{
|
||||||
|
internal static class RequestExtensions
|
||||||
|
{
|
||||||
|
internal static bool? CanHaveBody(this HttpRequest request)
|
||||||
|
{
|
||||||
|
return request.HttpContext.Features.Get<IHttpRequestBodyDetectionFeature>()?.CanHaveBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ namespace CodeGenerator
|
||||||
var alwaysFeatures = new[]
|
var alwaysFeatures = new[]
|
||||||
{
|
{
|
||||||
"IHttpRequestFeature",
|
"IHttpRequestFeature",
|
||||||
|
"IHttpRequestBodyDetectionFeature",
|
||||||
"IHttpResponseFeature",
|
"IHttpResponseFeature",
|
||||||
"IHttpResponseBodyFeature",
|
"IHttpResponseBodyFeature",
|
||||||
"IRequestBodyPipeFeature",
|
"IRequestBodyPipeFeature",
|
||||||
|
|
@ -58,6 +59,7 @@ namespace CodeGenerator
|
||||||
var implementedFeatures = new[]
|
var implementedFeatures = new[]
|
||||||
{
|
{
|
||||||
"IHttpRequestFeature",
|
"IHttpRequestFeature",
|
||||||
|
"IHttpRequestBodyDetectionFeature",
|
||||||
"IHttpResponseFeature",
|
"IHttpResponseFeature",
|
||||||
"IHttpResponseBodyFeature",
|
"IHttpResponseBodyFeature",
|
||||||
"IRequestBodyPipeFeature",
|
"IRequestBodyPipeFeature",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue