Skip to content

Commit 06c9d90

Browse files
authored
Feature/v4 (#35)
* v4.0: - timeouts - change miliseconds to TimeSpan - last chance - disabling - stream fake message - StartOrFail, ReconnectOrFail - fail fast approach, throws exception on connection error * ReconnectionHappened and DisconnectionHappened - extend response, add additional info about close status and caused exception * DisconnectionHappened stream - enable canceling reconnection
1 parent 691a693 commit 06c9d90

20 files changed

+835
-152
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ deploy:
1212
provider: script
1313
skip_cleanup: true
1414
script:
15-
- cd src/Websocket.Client && dotnet pack /p:PackageVersion=3.2.$TRAVIS_BUILD_NUMBER -c Release && cd bin/Release && dotnet nuget push **/*.3.2.$TRAVIS_BUILD_NUMBER.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json
15+
- cd src/Websocket.Client && dotnet pack /p:PackageVersion=4.0.$TRAVIS_BUILD_NUMBER -c Release && cd bin/Release && dotnet nuget push **/*.4.0.$TRAVIS_BUILD_NUMBER.nupkg -k $NUGET_API_KEY -s https://api.nuget.org/v3/index.json
1616
on:
1717
branch: master

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2019 Mariusz Kotas
3+
Copyright (c) 2020 Mariusz Kotas
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Websocket.Client.sln.DotSettings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
22
<s:Boolean x:Key="/Default/UserDictionary/Words/=Bitmex/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fakely/@EntryIndexedValue">True</s:Boolean>
34
<s:Boolean x:Key="/Default/UserDictionary/Words/=releaser/@EntryIndexedValue">True</s:Boolean>
5+
<s:Boolean x:Key="/Default/UserDictionary/Words/=subprotocol/@EntryIndexedValue">True</s:Boolean>
46
<s:Boolean x:Key="/Default/UserDictionary/Words/=testnet/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

src/Websocket.Client/IWebsocketClient.cs

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Net.WebSockets;
33
using System.Text;
44
using System.Threading.Tasks;
5+
using Websocket.Client.Models;
56

67
namespace Websocket.Client
78
{
@@ -22,24 +23,26 @@ public interface IWebsocketClient : IDisposable
2223
/// <summary>
2324
/// Stream for reconnection event (triggered after the new connection)
2425
/// </summary>
25-
IObservable<ReconnectionType> ReconnectionHappened { get; }
26+
IObservable<ReconnectionInfo> ReconnectionHappened { get; }
2627

2728
/// <summary>
2829
/// Stream for disconnection event (triggered after the connection was lost)
2930
/// </summary>
30-
IObservable<DisconnectionType> DisconnectionHappened { get; }
31+
IObservable<DisconnectionInfo> DisconnectionHappened { get; }
3132

3233
/// <summary>
3334
/// Time range in ms, how long to wait before reconnecting if no message comes from server.
34-
/// Default 60000 ms (1 minute)
35+
/// Set null to disable this feature.
36+
/// Default: 1 minute.
3537
/// </summary>
36-
int ReconnectTimeoutMs { get; set; }
38+
TimeSpan? ReconnectTimeout { get; set; }
3739

3840
/// <summary>
3941
/// Time range in ms, how long to wait before reconnecting if last reconnection failed.
40-
/// Default 60000 ms (1 minute)
42+
/// Set null to disable this feature.
43+
/// Default: 1 minute.
4144
/// </summary>
42-
int ErrorReconnectTimeoutMs { get; set; }
45+
TimeSpan? ErrorReconnectTimeout { get; set; }
4346

4447
/// <summary>
4548
/// Get or set the name of the current websocket client instance.
@@ -75,10 +78,19 @@ public interface IWebsocketClient : IDisposable
7578
Encoding MessageEncoding { get; set; }
7679

7780
/// <summary>
78-
/// Start listening to the websocket stream on the background thread
81+
/// Start listening to the websocket stream on the background thread.
82+
/// In case of connection error it doesn't throw an exception.
83+
/// Only streams a message via 'DisconnectionHappened' and logs it.
7984
/// </summary>
8085
Task Start();
8186

87+
/// <summary>
88+
/// Start listening to the websocket stream on the background thread.
89+
/// In case of connection error it throws an exception.
90+
/// Fail fast approach.
91+
/// </summary>
92+
Task StartOrFail();
93+
8294
/// <summary>
8395
/// Stop/close websocket connection with custom close code.
8496
/// Method could throw exceptions.
@@ -121,7 +133,22 @@ public interface IWebsocketClient : IDisposable
121133
/// <summary>
122134
/// Force reconnection.
123135
/// Closes current websocket stream and perform a new connection to the server.
136+
/// In case of connection error it doesn't throw an exception, but tries to reconnect indefinitely.
124137
/// </summary>
125138
Task Reconnect();
139+
140+
/// <summary>
141+
/// Force reconnection.
142+
/// Closes current websocket stream and perform a new connection to the server.
143+
/// In case of connection error it throws an exception and doesn't perform any other reconnection try.
144+
/// </summary>
145+
Task ReconnectOrFail();
146+
147+
/// <summary>
148+
/// Stream/publish fake message (via 'MessageReceived' observable).
149+
/// Use for testing purposes to simulate a server message.
150+
/// </summary>
151+
/// <param name="message">Message to be stream</param>
152+
void StreamFakeMessage(ResponseMessage message);
126153
}
127154
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System;
2+
using System.Net.WebSockets;
3+
4+
// ReSharper disable once CheckNamespace
5+
namespace Websocket.Client
6+
{
7+
/// <summary>
8+
/// Info about happened disconnection
9+
/// </summary>
10+
public class DisconnectionInfo
11+
{
12+
/// <inheritdoc />
13+
public DisconnectionInfo(DisconnectionType type, WebSocketCloseStatus? closeStatus,
14+
string closeStatusDescription, string subProtocol, Exception exception)
15+
{
16+
Type = type;
17+
CloseStatus = closeStatus;
18+
CloseStatusDescription = closeStatusDescription;
19+
SubProtocol = subProtocol;
20+
Exception = exception;
21+
}
22+
23+
/// <summary>
24+
/// Disconnection reason
25+
/// </summary>
26+
public DisconnectionType Type { get; }
27+
28+
/// <summary>
29+
/// Indicates the reason why the remote endpoint initiated the close handshake
30+
/// </summary>
31+
public WebSocketCloseStatus? CloseStatus { get; }
32+
33+
/// <summary>
34+
/// Allows the remote endpoint to describe the reason whe the connection was closed
35+
/// </summary>
36+
public string CloseStatusDescription { get; }
37+
38+
/// <summary>
39+
/// The subprotocol that was negotiated during the opening handshake
40+
/// </summary>
41+
public string SubProtocol { get; }
42+
43+
/// <summary>
44+
/// Exception that cause disconnection, can be null
45+
/// </summary>
46+
public Exception Exception { get; }
47+
48+
49+
/// <summary>
50+
/// Set to true if you want to cancel ongoing reconnection
51+
/// </summary>
52+
public bool CancelReconnection { get; set; }
53+
54+
55+
/// <summary>
56+
/// Simple factory method
57+
/// </summary>
58+
public static DisconnectionInfo Create(DisconnectionType type, WebSocket client, Exception exception)
59+
{
60+
return new DisconnectionInfo(type, client?.CloseStatus, client?.CloseStatusDescription,
61+
client?.SubProtocol, exception);
62+
}
63+
}
64+
}

src/Websocket.Client/DisconnectionType.cs renamed to src/Websocket.Client/Models/DisconnectionType.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
namespace Websocket.Client
1+
// ReSharper disable once CheckNamespace
2+
namespace Websocket.Client
23
{
34
/// <summary>
45
/// Type that specify happened disconnection
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using System;
2+
3+
// ReSharper disable once CheckNamespace
4+
namespace Websocket.Client.Models
5+
{
6+
/// <summary>
7+
/// Info about happened reconnection
8+
/// </summary>
9+
public class ReconnectionInfo
10+
{
11+
/// <inheritdoc />
12+
public ReconnectionInfo(ReconnectionType type)
13+
{
14+
Type = type;
15+
}
16+
17+
/// <summary>
18+
/// Reconnection reason
19+
/// </summary>
20+
public ReconnectionType Type { get; }
21+
22+
/// <summary>
23+
/// Simple factory method
24+
/// </summary>
25+
public static ReconnectionInfo Create(ReconnectionType type)
26+
{
27+
return new ReconnectionInfo(type);
28+
}
29+
}
30+
}

src/Websocket.Client/ReconnectionType.cs renamed to src/Websocket.Client/Models/ReconnectionType.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
namespace Websocket.Client
1+
// ReSharper disable once CheckNamespace
2+
namespace Websocket.Client
23
{
34
/// <summary>
45
/// Type that specify happened reconnection

src/Websocket.Client/Websocket.Client.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
<PropertyGroup>
44
<TargetFramework>netstandard2.0</TargetFramework>
55
<PackageId>Websocket.Client</PackageId>
6-
<Version>3.2.0</Version>
6+
<Version>4.0.0</Version>
77
<Authors>Mariusz Kotas</Authors>
88
<Description>Client for websocket API with built-in reconnection and error handling</Description>
99
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
10-
<PackageReleaseNotes>Release of version 3.0</PackageReleaseNotes>
11-
<Copyright>Copyright 2019 Mariusz Kotas. All rights reserved.</Copyright>
10+
<PackageReleaseNotes>Release of version 4.0</PackageReleaseNotes>
11+
<Copyright>Copyright 2020 Mariusz Kotas. All rights reserved.</Copyright>
1212
<PackageTags>websockets websocket client</PackageTags>
1313
<PackageLicenseUrl>https://github.com/Marfusios/websocket-client/blob/master/LICENSE</PackageLicenseUrl>
1414
<PackageProjectUrl>https://github.com/Marfusios/websocket-client</PackageProjectUrl>
@@ -17,8 +17,8 @@
1717
<RepositoryType>Git</RepositoryType>
1818
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
1919
<GenerateDocumentationFile>true</GenerateDocumentationFile>
20-
<AssemblyVersion>3.2.0.0</AssemblyVersion>
21-
<FileVersion>3.2.0.0</FileVersion>
20+
<AssemblyVersion>4.0.0.0</AssemblyVersion>
21+
<FileVersion>4.0.0.0</FileVersion>
2222
</PropertyGroup>
2323

2424
<ItemGroup>

src/Websocket.Client/WebsocketClient.Reconnecting.cs

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
42
using System.Threading;
53
using System.Threading.Tasks;
64
using Websocket.Client.Logging;
@@ -13,8 +11,24 @@ public partial class WebsocketClient
1311
/// <summary>
1412
/// Force reconnection.
1513
/// Closes current websocket stream and perform a new connection to the server.
14+
/// In case of connection error it doesn't throw an exception, but tries to reconnect indefinitely.
1615
/// </summary>
17-
public async Task Reconnect()
16+
public Task Reconnect()
17+
{
18+
return ReconnectInternal(false);
19+
}
20+
21+
/// <summary>
22+
/// Force reconnection.
23+
/// Closes current websocket stream and perform a new connection to the server.
24+
/// In case of connection error it throws an exception and doesn't perform any other reconnection try.
25+
/// </summary>
26+
public Task ReconnectOrFail()
27+
{
28+
return ReconnectInternal(true);
29+
}
30+
31+
private async Task ReconnectInternal(bool failFast)
1832
{
1933
if (!IsStarted)
2034
{
@@ -24,39 +38,47 @@ public async Task Reconnect()
2438

2539
try
2640
{
27-
await ReconnectSynchronized(ReconnectionType.ByUser).ConfigureAwait(false);
28-
41+
await ReconnectSynchronized(ReconnectionType.ByUser, failFast, null).ConfigureAwait(false);
2942
}
3043
finally
3144
{
3245
_reconnecting = false;
3346
}
3447
}
3548

36-
37-
private async Task ReconnectSynchronized(ReconnectionType type)
49+
private async Task ReconnectSynchronized(ReconnectionType type, bool failFast, Exception causedException)
3850
{
3951
using (await _locker.LockAsync())
4052
{
41-
await Reconnect(type);
53+
await Reconnect(type, failFast, causedException);
4254
}
4355
}
4456

45-
private async Task Reconnect(ReconnectionType type)
57+
private async Task Reconnect(ReconnectionType type, bool failFast, Exception causedException)
4658
{
4759
IsRunning = false;
4860
if (_disposing)
4961
return;
5062

5163
_reconnecting = true;
52-
if (type != ReconnectionType.Error)
53-
_disconnectedSubject.OnNext(TranslateTypeToDisconnection(type));
5464

65+
var disType = TranslateTypeToDisconnection(type);
66+
var disInfo = DisconnectionInfo.Create(disType, _client, causedException);
67+
if (type != ReconnectionType.Error)
68+
{
69+
_disconnectedSubject.OnNext(disInfo);
70+
if (disInfo.CancelReconnection)
71+
{
72+
// reconnection canceled by user, do nothing
73+
Logger.Info(L($"Reconnecting canceled by user, exiting."));
74+
}
75+
}
76+
5577
_cancellation.Cancel();
5678
_client?.Abort();
5779
_client?.Dispose();
5880

59-
if (!IsReconnectionEnabled)
81+
if (!IsReconnectionEnabled || disInfo.CancelReconnection)
6082
{
6183
// reconnection disabled, do nothing
6284
IsStarted = false;
@@ -66,7 +88,7 @@ private async Task Reconnect(ReconnectionType type)
6688

6789
Logger.Debug(L("Reconnecting..."));
6890
_cancellation = new CancellationTokenSource();
69-
await StartClient(_url, _cancellation.Token, type).ConfigureAwait(false);
91+
await StartClient(_url, _cancellation.Token, type, failFast).ConfigureAwait(false);
7092
_reconnecting = false;
7193
}
7294

@@ -84,22 +106,22 @@ private void DeactivateLastChance()
84106

85107
private void LastChance(object state)
86108
{
87-
if (!IsReconnectionEnabled)
109+
if (!IsReconnectionEnabled || ReconnectTimeout == null)
88110
{
89111
// reconnection disabled, do nothing
90112
DeactivateLastChance();
91113
return;
92114
}
93115

94-
var timeoutMs = Math.Abs(ReconnectTimeoutMs);
116+
var timeoutMs = Math.Abs(ReconnectTimeout.Value.TotalMilliseconds);
95117
var diffMs = Math.Abs(DateTime.UtcNow.Subtract(_lastReceivedMsg).TotalMilliseconds);
96118
if (diffMs > timeoutMs)
97119
{
98120
Logger.Debug(L($"Last message received more than {timeoutMs:F} ms ago. Hard restart.."));
99121

100122
DeactivateLastChance();
101123
#pragma warning disable 4014
102-
ReconnectSynchronized(ReconnectionType.NoMessageReceived);
124+
ReconnectSynchronized(ReconnectionType.NoMessageReceived, false, null);
103125
#pragma warning restore 4014
104126
}
105127
}

0 commit comments

Comments
 (0)