Exemplo n.º 1
0
        private async Task SendInternalServerErrorResponse(TcpClient client, EndPoint remoteEndPoint, Exception ex, RenegadeDispatcher dispatcher, CancellationToken cancellationToken)
        {
            dispatcher.InvokeAsync(() =>
            {
                Engine.ConsoleOutput($"[{nameof(PackageServer)}]: Client {remoteEndPoint} triggered internal server error '{ex.Message}'\n");
            });

            NetworkStream clientStream = client.GetStream();

            StringBuilder responseHeaderBuilder = new StringBuilder();

            responseHeaderBuilder.Append("HTTP/1.0 500 Internal Server Error");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("DATE: ").Append(DateTime.UtcNow);
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("CONTENT-LENGTH: 0");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("CONTENT-TYPE: application/octet-stream");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("ACCEPT-RANGES: bytes");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("CONNECTION: close");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append(Environment.NewLine);

            byte[] responseHeader = Encoding.UTF8.GetBytes(responseHeaderBuilder.ToString());
            await clientStream.WriteAsync(responseHeader, 0, responseHeader.Length, cancellationToken);
        }
Exemplo n.º 2
0
        private async Task SendNotFoundResponse(TcpClient client, EndPoint remoteEndPoint, string filePath, RenegadeDispatcher dispatcher, CancellationToken cancellationToken)
        {
            dispatcher.InvokeAsync(() =>
            {
                Engine.ConsoleOutput($"[{nameof(PackageServer)}]: Client {remoteEndPoint} tried to request file '{filePath}' that does not exist\n");
            });

            NetworkStream clientStream = client.GetStream();

            StringBuilder responseHeaderBuilder = new StringBuilder();

            responseHeaderBuilder.Append("HTTP/1.0 404 Not Found");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("DATE: ").Append(DateTime.UtcNow);
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("CONTENT-LENGTH: 0");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("CONTENT-TYPE: application/octet-stream");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("ACCEPT-RANGES: bytes");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append("CONNECTION: close");
            responseHeaderBuilder.Append(Environment.NewLine);
            responseHeaderBuilder.Append(Environment.NewLine);

            byte[] responseHeader = Encoding.UTF8.GetBytes(responseHeaderBuilder.ToString());
            await clientStream.WriteAsync(responseHeader, 0, responseHeader.Length, cancellationToken);
        }
Exemplo n.º 3
0
        private async Task SendFileToClient(TcpClient client, EndPoint remoteEndPoint, string requestTargetPath, RenegadeDispatcher dispatcher, CancellationToken cancellationToken)
        {
            string fullPath = Path.GetFullPath(Path.Combine(ttfsPath, requestTargetPath));

            try
            {
                using (FileStream fileStream = File.OpenRead(fullPath))
                {
                    NetworkStream clientStream = client.GetStream();

                    StringBuilder responseHeaderBuilder = new StringBuilder();
                    responseHeaderBuilder.Append("HTTP/1.0 200 OK");
                    responseHeaderBuilder.Append(Environment.NewLine);
                    responseHeaderBuilder.Append("DATE: ").Append(DateTime.UtcNow);
                    responseHeaderBuilder.Append(Environment.NewLine);
                    responseHeaderBuilder.Append("CONTENT-LENGTH: ").Append(fileStream.Length);
                    responseHeaderBuilder.Append(Environment.NewLine);
                    responseHeaderBuilder.Append("CONTENT-TYPE: application/octet-stream");
                    responseHeaderBuilder.Append(Environment.NewLine);
                    responseHeaderBuilder.Append("ACCEPT-RANGES: bytes");
                    responseHeaderBuilder.Append(Environment.NewLine);
                    responseHeaderBuilder.Append("CONNECTION: keep-alive");
                    responseHeaderBuilder.Append(Environment.NewLine);
                    responseHeaderBuilder.Append(Environment.NewLine);

                    byte[] responseHeader = Encoding.UTF8.GetBytes(responseHeaderBuilder.ToString());
                    await clientStream.WriteAsync(responseHeader, 0, responseHeader.Length, cancellationToken);

                    await fileStream.CopyToAsync(clientStream, ClientUploadFileBufferSize, cancellationToken);
                }
            }
            catch (FileNotFoundException)
            {
                // Client did something wrong
                await SendNotFoundResponse(client, remoteEndPoint, fullPath, dispatcher, cancellationToken);
            }
            catch (IOException ex)
            {
                if (ex.InnerException is SocketException socketException)
                {
                    if (
                        socketException.SocketErrorCode == SocketError.Interrupted ||
                        socketException.SocketErrorCode == SocketError.ConnectionAborted ||
                        socketException.SocketErrorCode == SocketError.ConnectionRefused ||
                        socketException.SocketErrorCode == SocketError.ConnectionReset)
                    {
                        throw new OperationCanceledException(); // Connection closed
                    }
                }

                // We did something wrong
                await SendInternalServerErrorResponse(client, remoteEndPoint, ex, dispatcher, cancellationToken);
            }
        }
Exemplo n.º 4
0
        private async Task HandleClientAsync(TcpClient client, EndPoint remoteEndPoint, RenegadeDispatcher dispatcher, CancellationToken cancellationToken)
        {
            try
            {
                TTFSHTTPRequestParser requestParser = new TTFSHTTPRequestParser(ClientGetRequestBufferSize);

                NetworkStream clientStream = client.GetStream();

                while (!requestParser.IsBufferFull && !cancellationToken.IsCancellationRequested)
                {
                    TTFSHTTPReadResult readResult = await requestParser.ReadAsync(clientStream, cancellationToken);

                    if (readResult == TTFSHTTPReadResult.PeerSocketClosed)
                    {
                        // We're done here, client closed connection
                        return;
                    }

                    // We've succesfully parsed the entire request, or it was invalid
                    if (readResult == TTFSHTTPReadResult.Done || readResult == TTFSHTTPReadResult.InvalidData)
                    {
                        break;
                    }
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    // We're done here

                    return;
                }

                if (ValidateClientRequest(requestParser))
                {
                    await SendFileToClient(client, remoteEndPoint, requestParser.RequestTargetPath, dispatcher, cancellationToken);
                }
                else
                {
                    // Client did something wrong
                    await SendBadRequestResponse(client, remoteEndPoint, dispatcher, cancellationToken);
                }
            }
            catch (SocketException ex)
            {
                if (
                    ex.SocketErrorCode == SocketError.Interrupted ||
                    ex.SocketErrorCode == SocketError.ConnectionAborted ||
                    ex.SocketErrorCode == SocketError.ConnectionRefused ||
                    ex.SocketErrorCode == SocketError.ConnectionReset)
                {
                    throw new OperationCanceledException();
                }
                else
                {
                    dispatcher.InvokeAsync(() =>
                    {
                        Engine.ConsoleOutput($"[{nameof(PackageServer)}]: Client {remoteEndPoint} triggered internal server error '{ex.Message}'\n");
                    });
                }
            }
            catch (ObjectDisposedException) when(cancellationToken.IsCancellationRequested)
            {
                throw new OperationCanceledException();
            }
        }
Exemplo n.º 5
0
        private (TcpClient Client, EndPoint RemoteEndPoint)? AcceptClient(RenegadeDispatcher dispatcher)
        {
            try
            {
                TcpClient client = listener.AcceptTcpClient();
                try
                {
                    // Save the endpoint, as this may throw an exception
                    EndPoint remoteEndPoint = client.Client.RemoteEndPoint;

                    lock (connectionCounts)
                    {
                        // Only check for exceeding max connection count if we have specified a max count
                        if (maxConnectionCount > 0)
                        {
                            int currentConnectionCount = GetCounnectionCount(remoteEndPoint);
                            if (currentConnectionCount >= maxConnectionCount)
                            {
                                client.Dispose();

                                return(null);
                            }
                        }

                        IncreaseConnectionCount(remoteEndPoint);

                        return(client, remoteEndPoint);
                    }
                }
                catch (Exception) // Odd, but I guess this can happen
                {
                    client.Dispose();

                    throw;
                }
            }
            catch (SocketException ex)
            {
                if (
                    ex.SocketErrorCode == SocketError.Interrupted ||
                    ex.SocketErrorCode == SocketError.ConnectionAborted ||
                    ex.SocketErrorCode == SocketError.ConnectionRefused ||
                    ex.SocketErrorCode == SocketError.ConnectionReset)
                {
                    throw new OperationCanceledException();
                }
                else
                {
                    dispatcher.InvokeAsync(() =>
                    {
                        Engine.ConsoleOutput($"[{nameof(PackageServer)}]: Client error '{ex.Message}'\n");
                    });

                    return(null); // Some communication error could've happen, should not stop everything
                }
            }
            catch (ObjectDisposedException) when(cancellationTokenSource.IsCancellationRequested)
            {
                throw new OperationCanceledException(); // May be thrown when cancelled
            }
        }
Exemplo n.º 6
0
        private void ServerLoop()
        {
            RenegadeDispatcher dispatcher = Engine.Dispatcher;

            dispatcher.MaxQueueSize = MaxDispatcherQueueSize; // Prevent clients from flooding the dispatcher queue

            try
            {
                // Keep going until a cancellation has been requested
                while (!cancellationTokenSource.IsCancellationRequested)
                {
                    // Try get a client, this may return null when a client has a lot of connections open
                    var acceptedClient = AcceptClient(dispatcher);
                    if (!acceptedClient.HasValue)
                    {
                        continue;
                    }

                    // Make sure a client can timeout, or else a client can keep a connection open for a very long time (by only sending 1 byte or something like that)
                    CancellationTokenSource timeoutCancellationTokenSource = null;
                    CancellationTokenSource linkedCancellationTokenSource  = null;
                    if (clientTimeout.TotalMinutes > 0)
                    {
                        timeoutCancellationTokenSource = new CancellationTokenSource(clientTimeout);
                        linkedCancellationTokenSource  = CancellationTokenSource.CreateLinkedTokenSource(
                            cancellationTokenSource.Token,
                            timeoutCancellationTokenSource.Token);
                    }

                    // First task can timeout
                    Task.Run(async() =>
                    {
                        await HandleClientAsync(
                            acceptedClient.Value.Client,
                            acceptedClient.Value.RemoteEndPoint,
                            dispatcher,
                            timeoutCancellationTokenSource == null ? cancellationTokenSource.Token : timeoutCancellationTokenSource.Token);
                    }, timeoutCancellationTokenSource == null ? cancellationTokenSource.Token : timeoutCancellationTokenSource.Token)
                    .ContinueWith(delegate // Second task cannot timeout or be canceled, since we need to do some cleanup stuff
                    {
                        try
                        {
                            // Check if we timed out
                            if (timeoutCancellationTokenSource != null && timeoutCancellationTokenSource.IsCancellationRequested)
                            {
                                dispatcher.InvokeAsync(() =>
                                {
                                    Engine.ConsoleOutput($"[{nameof(PackageServer)}]: Client {acceptedClient.Value.RemoteEndPoint} timed out after {clientTimeout.TotalMinutes} minute(s)\n");
                                });
                            }

                            // Clean up stuff
                            timeoutCancellationTokenSource?.Dispose();
                            linkedCancellationTokenSource?.Dispose();

                            acceptedClient.Value.Client.Dispose();
                        }
                        finally
                        {
                            // Make sure we always decrement this, so that the thread can exit when waiting for connections to get closed
                            DecreaseConnectionCount(acceptedClient.Value.RemoteEndPoint);
                        }
                    });
                }
            }
            catch (OperationCanceledException)
            {
                // Accept may throw this, can still shutdown normally
            }
            catch (ObjectDisposedException) when(cancellationTokenSource.IsCancellationRequested)
            {
                // Can happen, should not prevent for normal shutdown
            }
            catch (SocketException ex) when(
                ex.SocketErrorCode == SocketError.Interrupted ||
                ex.SocketErrorCode == SocketError.ConnectionAborted ||
                ex.SocketErrorCode == SocketError.ConnectionRefused ||
                ex.SocketErrorCode == SocketError.ConnectionReset)
            {
                // When we stop the listener and are in the middle of a blocking accept, this exception may be thrown
            }
            catch (Exception ex)
            {
                dispatcher.InvokeAsync(() =>
                {
                    Engine.ConsoleOutput($"[{nameof(PackageServer)}]: Fatal exception in file server thread '{ex.Message}'\n");
                });

                throw;
            }

            // Wait until all client connections are closed
            lock (connectionCounts)
            {
                while (connectionCounts.Count > 0)
                {
                    Monitor.Wait(connectionCounts);
                }
            }
        }