/// <summary> /// Add a route. /// </summary> /// <param name="method">The HTTP method.</param> /// <param name="path">URL path, i.e. /path/to/resource.</param> /// <param name="handler">Method to invoke.</param> /// <param name="guid">Globally-unique identifier.</param> /// <param name="metadata">User-supplied metadata.</param> public void Add(HttpMethod method, string path, Func <HttpContext, Task> handler, string guid = null, object metadata = null) { if (String.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } if (handler == null) { throw new ArgumentNullException(nameof(handler)); } lock (_Lock) { ParameterRoute pr = new ParameterRoute(method, path, handler, guid, metadata); _Routes.Add(pr, handler); } }
private async Task AcceptConnections(CancellationToken token) { try { #region Process-Requests while (_HttpListener.IsListening) { if (_RequestCount >= _Settings.IO.MaxRequests) { await Task.Delay(100, token).ConfigureAwait(false); continue; } HttpListenerContext listenerCtx = await _HttpListener.GetContextAsync().ConfigureAwait(false); Interlocked.Increment(ref _RequestCount); HttpContext ctx = null; Task unawaited = Task.Run(async() => { DateTime startTime = DateTime.Now; try { #region Build-Context Events.HandleConnectionReceived(this, new ConnectionEventArgs( listenerCtx.Request.RemoteEndPoint.Address.ToString(), listenerCtx.Request.RemoteEndPoint.Port)); ctx = new HttpContext(listenerCtx, _Settings, Events); Events.HandleRequestReceived(this, new RequestEventArgs(ctx)); if (_Settings.Debug.Requests) { Events.Logger?.Invoke( _Header + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } Statistics.IncrementRequestCounter(ctx.Request.Method); Statistics.IncrementReceivedPayloadBytes(ctx.Request.ContentLength); #endregion #region Check-Access-Control if (!_Settings.AccessControl.Permit(ctx.Request.Source.IpAddress)) { Events.HandleRequestDenied(this, new RequestEventArgs(ctx)); if (_Settings.Debug.AccessControl) { Events.Logger?.Invoke(_Header + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " denied due to access control"); } listenerCtx.Response.StatusCode = 403; listenerCtx.Response.Close(); return; } #endregion #region Process-Preflight-Requests if (ctx.Request.Method == HttpMethod.OPTIONS) { if (_Routes.Preflight != null) { if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "preflight route for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } await _Routes.Preflight(ctx).ConfigureAwait(false); return; } } #endregion #region Pre-Routing-Handler bool terminate = false; if (_Routes.PreRouting != null) { terminate = await _Routes.PreRouting(ctx).ConfigureAwait(false); if (terminate) { if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "prerouting terminated connection for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } return; } } #endregion #region Content-Routes if (ctx.Request.Method == HttpMethod.GET || ctx.Request.Method == HttpMethod.HEAD) { ContentRoute cr = null; if (_Routes.Content.Match(ctx.Request.Url.RawWithoutQuery, out cr)) { if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "content route for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } ctx.RouteType = RouteTypeEnum.Content; ctx.Route = cr; await _Routes.ContentHandler.Process(ctx, token).ConfigureAwait(false); return; } } #endregion #region Static-Routes StaticRoute sr = null; Func <HttpContext, Task> handler = _Routes.Static.Match(ctx.Request.Method, ctx.Request.Url.RawWithoutQuery, out sr); if (handler != null) { if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "static route for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } ctx.RouteType = RouteTypeEnum.Static; ctx.Route = sr; await handler(ctx).ConfigureAwait(false); return; } #endregion #region Parameter-Routes ParameterRoute pr = null; Dictionary <string, string> parameters = null; handler = _Routes.Parameter.Match(ctx.Request.Method, ctx.Request.Url.RawWithoutQuery, out parameters, out pr); if (handler != null) { ctx.Request.Url.Parameters = new Dictionary <string, string>(parameters); if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "parameter route for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } ctx.RouteType = RouteTypeEnum.Parameter; ctx.Route = pr; await handler(ctx).ConfigureAwait(false); return; } #endregion #region Dynamic-Routes DynamicRoute dr = null; handler = _Routes.Dynamic.Match(ctx.Request.Method, ctx.Request.Url.RawWithoutQuery, out dr); if (handler != null) { if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "dynamic route for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } ctx.RouteType = RouteTypeEnum.Dynamic; ctx.Route = dr; await handler(ctx).ConfigureAwait(false); return; } #endregion #region Default-Route if (_Routes.Default != null) { if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "default route for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } ctx.RouteType = RouteTypeEnum.Default; await _Routes.Default(ctx).ConfigureAwait(false); return; } else { if (_Settings.Debug.Routing) { Events.Logger?.Invoke( _Header + "default route not found for " + ctx.Request.Source.IpAddress + ":" + ctx.Request.Source.Port + " " + ctx.Request.Method.ToString() + " " + ctx.Request.Url.RawWithoutQuery); } ctx.Response.StatusCode = 404; ctx.Response.ContentType = Pages.Default404Page.ContentType; await ctx.Response.Send(Pages.Default404Page.Content).ConfigureAwait(false); return; } #endregion } catch (Exception eInner) { ctx.Response.StatusCode = 500; ctx.Response.ContentType = Pages.Default500Page.ContentType; await ctx.Response.Send(Pages.Default500Page.Content).ConfigureAwait(false); Events.HandleExceptionEncountered(this, new ExceptionEventArgs(ctx, eInner)); } finally { Interlocked.Decrement(ref _RequestCount); if (ctx != null && ctx.Response != null && ctx.Response.ResponseSent) { Events.HandleResponseSent(this, new ResponseEventArgs(ctx, TotalMsFrom(startTime))); Statistics.IncrementSentPayloadBytes(ctx.Response.ContentLength); } } }, token); } #endregion } catch (TaskCanceledException) { } catch (OperationCanceledException) { } catch (HttpListenerException) { } catch (Exception e) { Events.HandleExceptionEncountered(this, new ExceptionEventArgs(null, e)); } finally { Events.HandleServerStopped(this, EventArgs.Empty); } }
/// <summary> /// Match a request method and URL to a handler method. /// </summary> /// <param name="method">The HTTP method.</param> /// <param name="path">URL path.</param> /// <param name="vals">Values extracted from the URL.</param> /// <param name="pr">Matching route.</param> /// <returns>True if match exists.</returns> public Func <HttpContext, Task> Match(HttpMethod method, string path, out Dictionary <string, string> vals, out ParameterRoute pr) { pr = null; vals = null; if (String.IsNullOrEmpty(path)) { throw new ArgumentNullException(nameof(path)); } string consolidatedPath = BuildConsolidatedPath(method, path); lock (_Lock) { foreach (KeyValuePair <ParameterRoute, Func <HttpContext, Task> > route in _Routes) { if (_Matcher.Match( consolidatedPath, BuildConsolidatedPath(route.Key.Method, route.Key.Path), out vals)) { pr = route.Key; return(route.Value); } } } return(null); }