< Summary

Information
Class: NanoRoute.HttpListenerRouter
Assembly: NanoRoute.dll
File(s): /home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Public/HttpListenerRouter.cs
Line coverage
100%
Covered lines: 29
Uncovered lines: 0
Coverable lines: 29
Total lines: 155
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBlocks covered Blocks not covered
HttpListenerRouter()80
GetRequest(...)360
HttpListenerRouter(...)30
CreateBuilder()50

File(s)

/home/runner/work/nanoroute/nanoroute/Src/NanoRoute/Public/HttpListenerRouter.cs

#LineLine coverage
 1/********************************************************************************
 2* HttpListenerRouter.cs                                                         *
 3*                                                                               *
 4* Author: Denes Solti                                                           *
 5********************************************************************************/
 6using System;
 7using System.Collections.Frozen;
 8using System.Collections.Generic;
 9using System.IO;
 10using System.Net;
 11using System.Net.Http;
 12using System.Net.Http.Headers;
 13using System.Threading;
 14using System.Threading.Tasks;
 15
 16namespace NanoRoute
 17{
 18    using Internals;
 19
 20    /// <summary>
 21    /// Routes <see cref="HttpListenerContext"/> instances through a NanoRoute pipeline.
 22    /// </summary>
 23    /// <remarks>
 24    /// This adapter converts incoming <see cref="HttpListener"/> traffic into the core
 25    /// <see cref="HttpRequestMessage"/>/<see cref="HttpResponseMessage"/> pipeline used by NanoRoute.
 26    /// </remarks>
 27    public class HttpListenerRouter: Router
 28    {
 29        // https://learn.microsoft.com/en-us/dotnet/api/system.net.httplistenerresponse.headers?view=net-10.0#remarks
 130        private static readonly FrozenSet<string> s_reservedHeaders = new List<string>
 131        {
 132            "Content-Length",
 133            "Transfer-Encoding",
 134            "Keep-Alive",
 135            "Server"
 136        }.ToFrozenSet(StringComparer.OrdinalIgnoreCase);
 37
 38        private static async Task HandleResponse(HttpResponseMessage responseMessage, HttpListenerResponse response, Can
 39        {
 40            response.StatusCode = (int) responseMessage.StatusCode;
 41
 42            CopyResponseHeaders(responseMessage.Headers);
 43
 44            if (responseMessage.Content is not null)
 45            {
 46                CopyResponseHeaders(responseMessage.Content.Headers);
 47
 48                using Stream buffer = await responseMessage.Content.ReadAsStreamAsync();
 49
 50                // https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/librar
 51                await buffer.CopyToAsync(response.OutputStream, 81920, cancellation);
 52            }
 53
 54            response.Close();
 55
 56            void CopyResponseHeaders(IEnumerable<KeyValuePair<string, IEnumerable<string>>> headers)
 57            {
 58                foreach (KeyValuePair<string, IEnumerable<string>> header in headers)
 59                    if (!s_reservedHeaders.Contains(header.Key))
 60                        response.Headers.Add(header.Key, string.Join(",", header.Value));
 61            }
 62        }
 63
 64        private static HttpRequestMessage GetRequest(HttpListenerRequest request)
 165        {
 166            HttpRequestMessage requestMessage = new
 167            (
 168                new HttpMethod(request.HttpMethod),
 169                request.Url
 170            );
 71
 172            if (request.HasEntityBody)
 173                requestMessage.Content = new StreamContent(request.InputStream);
 74
 175            foreach (string headerName in request.Headers.AllKeys)
 176            {
 177                HttpHeaders headers = requestMessage.Content is not null && HttpRequestMessage.ContentHeaders.Contains(h
 178                    ? requestMessage.Content.Headers
 179                    : requestMessage.Headers;
 80
 81                // Some header (like Content-Type) has its default value. Without this line we'd just append the value l
 182                headers.Remove(headerName);
 183                headers.TryAddWithoutValidation(headerName, request.Headers.GetValues(headerName));
 184            }
 85
 186            requestMessage.Properties[OriginalRequestName] = request;
 187            requestMessage.Properties[TraceIdName] = request.RequestTraceIdentifier.ToString("N");
 88
 189            return requestMessage;
 190        }
 91
 192        private HttpListenerRouter(RouterBuilder<HttpListenerRouter, HttpListenerRouterConfig> builder) : base(builder, 
 93
 94        /// <summary>
 95        /// Routes a single <see cref="HttpListener"/> request and writes the produced response.
 96        /// </summary>
 97        /// <param name="context">The listener context that supplies the request and receives the response.</param>
 98        /// <param name="services">The service provider exposed to handlers through <see cref="RequestContext.Services"/
 99        /// <param name="cancellation">A token that can cancel request processing and response streaming.</param>
 100        /// <returns>A task that completes after the router has finished writing the response.</returns>
 101        /// <exception cref="OperationCanceledException">
 102        /// Thrown when the caller cancels <paramref name="cancellation"/>. The listener response is aborted before the
 103        /// exception is rethrown.
 104        /// </exception>
 105        /// <remarks>
 106        /// Request and content headers are copied into the intermediate <see cref="HttpRequestMessage"/>.
 107        /// The original <see cref="HttpListenerRequest"/> is stored in
 108        /// <see cref="Router.OriginalRequestName"/> on the generated request message.
 109        /// Response headers are copied back except for reserved <see cref="HttpListenerResponse"/> headers that
 110        /// must be managed by <see cref="HttpListener"/> itself. Cancellation is not translated into an HTTP error
 111        /// response by this adapter.
 112        /// </remarks>
 113        /// <example>
 114        /// <code>
 115        /// HttpListenerRouter router = HttpListenerRouter
 116        ///     .CreateBuilder()
 117        ///     .AddJsonErrorDetails()
 118        ///     .AddDefaultValueParsers()
 119        ///     .AddHandler("GET", "/hello/{name:str}", (context, _) =&gt;
 120        ///         Task.FromResult(HttpResponseMessage.Json(new
 121        ///         {
 122        ///             message = $"Hello {context.Parameters["name"]}"
 123        ///         })))
 124        ///     .CreateRouter();
 125        ///
 126        /// await router.Route(listenerContext, services, cancellationToken);
 127        /// </code>
 128        /// </example>
 129        public async Task Route(HttpListenerContext context, IServiceProvider services, CancellationToken cancellation =
 130        {
 131            Ensure.NotNull(context);
 132            Ensure.NotNull(services);
 133
 134            using HttpRequestMessage request = GetRequest(context.Request);
 135
 136            try
 137            {
 138                using HttpResponseMessage response = await Handle(request, services, cancellation);
 139
 140                await HandleResponse(response, context.Response, cancellation);
 141            }
 142            catch (OperationCanceledException)
 143            {
 144                context.Response.Abort();
 145                throw;
 146            }
 147        }
 148
 149        /// <summary>
 150        /// Creates a strongly typed builder for configuring an <see cref="HttpListenerRouter"/>.
 151        /// </summary>
 152        /// <returns>A builder that can register handlers, value parsers, and router configuration.</returns>
 1153        public static RouterBuilder<HttpListenerRouter, HttpListenerRouterConfig> CreateBuilder() => new(static bldr => 
 154    }
 155}