source: binary-improvements2/SpaceWizards.HttpListener/src/System/Net/Managed/HttpEndPointListener.cs@ 385

Last change on this file since 385 was 385, checked in by alloc, 4 years ago

Fixed HttpListener running the IPv6 socket in dual mode on some platforms

File size: 14.0 KB
Line 
1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3
4//
5// System.Net.HttpEndPointListener
6//
7// Author:
8// Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
9//
10// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
11// Copyright (c) 2012 Xamarin, Inc. (http://xamarin.com)
12//
13// Permission is hereby granted, free of charge, to any person obtaining
14// a copy of this software and associated documentation files (the
15// "Software"), to deal in the Software without restriction, including
16// without limitation the rights to use, copy, modify, merge, publish,
17// distribute, sublicense, and/or sell copies of the Software, and to
18// permit persons to whom the Software is furnished to do so, subject to
19// the following conditions:
20//
21// The above copyright notice and this permission notice shall be
22// included in all copies or substantial portions of the Software.
23//
24// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31//
32
33// ReSharper disable ConditionIsAlwaysTrueOrFalse
34// ReSharper disable UnusedParameter.Local
35
36using System;
37using System.Collections.Generic;
38using System.Net;
39using System.Net.Sockets;
40using System.Security.Cryptography.X509Certificates;
41using System.Threading;
42
43namespace SpaceWizards.HttpListener
44{
45 internal sealed class HttpEndPointListener
46 {
47 private readonly HttpListener _listener;
48 private readonly IPEndPoint _endpoint;
49 private readonly Socket _socket;
50 private readonly Dictionary<HttpConnection, HttpConnection> _unregisteredConnections;
51 private Dictionary<ListenerPrefix, HttpListener> _prefixes;
52 private List<ListenerPrefix>? _unhandledPrefixes; // host = '*'
53 private List<ListenerPrefix>? _allPrefixes; // host = '+'
54 private X509Certificate? _cert;
55 private bool _secure;
56
57 public HttpEndPointListener(HttpListener listener, IPAddress addr, int port, bool secure)
58 {
59 _listener = listener;
60
61 if (secure)
62 {
63 _secure = secure;
64 _cert = _listener.LoadCertificateAndKey (addr, port);
65 }
66
67 _endpoint = new IPEndPoint(addr, port);
68 _socket = new Socket(addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
69
70 if (addr.AddressFamily == AddressFamily.InterNetworkV6) {
71 // Make sure that all platforms behave the same for IPv6 sockets (at least Linux by
72 // default has them in dual mode, so it would conflict with the explicit IPv4 socket)
73 _socket.DualMode = false;
74 }
75
76 _socket.Bind(_endpoint);
77 _socket.Listen(500);
78
79 SocketAsyncEventArgs args = new SocketAsyncEventArgs();
80 args.UserToken = this;
81 args.Completed += OnAccept;
82 Accept(args);
83
84 _prefixes = new Dictionary<ListenerPrefix, HttpListener>();
85 _unregisteredConnections = new Dictionary<HttpConnection, HttpConnection>();
86 }
87
88 internal HttpListener Listener
89 {
90 get { return _listener; }
91 }
92
93 private void Accept(SocketAsyncEventArgs e)
94 {
95 e.AcceptSocket = null;
96 bool asyn;
97 try
98 {
99 asyn = _socket.AcceptAsync(e);
100 }
101 catch (ObjectDisposedException)
102 {
103 // Once the listener starts running, it kicks off an async accept,
104 // and each subsequent accept initiates the next async accept. At
105 // point if the listener is torn down, the socket will be disposed
106 // and the AcceptAsync on the socket can fail with an ODE. Far from
107 // ideal, but for now just eat such exceptions.
108 return;
109 }
110 if (!asyn)
111 {
112 ProcessAccept(e);
113 }
114 }
115
116 private static void ProcessAccept(SocketAsyncEventArgs args)
117 {
118 HttpEndPointListener epl = (HttpEndPointListener)args.UserToken!;
119
120 Socket? accepted = args.SocketError == SocketError.Success ? args.AcceptSocket : null;
121 epl.Accept(args);
122
123 if (accepted == null)
124 return;
125
126 if (epl._secure && epl._cert == null)
127 {
128 accepted.Close();
129 return;
130 }
131
132 HttpConnection conn;
133 try
134 {
135 conn = new HttpConnection(accepted, epl, epl._secure, epl._cert!);
136 }
137 catch
138 {
139 accepted.Close();
140 return;
141 }
142
143 lock (epl._unregisteredConnections)
144 {
145 epl._unregisteredConnections[conn] = conn;
146 }
147 conn.BeginReadRequest();
148 }
149
150 private static void OnAccept(object? sender, SocketAsyncEventArgs e)
151 {
152 ProcessAccept(e);
153 }
154
155 internal void RemoveConnection(HttpConnection conn)
156 {
157 lock (_unregisteredConnections)
158 {
159 _unregisteredConnections.Remove(conn);
160 }
161 }
162
163 public bool BindContext(HttpListenerContext context)
164 {
165 HttpListenerRequest req = context.Request;
166 ListenerPrefix? prefix;
167 HttpListener? listener = SearchListener(req.Url, out prefix);
168 if (listener == null)
169 return false;
170
171 context._listener = listener;
172 context.Connection.Prefix = prefix;
173 return true;
174 }
175
176 public void UnbindContext(HttpListenerContext context)
177 {
178 if (context == null || context.Request == null)
179 return;
180
181 context._listener!.UnregisterContext(context);
182 }
183
184 private HttpListener? SearchListener(Uri? uri, out ListenerPrefix? prefix)
185 {
186 prefix = null;
187 if (uri == null)
188 return null;
189
190 string host = uri.Host;
191 int port = uri.Port;
192 string path = WebUtility.UrlDecode(uri.AbsolutePath);
193 string pathSlash = path[path.Length - 1] == '/' ? path : path + "/";
194
195 HttpListener? bestMatch = null;
196 int bestLength = -1;
197
198 if (host != null && host != "")
199 {
200 Dictionary<ListenerPrefix, HttpListener> localPrefixes = _prefixes;
201 foreach (ListenerPrefix p in localPrefixes.Keys)
202 {
203 string ppath = p.Path!;
204 if (ppath.Length < bestLength)
205 continue;
206
207 if (p.Host != host || p.Port != port)
208 continue;
209
210 if (path.StartsWith(ppath, StringComparison.Ordinal) || pathSlash.StartsWith(ppath, StringComparison.Ordinal))
211 {
212 bestLength = ppath.Length;
213 bestMatch = localPrefixes[p];
214 prefix = p;
215 }
216 }
217 if (bestLength != -1)
218 return bestMatch;
219 }
220
221 List<ListenerPrefix>? list = _unhandledPrefixes;
222 bestMatch = MatchFromList(host, path, list, out prefix);
223
224 if (path != pathSlash && bestMatch == null)
225 bestMatch = MatchFromList(host, pathSlash, list, out prefix);
226
227 if (bestMatch != null)
228 return bestMatch;
229
230 list = _allPrefixes;
231 bestMatch = MatchFromList(host, path, list, out prefix);
232
233 if (path != pathSlash && bestMatch == null)
234 bestMatch = MatchFromList(host, pathSlash, list, out prefix);
235
236 if (bestMatch != null)
237 return bestMatch;
238
239 return null;
240 }
241
242 private HttpListener? MatchFromList(string? host, string path, List<ListenerPrefix>? list, out ListenerPrefix? prefix)
243 {
244 prefix = null;
245 if (list == null)
246 return null;
247
248 HttpListener? bestMatch = null;
249 int bestLength = -1;
250
251 foreach (ListenerPrefix p in list)
252 {
253 string ppath = p.Path!;
254 if (ppath.Length < bestLength)
255 continue;
256
257 if (path.StartsWith(ppath, StringComparison.Ordinal))
258 {
259 bestLength = ppath.Length;
260 bestMatch = p._listener;
261 prefix = p;
262 }
263 }
264
265 return bestMatch;
266 }
267
268 private void AddSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
269 {
270 if (list == null)
271 return;
272
273 foreach (ListenerPrefix p in list)
274 {
275 if (p.Path == prefix.Path)
276 throw new HttpListenerException((int)HttpStatusCode.BadRequest, SR.Format(SR.net_listener_already, prefix));
277 }
278 list.Add(prefix);
279 }
280
281 private bool RemoveSpecial(List<ListenerPrefix> list, ListenerPrefix prefix)
282 {
283 if (list == null)
284 return false;
285
286 int c = list.Count;
287 for (int i = 0; i < c; i++)
288 {
289 ListenerPrefix p = list[i];
290 if (p.Path == prefix.Path)
291 {
292 list.RemoveAt(i);
293 return true;
294 }
295 }
296 return false;
297 }
298
299 private void CheckIfRemove()
300 {
301 if (_prefixes.Count > 0)
302 return;
303
304 List<ListenerPrefix>? list = _unhandledPrefixes;
305 if (list != null && list.Count > 0)
306 return;
307
308 list = _allPrefixes;
309 if (list != null && list.Count > 0)
310 return;
311
312 HttpEndPointManager.RemoveEndPoint(this, _endpoint);
313 }
314
315 public void Close()
316 {
317 _socket.Close();
318 lock (_unregisteredConnections)
319 {
320 // Clone the list because RemoveConnection can be called from Close
321 var connections = new List<HttpConnection>(_unregisteredConnections.Keys);
322
323 foreach (HttpConnection c in connections)
324 c.Close(true);
325 _unregisteredConnections.Clear();
326 }
327 }
328
329 public void AddPrefix(ListenerPrefix prefix, HttpListener listener)
330 {
331 List<ListenerPrefix>? current;
332 List<ListenerPrefix> future;
333 if (prefix.Host == "*")
334 {
335 do
336 {
337 current = _unhandledPrefixes;
338 future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
339 prefix._listener = listener;
340 AddSpecial(future, prefix);
341 } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
342 return;
343 }
344
345 if (prefix.Host == "+")
346 {
347 do
348 {
349 current = _allPrefixes;
350 future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
351 prefix._listener = listener;
352 AddSpecial(future, prefix);
353 } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
354 return;
355 }
356
357 Dictionary<ListenerPrefix, HttpListener> prefs, p2;
358 do
359 {
360 prefs = _prefixes;
361 if (prefs.ContainsKey(prefix))
362 {
363 throw new HttpListenerException((int)HttpStatusCode.BadRequest, SR.Format(SR.net_listener_already, prefix));
364 }
365 p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
366 p2[prefix] = listener;
367 } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
368 }
369
370 public void RemovePrefix(ListenerPrefix prefix, HttpListener listener)
371 {
372 List<ListenerPrefix>? current;
373 List<ListenerPrefix> future;
374 if (prefix.Host == "*")
375 {
376 do
377 {
378 current = _unhandledPrefixes;
379 future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
380 if (!RemoveSpecial(future, prefix))
381 break; // Prefix not found
382 } while (Interlocked.CompareExchange(ref _unhandledPrefixes, future, current) != current);
383
384 CheckIfRemove();
385 return;
386 }
387
388 if (prefix.Host == "+")
389 {
390 do
391 {
392 current = _allPrefixes;
393 future = current != null ? new List<ListenerPrefix>(current) : new List<ListenerPrefix>();
394 if (!RemoveSpecial(future, prefix))
395 break; // Prefix not found
396 } while (Interlocked.CompareExchange(ref _allPrefixes, future, current) != current);
397 CheckIfRemove();
398 return;
399 }
400
401 Dictionary<ListenerPrefix, HttpListener> prefs, p2;
402 do
403 {
404 prefs = _prefixes;
405 if (!prefs.ContainsKey(prefix))
406 break;
407
408 p2 = new Dictionary<ListenerPrefix, HttpListener>(prefs);
409 p2.Remove(prefix);
410 } while (Interlocked.CompareExchange(ref _prefixes, p2, prefs) != prefs);
411 CheckIfRemove();
412 }
413 }
414}
Note: See TracBrowser for help on using the repository browser.