예제 #1
0
        protected static async Task WriteFileAsync(MazeContext context, Stream fileStream, RangeItemHeaderValue range, long rangeLength)
        {
            var outputStream = context.Response.Body;

            using (fileStream)
            {
                try
                {
                    if (range == null)
                    {
                        await StreamCopyOperation.CopyToAsync(fileStream, outputStream, count : null, bufferSize : BufferSize, cancel : context.RequestAborted);
                    }
                    else
                    {
                        fileStream.Seek(range.From.Value, SeekOrigin.Begin);
                        await StreamCopyOperation.CopyToAsync(fileStream, outputStream, rangeLength, BufferSize, context.RequestAborted);
                    }
                }
                catch (OperationCanceledException)
                {
                    // Don't throw this exception, it's most likely caused by the client disconnecting.
                    // However, if it was cancelled for any other reason we need to prevent empty responses.
                    context.Abort();
                }
            }
        }
예제 #2
0
 /// <summary>
 ///     Creates a new <see cref="OutputFormatterWriteContext" />.
 /// </summary>
 /// <param name="context">The <see cref="MazeContext" /> for the current request.</param>
 /// <param name="writerFactory">The delegate used to create a <see cref="TextWriter" /> for writing the response.</param>
 /// <param name="objectType">The <see cref="Type" /> of the object to write to the response.</param>
 /// <param name="object">The object to write to the response.</param>
 public OutputFormatterWriteContext(MazeContext context, Func <Stream, Encoding, TextWriter> writerFactory,
                                    Type objectType, object @object)
     : base(context)
 {
     WriterFactory = writerFactory ?? throw new ArgumentNullException(nameof(writerFactory));
     ObjectType    = objectType;
     Object        = @object;
 }
예제 #3
0
 /// <summary>
 ///     Creates a new instance of <see cref="InputFormatterContext" />.
 /// </summary>
 /// <param name="httpContext">
 ///     The <see cref="Microsoft.AspNetCore.Http.HttpContext" /> for the current operation.
 /// </param>
 /// <param name="modelName">The name of the model.</param>
 /// <param name="modelState">
 ///     The <see cref="Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" /> for recording errors.
 /// </param>
 /// <param name="metadata">
 ///     The <see cref="ModelMetadata" /> of the model to deserialize.
 /// </param>
 /// <param name="readerFactory">
 ///     A delegate which can create a <see cref="TextReader" /> for the request body.
 /// </param>
 public InputFormatterContext(
     MazeContext httpContext,
     string modelName,
     ModelStateDictionary modelState,
     ModelMetadata metadata,
     Func <Stream, Encoding, TextReader> readerFactory)
     : this(httpContext, modelName, modelState, metadata, readerFactory, false)
 {
 }
예제 #4
0
        private Task WriteError(MazeContext context, RestError error, int statusCode)
        {
            var actionContext = new DefaultActionContext(context, null, ImmutableDictionary <string, object> .Empty);

            return(new ObjectResult(new[] { error })
            {
                StatusCode = statusCode
            }.ExecuteResultAsync(actionContext));
        }
예제 #5
0
 /// <summary>
 ///     Creates a new instance of <see cref="InputFormatterContext" />.
 /// </summary>
 /// <param name="httpContext">
 ///     The <see cref="Microsoft.AspNetCore.Http.HttpContext" /> for the current operation.
 /// </param>
 /// <param name="modelName">The name of the model.</param>
 /// <param name="modelState">
 ///     The <see cref="Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" /> for recording errors.
 /// </param>
 /// <param name="metadata">
 ///     The <see cref="ModelMetadata" /> of the model to deserialize.
 /// </param>
 /// <param name="readerFactory">
 ///     A delegate which can create a <see cref="TextReader" /> for the request body.
 /// </param>
 /// <param name="treatEmptyInputAsDefaultValue">
 ///     A value for the <see cref="TreatEmptyInputAsDefaultValue" /> property.
 /// </param>
 public InputFormatterContext(
     MazeContext httpContext,
     string modelName,
     ModelStateDictionary modelState,
     ModelMetadata metadata,
     Func <Stream, Encoding, TextReader> readerFactory,
     bool treatEmptyInputAsDefaultValue)
 {
     MazeContext   = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
     ModelName     = modelName ?? throw new ArgumentNullException(nameof(modelName));
     ModelState    = modelState ?? throw new ArgumentNullException(nameof(modelState));
     Metadata      = metadata ?? throw new ArgumentNullException(nameof(metadata));
     ReaderFactory = readerFactory ?? throw new ArgumentNullException(nameof(readerFactory));
     TreatEmptyInputAsDefaultValue = treatEmptyInputAsDefaultValue;
     ModelType = metadata.ModelType;
 }
예제 #6
0
        public ResolveResult Resolve(MazeContext context)
        {
            var pathDecoded = HttpUtility.UrlDecode(context.Request.Path);

            var results = _routeResolverTrie.GetMatches(context.Request.Method, pathDecoded);

            if (!results.Any())
            {
                return(new ResolveResult(false));
            }

            var matchResult = results.OrderByDescending(x => x.Score).First();

            return(new ResolveResult
            {
                RouteDescription = matchResult.RouteDescription,
                Parameters = matchResult.Parameters
            });
        }
예제 #7
0
 public MazeGeneratorController(ILogger <MazeGeneratorController> logger, MazeContext context)
 {
     _logger  = logger;
     _context = context;
 }
예제 #8
0
        /// <summary>
        ///     Returns the normalized form of the requested range if the Range Header in the <see cref="MazeContext.Request" />
        ///     is valid.
        /// </summary>
        /// <param name="context">The <see cref="MazeContext" /> associated with the request.</param>
        /// <param name="requestHeaders">The <see cref="RequestHeaders" /> associated with the given <paramref name="context" />.</param>
        /// <param name="length">The total length of the file representation requested.</param>
        /// <param name="logger">The <see cref="ILogger" />.</param>
        /// <returns>
        ///     A boolean value which represents if the <paramref name="requestHeaders" /> contain a single valid
        ///     range request. A <see cref="RangeItemHeaderValue" /> which represents the normalized form of the
        ///     range parsed from the <paramref name="requestHeaders" /> or <c>null</c> if it cannot be normalized.
        /// </returns>
        /// <remark>
        ///     If the Range header exists but cannot be parsed correctly, or if the provided length is 0, then the range request
        ///     cannot be satisfied (status 416).
        ///     This results in (<c>true</c>,<c>null</c>) return values.
        /// </remark>
        public static (bool isRangeRequest, RangeItemHeaderValue range) ParseRange(
            MazeContext context,
            RequestHeaders requestHeaders,
            long length,
            ILogger logger)
        {
            var rawRangeHeader = context.Request.Headers[HeaderNames.Range];

            if (StringValues.IsNullOrEmpty(rawRangeHeader))
            {
                logger.LogTrace("Range header's value is empty.");
                return(false, null);
            }

            // Perf: Check for a single entry before parsing it
            if (rawRangeHeader.Count > 1 || rawRangeHeader[0].IndexOf(',') >= 0)
            {
                logger.LogDebug("Multiple ranges are not supported.");

                // The spec allows for multiple ranges but we choose not to support them because the client may request
                // very strange ranges (e.g. each byte separately, overlapping ranges, etc.) that could negatively
                // impact the server. Ignore the header and serve the response normally.
                return(false, null);
            }

            var rangeHeader = requestHeaders.Range;

            if (rangeHeader == null)
            {
                logger.LogDebug("Range header's value is invalid.");
                // Invalid
                return(false, null);
            }

            // Already verified above
            Debug.Assert(rangeHeader.Ranges.Count == 1);

            var ranges = rangeHeader.Ranges;

            if (ranges == null)
            {
                logger.LogDebug("Range header's value is invalid.");
                return(false, null);
            }

            if (ranges.Count == 0)
            {
                return(true, null);
            }

            if (length == 0)
            {
                return(true, null);
            }

            // Normalize the ranges
            var range = NormalizeRange(ranges.SingleOrDefault(), length);

            // Return the single range
            return(true, range);
        }
 /// <summary>
 ///     Creates a new <see cref="OutputFormatterCanWriteContext" />.
 /// </summary>
 /// <param name="context">The <see cref="MazeContext" /> for the current request.</param>
 protected OutputFormatterCanWriteContext(MazeContext context)
 {
     MazeContext = context ?? throw new ArgumentNullException(nameof(context));
 }
 public MazesController(MazeContext context)
 {
     _context = context;
 }
예제 #11
0
 public DefaultActionContext(MazeContext context, Route route, IReadOnlyDictionary <string, object> routeData)
 {
     Context   = context;
     Route     = route;
     RouteData = routeData;
 }
예제 #12
0
        /// <inheritdoc />
        public async Task Execute(MazeContext context, IChannelServer channelServer)
        {
            _logger.LogDebug($"Resolve Maze path {context.Request.Path}");
            var result = _routeResolver.Resolve(context);

            if (!result.Success)
            {
                _logger.LogDebug("Path not found");
                await WriteError(context, BusinessErrors.Commander.RouteNotFound(context.Request.Path),
                                 StatusCodes.Status404NotFound);

                return;
            }

            _logger.LogDebug(
                $"Route resolved (package: {result.RouteDescription.PackageIdentity}). Get cached route info.");
            var route         = _routeCache.Routes[result.RouteDescription];
            var actionContext = new DefaultActionContext(context, route, result.Parameters.ToImmutableDictionary());

            _logger.LogDebug($"Invoke method {route.RouteMethod}");

            IActionResult actionResult;

            try
            {
                switch (route.RouteType)
                {
                case RouteType.Http:
                    actionResult = await route.ActionInvoker.Value.Invoke(actionContext);

                    break;

                case RouteType.ChannelInit:
                    _logger.LogDebug("Create channel {channelName}", actionContext.Route.ControllerType.FullName);
                    var channel = await route.ActionInvoker.Value.InitializeChannel(actionContext, channelServer);

                    context.Response.Headers.Add(HeaderNames.Location, "ws://channels/" + channel.ChannelId);
                    actionResult = new StatusCodeResult(StatusCodes.Status201Created);
                    break;

                case RouteType.Channel:
                    var channelId = int.Parse(actionContext.Context.Request.Headers["ChannelId"]);
                    _logger.LogDebug("Request channel with id {channelId}", channelId);

                    var foundChannel = channelServer.GetChannel(channelId);
                    actionResult =
                        await route.ActionInvoker.Value.InvokeChannel(actionContext, (MazeChannel)foundChannel);

                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }
            }
            catch (Exception e)
            {
                if (context.RequestAborted.IsCancellationRequested)
                {
                    return;
                }

                _logger.LogError(e,
                                 $"Error occurred when invoking method {route.RouteMethod} of package {result.RouteDescription.PackageIdentity} (path: {context.Request.Path})");
                await WriteError(context,
                                 BusinessErrors.Commander.ActionError(e.GetType().Name, route.RouteMethod.Name, e.Message),
                                 StatusCodes.Status500InternalServerError);

                return;
            }

            if (context.RequestAborted.IsCancellationRequested)
            {
                return;
            }

            try
            {
                await actionResult.ExecuteResultAsync(actionContext);
            }
            catch (Exception e)
            {
                if (context.RequestAborted.IsCancellationRequested)
                {
                    return;
                }

                _logger.LogError(e,
                                 $"Error occurred when executing action result {route.RouteMethod} of package {result.RouteDescription.PackageIdentity} (path: {context.Request.Path})");
                await WriteError(context,
                                 BusinessErrors.Commander.ResultExecutionError(e.GetType().Name, actionResult?.GetType().Name,
                                                                               e.Message), StatusCodes.Status500InternalServerError);

                return;
            }

            _logger.LogDebug("Request successfully executed.");
        }