/// <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")); }
/// <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); }
/// <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; } }
/// <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 "302 - Found"</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); }
/// <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))); }
/// <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); }
/// <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); })); }
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")); } }
/// <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; }
/// <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); }
/// <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); } } }
/// <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); }