Beispiel #1
0
        /// <summary>
        /// Helper method to ensure that the different ways of routing
        /// result in the same setup
        /// </summary>
        /// <param name="server">The server runner instance</param>
        private void CommonTesting(ServerRunner server)
        {
            Assert.AreEqual(HttpStatusCode.NotFound, server.GetStatusCode("/"));
            Assert.AreEqual(HttpStatusCode.OK, server.GetStatusCode("/home"));
            Assert.AreEqual(HttpStatusCode.NotFound, server.GetStatusCode("/xyz"));
            Assert.AreEqual(HttpStatusCode.NotFound, server.GetStatusCode("/home1"));

            Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/home", "GET"));
            Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/home", "XYZ"));

            Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/home1", "XYZ"));
            Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/home", "XYZ"));
            Assert.AreEqual(ControllerItems.ENTRY_DEFAULT_INDEX, server.GetStatusMessage("/api/v1/entry"));
            Assert.AreEqual(ControllerItems.ENTRY_INDEX_ID, server.GetStatusMessage("/api/v1/entry/4"));
            Assert.AreEqual(HttpStatusCode.BadRequest, server.GetStatusCode("/api/v1/entry/x"));
            Assert.AreEqual(ControllerItems.ENTRY_DETAIL_INDEX, server.GetStatusMessage("/api/v1/entry/detail"));
            Assert.AreEqual(ControllerItems.ENTRY_DETAIL_ID, server.GetStatusMessage("/api/v1/entry/detail/7"));
            Assert.AreEqual(HttpStatusCode.BadRequest, server.GetStatusCode("/api/v1/entry/detail/y"));

            Assert.AreEqual(ControllerItems.ENTRY_DETAIL_CROSS, server.GetStatusMessage("/api/v1/entry/7/detail"));

            Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1/"));
            Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1"));
            Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1/home"));
            Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1/xyz"));

            Assert.AreEqual(ControllerItems.WAIT_INDEX, server.GetStatusMessage("/api/v1/wait", "GET"));
            Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.MethodNotAllowed), server.GetStatusMessage("/api/v1/wait", "POST"));

            Assert.AreEqual(HttpStatusCode.NotFound, server.GetStatusCode("/api/v1/4/detail"));
        }
Beispiel #2
0
        /// <summary>
        /// Attempts to route the request to a controller instance.
        /// </summary>
        /// <param name="context">The exexcution context.</param>
        public async Task <bool> Process(IHttpContext context)
        {
            var anymatches = false;
            var test       = m_routeparser
                             .MatchRequest(context.Request.Path)
                             .Where(x => { anymatches = true; return(x.Key.AcceptsVerb(context.Request.Method)); })
                             .OrderBy(x => x.Value.Count)
                             .ToList();

            // If we do not handle this entry, pass the request down the stack
            if (!anymatches)
            {
                return(false);
            }

            // If we filtered all targets based on verb,
            // we are handling the path, but the verb is not allowed
            if (test.Count == 0)
            {
                context.Response.StatusCode    = HttpStatusCode.MethodNotAllowed;
                context.Response.StatusMessage = HttpStatusMessages.DefaultMessage(HttpStatusCode.MethodNotAllowed);
                return(true);
            }

            var handler = test.First();

            await HandleWithMethod(context, handler.Key.Action, handler.Key.Controller, handler.Value);

            return(true);
        }
Beispiel #3
0
        /// <summary>
        /// Flush all headers async.
        /// This method can be called multiple times if desired.
        /// </summary>
        /// <returns>The headers async.</returns>
        public async Task FlushHeadersAsync()
        {
            if (!m_hasSentHeaders)
            {
                if (string.IsNullOrWhiteSpace(this.StatusMessage))
                {
                    this.StatusMessage = HttpStatusMessages.DefaultMessage(this.StatusCode);
                }

                var line = Encoding.ASCII.GetBytes(string.Format("{0} {1} {2}{3}", this.HttpVersion, (int)this.StatusCode, this.StatusMessage, CRLF));
                await m_stream.WriteAsync(line, 0, line.Length);

                foreach (var e in m_headers)
                {
                    line = Encoding.ASCII.GetBytes(string.Format("{0}: {1}{2}", e.Key, e.Value, CRLF));
                    await m_stream.WriteAsync(line, 0, line.Length);
                }

                foreach (var cookie in Cookies)
                {
                    var sb = new StringBuilder();
                    sb.Append("Set-Cookie: ");
                    sb.Append(cookie.Name);
                    if (!string.IsNullOrWhiteSpace(cookie.Value))
                    {
                        sb.Append("=");

                        // URL encoding not required, but common practice
                        sb.Append(Uri.EscapeDataString(cookie.Value));
                    }

                    foreach (var setting in cookie.Settings)
                    {
                        sb.Append("; ");
                        sb.Append(setting.Key);
                        if (!string.IsNullOrWhiteSpace(setting.Value))
                        {
                            sb.Append("=");
                            sb.Append(setting.Value);
                        }
                    }

                    sb.Append(CRLF);
                    line = Encoding.ASCII.GetBytes(sb.ToString());

                    await m_stream.WriteAsync(line, 0, line.Length);
                }

                if (Cookies is List <IResponseCookie> )
                {
                    Cookies = ((List <IResponseCookie>)Cookies).AsReadOnly();
                }

                line = Encoding.ASCII.GetBytes(CRLF);
                await m_stream.WriteAsync(line, 0, line.Length);

                m_hasSentHeaders = true;
            }
        }
Beispiel #4
0
        /// <summary>
        /// Sets the status code to NotFound with an optional non-default message on the context's response instance
        /// </summary>
        /// <param name="context">The context instance to set the status on</param>
        /// <param name="targeturl">The target url to redirect to</param>
        /// <param name="statuscode">The optional status code to use, if not using the default &quot;302 - Found&quot;</param>
        /// <param name="message">The optional status message; will use a default message if this is not set</param>
        /// <returns><c>true<c/></returns>
        public static bool SetResponseRedirect(this IHttpContext context, string targeturl, HttpStatusCode code = HttpStatusCode.Found, string message = null)
        {
            context.Response.Headers["Location"] = targeturl;
            context.Response.StatusCode          = code;
            context.Response.StatusMessage       = message ?? HttpStatusMessages.DefaultMessage(context.Response.StatusCode);

            return(true);
        }
Beispiel #5
0
 /// <summary>
 /// Execute the method with the specified context.
 /// </summary>
 /// <param name="context">The context to use.</param>
 public Task Execute(IHttpContext context)
 {
     // We do not use the HTTP status codes
     context.Response.StatusCode    = HttpStatusCode.OK;
     context.Response.StatusMessage = HttpStatusMessages.DefaultMessage(HttpStatusCode.OK);
     context.Response.SetNonCacheable();
     return(context.Response.WriteAllJsonAsync(JsonConvert.SerializeObject(this)));
 }
Beispiel #6
0
 /// <summary>
 /// Sets the status code and optional non-default message on the response instance
 /// </summary>
 /// <param name="response">The response instance to set the code on</param>
 /// <param name="code">The status code to set</param>
 /// <param name="message">The optional status message; will use a default message if this is not set</param>
 /// <returns><c>true<c/></returns>
 public static bool SetStatus(this IHttpResponse response, HttpStatusCode code, string message = null)
 {
     response.StatusCode = code;
     if (string.IsNullOrWhiteSpace(message))
     {
         message = HttpStatusMessages.DefaultMessage(code);
     }
     response.StatusMessage = message;
     return(true);
 }
Beispiel #7
0
        /// <summary>
        /// Sets the status for the request
        /// </summary>
        /// <param name="code">The status code.</param>
        /// <param name="message">An optional status message.</param>
        protected IResult Status(HttpStatusCode code, string message = null, bool disablecaching = true)
        {
            return(new LambdaResult(ctx =>
            {
                if (disablecaching)
                {
                    ctx.Response.SetNonCacheable();
                }

                ctx.Response.StatusCode = code;
                ctx.Response.StatusMessage = message ?? HttpStatusMessages.DefaultMessage(code);
            }));
        }
Beispiel #8
0
        public void TestRoutingWithDefaultController()
        {
            using (var server = new ServerRunner(
                       new Ceen.Httpd.ServerConfig()
                       .AddLogger(new Ceen.Httpd.Logging.StdOutErrors())
                       .AddRoute(
                           typeof(ControllerItems)
                           .GetNestedTypes()
                           .ToRoute(
                               new ControllerRouterConfig(
                                   typeof(ControllerItems.HomeController))
            {
                HideDefaultController = false, Debug = true
            }
                               ))
                       ))
            {
                Assert.AreEqual(HttpStatusCode.OK, server.GetStatusCode("/"));
                Assert.AreEqual(HttpStatusCode.OK, server.GetStatusCode("/home"));
                Assert.AreEqual(HttpStatusCode.NotFound, server.GetStatusCode("/xyz"));
                Assert.AreEqual(HttpStatusCode.NotFound, server.GetStatusCode("/home1"));

                Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/", "GET"));
                Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/", "XYZ"));
                Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/home", "GET"));
                Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/home", "XYZ"));

                Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/home1", "XYZ"));
                Assert.AreEqual(ControllerItems.XYZ_HOME_INDEX, server.GetStatusMessage("/home", "XYZ"));
                Assert.AreEqual(ControllerItems.ENTRY_DEFAULT_INDEX, server.GetStatusMessage("/api/v1/entry"));
                Assert.AreEqual(ControllerItems.ENTRY_INDEX_ID, server.GetStatusMessage("/api/v1/entry/4"));
                Assert.AreEqual(HttpStatusCode.BadRequest, server.GetStatusCode("/api/v1/entry/x"));
                Assert.AreEqual(ControllerItems.ENTRY_DETAIL_INDEX, server.GetStatusMessage("/api/v1/entry/detail"));
                Assert.AreEqual(ControllerItems.ENTRY_DETAIL_ID, server.GetStatusMessage("/api/v1/entry/detail/7"));
                Assert.AreEqual(HttpStatusCode.BadRequest, server.GetStatusCode("/api/v1/entry/detail/y"));

                Assert.AreEqual(ControllerItems.ENTRY_DETAIL_CROSS, server.GetStatusMessage("/api/v1/entry/7/detail"));

                Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1/"));
                Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1"));
                Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1/home"));
                Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.NotFound), server.GetStatusMessage("/api/v1/xyz"));

                Assert.AreEqual(ControllerItems.WAIT_INDEX, server.GetStatusMessage("/api/v1/wait", "GET"));
                Assert.AreEqual(HttpStatusMessages.DefaultMessage(HttpStatusCode.MethodNotAllowed), server.GetStatusMessage("/api/v1/wait", "POST"));

                Assert.AreEqual(HttpStatusCode.NotFound, server.GetStatusCode("/api/v1/4/detail"));
            }
        }
Beispiel #9
0
 /// <summary>
 /// Initializes a new instance of the <see cref="T:Ceen.Mvc.StatusCodeResult"/> struct.
 /// </summary>
 /// <param name="code">The status code.</param>
 /// <param name="message">The status message.</param>
 public StatusCodeResult(HttpStatusCode code, string message = null, bool disableCaching = true)
 {
     StatusCode     = code;
     StatusMessage  = message ?? HttpStatusMessages.DefaultMessage(code);
     DisableCaching = disableCaching;
 }
Beispiel #10
0
 /// <summary>
 /// Performs a 302 redirect
 /// </summary>
 /// <param name="newurl">The target url.</param>
 public void Redirect(string newurl)
 {
     Headers["Location"] = newurl;
     StatusCode          = HttpStatusCode.Found;
     StatusMessage       = HttpStatusMessages.DefaultMessage(StatusCode);
 }
Beispiel #11
0
        /// <summary>
        /// Dispatcher method for handling a request
        /// </summary>
        /// <param name="stream">The underlying stream.</param>
        /// <param name="endpoint">The remote endpoint.</param>
        /// <param name="logtaskid">The task id for logging and tracing</param>
        /// <param name="clientcert">The client certificate if any.</param>
        /// <param name="controller">The runner controller.</param>
        /// <param name="sslProtocol">The SSL protocol being used</param>
        /// <param name="isConnected">A method for checking if the socket is connected</param>
        private static async Task Runner(Stream stream, EndPoint endpoint, string logtaskid, X509Certificate clientcert, SslProtocols sslProtocol, RunnerControl controller, Func <bool> isConnected)
        {
            var config   = controller.Config;
            var storage  = config.Storage;
            var requests = config.KeepAliveMaxRequests;

            bool keepingalive = false;

            HttpContext  context = null;
            HttpRequest  cur     = null;
            HttpResponse resp    = null;
            DateTime     started = new DateTime();

            using (var bs = new BufferedStreamReader(stream))
            {
                try
                {
                    config.DebugLogHandler?.Invoke("Running task", logtaskid, endpoint);
                    if (!controller.RegisterActive(logtaskid))
                    {
                        return;
                    }

                    do
                    {
                        var reqid = SetLoggingRequestID();
                        bs.ResetReadLength(config.MaxPostSize);
                        started  = DateTime.Now;
                        context  = new HttpContext(
                            cur  = new HttpRequest(endpoint, logtaskid, reqid, clientcert, sslProtocol, isConnected),
                            resp = new HttpResponse(stream, config),
                            storage
                            );

                        // Setup up the callback for allowing handlers to report errors
                        context.LogHandlerDelegate = (ex) => LogMessageAsync(controller, context, ex, started, DateTime.Now - started);

                        var timeoutcontroltask = new TaskCompletionSource <bool>();
                        var idletime           = TimeSpan.FromSeconds(config.RequestHeaderReadTimeoutSeconds);

                        // Set up timeout for processing
                        cur.SetProcessingTimeout(TimeSpan.FromSeconds(config.MaxProcessingTimeSeconds));

                        config.DebugLogHandler?.Invoke("Parsing headers", logtaskid, endpoint);
                        try
                        {
                            var ct = new CancellationTokenSource();
                            ct.CancelAfter(TimeSpan.FromSeconds(keepingalive ? config.KeepAliveTimeoutSeconds : config.RequestIdleTimeoutSeconds));
                            using (ct.Token.Register(() => timeoutcontroltask.TrySetCanceled(), useSynchronizationContext: false))
                                await cur.Parse(bs, config, idletime, timeoutcontroltask.Task, controller.StopTask);
                        }
                        catch (EmptyStreamClosedException)
                        {
                            // Client has closed the connection
                            break;
                        }
                        catch (HttpException hex)
                        {
                            // Errors during header parsing are unlikely to
                            // keep the connection in a consistent state
                            resp.StatusCode    = hex.StatusCode;
                            resp.StatusMessage = hex.StatusMessage;
                            await resp.FlushHeadersAsync();

                            throw;
                        }
                        catch (Exception ex)
                        {
                            config.DebugLogHandler?.Invoke($"Failed while reading header: {ex}", logtaskid, cur);
                            throw;
                        }

                        string keepalive;
                        cur.Headers.TryGetValue("Connection", out keepalive);
                        if (("keep-alive".Equals(keepalive, StringComparison.OrdinalIgnoreCase) || keepingalive) && requests > 1)
                        {
                            resp.KeepAlive = true;
                            if (!keepingalive)
                            {
                                resp.AddHeader("Keep-Alive", string.Format("timeout={0}, max={1}", config.KeepAliveTimeoutSeconds, config.KeepAliveMaxRequests));
                            }
                        }
                        else
                        {
                            resp.KeepAlive = false;
                        }


                        if (config.Loggers != null)
                        {
                            var count = config.Loggers.Count;
                            if (count == 1)
                            {
                                var sl = config.Loggers[0] as IStartLogger;
                                if (sl != null)
                                {
                                    await sl.LogRequestStarted(cur);
                                }
                            }
                            else if (count != 0)
                            {
                                await Task.WhenAll(config.Loggers.Where(x => x is IStartLogger).Cast <IStartLogger>().Select(x => x.LogRequestStarted(cur)));
                            }
                        }

                        config.DebugLogHandler?.Invoke("Running handler", logtaskid, cur);

                        try
                        {
                            // Trigger the streams to stop reading/writing data when the timeout happens
                            using (cur.TimeoutCancellationToken.Register(() => timeoutcontroltask.TrySetCanceled(), useSynchronizationContext: false))
                            {
                                if (cur.TimeoutCancellationToken.IsCancellationRequested)
                                {
                                    if (timeoutcontroltask.Task.Status == TaskStatus.Canceled)
                                    {
                                        throw new TimeoutException();
                                    }
                                    else
                                    {
                                        timeoutcontroltask.TrySetCanceled();
                                        throw new OperationCanceledException();
                                    }
                                }

                                // Process the request
                                do
                                {
                                    cur.ClearHandlerStack();
                                    var target = resp.ClearInternalRedirect();
                                    if (target != null)
                                    {
                                        cur.Path = target;
                                    }

                                    if (!await config.Router.Process(context))
                                    {
                                        throw new HttpException(Ceen.HttpStatusCode.NotFound);
                                    }
                                }while (resp.IsRedirectingInternally);
                            }
                        }
                        catch (HttpException hex)
                        {
                            // Try to set the status code to 500
                            if (resp.HasSentHeaders)
                            {
                                throw;
                            }

                            resp.StatusCode    = hex.StatusCode;
                            resp.StatusMessage = hex.StatusMessage;
                        }

                        config.DebugLogHandler?.Invoke("Flushing response", logtaskid, cur);

                        // If the handler has not flushed, we do it
                        await resp.FlushAndSetLengthAsync();

                        // Check if keep-alive is possible
                        keepingalive = resp.KeepAlive && resp.HasWrittenCorrectLength;
                        requests--;

                        await LogMessageAsync(controller, context, null, started, DateTime.Now - started);
                    } while (keepingalive);
                }
                catch (Exception ex)
                {
                    // If possible, report a 500 error to the client
                    if (resp != null)
                    {
                        try
                        {
                            if (!resp.HasSentHeaders)
                            {
                                resp.StatusCode    = Ceen.HttpStatusCode.InternalServerError;
                                resp.StatusMessage = HttpStatusMessages.DefaultMessage(Ceen.HttpStatusCode.InternalServerError);
                            }
                        }
                        catch (Exception nex) { config.DebugLogHandler?.Invoke($"Failed to send headers: {nex}", logtaskid, cur); }

                        try { await resp.FlushAsErrorAsync(); }
                        catch (Exception nex) { config.DebugLogHandler?.Invoke($"Failed to FlushAsErrors: {nex}", logtaskid, cur); }
                    }

                    try { stream.Close(); }
                    catch (Exception nex) { config.DebugLogHandler?.Invoke($"Failed to close stream: {nex}", logtaskid, cur); }

                    try { await LogMessageAsync(controller, context, ex, started, DateTime.Now - started); }
                    catch (Exception nex) { config.DebugLogHandler?.Invoke($"Failed to log request: {nex}", logtaskid, cur); }

                    config.DebugLogHandler?.Invoke("Failed handler", logtaskid, cur);
                }
                finally
                {
                    controller.RegisterStopped(logtaskid);
                    config.DebugLogHandler?.Invoke("Terminating handler", logtaskid, cur);
                }
            }
        }
Beispiel #12
0
 /// <summary>
 /// Initializes a new instance of the <see cref="T:Ceen.Mvc.StatusCodeResult"/> struct.
 /// </summary>
 /// <param name="code">The status code.</param>
 /// <param name="message">The status message.</param>
 public StatusCodeResult(HttpStatusCode code, string message = null)
 {
     StatusCode    = code;
     StatusMessage = message ?? HttpStatusMessages.DefaultMessage(code);
 }