Skip to content

Commit df712cc

Browse files
authored
Add HttpProtocol (#18049)
1 parent 337b951 commit df712cc

File tree

19 files changed

+284
-99
lines changed

19 files changed

+284
-99
lines changed

src/Hosting/TestHost/src/ClientHandler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ protected override async Task<HttpResponseMessage> SendAsync(
100100
if (request.Version == HttpVersion.Version20)
101101
{
102102
// https://tools.ietf.org/html/rfc7540
103-
req.Protocol = "HTTP/2";
103+
req.Protocol = HttpProtocol.Http2;
104104
}
105105
else
106106
{

src/Hosting/TestHost/src/HttpContextBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ internal HttpContextBuilder(ApplicationWrapper application, bool allowSynchronou
4141
_requestLifetimeFeature = new RequestLifetimeFeature(Abort);
4242

4343
var request = _httpContext.Request;
44-
request.Protocol = "HTTP/1.1";
44+
request.Protocol = HttpProtocol.Http11;
4545
request.Method = HttpMethods.Get;
4646

4747
_requestPipe = new Pipe();
@@ -98,7 +98,7 @@ internal Task<HttpContext> SendAsync(CancellationToken cancellationToken)
9898
async Task RunRequestAsync()
9999
{
100100
// HTTP/2 specific features must be added after the request has been configured.
101-
if (string.Equals("HTTP/2", _httpContext.Request.Protocol, StringComparison.OrdinalIgnoreCase))
101+
if (HttpProtocol.IsHttp2(_httpContext.Request.Protocol))
102102
{
103103
_httpContext.Features.Set<IHttpResetFeature>(this);
104104
}

src/Hosting/TestHost/src/RequestFeature.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public RequestFeature()
1616
Method = "GET";
1717
Path = "";
1818
PathBase = "";
19-
Protocol = "HTTP/1.1";
19+
Protocol = HttpProtocol.Http11;
2020
QueryString = "";
2121
Scheme = "http";
2222
}

src/Hosting/TestHost/test/ClientHandlerTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public Task ExpectedKeysAreAvailable()
2727
var handler = new ClientHandler(new PathString("/A/Path/"), new DummyApplication(context =>
2828
{
2929
// TODO: Assert.True(context.RequestAborted.CanBeCanceled);
30-
Assert.Equal("HTTP/1.1", context.Request.Protocol);
30+
Assert.Equal(HttpProtocol.Http11, context.Request.Protocol);
3131
Assert.Equal("GET", context.Request.Method);
3232
Assert.Equal("https", context.Request.Scheme);
3333
Assert.Equal("/A/Path", context.Request.PathBase.Value);
@@ -53,7 +53,7 @@ public Task ExpectedKeysAreInFeatures()
5353
var handler = new ClientHandler(new PathString("/A/Path/"), new InspectingApplication(features =>
5454
{
5555
Assert.True(features.Get<IHttpRequestLifetimeFeature>().RequestAborted.CanBeCanceled);
56-
Assert.Equal("HTTP/1.1", features.Get<IHttpRequestFeature>().Protocol);
56+
Assert.Equal(HttpProtocol.Http11, features.Get<IHttpRequestFeature>().Protocol);
5757
Assert.Equal("GET", features.Get<IHttpRequestFeature>().Method);
5858
Assert.Equal("https", features.Get<IHttpRequestFeature>().Scheme);
5959
Assert.Equal("/A/Path", features.Get<IHttpRequestFeature>().PathBase);

src/Hosting/TestHost/test/HttpContextBuilderTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public async Task ExpectedValuesAreAvailable()
3333
});
3434

3535
Assert.True(context.RequestAborted.CanBeCanceled);
36-
Assert.Equal("HTTP/1.1", context.Request.Protocol);
36+
Assert.Equal(HttpProtocol.Http11, context.Request.Protocol);
3737
Assert.Equal("POST", context.Request.Method);
3838
Assert.Equal("https", context.Request.Scheme);
3939
Assert.Equal("example.com", context.Request.Host.Value);

src/Http/Http.Abstractions/ref/Microsoft.AspNetCore.Http.Abstractions.netcoreapp.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,18 @@ public static partial class HttpMethods
250250
public static bool IsPut(string method) { throw null; }
251251
public static bool IsTrace(string method) { throw null; }
252252
}
253+
public static partial class HttpProtocol
254+
{
255+
public static readonly string Http10;
256+
public static readonly string Http11;
257+
public static readonly string Http2;
258+
public static readonly string Http3;
259+
public static string GetHttpProtocol(System.Version version) { throw null; }
260+
public static bool IsHttp10(string protocol) { throw null; }
261+
public static bool IsHttp11(string protocol) { throw null; }
262+
public static bool IsHttp2(string protocol) { throw null; }
263+
public static bool IsHttp3(string protocol) { throw null; }
264+
}
253265
public abstract partial class HttpRequest
254266
{
255267
protected HttpRequest() { }
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace Microsoft.AspNetCore.Http
7+
{
8+
/// <summary>
9+
/// Contains methods to verify the request protocol version of an HTTP request.
10+
/// </summary>
11+
public static class HttpProtocol
12+
{
13+
// We are intentionally using 'static readonly' here instead of 'const'.
14+
// 'const' values would be embedded into each assembly that used them
15+
// and each consuming assembly would have a different 'string' instance.
16+
// Using .'static readonly' means that all consumers get these exact same
17+
// 'string' instance, which means the 'ReferenceEquals' checks below work
18+
// and allow us to optimize comparisons when these constants are used.
19+
20+
// Please do NOT change these to 'const'
21+
public static readonly string Http10 = "HTTP/1.0";
22+
public static readonly string Http11 = "HTTP/1.1";
23+
public static readonly string Http2 = "HTTP/2";
24+
public static readonly string Http3 = "HTTP/3";
25+
26+
/// <summary>
27+
/// Returns a value that indicates if the HTTP request protocol is HTTP/1.0.
28+
/// </summary>
29+
/// <param name="protocol">The HTTP request protocol.</param>
30+
/// <returns>
31+
/// <see langword="true" /> if the protocol is HTTP/1.0; otherwise, <see langword="false" />.
32+
/// </returns>
33+
public static bool IsHttp10(string protocol)
34+
{
35+
return object.ReferenceEquals(Http10, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http10, protocol);
36+
}
37+
38+
/// <summary>
39+
/// Returns a value that indicates if the HTTP request protocol is HTTP/1.1.
40+
/// </summary>
41+
/// <param name="protocol">The HTTP request protocol.</param>
42+
/// <returns>
43+
/// <see langword="true" /> if the protocol is HTTP/1.1; otherwise, <see langword="false" />.
44+
/// </returns>
45+
public static bool IsHttp11(string protocol)
46+
{
47+
return object.ReferenceEquals(Http11, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http11, protocol);
48+
}
49+
50+
/// <summary>
51+
/// Returns a value that indicates if the HTTP request protocol is HTTP/2.
52+
/// </summary>
53+
/// <param name="protocol">The HTTP request protocol.</param>
54+
/// <returns>
55+
/// <see langword="true" /> if the protocol is HTTP/2; otherwise, <see langword="false" />.
56+
/// </returns>
57+
public static bool IsHttp2(string protocol)
58+
{
59+
return object.ReferenceEquals(Http2, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http2, protocol);
60+
}
61+
62+
/// <summary>
63+
/// Returns a value that indicates if the HTTP request protocol is HTTP/3.
64+
/// </summary>
65+
/// <param name="protocol">The HTTP request protocol.</param>
66+
/// <returns>
67+
/// <see langword="true" /> if the protocol is HTTP/3; otherwise, <see langword="false" />.
68+
/// </returns>
69+
public static bool IsHttp3(string protocol)
70+
{
71+
return object.ReferenceEquals(Http3, protocol) || StringComparer.OrdinalIgnoreCase.Equals(Http3, protocol);
72+
}
73+
74+
/// <summary>
75+
/// Gets the HTTP request protocol for the specified <see cref="Version"/>.
76+
/// </summary>
77+
/// <param name="version">The version.</param>
78+
/// <returns>A HTTP request protocol.</returns>
79+
public static string GetHttpProtocol(Version version)
80+
{
81+
if (version == null)
82+
{
83+
throw new ArgumentNullException(nameof(version));
84+
}
85+
86+
return version switch
87+
{
88+
{ Major: 3, Minor: 0 } => Http3,
89+
{ Major: 2, Minor: 0 } => Http2,
90+
{ Major: 1, Minor: 1 } => Http11,
91+
{ Major: 1, Minor: 0 } => Http10,
92+
_ => throw new ArgumentOutOfRangeException(nameof(version), "Version doesn't map to a known HTTP protocol.")
93+
};
94+
}
95+
}
96+
}
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using Xunit;
6+
7+
namespace Microsoft.AspNetCore.Http.Abstractions
8+
{
9+
public class HttpProtocolTests
10+
{
11+
[Fact]
12+
public void Http3_Success()
13+
{
14+
Assert.Equal("HTTP/3", HttpProtocol.Http3);
15+
}
16+
17+
[Theory]
18+
[InlineData("HTTP/3", true)]
19+
[InlineData("http/3", true)]
20+
[InlineData("HTTP/1.1", false)]
21+
[InlineData("HTTP/3.0", false)]
22+
[InlineData("HTTP/1", false)]
23+
[InlineData(" HTTP/3", false)]
24+
[InlineData("HTTP/3 ", false)]
25+
public void IsHttp3_Success(string protocol, bool match)
26+
{
27+
Assert.Equal(match, HttpProtocol.IsHttp3(protocol));
28+
}
29+
30+
[Fact]
31+
public void Http2_Success()
32+
{
33+
Assert.Equal("HTTP/2", HttpProtocol.Http2);
34+
}
35+
36+
[Theory]
37+
[InlineData("HTTP/2", true)]
38+
[InlineData("http/2", true)]
39+
[InlineData("HTTP/1.1", false)]
40+
[InlineData("HTTP/2.0", false)]
41+
[InlineData("HTTP/1", false)]
42+
[InlineData(" HTTP/2", false)]
43+
[InlineData("HTTP/2 ", false)]
44+
public void IsHttp2_Success(string protocol, bool match)
45+
{
46+
Assert.Equal(match, HttpProtocol.IsHttp2(protocol));
47+
}
48+
49+
[Fact]
50+
public void Http11_Success()
51+
{
52+
Assert.Equal("HTTP/1.1", HttpProtocol.Http11);
53+
}
54+
55+
[Theory]
56+
[InlineData("HTTP/1.1", true)]
57+
[InlineData("http/1.1", true)]
58+
[InlineData("HTTP/2", false)]
59+
[InlineData("HTTP/1.0", false)]
60+
[InlineData("HTTP/1", false)]
61+
[InlineData(" HTTP/1.1", false)]
62+
[InlineData("HTTP/1.1 ", false)]
63+
public void IsHttp11_Success(string protocol, bool match)
64+
{
65+
Assert.Equal(match, HttpProtocol.IsHttp11(protocol));
66+
}
67+
68+
[Fact]
69+
public void Http10_Success()
70+
{
71+
Assert.Equal("HTTP/1.0", HttpProtocol.Http10);
72+
}
73+
74+
[Theory]
75+
[InlineData("HTTP/1.0", true)]
76+
[InlineData("http/1.0", true)]
77+
[InlineData("HTTP/2", false)]
78+
[InlineData("HTTP/1.1", false)]
79+
[InlineData("HTTP/1", false)]
80+
[InlineData(" HTTP/1.0", false)]
81+
[InlineData("HTTP/1.0 ", false)]
82+
public void IsHttp10_Success(string protocol, bool match)
83+
{
84+
Assert.Equal(match, HttpProtocol.IsHttp10(protocol));
85+
}
86+
87+
public static TheoryData<Version, string> s_ValidData = new TheoryData<Version, string>
88+
{
89+
{ new Version(3, 0), "HTTP/3" },
90+
{ new Version(2, 0), "HTTP/2" },
91+
{ new Version(1, 1), "HTTP/1.1" },
92+
{ new Version(1, 0), "HTTP/1.0" }
93+
};
94+
95+
[Theory]
96+
[MemberData(nameof(s_ValidData))]
97+
public void GetHttpProtocol_CorrectIETFVersion(Version version, string expected)
98+
{
99+
var actual = HttpProtocol.GetHttpProtocol(version);
100+
101+
Assert.Equal(expected, actual);
102+
}
103+
104+
public static TheoryData<Version> s_InvalidData = new TheoryData<Version>
105+
{
106+
{ new Version(0, 3) },
107+
{ new Version(2, 1) }
108+
};
109+
110+
[Theory]
111+
[MemberData(nameof(s_InvalidData))]
112+
public void GetHttpProtocol_ThrowErrorForUnknownVersion(Version version)
113+
{
114+
Assert.Throws<ArgumentOutOfRangeException>(() => HttpProtocol.GetHttpProtocol(version));
115+
}
116+
}
117+
}

src/Security/Authentication/Negotiate/src/NegotiateHandler.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public NegotiateHandler(IOptionsMonitor<NegotiateOptions> options, ILoggerFactor
5757
/// <returns></returns>
5858
protected override Task<object> CreateEventsAsync() => Task.FromResult<object>(new NegotiateEvents());
5959

60-
private bool IsHttp2 => string.Equals("HTTP/2", Request.Protocol, StringComparison.OrdinalIgnoreCase);
60+
private bool IsSupportedProtocol => HttpProtocol.IsHttp11(Request.Protocol) || HttpProtocol.IsHttp10(Request.Protocol);
6161

6262
/// <summary>
6363
/// Intercepts incomplete Negotiate authentication handshakes and continues or completes them.
@@ -80,10 +80,10 @@ public async Task<bool> HandleRequestAsync()
8080

8181
_requestProcessed = true;
8282

83-
if (IsHttp2)
83+
if (!IsSupportedProtocol)
8484
{
85-
// HTTP/2 is not supported. Do not throw because this may be running on a server that supports
86-
// both HTTP/1 and HTTP/2.
85+
// HTTP/1.0 and HTTP/1.1 are supported. Do not throw because this may be running on a server that supports
86+
// additional protocols.
8787
return false;
8888
}
8989

@@ -291,7 +291,7 @@ protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
291291
throw new InvalidOperationException("AuthenticateAsync must not be called before the UseAuthentication middleware runs.");
292292
}
293293

294-
if (IsHttp2)
294+
if (!IsSupportedProtocol)
295295
{
296296
// Not supported. We don't throw because Negotiate may be set as the default auth
297297
// handler on a server that's running HTTP/1 and HTTP/2. We'll challenge HTTP/2 requests

src/Servers/HttpSys/src/FeatureContext.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ string IHttpRequestFeature.Protocol
181181
{
182182
if (IsNotInitialized(Fields.Protocol))
183183
{
184-
_httpProtocolVersion = Request.ProtocolVersion.GetHttpProtocolVersion();
184+
_httpProtocolVersion = HttpProtocol.GetHttpProtocol(Request.ProtocolVersion);
185185
SetInitialized(Fields.Protocol);
186186
}
187187
return _httpProtocolVersion;

src/Servers/IIS/IIS/src/Core/IISHttpContext.FeatureCollection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private void ExtraFeatureSet(Type key, object value)
8787

8888
string IHttpRequestFeature.Protocol
8989
{
90-
get => _httpProtocolVersion ??= HttpVersion.GetHttpProtocolVersion();
90+
get => _httpProtocolVersion ??= HttpProtocol.GetHttpProtocol(HttpVersion);
9191
set => _httpProtocolVersion = value;
9292
}
9393

0 commit comments

Comments
 (0)