Skip to content

Commit cb7d408

Browse files
gedemgedem
authored andcommitted
Persistent connection ID for browsers and proxies
1 parent 6ddb26b commit cb7d408

File tree

15 files changed

+244
-79
lines changed

15 files changed

+244
-79
lines changed

src/NetCoreStack.WebSockets/ConnectionManager.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,11 @@ await transport.WebSocket.SendAsync(descriptor.Segments,
102102
else
103103
{
104104
// Only text messages
105-
_lifetimeManager.AddQueue(new MessageHolder { ConnectionId = transport.ConnectionId, Segments = descriptor.Segments });
105+
_lifetimeManager.AddQueue(transport.ConnectionId, new MessageHolder
106+
{
107+
Segments = descriptor.Segments,
108+
KeepTime = DateTime.Now.AddMinutes(3)
109+
});
106110
}
107111
}
108112

@@ -140,6 +144,17 @@ public async Task ConnectAsync(WebSocket webSocket, string connectionId, string
140144
if (Connections.TryGetValue(connectionId, out transport))
141145
{
142146
transport.ReConnect(webSocket);
147+
List<MessageHolder> messages = _lifetimeManager.TryDequeue(connectionId);
148+
foreach (var message in messages)
149+
{
150+
await SendAsync(transport, new WebSocketMessageDescriptor
151+
{
152+
MessageType = WebSocketMessageType.Text,
153+
Segments = message.Segments,
154+
EndOfMessage = true,
155+
IsQueue = true,
156+
});
157+
}
143158
}
144159
else
145160
{

src/NetCoreStack.WebSockets/Internal/MessageHolder.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ namespace NetCoreStack.WebSockets.Internal
44
{
55
public class MessageHolder
66
{
7-
public string ConnectionId { get; set; }
87
public ArraySegment<byte> Segments { get; set; }
8+
9+
public DateTime KeepTime { get; set; }
910
}
1011
}
Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,57 @@
1-
using System.Collections.Concurrent;
1+
using System;
2+
using System.Collections.Concurrent;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading;
26

37
namespace NetCoreStack.WebSockets.Internal
48
{
59
public class TransportLifetimeManager
610
{
7-
public ConcurrentQueue<MessageHolder> Queue { get; }
8-
11+
private readonly Timer _timer = null;
12+
private readonly ConcurrentDictionary<string, List<MessageHolder>> _queueDict;
913
public TransportLifetimeManager()
1014
{
11-
Queue = new ConcurrentQueue<MessageHolder>();
15+
_timer = new Timer(CheckQueue, null, new TimeSpan(0, 0, 5), new TimeSpan(0, 1, 0));
16+
_queueDict = new ConcurrentDictionary<string, List<MessageHolder>>();
17+
}
18+
19+
public void CheckQueue(object state)
20+
{
21+
var now = DateTime.Now;
22+
foreach (KeyValuePair<string, List<MessageHolder>> entry in _queueDict)
23+
{
24+
List<MessageHolder> timeoutItems = entry.Value.Where(p => p.KeepTime < now).ToList();
25+
entry.Value.RemoveAll(p => p.KeepTime < now);
26+
}
1227
}
1328

14-
public void AddQueue(MessageHolder holder)
29+
public void AddQueue(string connectionId, MessageHolder holder)
1530
{
16-
Queue.Enqueue(holder);
31+
List<MessageHolder> queue = null;
32+
if (_queueDict.TryGetValue(connectionId, out queue))
33+
{
34+
queue.Add(holder);
35+
}
36+
else
37+
{
38+
var items = new List<MessageHolder>(new List<MessageHolder> { holder });
39+
_queueDict.TryAdd(connectionId, items);
40+
}
41+
}
42+
43+
public List<MessageHolder> TryDequeue(string connectionId)
44+
{
45+
List<MessageHolder> queue = null;
46+
if (_queueDict.TryGetValue(connectionId, out queue))
47+
{
48+
var now = DateTime.Now;
49+
var items = queue.Where(x => x.KeepTime > now).ToList();
50+
queue.RemoveAll(x => x.KeepTime > now);
51+
return items;
52+
}
53+
54+
return new List<MessageHolder>();
1755
}
1856
}
1957
}

src/NetCoreStack.WebSockets/Internal/WebSocketMessageDescriptor.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class WebSocketMessageDescriptor
99
public ArraySegment<byte> Segments { get; set; }
1010
public WebSocketMessageType MessageType { get; set; }
1111
public bool EndOfMessage { get; set; }
12+
public bool IsQueue { get; set; }
1213
public CancellationToken CancellationToken { get; set; }
1314

1415
public WebSocketMessageDescriptor()

test/AutobahnTestApp/Program.cs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,38 @@
1-
using Microsoft.AspNetCore.Builder;
2-
using Microsoft.AspNetCore.Hosting;
1+
using System;
32
using System.IO;
3+
using Microsoft.AspNetCore.Hosting;
4+
using Microsoft.Extensions.Configuration;
45

56
namespace AutobahnTestApp
67
{
78
public class Program
89
{
910
public static void Main(string[] args)
1011
{
11-
var host = new WebHostBuilder()
12-
.UseKestrel()
12+
var config = new ConfigurationBuilder()
13+
.AddCommandLine(args)
14+
.Build();
15+
16+
var builder = new WebHostBuilder()
17+
.UseConfiguration(config)
1318
.UseContentRoot(Directory.GetCurrentDirectory())
1419
.UseIISIntegration()
15-
.UseStartup<Startup>()
16-
.Build();
20+
.UseStartup<Startup>();
21+
22+
if (string.Equals(builder.GetSetting("server"), "Microsoft.AspNetCore.Server.WebListener", System.StringComparison.Ordinal))
23+
{
24+
builder.UseWebListener();
25+
}
26+
else
27+
{
28+
builder.UseKestrel(options =>
29+
{
30+
var certPath = Path.Combine(AppContext.BaseDirectory, "TestResources", "testCert.pfx");
31+
options.UseHttps(certPath, "testPassword");
32+
});
33+
}
1734

35+
var host = builder.Build();
1836
host.Run();
1937
}
2038
}

test/AutobahnTestApp/Properties/launchSettings.json

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,39 @@
33
"windowsAuthentication": false,
44
"anonymousAuthentication": true,
55
"iisExpress": {
6-
"applicationUrl": "http://localhost:3596/",
7-
"sslPort": 0
6+
"applicationUrl": "http://localhost:6155/",
7+
"sslPort": 44371
88
}
99
},
1010
"profiles": {
1111
"IIS Express": {
1212
"commandName": "IISExpress",
1313
"launchBrowser": true,
14-
"launchUrl": "api/values",
1514
"environmentVariables": {
16-
"ASPNETCORE_ENVIRONMENT": "Development"
15+
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
1716
}
1817
},
1918
"AutobahnTestApp": {
2019
"commandName": "Project",
2120
"launchBrowser": true,
22-
"launchUrl": "http://localhost:5000/api/values",
21+
"launchUrl": "http://localhost:5000",
2322
"environmentVariables": {
24-
"ASPNETCORE_ENVIRONMENT": "Development"
23+
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
24+
}
25+
},
26+
"AutobahnTestApp (SSL)": {
27+
"commandName": "Project",
28+
"launchBrowser": true,
29+
"launchUrl": "https://localhost:5443",
30+
"environmentVariables": {
31+
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
32+
}
33+
},
34+
"WebListener": {
35+
"commandName": "Project",
36+
"commandLineArgs": "--server Microsoft.AspNetCore.Server.WebListener",
37+
"environmentVariables": {
38+
"ASPNETCORE_ENVIRONMENT": "ManagedSockets"
2539
}
2640
}
2741
}

test/AutobahnTestApp/Startup.cs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,58 @@
11
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
2+
using System.Net.WebSockets;
3+
using System.Threading;
44
using System.Threading.Tasks;
55
using Microsoft.AspNetCore.Builder;
66
using Microsoft.AspNetCore.Hosting;
7-
using Microsoft.Extensions.Configuration;
8-
using Microsoft.Extensions.DependencyInjection;
7+
using Microsoft.AspNetCore.Http;
8+
using Microsoft.AspNetCore.Http.Features;
99
using Microsoft.Extensions.Logging;
1010

1111
namespace AutobahnTestApp
1212
{
1313
public class Startup
1414
{
15-
public Startup(IHostingEnvironment env)
15+
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
16+
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
1617
{
17-
var builder = new ConfigurationBuilder()
18-
.SetBasePath(env.ContentRootPath)
19-
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
20-
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
21-
.AddEnvironmentVariables();
22-
Configuration = builder.Build();
23-
}
18+
if (!env.IsEnvironment("NativeSockets"))
19+
{
20+
// Register a middleware that disables the server-provided WebSockets feature
21+
app.Use((context, next) =>
22+
{
23+
context.Features.Set<IHttpWebSocketFeature>(null);
24+
return next();
25+
});
26+
}
27+
app.UseWebSockets();
2428

25-
public IConfigurationRoot Configuration { get; }
29+
app.Use(async (context, next) =>
30+
{
31+
if (context.WebSockets.IsWebSocketRequest)
32+
{
33+
var webSocket = await context.WebSockets.AcceptWebSocketAsync();
34+
await Echo(webSocket);
35+
}
36+
else
37+
{
38+
var wsScheme = context.Request.IsHttps ? "wss" : "ws";
39+
var wsUrl = $"{wsScheme}://{context.Request.Host.Host}:{context.Request.Host.Port}{context.Request.Path}";
40+
await context.Response.WriteAsync($"Ready to accept a WebSocket request at: {wsUrl}");
41+
}
42+
});
2643

27-
// This method gets called by the runtime. Use this method to add services to the container.
28-
public void ConfigureServices(IServiceCollection services)
29-
{
30-
// Add framework services.
31-
services.AddMvc();
3244
}
3345

34-
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
35-
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
46+
private async Task Echo(WebSocket webSocket)
3647
{
37-
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
38-
loggerFactory.AddDebug();
39-
40-
app.UseMvc();
48+
var buffer = new byte[1024 * 4];
49+
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
50+
while (!result.CloseStatus.HasValue)
51+
{
52+
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);
53+
result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
54+
}
55+
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
4156
}
4257
}
4358
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
The password for this is 'testPassword'
2+
3+
DO NOT EVER TRUST THIS CERT. The private key for it is publicly released.

test/AutobahnTestApp/project.json

Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
{
22
"dependencies": {
3-
"Microsoft.AspNetCore.Mvc": "1.1.0",
4-
"Microsoft.AspNetCore.Hosting": "1.1.0",
5-
"Microsoft.AspNetCore.Hosting.Abstractions": "1.1.0",
6-
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
7-
"Microsoft.Extensions.Configuration.FileExtensions": "1.1.0",
3+
"Microsoft.AspNetCore.Diagnostics": "1.1.0",
84
"Microsoft.AspNetCore.Server.IISIntegration": "1.1.0",
9-
"Microsoft.Extensions.Logging.Debug": "1.1.0",
5+
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0",
6+
"Microsoft.AspNetCore.Server.Kestrel.Https": "1.1.0",
7+
"Microsoft.AspNetCore.Server.WebListener": "1.1.0",
8+
"Microsoft.AspNetCore.WebSockets": "1.0.0",
9+
"Microsoft.Extensions.Configuration.CommandLine": "1.1.0",
1010
"Microsoft.Extensions.Logging.Console": "1.1.0",
11-
"Microsoft.Extensions.Configuration.Json": "1.1.0",
1211
"Microsoft.NETCore.App": {
1312
"version": "1.1.0",
1413
"type": "platform"
1514
}
1615
},
17-
1816
"tools": {
1917
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.1.0-preview4-final"
2018
},
21-
2219
"frameworks": {
2320
"netcoreapp1.1": {
2421
"imports": [
@@ -27,28 +24,27 @@
2724
]
2825
}
2926
},
30-
3127
"buildOptions": {
3228
"emitEntryPoint": true,
33-
"preserveCompilationContext": true
29+
"copyToOutput": [
30+
"TestResources/testCert.pfx"
31+
]
3432
},
35-
3633
"runtimeOptions": {
3734
"configProperties": {
3835
"System.GC.Server": true
3936
}
4037
},
41-
4238
"publishOptions": {
4339
"include": [
4440
"wwwroot",
45-
"**/*.cshtml",
46-
"appsettings.json",
47-
"web.config"
41+
"web.config",
42+
"TestResources/testCert.pfx"
4843
]
4944
},
50-
5145
"scripts": {
52-
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
46+
"postpublish": [
47+
"dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%"
48+
]
5349
}
54-
}
50+
}

0 commit comments

Comments
 (0)