diff --git a/src/Kestrel.Core/BadHttpRequestException.cs b/src/Kestrel.Core/BadHttpRequestException.cs
index 1414753d9f..4fc2bb1521 100644
--- a/src/Kestrel.Core/BadHttpRequestException.cs
+++ b/src/Kestrel.Core/BadHttpRequestException.cs
@@ -38,6 +38,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
throw GetException(reason);
}
+ [StackTraceHidden]
+ public static void Throw(RequestRejectionReason reason, HttpMethod method)
+ => throw GetException(reason, method.ToString().ToUpperInvariant());
+
[MethodImpl(MethodImplOptions.NoInlining)]
internal static BadHttpRequestException GetException(RequestRejectionReason reason)
{
diff --git a/src/Kestrel.Core/Internal/Http/Http1Connection.cs b/src/Kestrel.Core/Internal/Http/Http1Connection.cs
index 4b24908f53..142902a8de 100644
--- a/src/Kestrel.Core/Internal/Http/Http1Connection.cs
+++ b/src/Kestrel.Core/Internal/Http/Http1Connection.cs
@@ -181,13 +181,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
OnAuthorityFormTarget(method, target);
}
- Method = method != HttpMethod.Custom
- ? HttpUtilities.MethodToString(method) ?? string.Empty
- : customMethod.GetAsciiStringNonNullCharacters();
+ Method = method;
+ if (method == HttpMethod.Custom)
+ {
+ _methodText = customMethod.GetAsciiStringNonNullCharacters();
+ }
+
_httpVersion = version;
Debug.Assert(RawTarget != null, "RawTarget was not set");
- Debug.Assert(Method != null, "Method was not set");
+ Debug.Assert(((IHttpRequestFeature)this).Method != null, "Method was not set");
Debug.Assert(Path != null, "Path was not set");
Debug.Assert(QueryString != null, "QueryString was not set");
Debug.Assert(HttpVersion != null, "HttpVersion was not set");
diff --git a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs
index 427bbacfcf..4eb3f8928d 100644
--- a/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs
+++ b/src/Kestrel.Core/Internal/Http/Http1MessageBody.cs
@@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
// If we got here, request contains no Content-Length or Transfer-Encoding header.
// Reject with 411 Length Required.
- if (HttpMethods.IsPost(context.Method) || HttpMethods.IsPut(context.Method))
+ if (context.Method == HttpMethod.Post || context.Method == HttpMethod.Put)
{
var requestRejectionReason = httpVersion == HttpVersion.Http11 ? RequestRejectionReason.LengthRequired : RequestRejectionReason.LengthRequiredHttp10;
BadHttpRequestException.Throw(requestRejectionReason, context.Method);
diff --git a/src/Kestrel.Core/Internal/Http/HttpMethod.cs b/src/Kestrel.Core/Internal/Http/HttpMethod.cs
index 0aa230cf50..3e6ff0667e 100644
--- a/src/Kestrel.Core/Internal/Http/HttpMethod.cs
+++ b/src/Kestrel.Core/Internal/Http/HttpMethod.cs
@@ -16,5 +16,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Options,
Custom,
+
+ None = byte.MaxValue,
}
}
\ No newline at end of file
diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs
index cc0337a833..055e8a1e76 100644
--- a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs
+++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs
@@ -11,6 +11,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
+using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
@@ -89,8 +90,20 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
string IHttpRequestFeature.Method
{
- get => Method;
- set => Method = value;
+ get
+ {
+ if (_methodText != null)
+ {
+ return _methodText;
+ }
+
+ _methodText = HttpUtilities.MethodToString(Method) ?? string.Empty;
+ return _methodText;
+ }
+ set
+ {
+ _methodText = value;
+ }
}
string IHttpRequestFeature.PathBase
diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs
index 80d073f23e..274376c31b 100644
--- a/src/Kestrel.Core/Internal/Http/HttpProtocol.cs
+++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.cs
@@ -62,6 +62,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private readonly IHttpProtocolContext _context;
+ protected string _methodText = null;
private string _scheme = null;
public HttpProtocol(IHttpProtocolContext context)
@@ -119,7 +120,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public IPAddress LocalIpAddress { get; set; }
public int LocalPort { get; set; }
public string Scheme { get; set; }
- public string Method { get; set; }
+ public HttpMethod Method { get; set; }
public string PathBase { get; set; }
public string Path { get; set; }
public string QueryString { get; set; }
@@ -324,7 +325,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
AllowSynchronousIO = ServerOptions.AllowSynchronousIO;
TraceIdentifier = null;
- Method = null;
+ Method = HttpMethod.None;
+ _methodText = null;
PathBase = null;
Path = null;
RawTarget = null;
@@ -905,7 +907,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{
var responseHeaders = HttpResponseHeaders;
- if (!HttpMethods.IsHead(Method) &&
+ if (Method != HttpMethod.Head &&
StatusCode != StatusCodes.Status304NotModified &&
!responseHeaders.HasTransferEncoding &&
responseHeaders.ContentLength.HasValue &&
@@ -1074,7 +1076,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Log.ConnectionKeepAlive(ConnectionId);
}
- if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0)
+ if (Method == HttpMethod.Head && _responseBytesWritten > 0)
{
Log.ConnectionHeadResponseBodyWrite(ConnectionId, _responseBytesWritten);
}
@@ -1094,7 +1096,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
Log.ConnectionKeepAlive(ConnectionId);
}
- if (HttpMethods.IsHead(Method) && _responseBytesWritten > 0)
+ if (Method == HttpMethod.Head && _responseBytesWritten > 0)
{
Log.ConnectionHeadResponseBodyWrite(ConnectionId, _responseBytesWritten);
}
@@ -1124,7 +1126,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
// Set whether response can have body
- _canHaveBody = StatusCanHaveBody(StatusCode) && Method != "HEAD";
+ _canHaveBody = StatusCanHaveBody(StatusCode) && Method != HttpMethod.Head;
// Don't set the Content-Length or Transfer-Encoding headers
// automatically for HEAD requests or 204, 205, 304 responses.
@@ -1261,7 +1263,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
public void HandleNonBodyResponseWrite()
{
// Writes to HEAD response are ignored and logged at the end of the request
- if (Method != "HEAD")
+ if (Method != HttpMethod.Head)
{
ThrowWritingToResponseBodyNotSupported();
}
diff --git a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs
index 873cb1fbea..de212d1933 100644
--- a/src/Kestrel.Core/Internal/Http2/Http2Stream.cs
+++ b/src/Kestrel.Core/Internal/Http2/Http2Stream.cs
@@ -5,6 +5,7 @@ using System;
using System.Buffers;
using System.IO.Pipelines;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
@@ -53,7 +54,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// We don't need any of the parameters because we don't implement BeginRead to actually
// do the reading from a pipeline, nor do we use endConnection to report connection-level errors.
- Method = RequestHeaders[":method"];
+ var methodText = RequestHeaders[":method"];
+ Method = HttpUtilities.GetKnownMethod(methodText);
+ _methodText = methodText;
+
Scheme = RequestHeaders[":scheme"];
_httpVersion = Http.HttpVersion.Http2;
diff --git a/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs b/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs
index 28f73a9031..457b7afe5f 100644
--- a/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs
+++ b/src/Kestrel.Core/Internal/Infrastructure/HttpUtilities.cs
@@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
@@ -177,6 +178,87 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure
return HttpMethod.Custom;
}
+ ///
+ /// Parses string for a known HTTP method.
+ ///
+ ///
+ /// A "known HTTP method" can be an HTTP method name defined in the HTTP/1.1 RFC.
+ /// The Known Methods (CONNECT, DELETE, GET, HEAD, PATCH, POST, PUT, OPTIONS, TRACE)
+ ///
+ ///
+ public static HttpMethod GetKnownMethod(string value)
+ {
+ // Called by http/2
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ var length = value.Length;
+ if (length == 0)
+ {
+ throw new ArgumentException(nameof(value));
+ }
+
+ // Start with custom and assign if known method is found
+ var method = HttpMethod.Custom;
+
+ var firstChar = value[0];
+ if (length == 3)
+ {
+ if (firstChar == 'G' && string.Equals(value, HttpMethods.Get, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Get;
+ }
+ else if (firstChar == 'P' && string.Equals(value, HttpMethods.Put, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Put;
+ }
+ }
+ else if (length == 4)
+ {
+ if (firstChar == 'H' && string.Equals(value, HttpMethods.Head, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Head;
+ }
+ else if(firstChar == 'P' && string.Equals(value, HttpMethods.Post, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Post;
+ }
+ }
+ else if (length == 5)
+ {
+ if (firstChar == 'T' && string.Equals(value, HttpMethods.Trace, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Trace;
+ }
+ else if(firstChar == 'P' && string.Equals(value, HttpMethods.Patch, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Patch;
+ }
+ }
+ else if (length == 6)
+ {
+ if (firstChar == 'D' && string.Equals(value, HttpMethods.Delete, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Delete;
+ }
+ }
+ else if (length == 7)
+ {
+ if (firstChar == 'C' && string.Equals(value, HttpMethods.Connect, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Connect;
+ }
+ else if (firstChar == 'O' && string.Equals(value, HttpMethods.Options, StringComparison.Ordinal))
+ {
+ method = HttpMethod.Options;
+ }
+ }
+
+ return method;
+ }
+
///
/// Checks 9 bytes from correspond to a known HTTP version.
///
diff --git a/test/Kestrel.Core.Tests/Http1ConnectionTests.cs b/test/Kestrel.Core.Tests/Http1ConnectionTests.cs
index 56711ed1b3..41c933a429 100644
--- a/test/Kestrel.Core.Tests/Http1ConnectionTests.cs
+++ b/test/Kestrel.Core.Tests/Http1ConnectionTests.cs
@@ -344,7 +344,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_transport.Input.AdvanceTo(_consumed, _examined);
Assert.True(returnValue);
- Assert.Equal(expectedMethod, _http1Connection.Method);
+ Assert.Equal(expectedMethod, ((IHttpRequestFeature)_http1Connection).Method);
Assert.Equal(expectedRawTarget, _http1Connection.RawTarget);
Assert.Equal(expectedDecodedPath, _http1Connection.Path);
Assert.Equal(expectedQueryString, _http1Connection.QueryString);
@@ -532,7 +532,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
// Arrange
_http1Connection.HttpVersion = "HTTP/1.1";
- ((IHttpRequestFeature)_http1Connection).Method = "HEAD";
+ _http1Connection.Method = HttpMethod.Head;
// Act/Assert
await _http1Connection.WriteAsync(new ArraySegment(new byte[1]));
@@ -543,7 +543,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
// Arrange
_http1Connection.HttpVersion = "HTTP/1.1";
- ((IHttpRequestFeature)_http1Connection).Method = "HEAD";
+ _http1Connection.Method = HttpMethod.Head;
// Act/Assert
await _http1Connection.WriteAsync(new ArraySegment(new byte[1]), default(CancellationToken));
@@ -554,7 +554,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
{
// Arrange
_http1Connection.HttpVersion = "HTTP/1.1";
- ((IHttpRequestFeature)_http1Connection).Method = "HEAD";
+ _http1Connection.Method = HttpMethod.Head;
// Act
_http1Connection.ResponseHeaders.Add("Transfer-Encoding", "chunked");
diff --git a/test/Kestrel.Core.Tests/MessageBodyTests.cs b/test/Kestrel.Core.Tests/MessageBodyTests.cs
index 41d0724bd0..8a563474fe 100644
--- a/test/Kestrel.Core.Tests/MessageBodyTests.cs
+++ b/test/Kestrel.Core.Tests/MessageBodyTests.cs
@@ -333,9 +333,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Theory]
- [InlineData("POST")]
- [InlineData("PUT")]
- public void ForThrowsWhenMethodRequiresLengthButNoContentLengthOrTransferEncodingIsSet(string method)
+ [InlineData(HttpMethod.Post)]
+ [InlineData(HttpMethod.Put)]
+ public void ForThrowsWhenMethodRequiresLengthButNoContentLengthOrTransferEncodingIsSet(HttpMethod method)
{
using (var input = new TestInput())
{
@@ -344,14 +344,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Http1MessageBody.For(HttpVersion.Http11, new HttpRequestHeaders(), input.Http1Connection));
Assert.Equal(StatusCodes.Status411LengthRequired, ex.StatusCode);
- Assert.Equal(CoreStrings.FormatBadRequest_LengthRequired(method), ex.Message);
+ Assert.Equal(CoreStrings.FormatBadRequest_LengthRequired(((IHttpRequestFeature)input.Http1Connection).Method), ex.Message);
}
}
[Theory]
- [InlineData("POST")]
- [InlineData("PUT")]
- public void ForThrowsWhenMethodRequiresLengthButNoContentLengthSetHttp10(string method)
+ [InlineData(HttpMethod.Post)]
+ [InlineData(HttpMethod.Put)]
+ public void ForThrowsWhenMethodRequiresLengthButNoContentLengthSetHttp10(HttpMethod method)
{
using (var input = new TestInput())
{
@@ -360,7 +360,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
Http1MessageBody.For(HttpVersion.Http10, new HttpRequestHeaders(), input.Http1Connection));
Assert.Equal(StatusCodes.Status400BadRequest, ex.StatusCode);
- Assert.Equal(CoreStrings.FormatBadRequest_LengthRequiredHttp10(method), ex.Message);
+ Assert.Equal(CoreStrings.FormatBadRequest_LengthRequiredHttp10(((IHttpRequestFeature)input.Http1Connection).Method), ex.Message);
}
}