Skip to content

Commit 65a61c0

Browse files
StormHubSeabiscuit
authored andcommitted
WebSocket support. (Azure#400)
1 parent 69b0f9a commit 65a61c0

File tree

119 files changed

+9740
-6
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

119 files changed

+9740
-6
lines changed

DotNetty.sln

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HttpServer", "examples\Http
9898
EndProject
9999
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DotNetty.Transport.Libuv.Tests", "test\DotNetty.Transport.Libuv.Tests\DotNetty.Transport.Libuv.Tests.csproj", "{5938CE7C-61CD-4C70-82B8-B236AC881A14}"
100100
EndProject
101+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSockets.Client", "examples\WebSockets.Client\WebSockets.Client.csproj", "{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}"
102+
EndProject
103+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebSockets.Server", "examples\WebSockets.Server\WebSockets.Server.csproj", "{AA8F9D2A-1055-4018-84A3-D623172787CD}"
104+
EndProject
101105
Global
102106
GlobalSection(SharedMSBuildProjectFiles) = preSolution
103107
..\CuteAnt.Extensions.Common\src\CuteAnt.Extensions.Reflection.Sources\CuteAnt.Extensions.Reflection.Sources.projitems*{c7f8dbe6-77b7-494d-af00-018f7579548c}*SharedItemsImports = 13
@@ -706,6 +710,38 @@ Global
706710
{5938CE7C-61CD-4C70-82B8-B236AC881A14}.Release|x64.Build.0 = Release|Any CPU
707711
{5938CE7C-61CD-4C70-82B8-B236AC881A14}.Release|x86.ActiveCfg = Release|Any CPU
708712
{5938CE7C-61CD-4C70-82B8-B236AC881A14}.Release|x86.Build.0 = Release|Any CPU
713+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
714+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|Any CPU.Build.0 = Debug|Any CPU
715+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|ARM.ActiveCfg = Debug|Any CPU
716+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|ARM.Build.0 = Debug|Any CPU
717+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|x64.ActiveCfg = Debug|Any CPU
718+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|x64.Build.0 = Debug|Any CPU
719+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|x86.ActiveCfg = Debug|Any CPU
720+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Debug|x86.Build.0 = Debug|Any CPU
721+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|Any CPU.ActiveCfg = Release|Any CPU
722+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|Any CPU.Build.0 = Release|Any CPU
723+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|ARM.ActiveCfg = Release|Any CPU
724+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|ARM.Build.0 = Release|Any CPU
725+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|x64.ActiveCfg = Release|Any CPU
726+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|x64.Build.0 = Release|Any CPU
727+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|x86.ActiveCfg = Release|Any CPU
728+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649}.Release|x86.Build.0 = Release|Any CPU
729+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
730+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
731+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|ARM.ActiveCfg = Debug|Any CPU
732+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|ARM.Build.0 = Debug|Any CPU
733+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|x64.ActiveCfg = Debug|Any CPU
734+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|x64.Build.0 = Debug|Any CPU
735+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|x86.ActiveCfg = Debug|Any CPU
736+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Debug|x86.Build.0 = Debug|Any CPU
737+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
738+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|Any CPU.Build.0 = Release|Any CPU
739+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|ARM.ActiveCfg = Release|Any CPU
740+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|ARM.Build.0 = Release|Any CPU
741+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|x64.ActiveCfg = Release|Any CPU
742+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|x64.Build.0 = Release|Any CPU
743+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|x86.ActiveCfg = Release|Any CPU
744+
{AA8F9D2A-1055-4018-84A3-D623172787CD}.Release|x86.Build.0 = Release|Any CPU
709745
EndGlobalSection
710746
GlobalSection(SolutionProperties) = preSolution
711747
HideSolutionNode = FALSE
@@ -750,6 +786,8 @@ Global
750786
{26A8779A-44B1-46D4-A43E-FA2616EBFD02} = {01F3CC7E-F996-411E-AFD6-72673A826549}
751787
{BDDF75B4-4BAB-49DE-97A3-CA191C523636} = {3511E2D1-F581-420E-8DC4-8F0386947621}
752788
{5938CE7C-61CD-4C70-82B8-B236AC881A14} = {01F3CC7E-F996-411E-AFD6-72673A826549}
789+
{91F3EB7C-FB5C-4ADB-A3F2-D95A3165E649} = {3511E2D1-F581-420E-8DC4-8F0386947621}
790+
{AA8F9D2A-1055-4018-84A3-D623172787CD} = {3511E2D1-F581-420E-8DC4-8F0386947621}
753791
EndGlobalSection
754792
GlobalSection(ExtensibilityGlobals) = postSolution
755793
SolutionGuid = {A659CEFB-DDB3-49BE-AEDD-FF2F1B3297DB}

examples/Examples.Common/ClientSettings.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,14 @@ public static bool IsSsl
2121
public static int Port => int.Parse(ExampleHelper.Configuration["port"]);
2222

2323
public static int Size => int.Parse(ExampleHelper.Configuration["size"]);
24+
25+
public static bool UseLibuv
26+
{
27+
get
28+
{
29+
string libuv = ExampleHelper.Configuration["libuv"];
30+
return !string.IsNullOrEmpty(libuv) && bool.Parse(libuv);
31+
}
32+
}
2433
}
2534
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace WebSockets.Client
5+
{
6+
using System;
7+
using System.IO;
8+
using System.Net;
9+
using System.Net.Security;
10+
using System.Security.Cryptography.X509Certificates;
11+
using System.Threading.Tasks;
12+
using DotNetty.Buffers;
13+
using DotNetty.Codecs.Http;
14+
using DotNetty.Codecs.Http.WebSockets;
15+
using DotNetty.Codecs.Http.WebSockets.Extensions.Compression;
16+
using DotNetty.Handlers.Tls;
17+
using DotNetty.Transport.Bootstrapping;
18+
using DotNetty.Transport.Channels;
19+
using DotNetty.Transport.Channels.Sockets;
20+
using DotNetty.Transport.Libuv;
21+
using Examples.Common;
22+
23+
class Program
24+
{
25+
static async Task RunClientAsync()
26+
{
27+
var builder = new UriBuilder
28+
{
29+
Scheme = ClientSettings.IsSsl ? "wss" : "ws",
30+
Host = ClientSettings.Host.ToString(),
31+
Port = ClientSettings.Port
32+
};
33+
34+
string path = ExampleHelper.Configuration["path"];
35+
if (!string.IsNullOrEmpty(path))
36+
{
37+
builder.Path = path;
38+
}
39+
40+
Uri uri = builder.Uri;
41+
ExampleHelper.SetConsoleLogger();
42+
43+
bool useLibuv = ClientSettings.UseLibuv;
44+
Console.WriteLine("Transport type : " + (useLibuv ? "Libuv" : "Socket"));
45+
46+
IEventLoopGroup group;
47+
if (useLibuv)
48+
{
49+
group = new EventLoopGroup();
50+
}
51+
else
52+
{
53+
group = new MultithreadEventLoopGroup();
54+
}
55+
56+
X509Certificate2 cert = null;
57+
string targetHost = null;
58+
if (ClientSettings.IsSsl)
59+
{
60+
cert = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");
61+
targetHost = cert.GetNameInfo(X509NameType.DnsName, false);
62+
}
63+
try
64+
{
65+
var bootstrap = new Bootstrap();
66+
bootstrap
67+
.Group(group)
68+
.Option(ChannelOption.TcpNodelay, true);
69+
if (useLibuv)
70+
{
71+
bootstrap.Channel<TcpChannel>();
72+
}
73+
else
74+
{
75+
bootstrap.Channel<TcpSocketChannel>();
76+
}
77+
78+
// Connect with V13 (RFC 6455 aka HyBi-17). You can change it to V08 or V00.
79+
// If you change it to V00, ping is not supported and remember to change
80+
// HttpResponseDecoder to WebSocketHttpResponseDecoder in the pipeline.
81+
var handler =new WebSocketClientHandler(
82+
WebSocketClientHandshakerFactory.NewHandshaker(
83+
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
84+
85+
bootstrap.Handler(new ActionChannelInitializer<IChannel>(channel =>
86+
{
87+
IChannelPipeline pipeline = channel.Pipeline;
88+
if (cert != null)
89+
{
90+
pipeline.AddLast("tls", new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(targetHost)));
91+
}
92+
93+
pipeline.AddLast(
94+
new HttpClientCodec(),
95+
new HttpObjectAggregator(8192),
96+
WebSocketClientCompressionHandler.Instance,
97+
handler);
98+
}));
99+
100+
IChannel ch = await bootstrap.ConnectAsync(new IPEndPoint(ClientSettings.Host, ClientSettings.Port));
101+
await handler.HandshakeCompletion;
102+
103+
Console.WriteLine("WebSocket handshake completed.\n");
104+
Console.WriteLine("\t[bye]:Quit \n\t [ping]:Send ping frame\n\t Enter any text and Enter: Send text frame");
105+
while (true)
106+
{
107+
string msg = Console.ReadLine();
108+
if (msg == null)
109+
{
110+
break;
111+
}
112+
else if ("bye".Equals(msg.ToLower()))
113+
{
114+
await ch.WriteAndFlushAsync(new CloseWebSocketFrame());
115+
break;
116+
}
117+
else if ("ping".Equals(msg.ToLower()))
118+
{
119+
var frame = new PingWebSocketFrame(Unpooled.WrappedBuffer(new byte[] { 8, 1, 8, 1 }));
120+
await ch.WriteAndFlushAsync(frame);
121+
}
122+
else
123+
{
124+
WebSocketFrame frame = new TextWebSocketFrame(msg);
125+
await ch.WriteAndFlushAsync(frame);
126+
}
127+
}
128+
129+
await ch.CloseAsync();
130+
}
131+
finally
132+
{
133+
await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));
134+
}
135+
}
136+
137+
static void Main() => RunClientAsync().Wait();
138+
}
139+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
namespace WebSockets.Client
5+
{
6+
using System;
7+
using System.Text;
8+
using System.Threading.Tasks;
9+
using DotNetty.Codecs.Http;
10+
using DotNetty.Codecs.Http.WebSockets;
11+
using DotNetty.Common.Concurrency;
12+
using DotNetty.Common.Utilities;
13+
using DotNetty.Transport.Channels;
14+
15+
public class WebSocketClientHandler : SimpleChannelInboundHandler<object>
16+
{
17+
readonly WebSocketClientHandshaker handshaker;
18+
readonly TaskCompletionSource completionSource;
19+
20+
public WebSocketClientHandler(WebSocketClientHandshaker handshaker)
21+
{
22+
this.handshaker = handshaker;
23+
this.completionSource = new TaskCompletionSource();
24+
}
25+
26+
public Task HandshakeCompletion => this.completionSource.Task;
27+
28+
public override void ChannelActive(IChannelHandlerContext ctx) =>
29+
this.handshaker.HandshakeAsync(ctx.Channel).LinkOutcome(this.completionSource);
30+
31+
public override void ChannelInactive(IChannelHandlerContext context)
32+
{
33+
Console.WriteLine("WebSocket Client disconnected!");
34+
}
35+
36+
protected override void ChannelRead0(IChannelHandlerContext ctx, object msg)
37+
{
38+
IChannel ch = ctx.Channel;
39+
if (!this.handshaker.IsHandshakeComplete)
40+
{
41+
try
42+
{
43+
this.handshaker.FinishHandshake(ch, (IFullHttpResponse)msg);
44+
Console.WriteLine("WebSocket Client connected!");
45+
this.completionSource.TryComplete();
46+
}
47+
catch (WebSocketHandshakeException e)
48+
{
49+
Console.WriteLine("WebSocket Client failed to connect");
50+
this.completionSource.TrySetException(e);
51+
}
52+
53+
return;
54+
}
55+
56+
57+
if (msg is IFullHttpResponse response)
58+
{
59+
throw new InvalidOperationException(
60+
$"Unexpected FullHttpResponse (getStatus={response.Status}, content={response.Content.ToString(Encoding.UTF8)})");
61+
}
62+
63+
if (msg is TextWebSocketFrame textFrame)
64+
{
65+
Console.WriteLine($"WebSocket Client received message: {textFrame.Text()}");
66+
}
67+
else if (msg is PongWebSocketFrame)
68+
{
69+
Console.WriteLine("WebSocket Client received pong");
70+
}
71+
else if (msg is CloseWebSocketFrame)
72+
{
73+
Console.WriteLine("WebSocket Client received closing");
74+
ch.CloseAsync();
75+
}
76+
}
77+
78+
public override void ExceptionCaught(IChannelHandlerContext ctx, Exception exception)
79+
{
80+
Console.WriteLine("Exception: " + exception);
81+
this.completionSource.TrySetException(exception);
82+
ctx.CloseAsync();
83+
}
84+
}
85+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFrameworks>netcoreapp2.0;net451</TargetFrameworks>
6+
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
7+
</PropertyGroup>
8+
<PropertyGroup Condition=" '$(TargetFramework)' == 'net451' ">
9+
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<None Include="..\..\shared\dotnetty.com.pfx" Link="dotnetty.com.pfx">
13+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
14+
</None>
15+
<None Update="appsettings.json">
16+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
17+
</None>
18+
</ItemGroup>
19+
<ItemGroup>
20+
<ProjectReference Include="..\..\src\DotNetty.Buffers\DotNetty.Buffers.csproj" />
21+
<ProjectReference Include="..\..\src\DotNetty.Codecs.Http\DotNetty.Codecs.Http.csproj" />
22+
<ProjectReference Include="..\..\src\DotNetty.Codecs\DotNetty.Codecs.csproj" />
23+
<ProjectReference Include="..\..\src\DotNetty.Common\DotNetty.Common.csproj" />
24+
<ProjectReference Include="..\..\src\DotNetty.Handlers\DotNetty.Handlers.csproj" />
25+
<ProjectReference Include="..\..\src\DotNetty.Transport.Libuv\DotNetty.Transport.Libuv.csproj" />
26+
<ProjectReference Include="..\..\src\DotNetty.Transport\DotNetty.Transport.csproj" />
27+
<ProjectReference Include="..\Examples.Common\Examples.Common.csproj" />
28+
</ItemGroup>
29+
30+
</Project>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"ssl": "false",
3+
"host": "127.0.0.1",
4+
"port": "8080",
5+
"path": "/websocket",
6+
"libuv": "true"
7+
}

0 commit comments

Comments
 (0)