Compare commits

...

2 commits

Author SHA1 Message Date
uwaa
57293135a2 http: extra close statuses 2025-01-20 22:28:12 +00:00
uwaa
a0db75bfdd http: longer timeouts for ws 2025-01-20 22:28:00 +00:00
4 changed files with 40 additions and 17 deletions

View file

@ -60,6 +60,11 @@ public sealed class HttpServer
/// </summary> /// </summary>
public TimeSpan Timeout = TimeSpan.FromSeconds(20); public TimeSpan Timeout = TimeSpan.FromSeconds(20);
/// <summary>
/// The maximum time a websocket may be inactive before it is presumed dead and closed.
/// </summary>
public TimeSpan TimeoutWS = TimeSpan.FromSeconds(60);
readonly Dictionary<IPAddress, int> IPCounts = new Dictionary<IPAddress, int>(); readonly Dictionary<IPAddress, int> IPCounts = new Dictionary<IPAddress, int>();
readonly SemaphoreSlim IPCountsLock = new SemaphoreSlim(1, 1); readonly SemaphoreSlim IPCountsLock = new SemaphoreSlim(1, 1);
@ -165,6 +170,9 @@ public sealed class HttpServer
if (response is SwitchingProtocols swp) if (response is SwitchingProtocols swp)
{ {
httpStream.Timeout = TimeoutWS;
httpStream.KeepAlive();
//Create and run websocket //Create and run websocket
WebsocketRemote ws = new WebsocketRemote(req, clientInfo, httpStream, swp.Fields.WebSocketProtocol); WebsocketRemote ws = new WebsocketRemote(req, clientInfo, httpStream, swp.Fields.WebSocketProtocol);
CloseStatus closeStatus = await swp.Callback(ws); CloseStatus closeStatus = await swp.Callback(ws);

View file

@ -1,8 +1,6 @@
using System.Buffers; using System.Buffers;
using System.Collections.Specialized;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
using System.Web;
namespace Uwaa.HTTP; namespace Uwaa.HTTP;
@ -28,19 +26,27 @@ class HttpStream : IDisposable
/// </summary> /// </summary>
public TimeSpan Timeout; public TimeSpan Timeout;
internal readonly CancellationTokenSource CancelSrc = new CancellationTokenSource();
public HttpStream(Stream stream, TimeSpan timeout) : base() public HttpStream(Stream stream, TimeSpan timeout) : base()
{ {
Stream = stream; Stream = stream;
Timeout = timeout; Timeout = timeout;
Buffer = new BufferedStream(stream); Buffer = new BufferedStream(stream);
Decoder = Encoding.ASCII.GetDecoder(); Decoder = Encoding.ASCII.GetDecoder();
}
/// <summary>
/// Resets the timeout timer.
/// </summary>
public void KeepAlive()
{
CancelSrc.CancelAfter(Timeout);
} }
public async ValueTask<string> ReadLine() public async ValueTask<string> ReadLine()
{ {
CancellationTokenSource cancelSrc = new CancellationTokenSource(); CancelSrc.CancelAfter(Timeout);
cancelSrc.CancelAfter(Timeout);
const int maxChars = 4096; const int maxChars = 4096;
byte[] dataBuffer = ArrayPool<byte>.Shared.Rent(1); byte[] dataBuffer = ArrayPool<byte>.Shared.Rent(1);
@ -50,7 +56,7 @@ class HttpStream : IDisposable
int charBufferIndex = 0; int charBufferIndex = 0;
while (true) while (true)
{ {
if (await Buffer.ReadAsync(dataBuffer.AsMemory(0, 1), cancelSrc.Token) == 0) if (await Stream.ReadAsync(dataBuffer.AsMemory(0, 1), CancelSrc.Token) == 0)
if (charBufferIndex == 0) if (charBufferIndex == 0)
throw new SocketException((int)SocketError.ConnectionReset); throw new SocketException((int)SocketError.ConnectionReset);
else else
@ -87,15 +93,14 @@ class HttpStream : IDisposable
public async ValueTask<int> Read(Memory<byte> buffer) public async ValueTask<int> Read(Memory<byte> buffer)
{ {
CancellationTokenSource cancelSrc = new CancellationTokenSource(); CancelSrc.CancelAfter(Timeout);
cancelSrc.CancelAfter(Timeout);
try try
{ {
int index = 0; int index = 0;
while (index < buffer.Length) while (index < buffer.Length)
{ {
int count = await Buffer.ReadAsync(buffer[index..], cancelSrc.Token); int count = await Stream.ReadAsync(buffer[index..], CancelSrc.Token);
if (count == 0) if (count == 0)
break; break;
@ -134,11 +139,10 @@ class HttpStream : IDisposable
public ValueTask Write(ReadOnlyMemory<byte> bytes) public ValueTask Write(ReadOnlyMemory<byte> bytes)
{ {
CancellationTokenSource cancelSrc = new CancellationTokenSource(); CancelSrc.CancelAfter(Timeout);
cancelSrc.CancelAfter(Timeout);
try try
{ {
return Buffer.WriteAsync(bytes, cancelSrc.Token); return Buffer.WriteAsync(bytes, CancelSrc.Token);
} }
catch (IOException e) catch (IOException e)
{ {
@ -151,12 +155,11 @@ class HttpStream : IDisposable
public async Task Flush() public async Task Flush()
{ {
CancellationTokenSource cancelSrc = new CancellationTokenSource(); CancelSrc.CancelAfter(Timeout);
cancelSrc.CancelAfter(Timeout);
try try
{ {
await Buffer.FlushAsync(cancelSrc.Token); await Buffer.FlushAsync(CancelSrc.Token);
await Stream.FlushAsync(cancelSrc.Token); await Stream.FlushAsync(CancelSrc.Token);
} }
catch (IOException e) catch (IOException e)
{ {

View file

@ -12,5 +12,8 @@ public enum CloseStatus : ushort
PolicyViolation = 1008, PolicyViolation = 1008,
MessageTooBig = 1009, MessageTooBig = 1009,
MandatoryExtension = 1010, MandatoryExtension = 1010,
InternalServerError = 1011 InternalServerError = 1011,
ServiceRestart = 1012,
TryAgainLater = 1013,
BadGateway = 1014,
} }

View file

@ -14,6 +14,15 @@ public class Websocket
/// </summary> /// </summary>
public readonly string? SubProtocol; public readonly string? SubProtocol;
/// <summary>
/// The maximum time the websocket may be inactive before it is presumed dead and closed.
/// </summary>
public TimeSpan Timeout
{
get => Stream.Timeout;
set => Stream.Timeout = value;
}
internal readonly HttpStream Stream; internal readonly HttpStream Stream;
readonly List<byte> finalPayload = new List<byte>(); readonly List<byte> finalPayload = new List<byte>();