Skip to content

Commit cb6d83a

Browse files
committed
More compression
1 parent 71c1b11 commit cb6d83a

14 files changed

+586
-218
lines changed

src/Http/Headers/src/HeaderNames.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,9 @@ public static class HeaderNames
225225
/// <summary>Gets the <c>Sec-WebSocket-Version</c> HTTP header name.</summary>
226226
public static readonly string SecWebSocketVersion = "Sec-WebSocket-Version";
227227

228+
/// <summary>Gets the <c>Sec-WebSocket-Extensions</c> HTTP header name.</summary>
229+
public static readonly string SecWebSocketExtensions = "Sec-WebSocket-Extensions";
230+
228231
/// <summary>Gets the <c>Server</c> HTTP header name.</summary>
229232
public static readonly string Server = "Server";
230233

src/Http/Headers/src/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Microsoft.Net.Http.Headers.RangeConditionHeaderValue.RangeConditionHeaderValue(M
55
static readonly Microsoft.Net.Http.Headers.HeaderNames.Baggage -> string!
66
static readonly Microsoft.Net.Http.Headers.HeaderNames.Link -> string!
77
static readonly Microsoft.Net.Http.Headers.HeaderNames.ProxyConnection -> string!
8+
static readonly Microsoft.Net.Http.Headers.HeaderNames.SecWebSocketExtensions -> string!
89
static readonly Microsoft.Net.Http.Headers.HeaderNames.XContentTypeOptions -> string!
910
static readonly Microsoft.Net.Http.Headers.HeaderNames.XPoweredBy -> string!
1011
static readonly Microsoft.Net.Http.Headers.HeaderNames.XUACompatible -> string!

src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ abstract Microsoft.AspNetCore.Http.HttpRequest.ContentType.get -> string?
2323
static Microsoft.AspNetCore.Builder.UseExtensions.Use(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, System.Func<Microsoft.AspNetCore.Http.HttpContext!, Microsoft.AspNetCore.Http.RequestDelegate!, System.Threading.Tasks.Task!>! middleware) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
2424
static Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddleware(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, System.Type! middleware, params object?[]! args) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
2525
static Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.UseMiddleware<TMiddleware>(this Microsoft.AspNetCore.Builder.IApplicationBuilder! app, params object?[]! args) -> Microsoft.AspNetCore.Builder.IApplicationBuilder!
26+
virtual Microsoft.AspNetCore.Http.WebSocketManager.AcceptWebSocketAsync(Microsoft.AspNetCore.Http.WebSocketAcceptContext! acceptContext) -> System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket!>!

src/Http/Http.Abstractions/src/WebSocketManager.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.Net.WebSockets;
67
using System.Threading.Tasks;
78

89
namespace Microsoft.AspNetCore.Http
910
{
1011
/// <summary>
11-
/// Manages the establishment of WebSocket connections for a specific HTTP request.
12+
/// Manages the establishment of WebSocket connections for a specific HTTP request.
1213
/// </summary>
1314
public abstract class WebSocketManager
1415
{
@@ -37,5 +38,12 @@ public virtual Task<WebSocket> AcceptWebSocketAsync()
3738
/// <param name="subProtocol">The sub-protocol to use.</param>
3839
/// <returns>A task representing the completion of the transition.</returns>
3940
public abstract Task<WebSocket> AcceptWebSocketAsync(string? subProtocol);
41+
42+
/// <summary>
43+
///
44+
/// </summary>
45+
/// <param name="acceptContext"></param>
46+
/// <returns></returns>
47+
public virtual Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext acceptContext) => throw new NotImplementedException();
4048
}
4149
}

src/Http/Http.Features/src/IHeaderDictionary.Keyed.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,9 @@ public partial interface IHeaderDictionary
202202
/// <summary>Gets or sets the <c>Sec-WebSocket-Version</c> HTTP header.</summary>
203203
StringValues SecWebSocketVersion { get => this[HeaderNames.SecWebSocketVersion]; set => this[HeaderNames.SecWebSocketVersion] = value; }
204204

205+
/// <summary>Gets or sets the <c>Sec-WebSocket-Extensions</c> HTTP header.</summary>
206+
StringValues SecWebSocketExtensions { get => this[HeaderNames.SecWebSocketExtensions]; set => this[HeaderNames.SecWebSocketExtensions] = value; }
207+
205208
/// <summary>Gets or sets the <c>Server</c> HTTP header.</summary>
206209
StringValues Server { get => this[HeaderNames.Server]; set => this[HeaderNames.Server] = value; }
207210

src/Http/Http.Features/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ Microsoft.AspNetCore.Http.IHeaderDictionary.RetryAfter.get -> Microsoft.Extensio
161161
Microsoft.AspNetCore.Http.IHeaderDictionary.RetryAfter.set -> void
162162
Microsoft.AspNetCore.Http.IHeaderDictionary.SecWebSocketAccept.get -> Microsoft.Extensions.Primitives.StringValues
163163
Microsoft.AspNetCore.Http.IHeaderDictionary.SecWebSocketAccept.set -> void
164+
Microsoft.AspNetCore.Http.IHeaderDictionary.SecWebSocketExtensions.get -> Microsoft.Extensions.Primitives.StringValues
165+
Microsoft.AspNetCore.Http.IHeaderDictionary.SecWebSocketExtensions.set -> void
164166
Microsoft.AspNetCore.Http.IHeaderDictionary.SecWebSocketKey.get -> Microsoft.Extensions.Primitives.StringValues
165167
Microsoft.AspNetCore.Http.IHeaderDictionary.SecWebSocketKey.set -> void
166168
Microsoft.AspNetCore.Http.IHeaderDictionary.SecWebSocketProtocol.get -> Microsoft.Extensions.Primitives.StringValues

src/Http/Http/src/Internal/DefaultWebSocketManager.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,17 @@ public override IList<string> WebSocketRequestedProtocols
6161
}
6262

6363
public override Task<WebSocket> AcceptWebSocketAsync(string? subProtocol)
64+
{
65+
return AcceptWebSocketAsync(new WebSocketAcceptContext() { SubProtocol = subProtocol });
66+
}
67+
68+
public override Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext acceptContext)
6469
{
6570
if (WebSocketFeature == null)
6671
{
6772
throw new NotSupportedException("WebSockets are not supported");
6873
}
69-
return WebSocketFeature.AcceptAsync(new WebSocketAcceptContext() { SubProtocol = subProtocol });
74+
return WebSocketFeature.AcceptAsync(acceptContext);
7075
}
7176

7277
struct FeatureInterfaces

src/Middleware/WebSockets/src/ExtendedWebSocketAcceptContext.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Net.WebSockets;
56
using Microsoft.AspNetCore.Http;
67

78
namespace Microsoft.AspNetCore.WebSockets
@@ -24,5 +25,10 @@ public class ExtendedWebSocketAcceptContext : WebSocketAcceptContext
2425
/// The interval to send pong frames. This is a heart-beat that keeps the connection alive.
2526
/// </summary>
2627
public TimeSpan? KeepAliveInterval { get; set; }
28+
29+
/// <summary>
30+
///
31+
/// </summary>
32+
public WebSocketCreationOptions? WebSocketOptions { get; set; }
2733
}
2834
}

src/Middleware/WebSockets/src/HandshakeHelpers.cs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Globalization;
7+
using System.Net.WebSockets;
58
using System.Security.Cryptography;
69
using System.Text;
710
using Microsoft.AspNetCore.Http;
@@ -72,5 +75,89 @@ public static string CreateResponseKey(string requestKey)
7275

7376
return Convert.ToBase64String(hashedBytes);
7477
}
78+
79+
public static bool ParseDeflateOptions(ReadOnlySpan<char> extension, WebSocketDeflateOptions options, [NotNullWhen(true)] out string? response)
80+
{
81+
response = null;
82+
var builder = new StringBuilder(WebSocketDeflateConstants.MaxExtensionLength);
83+
builder.Append(WebSocketDeflateConstants.Extension);
84+
85+
while (true)
86+
{
87+
int end = extension.IndexOf(';');
88+
ReadOnlySpan<char> value = (end >= 0 ? extension[..end] : extension).Trim();
89+
90+
if (value.Length > 0)
91+
{
92+
if (value.SequenceEqual(WebSocketDeflateConstants.ClientNoContextTakeover))
93+
{
94+
// REVIEW: If someone specifies true for server options, do we allow client to override this?
95+
options.ClientContextTakeover = false;
96+
builder.Append("; ").Append(WebSocketDeflateConstants.ClientNoContextTakeover);
97+
}
98+
else if (value.SequenceEqual(WebSocketDeflateConstants.ServerNoContextTakeover))
99+
{
100+
options.ServerContextTakeover = false;
101+
builder.Append("; ").Append(WebSocketDeflateConstants.ServerNoContextTakeover);
102+
}
103+
else if (value.StartsWith(WebSocketDeflateConstants.ClientMaxWindowBits))
104+
{
105+
var clientMaxWindowBits = ParseWindowBits(value, WebSocketDeflateConstants.ClientMaxWindowBits);
106+
if (clientMaxWindowBits > options.ClientMaxWindowBits)
107+
{
108+
return false;
109+
}
110+
// if client didn't send a value for ClientMaxWindowBits use the value the server set
111+
options.ClientMaxWindowBits = clientMaxWindowBits ?? options.ClientMaxWindowBits;
112+
builder.Append("; ").Append(WebSocketDeflateConstants.ClientMaxWindowBits).Append('=')
113+
.Append(options.ClientMaxWindowBits.ToString(CultureInfo.InvariantCulture));
114+
}
115+
else if (value.StartsWith(WebSocketDeflateConstants.ServerMaxWindowBits))
116+
{
117+
var serverMaxWindowBits = ParseWindowBits(value, WebSocketDeflateConstants.ServerMaxWindowBits);
118+
if (serverMaxWindowBits > options.ServerMaxWindowBits)
119+
{
120+
return false;
121+
}
122+
// if client didn't send a value for ServerMaxWindowBits use the value the server set
123+
options.ServerMaxWindowBits = serverMaxWindowBits ?? options.ServerMaxWindowBits;
124+
125+
builder.Append("; ")
126+
.Append(WebSocketDeflateConstants.ServerMaxWindowBits).Append('=')
127+
.Append(options.ServerMaxWindowBits.ToString(CultureInfo.InvariantCulture));
128+
}
129+
130+
static int? ParseWindowBits(ReadOnlySpan<char> value, string propertyName)
131+
{
132+
var startIndex = value.IndexOf('=');
133+
134+
// parameters can be sent without a value by the client, we'll use the values set by the app developer or the default of 15
135+
if (startIndex < 0)
136+
{
137+
return null;
138+
}
139+
140+
if (!int.TryParse(value[(startIndex + 1)..], NumberStyles.Integer, CultureInfo.InvariantCulture, out int windowBits) ||
141+
windowBits < 9 ||
142+
windowBits > 15)
143+
{
144+
throw new WebSocketException(WebSocketError.HeaderError, $"invalid {propertyName} used: {value[(startIndex + 1)..].ToString()}");
145+
}
146+
147+
return windowBits;
148+
}
149+
}
150+
151+
if (end < 0)
152+
{
153+
break;
154+
}
155+
extension = extension[(end + 1)..];
156+
}
157+
158+
response = builder.ToString();
159+
160+
return true;
161+
}
75162
}
76163
}

src/Middleware/WebSockets/src/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#nullable enable
22
Microsoft.AspNetCore.Builder.WebSocketOptions.AllowedOrigins.get -> System.Collections.Generic.IList<string!>!
3+
Microsoft.AspNetCore.WebSockets.ExtendedWebSocketAcceptContext.WebSocketOptions.get -> System.Net.WebSockets.WebSocketCreationOptions?
4+
Microsoft.AspNetCore.WebSockets.ExtendedWebSocketAcceptContext.WebSocketOptions.set -> void
35
Microsoft.AspNetCore.WebSockets.WebSocketMiddleware.Invoke(Microsoft.AspNetCore.Http.HttpContext! context) -> System.Threading.Tasks.Task!
46
~Microsoft.AspNetCore.WebSockets.WebSocketMiddleware.WebSocketMiddleware(Microsoft.AspNetCore.Http.RequestDelegate! next, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Builder.WebSocketOptions!>! options, Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory) -> void
57
override Microsoft.AspNetCore.WebSockets.ExtendedWebSocketAcceptContext.SubProtocol.get -> string?

0 commit comments

Comments
 (0)