| | | 1 | | /******************************************************************************** |
| | | 2 | | * Router.cs * |
| | | 3 | | * * |
| | | 4 | | * Author: Denes Solti * |
| | | 5 | | ********************************************************************************/ |
| | | 6 | | using System; |
| | | 7 | | using System.Diagnostics.CodeAnalysis; |
| | | 8 | | using System.Linq.Expressions; |
| | | 9 | | using System.Net.Http; |
| | | 10 | | using System.Reflection; |
| | | 11 | | using System.Runtime.CompilerServices; |
| | | 12 | | using System.Threading; |
| | | 13 | | using System.Threading.Tasks; |
| | | 14 | | |
| | | 15 | | namespace NanoRoute |
| | | 16 | | { |
| | | 17 | | using Internals; |
| | | 18 | | |
| | | 19 | | /// <summary> |
| | | 20 | | /// Executes the route matching pipeline built by <see cref="RouteScopeBuilder"/>. |
| | | 21 | | /// </summary> |
| | | 22 | | /// <remarks> |
| | | 23 | | /// A router is created from a builder snapshot. Matching walks the configured route tree, attaches bound parameters |
| | | 24 | | /// handlers in order until one returns a response without delegating further. |
| | | 25 | | /// </remarks> |
| | | 26 | | /// <example> |
| | | 27 | | /// <code> |
| | | 28 | | /// Router router = MyRouter |
| | | 29 | | /// .CreateBuilder() |
| | | 30 | | /// .AddDefaultValueParsers() |
| | | 31 | | /// .AddHandler("GET", "/health/", (context, _) => Results.Ok()) |
| | | 32 | | /// .CreateRouter(); |
| | | 33 | | /// </code> |
| | | 34 | | /// </example> |
| | | 35 | | public abstract class Router |
| | | 36 | | { |
| | | 37 | | private readonly RequestPipeline _requestPipeline; |
| | | 38 | | |
| | | 39 | | /// <summary> |
| | | 40 | | /// The request property key that stores the trace identifier associated with the current request. |
| | | 41 | | /// </summary> |
| | | 42 | | /// <example> |
| | | 43 | | /// <code> |
| | | 44 | | /// request.Properties[Router.TraceIdName] = traceId; |
| | | 45 | | /// </code> |
| | | 46 | | /// </example> |
| | | 47 | | public const string TraceIdName = "TraceId"; |
| | | 48 | | |
| | | 49 | | /// <summary> |
| | | 50 | | /// The request property key that stores the original transport-specific request object. |
| | | 51 | | /// </summary> |
| | | 52 | | /// <example> |
| | | 53 | | /// <code> |
| | | 54 | | /// request.Properties[Router.OriginalRequestName] = listenerRequest; |
| | | 55 | | /// </code> |
| | | 56 | | /// </example> |
| | | 57 | | public const string OriginalRequestName = "OriginalRequest"; |
| | | 58 | | |
| | | 59 | | /// <summary> |
| | | 60 | | /// Creates a new <see cref="Router"/> instance. |
| | | 61 | | /// </summary> |
| | | 62 | | /// <param name="routeScopeBuilder">The builder scope whose registered routes are captured by the router.</param |
| | | 63 | | /// <param name="config">The configuration assigned to the router instance.</param> |
| | | 64 | | /// <exception cref="ArgumentNullException"> |
| | | 65 | | /// Thrown when <paramref name="routeScopeBuilder"/> or <paramref name="config"/> is <see langword="null"/>. |
| | | 66 | | /// </exception> |
| | 2 | 67 | | protected Router(RouteScopeBuilder routeScopeBuilder, RouterConfig config) |
| | 2 | 68 | | { |
| | 2 | 69 | | Ensure.NotNull(routeScopeBuilder); |
| | 2 | 70 | | Ensure.NotNull(config); |
| | | 71 | | |
| | 2 | 72 | | _requestPipeline = new RequestPipeline(routeScopeBuilder.CreateSnapshot(), config); |
| | 2 | 73 | | Config = config; |
| | 2 | 74 | | } |
| | | 75 | | |
| | | 76 | | /// <summary> |
| | | 77 | | /// Routes an <see cref="HttpRequestMessage"/> through the configured handler pipeline. |
| | | 78 | | /// </summary> |
| | | 79 | | /// <param name="request">The request to process.</param> |
| | | 80 | | /// <param name="services">The service provider exposed to value parsers and handlers.</param> |
| | | 81 | | /// <param name="cancellation">A token that can cancel request processing.</param> |
| | | 82 | | /// <returns>The <see cref="HttpResponseMessage"/> produced by the matching handlers.</returns> |
| | | 83 | | /// <remarks> |
| | | 84 | | /// Prefix routes can participate in the same pipeline as exact routes. Consecutive <c>/</c> separators in the |
| | | 85 | | /// request path are treated as a single separator during matching. When several handlers match, NanoRoute |
| | | 86 | | /// evaluates compatible matches from shorter prefixes toward more specific matches and honors |
| | | 87 | | /// <see cref="MatchingPrecedence"/> when both literal and parameterized segments are available at the same dept |
| | | 88 | | /// Once a branch is selected at a given depth, NanoRoute does not return to sibling branches later in the pipel |
| | | 89 | | /// </remarks> |
| | | 90 | | /// <exception cref="HttpRequestException">Thrown when no handler matches the request path.</exception> |
| | | 91 | | /// <exception cref="ArgumentNullException"> |
| | | 92 | | /// Thrown when <paramref name="request"/> or <paramref name="services"/> is <see langword="null"/>. |
| | | 93 | | /// </exception> |
| | | 94 | | /// <exception cref="ArgumentException">Thrown when the request uses an unsupported HTTP method.</exception> |
| | | 95 | | /// <exception cref="OperationCanceledException"> |
| | | 96 | | /// Thrown when the caller cancels the <paramref name="cancellation"/>. |
| | | 97 | | /// </exception> |
| | | 98 | | /// <example> |
| | | 99 | | /// <code> |
| | | 100 | | /// using HttpResponseMessage response = await Handle(request, services, cancellation); |
| | | 101 | | /// </code> |
| | | 102 | | /// </example> |
| | | 103 | | #if DEBUG |
| | | 104 | | internal |
| | | 105 | | #endif |
| | | 106 | | protected Task<HttpResponseMessage> Handle(HttpRequestMessage request, IServiceProvider services, CancellationTo |
| | 2 | 107 | | { |
| | 2 | 108 | | Ensure.NotNull(request); |
| | 2 | 109 | | Ensure.NotNull(services); |
| | | 110 | | |
| | 2 | 111 | | RouterEventSource.Info.Write("RequestProcessingStarted", static request => new |
| | 2 | 112 | | { |
| | 2 | 113 | | RequestUri = request.RequestUri.OriginalString, |
| | 2 | 114 | | Verb = request.Method.Method |
| | 2 | 115 | | }, request); |
| | | 116 | | |
| | 2 | 117 | | return _requestPipeline.RunAsync(request, services, cancellation); |
| | 2 | 118 | | } |
| | | 119 | | |
| | | 120 | | /// <summary> |
| | | 121 | | /// Configuration assigned to this instance. |
| | | 122 | | /// </summary> |
| | | 123 | | public RouterConfig Config { get; } |
| | | 124 | | } |
| | | 125 | | |
| | | 126 | | /// <summary> |
| | | 127 | | /// Provides the self-typed base for concrete router implementations with strongly typed configuration. |
| | | 128 | | /// </summary> |
| | | 129 | | /// <typeparam name="TDescendant">The concrete router type produced by <see cref="CreateBuilder"/>.</typeparam> |
| | | 130 | | /// <typeparam name="TConfig">The configuration type exposed by <see cref="Config"/>.</typeparam> |
| | | 131 | | /// <param name="builder">The builder whose route snapshot and configuration initialize the router.</param> |
| | | 132 | | /// <remarks> |
| | | 133 | | /// Concrete routers derive from this type to inherit <see cref="CreateBuilder"/> and a typed |
| | | 134 | | /// <see cref="Config"/> property. The concrete router must expose a public or non-public constructor that accepts |
| | | 135 | | /// <see cref="RouterBuilder{TRouter, TConfig}"/> so the generated factory can create immutable router snapshots. |
| | | 136 | | /// </remarks> |
| | | 137 | | /// <example> |
| | | 138 | | /// <code> |
| | | 139 | | /// public sealed class MyRouter : Router<MyRouter, RouterConfig> |
| | | 140 | | /// { |
| | | 141 | | /// private MyRouter(RouterBuilder<MyRouter, RouterConfig> builder) : base(builder) |
| | | 142 | | /// { |
| | | 143 | | /// } |
| | | 144 | | /// } |
| | | 145 | | /// </code> |
| | | 146 | | /// </example> |
| | 2 | 147 | | public abstract class Router<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicConstructors | Dyna |
| | | 148 | | { |
| | 2 | 149 | | private static readonly Lazy<RouterFactoryDelegate<TDescendant, TConfig>> s_factory = new |
| | 2 | 150 | | ( |
| | 2 | 151 | | static () => |
| | 2 | 152 | | { |
| | 2 | 153 | | ParameterExpression builder = Expression.Parameter(typeof(RouterBuilder<TDescendant, TConfig>), nameof(b |
| | 2 | 154 | | |
| | 2 | 155 | | return Expression |
| | 2 | 156 | | .Lambda<RouterFactoryDelegate<TDescendant, TConfig>> |
| | 2 | 157 | | ( |
| | 2 | 158 | | Expression.New |
| | 2 | 159 | | ( |
| | 2 | 160 | | typeof(TDescendant).GetConstructor |
| | 2 | 161 | | ( |
| | 2 | 162 | | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, |
| | 2 | 163 | | null, |
| | 2 | 164 | | [typeof(RouterBuilder<TDescendant, TConfig>)], |
| | 2 | 165 | | [] |
| | 2 | 166 | | ) ?? throw new MissingMethodException(), |
| | 2 | 167 | | builder |
| | 2 | 168 | | ), |
| | 2 | 169 | | builder |
| | 2 | 170 | | ) |
| | 2 | 171 | | .Compile |
| | 2 | 172 | | ( |
| | 2 | 173 | | preferInterpretation: !RuntimeFeature.IsDynamicCodeSupported |
| | 2 | 174 | | ); |
| | 2 | 175 | | }, |
| | 2 | 176 | | isThreadSafe: true |
| | 2 | 177 | | ); |
| | | 178 | | |
| | | 179 | | /// <summary> |
| | | 180 | | /// Configuration assigned to this instance. |
| | | 181 | | /// </summary> |
| | 1 | 182 | | public new TConfig Config => (TConfig) base.Config; |
| | | 183 | | |
| | | 184 | | /// <summary> |
| | | 185 | | /// Creates a strongly typed builder. |
| | | 186 | | /// </summary> |
| | | 187 | | /// <returns>A builder that can register handlers, value parsers, and router configuration.</returns> |
| | | 188 | | /// <exception cref="MissingMethodException"> |
| | | 189 | | /// Thrown when <typeparamref name="TDescendant"/> does not declare a public or non-public constructor that |
| | | 190 | | /// accepts <see cref="RouterBuilder{TRouter, TConfig}"/>. |
| | | 191 | | /// </exception> |
| | | 192 | | /// <example> |
| | | 193 | | /// <code> |
| | | 194 | | /// RouterBuilder<MyRouter, RouterConfig> builder = MyRouter.CreateBuilder(); |
| | | 195 | | /// </code> |
| | | 196 | | /// </example> |
| | 2 | 197 | | public static RouterBuilder<TDescendant, TConfig> CreateBuilder() => new(s_factory.Value); |
| | | 198 | | } |
| | | 199 | | } |