| 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 | // ReSharper disable RedundantUsingDirective
|
|---|
| 5 | // ReSharper disable RedundantCast
|
|---|
| 6 | // ReSharper disable RedundantUnsafeContext
|
|---|
| 7 |
|
|---|
| 8 | using System;
|
|---|
| 9 | using System.Collections;
|
|---|
| 10 | using System.Collections.Generic;
|
|---|
| 11 | using System.IO;
|
|---|
| 12 | using System.Net;
|
|---|
| 13 | using System.Net.Security;
|
|---|
| 14 |
|
|---|
| 15 | namespace SpaceWizards.HttpListener
|
|---|
| 16 | {
|
|---|
| 17 | public sealed unsafe partial class HttpListener
|
|---|
| 18 | {
|
|---|
| 19 | public static bool IsSupported => true;
|
|---|
| 20 |
|
|---|
| 21 | private Dictionary<HttpListenerContext, HttpListenerContext> _listenerContexts = new Dictionary<HttpListenerContext, HttpListenerContext>();
|
|---|
| 22 | private List<HttpListenerContext> _contextQueue = new List<HttpListenerContext>();
|
|---|
| 23 | private List<ListenerAsyncResult> _asyncWaitQueue = new List<ListenerAsyncResult>();
|
|---|
| 24 | private Dictionary<HttpConnection, HttpConnection> _connections = new Dictionary<HttpConnection, HttpConnection>();
|
|---|
| 25 | private bool _unsafeConnectionNtlmAuthentication;
|
|---|
| 26 |
|
|---|
| 27 | public HttpListenerTimeoutManager TimeoutManager
|
|---|
| 28 | {
|
|---|
| 29 | get
|
|---|
| 30 | {
|
|---|
| 31 | CheckDisposed();
|
|---|
| 32 | return _timeoutManager;
|
|---|
| 33 | }
|
|---|
| 34 | }
|
|---|
| 35 |
|
|---|
| 36 | private void AddPrefixCore(string uriPrefix) => HttpEndPointManager.AddPrefix(uriPrefix, this);
|
|---|
| 37 |
|
|---|
| 38 | private void RemovePrefixCore(string uriPrefix) => HttpEndPointManager.RemovePrefix(uriPrefix, this);
|
|---|
| 39 |
|
|---|
| 40 | public void Start()
|
|---|
| 41 | {
|
|---|
| 42 | lock (_internalLock)
|
|---|
| 43 | {
|
|---|
| 44 | try
|
|---|
| 45 | {
|
|---|
| 46 | CheckDisposed();
|
|---|
| 47 | if (_state == State.Started)
|
|---|
| 48 | return;
|
|---|
| 49 |
|
|---|
| 50 | HttpEndPointManager.AddListener(this);
|
|---|
| 51 |
|
|---|
| 52 | _state = State.Started;
|
|---|
| 53 | }
|
|---|
| 54 | catch (Exception exception)
|
|---|
| 55 | {
|
|---|
| 56 | _state = State.Closed;
|
|---|
| 57 | if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Start {exception}");
|
|---|
| 58 | throw;
|
|---|
| 59 | }
|
|---|
| 60 | }
|
|---|
| 61 | }
|
|---|
| 62 |
|
|---|
| 63 | public bool UnsafeConnectionNtlmAuthentication
|
|---|
| 64 | {
|
|---|
| 65 | // NTLM isn't currently supported, so this is a nop anyway and we can just roundtrip the value
|
|---|
| 66 | get => _unsafeConnectionNtlmAuthentication;
|
|---|
| 67 | set
|
|---|
| 68 | {
|
|---|
| 69 | CheckDisposed();
|
|---|
| 70 | _unsafeConnectionNtlmAuthentication = value;
|
|---|
| 71 | }
|
|---|
| 72 | }
|
|---|
| 73 |
|
|---|
| 74 | public void Stop()
|
|---|
| 75 | {
|
|---|
| 76 |
|
|---|
| 77 | lock (_internalLock)
|
|---|
| 78 | {
|
|---|
| 79 | try
|
|---|
| 80 | {
|
|---|
| 81 | CheckDisposed();
|
|---|
| 82 | if (_state == State.Stopped)
|
|---|
| 83 | {
|
|---|
| 84 | return;
|
|---|
| 85 | }
|
|---|
| 86 |
|
|---|
| 87 | Close(false);
|
|---|
| 88 | }
|
|---|
| 89 | catch (Exception exception)
|
|---|
| 90 | {
|
|---|
| 91 | if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Stop {exception}");
|
|---|
| 92 | throw;
|
|---|
| 93 | }
|
|---|
| 94 | finally
|
|---|
| 95 | {
|
|---|
| 96 | _state = State.Stopped;
|
|---|
| 97 | }
|
|---|
| 98 | }
|
|---|
| 99 | }
|
|---|
| 100 |
|
|---|
| 101 | public void Abort()
|
|---|
| 102 | {
|
|---|
| 103 |
|
|---|
| 104 | lock (_internalLock)
|
|---|
| 105 | {
|
|---|
| 106 | try
|
|---|
| 107 | {
|
|---|
| 108 | if (_state == State.Closed)
|
|---|
| 109 | {
|
|---|
| 110 | return;
|
|---|
| 111 | }
|
|---|
| 112 |
|
|---|
| 113 | // Just detach and free resources. Don't call Stop (which may throw).
|
|---|
| 114 | if (_state == State.Started)
|
|---|
| 115 | {
|
|---|
| 116 | Close(true);
|
|---|
| 117 | }
|
|---|
| 118 | }
|
|---|
| 119 | catch (Exception exception)
|
|---|
| 120 | {
|
|---|
| 121 | if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Abort {exception}");
|
|---|
| 122 | throw;
|
|---|
| 123 | }
|
|---|
| 124 | finally
|
|---|
| 125 | {
|
|---|
| 126 | _state = State.Closed;
|
|---|
| 127 | }
|
|---|
| 128 | }
|
|---|
| 129 | }
|
|---|
| 130 |
|
|---|
| 131 | private void Dispose()
|
|---|
| 132 | {
|
|---|
| 133 |
|
|---|
| 134 | lock (_internalLock)
|
|---|
| 135 | {
|
|---|
| 136 | try
|
|---|
| 137 | {
|
|---|
| 138 | if (_state == State.Closed)
|
|---|
| 139 | {
|
|---|
| 140 | return;
|
|---|
| 141 | }
|
|---|
| 142 |
|
|---|
| 143 | Close(true);
|
|---|
| 144 | }
|
|---|
| 145 | catch (Exception exception)
|
|---|
| 146 | {
|
|---|
| 147 | if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(this, $"Dispose {exception}");
|
|---|
| 148 | throw;
|
|---|
| 149 | }
|
|---|
| 150 | finally
|
|---|
| 151 | {
|
|---|
| 152 | _state = State.Closed;
|
|---|
| 153 | }
|
|---|
| 154 | }
|
|---|
| 155 | }
|
|---|
| 156 |
|
|---|
| 157 | private void Close(bool force)
|
|---|
| 158 | {
|
|---|
| 159 | CheckDisposed();
|
|---|
| 160 | HttpEndPointManager.RemoveListener(this);
|
|---|
| 161 | Cleanup(force);
|
|---|
| 162 | }
|
|---|
| 163 |
|
|---|
| 164 | internal void UnregisterContext(HttpListenerContext context)
|
|---|
| 165 | {
|
|---|
| 166 | lock ((_listenerContexts as ICollection).SyncRoot)
|
|---|
| 167 | {
|
|---|
| 168 | _listenerContexts.Remove(context);
|
|---|
| 169 | }
|
|---|
| 170 | lock ((_contextQueue as ICollection).SyncRoot)
|
|---|
| 171 | {
|
|---|
| 172 | int idx = _contextQueue.IndexOf(context);
|
|---|
| 173 | if (idx >= 0)
|
|---|
| 174 | _contextQueue.RemoveAt(idx);
|
|---|
| 175 | }
|
|---|
| 176 | }
|
|---|
| 177 |
|
|---|
| 178 | internal void AddConnection(HttpConnection cnc)
|
|---|
| 179 | {
|
|---|
| 180 | lock ((_connections as ICollection).SyncRoot)
|
|---|
| 181 | {
|
|---|
| 182 | _connections[cnc] = cnc;
|
|---|
| 183 | }
|
|---|
| 184 | }
|
|---|
| 185 |
|
|---|
| 186 | internal void RemoveConnection(HttpConnection cnc)
|
|---|
| 187 | {
|
|---|
| 188 | lock ((_connections as ICollection).SyncRoot)
|
|---|
| 189 | {
|
|---|
| 190 | _connections.Remove(cnc);
|
|---|
| 191 | }
|
|---|
| 192 | }
|
|---|
| 193 |
|
|---|
| 194 | internal void RegisterContext(HttpListenerContext context)
|
|---|
| 195 | {
|
|---|
| 196 | lock ((_listenerContexts as ICollection).SyncRoot)
|
|---|
| 197 | {
|
|---|
| 198 | _listenerContexts[context] = context;
|
|---|
| 199 | }
|
|---|
| 200 |
|
|---|
| 201 | ListenerAsyncResult? ares = null;
|
|---|
| 202 | lock ((_asyncWaitQueue as ICollection).SyncRoot)
|
|---|
| 203 | {
|
|---|
| 204 | if (_asyncWaitQueue.Count == 0)
|
|---|
| 205 | {
|
|---|
| 206 | lock ((_contextQueue as ICollection).SyncRoot)
|
|---|
| 207 | _contextQueue.Add(context);
|
|---|
| 208 | }
|
|---|
| 209 | else
|
|---|
| 210 | {
|
|---|
| 211 | ares = _asyncWaitQueue[0];
|
|---|
| 212 | _asyncWaitQueue.RemoveAt(0);
|
|---|
| 213 | }
|
|---|
| 214 | }
|
|---|
| 215 |
|
|---|
| 216 | if (ares != null)
|
|---|
| 217 | {
|
|---|
| 218 | ares.Complete(context);
|
|---|
| 219 | }
|
|---|
| 220 | }
|
|---|
| 221 |
|
|---|
| 222 | private void Cleanup(bool close_existing)
|
|---|
| 223 | {
|
|---|
| 224 | lock ((_listenerContexts as ICollection).SyncRoot)
|
|---|
| 225 | {
|
|---|
| 226 | if (close_existing)
|
|---|
| 227 | {
|
|---|
| 228 | // Need to copy this since closing will call UnregisterContext
|
|---|
| 229 | ICollection keys = _listenerContexts.Keys;
|
|---|
| 230 | var all = new HttpListenerContext[keys.Count];
|
|---|
| 231 | keys.CopyTo(all, 0);
|
|---|
| 232 | _listenerContexts.Clear();
|
|---|
| 233 | for (int i = all.Length - 1; i >= 0; i--)
|
|---|
| 234 | all[i].Connection.Close(true);
|
|---|
| 235 | }
|
|---|
| 236 |
|
|---|
| 237 | lock ((_connections as ICollection).SyncRoot)
|
|---|
| 238 | {
|
|---|
| 239 | ICollection keys = _connections.Keys;
|
|---|
| 240 | var conns = new HttpConnection[keys.Count];
|
|---|
| 241 | keys.CopyTo(conns, 0);
|
|---|
| 242 | _connections.Clear();
|
|---|
| 243 | for (int i = conns.Length - 1; i >= 0; i--)
|
|---|
| 244 | conns[i].Close(true);
|
|---|
| 245 | }
|
|---|
| 246 | lock ((_contextQueue as ICollection).SyncRoot)
|
|---|
| 247 | {
|
|---|
| 248 | var ctxs = (HttpListenerContext[])_contextQueue.ToArray();
|
|---|
| 249 | _contextQueue.Clear();
|
|---|
| 250 | for (int i = ctxs.Length - 1; i >= 0; i--)
|
|---|
| 251 | ctxs[i].Connection.Close(true);
|
|---|
| 252 | }
|
|---|
| 253 |
|
|---|
| 254 | lock ((_asyncWaitQueue as ICollection).SyncRoot)
|
|---|
| 255 | {
|
|---|
| 256 | Exception exc = new ObjectDisposedException("listener");
|
|---|
| 257 | foreach (ListenerAsyncResult ares in _asyncWaitQueue)
|
|---|
| 258 | {
|
|---|
| 259 | ares.Complete(exc);
|
|---|
| 260 | }
|
|---|
| 261 | _asyncWaitQueue.Clear();
|
|---|
| 262 | }
|
|---|
| 263 | }
|
|---|
| 264 | }
|
|---|
| 265 |
|
|---|
| 266 | private HttpListenerContext? GetContextFromQueue()
|
|---|
| 267 | {
|
|---|
| 268 | lock ((_contextQueue as ICollection).SyncRoot)
|
|---|
| 269 | {
|
|---|
| 270 | if (_contextQueue.Count == 0)
|
|---|
| 271 | {
|
|---|
| 272 | return null;
|
|---|
| 273 | }
|
|---|
| 274 |
|
|---|
| 275 | HttpListenerContext context = _contextQueue[0];
|
|---|
| 276 | _contextQueue.RemoveAt(0);
|
|---|
| 277 |
|
|---|
| 278 | return context;
|
|---|
| 279 | }
|
|---|
| 280 | }
|
|---|
| 281 |
|
|---|
| 282 | public IAsyncResult BeginGetContext(AsyncCallback? callback, object? state)
|
|---|
| 283 | {
|
|---|
| 284 | CheckDisposed();
|
|---|
| 285 | if (_state != State.Started)
|
|---|
| 286 | {
|
|---|
| 287 | throw new InvalidOperationException(SR.Format(SR.net_listener_mustcall, "Start()"));
|
|---|
| 288 | }
|
|---|
| 289 |
|
|---|
| 290 | ListenerAsyncResult ares = new ListenerAsyncResult(this, callback, state);
|
|---|
| 291 |
|
|---|
| 292 | // lock wait_queue early to avoid race conditions
|
|---|
| 293 | lock ((_asyncWaitQueue as ICollection).SyncRoot)
|
|---|
| 294 | {
|
|---|
| 295 | lock ((_contextQueue as ICollection).SyncRoot)
|
|---|
| 296 | {
|
|---|
| 297 | HttpListenerContext? ctx = GetContextFromQueue();
|
|---|
| 298 | if (ctx != null)
|
|---|
| 299 | {
|
|---|
| 300 | ares.Complete(ctx, true);
|
|---|
| 301 | return ares;
|
|---|
| 302 | }
|
|---|
| 303 | }
|
|---|
| 304 |
|
|---|
| 305 | _asyncWaitQueue.Add(ares);
|
|---|
| 306 | }
|
|---|
| 307 |
|
|---|
| 308 | return ares;
|
|---|
| 309 | }
|
|---|
| 310 |
|
|---|
| 311 | public HttpListenerContext EndGetContext(IAsyncResult asyncResult)
|
|---|
| 312 | {
|
|---|
| 313 | CheckDisposed();
|
|---|
| 314 | if (asyncResult == null)
|
|---|
| 315 | {
|
|---|
| 316 | throw new ArgumentNullException(nameof(asyncResult));
|
|---|
| 317 | }
|
|---|
| 318 |
|
|---|
| 319 | ListenerAsyncResult? ares = asyncResult as ListenerAsyncResult;
|
|---|
| 320 | if (ares == null || !ReferenceEquals(this, ares._parent))
|
|---|
| 321 | {
|
|---|
| 322 | throw new ArgumentException(SR.net_io_invalidasyncresult, nameof(asyncResult));
|
|---|
| 323 | }
|
|---|
| 324 | if (ares._endCalled)
|
|---|
| 325 | {
|
|---|
| 326 | throw new InvalidOperationException(SR.Format(SR.net_io_invalidendcall, nameof(EndGetContext)));
|
|---|
| 327 | }
|
|---|
| 328 |
|
|---|
| 329 | ares._endCalled = true;
|
|---|
| 330 |
|
|---|
| 331 | if (!ares.IsCompleted)
|
|---|
| 332 | ares.AsyncWaitHandle.WaitOne();
|
|---|
| 333 |
|
|---|
| 334 | lock ((_asyncWaitQueue as ICollection).SyncRoot)
|
|---|
| 335 | {
|
|---|
| 336 | int idx = _asyncWaitQueue.IndexOf(ares);
|
|---|
| 337 | if (idx >= 0)
|
|---|
| 338 | _asyncWaitQueue.RemoveAt(idx);
|
|---|
| 339 | }
|
|---|
| 340 |
|
|---|
| 341 | HttpListenerContext context = ares.GetContext()!;
|
|---|
| 342 | context.ParseAuthentication(context.AuthenticationSchemes);
|
|---|
| 343 | return context;
|
|---|
| 344 | }
|
|---|
| 345 |
|
|---|
| 346 | internal AuthenticationSchemes SelectAuthenticationScheme(HttpListenerContext context)
|
|---|
| 347 | {
|
|---|
| 348 | return AuthenticationSchemeSelectorDelegate != null ? AuthenticationSchemeSelectorDelegate(context.Request) : _authenticationScheme;
|
|---|
| 349 | }
|
|---|
| 350 |
|
|---|
| 351 | public HttpListenerContext GetContext()
|
|---|
| 352 | {
|
|---|
| 353 | CheckDisposed();
|
|---|
| 354 | if (_state == State.Stopped)
|
|---|
| 355 | {
|
|---|
| 356 | throw new InvalidOperationException(SR.Format(SR.net_listener_mustcall, "Start()"));
|
|---|
| 357 | }
|
|---|
| 358 | if (_prefixes.Count == 0)
|
|---|
| 359 | {
|
|---|
| 360 | throw new InvalidOperationException(SR.Format(SR.net_listener_mustcall, "AddPrefix()"));
|
|---|
| 361 | }
|
|---|
| 362 |
|
|---|
| 363 | ListenerAsyncResult ares = (ListenerAsyncResult)BeginGetContext(null, null);
|
|---|
| 364 | ares._inGet = true;
|
|---|
| 365 |
|
|---|
| 366 | return EndGetContext(ares);
|
|---|
| 367 | }
|
|---|
| 368 | }
|
|---|
| 369 | }
|
|---|