Skip to content

Commit 334b209

Browse files
committed
multiple socket connections, breaking changes, performance improvements, invocators
1 parent fded493 commit 334b209

36 files changed

+445
-270
lines changed

README.md

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
### Cross-Platform WebSockets From Browsering (DMZ) Zone to API - Service (Backend) Zone
1+
### Cross-Platform WebSockets Proxy
22

3-
This project is demonstrating bidirectional connection and data transfer from low level layer (secure zone) to
4-
Browsering - DMZ (Public) zone via .NET Core WebSockets.
3+
This project is demonstrating bidirectional connection and data transfer via .NET Core WebSockets.
54

65
You can use it on your API - Service side to communicate among your trusted Backend API consumer
76
Clients (for example MVC Web Application Hosting) and at the same time may
@@ -15,7 +14,7 @@ operation with same interface.
1514

1615
#### Startup ConfigureServices
1716
```csharp
18-
// Add NetCoreStack Native Socket Services.
17+
// Add socket services.
1918
services.AddNativeWebSockets(options => {
2019
options.RegisterInvocator<ServerWebSocketCommandInvocator>(WebSocketCommands.All);
2120
});
@@ -26,7 +25,7 @@ services.AddNativeWebSockets(options => {
2625
app.UseNativeWebSockets();
2726
```
2827

29-
#### Controller with Dependency Injection
28+
#### Controller with dependency injection
3029
```csharp
3130
public MyController(IConnectionManager connectionManager)
3231
{
@@ -44,29 +43,28 @@ public async Task<IActionResult> SendAsync([FromBody]SimpleModel model)
4443
}
4544
```
4645

47-
### Usage for Trusted Clients or Browsering (DMZ) Layer
46+
### Clients
4847
#### Startup ConfigureServices
4948
```csharp
50-
// Client WebSocket - DMZ to API side connections
51-
services.AddProxyWebSockets(options => {
52-
options.WebSocketHostAddress = "localhost:7803";
53-
options.RegisterInvocator<CustomWebSocketCommandInvocator>(WebSocketCommands.All);
54-
});
55-
56-
// WebSockets for Browsers - User Agent ( javascript clients )
49+
// WebSockets for Browsers
5750
services.AddNativeWebSockets(options => {
5851
options.RegisterInvocator<AgentsWebSocketCommandInvocator>(WebSocketCommands.All);
5952
});
6053

54+
// Client WebSocket - Proxy connections
55+
services.AddProxyWebSockets()
56+
.Register<CustomWebSocketCommandInvocator>(connectorname, "localhost:7803")
57+
.Register<AnotherEndpointWebSocketCommandInvocator>(connectorname, "localhost:5000"); // Another endpoint registration, host address must be unique
58+
6159
// Add MVC framework services.
6260
services.AddMvc();
6361
```
6462
#### Startup Configure
6563
```csharp
66-
// Proxy (Domain App) Client WebSocket - DMZ to API side connections
64+
// Client WebSocket - Proxy connections
6765
app.UseProxyWebSockets();
6866

69-
// User Agent WebSockets for Browsers
67+
// WebSockets for Browsers
7068
app.UseNativeWebSockets();
7169

7270
// Use MVC
@@ -86,16 +84,35 @@ public class CustomWebSocketCommandInvocator : IClientWebSocketCommandInvocator
8684

8785
public Task InvokeAsync(WebSocketMessageContext context)
8886
{
89-
// Sending incoming data from Backend zone to the Clients (Browsers)
87+
// Sending incoming data from backend to the clients (Browsers)
9088
_connectionManager.BroadcastAsync(context);
9189
return Task.CompletedTask;
9290
}
9391
}
9492
```
9593

96-
### Prerequisites
97-
> [ASP.NET Core](https://github.com/aspnet/Home)
94+
```csharp
95+
public class ClientDiscoveryController : Controller
96+
{
97+
private readonly IWebSocketConnector _connector;
98+
public ClientDiscoveryController(IWebSocketConnector<CustomWebSocketCommandInvocator> connector)
99+
{
100+
_connector = connector;
101+
}
98102

99-
### Installation
103+
[HttpGet]
104+
public async Task<IActionResult> KeepAlive()
105+
{
106+
await _connector.SendAsync(new WebSocketMessageContext
107+
{
108+
Command = WebSocketCommands.DataSend,
109+
Value = new { Id = 1, Name = "Hello World!", DateTime = DateTime.Now }
110+
});
100111

101-
> dotnet restore
112+
return Ok();
113+
}
114+
}
115+
```
116+
117+
### Prerequisites
118+
> [ASP.NET Core](https://github.com/aspnet/Home)

src/NetCoreStack.WebSockets.ProxyClient/ApplicationBuilderExtensions.cs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
using Microsoft.AspNetCore.Builder;
22
using Microsoft.AspNetCore.Hosting;
33
using Microsoft.Extensions.DependencyInjection;
4-
using Microsoft.Extensions.Logging;
54
using System;
5+
using System.Collections.Generic;
66
using System.Threading;
77
using System.Threading.Tasks;
88

99
namespace NetCoreStack.WebSockets.ProxyClient
1010
{
1111
public static class ApplicationBuilderExtensions
1212
{
13+
private static void ThrowIfServiceNotRegistered(IServiceProvider applicationServices)
14+
{
15+
var service = applicationServices.GetService<ProxyClientMarkerService>();
16+
if (service == null)
17+
throw new InvalidOperationException(string.Format("Required services are not registered - are you missing a call to AddProxyWebSockets?"));
18+
}
19+
1320
public static IApplicationBuilder UseProxyWebSockets(this IApplicationBuilder app, CancellationTokenSource cancellationTokenSource = null)
1421
{
22+
ThrowIfServiceNotRegistered(app.ApplicationServices);
1523
var appLifeTime = app.ApplicationServices.GetService<IApplicationLifetime>();
16-
var loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
17-
var logger = loggerFactory.CreateLogger(nameof(ClientWebSocketConnector));
18-
var webSocketConnector = app.ApplicationServices.GetService<IWebSocketConnector>();
19-
20-
if (webSocketConnector != null && appLifeTime != null)
24+
IList<IWebSocketConnector> connectors = InvocatorFactory.GetConnectors(app.ApplicationServices);
25+
foreach (var connector in connectors)
2126
{
22-
appLifeTime.ApplicationStopping.Register(OnShutdown, webSocketConnector);
23-
Task.Run(async () => await webSocketConnector.ConnectAsync(cancellationTokenSource));
27+
appLifeTime.ApplicationStopping.Register(OnShutdown, connector);
28+
Task.Run(async () => await connector.ConnectAsync(cancellationTokenSource));
2429
}
2530

2631
return app;

src/NetCoreStack.WebSockets.ProxyClient/ClientWebSocketConnector.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Microsoft.Extensions.Logging;
2-
using Microsoft.Extensions.Options;
32
using NetCoreStack.WebSockets.Interfaces;
43
using NetCoreStack.WebSockets.Internal;
54
using System;
@@ -10,13 +9,13 @@
109

1110
namespace NetCoreStack.WebSockets.ProxyClient
1211
{
13-
internal class ClientWebSocketConnector : IWebSocketConnector
12+
public abstract class ClientWebSocketConnector : IWebSocketConnector
1413
{
1514
private string _connectionId;
1615
private ClientWebSocket _webSocket;
16+
private readonly IServiceProvider _serviceProvider;
1717
private readonly IStreamCompressor _compressor;
1818
private readonly ILoggerFactory _loggerFactory;
19-
private readonly InvocatorRegistry _invocatorRegistry;
2019

2120
public string ConnectionId
2221
{
@@ -37,44 +36,42 @@ public WebSocketState WebSocketState
3736
}
3837
}
3938

40-
public ProxyOptions Options { get; }
41-
42-
public ClientWebSocketConnector(IOptions<ProxyOptions> options,
43-
IStreamCompressor compressor,
44-
InvocatorRegistry invocatorRegistry,
39+
public ClientWebSocketConnector(IServiceProvider serviceProvider,
40+
IStreamCompressor compressor,
4541
ILoggerFactory loggerFactory)
4642
{
43+
_serviceProvider = serviceProvider;
4744
_compressor = compressor;
48-
_invocatorRegistry = invocatorRegistry;
4945
_loggerFactory = loggerFactory;
50-
Options = options.Value;
5146
}
5247

48+
protected abstract InvocatorContext CreateInvocatorContext();
49+
5350
private async Task TryConnectAsync(CancellationTokenSource cancellationTokenSource = null)
5451
{
55-
var name = Options.ConnectorName;
56-
var uri = new Uri($"ws://{Options.WebSocketHostAddress}");
52+
var invocatorContext = CreateInvocatorContext();
53+
var uri = new Uri($"ws://{invocatorContext.HostAddress}");
5754
_webSocket = new ClientWebSocket();
58-
_webSocket.Options.SetRequestHeader(SocketsConstants.ConnectorName, Options.ConnectorName);
55+
_webSocket.Options.SetRequestHeader(SocketsConstants.ConnectorName, invocatorContext.ConnectorName);
5956
try
6057
{
6158
CancellationToken token = cancellationTokenSource != null ? cancellationTokenSource.Token : CancellationToken.None;
6259
await _webSocket.ConnectAsync(uri, CancellationToken.None);
6360
}
6461
catch (Exception ex)
6562
{
66-
ProxyLogHelper.Log(_loggerFactory, Options, "Error", ex);
63+
ProxyLogHelper.Log(_loggerFactory, invocatorContext, "Error", ex);
6764
return;
6865
}
66+
6967
var receiverContext = new WebSocketReceiverContext
7068
{
7169
Compressor = _compressor,
72-
InvocatorRegistry = _invocatorRegistry,
70+
InvocatorContext = invocatorContext,
7371
LoggerFactory = _loggerFactory,
74-
Options = Options,
7572
WebSocket = _webSocket
7673
};
77-
var receiver = new WebSocketReceiver(receiverContext, Close, (connectionId) => {
74+
var receiver = new WebSocketReceiver(_serviceProvider, receiverContext, Close, (connectionId) => {
7875
_connectionId = connectionId;
7976
});
8077
await receiver.ReceiveAsync();
@@ -93,7 +90,8 @@ public async Task ConnectAsync(CancellationTokenSource cancellationTokenSource =
9390
}
9491
catch (Exception ex)
9592
{
96-
ProxyLogHelper.Log(_loggerFactory, Options, "Error", ex);
93+
var invocatorContext = CreateInvocatorContext();
94+
ProxyLogHelper.Log(_loggerFactory, invocatorContext, "Error", ex);
9795
}
9896

9997
if (WebSocketState == WebSocketState.Open)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using Microsoft.Extensions.Logging;
2+
using Microsoft.Extensions.Options;
3+
using NetCoreStack.WebSockets.Interfaces;
4+
using System;
5+
6+
namespace NetCoreStack.WebSockets.ProxyClient
7+
{
8+
public class ClientWebSocketConnectorOfInvocator<TInvocator> : ClientWebSocketConnector,
9+
IWebSocketConnector<TInvocator> where TInvocator : IClientWebSocketCommandInvocator
10+
{
11+
public ProxyOptions<TInvocator> Options { get; }
12+
13+
protected override InvocatorContext CreateInvocatorContext()
14+
{
15+
var name = Options.ConnectorName;
16+
var connectorKey = Options.ConnectorKey();
17+
var hostAddress = Options.WebSocketHostAddress;
18+
var invocatorType = Options.Invocator;
19+
return new InvocatorContext
20+
{
21+
ConnectorName = name,
22+
HostAddress = hostAddress,
23+
ConnectorKey = connectorKey,
24+
Invocator = invocatorType
25+
};
26+
}
27+
28+
public ClientWebSocketConnectorOfInvocator(IServiceProvider serviceProvider,
29+
IOptions<ProxyOptions<TInvocator>> options,
30+
IStreamCompressor compressor,
31+
ILoggerFactory loggerFactory)
32+
: base(serviceProvider, compressor, loggerFactory)
33+
{
34+
if (options == null)
35+
{
36+
throw new ArgumentNullException(nameof(options));
37+
}
38+
39+
Options = options.Value;
40+
}
41+
}
42+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
using System;
2+
3+
namespace NetCoreStack.WebSockets.ProxyClient
4+
{
5+
internal class ConnectorHostPair : IEquatable<ConnectorHostPair>
6+
{
7+
public string ConnectorName { get; }
8+
public string HostAddress { get; }
9+
public Type Invocator { get; }
10+
11+
public string Key { get; }
12+
13+
public ConnectorHostPair(string connectorname, string hostAddress, Type invocator)
14+
{
15+
ConnectorName = connectorname ?? throw new ArgumentNullException(nameof(connectorname));
16+
HostAddress = hostAddress ?? throw new ArgumentNullException(nameof(hostAddress));
17+
Invocator = invocator ?? throw new ArgumentNullException(nameof(invocator));
18+
19+
Key = $"{ConnectorName}|{HostAddress}|{Invocator.GetHashCode()}";
20+
}
21+
22+
public bool Equals(ConnectorHostPair other)
23+
{
24+
return other != null && other.Key.Equals(Key, StringComparison.OrdinalIgnoreCase);
25+
}
26+
27+
public override bool Equals(object obj)
28+
{
29+
return Equals(obj as ConnectorHostPair);
30+
}
31+
32+
public override int GetHashCode()
33+
{
34+
return Key.GetHashCode();
35+
}
36+
37+
public static bool operator ==(ConnectorHostPair left, ConnectorHostPair right)
38+
{
39+
if ((object)left == null)
40+
{
41+
return ((object)right == null);
42+
}
43+
else if ((object)right == null)
44+
{
45+
return ((object)left == null);
46+
}
47+
48+
return left.Equals(right);
49+
}
50+
51+
public static bool operator !=(ConnectorHostPair left, ConnectorHostPair right)
52+
{
53+
return !(left == right);
54+
}
55+
}
56+
}
Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,21 @@
1-
using Microsoft.Extensions.DependencyInjection;
2-
using Microsoft.Extensions.Logging;
3-
using System;
1+
using System;
2+
using System.Collections.Generic;
43
using System.Threading;
54
using System.Threading.Tasks;
65

76
namespace NetCoreStack.WebSockets.ProxyClient.Console
87
{
98
public static class ConsoleApplicationBuilderExtensions
109
{
11-
public static IServiceProvider UseProxyWebSocket(this IServiceProvider services, CancellationTokenSource cancellationTokenSource = null)
10+
public static IServiceProvider UseProxyWebSocket(this IServiceProvider serviceProvider, CancellationTokenSource cancellationTokenSource = null)
1211
{
13-
var loggerFactory = services.GetService<ILoggerFactory>();
14-
if (loggerFactory == null)
15-
loggerFactory = new LoggerFactory();
12+
IList<IWebSocketConnector> connectors = InvocatorFactory.GetConnectors(serviceProvider);
13+
foreach (var connector in connectors)
14+
{
15+
Task.Run(async () => await connector.ConnectAsync(cancellationTokenSource));
16+
}
1617

17-
var logger = loggerFactory.CreateLogger(nameof(ClientWebSocketConnector));
18-
var webSocketConnector = services.GetService<IWebSocketConnector>();
19-
if (webSocketConnector == null)
20-
throw new ArgumentNullException($"{nameof(webSocketConnector)} please try AddProxyWebSockets");
21-
22-
Task.Run(async () => await webSocketConnector.ConnectAsync(cancellationTokenSource));
23-
24-
return services;
18+
return serviceProvider;
2519
}
2620
}
2721
}

src/NetCoreStack.WebSockets.ProxyClient/IWebSocketConnector.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,13 @@ public interface IWebSocketConnector
88
{
99
string ConnectionId { get; }
1010
WebSocketState WebSocketState { get; }
11-
ProxyOptions Options { get; }
1211
Task ConnectAsync(CancellationTokenSource cancellationTokenSource);
1312
Task SendAsync(WebSocketMessageContext context);
1413
Task SendBinaryAsync(byte[] bytes);
1514
}
15+
16+
public interface IWebSocketConnector<THandler> : IWebSocketConnector where THandler : IClientWebSocketCommandInvocator
17+
{
18+
ProxyOptions<THandler> Options { get; }
19+
}
1620
}

0 commit comments

Comments
 (0)