Example #1
0
        /// <summary>
        /// Logs a message to all configured loggers
        /// </summary>
        /// <returns>The awaitable task.</returns>
        /// <param name="context">The request context.</param>
        /// <param name="ex">Exception data, if any.</param>
        /// <param name="start">The request start time.</param>
        /// <param name="duration">The request duration.</param>
        private static Task LogMessageAsync(RunnerControl controller, HttpContext context, Exception ex, DateTime start, TimeSpan duration)
        {
            var config = controller.Config;

            if (config.Loggers == null)
            {
                return(Task.FromResult(true));
            }

            CopyLogData?.Invoke(context);

            var count = config.Loggers.Count;

            if (count == 0)
            {
                return(Task.FromResult(true));
            }
            else if (count == 1)
            {
                return(config.Loggers[0].LogRequest(context, ex, start, duration));
            }
            else
            {
                return(Task.WhenAll(config.Loggers.Select(x => x.LogRequest(context, ex, start, duration))));
            }
        }
Example #2
0
        /// <summary>
        /// Listens to incoming connections and calls the spawner method for each new connection
        /// </summary>
        /// <returns>Awaitable task.</returns>
        /// <param name="addr">The address to listen to.</param>
        /// <param name="usessl">A flag indicating if the socket listens for SSL requests</param>
        /// <param name="stoptoken">The stoptoken.</param>
        /// <param name="config">The server configuration</param>
        /// <param name="spawner">The method handling the new connection.</param>
        private static async Task ListenToSocketInternalAsync(IPEndPoint addr, bool usessl, CancellationToken stoptoken, ServerConfig config, Action <TcpClient, EndPoint, string, RunnerControl> spawner)
        {
            var rc = new RunnerControl(stoptoken, usessl, config);

            var listener = new TcpListener(addr);

            listener.Start(config.SocketBacklog);

            var taskid = SetLoggingSocketHandlerID();

            while (!stoptoken.IsCancellationRequested)
            {
                // Wait if there are too many active
                config.DebugLogHandler?.Invoke("Waiting for throttle", taskid, null);
                await rc.ThrottleTask;
                config.DebugLogHandler?.Invoke("Waiting for socket", taskid, null);
                var ls = listener.AcceptTcpClientAsync();

                if (await Task.WhenAny(rc.StopTask, ls) == ls)
                {
                    config.DebugLogHandler?.Invoke("Re-waiting for socket", taskid, null);
                    var client    = await ls;
                    var newtaskid = SetLoggingTaskHandlerID();

                    try
                    {
                        int wt, cpt;
                        ThreadPool.GetAvailableThreads(out wt, out cpt);
                        config.DebugLogHandler?.Invoke(string.Format("Threadpool says {0}, {1}", wt, cpt), taskid, newtaskid);

                        config.DebugLogHandler?.Invoke(string.Format("Spawning runner with id: {0}", newtaskid), taskid, newtaskid);

                        // Read the endpoint here to avoid crashes when invoking the spawner
                        var ep = client.Client.RemoteEndPoint;
                        ThreadPool.QueueUserWorkItem(x => spawner(client, ep, newtaskid, rc));
                    }
                    catch (Exception ex)
                    {
                        config.DebugLogHandler?.Invoke("Failed to listen to socket", taskid, ex);
                    }
                }
            }

            config.DebugLogHandler?.Invoke("Stopping", taskid, null);

            listener.Stop();
            rc.Stop(taskid);

            config.DebugLogHandler?.Invoke("Socket stopped, waiting for workers ...", taskid, null);
            await rc.FinishedTask;

            config.DebugLogHandler?.Invoke("Stopped", taskid, null);
        }
Example #3
0
            /// <summary>
            /// Setup this instance
            /// </summary>
            /// <param name="usessl">If set to <c>true</c> use ssl.</param>
            /// <param name="config">The configuration.</param>
            protected void Setup(bool usessl, ServerConfig config)
            {
                if (StopToken != null)
                {
                    throw new Exception("Cannot call setup more than once");
                }
                if (config == null)
                {
                    throw new ArgumentNullException(nameof(config));
                }

                StopToken  = new CancellationTokenSource();
                Controller = new RunnerControl(StopToken.Token, usessl, config);
            }
Example #4
0
        /// <summary>
        /// Listens to incoming connections and calls the spawner method for each new connection
        /// </summary>
        /// <returns>Awaitable task.</returns>
        /// <param name="acceptAsync">Method that returns an accepted socket.</param>
        /// <param name="usessl">A flag indicating if the socket listens for SSL requests</param>
        /// <param name="stoptoken">The stoptoken.</param>
        /// <param name="config">The server configuration</param>
        /// <param name="spawner">The method handling the new connection.</param>
        private static async Task ListenToSocketInternalAsync(Func <CancellationToken, Task <KeyValuePair <long, EndPoint> > > acceptAsync, bool usessl, CancellationToken stoptoken, ServerConfig config, Action <long, EndPoint, string, RunnerControl> spawner)
        {
            if (acceptAsync == null)
            {
                throw new ArgumentNullException(nameof(acceptAsync));
            }

            var rc = new RunnerControl(stoptoken, usessl, config);

            var taskid = SetLoggingSocketHandlerID();

            while (!stoptoken.IsCancellationRequested)
            {
                // Wait if there are too many active
                config.DebugLogHandler?.Invoke("Waiting for throttle", taskid, null);
                await rc.ThrottleTask;
                config.DebugLogHandler?.Invoke("Waiting for socket", taskid, null);
                var ls = acceptAsync(stoptoken);

                if (await Task.WhenAny(rc.StopTask, ls) == ls)
                {
                    config.DebugLogHandler?.Invoke("Re-waiting for socket", taskid, null);
                    var client    = await ls;
                    var newtaskid = SetLoggingTaskHandlerID();

                    try
                    {
                        int wt, cpt;
                        ThreadPool.GetAvailableThreads(out wt, out cpt);
                        config.DebugLogHandler?.Invoke(string.Format("Threadpool says {0}, {1}", wt, cpt), taskid, newtaskid);
                        config.DebugLogHandler?.Invoke(string.Format("Spawning runner with id: {0}", newtaskid), taskid, newtaskid);

                        ThreadPool.QueueUserWorkItem(x => spawner(client.Key, client.Value, newtaskid, rc));
                    }
                    catch (Exception ex)
                    {
                        config.DebugLogHandler?.Invoke("Failed to listen to socket", taskid, ex);
                    }
                }
            }

            config.DebugLogHandler?.Invoke("Stopping", taskid, null);
            rc.Stop(taskid);

            config.DebugLogHandler?.Invoke("Socket stopped, waiting for workers ...", taskid, null);
            await rc.FinishedTask;

            config.DebugLogHandler?.Invoke("Stopped", taskid, null);
        }
Example #5
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);
                }
            }
        }
Example #6
0
        /// <summary>
        /// Handler method for connections
        /// </summary>
        /// <param name="stream">The stream.</param>
        /// <param name="remoteEndPoint">The remote endpoint.</param>
        /// <param name="logtaskid">The task id for logging and tracing</param>
        /// <param name="controller">The runner controller.</param>
        private static async Task RunClient(Stream stream, EndPoint remoteEndPoint, string logtaskid, RunnerControl controller, Func <bool> isConnected)
        {
            var config  = controller.Config;
            var storage = config.Storage;

            using (stream)
                using (var ssl = controller.m_useSSL ? new SslStream(stream, false) : null)
                {
                    config.DebugLogHandler?.Invoke(string.Format("Running {0}", controller.m_useSSL ? "SSL" : "plain"), logtaskid, remoteEndPoint);

                    // Slightly higher value here to avoid races with the other timeout mechanisms
                    stream.ReadTimeout = stream.WriteTimeout = (controller.Config.RequestIdleTimeoutSeconds + 1) * 1000;

                    X509Certificate clientcert = null;

                    // For SSL only: negotiate the connection
                    if (ssl != null)
                    {
                        config.DebugLogHandler?.Invoke("Authenticate SSL", logtaskid, remoteEndPoint);

                        try
                        {
                            await ssl.AuthenticateAsServerAsync(config.SSLCertificate, config.SSLRequireClientCert, config.SSLEnabledProtocols, config.SSLCheckCertificateRevocation);
                        }
                        catch (Exception aex)
                        {
                            config.DebugLogHandler?.Invoke("Failed setting up SSL", logtaskid, remoteEndPoint);

                            // Log a message indicating that we failed setting up SSL
                            await LogMessageAsync(controller, new HttpContext(new HttpRequest(remoteEndPoint, logtaskid, logtaskid, null, SslProtocols.None, () => false), null, storage), aex, DateTime.Now, new TimeSpan());

                            return;
                        }

                        config.DebugLogHandler?.Invoke("Run SSL", logtaskid, remoteEndPoint);
                        clientcert = ssl.RemoteCertificate;
                    }

                    await Runner(ssl == null?(Stream)stream : ssl, remoteEndPoint, logtaskid, clientcert, ssl == null?SslProtocols.None : ssl.SslProtocol, controller, isConnected);

                    config.DebugLogHandler?.Invoke("Done running", logtaskid, remoteEndPoint);
                }
        }
Example #7
0
 /// <summary>
 /// Handler method for connections
 /// </summary>
 /// <param name="client">The new connection.</param>
 /// <param name="remoteEndPoint">The remote endpoint.</param>
 /// <param name="logtaskid">The task id for logging and tracing</param>
 /// <param name="controller">The runner controller.</param>
 private static async void RunClient(TcpClient client, EndPoint remoteEndPoint, string logtaskid, RunnerControl controller)
 {
     using (client)
         await RunClient(client.GetStream(), remoteEndPoint, logtaskid, controller, () => client.Connected);
 }
Example #8
0
        /// <summary>
        /// Runs a client, using a socket handle from DuplicateAndClose
        /// </summary>
        /// <param name="socketinfo">The socket handle.</param>
        /// <param name="remoteEndPoint">The remote endpoint.</param>
        /// <param name="logtaskid">The log task ID.</param>
        /// <param name="controller">The controller instance</param>
        private static void RunClient(SocketInformation socketinfo, EndPoint remoteEndPoint, string logtaskid, RunnerControl controller)
        {
            var client = new TcpClient()
            {
                Client = new Socket(socketinfo)
            };

            RunClient(client, remoteEndPoint, logtaskid, controller);
        }