Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Server Name Indication #4

Open
wants to merge 35 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8e73eef
Support for Server Name Indication
viktor-nikolaev Mar 1, 2019
e1b8991
Improvement: add try/catch on all .SendAsync methods
quynh-ng Mar 2, 2019
787fd86
Upgrade to latest components
quynh-ng Mar 6, 2019
c34f96b
Improvements: add locking mechanims, check disposed objects when send…
quynh-ng Mar 12, 2019
ef5a36f
Code refactoring
quynh-ng Mar 16, 2019
a0da85f
Improvement: add support of dual IP modes (v4 & v6)
quynh-ng Mar 26, 2019
43f7c7f
Upgrade to latest components to release new nuget version
quynh-ng Apr 1, 2019
08841ac
Add options to build custom ping/pong payload
quynh-ng Apr 8, 2019
52b1cc9
Add options to build custom ping/pong payload
quynh-ng Apr 9, 2019
8eaaf28
Improvement: custom ping/pong payload
quynh-ng Apr 11, 2019
0d58799
Upgrade to latest components
quynh-ng May 1, 2019
9c943f6
Close websocket
quynh-ng May 6, 2019
c19fdab
Improvement: add async close methods
quynh-ng May 7, 2019
21a0572
Improvement: OS name
quynh-ng May 22, 2019
0ff1284
Improvement: add async close methods
quynh-ng Jun 12, 2019
9d8b09a
Improvement: add try/catch on all .SendAsync methods
quynh-ng Mar 2, 2019
0f74658
Upgrade to latest components
quynh-ng Mar 6, 2019
de5a0d6
Improvements: add locking mechanims, check disposed objects when send…
quynh-ng Mar 12, 2019
6bb72c9
Code refactoring
quynh-ng Mar 16, 2019
a7d934e
Improvement: add support of dual IP modes (v4 & v6)
quynh-ng Mar 26, 2019
cce2673
Upgrade to latest components to release new nuget version
quynh-ng Apr 1, 2019
71b2281
Add options to build custom ping/pong payload
quynh-ng Apr 8, 2019
d3c4d04
Add options to build custom ping/pong payload
quynh-ng Apr 9, 2019
fb38d19
Improvement: custom ping/pong payload
quynh-ng Apr 11, 2019
8339f1a
Upgrade to latest components
quynh-ng May 1, 2019
8b1ed45
Close websocket
quynh-ng May 6, 2019
78947cd
Improvement: add async close methods
quynh-ng May 7, 2019
de99061
Improvement: OS name
quynh-ng May 22, 2019
475edf5
Improvement: add async close methods
quynh-ng Jun 12, 2019
127cce2
Support for Server Name Indication
viktor-nikolaev Mar 1, 2019
3d3b881
Merge remote-tracking branch 'origin/develop' into develop
viktor-nikolaev Jun 20, 2019
00f373f
Improvements: add locking mechanims, check disposed objects when send…
quynh-ng Mar 12, 2019
91141e0
Add options to build custom ping/pong payload
quynh-ng Apr 9, 2019
b92e651
Upgrade to latest components
quynh-ng May 1, 2019
0da2b07
fixed headers filtering
viktor-nikolaev Jun 20, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 28 additions & 85 deletions PingPong.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,86 +9,40 @@

namespace net.vieapps.Components.WebSockets
{
/// <summary>
/// Pong EventArgs
/// </summary>
internal class PongEventArgs : EventArgs
{
/// <summary>
/// The data extracted from a Pong WebSocket frame
/// </summary>
public ArraySegment<byte> Payload { get; private set; }

/// <summary>
/// Initialises a new instance of the PongEventArgs class
/// </summary>
/// <param name="payload">The pong payload must be 125 bytes or less (can be zero bytes)</param>
public PongEventArgs(ArraySegment<byte> payload) => this.Payload = payload;
}

// --------------------------------------------------

/// <summary>
/// Ping Pong Manager used to facilitate ping pong WebSocket messages
/// </summary>
internal interface IPingPongManager
{
/// <summary>
/// Raised when a Pong frame is received
/// </summary>
event EventHandler<PongEventArgs> Pong;

/// <summary>
/// Sends a ping frame
/// </summary>
/// <param name="payload">The payload (must be 125 bytes of less)</param>
/// <param name="cancellation">The cancellation token</param>
Task SendPingAsync(ArraySegment<byte> payload, CancellationToken cancellation = default(CancellationToken));
}

// --------------------------------------------------

/// <summary>
/// Ping Pong Manager used to facilitate ping pong WebSocket messages
/// </summary>
internal class PingPongManager : IPingPongManager
internal class PingPongManager
{
readonly WebSocketImplementation _websocket;
readonly Task _pingTask;
readonly CancellationToken _cancellationToken;
readonly Stopwatch _stopwatch;
long _pingSentTicks;

/// <summary>
/// Raised when a Pong frame is received
/// </summary>
public event EventHandler<PongEventArgs> Pong;
readonly Action<ManagedWebSocket, byte[]> _onPong;
readonly Func<ManagedWebSocket, byte[], byte[]> _getPongPayload;
readonly Func<ManagedWebSocket, byte[]> _getPingPayload;
long _pingTimestamp = 0;

/// <summary>
/// Initialises a new instance of the PingPongManager to facilitate ping pong WebSocket messages.
/// </summary>
/// <param name="websocket">The WebSocket instance used to listen to ping messages and send pong messages</param>
/// <param name="cancellationToken">The token used to cancel a pending ping send AND the automatic sending of ping messages if KeepAliveInterval is positive</param>
public PingPongManager(WebSocketImplementation websocket, CancellationToken cancellationToken)
public PingPongManager(WebSocketImplementation websocket, WebSocketOptions options, CancellationToken cancellationToken)
{
this._websocket = websocket;
this._websocket.Pong += this.DoPong;
this._cancellationToken = cancellationToken;
this._stopwatch = Stopwatch.StartNew();
this._pingTask = Task.Run(this.DoPingAsync);
this._getPongPayload = options.GetPongPayload;
this._onPong = options.OnPong;
if (this._websocket.KeepAliveInterval != TimeSpan.Zero)
{
this._getPingPayload = options.GetPingPayload;
Task.Run(this.SendPingAsync).ConfigureAwait(false);
}
}

public void OnPong(byte[] pong)
{
this._pingTimestamp = 0;
this._onPong?.Invoke(this._websocket, pong);
}

/// <summary>
/// Sends a ping frame
/// </summary>
/// <param name="payload">The payload (must be 125 bytes of less)</param>
/// <param name="cancellationToken">The cancellation token</param>
public Task SendPingAsync(ArraySegment<byte> payload, CancellationToken cancellationToken = default(CancellationToken))
=> this._websocket.SendPingAsync(payload, cancellationToken);
public Task SendPongAsync(byte[] ping)
=> this._websocket.SendPongAsync((this._getPongPayload?.Invoke(this._websocket, ping) ?? ping).ToArraySegment(), this._cancellationToken);

async Task DoPingAsync()
public async Task SendPingAsync()
{
Events.Log.PingPongManagerStarted(this._websocket.ID, (int)this._websocket.KeepAliveInterval.TotalSeconds);
Events.Log.PingPongManagerStarted(this._websocket.ID, this._websocket.KeepAliveInterval.TotalSeconds.CastAs<int>());
try
{
while (!this._cancellationToken.IsCancellationRequested)
Expand All @@ -97,30 +51,19 @@ async Task DoPingAsync()
if (this._websocket.State != WebSocketState.Open)
break;

if (this._pingSentTicks != 0)
if (this._pingTimestamp != 0)
{
Events.Log.KeepAliveIntervalExpired(this._websocket.ID, (int)this._websocket.KeepAliveInterval.TotalSeconds);
await this._websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, $"No Pong message received in response to a Ping after KeepAliveInterval ({this._websocket.KeepAliveInterval})", this._cancellationToken).ConfigureAwait(false);
await this._websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, $"No PONG message received in response to a PING message after keep-alive interval ({this._websocket.KeepAliveInterval})", this._cancellationToken).ConfigureAwait(false);
break;
}

this._pingSentTicks = this._stopwatch.Elapsed.Ticks;
await this.SendPingAsync(this._pingSentTicks.ToArraySegment(), this._cancellationToken).ConfigureAwait(false);
this._pingTimestamp = DateTime.Now.ToUnixTimestamp();
await this._websocket.SendPingAsync((this._getPingPayload?.Invoke(this._websocket) ?? this._pingTimestamp.ToBytes()).ToArraySegment(), this._cancellationToken).ConfigureAwait(false);
}
}
catch (OperationCanceledException)
{
// normal, do nothing
}
catch { }
Events.Log.PingPongManagerEnded(this._websocket.ID);
}

protected virtual void OnPong(PongEventArgs args) => this.Pong?.Invoke(this, args);

void DoPong(object sender, PongEventArgs arg)
{
this._pingSentTicks = 0;
this.OnPong(arg);
}
}
}
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public EndPoint LocalEndPoint { get; }

// Extra information
public Dictionary<string, object> Extra { get; }

// Headers information
public Dictionary<string, string> Headers { get; }
```

## Fly on the sky with Event-liked driven
Expand Down Expand Up @@ -117,8 +120,8 @@ var websocket = new WebSocket
And this class has some methods for working on both side of client and server role:

```csharp
void Connect(Uri uri, WebSocketOptions options, Action<ManagedWebSocket> onSuccess, Action<Exception> onFailed);
void StartListen(int port, X509Certificate2 certificate, Action onSuccess, Action<Exception> onFailed);
void Connect(Uri uri, WebSocketOptions options, Action<ManagedWebSocket> onSuccess, Action<Exception> onFailure);
void StartListen(int port, X509Certificate2 certificate, Action onSuccess, Action<Exception> onFailure, Func<ManagedWebSocket, byte[]> getPingPayload, Func<ManagedWebSocket, byte[], byte[]> getPongPayload, Action<ManagedWebSocket, byte[]> onPong);
void StopListen();
```

Expand Down Expand Up @@ -149,7 +152,7 @@ websocket.StartListen();

Want to have a free SSL certificate? Take a look at [Let's Encrypt](https://letsencrypt.org/).

Special: A simple tool named [lets-encrypt-win-simple](https://github.com/PKISharp/win-acme) will help your IIS works with Let's Encrypt very well.
Special: A simple tool named [win-acme](https://github.com/PKISharp/win-acme) will help your IIS works with Let's Encrypt very well.

### SubProtocol Negotiation

Expand Down Expand Up @@ -185,7 +188,7 @@ When integrate this component with your app that hosted by ASP.NET / ASP.NET Cor
then the method **WrapAsync** is here to help. This method will return a task that run a process for receiving messages from this WebSocket connection.

```csharp
Task WrapAsync(System.Net.WebSockets.WebSocket webSocket, Uri requestUri, EndPoint remoteEndPoint, EndPoint localEndPoint, string userAgent, string urlReferrer, string headers, string cookies, Action<ManagedWebSocket> onSuccess);
Task WrapAsync(System.Net.WebSockets.WebSocket webSocket, Uri requestUri, EndPoint remoteEndPoint, EndPoint localEndPoint, Dictionary<string, string> headers, Action<ManagedWebSocket> onSuccess);
```

And might be you need an extension method to wrap an existing WebSocket connection, then take a look at some lines of code below:
Expand Down Expand Up @@ -316,7 +319,7 @@ bool CloseWebSocket(ManagedWebSocket websocket, WebSocketCloseStatus closeStatus

Our prefers:
- [Microsoft.Extensions.Logging.Console](https://www.nuget.org/packages/Microsoft.Extensions.Logging.Console): live logs
- [Serilog.Extensions.Logging.File](https://www.nuget.org/packages/Serilog.Extensions.Logging.File): rolling log files (by date) - high performance, and very simple to use
- [Serilog.Extensions.Logging.File](https://www.nuget.org/packages/Serilog.Extensions.Logging.File): rolling log files (by hour or date) - high performance, and very simple to use

### Namespaces

Expand Down
14 changes: 7 additions & 7 deletions VIEApps.Components.WebSockets.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,24 @@
<AssemblyTitle>VIEApps NGX WebSockets</AssemblyTitle>
<SignAssembly>false</SignAssembly>
<AssemblyOriginatorKeyFile>../VIEApps.Components.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>10.2.1903.1</AssemblyVersion>
<FileVersion>10.2.1903.1</FileVersion>
<InformationalVersion>v10.2.netstandard-2+rev:2019.03.01-latest.components-ignore.cert.errors</InformationalVersion>
<AssemblyVersion>10.2.1906.2</AssemblyVersion>
<FileVersion>10.2.1906.2</FileVersion>
<InformationalVersion>v10.2.netstandard-2+rev:2019.06.12-async.close</InformationalVersion>
<Product>VIEApps NGX</Product>
<Company>VIEApps.net</Company>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageId>VIEApps.Components.WebSockets</PackageId>
<Version>10.2.1903.1</Version>
<Version>10.2.1906.2</Version>
<Title>VIEApps NGX WebSockets</Title>
<Description>High performance WebSocket on .NET Standard 2.0 (both server and client - standalone or wrapper of System.Net.WebSockets.WebSocket)</Description>
<Authors>VIEApps.net</Authors>
<Copyright>© 2019 VIEApps.net</Copyright>
<PackageLicense>LICENSE.md</PackageLicense>
<PackageOutputPath>..\</PackageOutputPath>
<PackageTags>websocket;websockets;websocket-client;websocket-server;websocket-wrapper;vieapps;vieapps.components</PackageTags>
<PackageReleaseNotes>Upgrade to latest components, add options to ignore remote certificate errors (support for client certificates)</PackageReleaseNotes>
<PackageReleaseNotes>Improvement: add async close methods</PackageReleaseNotes>
<PackageProjectUrl>https://vieapps.net/</PackageProjectUrl>
<PackageIconUrl>https://github.com/vieapps/Components.Utility/raw/master/logo.png</PackageIconUrl>
<PackageProjectUrl>https://github.com/vieapps/Components.WebSockets</PackageProjectUrl>
<RepositoryUrl>https://github.com/vieapps/Components.WebSockets</RepositoryUrl>
</PropertyGroup>

Expand All @@ -39,7 +39,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="VIEApps.Components.Utility" Version="10.2.1903.1" />
<PackageReference Include="VIEApps.Components.Utility" Version="10.2.1906.2" />
</ItemGroup>

</Project>
Loading