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); }
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); }
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 } }
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(); } }
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); } } }