-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
Server.cs
189 lines (157 loc) · 6.2 KB
/
Server.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
using SignalR.Hosting.Common;
using SignalR.Hosting.Self.Infrastructure;
using System;
using System.Diagnostics;
using System.Net;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace SignalR.Hosting.Self
{
public unsafe class Server : RoutingHost, IDisposable
{
private readonly string _url;
private readonly HttpListener _listener;
private CriticalHandle _requestQueueHandle;
private DisconnectHandler _disconnectHandler;
private readonly CancellationTokenSource _shutdownToken = new CancellationTokenSource();
public Action<HostContext> OnProcessRequest { get; set; }
/// <summary>
/// Initializes new instance of <see cref="Server"/>.
/// </summary>
/// <param name="url">The url to host the server on.</param>
public Server(string url)
: this(url, GlobalHost.DependencyResolver)
{
}
/// <summary>
/// Initializes new instance of <see cref="Server"/>.
/// </summary>
/// <param name="url">The url to host the server on.</param>
/// <param name="resolver">The dependency resolver for the server.</param>
public Server(string url, IDependencyResolver resolver)
: base(resolver)
{
_url = url.Replace("*", @".*?");
_listener = new HttpListener();
_listener.Prefixes.Add(url);
_disconnectHandler = new DisconnectHandler(_listener);
resolver.InitializePerformanceCounters(Process.GetCurrentProcess().GetUniqueInstanceName(_shutdownToken.Token), _shutdownToken.Token);
}
public AuthenticationSchemes AuthenticationSchemes
{
get { return _listener.AuthenticationSchemes; }
set { _listener.AuthenticationSchemes = value; }
}
/// <summary>
/// Starts the server connection.
/// </summary>
public void Start()
{
_listener.Start();
_disconnectHandler.Initialize();
ReceiveLoop();
}
/// <summary>
/// Stops the server.
/// </summary>
public void Stop()
{
_listener.Stop();
}
private void ReceiveLoop()
{
_listener.BeginGetContext(ar =>
{
HttpListenerContext context;
try
{
context = _listener.EndGetContext(ar);
}
catch (Exception)
{
return;
}
ReceiveLoop();
// Process the request async
ProcessRequestAsync(context).ContinueWith(task =>
{
if (task.IsFaulted)
{
Exception ex = task.Exception.GetBaseException();
context.Response.ServerError(ex).Catch();
Debug.WriteLine(ex.Message);
}
context.Response.CloseSafe();
});
}, null);
}
private Task ProcessRequestAsync(HttpListenerContext context)
{
try
{
Debug.WriteLine("Server: Incoming request to {0}.", context.Request.Url);
PersistentConnection connection;
string path = ResolvePath(context.Request.Url);
if (TryGetConnection(path, out connection))
{
// https://developer.mozilla.org/En/HTTP_Access_Control
string origin = context.Request.Headers["Origin"];
if (!String.IsNullOrEmpty(origin))
{
context.Response.AddHeader("Access-Control-Allow-Origin", origin);
context.Response.AddHeader("Access-Control-Allow-Credentials", "true");
}
var request = new HttpListenerRequestWrapper(context);
var response = new HttpListenerResponseWrapper(context.Response, _disconnectHandler.GetDisconnectToken(context));
var hostContext = new HostContext(request, response);
#if NET45
hostContext.Items[HostConstants.SupportsWebSockets] = Environment.OSVersion.Version.Major >= 6 && Environment.OSVersion.Version.Minor >= 2;
#endif
if (OnProcessRequest != null)
{
OnProcessRequest(hostContext);
}
#if DEBUG
hostContext.Items[HostConstants.DebugMode] = true;
#endif
hostContext.Items["System.Net.HttpListenerContext"] = context;
hostContext.Items[HostConstants.ShutdownToken] = _shutdownToken.Token;
// Initialize the connection
connection.Initialize(DependencyResolver, hostContext);
return connection.ProcessRequestAsync(hostContext);
}
if (path.Equals("/clientaccesspolicy.xml", StringComparison.InvariantCultureIgnoreCase))
{
return context.Response.WriteAsync(Resources.ClientAccessPolicyXml);
}
return context.Response.NotFound();
}
catch (Exception ex)
{
return TaskAsyncHelper.FromError(ex);
}
}
private string ResolvePath(Uri url)
{
string baseUrl = url.GetComponents(UriComponents.Scheme | UriComponents.HostAndPort | UriComponents.Path, UriFormat.SafeUnescaped);
Match match = Regex.Match(baseUrl, "^" + _url);
if (!match.Success)
{
throw new InvalidOperationException("Unable to resolve path");
}
string path = baseUrl.Substring(match.Value.Length);
if (!path.StartsWith("/"))
{
return "/" + path;
}
return path;
}
public void Dispose()
{
_shutdownToken.Cancel(throwOnFirstException: false);
}
}
}