http: example fixes and security fixes
This commit is contained in:
parent
97e7d5db5d
commit
b4052c6460
8 changed files with 46 additions and 20 deletions
|
@ -51,8 +51,8 @@ static class Program
|
|||
router.Add(new CORS());
|
||||
router.Add("custom", new CustomRoute());
|
||||
router.Add("subpath", subpath);
|
||||
router.Add(new FileEndpoint("www-static"));
|
||||
router.Add(new FileEndpoint("www-dynamic"));
|
||||
router.Add(new FileEndpoint("www-static") { Index = false });
|
||||
router.Add(new FileEndpoint("www-dynamic") { Index = false });
|
||||
router.Add("", Root);
|
||||
router.Add(new StaticEndpoint(HttpResponse.NotFound("File not found")));
|
||||
return router;
|
||||
|
@ -109,7 +109,7 @@ static class Program
|
|||
if (req.IsWebsocket)
|
||||
return Websocket(req, info);
|
||||
|
||||
byte[] indexFile = await File.ReadAllBytesAsync("www-static/index.htm");
|
||||
byte[] indexFile = await File.ReadAllBytesAsync("www-static/index.html");
|
||||
HttpContent html = new HttpContent(new MIMEType("text", "html"), indexFile);
|
||||
return HttpResponse.OK(html);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div style="padding-left:20px;">
|
||||
<a href="./websocket">Websocket test</a><br>
|
||||
<a href="./custom/foo/bar">Custom route</a><br>
|
||||
<a href="./test">test.htm</a><br>
|
||||
<a href="./test">test.html</a><br>
|
||||
<br>
|
||||
<a href="./subpath/foo">Foo</a><br>
|
||||
<a href="./subpath/bar">Bar</a><br>
|
|
@ -42,7 +42,7 @@ public sealed class HttpClient
|
|||
innerStream = ssl;
|
||||
}
|
||||
|
||||
HttpStream stream = new HttpStream(innerStream);
|
||||
HttpStream stream = new HttpStream(innerStream, TimeSpan.FromSeconds(60));
|
||||
|
||||
//Send request
|
||||
await FixRequest(request, host, ConnectionType.Close).WriteTo(stream);
|
||||
|
@ -136,7 +136,7 @@ public sealed class HttpClient
|
|||
innerStream = ssl;
|
||||
}
|
||||
|
||||
stream = new HttpStream(innerStream);
|
||||
stream = new HttpStream(innerStream, Timeout);
|
||||
}
|
||||
|
||||
try
|
||||
|
|
|
@ -56,7 +56,7 @@ public sealed class HttpServer
|
|||
public event Action<IPEndPoint, Exception>? OnException;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum time the socket may be inactive before it is presumed dead and closed.
|
||||
/// The maximum time a socket may be inactive before it is presumed dead and closed.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout = TimeSpan.FromSeconds(20);
|
||||
|
||||
|
@ -139,11 +139,11 @@ public sealed class HttpServer
|
|||
//HTTP request-response loop
|
||||
while (client.Connected)
|
||||
{
|
||||
HttpStream httpStream = new HttpStream(stream);
|
||||
HttpStream httpStream = new HttpStream(stream, Timeout);
|
||||
try
|
||||
{
|
||||
HttpClientInfo clientInfo = new HttpClientInfo(client, endpoint);
|
||||
HttpRequest req = await httpStream.ReadRequest().WaitAsync(Timeout);
|
||||
HttpRequest req = await httpStream.ReadRequest();
|
||||
|
||||
//Parse path
|
||||
ArraySegment<string> pathSpl = req.Path.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
@ -161,14 +161,14 @@ public sealed class HttpServer
|
|||
HttpResponse? response = (await Router.GetResponse(req, clientInfo, pathSpl)) ?? HttpResponse.NotFound("Router produced no response");
|
||||
|
||||
OnResponse?.Invoke(req, clientInfo, response);
|
||||
await response.WriteTo(httpStream).WaitAsync(Timeout);
|
||||
await response.WriteTo(httpStream);
|
||||
|
||||
if (response is SwitchingProtocols swp)
|
||||
{
|
||||
//Create and run websocket
|
||||
WebsocketRemote ws = new WebsocketRemote(req, clientInfo, httpStream, swp.Fields.WebSocketProtocol);
|
||||
CloseStatus closeStatus = await swp.Callback(ws);
|
||||
await ws.Close(closeStatus).WaitAsync(Timeout);
|
||||
await ws.Close(closeStatus);
|
||||
break; //Close
|
||||
}
|
||||
|
||||
|
|
|
@ -23,15 +23,25 @@ class HttpStream : IDisposable
|
|||
/// </summary>
|
||||
readonly Decoder Decoder;
|
||||
|
||||
public HttpStream(Stream stream) : base()
|
||||
/// <summary>
|
||||
/// The maximum time the socket may be inactive before it is presumed dead and closed.
|
||||
/// </summary>
|
||||
public TimeSpan Timeout;
|
||||
|
||||
public HttpStream(Stream stream, TimeSpan timeout) : base()
|
||||
{
|
||||
Stream = stream;
|
||||
Timeout = timeout;
|
||||
Buffer = new BufferedStream(stream);
|
||||
Decoder = Encoding.ASCII.GetDecoder();
|
||||
|
||||
}
|
||||
|
||||
public async ValueTask<string> ReadLine()
|
||||
{
|
||||
CancellationTokenSource cancelSrc = new CancellationTokenSource();
|
||||
cancelSrc.CancelAfter(Timeout);
|
||||
|
||||
const int maxChars = 4096;
|
||||
byte[] dataBuffer = ArrayPool<byte>.Shared.Rent(1);
|
||||
char[] charBuffer = ArrayPool<char>.Shared.Rent(maxChars);
|
||||
|
@ -40,7 +50,7 @@ class HttpStream : IDisposable
|
|||
int charBufferIndex = 0;
|
||||
while (true)
|
||||
{
|
||||
if (await Buffer.ReadAsync(dataBuffer.AsMemory(0, 1)) == 0)
|
||||
if (await Buffer.ReadAsync(dataBuffer.AsMemory(0, 1), cancelSrc.Token) == 0)
|
||||
if (charBufferIndex == 0)
|
||||
throw new SocketException((int)SocketError.ConnectionReset);
|
||||
else
|
||||
|
@ -77,12 +87,15 @@ class HttpStream : IDisposable
|
|||
|
||||
public async ValueTask<int> Read(Memory<byte> buffer)
|
||||
{
|
||||
CancellationTokenSource cancelSrc = new CancellationTokenSource();
|
||||
cancelSrc.CancelAfter(Timeout);
|
||||
|
||||
try
|
||||
{
|
||||
int index = 0;
|
||||
while (index < buffer.Length)
|
||||
{
|
||||
int count = await Buffer.ReadAsync(buffer[index..]);
|
||||
int count = await Buffer.ReadAsync(buffer[index..], cancelSrc.Token);
|
||||
if (count == 0)
|
||||
break;
|
||||
|
||||
|
@ -102,8 +115,11 @@ class HttpStream : IDisposable
|
|||
|
||||
public ValueTask Write(string text)
|
||||
{
|
||||
CancellationTokenSource cancelSrc = new CancellationTokenSource();
|
||||
cancelSrc.CancelAfter(Timeout);
|
||||
|
||||
byte[] data = Encoding.ASCII.GetBytes(text);
|
||||
return Buffer.WriteAsync(data);
|
||||
return Buffer.WriteAsync(data, cancelSrc.Token);
|
||||
}
|
||||
|
||||
public ValueTask WriteLine(string text)
|
||||
|
@ -118,9 +134,11 @@ class HttpStream : IDisposable
|
|||
|
||||
public ValueTask Write(ReadOnlyMemory<byte> bytes)
|
||||
{
|
||||
CancellationTokenSource cancelSrc = new CancellationTokenSource();
|
||||
cancelSrc.CancelAfter(Timeout);
|
||||
try
|
||||
{
|
||||
return Buffer.WriteAsync(bytes);
|
||||
return Buffer.WriteAsync(bytes, cancelSrc.Token);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
@ -133,10 +151,12 @@ class HttpStream : IDisposable
|
|||
|
||||
public async Task Flush()
|
||||
{
|
||||
CancellationTokenSource cancelSrc = new CancellationTokenSource();
|
||||
cancelSrc.CancelAfter(Timeout);
|
||||
try
|
||||
{
|
||||
await Buffer.FlushAsync();
|
||||
await Stream.FlushAsync();
|
||||
await Buffer.FlushAsync(cancelSrc.Token);
|
||||
await Stream.FlushAsync(cancelSrc.Token);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
|
|
|
@ -58,6 +58,11 @@ public partial class FileEndpoint : RouterBase
|
|||
/// </summary>
|
||||
public string Directory;
|
||||
|
||||
/// <summary>
|
||||
/// If true, an empty path will be interpreted as "index.html".
|
||||
/// </summary>
|
||||
public bool Index = true;
|
||||
|
||||
public override HttpMethod Method => HttpMethod.GET;
|
||||
|
||||
public override int Arguments => 1;
|
||||
|
@ -73,12 +78,13 @@ public partial class FileEndpoint : RouterBase
|
|||
if (FilenameChecker().IsMatch(asset))
|
||||
return HttpResponse.BadRequest("Illegal chars in asset path");
|
||||
|
||||
return await GetFile(asset);
|
||||
HttpContent? file = await GetFile(asset);
|
||||
return file == null ? null : HttpResponse.OK(file);
|
||||
}
|
||||
|
||||
protected async ValueTask<HttpContent?> GetFile(string asset)
|
||||
{
|
||||
if (asset == "")
|
||||
if (Index && asset == "")
|
||||
asset = "index.html";
|
||||
|
||||
string assetPath = $"{Directory}/{asset}";
|
||||
|
|
Loading…
Reference in a new issue