This commit is contained in:
Johnny Z 2018-08-14 03:58:52 +10:00 коммит произвёл Max Gortman
Родитель 9e3a84189f
Коммит 2c0c10939e
119 изменённых файлов: 9718 добавлений и 6 удалений

Просмотреть файл

@ -103,6 +103,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Codecs.Http", "src
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Codecs.Http.Tests", "test\DotNetty.Codecs.Http.Tests\DotNetty.Codecs.Http.Tests.csproj", "{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSockets.Server", "examples\WebSockets.Server\WebSockets.Server.csproj", "{EA387B4B-DAD0-4E34-B8A3-79EA4616726A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebSockets.Client", "examples\WebSockets.Client\WebSockets.Client.csproj", "{3326DB6E-023E-483F-9A1C-5905D3091B57}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -273,6 +277,14 @@ Global
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF}.Release|Any CPU.Build.0 = Release|Any CPU
{EA387B4B-DAD0-4E34-B8A3-79EA4616726A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA387B4B-DAD0-4E34-B8A3-79EA4616726A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA387B4B-DAD0-4E34-B8A3-79EA4616726A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA387B4B-DAD0-4E34-B8A3-79EA4616726A}.Release|Any CPU.Build.0 = Release|Any CPU
{3326DB6E-023E-483F-9A1C-5905D3091B57}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3326DB6E-023E-483F-9A1C-5905D3091B57}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3326DB6E-023E-483F-9A1C-5905D3091B57}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3326DB6E-023E-483F-9A1C-5905D3091B57}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -319,6 +331,8 @@ Global
{A7CACAE7-66E7-43DA-948B-28EB0DDDB582} = {F716F1EF-81EF-4020-914A-5422A13A9E13}
{5F68A5B1-7907-4B16-8AFE-326E9DD7D65B} = {126EA539-4B28-4B07-8B5D-D1D7F794D189}
{16C89E7C-1575-4685-8DFA-8E7E2C6101BF} = {541093F6-616E-43D9-B671-FCD1F9C0A181}
{EA387B4B-DAD0-4E34-B8A3-79EA4616726A} = {F716F1EF-81EF-4020-914A-5422A13A9E13}
{3326DB6E-023E-483F-9A1C-5905D3091B57} = {F716F1EF-81EF-4020-914A-5422A13A9E13}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
{9FE6A783-C20D-4097-9988-4178E2C4CE75} = {126EA539-4B28-4B07-8B5D-D1D7F794D189}

Просмотреть файл

@ -21,5 +21,14 @@ namespace Examples.Common
public static int Port => int.Parse(ExampleHelper.Configuration["port"]);
public static int Size => int.Parse(ExampleHelper.Configuration["size"]);
public static bool UseLibuv
{
get
{
string libuv = ExampleHelper.Configuration["libuv"];
return !string.IsNullOrEmpty(libuv) && bool.Parse(libuv);
}
}
}
}

Просмотреть файл

@ -0,0 +1,139 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace WebSockets.Client
{
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using DotNetty.Transport.Libuv;
using Examples.Common;
class Program
{
static async Task RunClientAsync()
{
var builder = new UriBuilder
{
Scheme = ClientSettings.IsSsl ? "wss" : "ws",
Host = ClientSettings.Host.ToString(),
Port = ClientSettings.Port
};
string path = ExampleHelper.Configuration["path"];
if (!string.IsNullOrEmpty(path))
{
builder.Path = path;
}
Uri uri = builder.Uri;
ExampleHelper.SetConsoleLogger();
bool useLibuv = ClientSettings.UseLibuv;
Console.WriteLine("Transport type : " + (useLibuv ? "Libuv" : "Socket"));
IEventLoopGroup group;
if (useLibuv)
{
group = new EventLoopGroup();
}
else
{
group = new MultithreadEventLoopGroup();
}
X509Certificate2 cert = null;
string targetHost = null;
if (ClientSettings.IsSsl)
{
cert = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
targetHost = cert.GetNameInfo(X509NameType.DnsName, false);
}
try
{
var bootstrap = new Bootstrap();
bootstrap
.Group(group)
.Option(ChannelOption.TcpNodelay, true);
if (useLibuv)
{
bootstrap.Channel<TcpChannel>();
}
else
{
bootstrap.Channel<TcpSocketChannel>();
}
// Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
// If you change it to V00, ping is not supported and remember to change
// HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
var handler =new WebSocketClientHandler(
WebSocketClientHandshakerFactory.NewHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
bootstrap.Handler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
if (cert != null)
{
pipeline.AddLast("tls", new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(targetHost)));
}
pipeline.AddLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
WebSocketClientCompressionHandler.Instance,
handler);
}));
IChannel ch = await bootstrap.ConnectAsync(new IPEndPoint(ClientSettings.Host, ClientSettings.Port));
await handler.HandshakeCompletion;
Console.WriteLine("WebSocket handshake completed.\n");
Console.WriteLine("\t[bye]:Quit \n\t [ping]:Send ping frame\n\t Enter any text and Enter: Send text frame");
while (true)
{
string msg = Console.ReadLine();
if (msg == null)
{
break;
}
else if ("bye".Equals(msg.ToLower()))
{
await ch.WriteAndFlushAsync(new CloseWebSocketFrame());
break;
}
else if ("ping".Equals(msg.ToLower()))
{
var frame = new PingWebSocketFrame(Unpooled.WrappedBuffer(new byte[] { 8, 1, 8, 1 }));
await ch.WriteAndFlushAsync(frame);
}
else
{
WebSocketFrame frame = new TextWebSocketFrame(msg);
await ch.WriteAndFlushAsync(frame);
}
}
await ch.CloseAsync();
}
finally
{
await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
}
}
static void Main() => RunClientAsync().Wait();
}
}

Просмотреть файл

@ -0,0 +1,85 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace WebSockets.Client
{
using System;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Common.Concurrency;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class WebSocketClientHandler : SimpleChannelInboundHandler<object>
{
readonly WebSocketClientHandshaker handshaker;
readonly TaskCompletionSource completionSource;
public WebSocketClientHandler(WebSocketClientHandshaker handshaker)
{
this.handshaker = handshaker;
this.completionSource = new TaskCompletionSource();
}
public Task HandshakeCompletion => this.completionSource.Task;
public override void ChannelActive(IChannelHandlerContext ctx) =>
this.handshaker.HandshakeAsync(ctx.Channel).LinkOutcome(this.completionSource);
public override void ChannelInactive(IChannelHandlerContext context)
{
Console.WriteLine("WebSocket Client disconnected!");
}
protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
{
IChannel ch = ctx.Channel;
if (!this.handshaker.IsHandshakeComplete)
{
try
{
this.handshaker.FinishHandshake(ch, (IFullHttpResponse)msg);
Console.WriteLine("WebSocket Client connected!");
this.completionSource.TryComplete();
}
catch (WebSocketHandshakeException e)
{
Console.WriteLine("WebSocket Client failed to connect");
this.completionSource.TrySetException(e);
}
return;
}
if (msg is IFullHttpResponse response)
{
throw new InvalidOperationException(
$"Unexpected FullHttpResponse (getStatus={response.Status}, content={response.Content.ToString(Encoding.UTF8)})");
}
if (msg is TextWebSocketFrame textFrame)
{
Console.WriteLine($"WebSocket Client received message: {textFrame.Text()}");
}
else if (msg is PongWebSocketFrame)
{
Console.WriteLine("WebSocket Client received pong");
}
else if (msg is CloseWebSocketFrame)
{
Console.WriteLine("WebSocket Client received closing");
ch.CloseAsync();
}
}
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception exception)
{
Console.WriteLine("Exception: " + exception);
this.completionSource.TrySetException(exception);
ctx.CloseAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp1.1;net451</TargetFrameworks>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">1.6.1</NetStandardImplicitPackageVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net451' ">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\shared\dotnetty.com.pfx" Link="dotnetty.com.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetty.Buffers\DotNetty.Buffers.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Codecs.Http\DotNetty.Codecs.Http.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Codecs\DotNetty.Codecs.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Common\DotNetty.Common.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Handlers\DotNetty.Handlers.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Transport.Libuv\DotNetty.Transport.Libuv.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Transport\DotNetty.Transport.csproj" />
<ProjectReference Include="..\Examples.Common\Examples.Common.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,7 @@
{
"ssl": "false",
"host": "127.0.0.1",
"port": "8080",
"path": "/websocket",
"libuv": "true"
}

Просмотреть файл

@ -0,0 +1,124 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace WebSockets.Server
{
using System;
using System.IO;
using System.Net;
using System.Runtime;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using DotNetty.Codecs.Http;
using DotNetty.Common;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using DotNetty.Transport.Libuv;
using Examples.Common;
class Program
{
static Program()
{
ResourceLeakDetector.Level = ResourceLeakDetector.DetectionLevel.Disabled;
}
static async Task RunServerAsync()
{
Console.WriteLine(
$"\n{RuntimeInformation.OSArchitecture} {RuntimeInformation.OSDescription}"
+ $"\n{RuntimeInformation.ProcessArchitecture} {RuntimeInformation.FrameworkDescription}"
+ $"\nProcessor Count : {Environment.ProcessorCount}\n");
bool useLibuv = ServerSettings.UseLibuv;
Console.WriteLine("Transport type : " + (useLibuv ? "Libuv" : "Socket"));
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
}
Console.WriteLine($"Server garbage collection : {(GCSettings.IsServerGC ? "Enabled" : "Disabled")}");
Console.WriteLine($"Current latency mode for garbage collection: {GCSettings.LatencyMode}");
Console.WriteLine("\n");
IEventLoopGroup bossGroup;
IEventLoopGroup workGroup;
if (useLibuv)
{
var dispatcher = new DispatcherEventLoopGroup();
bossGroup = dispatcher;
workGroup = new WorkerEventLoopGroup(dispatcher);
}
else
{
bossGroup = new MultithreadEventLoopGroup(1);
workGroup = new MultithreadEventLoopGroup();
}
X509Certificate2 tlsCertificate = null;
if (ServerSettings.IsSsl)
{
tlsCertificate = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
}
try
{
var bootstrap = new ServerBootstrap();
bootstrap.Group(bossGroup, workGroup);
if (useLibuv)
{
bootstrap.Channel<TcpServerChannel>();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
|| RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
bootstrap
.Option(ChannelOption.SoReuseport, true)
.ChildOption(ChannelOption.SoReuseaddr, true);
}
}
else
{
bootstrap.Channel<TcpServerSocketChannel>();
}
bootstrap
.Option(ChannelOption.SoBacklog, 8192)
.ChildHandler(new ActionChannelInitializer<IChannel>(channel =>
{
IChannelPipeline pipeline = channel.Pipeline;
if (tlsCertificate != null)
{
pipeline.AddLast(TlsHandler.Server(tlsCertificate));
}
pipeline.AddLast(new HttpServerCodec());
pipeline.AddLast(new HttpObjectAggregator(65536));
pipeline.AddLast(new WebSocketServerHandler());
}));
int port = ServerSettings.Port;
IChannel bootstrapChannel = await bootstrap.BindAsync(IPAddress.Loopback, port);
Console.WriteLine("Open your web browser and navigate to "
+ $"{(ServerSettings.IsSsl ? "https" : "http")}"
+ $"://127.0.0.1:{port}/");
Console.WriteLine("Listening on "
+ $"{(ServerSettings.IsSsl ? "wss" : "ws")}"
+ $"://127.0.0.1:{port}/websocket");
Console.ReadLine();
await bootstrapChannel.CloseAsync();
}
finally
{
workGroup.ShutdownGracefullyAsync().Wait();
bossGroup.ShutdownGracefullyAsync().Wait();
}
}
static void Main() => RunServerAsync().Wait();
}
}

Просмотреть файл

@ -0,0 +1,172 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace WebSockets.Server
{
using System.Text;
using DotNetty.Buffers;
static class WebSocketServerBenchmarkPage
{
const string Newline = "\r\n";
public static IByteBuffer GetContent(string webSocketLocation) =>
Unpooled.WrappedBuffer(
Encoding.ASCII.GetBytes(
"<html><head><title>Web Socket Performance Test</title></head>" + Newline +
"<body>" + Newline +
"<h2>WebSocket Performance Test</h2>" + Newline +
"<label>Connection Status:</label>" + Newline +
"<label id=\"connectionLabel\"></label><br />" + Newline +
"<form onsubmit=\"return false;\">" + Newline +
"Message size:" +
"<input type=\"text\" id=\"messageSize\" value=\"1024\"/><br>" + Newline +
"Number of messages:" +
"<input type=\"text\" id=\"nrMessages\" value=\"100000\"/><br>" + Newline +
"Data Type:" +
"<input type=\"radio\" name=\"type\" id=\"typeText\" value=\"text\" checked>text" +
"<input type=\"radio\" name=\"type\" id=\"typeBinary\" value=\"binary\">binary<br>" + Newline +
"Mode:<br>" + Newline +
"<input type=\"radio\" name=\"mode\" id=\"modeSingle\" value=\"single\" checked>" +
"Wait for response after each messages<br>" + Newline +
"<input type=\"radio\" name=\"mode\" id=\"modeAll\" value=\"all\">" +
"Send all messages and then wait for all responses<br>" + Newline +
"<input type=\"checkbox\" id=\"verifiyResponses\">Verify responded messages<br>" + Newline +
"<input type=\"button\" value=\"Start Benchmark\"" + Newline +
" onclick=\"startBenchmark()\" />" + Newline +
"<h3>Output</h3>" + Newline +
"<textarea id=\"output\" style=\"width:500px;height:300px;\"></textarea>" + Newline +
"<br>" + Newline +
"<input type=\"button\" value=\"Clear\" onclick=\"clearText()\">" + Newline +
"</form>" + Newline +
"<script type=\"text/javascript\">" + Newline +
"var benchRunning = false;" + Newline +
"var messageSize = 0;" + Newline +
"var totalMessages = 0;" + Newline +
"var rcvdMessages = 0;" + Newline +
"var isBinary = true;" + Newline +
"var isSingle = true;" + Newline +
"var verifiyResponses = false;" + Newline +
"var benchData = null;" + Newline +
"var startTime;" + Newline +
"var endTime;" + Newline +
"var socket;" + Newline +
"var output = document.getElementById('output');" + Newline +
"var connectionLabel = document.getElementById('connectionLabel');" + Newline +
"if (!window.WebSocket) {" + Newline +
" window.WebSocket = window.MozWebSocket;" + Newline +
'}' + Newline +
"if (window.WebSocket) {" + Newline +
" socket = new WebSocket(\"" + webSocketLocation + "\");" + Newline +
" socket.binaryType = 'arraybuffer';" + Newline +
" socket.onmessage = function(event) {" + Newline +
" if (verifiyResponses) {" + Newline +
" if (isBinary) {" + Newline +
" if (!(event.data instanceof ArrayBuffer) || " + Newline +
" event.data.byteLength != benchData.byteLength) {" + Newline +
" onInvalidResponse(benchData, event.data);" + Newline +
" return;" + Newline +
" } else {" + Newline +
" var v = new Uint8Array(event.data);" + Newline +
" for (var j = 0; j < benchData.byteLength; j++) {" + Newline +
" if (v[j] != benchData[j]) {" + Newline +
" onInvalidResponse(benchData, event.data);" + Newline +
" return;" + Newline +
" }" + Newline +
" }" + Newline +
" }" + Newline +
" } else {" + Newline +
" if (event.data != benchData) {" + Newline +
" onInvalidResponse(benchData, event.data);" + Newline +
" return;" + Newline +
" }" + Newline +
" }" + Newline +
" }" + Newline +
" rcvdMessages++;" + Newline +
" if (rcvdMessages == totalMessages) {" + Newline +
" onFinished();" + Newline +
" } else if (isSingle) {" + Newline +
" socket.send(benchData);" + Newline +
" }" + Newline +
" };" + Newline +
" socket.onopen = function(event) {" + Newline +
" connectionLabel.innerHTML = \"Connected\";" + Newline +
" };" + Newline +
" socket.onclose = function(event) {" + Newline +
" benchRunning = false;" + Newline +
" connectionLabel.innerHTML = \"Disconnected\";" + Newline +
" };" + Newline +
"} else {" + Newline +
" alert(\"Your browser does not support Web Socket.\");" + Newline +
'}' + Newline +
Newline +
"function onInvalidResponse(sent,recvd) {" + Newline +
" socket.close();" + Newline +
" alert(\"Error: Sent data did not match the received data!\");" + Newline +
"}" + Newline +
Newline +
"function clearText() {" + Newline +
" output.value=\"\";" + Newline +
"}" + Newline +
Newline +
"function createBenchData() {" + Newline +
" if (isBinary) {" + Newline +
" benchData = new Uint8Array(messageSize);" + Newline +
" for (var i=0; i < messageSize; i++) {" + Newline +
" benchData[i] += Math.floor(Math.random() * 255);" + Newline +
" }" + Newline +
" } else { " + Newline +
" benchData = \"\";" + Newline +
" for (var i=0; i < messageSize; i++) {" + Newline +
" benchData += String.fromCharCode(Math.floor(Math.random() * (123 - 65) + 65));" + Newline +
" }" + Newline +
" }" + Newline +
"}" + Newline +
Newline +
"function startBenchmark(message) {" + Newline +
" if (!window.WebSocket || benchRunning) { return; }" + Newline +
" if (socket.readyState == WebSocket.OPEN) {" + Newline +
" isBinary = document.getElementById('typeBinary').checked;" + Newline +
" isSingle = document.getElementById('modeSingle').checked;" + Newline +
" verifiyResponses = document.getElementById('verifiyResponses').checked;" + Newline +
" messageSize = parseInt(document.getElementById('messageSize').value);" + Newline +
" totalMessages = parseInt(document.getElementById('nrMessages').value);" + Newline +
" if (isNaN(messageSize) || isNaN(totalMessages)) return;" + Newline +
" createBenchData();" + Newline +
" output.value = output.value + '\\nStarting Benchmark';" + Newline +
" rcvdMessages = 0;" + Newline +
" benchRunning = true;" + Newline +
" startTime = new Date();" + Newline +
" if (isSingle) {" + Newline +
" socket.send(benchData);" + Newline +
" } else {" + Newline +
" for (var i = 0; i < totalMessages; i++) socket.send(benchData);" + Newline +
" }" + Newline +
" } else {" + Newline +
" alert(\"The socket is not open.\");" + Newline +
" }" + Newline +
'}' + Newline +
Newline +
"function onFinished() {" + Newline +
" endTime = new Date();" + Newline +
" var duration = (endTime - startTime) / 1000.0;" + Newline +
" output.value = output.value + '\\nTest took: ' + duration + 's';" + Newline +
" var messagesPerS = totalMessages / duration;" + Newline +
" output.value = output.value + '\\nPerformance: ' + messagesPerS + ' Messages/s';" + Newline +
" output.value = output.value + ' in each direction';" + Newline +
" output.value = output.value + '\\nRound trip: ' + 1000.0/messagesPerS + 'ms';" + Newline +
" var throughput = messageSize * totalMessages / duration;" + Newline +
" var throughputText;" + Newline +
" if (isBinary) throughputText = throughput / (1024*1024) + ' MB/s';" + Newline +
" else throughputText = throughput / (1000*1000) + ' MChars/s';" + Newline +
" output.value = output.value + '\\nThroughput: ' + throughputText;" + Newline +
" output.value = output.value + ' in each direction';" + Newline +
" benchRunning = false;" + Newline +
"}" + Newline +
"</script>" + Newline +
"</body>" + Newline +
"</html>" + Newline));
}
}

Просмотреть файл

@ -0,0 +1,160 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace WebSockets.Server
{
using System;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Codecs.Http;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using Examples.Common;
using static DotNetty.Codecs.Http.HttpVersion;
using static DotNetty.Codecs.Http.HttpResponseStatus;
public sealed class WebSocketServerHandler : SimpleChannelInboundHandler<object>
{
const string WebsocketPath = "/websocket";
WebSocketServerHandshaker handshaker;
protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
{
if (msg is IFullHttpRequest request)
{
this.HandleHttpRequest(ctx, request);
}
else if (msg is WebSocketFrame frame)
{
this.HandleWebSocketFrame(ctx, frame);
}
}
public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();
void HandleHttpRequest(IChannelHandlerContext ctx, IFullHttpRequest req)
{
// Handle a bad request.
if (!req.Result.IsSuccess)
{
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, BadRequest));
return;
}
// Allow only GET methods.
if (!Equals(req.Method, HttpMethod.Get))
{
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, Forbidden));
return;
}
// Send the demo page and favicon.ico
if ("/".Equals(req.Uri))
{
IByteBuffer content = WebSocketServerBenchmarkPage.GetContent(GetWebSocketLocation(req));
var res = new DefaultFullHttpResponse(Http11, OK, content);
res.Headers.Set(HttpHeaderNames.ContentType, "text/html; charset=UTF-8");
HttpUtil.SetContentLength(res, content.ReadableBytes);
SendHttpResponse(ctx, req, res);
return;
}
if ("/favicon.ico".Equals(req.Uri))
{
var res = new DefaultFullHttpResponse(Http11, NotFound);
SendHttpResponse(ctx, req, res);
return;
}
// Handshake
var wsFactory = new WebSocketServerHandshakerFactory(
GetWebSocketLocation(req), null, true, 5 * 1024 * 1024);
this.handshaker = wsFactory.NewHandshaker(req);
if (this.handshaker == null)
{
WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(ctx.Channel);
}
else
{
this.handshaker.HandshakeAsync(ctx.Channel, req);
}
}
void HandleWebSocketFrame(IChannelHandlerContext ctx, WebSocketFrame frame)
{
// Check for closing frame
if (frame is CloseWebSocketFrame)
{
this.handshaker.CloseAsync(ctx.Channel, (CloseWebSocketFrame)frame.Retain());
return;
}
if (frame is PingWebSocketFrame)
{
ctx.WriteAsync(new PongWebSocketFrame((IByteBuffer)frame.Content.Retain()));
return;
}
if (frame is TextWebSocketFrame)
{
// Echo the frame
ctx.WriteAsync(frame.Retain());
return;
}
if (frame is BinaryWebSocketFrame)
{
// Echo the frame
ctx.WriteAsync(frame.Retain());
}
}
static void SendHttpResponse(IChannelHandlerContext ctx, IFullHttpRequest req, IFullHttpResponse res)
{
// Generate an error page if response getStatus code is not OK (200).
if (res.Status.Code != 200)
{
IByteBuffer buf = Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes(res.Status.ToString()));
res.Content.WriteBytes(buf);
buf.Release();
HttpUtil.SetContentLength(res, res.Content.ReadableBytes);
}
// Send the response and close the connection if necessary.
Task task = ctx.Channel.WriteAndFlushAsync(res);
if (!HttpUtil.IsKeepAlive(req) || res.Status.Code != 200)
{
task.ContinueWith((t, c) => ((IChannelHandlerContext)c).CloseAsync(),
ctx, TaskContinuationOptions.ExecuteSynchronously);
}
}
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception e)
{
Console.WriteLine($"{nameof(WebSocketServerHandler)} {0}", e);
ctx.CloseAsync();
}
static string GetWebSocketLocation(IFullHttpRequest req)
{
bool result = req.Headers.TryGet(HttpHeaderNames.Host, out ICharSequence value);
Debug.Assert(result, "Host header does not exist.");
string location= value.ToString() + WebsocketPath;
if (ServerSettings.IsSsl)
{
return "wss://" + location;
}
else
{
return "ws://" + location;
}
}
}
}

Просмотреть файл

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp1.1;net451</TargetFrameworks>
<NetStandardImplicitPackageVersion Condition=" '$(TargetFramework)' == 'netcoreapp1.1' ">1.6.1</NetStandardImplicitPackageVersion>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net451' ">
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\shared\dotnetty.com.pfx" Link="dotnetty.com.pfx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\DotNetty.Buffers\DotNetty.Buffers.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Codecs.Http\DotNetty.Codecs.Http.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Codecs\DotNetty.Codecs.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Common\DotNetty.Common.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Handlers\DotNetty.Handlers.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Transport.Libuv\DotNetty.Transport.Libuv.csproj" />
<ProjectReference Include="..\..\src\DotNetty.Transport\DotNetty.Transport.csproj" />
<ProjectReference Include="..\Examples.Common\Examples.Common.csproj" />
</ItemGroup>
</Project>

Просмотреть файл

@ -0,0 +1,4 @@
{
"port": "8080",
"libuv": "true"
}

Просмотреть файл

@ -338,7 +338,7 @@ namespace DotNetty.Buffers
return length == 0 ? Empty : CopiedBuffer(new string(array, offset, length), encoding);
}
static IByteBuffer CopiedBuffer(string value, Encoding encoding) => ByteBufferUtil.EncodeString0(Allocator, true, value, encoding, 0);
public static IByteBuffer CopiedBuffer(string value, Encoding encoding) => ByteBufferUtil.EncodeString0(Allocator, true, value, encoding, 0);
/// <summary>
/// Creates a new 4-byte big-endian buffer that holds the specified 32-bit integer.

Просмотреть файл

@ -9,6 +9,12 @@ namespace DotNetty.Codecs.Http
public sealed class HttpScheme
{
// Scheme for non-secure HTTP connection.
public static readonly HttpScheme Http = new HttpScheme(80, "http");
// Scheme for secure HTTP connection.
public static readonly HttpScheme Https = new HttpScheme(443, "https");
readonly int port;
readonly AsciiString name;

Просмотреть файл

@ -98,7 +98,6 @@ namespace DotNetty.Codecs.Http
base.SanitizeHeadersBeforeEncode(msg, isAlwaysEmpty);
}
protected override bool IsContentAlwaysEmpty(IHttpResponse msg)
{
this.method = this.serverCodec.queue.Count > 0 ? this.serverCodec.queue.Dequeue() : null;

Просмотреть файл

@ -219,7 +219,6 @@ namespace DotNetty.Codecs.Http
}
// Make sure the CONNECTION header is present.
;
if (!request.Headers.TryGet(HttpHeaderNames.Connection, out ICharSequence connectionHeader))
{
return false;
@ -310,7 +309,6 @@ namespace DotNetty.Codecs.Http
}
if (c == ',')
{
// Add the string and reset the builder for the next protocol.
// Add the string and reset the builder for the next protocol.
protocols.Add(new AsciiString(builder.ToString()));
builder.Length = 0;

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Buffers;
public class BinaryWebSocketFrame : WebSocketFrame
{
public BinaryWebSocketFrame()
: base(Unpooled.Buffer(0))
{
}
public BinaryWebSocketFrame(IByteBuffer binaryData)
: base(binaryData)
{
}
public BinaryWebSocketFrame(bool finalFragment, int rsv, IByteBuffer binaryData)
: base(finalFragment, rsv, binaryData)
{
}
public override IByteBufferHolder Replace(IByteBuffer content) => new BinaryWebSocketFrame(this.IsFinalFragment, this.Rsv, content);
}
}

Просмотреть файл

@ -0,0 +1,95 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Text;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
public class CloseWebSocketFrame : WebSocketFrame
{
public CloseWebSocketFrame()
: base(Unpooled.Buffer(0))
{
}
public CloseWebSocketFrame(int statusCode, ICharSequence reasonText)
: this(true, 0, statusCode, reasonText)
{
}
public CloseWebSocketFrame(bool finalFragment, int rsv)
: this(finalFragment, rsv, Unpooled.Buffer(0))
{
}
public CloseWebSocketFrame(bool finalFragment, int rsv, int statusCode, ICharSequence reasonText)
: base(finalFragment, rsv, NewBinaryData(statusCode, reasonText))
{
}
static IByteBuffer NewBinaryData(int statusCode, ICharSequence reasonText)
{
if (reasonText == null)
{
reasonText = StringCharSequence.Empty;
}
IByteBuffer binaryData = Unpooled.Buffer(2 + reasonText.Count);
binaryData.WriteShort(statusCode);
if (reasonText.Count > 0)
{
binaryData.WriteCharSequence(reasonText, Encoding.UTF8);
}
binaryData.SetReaderIndex(0);
return binaryData;
}
public CloseWebSocketFrame(bool finalFragment, int rsv, IByteBuffer binaryData)
: base(finalFragment, rsv, binaryData)
{
}
///<summary>
/// Returns the closing status code as per http://tools.ietf.org/html/rfc6455#section-7.4 RFC 6455.
/// If a getStatus code is set, -1 is returned.
/// </summary>
public int StatusCode()
{
IByteBuffer binaryData = this.Content;
if (binaryData == null || binaryData.Capacity == 0)
{
return -1;
}
binaryData.SetReaderIndex(0);
int statusCode = binaryData.ReadShort();
binaryData.SetReaderIndex(0);
return statusCode;
}
///<summary>
/// Returns the reason text as per http://tools.ietf.org/html/rfc6455#section-7.4 RFC 6455
/// If a reason text is not supplied, an empty string is returned.
/// </summary>
public ICharSequence ReasonText()
{
IByteBuffer binaryData = this.Content;
if (binaryData == null || binaryData.Capacity <= 2)
{
return StringCharSequence.Empty;
}
binaryData.SetReaderIndex(2);
string reasonText = binaryData.ToString(Encoding.UTF8);
binaryData.SetReaderIndex(0);
return new StringCharSequence(reasonText);
}
public override IByteBufferHolder Replace(IByteBuffer content) => new CloseWebSocketFrame(this.IsFinalFragment, this.Rsv, content);
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Text;
using DotNetty.Buffers;
public class ContinuationWebSocketFrame : WebSocketFrame
{
public ContinuationWebSocketFrame()
: this(Unpooled.Buffer(0))
{
}
public ContinuationWebSocketFrame(IByteBuffer binaryData)
: base(binaryData)
{
}
public ContinuationWebSocketFrame(bool finalFragment, int rsv, IByteBuffer binaryData)
: base(finalFragment, rsv, binaryData)
{
}
public ContinuationWebSocketFrame(bool finalFragment, int rsv, string text)
: this(finalFragment, rsv, FromText(text))
{
}
public string Text() => this.Content.ToString(Encoding.UTF8);
static IByteBuffer FromText(string text) => string.IsNullOrEmpty(text)
? Unpooled.Empty : Unpooled.CopiedBuffer(text, Encoding.UTF8);
public override IByteBufferHolder Replace(IByteBuffer content) => new ContinuationWebSocketFrame(this.IsFinalFragment, this.Rsv, content);
}
}

Просмотреть файл

@ -0,0 +1,134 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Codecs.Compression;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
abstract class DeflateDecoder : WebSocketExtensionDecoder
{
internal static readonly byte[] FrameTail = { 0x00, 0x00, 0xff, 0xff };
readonly bool noContext;
EmbeddedChannel decoder;
protected DeflateDecoder(bool noContext)
{
this.noContext = noContext;
}
protected abstract bool AppendFrameTail(WebSocketFrame msg);
protected abstract int NewRsv(WebSocketFrame msg);
protected override void Decode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> output)
{
if (this.decoder == null)
{
if (!(msg is TextWebSocketFrame) && !(msg is BinaryWebSocketFrame))
{
throw new CodecException($"unexpected initial frame type: {msg.GetType().Name}");
}
this.decoder = new EmbeddedChannel(ZlibCodecFactory.NewZlibDecoder(ZlibWrapper.None));
}
bool readable = msg.Content.IsReadable();
this.decoder.WriteInbound(msg.Content.Retain());
if (this.AppendFrameTail(msg))
{
this.decoder.WriteInbound(Unpooled.WrappedBuffer(FrameTail));
}
CompositeByteBuffer compositeUncompressedContent = ctx.Allocator.CompositeDirectBuffer();
for (;;)
{
var partUncompressedContent = this.decoder.ReadInbound<IByteBuffer>();
if (partUncompressedContent == null)
{
break;
}
if (!partUncompressedContent.IsReadable())
{
partUncompressedContent.Release();
continue;
}
compositeUncompressedContent.AddComponent(true, partUncompressedContent);
}
// Correctly handle empty frames
// See https://github.com/netty/netty/issues/4348
if (readable && compositeUncompressedContent.NumComponents <= 0)
{
compositeUncompressedContent.Release();
throw new CodecException("cannot read uncompressed buffer");
}
if (msg.IsFinalFragment && this.noContext)
{
this.Cleanup();
}
WebSocketFrame outMsg;
if (msg is TextWebSocketFrame)
{
outMsg = new TextWebSocketFrame(msg.IsFinalFragment, this.NewRsv(msg), compositeUncompressedContent);
}
else if (msg is BinaryWebSocketFrame)
{
outMsg = new BinaryWebSocketFrame(msg.IsFinalFragment, this.NewRsv(msg), compositeUncompressedContent);
}
else if (msg is ContinuationWebSocketFrame)
{
outMsg = new ContinuationWebSocketFrame(msg.IsFinalFragment, this.NewRsv(msg), compositeUncompressedContent);
}
else
{
throw new CodecException($"unexpected frame type: {msg.GetType().Name}");
}
output.Add(outMsg);
}
public override void HandlerRemoved(IChannelHandlerContext ctx)
{
this.Cleanup();
base.HandlerRemoved(ctx);
}
public override void ChannelInactive(IChannelHandlerContext ctx)
{
this.Cleanup();
base.ChannelInactive(ctx);
}
void Cleanup()
{
if (this.decoder != null)
{
// Clean-up the previous encoder if not cleaned up correctly.
if (this.decoder.Finish())
{
for (;;)
{
var buf = this.decoder.ReadOutbound<IByteBuffer>();
if (buf == null)
{
break;
}
// Release the buffer
buf.Release();
}
}
this.decoder = null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,136 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Codecs.Compression;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
using static DeflateDecoder;
abstract class DeflateEncoder : WebSocketExtensionEncoder
{
readonly int compressionLevel;
readonly int windowSize;
readonly bool noContext;
EmbeddedChannel encoder;
protected DeflateEncoder(int compressionLevel, int windowSize, bool noContext)
{
this.compressionLevel = compressionLevel;
this.windowSize = windowSize;
this.noContext = noContext;
}
protected abstract int Rsv(WebSocketFrame msg);
protected abstract bool RemoveFrameTail(WebSocketFrame msg);
protected override void Encode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> output)
{
if (this.encoder == null)
{
this.encoder = new EmbeddedChannel(
ZlibCodecFactory.NewZlibEncoder(
ZlibWrapper.None,
this.compressionLevel,
this.windowSize,
8));
}
this.encoder.WriteOutbound(msg.Content.Retain());
CompositeByteBuffer fullCompressedContent = ctx.Allocator.CompositeBuffer();
for (;;)
{
var partCompressedContent = this.encoder.ReadOutbound<IByteBuffer>();
if (partCompressedContent == null)
{
break;
}
if (!partCompressedContent.IsReadable())
{
partCompressedContent.Release();
continue;
}
fullCompressedContent.AddComponent(true, partCompressedContent);
}
if (fullCompressedContent.NumComponents <= 0)
{
fullCompressedContent.Release();
throw new CodecException("cannot read compressed buffer");
}
if (msg.IsFinalFragment && this.noContext)
{
this.Cleanup();
}
IByteBuffer compressedContent;
if (this.RemoveFrameTail(msg))
{
int realLength = fullCompressedContent.ReadableBytes - FrameTail.Length;
compressedContent = fullCompressedContent.Slice(0, realLength);
}
else
{
compressedContent = fullCompressedContent;
}
WebSocketFrame outMsg;
if (msg is TextWebSocketFrame)
{
outMsg = new TextWebSocketFrame(msg.IsFinalFragment, this.Rsv(msg), compressedContent);
}
else if (msg is BinaryWebSocketFrame)
{
outMsg = new BinaryWebSocketFrame(msg.IsFinalFragment, this.Rsv(msg), compressedContent);
}
else if (msg is ContinuationWebSocketFrame)
{
outMsg = new ContinuationWebSocketFrame(msg.IsFinalFragment, this.Rsv(msg), compressedContent);
}
else
{
throw new CodecException($"unexpected frame type: {msg.GetType().Name}");
}
output.Add(outMsg);
}
public override void HandlerRemoved(IChannelHandlerContext ctx)
{
this.Cleanup();
base.HandlerRemoved(ctx);
}
void Cleanup()
{
if (this.encoder != null)
{
// Clean-up the previous encoder if not cleaned up correctly.
if (this.encoder.Finish())
{
for (;;)
{
var buf = this.encoder.ReadOutbound<IByteBuffer>();
if (buf == null)
{
break;
}
// Release the buffer
buf.Release();
}
}
this.encoder = null;
}
}
}
}

Просмотреть файл

@ -0,0 +1,69 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System;
using System.Collections.Generic;
using static DeflateFrameServerExtensionHandshaker;
public sealed class DeflateFrameClientExtensionHandshaker : IWebSocketClientExtensionHandshaker
{
readonly int compressionLevel;
readonly bool useWebkitExtensionName;
public DeflateFrameClientExtensionHandshaker(bool useWebkitExtensionName)
: this(6, useWebkitExtensionName)
{
}
public DeflateFrameClientExtensionHandshaker(int compressionLevel, bool useWebkitExtensionName)
{
if (compressionLevel < 0 || compressionLevel > 9)
{
throw new ArgumentException($"compressionLevel: {compressionLevel} (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
this.useWebkitExtensionName = useWebkitExtensionName;
}
public WebSocketExtensionData NewRequestData() => new WebSocketExtensionData(
this.useWebkitExtensionName ? XWebkitDeflateFrameExtension : DeflateFrameExtension,
new Dictionary<string, string>());
public IWebSocketClientExtension HandshakeExtension(WebSocketExtensionData extensionData)
{
if (!XWebkitDeflateFrameExtension.Equals(extensionData.Name) &&
!DeflateFrameExtension.Equals(extensionData.Name))
{
return null;
}
if (extensionData.Parameters.Count == 0)
{
return new DeflateFrameClientExtension(this.compressionLevel);
}
else
{
return null;
}
}
sealed class DeflateFrameClientExtension : IWebSocketClientExtension
{
readonly int compressionLevel;
public DeflateFrameClientExtension(int compressionLevel)
{
this.compressionLevel = compressionLevel;
}
public int Rsv => WebSocketRsv.Rsv1;
public WebSocketExtensionEncoder NewExtensionEncoder() => new PerFrameDeflateEncoder(this.compressionLevel, 15, false);
public WebSocketExtensionDecoder NewExtensionDecoder() => new PerFrameDeflateDecoder(false);
}
}
}

Просмотреть файл

@ -0,0 +1,68 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System;
using System.Collections.Generic;
public sealed class DeflateFrameServerExtensionHandshaker : IWebSocketServerExtensionHandshaker
{
internal static readonly string XWebkitDeflateFrameExtension = "x-webkit-deflate-frame";
internal static readonly string DeflateFrameExtension = "deflate-frame";
readonly int compressionLevel;
public DeflateFrameServerExtensionHandshaker()
: this(6)
{
}
public DeflateFrameServerExtensionHandshaker(int compressionLevel)
{
if (compressionLevel < 0 || compressionLevel > 9)
{
throw new ArgumentException($"compressionLevel: {compressionLevel} (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
}
public IWebSocketServerExtension HandshakeExtension(WebSocketExtensionData extensionData)
{
if (!XWebkitDeflateFrameExtension.Equals(extensionData.Name)
&& !DeflateFrameExtension.Equals(extensionData.Name))
{
return null;
}
if (extensionData.Parameters.Count == 0)
{
return new DeflateFrameServerExtension(this.compressionLevel, extensionData.Name);
}
else
{
return null;
}
}
sealed class DeflateFrameServerExtension : IWebSocketServerExtension
{
readonly string extensionName;
readonly int compressionLevel;
public DeflateFrameServerExtension(int compressionLevel, string extensionName)
{
this.extensionName = extensionName;
this.compressionLevel = compressionLevel;
}
public int Rsv => WebSocketRsv.Rsv1;
public WebSocketExtensionEncoder NewExtensionEncoder() => new PerFrameDeflateEncoder(this.compressionLevel, 15, false);
public WebSocketExtensionDecoder NewExtensionDecoder() => new PerFrameDeflateDecoder(false);
public WebSocketExtensionData NewReponseData() => new WebSocketExtensionData(this.extensionName, new Dictionary<string, string>());
}
}
}

Просмотреть файл

@ -0,0 +1,21 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
class PerFrameDeflateDecoder : DeflateDecoder
{
public PerFrameDeflateDecoder(bool noContext)
: base(noContext)
{
}
public override bool AcceptInboundMessage(object msg) =>
(msg is TextWebSocketFrame || msg is BinaryWebSocketFrame || msg is ContinuationWebSocketFrame)
&& (((WebSocketFrame) msg).Rsv & WebSocketRsv.Rsv1) > 0;
protected override int NewRsv(WebSocketFrame msg) => msg.Rsv ^ WebSocketRsv.Rsv1;
protected override bool AppendFrameTail(WebSocketFrame msg) => true;
}
}

Просмотреть файл

@ -0,0 +1,22 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
class PerFrameDeflateEncoder : DeflateEncoder
{
public PerFrameDeflateEncoder(int compressionLevel, int windowSize, bool noContext)
: base(compressionLevel, windowSize, noContext)
{
}
public override bool AcceptOutboundMessage(object msg) =>
(msg is TextWebSocketFrame || msg is BinaryWebSocketFrame || msg is ContinuationWebSocketFrame)
&& ((WebSocketFrame)msg).Content.ReadableBytes > 0
&& (((WebSocketFrame)msg).Rsv & WebSocketRsv.Rsv1) == 0;
protected override int Rsv(WebSocketFrame msg) => msg.Rsv | WebSocketRsv.Rsv1;
protected override bool RemoveFrameTail(WebSocketFrame msg) => true;
}
}

Просмотреть файл

@ -0,0 +1,179 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System;
using System.Collections.Generic;
using DotNetty.Codecs.Compression;
using static PerMessageDeflateServerExtensionHandshaker;
public sealed class PerMessageDeflateClientExtensionHandshaker : IWebSocketClientExtensionHandshaker
{
readonly int compressionLevel;
readonly bool allowClientWindowSize;
readonly int requestedServerWindowSize;
readonly bool allowClientNoContext;
readonly bool requestedServerNoContext;
public PerMessageDeflateClientExtensionHandshaker()
: this(6, ZlibCodecFactory.IsSupportingWindowSizeAndMemLevel, MaxWindowSize, false, false)
{
}
public PerMessageDeflateClientExtensionHandshaker(int compressionLevel,
bool allowClientWindowSize, int requestedServerWindowSize,
bool allowClientNoContext, bool requestedServerNoContext)
{
if (requestedServerWindowSize > MaxWindowSize || requestedServerWindowSize < MinWindowSize)
{
throw new ArgumentException($"requestedServerWindowSize: {requestedServerWindowSize} (expected: 8-15)");
}
if (compressionLevel < 0 || compressionLevel > 9)
{
throw new ArgumentException($"compressionLevel: {compressionLevel} (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
this.allowClientWindowSize = allowClientWindowSize;
this.requestedServerWindowSize = requestedServerWindowSize;
this.allowClientNoContext = allowClientNoContext;
this.requestedServerNoContext = requestedServerNoContext;
}
public WebSocketExtensionData NewRequestData()
{
var parameters = new Dictionary<string, string>(4);
if (this.requestedServerWindowSize != MaxWindowSize)
{
parameters.Add(ServerNoContext, null);
}
if (this.allowClientNoContext)
{
parameters.Add(ClientNoContext, null);
}
if (this.requestedServerWindowSize != MaxWindowSize)
{
parameters.Add(ServerMaxWindow, Convert.ToString(this.requestedServerWindowSize));
}
if (this.allowClientWindowSize)
{
parameters.Add(ClientMaxWindow, null);
}
return new WebSocketExtensionData(PerMessageDeflateExtension, parameters);
}
public IWebSocketClientExtension HandshakeExtension(WebSocketExtensionData extensionData)
{
if (!PerMessageDeflateExtension.Equals(extensionData.Name))
{
return null;
}
bool succeed = true;
int clientWindowSize = MaxWindowSize;
int serverWindowSize = MaxWindowSize;
bool serverNoContext = false;
bool clientNoContext = false;
foreach (KeyValuePair<string, string> parameter in extensionData.Parameters)
{
if (ClientMaxWindow.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// allowed client_window_size_bits
if (this.allowClientWindowSize)
{
clientWindowSize = int.Parse(parameter.Value);
}
else
{
succeed = false;
}
}
else if (ServerMaxWindow.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// acknowledged server_window_size_bits
serverWindowSize = int.Parse(parameter.Value);
if (clientWindowSize > MaxWindowSize || clientWindowSize < MinWindowSize)
{
succeed = false;
}
}
else if (ClientNoContext.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// allowed client_no_context_takeover
if (this.allowClientNoContext)
{
clientNoContext = true;
}
else
{
succeed = false;
}
}
else if (ServerNoContext.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// acknowledged server_no_context_takeover
if (this.requestedServerNoContext)
{
serverNoContext = true;
}
else
{
succeed = false;
}
}
else
{
// unknown parameter
succeed = false;
}
if (!succeed)
{
break;
}
}
if ((this.requestedServerNoContext && !serverNoContext)
|| this.requestedServerWindowSize != serverWindowSize)
{
succeed = false;
}
if (succeed)
{
return new WebSocketPermessageDeflateExtension(serverNoContext, serverWindowSize,
clientNoContext, this.compressionLevel);
}
else
{
return null;
}
}
sealed class WebSocketPermessageDeflateExtension : IWebSocketClientExtension
{
readonly bool serverNoContext;
readonly int serverWindowSize;
readonly bool clientNoContext;
readonly int compressionLevel;
public int Rsv => WebSocketRsv.Rsv1;
public WebSocketPermessageDeflateExtension(bool serverNoContext, int serverWindowSize,
bool clientNoContext, int compressionLevel)
{
this.serverNoContext = serverNoContext;
this.serverWindowSize = serverWindowSize;
this.clientNoContext = clientNoContext;
this.compressionLevel = compressionLevel;
}
public WebSocketExtensionEncoder NewExtensionEncoder() =>
new PerMessageDeflateEncoder(this.compressionLevel, this.serverWindowSize, this.serverNoContext);
public WebSocketExtensionDecoder NewExtensionDecoder() => new PerMessageDeflateDecoder(this.clientNoContext);
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Transport.Channels;
class PerMessageDeflateDecoder : DeflateDecoder
{
bool compressing;
public PerMessageDeflateDecoder(bool noContext)
: base(noContext)
{
}
public override bool AcceptInboundMessage(object msg) =>
((msg is TextWebSocketFrame || msg is BinaryWebSocketFrame)
&& (((WebSocketFrame)msg).Rsv & WebSocketRsv.Rsv1) > 0)
|| (msg is ContinuationWebSocketFrame && this.compressing);
protected override int NewRsv(WebSocketFrame msg) =>
(msg.Rsv & WebSocketRsv.Rsv1) > 0 ? msg.Rsv ^ WebSocketRsv.Rsv1 : msg.Rsv;
protected override bool AppendFrameTail(WebSocketFrame msg) => msg.IsFinalFragment;
protected override void Decode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> output)
{
base.Decode(ctx, msg, output);
if (msg.IsFinalFragment)
{
this.compressing = false;
}
else if (msg is TextWebSocketFrame || msg is BinaryWebSocketFrame)
{
this.compressing = true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Transport.Channels;
class PerMessageDeflateEncoder : DeflateEncoder
{
bool compressing;
public PerMessageDeflateEncoder(int compressionLevel, int windowSize, bool noContext)
: base(compressionLevel, windowSize, noContext)
{
}
public override bool AcceptOutboundMessage(object msg) =>
((msg is TextWebSocketFrame || msg is BinaryWebSocketFrame)
&& (((WebSocketFrame) msg).Rsv & WebSocketRsv.Rsv1) == 0)
|| (msg is ContinuationWebSocketFrame && this.compressing);
protected override int Rsv(WebSocketFrame msg) =>
msg is TextWebSocketFrame || msg is BinaryWebSocketFrame
? msg.Rsv | WebSocketRsv.Rsv1
: msg.Rsv;
protected override bool RemoveFrameTail(WebSocketFrame msg) => msg.IsFinalFragment;
protected override void Encode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> output)
{
base.Encode(ctx, msg, output);
if (msg.IsFinalFragment)
{
this.compressing = false;
}
else if (msg is TextWebSocketFrame || msg is BinaryWebSocketFrame)
{
this.compressing = true;
}
}
}
}

Просмотреть файл

@ -0,0 +1,174 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
using System;
using System.Collections.Generic;
using DotNetty.Codecs.Compression;
public sealed class PerMessageDeflateServerExtensionHandshaker : IWebSocketServerExtensionHandshaker
{
public static readonly int MinWindowSize = 8;
public static readonly int MaxWindowSize = 15;
internal static readonly string PerMessageDeflateExtension = "permessage-deflate";
internal static readonly string ClientMaxWindow = "client_max_window_bits";
internal static readonly string ServerMaxWindow = "server_max_window_bits";
internal static readonly string ClientNoContext = "client_no_context_takeover";
internal static readonly string ServerNoContext = "server_no_context_takeover";
readonly int compressionLevel;
readonly bool allowServerWindowSize;
readonly int preferredClientWindowSize;
readonly bool allowServerNoContext;
readonly bool preferredClientNoContext;
public PerMessageDeflateServerExtensionHandshaker()
: this(6, ZlibCodecFactory.IsSupportingWindowSizeAndMemLevel, MaxWindowSize, false, false)
{
}
public PerMessageDeflateServerExtensionHandshaker(int compressionLevel,
bool allowServerWindowSize, int preferredClientWindowSize,
bool allowServerNoContext, bool preferredClientNoContext)
{
if (preferredClientWindowSize > MaxWindowSize || preferredClientWindowSize < MinWindowSize)
{
throw new ArgumentException($"preferredServerWindowSize: {preferredClientWindowSize} (expected: 8-15)");
}
if (compressionLevel < 0 || compressionLevel > 9)
{
throw new ArgumentException($"compressionLevel: {compressionLevel} (expected: 0-9)");
}
this.compressionLevel = compressionLevel;
this.allowServerWindowSize = allowServerWindowSize;
this.preferredClientWindowSize = preferredClientWindowSize;
this.allowServerNoContext = allowServerNoContext;
this.preferredClientNoContext = preferredClientNoContext;
}
public IWebSocketServerExtension HandshakeExtension(WebSocketExtensionData extensionData)
{
if (!PerMessageDeflateExtension.Equals(extensionData.Name))
{
return null;
}
bool deflateEnabled = true;
int clientWindowSize = MaxWindowSize;
int serverWindowSize = MaxWindowSize;
bool serverNoContext = false;
bool clientNoContext = false;
foreach (KeyValuePair<string, string> parameter in extensionData.Parameters)
{
if (ClientMaxWindow.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// use preferred clientWindowSize because client is compatible with customization
clientWindowSize = this.preferredClientWindowSize;
}
else if (ServerMaxWindow.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// use provided windowSize if it is allowed
if (this.allowServerWindowSize)
{
serverWindowSize = int.Parse(parameter.Value);
if (serverWindowSize > MaxWindowSize || serverWindowSize < MinWindowSize)
{
deflateEnabled = false;
}
}
else
{
deflateEnabled = false;
}
}
else if (ClientNoContext.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// use preferred clientNoContext because client is compatible with customization
clientNoContext = this.preferredClientNoContext;
}
else if (ServerNoContext.Equals(parameter.Key, StringComparison.OrdinalIgnoreCase))
{
// use server no context if allowed
if (this.allowServerNoContext)
{
serverNoContext = true;
}
else
{
deflateEnabled = false;
}
}
else
{
// unknown parameter
deflateEnabled = false;
}
if (!deflateEnabled)
{
break;
}
}
if (deflateEnabled)
{
return new WebSocketPermessageDeflateExtension(this.compressionLevel, serverNoContext,
serverWindowSize, clientNoContext, clientWindowSize);
}
else
{
return null;
}
}
sealed class WebSocketPermessageDeflateExtension : IWebSocketServerExtension
{
readonly int compressionLevel;
readonly bool serverNoContext;
readonly int serverWindowSize;
readonly bool clientNoContext;
readonly int clientWindowSize;
public WebSocketPermessageDeflateExtension(int compressionLevel, bool serverNoContext,
int serverWindowSize, bool clientNoContext, int clientWindowSize)
{
this.compressionLevel = compressionLevel;
this.serverNoContext = serverNoContext;
this.serverWindowSize = serverWindowSize;
this.clientNoContext = clientNoContext;
this.clientWindowSize = clientWindowSize;
}
public int Rsv => WebSocketRsv.Rsv1;
public WebSocketExtensionEncoder NewExtensionEncoder() =>
new PerMessageDeflateEncoder(this.compressionLevel, this.clientWindowSize, this.clientNoContext);
public WebSocketExtensionDecoder NewExtensionDecoder() => new PerMessageDeflateDecoder(this.serverNoContext);
public WebSocketExtensionData NewReponseData()
{
var parameters = new Dictionary<string, string>(4);
if (this.serverNoContext)
{
parameters.Add(ServerNoContext, null);
}
if (this.clientNoContext)
{
parameters.Add(ClientNoContext, null);
}
if (this.serverWindowSize != MaxWindowSize)
{
parameters.Add(ServerMaxWindow, Convert.ToString(this.serverWindowSize));
}
if (this.clientWindowSize != MaxWindowSize)
{
parameters.Add(ClientMaxWindow, Convert.ToString(this.clientWindowSize));
}
return new WebSocketExtensionData(PerMessageDeflateExtension, parameters);
}
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
public sealed class WebSocketClientCompressionHandler : WebSocketClientExtensionHandler
{
public static readonly WebSocketClientCompressionHandler Instance = new WebSocketClientCompressionHandler();
public override bool IsSharable => true;
WebSocketClientCompressionHandler()
: base(new PerMessageDeflateClientExtensionHandshaker(),
new DeflateFrameClientExtensionHandshaker(false),
new DeflateFrameClientExtensionHandshaker(true))
{
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions.Compression
{
public class WebSocketServerCompressionHandler : WebSocketServerExtensionHandler
{
public WebSocketServerCompressionHandler()
: base(new PerMessageDeflateServerExtensionHandshaker(), new DeflateFrameServerExtensionHandshaker())
{
}
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
public interface IWebSocketClientExtension : IWebSocketExtension
{
}
}

Просмотреть файл

@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
public interface IWebSocketClientExtensionHandshaker
{
WebSocketExtensionData NewRequestData();
IWebSocketClientExtension HandshakeExtension(WebSocketExtensionData extensionData);
}
}

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
public interface IWebSocketExtension
{
/// <summary>
/// The reserved bit value to ensure that no other extension should interfere.
/// </summary>
int Rsv { get; }
WebSocketExtensionEncoder NewExtensionEncoder();
WebSocketExtensionDecoder NewExtensionDecoder();
}
public static class WebSocketRsv
{
public static readonly int Rsv1 = 0x04;
public static readonly int Rsv2 = 0x02;
public static readonly int Rsv3 = 0x01;
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
public interface IWebSocketServerExtension : IWebSocketExtension
{
WebSocketExtensionData NewReponseData();
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
public interface IWebSocketServerExtensionHandshaker
{
IWebSocketServerExtension HandshakeExtension(WebSocketExtensionData extensionData);
}
}

Просмотреть файл

@ -0,0 +1,101 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class WebSocketClientExtensionHandler : ChannelHandlerAdapter
{
readonly List<IWebSocketClientExtensionHandshaker> extensionHandshakers;
public WebSocketClientExtensionHandler(params IWebSocketClientExtensionHandshaker[] extensionHandshakers)
{
Contract.Requires(extensionHandshakers != null && extensionHandshakers.Length > 0);
this.extensionHandshakers = new List<IWebSocketClientExtensionHandshaker>(extensionHandshakers);
}
public override Task WriteAsync(IChannelHandlerContext ctx, object msg)
{
if (msg is IHttpRequest request && WebSocketExtensionUtil.IsWebsocketUpgrade(request.Headers))
{
string headerValue = null;
if (request.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value))
{
headerValue = value.ToString();
}
foreach (IWebSocketClientExtensionHandshaker extensionHandshaker in this.extensionHandshakers)
{
WebSocketExtensionData extensionData = extensionHandshaker.NewRequestData();
headerValue = WebSocketExtensionUtil.AppendExtension(headerValue,
extensionData.Name, extensionData.Parameters);
}
request.Headers.Set(HttpHeaderNames.SecWebsocketExtensions, headerValue);
}
return base.WriteAsync(ctx, msg);
}
public override void ChannelRead(IChannelHandlerContext ctx, object msg)
{
if (msg is IHttpResponse response
&& WebSocketExtensionUtil.IsWebsocketUpgrade(response.Headers))
{
string extensionsHeader = null;
if (response.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value))
{
extensionsHeader = value.ToString();
}
if (extensionsHeader != null)
{
List<WebSocketExtensionData> extensions =
WebSocketExtensionUtil.ExtractExtensions(extensionsHeader);
var validExtensions = new List<IWebSocketClientExtension>(extensions.Count);
int rsv = 0;
foreach (WebSocketExtensionData extensionData in extensions)
{
IWebSocketClientExtension validExtension = null;
foreach (IWebSocketClientExtensionHandshaker extensionHandshaker in this.extensionHandshakers)
{
validExtension = extensionHandshaker.HandshakeExtension(extensionData);
if (validExtension != null)
{
break;
}
}
if (validExtension != null && (validExtension.Rsv & rsv) == 0)
{
rsv = rsv | validExtension.Rsv;
validExtensions.Add(validExtension);
}
else
{
throw new CodecException($"invalid WebSocket Extension handshake for \"{extensionsHeader}\"");
}
}
foreach (IWebSocketClientExtension validExtension in validExtensions)
{
WebSocketExtensionDecoder decoder = validExtension.NewExtensionDecoder();
WebSocketExtensionEncoder encoder = validExtension.NewExtensionEncoder();
ctx.Channel.Pipeline.AddAfter(ctx.Name, decoder.GetType().Name, decoder);
ctx.Channel.Pipeline.AddAfter(ctx.Name, encoder.GetType().Name, encoder);
}
}
ctx.Channel.Pipeline.Remove(ctx.Name);
}
base.ChannelRead(ctx, msg);
}
}
}

Просмотреть файл

@ -0,0 +1,28 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
using System.Collections.Generic;
using System.Diagnostics.Contracts;
public sealed class WebSocketExtensionData
{
readonly string name;
readonly Dictionary<string, string> parameters;
public WebSocketExtensionData(string name, IDictionary<string, string> parameters)
{
Contract.Requires(name != null);
Contract.Requires(parameters != null);
this.name = name;
this.parameters = new Dictionary<string, string>(parameters);
}
public string Name => this.name;
public Dictionary<string, string> Parameters => this.parameters;
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
public abstract class WebSocketExtensionDecoder : MessageToMessageDecoder<WebSocketFrame>
{
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
public abstract class WebSocketExtensionEncoder : MessageToMessageEncoder<WebSocketFrame>
{
}
}

Просмотреть файл

@ -0,0 +1,84 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
public static class WebSocketExtensionUtil
{
const char ExtensionSeparator = ',';
const char ParameterSeparator = ';';
const char ParameterEqual = '=';
static readonly Regex Parameter = new Regex("^([^=]+)(=[\\\"]?([^\\\"]+)[\\\"]?)?$", RegexOptions.Compiled);
internal static bool IsWebsocketUpgrade(HttpHeaders headers) =>
headers.ContainsValue(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade, true)
&& headers.Contains(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket, true);
public static List<WebSocketExtensionData> ExtractExtensions(string extensionHeader)
{
string[] rawExtensions = extensionHeader.Split(ExtensionSeparator);
if (rawExtensions.Length > 0)
{
var extensions = new List<WebSocketExtensionData>(rawExtensions.Length);
foreach (string rawExtension in rawExtensions)
{
string[] extensionParameters = rawExtension.Split(ParameterSeparator);
string name = extensionParameters[0].Trim();
Dictionary<string, string> parameters;
if (extensionParameters.Length > 1)
{
parameters = new Dictionary<string, string>(extensionParameters.Length - 1);
for (int i = 1; i < extensionParameters.Length; i++)
{
string parameter = extensionParameters[i].Trim();
Match match = Parameter.Match(parameter);
if (match.Success)
{
parameters.Add(match.Groups[1].Value, match.Groups[3].Value);
}
}
}
else
{
parameters = new Dictionary<string, string>();
}
extensions.Add(new WebSocketExtensionData(name, parameters));
}
return extensions;
}
else
{
return new List<WebSocketExtensionData>();
}
}
internal static string AppendExtension(string currentHeaderValue, string extensionName,
Dictionary<string, string> extensionParameters)
{
var newHeaderValue = new StringBuilder(currentHeaderValue?.Length ?? extensionName.Length + 1);
if (currentHeaderValue != null && currentHeaderValue.Trim() != string.Empty)
{
newHeaderValue.Append(currentHeaderValue);
newHeaderValue.Append(ExtensionSeparator);
}
newHeaderValue.Append(extensionName);
foreach (KeyValuePair<string, string> extensionParameter in extensionParameters)
{
newHeaderValue.Append(ParameterSeparator);
newHeaderValue.Append(extensionParameter.Key);
if (extensionParameter.Value != null)
{
newHeaderValue.Append(ParameterEqual);
newHeaderValue.Append(extensionParameter.Value);
}
}
return newHeaderValue.ToString();
}
}
}

Просмотреть файл

@ -0,0 +1,118 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets.Extensions
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class WebSocketServerExtensionHandler : ChannelHandlerAdapter
{
readonly List<IWebSocketServerExtensionHandshaker> extensionHandshakers;
List<IWebSocketServerExtension> validExtensions;
public WebSocketServerExtensionHandler(params IWebSocketServerExtensionHandshaker[] extensionHandshakers)
{
Contract.Requires(extensionHandshakers != null && extensionHandshakers.Length > 0);
this.extensionHandshakers = new List<IWebSocketServerExtensionHandshaker>(extensionHandshakers);
}
public override void ChannelRead(IChannelHandlerContext ctx, object msg)
{
if (msg is IHttpRequest request)
{
if (WebSocketExtensionUtil.IsWebsocketUpgrade(request.Headers))
{
if (request.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value)
&& value != null)
{
string extensionsHeader = value.ToString();
List<WebSocketExtensionData> extensions =
WebSocketExtensionUtil.ExtractExtensions(extensionsHeader);
int rsv = 0;
foreach (WebSocketExtensionData extensionData in extensions)
{
IWebSocketServerExtension validExtension = null;
foreach (IWebSocketServerExtensionHandshaker extensionHandshaker in this.extensionHandshakers)
{
validExtension = extensionHandshaker.HandshakeExtension(extensionData);
if (validExtension != null)
{
break;
}
}
if (validExtension != null && (validExtension.Rsv & rsv) == 0)
{
if (this.validExtensions == null)
{
this.validExtensions = new List<IWebSocketServerExtension>(1);
}
rsv = rsv | validExtension.Rsv;
this.validExtensions.Add(validExtension);
}
}
}
}
}
base.ChannelRead(ctx, msg);
}
public override Task WriteAsync(IChannelHandlerContext ctx, object msg)
{
Action<Task> continuationAction = null;
if (msg is IHttpResponse response
&& WebSocketExtensionUtil.IsWebsocketUpgrade(response.Headers)
&& this.validExtensions != null)
{
string headerValue = null;
if (response.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value))
{
headerValue = value?.ToString();
}
foreach (IWebSocketServerExtension extension in this.validExtensions)
{
WebSocketExtensionData extensionData = extension.NewReponseData();
headerValue = WebSocketExtensionUtil.AppendExtension(headerValue,
extensionData.Name, extensionData.Parameters);
}
continuationAction = promise =>
{
if (promise.Status == TaskStatus.RanToCompletion)
{
foreach (IWebSocketServerExtension extension in this.validExtensions)
{
WebSocketExtensionDecoder decoder = extension.NewExtensionDecoder();
WebSocketExtensionEncoder encoder = extension.NewExtensionEncoder();
ctx.Channel.Pipeline.AddAfter(ctx.Name, decoder.GetType().Name, decoder);
ctx.Channel.Pipeline.AddAfter(ctx.Name, encoder.GetType().Name, encoder);
}
}
ctx.Channel.Pipeline.Remove(ctx.Name);
};
if (headerValue != null)
{
response.Headers.Set(HttpHeaderNames.SecWebsocketExtensions, headerValue);
}
}
return continuationAction == null
? base.WriteAsync(ctx, msg)
: base.WriteAsync(ctx, msg)
.ContinueWith(continuationAction, TaskContinuationOptions.ExecuteSynchronously);
}
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Transport.Channels;
/// <summary>
/// Marker interface which all WebSocketFrame decoders need to implement. This makes it
/// easier to access the added encoder later in the <see cref="IChannelPipeline"/>
/// </summary>
public interface IWebSocketFrameDecoder : IChannelHandler
{
}
}

Просмотреть файл

@ -0,0 +1,15 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Transport.Channels;
/// <summary>
/// Marker interface which all WebSocketFrame encoders need to implement. This makes it
/// easier to access the added encoder later in the <see cref="IChannelPipeline"/>.
/// </summary>
public interface IWebSocketFrameEncoder : IChannelHandler
{
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Buffers;
public class PingWebSocketFrame : WebSocketFrame
{
public PingWebSocketFrame()
: base(true, 0, Unpooled.Buffer(0))
{
}
public PingWebSocketFrame(IByteBuffer binaryData)
: base(binaryData)
{
}
public PingWebSocketFrame(bool finalFragment, int rsv, IByteBuffer binaryData)
: base(finalFragment, rsv, binaryData)
{
}
public override IByteBufferHolder Replace(IByteBuffer content) => new PingWebSocketFrame(this.IsFinalFragment, this.Rsv, content);
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Buffers;
public class PongWebSocketFrame : WebSocketFrame
{
public PongWebSocketFrame()
: base(Unpooled.Buffer(0))
{
}
public PongWebSocketFrame(IByteBuffer binaryData)
: base(binaryData)
{
}
public PongWebSocketFrame(bool finalFragment, int rsv, IByteBuffer binaryData)
: base(finalFragment, rsv, binaryData)
{
}
public override IByteBufferHolder Replace(IByteBuffer content) => new PongWebSocketFrame(this.IsFinalFragment, this.Rsv, content);
}
}

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Text;
using DotNetty.Buffers;
public class TextWebSocketFrame : WebSocketFrame
{
public TextWebSocketFrame()
: base(Unpooled.Buffer(0))
{
}
public TextWebSocketFrame(string text)
: base(FromText(text))
{
}
public TextWebSocketFrame(IByteBuffer binaryData)
: base(binaryData)
{
}
public TextWebSocketFrame(bool finalFragment, int rsv, string text)
: base(finalFragment, rsv, FromText(text))
{
}
static IByteBuffer FromText(string text) => string.IsNullOrEmpty(text)
? Unpooled.Empty : Unpooled.CopiedBuffer(text, Encoding.UTF8);
public TextWebSocketFrame(bool finalFragment, int rsv, IByteBuffer binaryData)
: base(finalFragment, rsv, binaryData)
{
}
public string Text() => this.Content.ToString(Encoding.UTF8);
public override IByteBufferHolder Replace(IByteBuffer content) => new TextWebSocketFrame(this.IsFinalFragment, this.Rsv, content);
}
}

Просмотреть файл

@ -0,0 +1,90 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
public class Utf8FrameValidator : ChannelHandlerAdapter
{
int fragmentedFramesCount;
Utf8Validator utf8Validator;
public override void ChannelRead(IChannelHandlerContext ctx, object message)
{
if (message is WebSocketFrame frame)
{
// Processing for possible fragmented messages for text and binary
// frames
if (frame.IsFinalFragment)
{
// Final frame of the sequence. Apparently ping frames are
// allowed in the middle of a fragmented message
if (!(frame is PingWebSocketFrame))
{
this.fragmentedFramesCount = 0;
// Check text for UTF8 correctness
if (frame is TextWebSocketFrame
|| (this.utf8Validator != null && this.utf8Validator.IsChecking))
{
// Check UTF-8 correctness for this payload
this.CheckUtf8String(ctx, frame.Content);
// This does a second check to make sure UTF-8
// correctness for entire text message
this.utf8Validator.Finish();
}
}
}
else
{
// Not final frame so we can expect more frames in the
// fragmented sequence
if (this.fragmentedFramesCount == 0)
{
// First text or binary frame for a fragmented set
if (frame is TextWebSocketFrame)
{
this.CheckUtf8String(ctx, frame.Content);
}
}
else
{
// Subsequent frames - only check if init frame is text
if (this.utf8Validator != null && this.utf8Validator.IsChecking)
{
this.CheckUtf8String(ctx, frame.Content);
}
}
// Increment counter
this.fragmentedFramesCount++;
}
}
base.ChannelRead(ctx, message);
}
void CheckUtf8String(IChannelHandlerContext ctx, IByteBuffer buffer)
{
try
{
if (this.utf8Validator == null)
{
this.utf8Validator = new Utf8Validator();
}
this.utf8Validator.Check(buffer);
}
catch (CorruptedFrameException)
{
if (ctx.Channel.Active)
{
ctx.WriteAndFlushAsync(Unpooled.Empty)
.ContinueWith(t => ctx.Channel.CloseAsync());
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,76 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoPropertyWithPrivateSetter
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Runtime.CompilerServices;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
sealed class Utf8Validator : IByteProcessor
{
const int Utf8Accept = 0;
const int Utf8Reject = 12;
static readonly byte[] Types = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8,
8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3, 11, 6, 6, 6, 5, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8 };
static readonly byte[] States = { 0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72, 12, 12,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 0, 12, 12, 12, 12, 12, 0, 12, 0, 12, 12,
12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12, 12, 12, 12, 12, 12, 36,
12, 36, 12, 12, 12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12, 12, 36, 12, 12, 12, 12,
12, 12, 12, 12, 12, 12 };
int state = Utf8Accept;
int codep;
bool checking;
public void Check(IByteBuffer buffer)
{
this.checking = true;
buffer.ForEachByte(this);
}
public void Finish()
{
this.checking = false;
this.codep = 0;
if (this.state != Utf8Accept)
{
this.state = Utf8Accept;
ThrowCorruptedFrameException();
}
}
public bool Process(byte b)
{
byte type = Types[b & 0xFF];
this.codep = this.state != Utf8Accept ? b & 0x3f | this.codep << 6 : 0xff >> type & b;
this.state = States[this.state + type];
if (this.state == Utf8Reject)
{
this.checking = false;
ThrowCorruptedFrameException();
}
return true;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowCorruptedFrameException() => throw new CorruptedFrameException("bytes are not UTF-8");
public bool IsChecking => this.checking;
}
}

Просмотреть файл

@ -0,0 +1,133 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Transport.Channels;
using static Buffers.ByteBufferUtil;
public class WebSocket00FrameDecoder : ReplayingDecoder<WebSocket00FrameDecoder.Void>, IWebSocketFrameDecoder
{
public enum Void
{
// Empty state
}
const int DefaultMaxFrameSize = 16384;
readonly long maxFrameSize;
bool receivedClosingHandshake;
public WebSocket00FrameDecoder() : this(DefaultMaxFrameSize)
{
}
public WebSocket00FrameDecoder(int maxFrameSize) : base(default(Void))
{
this.maxFrameSize = maxFrameSize;
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
// Discard all data received if closing handshake was received before.
if (this.receivedClosingHandshake)
{
input.SkipBytes(this.ActualReadableBytes);
return;
}
// Decode a frame otherwise.
byte type = input.ReadByte();
WebSocketFrame frame;
if ((type & 0x80) == 0x80)
{
// If the MSB on type is set, decode the frame length
frame = this.DecodeBinaryFrame(context, type, input);
}
else
{
// Decode a 0xff terminated UTF-8 string
frame = this.DecodeTextFrame(context, input);
}
if (frame != null)
{
output.Add(frame);
}
}
WebSocketFrame DecodeBinaryFrame(IChannelHandlerContext ctx, byte type, IByteBuffer buffer)
{
long frameSize = 0;
int lengthFieldSize = 0;
byte b;
do
{
b = buffer.ReadByte();
frameSize <<= 7;
frameSize |= (uint)(b & 0x7f);
if (frameSize > this.maxFrameSize)
{
throw new TooLongFrameException(nameof(WebSocket00FrameDecoder));
}
lengthFieldSize++;
if (lengthFieldSize > 8)
{
// Perhaps a malicious peer?
throw new TooLongFrameException(nameof(WebSocket00FrameDecoder));
}
} while ((b & 0x80) == 0x80);
if (type == 0xFF && frameSize == 0)
{
this.receivedClosingHandshake = true;
return new CloseWebSocketFrame();
}
IByteBuffer payload = ReadBytes(ctx.Allocator, buffer, (int)frameSize);
return new BinaryWebSocketFrame(payload);
}
WebSocketFrame DecodeTextFrame(IChannelHandlerContext ctx, IByteBuffer buffer)
{
int ridx = buffer.ReaderIndex;
int rbytes = this.ActualReadableBytes;
int delimPos = buffer.IndexOf(ridx, ridx + rbytes, 0xFF);
if (delimPos == -1)
{
// Frame delimiter (0xFF) not found
if (rbytes > this.maxFrameSize)
{
// Frame length exceeded the maximum
throw new TooLongFrameException(nameof(WebSocket00FrameDecoder));
}
else
{
// Wait until more data is received
return null;
}
}
int frameSize = delimPos - ridx;
if (frameSize > this.maxFrameSize)
{
throw new TooLongFrameException(nameof(WebSocket00FrameDecoder));
}
IByteBuffer binaryData = ReadBytes(ctx.Allocator, buffer, frameSize);
buffer.SkipBytes(1);
int ffDelimPos = binaryData.IndexOf(binaryData.ReaderIndex, binaryData.WriterIndex, 0xFF);
if (ffDelimPos >= 0)
{
binaryData.Release();
throw new ArgumentException("a text frame should not contain 0xFF.");
}
return new TextWebSocketFrame(binaryData);
}
}
}

Просмотреть файл

@ -0,0 +1,104 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable InconsistentNaming
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Collections.Generic;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class WebSocket00FrameEncoder : MessageToMessageEncoder<WebSocketFrame>, IWebSocketFrameEncoder
{
static readonly IByteBuffer _0X00 = Unpooled.UnreleasableBuffer(
Unpooled.DirectBuffer(1, 1).WriteByte(0x00));
static readonly IByteBuffer _0XFF = Unpooled.UnreleasableBuffer(
Unpooled.DirectBuffer(1, 1).WriteByte(0xFF));
static readonly IByteBuffer _0XFF_0X00 = Unpooled.UnreleasableBuffer(
Unpooled.DirectBuffer(2, 2).WriteByte(0xFF).WriteByte(0x00));
public override bool IsSharable => true;
protected override void Encode(IChannelHandlerContext context, WebSocketFrame message, List<object> output)
{
if (message is TextWebSocketFrame)
{
// Text frame
IByteBuffer data = message.Content;
output.Add(_0X00.Duplicate());
output.Add(data.Retain());
output.Add(_0XFF.Duplicate());
}
else if (message is CloseWebSocketFrame)
{
// Close frame, needs to call duplicate to allow multiple writes.
// See https://github.com/netty/netty/issues/2768
output.Add(_0XFF_0X00.Duplicate());
}
else
{
// Binary frame
IByteBuffer data = message.Content;
int dataLen = data.ReadableBytes;
IByteBuffer buf = context.Allocator.Buffer(5);
bool release = true;
try
{
// Encode type.
buf.WriteByte(0x80);
// Encode length.
int b1 = dataLen.RightUShift(28) & 0x7F;
int b2 = dataLen.RightUShift(14) & 0x7F;
int b3 = dataLen.RightUShift(7) & 0x7F;
int b4 = dataLen & 0x7F;
if (b1 == 0)
{
if (b2 == 0)
{
if (b3 == 0)
{
buf.WriteByte(b4);
}
else
{
buf.WriteByte(b3 | 0x80);
buf.WriteByte(b4);
}
}
else
{
buf.WriteByte(b2 | 0x80);
buf.WriteByte(b3 | 0x80);
buf.WriteByte(b4);
}
}
else
{
buf.WriteByte(b1 | 0x80);
buf.WriteByte(b2 | 0x80);
buf.WriteByte(b3 | 0x80);
buf.WriteByte(b4);
}
// Encode binary data.
output.Add(buf);
output.Add(data.Retain());
release = false;
}
finally
{
if (release)
{
buf.Release();
}
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,18 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
public class WebSocket07FrameDecoder : WebSocket08FrameDecoder
{
public WebSocket07FrameDecoder(bool expectMaskedFrames, bool allowExtensions, int maxFramePayloadLength)
: this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false)
{
}
public WebSocket07FrameDecoder(bool expectMaskedFrames, bool allowExtensions, int maxFramePayloadLength, bool allowMaskMismatch)
: base(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch)
{
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
public class WebSocket07FrameEncoder : WebSocket08FrameEncoder
{
public WebSocket07FrameEncoder(bool maskPayload)
: base(maskPayload)
{
}
}
}

Просмотреть файл

@ -0,0 +1,467 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable UseStringInterpolation
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Common.Internal.Logging;
using DotNetty.Transport.Channels;
using static Buffers.ByteBufferUtil;
public class WebSocket08FrameDecoder : ByteToMessageDecoder, IWebSocketFrameDecoder
{
enum State
{
ReadingFirst,
ReadingSecond,
ReadingSize,
MaskingKey,
Payload,
Corrupt
}
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<WebSocket08FrameDecoder>();
const byte OpcodeCont = 0x0;
const byte OpcodeText = 0x1;
const byte OpcodeBinary = 0x2;
const byte OpcodeClose = 0x8;
const byte OpcodePing = 0x9;
const byte OpcodePong = 0xA;
readonly long maxFramePayloadLength;
readonly bool allowExtensions;
readonly bool expectMaskedFrames;
readonly bool allowMaskMismatch;
int fragmentedFramesCount;
bool frameFinalFlag;
bool frameMasked;
int frameRsv;
int frameOpcode;
long framePayloadLength;
byte[] maskingKey;
int framePayloadLen1;
bool receivedClosingHandshake;
State state = State.ReadingFirst;
public WebSocket08FrameDecoder(bool expectMaskedFrames, bool allowExtensions, int maxFramePayloadLength)
: this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false)
{
}
public WebSocket08FrameDecoder(bool expectMaskedFrames, bool allowExtensions, int maxFramePayloadLength, bool allowMaskMismatch)
{
this.expectMaskedFrames = expectMaskedFrames;
this.allowMaskMismatch = allowMaskMismatch;
this.allowExtensions = allowExtensions;
this.maxFramePayloadLength = maxFramePayloadLength;
}
protected override void Decode(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
// Discard all data received if closing handshake was received before.
if (this.receivedClosingHandshake)
{
input.SkipBytes(this.ActualReadableBytes);
return;
}
switch (this.state)
{
case State.ReadingFirst:
if (!input.IsReadable())
{
return;
}
this.framePayloadLength = 0;
// FIN, RSV, OPCODE
byte b = input.ReadByte();
this.frameFinalFlag = (b & 0x80) != 0;
this.frameRsv = (b & 0x70) >> 4;
this.frameOpcode = b & 0x0F;
if (Logger.DebugEnabled)
{
Logger.Debug("Decoding WebSocket Frame opCode={}", this.frameOpcode);
}
this.state = State.ReadingSecond;
goto case State.ReadingSecond;
case State.ReadingSecond:
if (!input.IsReadable())
{
return;
}
// MASK, PAYLOAD LEN 1
b = input.ReadByte();
this.frameMasked = (b & 0x80) != 0;
this.framePayloadLen1 = b & 0x7F;
if (this.frameRsv != 0 && !this.allowExtensions)
{
this.ProtocolViolation(context, $"RSV != 0 and no extension negotiated, RSV:{this.frameRsv}");
return;
}
if (!this.allowMaskMismatch && this.expectMaskedFrames != this.frameMasked)
{
this.ProtocolViolation(context, "received a frame that is not masked as expected");
return;
}
// control frame (have MSB in opcode set)
if (this.frameOpcode > 7)
{
// control frames MUST NOT be fragmented
if (!this.frameFinalFlag)
{
this.ProtocolViolation(context, "fragmented control frame");
return;
}
// control frames MUST have payload 125 octets or less
if (this.framePayloadLen1 > 125)
{
this.ProtocolViolation(context, "control frame with payload length > 125 octets");
return;
}
// check for reserved control frame opcodes
if (!(this.frameOpcode == OpcodeClose
|| this.frameOpcode == OpcodePing
|| this.frameOpcode == OpcodePong))
{
this.ProtocolViolation(context, $"control frame using reserved opcode {this.frameOpcode}");
return;
}
// close frame : if there is a body, the first two bytes of the
// body MUST be a 2-byte unsigned integer (in network byte
// order) representing a getStatus code
if (this.frameOpcode == 8 && this.framePayloadLen1 == 1)
{
this.ProtocolViolation(context, "received close control frame with payload len 1");
return;
}
}
else // data frame
{
// check for reserved data frame opcodes
if (!(this.frameOpcode == OpcodeCont || this.frameOpcode == OpcodeText
|| this.frameOpcode == OpcodeBinary))
{
this.ProtocolViolation(context, $"data frame using reserved opcode {this.frameOpcode}");
return;
}
// check opcode vs message fragmentation state 1/2
if (this.fragmentedFramesCount == 0 && this.frameOpcode == OpcodeCont)
{
this.ProtocolViolation(context, "received continuation data frame outside fragmented message");
return;
}
// check opcode vs message fragmentation state 2/2
if (this.fragmentedFramesCount != 0 && this.frameOpcode != OpcodeCont && this.frameOpcode != OpcodePing)
{
this.ProtocolViolation(context, "received non-continuation data frame while inside fragmented message");
return;
}
}
this.state = State.ReadingSize;
goto case State.ReadingSize;
case State.ReadingSize:
// Read frame payload length
if (this.framePayloadLen1 == 126)
{
if (input.ReadableBytes < 2)
{
return;
}
this.framePayloadLength = input.ReadUnsignedShort();
if (this.framePayloadLength < 126)
{
this.ProtocolViolation(context, "invalid data frame length (not using minimal length encoding)");
return;
}
}
else if (this.framePayloadLen1 == 127)
{
if (input.ReadableBytes < 8)
{
return;
}
this.framePayloadLength = input.ReadLong();
// TODO: check if it's bigger than 0x7FFFFFFFFFFFFFFF, Maybe
// just check if it's negative?
if (this.framePayloadLength < 65536)
{
this.ProtocolViolation(context, "invalid data frame length (not using minimal length encoding)");
return;
}
}
else
{
this.framePayloadLength = this.framePayloadLen1;
}
if (this.framePayloadLength > this.maxFramePayloadLength)
{
this.ProtocolViolation(context, $"Max frame length of {this.maxFramePayloadLength} has been exceeded.");
return;
}
if (Logger.DebugEnabled)
{
Logger.Debug("Decoding WebSocket Frame length={}", this.framePayloadLength);
}
this.state = State.MaskingKey;
goto case State.MaskingKey;
case State.MaskingKey:
if (this.frameMasked)
{
if (input.ReadableBytes < 4)
{
return;
}
if (this.maskingKey == null)
{
this.maskingKey = new byte[4];
}
input.ReadBytes(this.maskingKey);
}
this.state = State.Payload;
goto case State.Payload;
case State.Payload:
if (input.ReadableBytes < this.framePayloadLength)
{
return;
}
IByteBuffer payloadBuffer = null;
try
{
payloadBuffer = ReadBytes(context.Allocator, input, ToFrameLength(this.framePayloadLength));
// Now we have all the data, the next checkpoint must be the next
// frame
this.state = State.ReadingFirst;
// Unmask data if needed
if (this.frameMasked)
{
this.Unmask(payloadBuffer);
}
// Processing ping/pong/close frames because they cannot be
// fragmented
if (this.frameOpcode == OpcodePing)
{
output.Add(new PingWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer));
payloadBuffer = null;
return;
}
if (this.frameOpcode == OpcodePong)
{
output.Add(new PongWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer));
payloadBuffer = null;
return;
}
if (this.frameOpcode == OpcodeClose)
{
this.receivedClosingHandshake = true;
this.CheckCloseFrameBody(context, payloadBuffer);
output.Add(new CloseWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer));
payloadBuffer = null;
return;
}
// Processing for possible fragmented messages for text and binary
// frames
if (this.frameFinalFlag)
{
// Final frame of the sequence. Apparently ping frames are
// allowed in the middle of a fragmented message
if (this.frameOpcode != OpcodePing)
{
this.fragmentedFramesCount = 0;
}
}
else
{
// Increment counter
this.fragmentedFramesCount++;
}
// Return the frame
if (this.frameOpcode == OpcodeText)
{
output.Add(new TextWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer));
payloadBuffer = null;
return;
}
else if (this.frameOpcode == OpcodeBinary)
{
output.Add(new BinaryWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer));
payloadBuffer = null;
return;
}
else if (this.frameOpcode == OpcodeCont)
{
output.Add(new ContinuationWebSocketFrame(this.frameFinalFlag, this.frameRsv, payloadBuffer));
payloadBuffer = null;
return;
}
else
{
ThrowNotSupportedException(this.frameOpcode);
break;
}
}
finally
{
payloadBuffer?.Release();
}
case State.Corrupt:
if (input.IsReadable())
{
// If we don't keep reading Netty will throw an exception saying
// we can't return null if no bytes read and state not changed.
input.ReadByte();
}
return;
default:
throw new Exception("Shouldn't reach here.");
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowNotSupportedException(int frameOpcode)
{
throw GetNotSupportedException();
NotSupportedException GetNotSupportedException()
{
return new NotSupportedException($"Cannot decode web socket frame with opcode: {frameOpcode}");
}
}
void Unmask(IByteBuffer frame)
{
int i = frame.ReaderIndex;
int end = frame.WriterIndex;
int intMask = (this.maskingKey[0] << 24)
| (this.maskingKey[1] << 16)
| (this.maskingKey[2] << 8)
| this.maskingKey[3];
for (; i + 3 < end; i += 4)
{
int unmasked = frame.GetInt(i) ^ intMask;
frame.SetInt(i, unmasked);
}
for (; i < end; i++)
{
frame.SetByte(i, frame.GetByte(i) ^ this.maskingKey[i % 4]);
}
}
void ProtocolViolation(IChannelHandlerContext ctx, string reason) => this.ProtocolViolation(ctx, new CorruptedFrameException(reason));
void ProtocolViolation(IChannelHandlerContext ctx, CorruptedFrameException ex)
{
this.state = State.Corrupt;
if (ctx.Channel.Active)
{
object closeMessage;
if (this.receivedClosingHandshake)
{
closeMessage = Unpooled.Empty;
}
else
{
closeMessage = new CloseWebSocketFrame(1002, null);
}
ctx.WriteAndFlushAsync(closeMessage)
.ContinueWith((t, c) => ((IChannel)c).CloseAsync(),
ctx.Channel, TaskContinuationOptions.ExecuteSynchronously);
}
throw ex;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static int ToFrameLength(long l)
{
if (l > int.MaxValue)
{
ThrowTooLongFrameException(l);
}
return (int)l;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowTooLongFrameException(long l)
{
throw GetTooLongFrameException();
TooLongFrameException GetTooLongFrameException()
{
return new TooLongFrameException(string.Format("Length: {0}", l));
}
}
protected void CheckCloseFrameBody(IChannelHandlerContext ctx, IByteBuffer buffer)
{
if (buffer == null || !buffer.IsReadable())
{
return;
}
if (buffer.ReadableBytes == 1)
{
this.ProtocolViolation(ctx, "Invalid close frame body");
}
// Save reader index
int idx = buffer.ReaderIndex;
buffer.SetReaderIndex(0);
// Must have 2 byte integer within the valid range
int statusCode = buffer.ReadShort();
if (statusCode >= 0 && statusCode <= 999 || statusCode >= 1004 && statusCode <= 1006
|| statusCode >= 1012 && statusCode <= 2999)
{
this.ProtocolViolation(ctx, $"Invalid close frame getStatus code: {statusCode}");
}
// May have UTF-8 message
if (buffer.IsReadable())
{
try
{
new Utf8Validator().Check(buffer);
}
catch (CorruptedFrameException ex)
{
this.ProtocolViolation(ctx, ex);
}
}
// Restore reader index
buffer.SetReaderIndex(idx);
}
}
}

Просмотреть файл

@ -0,0 +1,219 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using DotNetty.Buffers;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class WebSocket08FrameEncoder : MessageToMessageEncoder<WebSocketFrame>, IWebSocketFrameEncoder
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<WebSocket08FrameEncoder>();
const byte OpcodeCont = 0x0;
const byte OpcodeText = 0x1;
const byte OpcodeBinary = 0x2;
const byte OpcodeClose = 0x8;
const byte OpcodePing = 0x9;
const byte OpcodePong = 0xA;
///
// The size threshold for gathering writes. Non-Masked messages bigger than this size will be be sent fragmented as
// a header and a content ByteBuf whereas messages smaller than the size will be merged into a single buffer and
// sent at once.
// Masked messages will always be sent at once.
//
const int GatheringWriteThreshold = 1024;
readonly bool maskPayload;
readonly Random random;
public WebSocket08FrameEncoder(bool maskPayload)
{
this.maskPayload = maskPayload;
this.random = new Random();
}
protected override unsafe void Encode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> output)
{
IByteBuffer data = msg.Content;
var mask = stackalloc byte[4];
byte opcode = 0;
if (msg is TextWebSocketFrame)
{
opcode = OpcodeText;
}
else if (msg is PingWebSocketFrame)
{
opcode = OpcodePing;
}
else if (msg is PongWebSocketFrame)
{
opcode = OpcodePong;
}
else if (msg is CloseWebSocketFrame)
{
opcode = OpcodeClose;
}
else if (msg is BinaryWebSocketFrame)
{
opcode = OpcodeBinary;
}
else if (msg is ContinuationWebSocketFrame)
{
opcode = OpcodeCont;
}
else
{
ThrowNotSupportedException(msg);
}
int length = data.ReadableBytes;
if (Logger.DebugEnabled)
{
Logger.Debug($"Encoding WebSocket Frame opCode={opcode} length={length}");
}
int b0 = 0;
if (msg.IsFinalFragment)
{
b0 |= 1 << 7;
}
b0 |= msg.Rsv % 8 << 4;
b0 |= opcode % 128;
if (opcode == OpcodePing && length > 125)
{
ThrowTooLongFrameException(length);
}
bool release = true;
IByteBuffer buf = null;
try
{
int maskLength = this.maskPayload ? 4 : 0;
if (length <= 125)
{
int size = 2 + maskLength;
if (this.maskPayload || length <= GatheringWriteThreshold)
{
size += length;
}
buf = ctx.Allocator.Buffer(size);
buf.WriteByte(b0);
byte b = (byte)(this.maskPayload ? 0x80 | (byte)length : (byte)length);
buf.WriteByte(b);
}
else if (length <= 0xFFFF)
{
int size = 4 + maskLength;
if (this.maskPayload || length <= GatheringWriteThreshold)
{
size += length;
}
buf = ctx.Allocator.Buffer(size);
buf.WriteByte(b0);
buf.WriteByte(this.maskPayload ? 0xFE : 126);
buf.WriteByte(length.RightUShift(8) & 0xFF);
buf.WriteByte(length & 0xFF);
}
else
{
int size = 10 + maskLength;
if (this.maskPayload || length <= GatheringWriteThreshold)
{
size += length;
}
buf = ctx.Allocator.Buffer(size);
buf.WriteByte(b0);
buf.WriteByte(this.maskPayload ? 0xFF : 127);
buf.WriteLong(length);
}
// Write payload
if (this.maskPayload)
{
int intMask = (this.random.Next() * int.MaxValue);
// Mask bytes in BE
uint unsignedValue = (uint)intMask;
*mask = (byte)(unsignedValue >> 24);
*(mask + 1) = (byte)(unsignedValue >> 16);
*(mask + 2) = (byte)(unsignedValue >> 8);
*(mask + 3) = (byte)unsignedValue;
// Mask in BE
buf.WriteInt(intMask);
int counter = 0;
int i = data.ReaderIndex;
int end = data.WriterIndex;
for (; i + 3 < end; i += 4)
{
int intData = data.GetInt(i);
buf.WriteInt(intData ^ intMask);
}
for (; i < end; i++)
{
byte byteData = data.GetByte(i);
buf.WriteByte(byteData ^ mask[counter++ % 4]);
}
output.Add(buf);
}
else
{
if (buf.WritableBytes >= data.ReadableBytes)
{
// merge buffers as this is cheaper then a gathering write if the payload is small enough
buf.WriteBytes(data);
output.Add(buf);
}
else
{
output.Add(buf);
output.Add(data.Retain());
}
}
release = false;
}
finally
{
if (release)
{
buf?.Release();
}
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowTooLongFrameException(int length)
{
throw GetTooLongFrameException();
TooLongFrameException GetTooLongFrameException()
{
return new TooLongFrameException(string.Format("invalid payload for PING (payload length must be <= 125, was {0}", length));
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowNotSupportedException(WebSocketFrame msg)
{
throw GetNotSupportedException();
NotSupportedException GetNotSupportedException()
{
return new NotSupportedException(string.Format("Cannot encode frame of type: {0}", msg.GetType().Name));
}
}
}
}

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
public class WebSocket13FrameDecoder : WebSocket08FrameDecoder
{
public WebSocket13FrameDecoder(bool expectMaskedFrames, bool allowExtensions, int maxFramePayloadLength)
: this(expectMaskedFrames, allowExtensions, maxFramePayloadLength, false)
{
}
public WebSocket13FrameDecoder(bool expectMaskedFrames, bool allowExtensions,int maxFramePayloadLength,
bool allowMaskMismatch)
: base(expectMaskedFrames, allowExtensions, maxFramePayloadLength, allowMaskMismatch)
{
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
public class WebSocket13FrameEncoder : WebSocket08FrameEncoder
{
public WebSocket13FrameEncoder(bool maskPayload)
: base(maskPayload)
{
}
}
}

Просмотреть файл

@ -0,0 +1,42 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Diagnostics.Contracts;
using DotNetty.Buffers;
using DotNetty.Handlers.Streams;
public sealed class WebSocketChunkedInput : IChunkedInput<WebSocketFrame>
{
readonly IChunkedInput<IByteBuffer> input;
readonly int rsv;
public WebSocketChunkedInput(IChunkedInput<IByteBuffer> input)
: this(input, 0)
{
}
public WebSocketChunkedInput(IChunkedInput<IByteBuffer> input, int rsv)
{
Contract.Requires(input != null);
this.input = input;
this.rsv = rsv;
}
public bool IsEndOfInput => this.input.IsEndOfInput;
public void Close() => this.input.Close();
public WebSocketFrame ReadChunk(IByteBufferAllocator allocator)
{
IByteBuffer buf = this.input.ReadChunk(allocator);
return buf != null ? new ContinuationWebSocketFrame(this.input.IsEndOfInput, this.rsv, buf) : null;
}
public long Length => this.input.Length;
public long Progress => this.input.Progress;
}
}

Просмотреть файл

@ -0,0 +1,425 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
// ReSharper disable ConvertToAutoPropertyWhenPossible
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using DotNetty.Common.Concurrency;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public abstract class WebSocketClientHandshaker
{
static readonly ClosedChannelException DefaultClosedChannelException = new ClosedChannelException();
static readonly string HttpSchemePrefix = HttpScheme.Http + "://";
static readonly string HttpsSchemePrefix = HttpScheme.Https + "://";
readonly Uri uri;
readonly WebSocketVersion version;
volatile bool handshakeComplete;
readonly string expectedSubprotocol;
volatile string actualSubprotocol;
protected readonly HttpHeaders CustomHeaders;
readonly int maxFramePayloadLength;
protected WebSocketClientHandshaker(Uri uri, WebSocketVersion version, string subprotocol,
HttpHeaders customHeaders, int maxFramePayloadLength)
{
this.uri = uri;
this.version = version;
this.expectedSubprotocol = subprotocol;
this.CustomHeaders = customHeaders;
this.maxFramePayloadLength = maxFramePayloadLength;
}
public Uri Uri => this.uri;
public WebSocketVersion Version => this.version;
public int MaxFramePayloadLength => this.maxFramePayloadLength;
public bool IsHandshakeComplete => this.handshakeComplete;
void SetHandshakeComplete() => this.handshakeComplete = true;
public string ExpectedSubprotocol => this.expectedSubprotocol;
public string ActualSubprotocol
{
get => this.actualSubprotocol;
private set => this.actualSubprotocol = value;
}
public Task HandshakeAsync(IChannel channel)
{
IFullHttpRequest request = this.NewHandshakeRequest();
var decoder = channel.Pipeline.Get<HttpResponseDecoder>();
if (decoder == null)
{
var codec = channel.Pipeline.Get<HttpClientCodec>();
if (codec == null)
{
return TaskEx.FromException(new InvalidOperationException("ChannelPipeline does not contain a HttpResponseDecoder or HttpClientCodec"));
}
}
var completion = new TaskCompletionSource();
channel.WriteAndFlushAsync(request).ContinueWith((t, state) =>
{
var tcs = (TaskCompletionSource)state;
switch (t.Status)
{
case TaskStatus.RanToCompletion:
IChannelPipeline p = channel.Pipeline;
IChannelHandlerContext ctx = p.Context<HttpRequestEncoder>() ?? p.Context<HttpClientCodec>();
if (ctx == null)
{
tcs.TrySetException(new InvalidOperationException("ChannelPipeline does not contain a HttpRequestEncoder or HttpClientCodec"));
return;
}
p.AddAfter(ctx.Name, "ws-encoder", this.NewWebSocketEncoder());
tcs.TryComplete();
break;
case TaskStatus.Canceled:
tcs.TrySetCanceled();
break;
case TaskStatus.Faulted:
tcs.TryUnwrap(t.Exception);
break;
default:
throw new ArgumentOutOfRangeException();
}
},
completion,
TaskContinuationOptions.ExecuteSynchronously);
return completion.Task;
}
protected internal abstract IFullHttpRequest NewHandshakeRequest();
public void FinishHandshake(IChannel channel, IFullHttpResponse response)
{
this.Verify(response);
// Verify the subprotocol that we received from the server.
// This must be one of our expected subprotocols - or null/empty if we didn't want to speak a subprotocol
string receivedProtocol = null;
if (response.Headers.TryGet(HttpHeaderNames.SecWebsocketProtocol, out ICharSequence headerValue))
{
receivedProtocol = headerValue.ToString().Trim();
}
string expectedProtocol = this.expectedSubprotocol ?? "";
bool protocolValid = false;
if (expectedProtocol.Length == 0 && receivedProtocol == null)
{
// No subprotocol required and none received
protocolValid = true;
this.ActualSubprotocol = this.expectedSubprotocol; // null or "" - we echo what the user requested
}
else if (expectedProtocol.Length > 0 && !string.IsNullOrEmpty(receivedProtocol))
{
// We require a subprotocol and received one -> verify it
foreach (string protocol in expectedProtocol.Split(','))
{
if (protocol.Trim().Equals(receivedProtocol))
{
protocolValid = true;
this.ActualSubprotocol = receivedProtocol;
break;
}
}
} // else mixed cases - which are all errors
if (!protocolValid)
{
throw new WebSocketHandshakeException($"Invalid subprotocol. Actual: {receivedProtocol}. Expected one of: {this.expectedSubprotocol}");
}
this.SetHandshakeComplete();
IChannelPipeline p = channel.Pipeline;
// Remove decompressor from pipeline if its in use
var decompressor = p.Get<HttpContentDecompressor>();
if (decompressor != null)
{
p.Remove(decompressor);
}
// Remove aggregator if present before
var aggregator = p.Get<HttpObjectAggregator>();
if (aggregator != null)
{
p.Remove(aggregator);
}
IChannelHandlerContext ctx = p.Context<HttpResponseDecoder>();
if (ctx == null)
{
ctx = p.Context<HttpClientCodec>();
if (ctx == null)
{
throw new InvalidOperationException("ChannelPipeline does not contain a HttpRequestEncoder or HttpClientCodec");
}
var codec = (HttpClientCodec)ctx.Handler;
// Remove the encoder part of the codec as the user may start writing frames after this method returns.
codec.RemoveOutboundHandler();
p.AddAfter(ctx.Name, "ws-decoder", this.NewWebSocketDecoder());
// Delay the removal of the decoder so the user can setup the pipeline if needed to handle
// WebSocketFrame messages.
// See https://github.com/netty/netty/issues/4533
channel.EventLoop.Execute(() => p.Remove(codec));
}
else
{
if (p.Get<HttpRequestEncoder>() != null)
{
// Remove the encoder part of the codec as the user may start writing frames after this method returns.
p.Remove<HttpRequestEncoder>();
}
IChannelHandlerContext context = ctx;
p.AddAfter(context.Name, "ws-decoder", this.NewWebSocketDecoder());
// Delay the removal of the decoder so the user can setup the pipeline if needed to handle
// WebSocketFrame messages.
// See https://github.com/netty/netty/issues/4533
channel.EventLoop.Execute(() => p.Remove(context.Handler));
}
}
public Task ProcessHandshakeAsync(IChannel channel, IHttpResponse response)
{
var completionSource = new TaskCompletionSource();
if (response is IFullHttpResponse res)
{
try
{
this.FinishHandshake(channel, res);
completionSource.TryComplete();
}
catch (Exception cause)
{
completionSource.TrySetException(cause);
}
}
else
{
IChannelPipeline p = channel.Pipeline;
IChannelHandlerContext ctx = p.Context<HttpResponseDecoder>();
if (ctx == null)
{
ctx = p.Context<HttpClientCodec>();
if (ctx == null)
{
completionSource.TrySetException(new InvalidOperationException("ChannelPipeline does not contain a HttpResponseDecoder or HttpClientCodec"));
}
}
else
{
// Add aggregator and ensure we feed the HttpResponse so it is aggregated. A limit of 8192 should be more
// then enough for the websockets handshake payload.
//
// TODO: Make handshake work without HttpObjectAggregator at all.
const string AggregatorName = "httpAggregator";
p.AddAfter(ctx.Name, AggregatorName, new HttpObjectAggregator(8192));
p.AddAfter(AggregatorName, "handshaker", new Handshaker(this, channel, completionSource));
try
{
ctx.FireChannelRead(ReferenceCountUtil.Retain(response));
}
catch (Exception cause)
{
completionSource.TrySetException(cause);
}
}
}
return completionSource.Task;
}
sealed class Handshaker : SimpleChannelInboundHandler<IFullHttpResponse>
{
readonly WebSocketClientHandshaker clientHandshaker;
readonly IChannel channel;
readonly TaskCompletionSource completion;
public Handshaker(WebSocketClientHandshaker clientHandshaker, IChannel channel, TaskCompletionSource completion)
{
this.clientHandshaker = clientHandshaker;
this.channel = channel;
this.completion = completion;
}
protected override void ChannelRead0(IChannelHandlerContext ctx, IFullHttpResponse msg)
{
// Remove and do the actual handshake
ctx.Channel.Pipeline.Remove(this);
try
{
this.clientHandshaker.FinishHandshake(this.channel, msg);
this.completion.TryComplete();
}
catch (Exception cause)
{
this.completion.TrySetException(cause);
}
}
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception cause)
{
// Remove ourself and fail the handshake promise.
ctx.Channel.Pipeline.Remove(this);
this.completion.TrySetException(cause);
}
public override void ChannelInactive(IChannelHandlerContext ctx)
{
// Fail promise if Channel was closed
this.completion.TrySetException(DefaultClosedChannelException);
ctx.FireChannelInactive();
}
}
protected abstract void Verify(IFullHttpResponse response);
protected internal abstract IWebSocketFrameDecoder NewWebSocketDecoder();
protected internal abstract IWebSocketFrameEncoder NewWebSocketEncoder();
public Task CloseAsync(IChannel channel, CloseWebSocketFrame frame)
{
Contract.Requires(channel != null);
return channel.WriteAndFlushAsync(frame);
}
internal static string RawPath(Uri wsUrl) => wsUrl.IsAbsoluteUri ? wsUrl.PathAndQuery : "/";
internal static string WebsocketHostValue(Uri wsUrl)
{
string scheme;
Uri uri;
if (wsUrl.IsAbsoluteUri)
{
scheme = wsUrl.Scheme;
uri = wsUrl;
}
else
{
scheme = null;
uri = AbsoluteUri(wsUrl);
}
int port = OriginalPort(uri);
if (port == -1)
{
return uri.Host;
}
string host = uri.Host;
if (port == HttpScheme.Http.Port)
{
return HttpScheme.Http.Name.ContentEquals(scheme)
|| WebSocketScheme.WS.Name.ContentEquals(scheme)
? host : NetUtil.ToSocketAddressString(host, port);
}
if (port == HttpScheme.Https.Port)
{
return HttpScheme.Https.Name.ToString().Equals(scheme)
|| WebSocketScheme.WSS.Name.ToString().Equals(scheme)
? host : NetUtil.ToSocketAddressString(host, port);
}
// if the port is not standard (80/443) its needed to add the port to the header.
// See http://tools.ietf.org/html/rfc6454#section-6.2
return NetUtil.ToSocketAddressString(host, port);
}
internal static string WebsocketOriginValue(Uri wsUrl)
{
string scheme;
Uri uri;
if (wsUrl.IsAbsoluteUri)
{
scheme = wsUrl.Scheme;
uri = wsUrl;
}
else
{
scheme = null;
uri = AbsoluteUri(wsUrl);
}
string schemePrefix;
int port = uri.Port;
int defaultPort;
if (WebSocketScheme.WSS.Name.ContentEquals(scheme)
|| HttpScheme.Https.Name.ContentEquals(scheme)
|| (scheme == null && port == WebSocketScheme.WSS.Port))
{
schemePrefix = HttpsSchemePrefix;
defaultPort = WebSocketScheme.WSS.Port;
}
else
{
schemePrefix = HttpSchemePrefix;
defaultPort = WebSocketScheme.WS.Port;
}
// Convert uri-host to lower case (by RFC 6454, chapter 4 "Origin of a URI")
string host = uri.Host.ToLower();
if (port != defaultPort && port != -1)
{
// if the port is not standard (80/443) its needed to add the port to the header.
// See http://tools.ietf.org/html/rfc6454#section-6.2
return schemePrefix + NetUtil.ToSocketAddressString(host, port);
}
return schemePrefix + host;
}
static Uri AbsoluteUri(Uri uri)
{
if (uri.IsAbsoluteUri)
{
return uri;
}
string relativeUri = uri.OriginalString;
return new Uri(relativeUri.StartsWith("//")
? HttpScheme.Http + ":" + relativeUri
: HttpSchemePrefix + relativeUri);
}
static int OriginalPort(Uri uri)
{
int index = uri.Scheme.Length + 3 + uri.Host.Length;
if (index < uri.OriginalString.Length
&& uri.OriginalString[index] == ':')
{
return uri.Port;
}
return -1;
}
}
}

Просмотреть файл

@ -0,0 +1,163 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Runtime.CompilerServices;
using DotNetty.Buffers;
using DotNetty.Common.Internal;
using DotNetty.Common.Utilities;
public class WebSocketClientHandshaker00 : WebSocketClientHandshaker
{
static readonly AsciiString Websocket = AsciiString.Cached("WebSocket");
IByteBuffer expectedChallengeResponseBytes;
public WebSocketClientHandshaker00(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
HttpHeaders customHeaders, int maxFramePayloadLength)
: base(webSocketUrl, version, subprotocol, customHeaders, maxFramePayloadLength)
{
}
protected internal override unsafe IFullHttpRequest NewHandshakeRequest()
{
// Make keys
int spaces1 = WebSocketUtil.RandomNumber(1, 12);
int spaces2 = WebSocketUtil.RandomNumber(1, 12);
int max1 = int.MaxValue / spaces1;
int max2 = int.MaxValue / spaces2;
int number1 = WebSocketUtil.RandomNumber(0, max1);
int number2 = WebSocketUtil.RandomNumber(0, max2);
int product1 = number1 * spaces1;
int product2 = number2 * spaces2;
string key1 = Convert.ToString(product1);
string key2 = Convert.ToString(product2);
key1 = InsertRandomCharacters(key1);
key2 = InsertRandomCharacters(key2);
key1 = InsertSpaces(key1, spaces1);
key2 = InsertSpaces(key2, spaces2);
byte[] key3 = WebSocketUtil.RandomBytes(8);
var challenge = new byte[16];
fixed (byte* bytes = challenge)
{
Unsafe.WriteUnaligned(bytes, number1);
Unsafe.WriteUnaligned(bytes + 4, number2);
PlatformDependent.CopyMemory(key3, 0, bytes + 8, 8);
}
this.expectedChallengeResponseBytes = Unpooled.WrappedBuffer(WebSocketUtil.Md5(challenge));
// Get path
Uri wsUrl = this.Uri;
string path = RawPath(wsUrl);
// Format request
var request = new DefaultFullHttpRequest(HttpVersion.Http11, HttpMethod.Get, path);
HttpHeaders headers = request.Headers;
headers.Add(HttpHeaderNames.Upgrade, Websocket)
.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade)
.Add(HttpHeaderNames.Host, WebsocketHostValue(wsUrl))
.Add(HttpHeaderNames.Origin, WebsocketOriginValue(wsUrl))
.Add(HttpHeaderNames.SecWebsocketKey1, key1)
.Add(HttpHeaderNames.SecWebsocketKey2, key2);
string expectedSubprotocol = this.ExpectedSubprotocol;
if (!string.IsNullOrEmpty(expectedSubprotocol))
{
headers.Add(HttpHeaderNames.SecWebsocketProtocol, expectedSubprotocol);
}
if (this.CustomHeaders != null)
{
headers.Add(this.CustomHeaders);
}
// Set Content-Length to workaround some known defect.
// See also: http://www.ietf.org/mail-archive/web/hybi/current/msg02149.html
headers.Set(HttpHeaderNames.ContentLength, key3.Length);
request.Content.WriteBytes(key3);
return request;
}
protected override void Verify(IFullHttpResponse response)
{
if (!response.Status.Equals(HttpResponseStatus.SwitchingProtocols))
{
throw new WebSocketHandshakeException($"Invalid handshake response getStatus: {response.Status}");
}
HttpHeaders headers = response.Headers;
if (!headers.TryGet(HttpHeaderNames.Upgrade, out ICharSequence upgrade)
||!Websocket.ContentEqualsIgnoreCase(upgrade))
{
throw new WebSocketHandshakeException($"Invalid handshake response upgrade: {upgrade}");
}
if (!headers.ContainsValue(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade, true))
{
headers.TryGet(HttpHeaderNames.Connection, out upgrade);
throw new WebSocketHandshakeException($"Invalid handshake response connection: {upgrade}");
}
IByteBuffer challenge = response.Content;
if (!challenge.Equals(this.expectedChallengeResponseBytes))
{
throw new WebSocketHandshakeException("Invalid challenge");
}
}
static string InsertRandomCharacters(string key)
{
int count = WebSocketUtil.RandomNumber(1, 12);
var randomChars = new char[count];
int randCount = 0;
while (randCount < count)
{
int rand = unchecked((int)(WebSocketUtil.RandomNext() * 0x7e + 0x21));
if (0x21 < rand && rand < 0x2f || 0x3a < rand && rand < 0x7e)
{
randomChars[randCount] = (char)rand;
randCount += 1;
}
}
for (int i = 0; i < count; i++)
{
int split = WebSocketUtil.RandomNumber(0, key.Length);
string part1 = key.Substring(0, split);
string part2 = key.Substring(split);
key = part1 + randomChars[i] + part2;
}
return key;
}
static string InsertSpaces(string key, int spaces)
{
for (int i = 0; i < spaces; i++)
{
int split = WebSocketUtil.RandomNumber(1, key.Length - 1);
string part1 = key.Substring(0, split);
string part2 = key.Substring(split);
key = part1 + ' ' + part2;
}
return key;
}
protected internal override IWebSocketFrameDecoder NewWebSocketDecoder() => new WebSocket00FrameDecoder(this.MaxFramePayloadLength);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket00FrameEncoder();
}
}

Просмотреть файл

@ -0,0 +1,117 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Text;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
public class WebSocketClientHandshaker07 : WebSocketClientHandshaker
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<WebSocketClientHandshaker07>();
public static readonly string MagicGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
AsciiString expectedChallengeResponseString;
readonly bool allowExtensions;
readonly bool performMasking;
readonly bool allowMaskMismatch;
public WebSocketClientHandshaker07(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength)
: this(webSocketUrl, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true, false)
{
}
public WebSocketClientHandshaker07(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
bool performMasking, bool allowMaskMismatch)
: base(webSocketUrl, version, subprotocol, customHeaders, maxFramePayloadLength)
{
this.allowExtensions = allowExtensions;
this.performMasking = performMasking;
this.allowMaskMismatch = allowMaskMismatch;
}
protected internal override IFullHttpRequest NewHandshakeRequest()
{
// Get path
Uri wsUrl = this.Uri;
string path = RawPath(wsUrl);
// Get 16 bit nonce and base 64 encode it
byte[] nonce = WebSocketUtil.RandomBytes(16);
string key = WebSocketUtil.Base64String(nonce);
string acceptSeed = key + MagicGuid;
byte[] sha1 = WebSocketUtil.Sha1(Encoding.ASCII.GetBytes(acceptSeed));
this.expectedChallengeResponseString = new AsciiString(WebSocketUtil.Base64String(sha1));
if (Logger.DebugEnabled)
{
Logger.Debug("WebSocket version 07 client handshake key: {}, expected response: {}",
key, this.expectedChallengeResponseString);
}
// Format request
var request = new DefaultFullHttpRequest(HttpVersion.Http11, HttpMethod.Get, path);
HttpHeaders headers = request.Headers;
headers.Add(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket)
.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade)
.Add(HttpHeaderNames.SecWebsocketKey, key)
.Add(HttpHeaderNames.Host, WebsocketHostValue(wsUrl))
.Add(HttpHeaderNames.SecWebsocketOrigin, WebsocketOriginValue(wsUrl));
string expectedSubprotocol = this.ExpectedSubprotocol;
if (!string.IsNullOrEmpty(expectedSubprotocol))
{
headers.Add(HttpHeaderNames.SecWebsocketProtocol, expectedSubprotocol);
}
headers.Add(HttpHeaderNames.SecWebsocketVersion, "7");
if (this.CustomHeaders != null)
{
headers.Add(this.CustomHeaders);
}
return request;
}
protected override void Verify(IFullHttpResponse response)
{
HttpResponseStatus status = HttpResponseStatus.SwitchingProtocols;
HttpHeaders headers = response.Headers;
if (!response.Status.Equals(status))
{
throw new WebSocketHandshakeException($"Invalid handshake response getStatus: {response.Status}");
}
if (headers.TryGet(HttpHeaderNames.Upgrade, out ICharSequence upgrade)
|| !HttpHeaderValues.Websocket.ContentEqualsIgnoreCase(upgrade))
{
throw new WebSocketHandshakeException($"Invalid handshake response upgrade: {upgrade}");
}
if (!headers.ContainsValue(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade, true))
{
headers.TryGet(HttpHeaderNames.Connection, out upgrade);
throw new WebSocketHandshakeException($"Invalid handshake response connection: {upgrade}");
}
if (headers.TryGet(HttpHeaderNames.SecWebsocketAccept, out ICharSequence accept)
|| !accept.Equals(this.expectedChallengeResponseString))
{
throw new WebSocketHandshakeException($"Invalid challenge. Actual: {accept}. Expected: {this.expectedChallengeResponseString}");
}
}
protected internal override IWebSocketFrameDecoder NewWebSocketDecoder() => new WebSocket07FrameDecoder(
false, this.allowExtensions, this.MaxFramePayloadLength, this.allowMaskMismatch);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket07FrameEncoder(this.performMasking);
}
}

Просмотреть файл

@ -0,0 +1,118 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Text;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
public class WebSocketClientHandshaker08 : WebSocketClientHandshaker
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<WebSocketClientHandshaker08>();
public static readonly string MagicGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
AsciiString expectedChallengeResponseString;
readonly bool allowExtensions;
readonly bool performMasking;
readonly bool allowMaskMismatch;
public WebSocketClientHandshaker08(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength)
: this(webSocketUrl, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true, false)
{
}
public WebSocketClientHandshaker08(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
bool performMasking, bool allowMaskMismatch)
: base(webSocketUrl, version, subprotocol, customHeaders, maxFramePayloadLength)
{
this.allowExtensions = allowExtensions;
this.performMasking = performMasking;
this.allowMaskMismatch = allowMaskMismatch;
}
protected internal override IFullHttpRequest NewHandshakeRequest()
{
// Get path
Uri wsUrl = this.Uri;
string path = RawPath(wsUrl);
// Get 16 bit nonce and base 64 encode it
byte[] nonce = WebSocketUtil.RandomBytes(16);
string key = WebSocketUtil.Base64String(nonce);
string acceptSeed = key + MagicGuid;
byte[] sha1 = WebSocketUtil.Sha1(Encoding.ASCII.GetBytes(acceptSeed));
this.expectedChallengeResponseString = new AsciiString(WebSocketUtil.Base64String(sha1));
if (Logger.DebugEnabled)
{
Logger.Debug("WebSocket version 08 client handshake key: {}, expected response: {}",
key, this.expectedChallengeResponseString);
}
// Format request
var request = new DefaultFullHttpRequest(HttpVersion.Http11, HttpMethod.Get, path);
HttpHeaders headers = request.Headers;
headers.Add(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket)
.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade)
.Add(HttpHeaderNames.SecWebsocketKey, key)
.Add(HttpHeaderNames.Host, WebsocketHostValue(wsUrl))
.Add(HttpHeaderNames.SecWebsocketOrigin, WebsocketOriginValue(wsUrl));
string expectedSubprotocol = this.ExpectedSubprotocol;
if (!string.IsNullOrEmpty(expectedSubprotocol))
{
headers.Add(HttpHeaderNames.SecWebsocketProtocol, expectedSubprotocol);
}
headers.Add(HttpHeaderNames.SecWebsocketVersion, "8");
if (this.CustomHeaders != null)
{
headers.Add(this.CustomHeaders);
}
return request;
}
protected override void Verify(IFullHttpResponse response)
{
HttpResponseStatus status = HttpResponseStatus.SwitchingProtocols;
HttpHeaders headers = response.Headers;
if (!response.Status.Equals(status))
{
throw new WebSocketHandshakeException($"Invalid handshake response getStatus: {response.Status}");
}
if (!headers.TryGet(HttpHeaderNames.Upgrade, out ICharSequence upgrade)
|| !HttpHeaderValues.Websocket.ContentEqualsIgnoreCase(upgrade))
{
throw new WebSocketHandshakeException($"Invalid handshake response upgrade: {upgrade}");
}
if (!headers.ContainsValue(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade, true))
{
headers.TryGet(HttpHeaderNames.Connection, out upgrade);
throw new WebSocketHandshakeException($"Invalid handshake response connection: {upgrade}");
}
if (!headers.TryGet(HttpHeaderNames.SecWebsocketAccept, out ICharSequence accept)
|| !accept.Equals(this.expectedChallengeResponseString))
{
throw new WebSocketHandshakeException($"Invalid challenge. Actual: {accept}. Expected: {this.expectedChallengeResponseString}");
}
}
protected internal override IWebSocketFrameDecoder NewWebSocketDecoder() => new WebSocket08FrameDecoder(
false, this.allowExtensions, this.MaxFramePayloadLength, this.allowMaskMismatch);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket08FrameEncoder(this.performMasking);
}
}

Просмотреть файл

@ -0,0 +1,119 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Text;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
public class WebSocketClientHandshaker13 : WebSocketClientHandshaker
{
static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<WebSocketClientHandshaker13>();
public static readonly string MagicGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
AsciiString expectedChallengeResponseString;
readonly bool allowExtensions;
readonly bool performMasking;
readonly bool allowMaskMismatch;
public WebSocketClientHandshaker13(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength)
: this(webSocketUrl, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true, false)
{
}
public WebSocketClientHandshaker13(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
bool performMasking, bool allowMaskMismatch)
: base(webSocketUrl, version, subprotocol, customHeaders, maxFramePayloadLength)
{
this.allowExtensions = allowExtensions;
this.performMasking = performMasking;
this.allowMaskMismatch = allowMaskMismatch;
}
protected internal override IFullHttpRequest NewHandshakeRequest()
{
// Get path
Uri wsUrl = this.Uri;
string path = RawPath(wsUrl);
// Get 16 bit nonce and base 64 encode it
byte[] nonce = WebSocketUtil.RandomBytes(16);
string key = WebSocketUtil.Base64String(nonce);
string acceptSeed = key + MagicGuid;
byte[] sha1 = WebSocketUtil.Sha1(Encoding.ASCII.GetBytes(acceptSeed));
this.expectedChallengeResponseString = new AsciiString(WebSocketUtil.Base64String(sha1));
if (Logger.DebugEnabled)
{
Logger.Debug("WebSocket version 13 client handshake key: {}, expected response: {}",
key, this.expectedChallengeResponseString);
}
// Format request
var request = new DefaultFullHttpRequest(HttpVersion.Http11, HttpMethod.Get, path);
HttpHeaders headers = request.Headers;
headers.Add(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket)
.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade)
.Add(HttpHeaderNames.SecWebsocketKey, key)
.Add(HttpHeaderNames.Host, WebsocketHostValue(wsUrl))
.Add(HttpHeaderNames.SecWebsocketOrigin, WebsocketOriginValue(wsUrl));
string expectedSubprotocol = this.ExpectedSubprotocol;
if (!string.IsNullOrEmpty(expectedSubprotocol))
{
headers.Add(HttpHeaderNames.SecWebsocketProtocol, expectedSubprotocol);
}
headers.Add(HttpHeaderNames.SecWebsocketVersion, "13");
if (this.CustomHeaders != null)
{
headers.Add(this.CustomHeaders);
}
return request;
}
protected override void Verify(IFullHttpResponse response)
{
HttpResponseStatus status = HttpResponseStatus.SwitchingProtocols;
HttpHeaders headers = response.Headers;
if (!response.Status.Equals(status))
{
throw new WebSocketHandshakeException($"Invalid handshake response getStatus: {response.Status}");
}
if (!headers.TryGet(HttpHeaderNames.Upgrade, out ICharSequence upgrade)
|| !HttpHeaderValues.Websocket.ContentEqualsIgnoreCase(upgrade))
{
throw new WebSocketHandshakeException($"Invalid handshake response upgrade: {upgrade}");
}
if (!headers.ContainsValue(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade, true))
{
headers.TryGet(HttpHeaderNames.Connection, out upgrade);
throw new WebSocketHandshakeException($"Invalid handshake response connection: {upgrade}");
}
if (!headers.TryGet(HttpHeaderNames.SecWebsocketAccept, out ICharSequence accept)
|| !accept.Equals(this.expectedChallengeResponseString))
{
throw new WebSocketHandshakeException($"Invalid challenge. Actual: {accept}. Expected: {this.expectedChallengeResponseString}");
}
}
protected internal override IWebSocketFrameDecoder NewWebSocketDecoder() => new WebSocket13FrameDecoder(
false, this.allowExtensions, this.MaxFramePayloadLength, this.allowMaskMismatch);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket13FrameEncoder(this.performMasking);
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using static WebSocketVersion;
public static class WebSocketClientHandshakerFactory
{
public static WebSocketClientHandshaker NewHandshaker(Uri webSocketUrl, WebSocketVersion version, string subprotocol, bool allowExtensions, HttpHeaders customHeaders) =>
NewHandshaker(webSocketUrl, version, subprotocol, allowExtensions, customHeaders, 65536);
public static WebSocketClientHandshaker NewHandshaker(Uri webSocketUrl, WebSocketVersion version, string subprotocol, bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength) =>
NewHandshaker(webSocketUrl, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true, false);
public static WebSocketClientHandshaker NewHandshaker(
Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders, int maxFramePayloadLength,
bool performMasking, bool allowMaskMismatch)
{
if (version == V13)
{
return new WebSocketClientHandshaker13(
webSocketUrl, V13, subprotocol, allowExtensions, customHeaders,
maxFramePayloadLength, performMasking, allowMaskMismatch);
}
if (version == V08)
{
return new WebSocketClientHandshaker08(
webSocketUrl, V08, subprotocol, allowExtensions, customHeaders,
maxFramePayloadLength, performMasking, allowMaskMismatch);
}
if (version == V07)
{
return new WebSocketClientHandshaker07(
webSocketUrl, V07, subprotocol, allowExtensions, customHeaders,
maxFramePayloadLength, performMasking, allowMaskMismatch);
}
if (version == V00)
{
return new WebSocketClientHandshaker00(
webSocketUrl, V00, subprotocol, customHeaders, maxFramePayloadLength);
}
throw new WebSocketHandshakeException($"Protocol version {version}not supported.");
}
}
}

Просмотреть файл

@ -0,0 +1,100 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
// ReSharper disable once ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoPropertyWhenPossible
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Collections.Generic;
using DotNetty.Transport.Channels;
public class WebSocketClientProtocolHandler : WebSocketProtocolHandler
{
readonly WebSocketClientHandshaker handshaker;
readonly bool handleCloseFrames;
public WebSocketClientHandshaker Handshaker => this.handshaker;
/// <summary>
/// Events that are fired to notify about handshake status
/// </summary>
public enum ClientHandshakeStateEvent
{
/// <summary>
/// The Handshake was started but the server did not response yet to the request
/// </summary>
HandshakeIssued,
/// <summary>
/// The Handshake was complete succesful and so the channel was upgraded to websockets
/// </summary>
HandshakeComplete
}
public WebSocketClientProtocolHandler(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders,
int maxFramePayloadLength, bool handleCloseFrames,
bool performMasking, bool allowMaskMismatch)
: this(WebSocketClientHandshakerFactory.NewHandshaker(webSocketUrl, version, subprotocol,
allowExtensions, customHeaders, maxFramePayloadLength,
performMasking, allowMaskMismatch), handleCloseFrames)
{
}
public WebSocketClientProtocolHandler(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders,
int maxFramePayloadLength, bool handleCloseFrames)
: this(webSocketUrl, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength,
handleCloseFrames, true, false)
{
}
public WebSocketClientProtocolHandler(Uri webSocketUrl, WebSocketVersion version, string subprotocol,
bool allowExtensions, HttpHeaders customHeaders,
int maxFramePayloadLength)
: this(webSocketUrl, version, subprotocol, allowExtensions, customHeaders, maxFramePayloadLength, true)
{
}
public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker, bool handleCloseFrames)
{
this.handshaker = handshaker;
this.handleCloseFrames = handleCloseFrames;
}
public WebSocketClientProtocolHandler(WebSocketClientHandshaker handshaker)
: this(handshaker, true)
{
}
protected override void Decode(IChannelHandlerContext ctx, WebSocketFrame frame, List<object> output)
{
if (this.handleCloseFrames && frame is CloseWebSocketFrame)
{
ctx.CloseAsync();
return;
}
base.Decode(ctx, frame, output);
}
public override void HandlerAdded(IChannelHandlerContext ctx)
{
IChannelPipeline cp = ctx.Channel.Pipeline;
if (cp.Get<WebSocketClientProtocolHandshakeHandler>() == null)
{
// Add the WebSocketClientProtocolHandshakeHandler before this one.
ctx.Channel.Pipeline.AddBefore(ctx.Name, nameof(WebSocketClientProtocolHandshakeHandler),
new WebSocketClientProtocolHandshakeHandler(this.handshaker));
}
if (cp.Get<Utf8FrameValidator>() == null)
{
// Add the UFT8 checking before this one.
ctx.Channel.Pipeline.AddBefore(ctx.Name, nameof(Utf8FrameValidator),
new Utf8FrameValidator());
}
}
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Threading.Tasks;
using DotNetty.Transport.Channels;
class WebSocketClientProtocolHandshakeHandler : ChannelHandlerAdapter
{
readonly WebSocketClientHandshaker handshaker;
internal WebSocketClientProtocolHandshakeHandler(WebSocketClientHandshaker handshaker)
{
this.handshaker = handshaker;
}
public override void ChannelActive(IChannelHandlerContext context)
{
base.ChannelActive(context);
this.handshaker.HandshakeAsync(context.Channel)
.ContinueWith((t, state) =>
{
var ctx = (IChannelHandlerContext)state;
if (t.Status == TaskStatus.RanToCompletion)
{
ctx.FireUserEventTriggered(WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HandshakeIssued);
}
else
{
ctx.FireExceptionCaught(t.Exception);
}
},
context,
TaskContinuationOptions.ExecuteSynchronously);
}
public override void ChannelRead(IChannelHandlerContext ctx, object msg)
{
if (!(msg is IFullHttpResponse))
{
ctx.FireChannelRead(msg);
return;
}
var response = (IFullHttpResponse)msg;
try
{
if (!this.handshaker.IsHandshakeComplete)
{
this.handshaker.FinishHandshake(ctx.Channel, response);
ctx.FireUserEventTriggered(WebSocketClientProtocolHandler.ClientHandshakeStateEvent.HandshakeComplete);
ctx.Channel.Pipeline.Remove(this);
return;
}
throw new InvalidOperationException("WebSocketClientHandshaker should have been finished yet");
}
finally
{
response.Release();
}
}
}
}

Просмотреть файл

@ -0,0 +1,44 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
public abstract class WebSocketFrame : DefaultByteBufferHolder
{
// Flag to indicate if this frame is the final fragment in a message. The first fragment (frame) may also be the
// final fragment.
readonly bool finalFragment;
// RSV1, RSV2, RSV3 used for extensions
readonly int rsv;
protected WebSocketFrame(IByteBuffer binaryData)
: this(true, 0, binaryData)
{
}
protected WebSocketFrame(bool finalFragment, int rsv, IByteBuffer binaryData)
: base(binaryData)
{
this.finalFragment = finalFragment;
this.rsv = rsv;
}
/// <summary>
/// Flag to indicate if this frame is the final fragment in a message. The first fragment (frame)
/// may also be the final fragment.
/// </summary>
public bool IsFinalFragment => this.finalFragment;
/// <summary>
/// RSV1, RSV2, RSV3 used for extensions
/// </summary>
public int Rsv => this.rsv;
public override string ToString() => StringUtil.SimpleClassName(this) + "(data: " + this.ContentToString() + ')';
}
}

Просмотреть файл

@ -0,0 +1,58 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using DotNetty.Buffers;
using DotNetty.Codecs;
using DotNetty.Transport.Channels;
public class WebSocketFrameAggregator : MessageAggregator<WebSocketFrame, WebSocketFrame, ContinuationWebSocketFrame, WebSocketFrame>
{
public WebSocketFrameAggregator(int maxContentLength)
: base(maxContentLength)
{
}
protected override bool IsStartMessage(WebSocketFrame msg) => msg is TextWebSocketFrame || msg is BinaryWebSocketFrame;
protected override bool IsContentMessage(WebSocketFrame msg) => msg is ContinuationWebSocketFrame;
protected override bool IsLastContentMessage(ContinuationWebSocketFrame msg) => this.IsContentMessage(msg) && msg.IsFinalFragment;
protected override bool IsAggregated(WebSocketFrame msg)
{
if (msg.IsFinalFragment)
{
return !this.IsContentMessage(msg);
}
return !this.IsStartMessage(msg) && !this.IsContentMessage(msg);
}
protected override bool IsContentLengthInvalid(WebSocketFrame start, int maxContentLength) => false;
protected override object NewContinueResponse(WebSocketFrame start, int maxContentLength, IChannelPipeline pipeline) => null;
protected override bool CloseAfterContinueResponse(object msg) => throw new NotSupportedException();
protected override bool IgnoreContentAfterContinueResponse(object msg) => throw new NotSupportedException();
protected override WebSocketFrame BeginAggregation(WebSocketFrame start, IByteBuffer content)
{
if (start is TextWebSocketFrame)
{
return new TextWebSocketFrame(true, start.Rsv, content);
}
if (start is BinaryWebSocketFrame)
{
return new BinaryWebSocketFrame(true, start.Rsv, content);
}
// Should not reach here.
throw new Exception("Unkonw WebSocketFrame type, must be either TextWebSocketFrame or BinaryWebSocketFrame");
}
}
}

Просмотреть файл

@ -0,0 +1,25 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
public class WebSocketHandshakeException : Exception
{
public WebSocketHandshakeException(string message, Exception innereException)
: base(message, innereException)
{
}
public WebSocketHandshakeException(string message)
: base(message)
{
}
public WebSocketHandshakeException(Exception innerException)
: base(null, innerException)
{
}
}
}

Просмотреть файл

@ -0,0 +1,47 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Collections.Generic;
using DotNetty.Transport.Channels;
public abstract class WebSocketProtocolHandler : MessageToMessageDecoder<WebSocketFrame>
{
readonly bool dropPongFrames;
internal WebSocketProtocolHandler() : this(true)
{
}
internal WebSocketProtocolHandler(bool dropPongFrames)
{
this.dropPongFrames = dropPongFrames;
}
protected override void Decode(IChannelHandlerContext ctx, WebSocketFrame frame, List<object> output)
{
if (frame is PingWebSocketFrame)
{
frame.Content.Retain();
ctx.Channel.WriteAndFlushAsync(new PongWebSocketFrame(frame.Content));
return;
}
if (frame is PongWebSocketFrame && this.dropPongFrames)
{
// Pong frames need to get ignored
return;
}
output.Add(frame.Retain());
}
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception cause)
{
ctx.FireExceptionCaught(cause);
ctx.CloseAsync();
}
}
}

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable InconsistentNaming
namespace DotNetty.Codecs.Http.WebSockets
{
using DotNetty.Common.Utilities;
public sealed class WebSocketScheme
{
// Scheme for non-secure WebSocket connection.
public static readonly WebSocketScheme WS = new WebSocketScheme(80, "ws");
// Scheme for secure WebSocket connection.
public static readonly WebSocketScheme WSS = new WebSocketScheme(443, "wss");
readonly int port;
readonly AsciiString name;
WebSocketScheme(int port, string name)
{
this.port = port;
this.name = AsciiString.Cached(name);
}
public AsciiString Name => this.name;
public int Port => this.port;
public override bool Equals(object obj) => obj is WebSocketScheme other
&& other.port == this.port && other.name.Equals(this.name);
public override int GetHashCode() => this.port * 31 + this.name.GetHashCode();
public override string ToString() => this.name.ToString();
}
}

Просмотреть файл

@ -0,0 +1,257 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
// ReSharper disable ConvertToAutoPropertyWhenPossible
// ReSharper disable ConvertToAutoPropertyWithPrivateSetter
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Threading.Tasks;
using DotNetty.Common.Concurrency;
using DotNetty.Common.Internal;
using DotNetty.Common.Internal.Logging;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public abstract class WebSocketServerHandshaker
{
protected static readonly IInternalLogger Logger = InternalLoggerFactory.GetInstance<WebSocketServerHandshaker>();
static readonly ClosedChannelException ClosedChannelException = new ClosedChannelException();
readonly string uri;
readonly string[] subprotocols;
readonly WebSocketVersion version;
readonly int maxFramePayloadLength;
string selectedSubprotocol;
// Use this as wildcard to support all requested sub-protocols
public static readonly string SubProtocolWildcard = "*";
protected WebSocketServerHandshaker(WebSocketVersion version, string uri, string subprotocols, int maxFramePayloadLength)
{
this.version = version;
this.uri = uri;
if (subprotocols != null)
{
string[] subprotocolArray = subprotocols.Split(',');
for (int i = 0; i < subprotocolArray.Length; i++)
{
subprotocolArray[i] = subprotocolArray[i].Trim();
}
this.subprotocols = subprotocolArray;
}
else
{
this.subprotocols = EmptyArrays.EmptyStrings;
}
this.maxFramePayloadLength = maxFramePayloadLength;
}
public string Uri => this.uri;
public ISet<string> Subprotocols()
{
var ret = new HashSet<string>(this.subprotocols);
return ret;
}
public WebSocketVersion Version => this. version;
public int MaxFramePayloadLength => this.maxFramePayloadLength;
public Task HandshakeAsync(IChannel channel, IFullHttpRequest req) => this.HandshakeAsync(channel, req, null);
public Task HandshakeAsync(IChannel channel, IFullHttpRequest req, HttpHeaders responseHeaders)
{
var completion = new TaskCompletionSource();
this.Handshake(channel, req, responseHeaders, completion);
return completion.Task;
}
public void Handshake(IChannel channel, IFullHttpRequest req, HttpHeaders responseHeaders, TaskCompletionSource completion)
{
if (Logger.DebugEnabled)
{
Logger.Debug("{} WebSocket version {} server handshake", channel, this.version);
}
IFullHttpResponse response = this.NewHandshakeResponse(req, responseHeaders);
IChannelPipeline p = channel.Pipeline;
if (p.Get<HttpObjectAggregator>() != null)
{
p.Remove<HttpObjectAggregator>();
}
if (p.Get<HttpContentCompressor>() != null)
{
p.Remove<HttpContentCompressor>();
}
IChannelHandlerContext ctx = p.Context<HttpRequestDecoder>();
string encoderName;
if (ctx == null)
{
// this means the user use a HttpServerCodec
ctx = p.Context<HttpServerCodec>();
if (ctx == null)
{
completion.TrySetException(new InvalidOperationException("No HttpDecoder and no HttpServerCodec in the pipeline"));
return;
}
p.AddBefore(ctx.Name, "wsdecoder", this.NewWebsocketDecoder());
p.AddBefore(ctx.Name, "wsencoder", this.NewWebSocketEncoder());
encoderName = ctx.Name;
}
else
{
p.Replace(ctx.Name, "wsdecoder", this.NewWebsocketDecoder());
encoderName = p.Context<HttpResponseEncoder>().Name;
p.AddBefore(encoderName, "wsencoder", this.NewWebSocketEncoder());
}
channel.WriteAndFlushAsync(response).ContinueWith(t =>
{
if (t.Status == TaskStatus.RanToCompletion)
{
p.Remove(encoderName);
completion.TryComplete();
}
else
{
completion.TrySetException(t.Exception);
}
});
}
public Task HandshakeAsync(IChannel channel, IHttpRequest req, HttpHeaders responseHeaders)
{
if (req is IFullHttpRequest request)
{
return this.HandshakeAsync(channel, request, responseHeaders);
}
if (Logger.DebugEnabled)
{
Logger.Debug("{} WebSocket version {} server handshake", channel, this.version);
}
IChannelPipeline p = channel.Pipeline;
IChannelHandlerContext ctx = p.Context<HttpRequestDecoder>();
if (ctx == null)
{
// this means the user use a HttpServerCodec
ctx = p.Context<HttpServerCodec>();
if (ctx == null)
{
return TaskEx.FromException(new InvalidOperationException("No HttpDecoder and no HttpServerCodec in the pipeline"));
}
}
// Add aggregator and ensure we feed the HttpRequest so it is aggregated. A limit o 8192 should be more then
// enough for the websockets handshake payload.
//
// TODO: Make handshake work without HttpObjectAggregator at all.
string aggregatorName = "httpAggregator";
p.AddAfter(ctx.Name, aggregatorName, new HttpObjectAggregator(8192));
var completion = new TaskCompletionSource();
p.AddAfter(aggregatorName, "handshaker", new Handshaker(this, channel, responseHeaders, completion));
try
{
ctx.FireChannelRead(ReferenceCountUtil.Retain(req));
}
catch (Exception cause)
{
completion.TrySetException(cause);
}
return completion.Task;
}
sealed class Handshaker : SimpleChannelInboundHandler<IFullHttpRequest>
{
readonly WebSocketServerHandshaker serverHandshaker;
readonly IChannel channel;
readonly HttpHeaders responseHeaders;
readonly TaskCompletionSource completion;
public Handshaker(WebSocketServerHandshaker serverHandshaker, IChannel channel, HttpHeaders responseHeaders, TaskCompletionSource completion)
{
this.serverHandshaker = serverHandshaker;
this.channel = channel;
this.responseHeaders = responseHeaders;
this.completion = completion;
}
protected override void ChannelRead0(IChannelHandlerContext ctx, IFullHttpRequest msg)
{
// Remove ourself and do the actual handshake
ctx.Channel.Pipeline.Remove(this);
this.serverHandshaker.Handshake(this.channel, msg, this.responseHeaders, this.completion);
}
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception cause)
{
// Remove ourself and fail the handshake promise.
ctx.Channel.Pipeline.Remove(this);
this.completion.TrySetException(cause);
ctx.FireExceptionCaught(cause);
}
public override void ChannelInactive(IChannelHandlerContext ctx)
{
// Fail promise if Channel was closed
this.completion.TrySetException(ClosedChannelException);
ctx.FireChannelInactive();
}
}
protected abstract IFullHttpResponse NewHandshakeResponse(IFullHttpRequest req, HttpHeaders responseHeaders);
public virtual Task CloseAsync(IChannel channel, CloseWebSocketFrame frame)
{
Contract.Requires(channel != null);
return channel.WriteAndFlushAsync(frame).ContinueWith((t, s) => ((IChannel)s).CloseAsync(),
channel, TaskContinuationOptions.ExecuteSynchronously);
}
protected string SelectSubprotocol(string requestedSubprotocols)
{
if (requestedSubprotocols == null || this.subprotocols.Length == 0)
{
return null;
}
string[] requestedSubprotocolArray = requestedSubprotocols.Split(',');
foreach (string p in requestedSubprotocolArray)
{
string requestedSubprotocol = p.Trim();
foreach (string supportedSubprotocol in this.subprotocols)
{
if (SubProtocolWildcard.Equals(supportedSubprotocol)
|| requestedSubprotocol.Equals(supportedSubprotocol))
{
this.selectedSubprotocol = requestedSubprotocol;
return requestedSubprotocol;
}
}
}
// No match found
return null;
}
public string SelectedSubprotocol => this.selectedSubprotocol;
protected internal abstract IWebSocketFrameDecoder NewWebsocketDecoder();
protected internal abstract IWebSocketFrameEncoder NewWebSocketEncoder();
}
}

Просмотреть файл

@ -0,0 +1,114 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class WebSocketServerHandshaker00 : WebSocketServerHandshaker
{
static readonly Regex BeginningDigit = new Regex("[^0-9]", RegexOptions.Compiled);
static readonly Regex BeginningSpace = new Regex("[^ ]", RegexOptions.Compiled);
public WebSocketServerHandshaker00(string webSocketUrl, string subprotocols, int maxFramePayloadLength)
: base(WebSocketVersion.V00, webSocketUrl, subprotocols, maxFramePayloadLength)
{
}
protected override IFullHttpResponse NewHandshakeResponse(IFullHttpRequest req, HttpHeaders headers)
{
// Serve the WebSocket handshake request.
if (!req.Headers.ContainsValue(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade, true)
|| !req.Headers.TryGet(HttpHeaderNames.Upgrade, out ICharSequence value)
|| !HttpHeaderValues.Websocket.ContentEqualsIgnoreCase(value))
{
throw new WebSocketHandshakeException("not a WebSocket handshake request: missing upgrade");
}
// Hixie 75 does not contain these headers while Hixie 76 does
bool isHixie76 = req.Headers.Contains(HttpHeaderNames.SecWebsocketKey1)
&& req.Headers.Contains(HttpHeaderNames.SecWebsocketKey2);
// Create the WebSocket handshake response.
var res = new DefaultFullHttpResponse(HttpVersion.Http11,
new HttpResponseStatus(101, new AsciiString(isHixie76 ? "WebSocket Protocol Handshake" : "Web Socket Protocol Handshake")));
if (headers != null)
{
res.Headers.Add(headers);
}
res.Headers.Add(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket);
res.Headers.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade);
// Fill in the headers and contents depending on handshake getMethod.
if (isHixie76)
{
// New handshake getMethod with a challenge:
value = req.Headers.Get(HttpHeaderNames.Origin, null);
Debug.Assert(value != null);
res.Headers.Add(HttpHeaderNames.SecWebsocketOrigin, value);
res.Headers.Add(HttpHeaderNames.SecWebsocketLocation, this.Uri);
if (req.Headers.TryGet(HttpHeaderNames.SecWebsocketProtocol, out ICharSequence subprotocols))
{
string selectedSubprotocol = this.SelectSubprotocol(subprotocols.ToString());
if (selectedSubprotocol == null)
{
if (Logger.DebugEnabled)
{
Logger.Debug("Requested subprotocol(s) not supported: {}", subprotocols);
}
}
else
{
res.Headers.Add(HttpHeaderNames.SecWebsocketProtocol, selectedSubprotocol);
}
}
// Calculate the answer of the challenge.
value = req.Headers.Get(HttpHeaderNames.SecWebsocketKey1, null);
Debug.Assert(value != null, $"{HttpHeaderNames.SecWebsocketKey1} must exist");
string key1 = value.ToString();
value = req.Headers.Get(HttpHeaderNames.SecWebsocketKey2, null);
Debug.Assert(value != null, $"{HttpHeaderNames.SecWebsocketKey2} must exist");
string key2 = value.ToString();
int a = (int)(long.Parse(BeginningDigit.Replace(key1, "")) /
BeginningSpace.Replace(key1, "").Length);
int b = (int)(long.Parse(BeginningDigit.Replace(key2, "")) /
BeginningSpace.Replace(key2, "").Length);
long c = req.Content.ReadLong();
IByteBuffer input = Unpooled.Buffer(16);
input.WriteInt(a);
input.WriteInt(b);
input.WriteLong(c);
res.Content.WriteBytes(WebSocketUtil.Md5(input.Array));
}
else
{
// Old Hixie 75 handshake getMethod with no challenge:
value = req.Headers.Get(HttpHeaderNames.Origin, null);
Debug.Assert(value != null);
res.Headers.Add(HttpHeaderNames.WebsocketOrigin, value);
res.Headers.Add(HttpHeaderNames.WebsocketLocation, this.Uri);
if (req.Headers.TryGet(HttpHeaderNames.WebsocketProtocol, out ICharSequence protocol))
{
res.Headers.Add(HttpHeaderNames.WebsocketProtocol, this.SelectSubprotocol(protocol.ToString()));
}
}
return res;
}
public override Task CloseAsync(IChannel channel, CloseWebSocketFrame frame) => channel.WriteAndFlushAsync(frame);
protected internal override IWebSocketFrameDecoder NewWebsocketDecoder() => new WebSocket00FrameDecoder(this.MaxFramePayloadLength);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket00FrameEncoder();
}
}

Просмотреть файл

@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Text;
using DotNetty.Common.Utilities;
public class WebSocketServerHandshaker07 : WebSocketServerHandshaker
{
public static readonly string Websocket07AcceptGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
readonly bool allowExtensions;
readonly bool allowMaskMismatch;
public WebSocketServerHandshaker07(string webSocketUrl, string subprotocols, bool allowExtensions, int maxFramePayloadLength)
: this(webSocketUrl, subprotocols, allowExtensions, maxFramePayloadLength, false)
{
}
public WebSocketServerHandshaker07(string webSocketUrl, string subprotocols, bool allowExtensions, int maxFramePayloadLength,
bool allowMaskMismatch)
: base(WebSocketVersion.V07, webSocketUrl, subprotocols, maxFramePayloadLength)
{
this.allowExtensions = allowExtensions;
this.allowMaskMismatch = allowMaskMismatch;
}
protected override IFullHttpResponse NewHandshakeResponse(IFullHttpRequest req, HttpHeaders headers)
{
var res = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.SwitchingProtocols);
if (headers != null)
{
res.Headers.Add(headers);
}
if (!req.Headers.TryGet(HttpHeaderNames.SecWebsocketKey, out ICharSequence key)
|| key == null)
{
throw new WebSocketHandshakeException("not a WebSocket request: missing key");
}
string acceptSeed = key + Websocket07AcceptGuid;
byte[] sha1 = WebSocketUtil.Sha1(Encoding.ASCII.GetBytes(acceptSeed));
string accept = WebSocketUtil.Base64String(sha1);
if (Logger.DebugEnabled)
{
Logger.Debug("WebSocket version 07 server handshake key: {}, response: {}.", key, accept);
}
res.Headers.Add(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket);
res.Headers.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade);
res.Headers.Add(HttpHeaderNames.SecWebsocketAccept, accept);
if (req.Headers.TryGet(HttpHeaderNames.SecWebsocketProtocol, out ICharSequence subprotocols)
&& subprotocols != null)
{
string selectedSubprotocol = this.SelectSubprotocol(subprotocols.ToString());
if (selectedSubprotocol == null)
{
if (Logger.DebugEnabled)
{
Logger.Debug("Requested subprotocol(s) not supported: {}", subprotocols);
}
}
else
{
res.Headers.Add(HttpHeaderNames.SecWebsocketProtocol, selectedSubprotocol);
}
}
return res;
}
protected internal override IWebSocketFrameDecoder NewWebsocketDecoder() => new WebSocket07FrameDecoder(
true, this.allowExtensions, this.MaxFramePayloadLength, this.allowMaskMismatch);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket07FrameEncoder(false);
}
}

Просмотреть файл

@ -0,0 +1,80 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Text;
using DotNetty.Common.Utilities;
public class WebSocketServerHandshaker08 : WebSocketServerHandshaker
{
public static readonly string Websocket08AcceptGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
readonly bool allowExtensions;
readonly bool allowMaskMismatch;
public WebSocketServerHandshaker08(string webSocketUrl, string subprotocols, bool allowExtensions, int maxFramePayloadLength)
: this(webSocketUrl, subprotocols, allowExtensions, maxFramePayloadLength, false)
{
}
public WebSocketServerHandshaker08(string webSocketUrl, string subprotocols, bool allowExtensions, int maxFramePayloadLength,
bool allowMaskMismatch)
: base(WebSocketVersion.V08, webSocketUrl, subprotocols, maxFramePayloadLength)
{
this.allowExtensions = allowExtensions;
this.allowMaskMismatch = allowMaskMismatch;
}
protected override IFullHttpResponse NewHandshakeResponse(IFullHttpRequest req, HttpHeaders headers)
{
var res = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.SwitchingProtocols);
if (headers != null)
{
res.Headers.Add(headers);
}
if (!req.Headers.TryGet(HttpHeaderNames.SecWebsocketKey, out ICharSequence key)
|| key == null)
{
throw new WebSocketHandshakeException("not a WebSocket request: missing key");
}
string acceptSeed = key + Websocket08AcceptGuid;
byte[] sha1 = WebSocketUtil.Sha1(Encoding.ASCII.GetBytes(acceptSeed));
string accept = WebSocketUtil.Base64String(sha1);
if (Logger.DebugEnabled)
{
Logger.Debug("WebSocket version 08 server handshake key: {}, response: {}", key, accept);
}
res.Headers.Add(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket);
res.Headers.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade);
res.Headers.Add(HttpHeaderNames.SecWebsocketAccept, accept);
if (req.Headers.TryGet(HttpHeaderNames.SecWebsocketProtocol, out ICharSequence subprotocols)
&& subprotocols != null)
{
string selectedSubprotocol = this.SelectSubprotocol(subprotocols.ToString());
if (selectedSubprotocol == null)
{
if (Logger.DebugEnabled)
{
Logger.Debug("Requested subprotocol(s) not supported: {}", subprotocols);
}
}
else
{
res.Headers.Add(HttpHeaderNames.SecWebsocketProtocol, selectedSubprotocol);
}
}
return res;
}
protected internal override IWebSocketFrameDecoder NewWebsocketDecoder() => new WebSocket08FrameDecoder(
true, this.allowExtensions, this.MaxFramePayloadLength, this.allowMaskMismatch);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket08FrameEncoder(false);
}
}

Просмотреть файл

@ -0,0 +1,79 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Text;
using DotNetty.Common.Utilities;
public class WebSocketServerHandshaker13 : WebSocketServerHandshaker
{
public static readonly string Websocket13AcceptGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
readonly bool allowExtensions;
readonly bool allowMaskMismatch;
public WebSocketServerHandshaker13(string webSocketUrl, string subprotocols, bool allowExtensions, int maxFramePayloadLength)
: this(webSocketUrl, subprotocols, allowExtensions, maxFramePayloadLength, false)
{
}
public WebSocketServerHandshaker13(string webSocketUrl, string subprotocols, bool allowExtensions, int maxFramePayloadLength,
bool allowMaskMismatch)
: base(WebSocketVersion.V13, webSocketUrl, subprotocols, maxFramePayloadLength)
{
this.allowExtensions = allowExtensions;
this.allowMaskMismatch = allowMaskMismatch;
}
protected override IFullHttpResponse NewHandshakeResponse(IFullHttpRequest req, HttpHeaders headers)
{
var res = new DefaultFullHttpResponse(HttpVersion.Http11, HttpResponseStatus.SwitchingProtocols);
if (headers != null)
{
res.Headers.Add(headers);
}
if (!req.Headers.TryGet(HttpHeaderNames.SecWebsocketKey, out ICharSequence key)
|| key == null)
{
throw new WebSocketHandshakeException("not a WebSocket request: missing key");
}
string acceptSeed = key.ToString() + Websocket13AcceptGuid;
byte[] sha1 = WebSocketUtil.Sha1(Encoding.ASCII.GetBytes(acceptSeed));
string accept = WebSocketUtil.Base64String(sha1);
if (Logger.DebugEnabled)
{
Logger.Debug("WebSocket version 13 server handshake key: {}, response: {}", key, accept);
}
res.Headers.Add(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket);
res.Headers.Add(HttpHeaderNames.Connection, HttpHeaderValues.Upgrade);
res.Headers.Add(HttpHeaderNames.SecWebsocketAccept, accept);
if (req.Headers.TryGet(HttpHeaderNames.SecWebsocketProtocol, out ICharSequence subprotocols)
&& subprotocols != null)
{
string selectedSubprotocol = this.SelectSubprotocol(subprotocols.ToString());
if (selectedSubprotocol == null)
{
if (Logger.DebugEnabled)
{
Logger.Debug("Requested subprotocol(s) not supported: {}", subprotocols);
}
}
else
{
res.Headers.Add(HttpHeaderNames.SecWebsocketProtocol, selectedSubprotocol);
}
}
return res;
}
protected internal override IWebSocketFrameDecoder NewWebsocketDecoder() => new WebSocket13FrameDecoder(
true, this.allowExtensions, this.MaxFramePayloadLength, this.allowMaskMismatch);
protected internal override IWebSocketFrameEncoder NewWebSocketEncoder() => new WebSocket13FrameEncoder(false);
}
}

Просмотреть файл

@ -0,0 +1,100 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Threading.Tasks;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
public class WebSocketServerHandshakerFactory
{
readonly string webSocketUrl;
readonly string subprotocols;
readonly bool allowExtensions;
readonly int maxFramePayloadLength;
readonly bool allowMaskMismatch;
public WebSocketServerHandshakerFactory(string webSocketUrl, string subprotocols, bool allowExtensions)
: this(webSocketUrl, subprotocols, allowExtensions, 65536)
{
}
public WebSocketServerHandshakerFactory(string webSocketUrl, string subprotocols, bool allowExtensions,
int maxFramePayloadLength)
: this(webSocketUrl, subprotocols, allowExtensions, maxFramePayloadLength, false)
{
}
public WebSocketServerHandshakerFactory(string webSocketUrl, string subprotocols, bool allowExtensions,
int maxFramePayloadLength, bool allowMaskMismatch)
{
this.webSocketUrl = webSocketUrl;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
this.maxFramePayloadLength = maxFramePayloadLength;
this.allowMaskMismatch = allowMaskMismatch;
}
public WebSocketServerHandshaker NewHandshaker(IHttpRequest req)
{
if (req.Headers.TryGet(HttpHeaderNames.SecWebsocketVersion, out ICharSequence version)
&& version != null)
{
if (version.Equals(WebSocketVersion.V13.ToHttpHeaderValue()))
{
// Version 13 of the wire protocol - RFC 6455 (version 17 of the draft hybi specification).
return new WebSocketServerHandshaker13(
this.webSocketUrl,
this.subprotocols,
this.allowExtensions,
this.maxFramePayloadLength,
this.allowMaskMismatch);
}
else if (version.Equals(WebSocketVersion.V08.ToHttpHeaderValue()))
{
// Version 8 of the wire protocol - version 10 of the draft hybi specification.
return new WebSocketServerHandshaker08(
this.webSocketUrl,
this.subprotocols,
this.allowExtensions,
this.maxFramePayloadLength,
this.allowMaskMismatch);
}
else if (version.Equals(WebSocketVersion.V07.ToHttpHeaderValue()))
{
// Version 8 of the wire protocol - version 07 of the draft hybi specification.
return new WebSocketServerHandshaker07(
this.webSocketUrl,
this.subprotocols,
this.allowExtensions,
this.maxFramePayloadLength,
this.allowMaskMismatch);
}
else
{
return null;
}
}
else
{
// Assume version 00 where version header was not specified
return new WebSocketServerHandshaker00(this.webSocketUrl, this.subprotocols, this.maxFramePayloadLength);
}
}
public static Task SendUnsupportedVersionResponse(IChannel channel)
{
var res = new DefaultFullHttpResponse(
HttpVersion.Http11,
HttpResponseStatus.UpgradeRequired);
res.Headers.Set(HttpHeaderNames.SecWebsocketVersion, WebSocketVersion.V13.ToHttpHeaderValue());
HttpUtil.SetContentLength(res, 0);
return channel.WriteAndFlushAsync(res);
}
}
}

Просмотреть файл

@ -0,0 +1,185 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using static HttpVersion;
public class WebSocketServerProtocolHandler : WebSocketProtocolHandler
{
public sealed class HandshakeComplete
{
readonly string requestUri;
readonly HttpHeaders requestHeaders;
readonly string selectedSubprotocol;
internal HandshakeComplete(string requestUri, HttpHeaders requestHeaders, string selectedSubprotocol)
{
this.requestUri = requestUri;
this.requestHeaders = requestHeaders;
this.selectedSubprotocol = selectedSubprotocol;
}
public string RequestUri => this.requestUri;
public HttpHeaders RequestHeaders => this.requestHeaders;
public string SelectedSubprotocol => this.selectedSubprotocol;
}
static readonly AttributeKey<WebSocketServerHandshaker> HandshakerAttrKey =
AttributeKey<WebSocketServerHandshaker>.ValueOf("HANDSHAKER");
readonly string websocketPath;
readonly string subprotocols;
readonly bool allowExtensions;
readonly int maxFramePayloadLength;
readonly bool allowMaskMismatch;
readonly bool checkStartsWith;
public WebSocketServerProtocolHandler(string websocketPath)
: this(websocketPath, null, false)
{
}
public WebSocketServerProtocolHandler(string websocketPath, bool checkStartsWith)
: this(websocketPath, null, false, 65536, false, checkStartsWith)
{
}
public WebSocketServerProtocolHandler(string websocketPath, string subprotocols)
: this(websocketPath, subprotocols, false)
{
}
public WebSocketServerProtocolHandler(string websocketPath, string subprotocols, bool allowExtensions)
: this(websocketPath, subprotocols, allowExtensions, 65536)
{
}
public WebSocketServerProtocolHandler(string websocketPath, string subprotocols,
bool allowExtensions, int maxFrameSize)
: this(websocketPath, subprotocols, allowExtensions, maxFrameSize, false)
{
}
public WebSocketServerProtocolHandler(string websocketPath, string subprotocols,
bool allowExtensions, int maxFrameSize, bool allowMaskMismatch)
: this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, false)
{
}
public WebSocketServerProtocolHandler(string websocketPath, string subprotocols, bool allowExtensions,
int maxFrameSize, bool allowMaskMismatch, bool checkStartsWith)
: this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, checkStartsWith, true)
{
}
public WebSocketServerProtocolHandler(string websocketPath, string subprotocols,
bool allowExtensions, int maxFrameSize, bool allowMaskMismatch, bool checkStartsWith, bool dropPongFrames)
: base(dropPongFrames)
{
this.websocketPath = websocketPath;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
this.maxFramePayloadLength = maxFrameSize;
this.allowMaskMismatch = allowMaskMismatch;
this.checkStartsWith = checkStartsWith;
}
public override void HandlerAdded(IChannelHandlerContext ctx)
{
IChannelPipeline cp = ctx.Channel.Pipeline;
if (cp.Get<WebSocketServerProtocolHandshakeHandler>() == null)
{
// Add the WebSocketHandshakeHandler before this one.
ctx.Channel.Pipeline.AddBefore(ctx.Name, nameof(WebSocketServerProtocolHandshakeHandler),
new WebSocketServerProtocolHandshakeHandler(
this.websocketPath,
this.subprotocols,
this.allowExtensions,
this.maxFramePayloadLength,
this.allowMaskMismatch,
this.checkStartsWith));
}
if (cp.Get<Utf8FrameValidator>() == null)
{
// Add the UFT8 checking before this one.
ctx.Channel.Pipeline.AddBefore(ctx.Name, nameof(Utf8FrameValidator), new Utf8FrameValidator());
}
}
protected override void Decode(IChannelHandlerContext ctx, WebSocketFrame frame, List<object> output)
{
if (frame is CloseWebSocketFrame socketFrame)
{
WebSocketServerHandshaker handshaker = GetHandshaker(ctx.Channel);
if (handshaker != null)
{
frame.Retain();
handshaker.CloseAsync(ctx.Channel, socketFrame);
}
else
{
ctx.WriteAndFlushAsync(Unpooled.Empty)
.ContinueWith((t, c) => ((IChannelHandlerContext)c).CloseAsync(),
ctx, TaskContinuationOptions.ExecuteSynchronously);
}
return;
}
base.Decode(ctx, frame, output);
}
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception cause)
{
if (cause is WebSocketHandshakeException)
{
var response = new DefaultFullHttpResponse(Http11, HttpResponseStatus.BadRequest,
Unpooled.WrappedBuffer(Encoding.ASCII.GetBytes(cause.Message)));
ctx.Channel.WriteAndFlushAsync(response)
.ContinueWith((t, c) => ((IChannelHandlerContext)c).CloseAsync(),
ctx, TaskContinuationOptions.ExecuteSynchronously);
}
else
{
ctx.FireExceptionCaught(cause);
ctx.CloseAsync();
}
}
internal static WebSocketServerHandshaker GetHandshaker(IChannel channel) => channel.GetAttribute(HandshakerAttrKey).Get();
internal static void SetHandshaker(IChannel channel, WebSocketServerHandshaker handshaker) => channel.GetAttribute(HandshakerAttrKey).Set(handshaker);
internal static IChannelHandler ForbiddenHttpRequestResponder() => new ForbiddenResponseHandler();
sealed class ForbiddenResponseHandler : ChannelHandlerAdapter
{
public override void ChannelRead(IChannelHandlerContext ctx, object msg)
{
if (msg is IFullHttpRequest request)
{
request.Release();
var response = new DefaultFullHttpResponse(Http11, HttpResponseStatus.Forbidden);
ctx.Channel.WriteAndFlushAsync(response);
}
else
{
ctx.FireChannelRead(msg);
}
}
}
}
}

Просмотреть файл

@ -0,0 +1,126 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System.Threading.Tasks;
using DotNetty.Common.Utilities;
using DotNetty.Handlers.Tls;
using DotNetty.Transport.Channels;
using static HttpUtil;
using static HttpMethod;
using static HttpVersion;
using static HttpResponseStatus;
class WebSocketServerProtocolHandshakeHandler : ChannelHandlerAdapter
{
readonly string websocketPath;
readonly string subprotocols;
readonly bool allowExtensions;
readonly int maxFramePayloadSize;
readonly bool allowMaskMismatch;
readonly bool checkStartsWith;
internal WebSocketServerProtocolHandshakeHandler(string websocketPath, string subprotocols,
bool allowExtensions, int maxFrameSize, bool allowMaskMismatch)
: this(websocketPath, subprotocols, allowExtensions, maxFrameSize, allowMaskMismatch, false)
{
}
internal WebSocketServerProtocolHandshakeHandler(string websocketPath, string subprotocols,
bool allowExtensions, int maxFrameSize, bool allowMaskMismatch, bool checkStartsWith)
{
this.websocketPath = websocketPath;
this.subprotocols = subprotocols;
this.allowExtensions = allowExtensions;
this.maxFramePayloadSize = maxFrameSize;
this.allowMaskMismatch = allowMaskMismatch;
this.checkStartsWith = checkStartsWith;
}
public override void ChannelRead(IChannelHandlerContext ctx, object msg)
{
var req = (IFullHttpRequest)msg;
if (this.IsNotWebSocketPath(req))
{
ctx.FireChannelRead(msg);
return;
}
try
{
if (!Equals(req.Method, Get))
{
SendHttpResponse(ctx, req, new DefaultFullHttpResponse(Http11, Forbidden));
return;
}
var wsFactory = new WebSocketServerHandshakerFactory(
GetWebSocketLocation(ctx.Channel.Pipeline, req, this.websocketPath), this.subprotocols,
this.allowExtensions, this.maxFramePayloadSize, this.allowMaskMismatch);
WebSocketServerHandshaker handshaker = wsFactory.NewHandshaker(req);
if (handshaker == null)
{
WebSocketServerHandshakerFactory.SendUnsupportedVersionResponse(ctx.Channel);
}
else
{
Task task = handshaker.HandshakeAsync(ctx.Channel, req);
task.ContinueWith(t =>
{
if (t.Status != TaskStatus.RanToCompletion)
{
ctx.FireExceptionCaught(t.Exception);
}
else
{
ctx.FireUserEventTriggered(new WebSocketServerProtocolHandler.HandshakeComplete(
req.Uri, req.Headers, handshaker.SelectedSubprotocol));
}
},
TaskContinuationOptions.ExecuteSynchronously);
WebSocketServerProtocolHandler.SetHandshaker(ctx.Channel, handshaker);
ctx.Channel.Pipeline.Replace(this, "WS403Responder",
WebSocketServerProtocolHandler.ForbiddenHttpRequestResponder());
}
}
finally
{
req.Release();
}
}
bool IsNotWebSocketPath(IFullHttpRequest req) => this.checkStartsWith
? !req.Uri.StartsWith(this.websocketPath)
: !req.Uri.Equals(this.websocketPath);
static void SendHttpResponse(IChannelHandlerContext ctx, IHttpRequest req, IHttpResponse res)
{
Task task = ctx.Channel.WriteAndFlushAsync(res);
if (!IsKeepAlive(req) || res.Status.Code != 200)
{
task.ContinueWith((t, c) => ((IChannel)c).CloseAsync(),
ctx.Channel, TaskContinuationOptions.ExecuteSynchronously);
}
}
static string GetWebSocketLocation(IChannelPipeline cp, IHttpRequest req, string path)
{
string protocol = "ws";
if (cp.Get<TlsHandler>() != null)
{
// SSL in use so use Secure WebSockets
protocol = "wss";
}
string host = null;
if (req.Headers.TryGet(HttpHeaderNames.Host, out ICharSequence value))
{
host = value.ToString();
}
return $"{protocol}://{host}{path}";
}
}
}

Просмотреть файл

@ -0,0 +1,67 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Security.Cryptography;
using System.Text;
using DotNetty.Codecs.Base64;
using DotNetty.Buffers;
using DotNetty.Common;
using DotNetty.Common.Internal;
static class WebSocketUtil
{
static readonly Random Random = PlatformDependent.GetThreadLocalRandom();
static readonly ThreadLocalMD5 LocalMd5 = new ThreadLocalMD5();
sealed class ThreadLocalMD5 : FastThreadLocal<MD5>
{
protected override MD5 GetInitialValue() => MD5.Create();
}
static readonly ThreadLocalSha1 LocalSha1 = new ThreadLocalSha1();
sealed class ThreadLocalSha1 : FastThreadLocal<SHA1>
{
protected override SHA1 GetInitialValue() => SHA1.Create();
}
internal static byte[] Md5(byte[] data)
{
MD5 md5 = LocalMd5.Value;
md5.Initialize();
return md5.ComputeHash(data);
}
internal static byte[] Sha1(byte[] data)
{
SHA1 sha1 = LocalSha1.Value;
sha1.Initialize();
return sha1.ComputeHash(data);
}
internal static string Base64String(byte[] data)
{
IByteBuffer encodedData = Unpooled.WrappedBuffer(data);
IByteBuffer encoded = Base64.Encode(encodedData);
string encodedString = encoded.ToString(Encoding.UTF8);
encoded.Release();
return encodedString;
}
internal static byte[] RandomBytes(int size)
{
var bytes = new byte[size];
Random.NextBytes(bytes);
return bytes;
}
internal static int RandomNumber(int minimum, int maximum) => unchecked((int)(Random.NextDouble() * maximum + minimum));
// Math.Random()
internal static double RandomNext() => Random.NextDouble();
}
}

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local
namespace DotNetty.Codecs.Http.WebSockets
{
using System;
using System.Runtime.CompilerServices;
using DotNetty.Common.Utilities;
public sealed class WebSocketVersion
{
public static readonly WebSocketVersion Unknown = new WebSocketVersion("");
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
// draft-ietf-hybi-thewebsocketprotocol- 00.
public static readonly WebSocketVersion V00 = new WebSocketVersion("0");
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07
// draft-ietf-hybi-thewebsocketprotocol- 07
public static readonly WebSocketVersion V07 = new WebSocketVersion("7");
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
// draft-ietf-hybi-thewebsocketprotocol- 10
public static readonly WebSocketVersion V08 = new WebSocketVersion("8");
// http://tools.ietf.org/html/rfc6455 This was originally
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
//draft-ietf-hybi-thewebsocketprotocol- 17>
public static readonly WebSocketVersion V13 = new WebSocketVersion("13");
readonly AsciiString value;
WebSocketVersion(string value)
{
this.value = AsciiString.Cached(value);
}
public override string ToString() => this.value.ToString();
public AsciiString ToHttpHeaderValue()
{
ThrowIfUnknown(this);
return this.value;
}
[MethodImpl(MethodImplOptions.NoInlining)]
static void ThrowIfUnknown(WebSocketVersion webSocketVersion)
{
if (webSocketVersion == Unknown)
{
throw new InvalidOperationException("Unknown web socket version");
}
}
}
}

Просмотреть файл

@ -363,6 +363,14 @@ namespace DotNetty.Codecs
}
}
protected virtual void DecodeLast(IChannelHandlerContext context, IByteBuffer input, List<object> output) => this.Decode(context, input, output);
protected virtual void DecodeLast(IChannelHandlerContext context, IByteBuffer input, List<object> output)
{
if (input.IsReadable())
{
// Only call decode() if there is something left in the buffer to decode.
// See https://github.com/netty/netty/issues/4386
this.Decode(context, input, output);
}
}
}
}

Просмотреть файл

@ -72,6 +72,7 @@ namespace DotNetty.Codecs.Compression
this.wrapperOverhead = ZlibUtil.WrapperOverhead(wrapper);
}
public JZlibEncoder(byte[] dictionary) : this(6, dictionary)
{
}

Просмотреть файл

@ -1,10 +1,13 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// ReSharper disable ConvertToAutoProperty
namespace DotNetty.Codecs.Compression
{
public static class ZlibCodecFactory
{
public static bool IsSupportingWindowSizeAndMemLevel => true;
public static ZlibEncoder NewZlibEncoder(int compressionLevel) => new JZlibEncoder(compressionLevel);
public static ZlibEncoder NewZlibEncoder(ZlibWrapper wrapper) => new JZlibEncoder(wrapper);

Просмотреть файл

@ -46,7 +46,11 @@ namespace DotNetty.Codecs.Compression
int overhead;
switch (wrapper)
{
case ZlibWrapper.None:
overhead = 0;
break;
case ZlibWrapper.Zlib:
case ZlibWrapper.ZlibOrNone:
overhead = 2;
break;
case ZlibWrapper.Gzip:

Просмотреть файл

@ -757,6 +757,37 @@ namespace DotNetty.Common.Utilities
return new AsciiString(this.value, start, end - start + 1, false);
}
public unsafe bool ContentEquals(string a)
{
if (a == null)
{
return false;
}
if (this.stringValue != null)
{
return this.stringValue.Equals(a);
}
if (this.length != a.Length)
{
return false;
}
if (this.length > 0)
{
fixed (char* p = a)
fixed (byte* b = &this.value[this.offset])
for (int i = 0; i < this.length; ++i)
{
if (CharToByte(*(p + i)) != *(b + i) )
{
return false;
}
}
}
return true;
}
public bool ContentEquals(ICharSequence a)
{
if (a == null || a.Count != this.length)

Просмотреть файл

@ -0,0 +1,251 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Common.Utilities
{
using System;
using System.Runtime.CompilerServices;
using System.Text;
public static class NetUtil
{
public static string ToSocketAddressString(string host, int port)
{
string portStr = Convert.ToString(port);
return NewSocketAddressStringBuilder(host, portStr,
!IsValidIpV6Address(host)).Append(':').Append(portStr).ToString();
}
static StringBuilder NewSocketAddressStringBuilder(string host, string port, bool ipv4)
{
int hostLen = host.Length;
if (ipv4)
{
// Need to include enough space for hostString:port.
return new StringBuilder(hostLen + 1 + port.Length).Append(host);
}
// Need to include enough space for [hostString]:port.
var stringBuilder = new StringBuilder(hostLen + 3 + port.Length);
if (hostLen > 1 && host[0] == '[' && host[hostLen - 1] == ']')
{
return stringBuilder.Append(host);
}
return stringBuilder.Append('[').Append(host).Append(']');
}
public static bool IsValidIpV6Address(string ip)
{
int end = ip.Length;
if (end < 2)
{
return false;
}
// strip "[]"
int start;
char c = ip[0];
if (c == '[')
{
end--;
if (ip[end] != ']')
{
// must have a close ]
return false;
}
start = 1;
c = ip[1];
}
else
{
start = 0;
}
int colons;
int compressBegin;
if (c == ':')
{
// an IPv6 address can start with "::" or with a number
if (ip[start + 1] != ':')
{
return false;
}
colons = 2;
compressBegin = start;
start += 2;
}
else
{
colons = 0;
compressBegin = -1;
}
int wordLen = 0;
for (int i = start; i < end; i++)
{
c = ip[i];
if (IsValidHexChar(c))
{
if (wordLen < 4)
{
wordLen++;
continue;
}
return false;
}
switch (c)
{
case ':':
if (colons > 7)
{
return false;
}
if (ip[i - 1] == ':')
{
if (compressBegin >= 0)
{
return false;
}
compressBegin = i - 1;
}
else
{
wordLen = 0;
}
colons++;
break;
case '.':
// case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d
// check a normal case (6 single colons)
if (compressBegin < 0 && colons != 6 ||
// a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an
// IPv4 ending, otherwise 7 :'s is bad
(colons == 7 && compressBegin >= start || colons > 7))
{
return false;
}
// Verify this address is of the correct structure to contain an IPv4 address.
// It must be IPv4-Mapped or IPv4-Compatible
// (see https://tools.ietf.org/html/rfc4291#section-2.5.5).
int ipv4Start = i - wordLen;
int j = ipv4Start - 2; // index of character before the previous ':'.
if (IsValidIPv4MappedChar(ip[j]))
{
if (!IsValidIPv4MappedChar(ip[j - 1]) ||
!IsValidIPv4MappedChar(ip[j - 2]) ||
!IsValidIPv4MappedChar(ip[j - 3]))
{
return false;
}
j -= 5;
}
for (; j >= start; --j)
{
char tmpChar = ip[j];
if (tmpChar != '0' && tmpChar != ':')
{
return false;
}
}
// 7 - is minimum IPv4 address length
int ipv4End = ip.IndexOf('%', ipv4Start + 7);
if (ipv4End < 0)
{
ipv4End = end;
}
return IsValidIpV4Address(ip, ipv4Start, ipv4End);
case '%':
// strip the interface name/index after the percent sign
end = i;
goto loop;
default:
return false;
}
loop:
// normal case without compression
if (compressBegin < 0)
{
return colons == 7 && wordLen > 0;
}
return compressBegin + 2 == end ||
// 8 colons is valid only if compression in start or end
wordLen > 0 && (colons < 8 || compressBegin <= start);
}
// normal case without compression
if (compressBegin < 0)
{
return colons == 7 && wordLen > 0;
}
return compressBegin + 2 == end ||
// 8 colons is valid only if compression in start or end
wordLen > 0 && (colons < 8 || compressBegin <= start);
}
static bool IsValidIpV4Address(string ip, int from, int toExcluded)
{
int len = toExcluded - from;
int i;
return len <= 15 && len >= 7 &&
(i = ip.IndexOf('.', from + 1)) > 0 && IsValidIpV4Word(ip, from, i) &&
(i = ip.IndexOf('.', from = i + 2)) > 0 && IsValidIpV4Word(ip, from - 1, i) &&
(i = ip.IndexOf('.', from = i + 2)) > 0 && IsValidIpV4Word(ip, from - 1, i) &&
IsValidIpV4Word(ip, i + 1, toExcluded);
}
static bool IsValidIpV4Word(string word, int from, int toExclusive)
{
int len = toExclusive - from;
char c0, c1, c2;
if (len < 1 || len > 3 || (c0 = word[from]) < '0')
{
return false;
}
if (len == 3)
{
return (c1 = word[from + 1]) >= '0'
&& (c2 = word[from + 2]) >= '0'
&& (c0 <= '1' && c1 <= '9' && c2 <= '9'
|| c0 == '2' && c1 <= '5' && (c2 <= '5' || c1 < '5' && c2 <= '9'));
}
return c0 <= '9' && (len == 1 || IsValidNumericChar(word[from + 1]));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool IsValidHexChar(char c)
{
return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool IsValidNumericChar(char c)
{
return c >= '0' && c <= '9';
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static bool IsValidIPv4MappedChar(char c)
{
return c == 'f' || c == 'F';
}
}
}

Просмотреть файл

@ -66,7 +66,6 @@ namespace DotNetty.Transport.Channels
}
}
void CheckAdded()
{
if (!this.handlerAdded)

Просмотреть файл

@ -11,6 +11,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<PackageReference Include="Moq" Version="4.7.99" />
</ItemGroup>
<ItemGroup>
<None Remove="Multipart\file-01.txt" />

Просмотреть файл

@ -0,0 +1,132 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests
{
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using DotNetty.Buffers;
using DotNetty.Common.Concurrency;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Embedded;
using Xunit;
public class HttpServerUpgradeHandlerTest
{
sealed class TestUpgradeCodec : HttpServerUpgradeHandler.IUpgradeCodec
{
public ICollection<AsciiString> RequiredUpgradeHeaders => new List<AsciiString>();
public bool PrepareUpgradeResponse(IChannelHandlerContext ctx, IFullHttpRequest upgradeRequest, HttpHeaders upgradeHeaders) => true;
public void UpgradeTo(IChannelHandlerContext ctx, IFullHttpRequest upgradeRequest)
{
// Ensure that the HttpServerUpgradeHandler is still installed when this is called
Assert.Equal(ctx.Channel.Pipeline.Context<HttpServerUpgradeHandler>(), ctx);
Assert.NotNull(ctx.Channel.Pipeline.Get<HttpServerUpgradeHandler>());
// Add a marker handler to signal that the upgrade has happened
ctx.Channel.Pipeline.AddAfter(ctx.Name, "marker", new ChannelHandlerAdapter());
}
}
sealed class UpgradeFactory : HttpServerUpgradeHandler.IUpgradeCodecFactory
{
public HttpServerUpgradeHandler.IUpgradeCodec NewUpgradeCodec(ICharSequence protocol) =>
new TestUpgradeCodec();
}
sealed class ChannelHandler : ChannelDuplexHandler
{
// marker boolean to signal that we're in the `channelRead` method
bool inReadCall;
bool writeUpgradeMessage;
bool writeFlushed;
public override void ChannelRead(IChannelHandlerContext ctx, object msg)
{
Assert.False(this.inReadCall);
Assert.False(this.writeUpgradeMessage);
this.inReadCall = true;
try
{
base.ChannelRead(ctx, msg);
// All in the same call stack, the upgrade codec should receive the message,
// written the upgrade response, and upgraded the pipeline.
Assert.True(this.writeUpgradeMessage);
Assert.False(this.writeFlushed);
//Assert.Null(ctx.Channel.Pipeline.Get<HttpServerCodec>());
//Assert.NotNull(ctx.Channel.Pipeline.Get("marker"));
}
finally
{
this.inReadCall = false;
}
}
public override Task WriteAsync(IChannelHandlerContext ctx, object msg)
{
// We ensure that we're in the read call and defer the write so we can
// make sure the pipeline was reformed irrespective of the flush completing.
Assert.True(this.inReadCall);
this.writeUpgradeMessage = true;
var completion = new TaskCompletionSource();
ctx.Channel.EventLoop.Execute(() =>
{
ctx.WriteAsync(msg)
.ContinueWith(t =>
{
if (t.Status == TaskStatus.RanToCompletion)
{
this.writeFlushed = true;
completion.TryComplete();
return;
}
completion.TrySetException(new InvalidOperationException($"Invalid WriteAsync task status {t.Status}"));
},
TaskContinuationOptions.ExecuteSynchronously);
});
return completion.Task;
}
}
[Fact]
public void UpgradesPipelineInSameMethodInvocation()
{
var httpServerCodec = new HttpServerCodec();
var factory = new UpgradeFactory();
var testInStackFrame = new ChannelHandler();
var upgradeHandler = new HttpServerUpgradeHandler(httpServerCodec, factory);
var channel = new EmbeddedChannel(testInStackFrame, httpServerCodec, upgradeHandler);
const string UpgradeString = "GET / HTTP/1.1\r\n" +
"Host: example.com\r\n" +
"Connection: Upgrade, HTTP2-Settings\r\n" +
"Upgrade: nextprotocol\r\n" +
"HTTP2-Settings: AAMAAABkAAQAAP__\r\n\r\n";
IByteBuffer upgrade = Unpooled.CopiedBuffer(Encoding.ASCII.GetBytes(UpgradeString));
Assert.False(channel.WriteInbound(upgrade));
//Assert.Null(channel.Pipeline.Get<HttpServerCodec>());
//Assert.NotNull(channel.Pipeline.Get("marker"));
channel.Flush();
Assert.Null(channel.Pipeline.Get<HttpServerCodec>());
Assert.NotNull(channel.Pipeline.Get("marker"));
var upgradeMessage = channel.ReadOutbound<IByteBuffer>();
const string ExpectedHttpResponse = "HTTP/1.1 101 Switching Protocols\r\n" +
"connection: upgrade\r\n" +
"upgrade: nextprotocol\r\n\r\n";
Assert.Equal(ExpectedHttpResponse, upgradeMessage.ToString(Encoding.ASCII));
Assert.True(upgradeMessage.Release());
Assert.False(channel.FinishAndReleaseAll());
}
}
}

Просмотреть файл

@ -0,0 +1,66 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using Xunit;
using static Http.WebSockets.Extensions.Compression.DeflateFrameServerExtensionHandshaker;
public sealed class DeflateFrameClientExtensionHandshakerTest
{
[Fact]
public void WebkitDeflateFrameData()
{
var handshaker = new DeflateFrameClientExtensionHandshaker(true);
WebSocketExtensionData data = handshaker.NewRequestData();
Assert.Equal(XWebkitDeflateFrameExtension, data.Name);
Assert.Empty(data.Parameters);
}
[Fact]
public void DeflateFrameData()
{
var handshaker = new DeflateFrameClientExtensionHandshaker(false);
WebSocketExtensionData data = handshaker.NewRequestData();
Assert.Equal(DeflateFrameExtension, data.Name);
Assert.Empty(data.Parameters);
}
[Fact]
public void NormalHandshake()
{
var handshaker = new DeflateFrameClientExtensionHandshaker(false);
IWebSocketClientExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(DeflateFrameExtension, new Dictionary<string, string>()));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerFrameDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerFrameDeflateEncoder>(extension.NewExtensionEncoder());
}
[Fact]
public void FailedHandshake()
{
var handshaker = new DeflateFrameClientExtensionHandshaker(false);
var parameters = new Dictionary<string, string>
{
{ "invalid", "12" }
};
IWebSocketClientExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(DeflateFrameExtension, parameters));
Assert.Null(extension);
}
}
}

Просмотреть файл

@ -0,0 +1,55 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using Xunit;
using static Http.WebSockets.Extensions.Compression.DeflateFrameServerExtensionHandshaker;
public sealed class DeflateFrameServerExtensionHandshakerTest
{
[Fact]
public void NormalHandshake()
{
var handshaker = new DeflateFrameServerExtensionHandshaker();
IWebSocketServerExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(DeflateFrameExtension, new Dictionary<string, string>()));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerFrameDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerFrameDeflateEncoder>(extension.NewExtensionEncoder());
}
[Fact]
public void WebkitHandshake()
{
var handshaker = new DeflateFrameServerExtensionHandshaker();
IWebSocketServerExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(XWebkitDeflateFrameExtension, new Dictionary<string, string>()));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerFrameDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerFrameDeflateEncoder>(extension.NewExtensionEncoder());
}
[Fact]
public void FailedHandshake()
{
var handshaker = new DeflateFrameServerExtensionHandshaker();
var parameters = new Dictionary<string, string>
{
{ "unknown", "11" }
};
IWebSocketServerExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(DeflateFrameExtension, parameters));
Assert.Null(extension);
}
}
}

Просмотреть файл

@ -0,0 +1,106 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System;
using DotNetty.Buffers;
using DotNetty.Codecs.Compression;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using DotNetty.Transport.Channels.Embedded;
using Xunit;
public sealed class PerFrameDeflateDecoderTest
{
readonly Random random;
public PerFrameDeflateDecoderTest()
{
this.random = new Random();
}
[Fact]
public void CompressedFrame()
{
var encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibEncoder(ZlibWrapper.None, 9, 15, 8));
var decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
var payload = new byte[300];
this.random.NextBytes(payload);
encoderChannel.WriteOutbound(Unpooled.WrappedBuffer(payload));
var compressedPayload = encoderChannel.ReadOutbound<IByteBuffer>();
var compressedFrame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3,
compressedPayload.Slice(0, compressedPayload.ReadableBytes - 4));
decoderChannel.WriteInbound(compressedFrame);
var uncompressedFrame = decoderChannel.ReadInbound<BinaryWebSocketFrame>();
Assert.NotNull(uncompressedFrame);
Assert.NotNull(uncompressedFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(uncompressedFrame);
Assert.Equal(WebSocketRsv.Rsv3, uncompressedFrame.Rsv);
Assert.Equal(300, uncompressedFrame.Content.ReadableBytes);
var finalPayload = new byte[300];
uncompressedFrame.Content.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
uncompressedFrame.Release();
}
[Fact]
public void NormalFrame()
{
var decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
var payload = new byte[300];
this.random.NextBytes(payload);
var frame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload));
decoderChannel.WriteInbound(frame);
var newFrame = decoderChannel.ReadInbound<BinaryWebSocketFrame>();
Assert.NotNull(newFrame);
Assert.NotNull(newFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(newFrame);
Assert.Equal(WebSocketRsv.Rsv3, newFrame.Rsv);
Assert.Equal(300, newFrame.Content.ReadableBytes);
var finalPayload = new byte[300];
newFrame.Content.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
newFrame.Release();
}
// See https://github.com/netty/netty/issues/4348
[Fact]
public void CompressedEmptyFrame()
{
var encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibEncoder(ZlibWrapper.None, 9, 15, 8));
var decoderChannel = new EmbeddedChannel(new PerFrameDeflateDecoder(false));
encoderChannel.WriteOutbound(Unpooled.Empty);
var compressedPayload = encoderChannel.ReadOutbound<IByteBuffer>();
var compressedFrame =
new BinaryWebSocketFrame(true, WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3, compressedPayload);
decoderChannel.WriteInbound(compressedFrame);
var uncompressedFrame = decoderChannel.ReadInbound<BinaryWebSocketFrame>();
Assert.NotNull(uncompressedFrame);
Assert.NotNull(uncompressedFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(uncompressedFrame);
Assert.Equal(WebSocketRsv.Rsv3, uncompressedFrame.Rsv);
Assert.Equal(0, uncompressedFrame.Content.ReadableBytes);
uncompressedFrame.Release();
}
}
}

Просмотреть файл

@ -0,0 +1,144 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System;
using DotNetty.Buffers;
using DotNetty.Codecs.Compression;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using DotNetty.Transport.Channels.Embedded;
using Xunit;
public sealed class PerFrameDeflateEncoderTest
{
readonly Random random;
public PerFrameDeflateEncoderTest()
{
this.random = new Random();
}
[Fact]
public void CompressedFrame()
{
var encoderChannel = new EmbeddedChannel(new PerFrameDeflateEncoder(9, 15, false));
var decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibDecoder(ZlibWrapper.None));
var payload = new byte[300];
this.random.NextBytes(payload);
var frame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload));
encoderChannel.WriteOutbound(frame);
var compressedFrame = encoderChannel.ReadOutbound<BinaryWebSocketFrame>();
Assert.NotNull(compressedFrame);
Assert.NotNull(compressedFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(compressedFrame);
Assert.Equal(WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3, compressedFrame.Rsv);
decoderChannel.WriteInbound(compressedFrame.Content);
decoderChannel.WriteInbound(DeflateDecoder.FrameTail);
var uncompressedPayload = decoderChannel.ReadInbound<IByteBuffer>();
Assert.Equal(300, uncompressedPayload.ReadableBytes);
var finalPayload = new byte[300];
uncompressedPayload.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
uncompressedPayload.Release();
}
[Fact]
public void AlreadyCompressedFrame()
{
var encoderChannel = new EmbeddedChannel(new PerFrameDeflateEncoder(9, 15, false));
var payload = new byte[300];
this.random.NextBytes(payload);
var frame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv3 | WebSocketRsv.Rsv1, Unpooled.WrappedBuffer(payload));
encoderChannel.WriteOutbound(frame);
var newFrame = encoderChannel.ReadOutbound<BinaryWebSocketFrame>();
Assert.NotNull(newFrame);
Assert.NotNull(newFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(newFrame);
Assert.Equal(WebSocketRsv.Rsv3 | WebSocketRsv.Rsv1, newFrame.Rsv);
Assert.Equal(300, newFrame.Content.ReadableBytes);
var finalPayload = new byte[300];
newFrame.Content.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
newFrame.Release();
}
[Fact]
public void FramementedFrame()
{
var encoderChannel = new EmbeddedChannel(new PerFrameDeflateEncoder(9, 15, false));
var decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibDecoder(ZlibWrapper.None));
var payload1 = new byte[100];
this.random.NextBytes(payload1);
var payload2 = new byte[100];
this.random.NextBytes(payload2);
var payload3 = new byte[100];
this.random.NextBytes(payload3);
var frame1 = new BinaryWebSocketFrame(false,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload1));
var frame2 = new ContinuationWebSocketFrame(false,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload2));
var frame3 = new ContinuationWebSocketFrame(true,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload3));
encoderChannel.WriteOutbound(frame1);
encoderChannel.WriteOutbound(frame2);
encoderChannel.WriteOutbound(frame3);
var compressedFrame1 = encoderChannel.ReadOutbound<BinaryWebSocketFrame>();
var compressedFrame2 = encoderChannel.ReadOutbound<ContinuationWebSocketFrame>();
var compressedFrame3 = encoderChannel.ReadOutbound<ContinuationWebSocketFrame>();
Assert.NotNull(compressedFrame1);
Assert.NotNull(compressedFrame2);
Assert.NotNull(compressedFrame3);
Assert.Equal(WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3, compressedFrame1.Rsv);
Assert.Equal(WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3, compressedFrame2.Rsv);
Assert.Equal(WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3, compressedFrame3.Rsv);
Assert.False(compressedFrame1.IsFinalFragment);
Assert.False(compressedFrame2.IsFinalFragment);
Assert.True(compressedFrame3.IsFinalFragment);
decoderChannel.WriteInbound(compressedFrame1.Content);
decoderChannel.WriteInbound(Unpooled.WrappedBuffer(DeflateDecoder.FrameTail));
var uncompressedPayload1 = decoderChannel.ReadInbound<IByteBuffer>();
var finalPayload1 = new byte[100];
uncompressedPayload1.ReadBytes(finalPayload1);
Assert.Equal(payload1, finalPayload1);
uncompressedPayload1.Release();
decoderChannel.WriteInbound(compressedFrame2.Content);
decoderChannel.WriteInbound(Unpooled.WrappedBuffer(DeflateDecoder.FrameTail));
var uncompressedPayload2 = decoderChannel.ReadInbound<IByteBuffer>();
var finalPayload2 = new byte[100];
uncompressedPayload2.ReadBytes(finalPayload2);
Assert.Equal(payload2, finalPayload2);
uncompressedPayload2.Release();
decoderChannel.WriteInbound(compressedFrame3.Content);
decoderChannel.WriteInbound(Unpooled.WrappedBuffer(DeflateDecoder.FrameTail));
var uncompressedPayload3 = decoderChannel.ReadInbound<IByteBuffer>();
var finalPayload3 = new byte[100];
uncompressedPayload3.ReadBytes(finalPayload3);
Assert.Equal(payload3, finalPayload3);
uncompressedPayload3.Release();
}
}
}

Просмотреть файл

@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Codecs.Compression;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using Xunit;
using static Http.WebSockets.Extensions.Compression.PerMessageDeflateServerExtensionHandshaker;
public sealed class PerMessageDeflateClientExtensionHandshakerTest
{
[Fact]
public void NormalData()
{
var handshaker = new PerMessageDeflateClientExtensionHandshaker();
WebSocketExtensionData data = handshaker.NewRequestData();
Assert.Equal(PerMessageDeflateExtension, data.Name);
Assert.Equal(ZlibCodecFactory.IsSupportingWindowSizeAndMemLevel ? 1 : 0, data.Parameters.Count);
}
[Fact]
public void CustomData()
{
var handshaker = new PerMessageDeflateClientExtensionHandshaker(6, true, 10, true, true);
WebSocketExtensionData data = handshaker.NewRequestData();
Assert.Equal(PerMessageDeflateExtension, data.Name);
Assert.Contains(ClientMaxWindow, data.Parameters.Keys);
Assert.Contains(ServerMaxWindow, data.Parameters.Keys);
Assert.Equal("10", data.Parameters[ServerMaxWindow]);
}
[Fact]
public void NormalHandshake()
{
var handshaker = new PerMessageDeflateClientExtensionHandshaker();
IWebSocketClientExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, new Dictionary<string, string>()));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerMessageDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerMessageDeflateEncoder>(extension.NewExtensionEncoder());
}
[Fact]
public void CustomHandshake()
{
var handshaker = new PerMessageDeflateClientExtensionHandshaker(6, true, 10, true, true);
var parameters = new Dictionary<string, string>
{
{ ClientMaxWindow, "12" },
{ ServerMaxWindow, "10" },
{ ClientNoContext, null },
{ ServerNoContext, null }
};
IWebSocketClientExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerMessageDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerMessageDeflateEncoder>(extension.NewExtensionEncoder());
parameters = new Dictionary<string, string>
{
{ ServerMaxWindow, "10" },
{ ServerNoContext, null }
};
extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerMessageDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerMessageDeflateEncoder>(extension.NewExtensionEncoder());
parameters = new Dictionary<string, string>();
extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.Null(extension);
}
}
}

Просмотреть файл

@ -0,0 +1,171 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System;
using DotNetty.Buffers;
using DotNetty.Codecs.Compression;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using DotNetty.Transport.Channels.Embedded;
using Xunit;
public sealed class PerMessageDeflateDecoderTest
{
readonly Random random;
public PerMessageDeflateDecoderTest()
{
this.random = new Random();
}
[Fact]
public void CompressedFrame()
{
var encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibEncoder(ZlibWrapper.None, 9, 15, 8));
var decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
var payload = new byte[300];
this.random.NextBytes(payload);
encoderChannel.WriteOutbound(Unpooled.WrappedBuffer(payload));
var compressedPayload = encoderChannel.ReadOutbound<IByteBuffer>();
var compressedFrame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3,
compressedPayload.Slice(0, compressedPayload.ReadableBytes - 4));
decoderChannel.WriteInbound(compressedFrame);
var uncompressedFrame = decoderChannel.ReadInbound<BinaryWebSocketFrame>();
Assert.NotNull(uncompressedFrame);
Assert.NotNull(uncompressedFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(uncompressedFrame);
Assert.Equal(WebSocketRsv.Rsv3, uncompressedFrame.Rsv);
Assert.Equal(300, uncompressedFrame.Content.ReadableBytes);
var finalPayload = new byte[300];
uncompressedFrame.Content.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
uncompressedFrame.Release();
}
[Fact]
public void NormalFrame()
{
var decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
var payload = new byte[300];
this.random.NextBytes(payload);
var frame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload));
decoderChannel.WriteInbound(frame);
var newFrame = decoderChannel.ReadInbound<BinaryWebSocketFrame>();
Assert.NotNull(newFrame);
Assert.NotNull(newFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(newFrame);
Assert.Equal(WebSocketRsv.Rsv3, newFrame.Rsv);
Assert.Equal(300, newFrame.Content.ReadableBytes);
var finalPayload = new byte[300];
newFrame.Content.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
newFrame.Release();
}
[Fact]
public void FramementedFrame()
{
var encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibEncoder(ZlibWrapper.None, 9, 15, 8));
var decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
var payload = new byte[300];
this.random.NextBytes(payload);
encoderChannel.WriteOutbound(Unpooled.WrappedBuffer(payload));
var compressedPayload = encoderChannel.ReadOutbound<IByteBuffer>();
compressedPayload = compressedPayload.Slice(0, compressedPayload.ReadableBytes - 4);
int oneThird = compressedPayload.ReadableBytes / 3;
var compressedFrame1 = new BinaryWebSocketFrame(false,
WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3,
compressedPayload.Slice(0, oneThird));
var compressedFrame2 = new ContinuationWebSocketFrame(false,
WebSocketRsv.Rsv3, compressedPayload.Slice(oneThird, oneThird));
var compressedFrame3 = new ContinuationWebSocketFrame(true,
WebSocketRsv.Rsv3, compressedPayload.Slice(oneThird * 2,
compressedPayload.ReadableBytes - oneThird * 2));
decoderChannel.WriteInbound(compressedFrame1.Retain());
decoderChannel.WriteInbound(compressedFrame2.Retain());
decoderChannel.WriteInbound(compressedFrame3);
var uncompressedFrame1 = decoderChannel.ReadInbound<BinaryWebSocketFrame>();
var uncompressedFrame2 = decoderChannel.ReadInbound<ContinuationWebSocketFrame>();
var uncompressedFrame3 = decoderChannel.ReadInbound<ContinuationWebSocketFrame>();
Assert.NotNull(uncompressedFrame1);
Assert.NotNull(uncompressedFrame2);
Assert.NotNull(uncompressedFrame3);
Assert.Equal(WebSocketRsv.Rsv3, uncompressedFrame1.Rsv);
Assert.Equal(WebSocketRsv.Rsv3, uncompressedFrame2.Rsv);
Assert.Equal(WebSocketRsv.Rsv3, uncompressedFrame3.Rsv);
IByteBuffer finalPayloadWrapped = Unpooled.WrappedBuffer(uncompressedFrame1.Content,
uncompressedFrame2.Content, uncompressedFrame3.Content);
Assert.Equal(300, finalPayloadWrapped.ReadableBytes);
var finalPayload = new byte[300];
finalPayloadWrapped.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
finalPayloadWrapped.Release();
}
[Fact]
public void MultiCompressedPayloadWithinFrame()
{
var encoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibEncoder(ZlibWrapper.None, 9, 15, 8));
var decoderChannel = new EmbeddedChannel(new PerMessageDeflateDecoder(false));
var payload1 = new byte[100];
this.random.NextBytes(payload1);
var payload2 = new byte[100];
this.random.NextBytes(payload2);
encoderChannel.WriteOutbound(Unpooled.WrappedBuffer(payload1));
var compressedPayload1 = encoderChannel.ReadOutbound<IByteBuffer>();
encoderChannel.WriteOutbound(Unpooled.WrappedBuffer(payload2));
var compressedPayload2 = encoderChannel.ReadOutbound<IByteBuffer>();
var compressedFrame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3,
Unpooled.WrappedBuffer(
compressedPayload1,
compressedPayload2.Slice(0, compressedPayload2.ReadableBytes - 4)));
decoderChannel.WriteInbound(compressedFrame);
var uncompressedFrame = decoderChannel.ReadInbound<BinaryWebSocketFrame>();
Assert.NotNull(uncompressedFrame);
Assert.NotNull(uncompressedFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(uncompressedFrame);
Assert.Equal(WebSocketRsv.Rsv3, uncompressedFrame.Rsv);
Assert.Equal(200, uncompressedFrame.Content.ReadableBytes);
var finalPayload1 = new byte[100];
uncompressedFrame.Content.ReadBytes(finalPayload1);
Assert.Equal(payload1, finalPayload1);
var finalPayload2 = new byte[100];
uncompressedFrame.Content.ReadBytes(finalPayload2);
Assert.Equal(payload2, finalPayload2);
uncompressedFrame.Release();
}
}
}

Просмотреть файл

@ -0,0 +1,142 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System;
using DotNetty.Buffers;
using DotNetty.Codecs.Compression;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using DotNetty.Transport.Channels.Embedded;
using Xunit;
public sealed class PerMessageDeflateEncoderTest
{
readonly Random random;
public PerMessageDeflateEncoderTest()
{
this.random = new Random();
}
[Fact]
public void CompressedFrame()
{
var encoderChannel = new EmbeddedChannel(new PerMessageDeflateEncoder(9, 15, false));
var decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibDecoder(ZlibWrapper.None));
var payload = new byte[300];
this.random.NextBytes(payload);
var frame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload));
encoderChannel.WriteOutbound(frame);
var compressedFrame = encoderChannel.ReadOutbound<BinaryWebSocketFrame>();
Assert.NotNull(compressedFrame);
Assert.NotNull(compressedFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(compressedFrame);
Assert.Equal(WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3, compressedFrame.Rsv);
decoderChannel.WriteInbound(compressedFrame.Content);
decoderChannel.WriteInbound(DeflateDecoder.FrameTail);
var uncompressedPayload = decoderChannel.ReadInbound<IByteBuffer>();
Assert.Equal(300, uncompressedPayload.ReadableBytes);
var finalPayload = new byte[300];
uncompressedPayload.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
uncompressedPayload.Release();
}
[Fact]
public void AlreadyCompressedFrame()
{
var encoderChannel = new EmbeddedChannel(new PerMessageDeflateEncoder(9, 15, false));
var payload = new byte[300];
this.random.NextBytes(payload);
var frame = new BinaryWebSocketFrame(true,
WebSocketRsv.Rsv3 | WebSocketRsv.Rsv1, Unpooled.WrappedBuffer(payload));
encoderChannel.WriteOutbound(frame);
var newFrame = encoderChannel.ReadOutbound<BinaryWebSocketFrame>();
Assert.NotNull(newFrame);
Assert.NotNull(newFrame.Content);
Assert.IsType<BinaryWebSocketFrame>(newFrame);
Assert.Equal(WebSocketRsv.Rsv3 | WebSocketRsv.Rsv1, newFrame.Rsv);
Assert.Equal(300, newFrame.Content.ReadableBytes);
var finalPayload = new byte[300];
newFrame.Content.ReadBytes(finalPayload);
Assert.Equal(payload, finalPayload);
newFrame.Release();
}
[Fact]
public void FramementedFrame()
{
var encoderChannel = new EmbeddedChannel(new PerMessageDeflateEncoder(9, 15, false));
var decoderChannel = new EmbeddedChannel(
ZlibCodecFactory.NewZlibDecoder(ZlibWrapper.None));
var payload1 = new byte[100];
this.random.NextBytes(payload1);
var payload2 = new byte[100];
this.random.NextBytes(payload2);
var payload3 = new byte[100];
this.random.NextBytes(payload3);
var frame1 = new BinaryWebSocketFrame(false,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload1));
var frame2 = new ContinuationWebSocketFrame(false,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload2));
var frame3 = new ContinuationWebSocketFrame(true,
WebSocketRsv.Rsv3, Unpooled.WrappedBuffer(payload3));
encoderChannel.WriteOutbound(frame1);
encoderChannel.WriteOutbound(frame2);
encoderChannel.WriteOutbound(frame3);
var compressedFrame1 = encoderChannel.ReadOutbound<BinaryWebSocketFrame>();
var compressedFrame2 = encoderChannel.ReadOutbound<ContinuationWebSocketFrame>();
var compressedFrame3 = encoderChannel.ReadOutbound<ContinuationWebSocketFrame>();
Assert.NotNull(compressedFrame1);
Assert.NotNull(compressedFrame2);
Assert.NotNull(compressedFrame3);
Assert.Equal(WebSocketRsv.Rsv1 | WebSocketRsv.Rsv3, compressedFrame1.Rsv);
Assert.Equal(WebSocketRsv.Rsv3, compressedFrame2.Rsv);
Assert.Equal(WebSocketRsv.Rsv3, compressedFrame3.Rsv);
Assert.False(compressedFrame1.IsFinalFragment);
Assert.False(compressedFrame2.IsFinalFragment);
Assert.True(compressedFrame3.IsFinalFragment);
decoderChannel.WriteInbound(compressedFrame1.Content);
var uncompressedPayload1 = decoderChannel.ReadInbound<IByteBuffer>();
var finalPayload1 = new byte[100];
uncompressedPayload1.ReadBytes(finalPayload1);
Assert.Equal(payload1, finalPayload1);
uncompressedPayload1.Release();
decoderChannel.WriteInbound(compressedFrame2.Content);
var uncompressedPayload2 = decoderChannel.ReadInbound<IByteBuffer>();
var finalPayload2 = new byte[100];
uncompressedPayload2.ReadBytes(finalPayload2);
Assert.Equal(payload2, finalPayload2);
uncompressedPayload2.Release();
decoderChannel.WriteInbound(compressedFrame3.Content);
decoderChannel.WriteInbound(DeflateDecoder.FrameTail);
var uncompressedPayload3 = decoderChannel.ReadInbound<IByteBuffer>();
var finalPayload3 = new byte[100];
uncompressedPayload3.ReadBytes(finalPayload3);
Assert.Equal(payload3, finalPayload3);
uncompressedPayload3.Release();
}
}
}

Просмотреть файл

@ -0,0 +1,122 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using Xunit;
using static Http.WebSockets.Extensions.Compression.PerMessageDeflateServerExtensionHandshaker;
public sealed class PerMessageDeflateServerExtensionHandshakerTest
{
[Fact]
public void NormalHandshake()
{
var handshaker = new PerMessageDeflateServerExtensionHandshaker();
IWebSocketServerExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, new Dictionary<string, string>()));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerMessageDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerMessageDeflateEncoder>(extension.NewExtensionEncoder());
WebSocketExtensionData data = extension.NewReponseData();
Assert.Equal(PerMessageDeflateExtension, data.Name);
Assert.Empty(data.Parameters);
var parameters = new Dictionary<string, string>
{
{ ClientMaxWindow, null },
{ ClientNoContext, null }
};
extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerMessageDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerMessageDeflateEncoder>(extension.NewExtensionEncoder());
data = extension.NewReponseData();
Assert.Equal(PerMessageDeflateExtension, data.Name);
Assert.Empty(data.Parameters);
parameters = new Dictionary<string, string>
{
{ ServerMaxWindow, "12" },
{ ServerNoContext, null }
};
extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.Null(extension);
}
[Fact]
public void CustomHandshake()
{
var handshaker = new PerMessageDeflateServerExtensionHandshaker(6, true, 10, true, true);
var parameters = new Dictionary<string, string>
{
{ ClientMaxWindow, null },
{ ServerMaxWindow, "12" },
{ ClientNoContext, null },
{ ServerNoContext, null }
};
IWebSocketServerExtension extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerMessageDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerMessageDeflateEncoder>(extension.NewExtensionEncoder());
WebSocketExtensionData data = extension.NewReponseData();
Assert.Equal(PerMessageDeflateExtension, data.Name);
Assert.Contains(ClientMaxWindow, data.Parameters.Keys);
Assert.Equal("10", data.Parameters[ClientMaxWindow]);
Assert.Contains(ServerMaxWindow, data.Parameters.Keys);
Assert.Equal("12", data.Parameters[ServerMaxWindow]);
parameters = new Dictionary<string, string>
{
{ ServerMaxWindow, "12" },
{ ServerNoContext, null }
};
extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.NotNull(extension);
Assert.Equal(WebSocketRsv.Rsv1, extension.Rsv);
Assert.IsType<PerMessageDeflateDecoder>(extension.NewExtensionDecoder());
Assert.IsType<PerMessageDeflateEncoder>(extension.NewExtensionEncoder());
data = extension.NewReponseData();
Assert.Equal(PerMessageDeflateExtension, data.Name);
Assert.Equal(2, data.Parameters.Count);
Assert.Contains(ServerMaxWindow, data.Parameters.Keys);
Assert.Equal("12", data.Parameters[ServerMaxWindow]);
Assert.Contains(ServerNoContext, data.Parameters.Keys);
parameters = new Dictionary<string, string>();
extension = handshaker.HandshakeExtension(
new WebSocketExtensionData(PerMessageDeflateExtension, parameters));
Assert.NotNull(extension);
data = extension.NewReponseData();
Assert.Equal(PerMessageDeflateExtension, data.Name);
Assert.Empty(data.Parameters);
}
}
}

Просмотреть файл

@ -0,0 +1,195 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions.Compression
{
using System.Collections.Generic;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels.Embedded;
using Xunit;
using static Http.WebSockets.Extensions.Compression.PerMessageDeflateServerExtensionHandshaker;
using static WebSocketExtensionTestUtil;
public sealed class WebSocketServerCompressionHandlerTest
{
[Fact]
public void NormalSuccess()
{
var ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
IHttpRequest req = NewUpgradeRequest(PerMessageDeflateExtension);
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(PerMessageDeflateExtension, exts[0].Name);
Assert.Empty(exts[0].Parameters);
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
[Fact]
public void ClientWindowSizeSuccess()
{
var ch = new EmbeddedChannel(
new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 10, false, false)));
IHttpRequest req = NewUpgradeRequest(PerMessageDeflateExtension + "; " + ClientMaxWindow);
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(PerMessageDeflateExtension, exts[0].Name);
Assert.Equal("10", exts[0].Parameters[ClientMaxWindow]);
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
[Fact]
public void ClientWindowSizeUnavailable()
{
var ch = new EmbeddedChannel(
new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 10, false, false)));
IHttpRequest req = NewUpgradeRequest(PerMessageDeflateExtension);
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(PerMessageDeflateExtension, exts[0].Name);
Assert.Empty(exts[0].Parameters);
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
[Fact]
public void ServerWindowSizeSuccess()
{
var ch = new EmbeddedChannel(
new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, true, 15, false, false)));
IHttpRequest req = NewUpgradeRequest(PerMessageDeflateExtension + "; " + ServerMaxWindow + "=10");
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(PerMessageDeflateExtension, exts[0].Name);
Assert.Equal("10", exts[0].Parameters[ServerMaxWindow]);
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
[Fact]
public void ServerWindowSizeDisable()
{
var ch = new EmbeddedChannel(
new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 15, false, false)));
IHttpRequest req = NewUpgradeRequest(PerMessageDeflateExtension + "; " + ServerMaxWindow + "=10");
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.False(res2.Headers.Contains(HttpHeaderNames.SecWebsocketExtensions));
Assert.Null(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.Null(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
[Fact]
public void ServerNoContext()
{
var ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
IHttpRequest req = NewUpgradeRequest(
PerMessageDeflateExtension + "; "
+ PerMessageDeflateServerExtensionHandshaker.ServerNoContext);
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.False(res2.Headers.Contains(HttpHeaderNames.SecWebsocketExtensions));
Assert.Null(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.Null(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
[Fact]
public void ClientNoContext()
{
var ch = new EmbeddedChannel(new WebSocketServerCompressionHandler());
IHttpRequest req = NewUpgradeRequest(
PerMessageDeflateExtension + "; "
+ PerMessageDeflateServerExtensionHandshaker.ClientNoContext);
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(PerMessageDeflateExtension, exts[0].Name);
Assert.Empty(exts[0].Parameters);
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
[Fact]
public void ServerWindowSizeDisableThenFallback()
{
var ch = new EmbeddedChannel(new WebSocketServerExtensionHandler(
new PerMessageDeflateServerExtensionHandshaker(6, false, 15, false, false)));
IHttpRequest req = NewUpgradeRequest(
PerMessageDeflateExtension + "; " + ServerMaxWindow + "=10, " +
PerMessageDeflateExtension);
ch.WriteInbound(req);
IHttpResponse res = NewUpgradeResponse(null);
ch.WriteOutbound(res);
var res2 = ch.ReadOutbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> exts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(PerMessageDeflateExtension, exts[0].Name);
Assert.Empty(exts[0].Parameters);
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateDecoder>());
Assert.NotNull(ch.Pipeline.Get<PerMessageDeflateEncoder>());
}
}
}

Просмотреть файл

@ -0,0 +1,239 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions
{
using System.Collections.Generic;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Common.Utilities;
using DotNetty.Transport.Channels.Embedded;
using Moq;
using Xunit;
using static WebSocketExtensionTestUtil;
public sealed class WebSocketClientExtensionHandlerTest
{
readonly Mock<IWebSocketClientExtensionHandshaker> mainHandshaker;
readonly Mock<IWebSocketClientExtensionHandshaker> fallbackHandshaker;
readonly Mock<IWebSocketClientExtension> mainExtension;
readonly Mock<IWebSocketClientExtension> fallbackExtension;
public WebSocketClientExtensionHandlerTest()
{
this.mainHandshaker = new Mock<IWebSocketClientExtensionHandshaker>(MockBehavior.Strict);
this.fallbackHandshaker = new Mock<IWebSocketClientExtensionHandshaker>(MockBehavior.Strict);
this.mainExtension = new Mock<IWebSocketClientExtension>(MockBehavior.Strict);
this.fallbackExtension = new Mock<IWebSocketClientExtension>(MockBehavior.Strict);
}
[Fact]
public void MainSuccess()
{
this.mainHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("main", new Dictionary<string, string>()));
this.mainHandshaker.Setup(x => x.HandshakeExtension(It.IsAny<WebSocketExtensionData>()))
.Returns(this.mainExtension.Object);
this.fallbackHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("fallback", new Dictionary<string, string>()));
this.mainExtension.Setup(x => x.Rsv).Returns(WebSocketRsv.Rsv1);
this.mainExtension.Setup(x => x.NewExtensionEncoder()).Returns(new DummyEncoder());
this.mainExtension.Setup(x => x.NewExtensionDecoder()).Returns(new DummyDecoder());
var ch = new EmbeddedChannel(
new WebSocketClientExtensionHandler(
this.mainHandshaker.Object,
this.fallbackHandshaker.Object));
IHttpRequest req = NewUpgradeRequest(null);
ch.WriteOutbound(req);
var req2 = ch.ReadOutbound<IHttpRequest>();
Assert.True(req2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
IHttpResponse res = NewUpgradeResponse("main");
ch.WriteInbound(res);
var res2 = ch.ReadInbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out value));
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(2, reqExts.Count);
Assert.Equal("main", reqExts[0].Name);
Assert.Equal("fallback", reqExts[1].Name);
Assert.Single(resExts);
Assert.Equal("main", resExts[0].Name);
Assert.Empty(resExts[0].Parameters);
Assert.NotNull(ch.Pipeline.Get<DummyDecoder>());
Assert.NotNull(ch.Pipeline.Get<DummyEncoder>());
this.mainExtension.Verify(x => x.Rsv, Times.AtLeastOnce);
}
[Fact]
public void FallbackSuccess()
{
this.mainHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("main", new Dictionary<string, string>()));
this.mainHandshaker.Setup(x => x.HandshakeExtension(It.IsAny<WebSocketExtensionData>()))
.Returns(default(IWebSocketClientExtension));
this.fallbackHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("fallback", new Dictionary<string, string>()));
this.fallbackHandshaker.Setup(x => x.HandshakeExtension(It.IsAny<WebSocketExtensionData>()))
.Returns(this.fallbackExtension.Object);
this.fallbackExtension.Setup(x => x.Rsv).Returns(WebSocketRsv.Rsv1);
this.fallbackExtension.Setup(x => x.NewExtensionEncoder()).Returns(new DummyEncoder());
this.fallbackExtension.Setup(x => x.NewExtensionDecoder()).Returns(new DummyDecoder());
var ch = new EmbeddedChannel(
new WebSocketClientExtensionHandler(
this.mainHandshaker.Object,
this.fallbackHandshaker.Object));
IHttpRequest req = NewUpgradeRequest(null);
ch.WriteOutbound(req);
var req2 = ch.ReadOutbound<IHttpRequest>();
Assert.True(req2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
IHttpResponse res = NewUpgradeResponse("fallback");
ch.WriteInbound(res);
var res2 = ch.ReadInbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out value));
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(2, reqExts.Count);
Assert.Equal("main", reqExts[0].Name);
Assert.Equal("fallback", reqExts[1].Name);
Assert.Single(resExts);
Assert.Equal("fallback", resExts[0].Name);
Assert.Empty(resExts[0].Parameters);
Assert.NotNull(ch.Pipeline.Get<DummyDecoder>());
Assert.NotNull(ch.Pipeline.Get<DummyEncoder>());
this.fallbackExtension.Verify(x => x.Rsv, Times.AtLeastOnce);
}
[Fact]
public void AllSuccess()
{
this.mainHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("main", new Dictionary<string, string>()));
this.mainHandshaker.Setup(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("main"))))
.Returns(this.mainExtension.Object);
this.mainHandshaker.Setup(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("fallback"))))
.Returns(default(IWebSocketClientExtension));
this.fallbackHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("fallback", new Dictionary<string, string>()));
this.fallbackHandshaker.Setup(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("main"))))
.Returns(default(IWebSocketClientExtension));
this.fallbackHandshaker.Setup(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("fallback"))))
.Returns(this.fallbackExtension.Object);
var mainEncoder = new DummyEncoder();
var mainDecoder = new DummyDecoder();
this.mainExtension.Setup(x => x.Rsv).Returns(WebSocketRsv.Rsv1);
this.mainExtension.Setup(x => x.NewExtensionEncoder()).Returns(mainEncoder);
this.mainExtension.Setup(x => x.NewExtensionDecoder()).Returns(mainDecoder);
var fallbackEncoder = new Dummy2Encoder();
var fallbackDecoder = new Dummy2Decoder();
this.fallbackExtension.Setup(x => x.Rsv).Returns(WebSocketRsv.Rsv2);
this.fallbackExtension.Setup(x => x.NewExtensionEncoder()).Returns(fallbackEncoder);
this.fallbackExtension.Setup(x => x.NewExtensionDecoder()).Returns(fallbackDecoder);
var ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
this.mainHandshaker.Object, this.fallbackHandshaker.Object));
IHttpRequest req = NewUpgradeRequest(null);
ch.WriteOutbound(req);
var req2 = ch.ReadOutbound<IHttpRequest>();
Assert.True(req2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
IHttpResponse res = NewUpgradeResponse("main, fallback");
ch.WriteInbound(res);
var res2 = ch.ReadInbound<IHttpResponse>();
Assert.True(res2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out value));
List<WebSocketExtensionData> resExts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
Assert.Equal(2, reqExts.Count);
Assert.Equal("main", reqExts[0].Name);
Assert.Equal("fallback", reqExts[1].Name);
Assert.Equal(2, resExts.Count);
Assert.Equal("main", resExts[0].Name);
Assert.Equal("fallback", resExts[1].Name);
Assert.NotNull(ch.Pipeline.Context(mainEncoder));
Assert.NotNull(ch.Pipeline.Context(mainDecoder));
Assert.NotNull(ch.Pipeline.Context(fallbackEncoder));
Assert.NotNull(ch.Pipeline.Context(fallbackDecoder));
this.mainExtension.Verify(x => x.Rsv, Times.AtLeastOnce);
this.fallbackExtension.Verify(x => x.Rsv, Times.AtLeastOnce);
}
[Fact]
public void MainAndFallbackUseRsv1WillFail()
{
this.mainHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("main", new Dictionary<string, string>()));
this.mainHandshaker.Setup(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("main"))))
.Returns(this.mainExtension.Object);
this.mainHandshaker.Setup(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("fallback"))))
.Returns(default(IWebSocketClientExtension));
this.fallbackHandshaker.Setup(x => x.NewRequestData())
.Returns(new WebSocketExtensionData("fallback", new Dictionary<string, string>()));
this.fallbackHandshaker.Setup(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("fallback"))))
.Returns(this.fallbackExtension.Object);
this.mainExtension.Setup(x => x.Rsv).Returns(WebSocketRsv.Rsv1);
this.fallbackExtension.Setup(x => x.Rsv).Returns(WebSocketRsv.Rsv1);
var ch = new EmbeddedChannel(new WebSocketClientExtensionHandler(
this.mainHandshaker.Object, this.fallbackHandshaker.Object));
IHttpRequest req = NewUpgradeRequest(null);
ch.WriteOutbound(req);
var req2 = ch.ReadOutbound<IHttpRequest>();
Assert.True(req2.Headers.TryGet(HttpHeaderNames.SecWebsocketExtensions, out ICharSequence value));
List<WebSocketExtensionData> reqExts = WebSocketExtensionUtil.ExtractExtensions(value.ToString());
IHttpResponse res = NewUpgradeResponse("main, fallback");
Assert.Throws<CodecException>(() => ch.WriteInbound(res));
Assert.Equal(2, reqExts.Count);
Assert.Equal("main", reqExts[0].Name);
Assert.Equal("fallback", reqExts[1].Name);
this.mainHandshaker.Verify(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("main"))), Times.AtLeastOnce);
this.mainHandshaker.Verify(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("fallback"))), Times.AtLeastOnce);
this.fallbackHandshaker.Verify(x => x.HandshakeExtension(
It.Is<WebSocketExtensionData>(v => v.Name.Equals("fallback"))), Times.AtLeastOnce);
this.mainExtension.Verify(x => x.Rsv, Times.AtLeastOnce);
this.fallbackExtension.Verify(x => x.Rsv, Times.AtLeastOnce);
}
}
}

Просмотреть файл

@ -0,0 +1,82 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace DotNetty.Codecs.Http.Tests.WebSockets.Extensions
{
using System.Collections.Generic;
using DotNetty.Codecs.Http.WebSockets;
using DotNetty.Codecs.Http.WebSockets.Extensions;
using DotNetty.Transport.Channels;
static class WebSocketExtensionTestUtil
{
public static IHttpRequest NewUpgradeRequest(string ext)
{
var req = new DefaultHttpRequest(
HttpVersion.Http11,
HttpMethod.Get,
"/chat");
req.Headers.Set(HttpHeaderNames.Host, "server.example.com");
req.Headers.Set(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket.ToString().ToLower());
req.Headers.Set(HttpHeaderNames.Connection, "Upgrade");
req.Headers.Set(HttpHeaderNames.Origin, "http://example.com");
if (ext != null)
{
req.Headers.Set(HttpHeaderNames.SecWebsocketExtensions, ext);
}
return req;
}
public static IHttpResponse NewUpgradeResponse(string ext)
{
var res = new DefaultHttpResponse(
HttpVersion.Http11,
HttpResponseStatus.SwitchingProtocols);
res.Headers.Set(HttpHeaderNames.Host, "server.example.com");
res.Headers.Set(HttpHeaderNames.Upgrade, HttpHeaderValues.Websocket.ToString().ToLower());
res.Headers.Set(HttpHeaderNames.Connection, "Upgrade");
res.Headers.Set(HttpHeaderNames.Origin, "http://example.com");
if (ext != null)
{
res.Headers.Set(HttpHeaderNames.SecWebsocketExtensions, ext);
}
return res;
}
internal class DummyEncoder : WebSocketExtensionEncoder
{
protected override void Encode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> ouput)
{
// unused
}
}
internal class DummyDecoder : WebSocketExtensionDecoder
{
protected override void Decode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> output)
{
// unused
}
}
internal class Dummy2Encoder : WebSocketExtensionEncoder
{
protected override void Encode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> ouput)
{
// unused
}
}
internal class Dummy2Decoder : WebSocketExtensionDecoder
{
protected override void Decode(IChannelHandlerContext ctx, WebSocketFrame msg, List<object> output)
{
// unused
}
}
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше