Got at least one data fetching method working; turns out, we can't use a patched LogicStack to get the data

This commit is contained in:
2026-01-14 22:11:11 +01:00
parent 40a8431464
commit 3f7122d30a
350 changed files with 41444 additions and 119 deletions

View File

@@ -0,0 +1,87 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace EmbedIO.Internal
{
// Wraps a response's output stream, buffering all data
// in a MemoryStream.
// When disposed, sets the response's ContentLength and copies all data
// to the output stream.
internal class BufferingResponseStream : Stream
{
private readonly IHttpResponse _response;
private readonly MemoryStream _buffer;
public BufferingResponseStream(IHttpResponse response)
{
_response = response;
_buffer = new MemoryStream();
}
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => _buffer.Length;
public override long Position
{
get => _buffer.Position;
set => throw SeekingNotSupported();
}
public override void Flush() => _buffer.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) => _buffer.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count) => throw ReadingNotSupported();
public override int ReadByte() => throw ReadingNotSupported();
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
=> throw ReadingNotSupported();
public override int EndRead(IAsyncResult asyncResult) => throw ReadingNotSupported();
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw ReadingNotSupported();
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
=> throw ReadingNotSupported();
public override long Seek(long offset, SeekOrigin origin) => throw SeekingNotSupported();
public override void SetLength(long value) => throw SeekingNotSupported();
public override void Write(byte[] buffer, int offset, int count) => _buffer.Write(buffer, offset, count);
public override void WriteByte(byte value) => _buffer.WriteByte(value);
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
=> _buffer.BeginWrite(buffer, offset, count, callback, state);
public override void EndWrite(IAsyncResult asyncResult) => _buffer.EndWrite(asyncResult);
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
=> _buffer.WriteAsync(buffer, offset, count, cancellationToken);
protected override void Dispose(bool disposing)
{
_response.ContentLength64 = _buffer.Length;
_buffer.Position = 0;
_buffer.CopyTo(_response.OutputStream);
if (disposing)
{
_buffer.Dispose();
}
}
private static Exception ReadingNotSupported() => new NotSupportedException("This stream does not support reading.");
private static Exception SeekingNotSupported() => new NotSupportedException("This stream does not support seeking.");
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Threading;
using System.Threading.Tasks;
namespace EmbedIO.Internal
{
internal class CompressionStream : Stream
{
private readonly Stream _target;
private readonly bool _leaveOpen;
public CompressionStream(Stream target, CompressionMethod compressionMethod)
{
switch (compressionMethod)
{
case CompressionMethod.Deflate:
_target = new DeflateStream(target, CompressionMode.Compress, true);
_leaveOpen = false;
break;
case CompressionMethod.Gzip:
_target = new GZipStream(target, CompressionMode.Compress, true);
_leaveOpen = false;
break;
default:
_target = target;
_leaveOpen = true;
break;
}
UncompressedLength = 0;
}
public long UncompressedLength { get; private set; }
public override bool CanRead => false;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => throw SeekingNotSupported();
public override long Position
{
get => throw SeekingNotSupported();
set => throw SeekingNotSupported();
}
public override void Flush() => _target.Flush();
public override Task FlushAsync(CancellationToken cancellationToken) => _target.FlushAsync(cancellationToken);
public override int Read(byte[] buffer, int offset, int count) => throw ReadingNotSupported();
public override int ReadByte() => throw ReadingNotSupported();
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
=> throw ReadingNotSupported();
public override int EndRead(IAsyncResult asyncResult) => throw ReadingNotSupported();
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) => throw ReadingNotSupported();
public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken)
=> throw ReadingNotSupported();
public override long Seek(long offset, SeekOrigin origin) => throw SeekingNotSupported();
public override void SetLength(long value) => throw SeekingNotSupported();
public override void Write(byte[] buffer, int offset, int count)
{
_target.Write(buffer, offset, count);
UncompressedLength += count;
}
public override void WriteByte(byte value)
{
_target.WriteByte(value);
UncompressedLength++;
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state)
=> _target.BeginWrite(
buffer,
offset,
count,
ar => {
UncompressedLength += count;
callback(ar);
},
state);
public override void EndWrite(IAsyncResult asyncResult)
{
_target.EndWrite(asyncResult);
}
public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await _target.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
UncompressedLength += count;
}
protected override void Dispose(bool disposing)
{
if (disposing && !_leaveOpen)
{
_target.Dispose();
}
base.Dispose(disposing);
}
private static Exception ReadingNotSupported() => new NotSupportedException("This stream does not support reading.");
private static Exception SeekingNotSupported() => new NotSupportedException("This stream does not support seeking.");
}
}

View File

@@ -0,0 +1,82 @@
using System.IO;
using System.IO.Compression;
namespace EmbedIO.Internal
{
internal static class CompressionUtility
{
public static byte[]? ConvertCompression(byte[] source, CompressionMethod sourceMethod, CompressionMethod targetMethod)
{
if (source == null)
return null;
if (sourceMethod == targetMethod)
return source;
switch (sourceMethod)
{
case CompressionMethod.Deflate:
using (var sourceStream = new MemoryStream(source, false))
{
using var decompressionStream = new DeflateStream(sourceStream, CompressionMode.Decompress, true);
using var targetStream = new MemoryStream();
if (targetMethod == CompressionMethod.Gzip)
{
using var compressionStream = new GZipStream(targetStream, CompressionMode.Compress, true);
decompressionStream.CopyTo(compressionStream);
}
else
{
decompressionStream.CopyTo(targetStream);
}
return targetStream.ToArray();
}
case CompressionMethod.Gzip:
using (var sourceStream = new MemoryStream(source, false))
{
using var decompressionStream = new GZipStream(sourceStream, CompressionMode.Decompress, true);
using var targetStream = new MemoryStream();
if (targetMethod == CompressionMethod.Deflate)
{
using var compressionStream = new DeflateStream(targetStream, CompressionMode.Compress, true);
decompressionStream.CopyToAsync(compressionStream);
}
else
{
decompressionStream.CopyTo(targetStream);
}
return targetStream.ToArray();
}
default:
using (var sourceStream = new MemoryStream(source, false))
{
using var targetStream = new MemoryStream();
switch (targetMethod)
{
case CompressionMethod.Deflate:
using (var compressionStream = new DeflateStream(targetStream, CompressionMode.Compress, true))
sourceStream.CopyTo(compressionStream);
break;
case CompressionMethod.Gzip:
using (var compressionStream = new GZipStream(targetStream, CompressionMode.Compress, true))
sourceStream.CopyTo(compressionStream);
break;
default:
// Just in case. Consider all other values as None.
return source;
}
return targetStream.ToArray();
}
}
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using EmbedIO.Utilities;
using Swan;
namespace EmbedIO.Internal
{
internal sealed class DummyWebModuleContainer : IWebModuleContainer
{
public static readonly IWebModuleContainer Instance = new DummyWebModuleContainer();
private DummyWebModuleContainer()
{
}
public IComponentCollection<IWebModule> Modules => throw UnexpectedCall();
public ConcurrentDictionary<object, object> SharedItems => throw UnexpectedCall();
public void Dispose()
{
}
private InternalErrorException UnexpectedCall([CallerMemberName] string member = "")
=> SelfCheck.Failure($"Unexpected call to {nameof(DummyWebModuleContainer)}.{member}.");
}
}

View File

@@ -0,0 +1,9 @@
using System.Collections.Specialized;
namespace EmbedIO.Internal
{
internal sealed class LockableNameValueCollection : NameValueCollection
{
public void MakeReadOnly() => IsReadOnly = true;
}
}

View File

@@ -0,0 +1,63 @@
using System.Collections.Generic;
using EmbedIO.Utilities;
using Swan.Configuration;
namespace EmbedIO.Internal
{
internal sealed class MimeTypeCustomizer : ConfiguredObject, IMimeTypeCustomizer
{
private readonly Dictionary<string, string> _customMimeTypes = new Dictionary<string, string>();
private readonly Dictionary<(string, string), bool> _data = new Dictionary<(string, string), bool>();
private bool? _defaultPreferCompression;
public string GetMimeType(string extension)
{
_customMimeTypes.TryGetValue(Validate.NotNull(nameof(extension), extension), out var result);
return result;
}
public bool TryDetermineCompression(string mimeType, out bool preferCompression)
{
var (type, subtype) = MimeType.UnsafeSplit(
Validate.MimeType(nameof(mimeType), mimeType, false));
if (_data.TryGetValue((type, subtype), out preferCompression))
return true;
if (_data.TryGetValue((type, "*"), out preferCompression))
return true;
if (!_defaultPreferCompression.HasValue)
return false;
preferCompression = _defaultPreferCompression.Value;
return true;
}
public void AddCustomMimeType(string extension, string mimeType)
{
EnsureConfigurationNotLocked();
_customMimeTypes[Validate.NotNullOrEmpty(nameof(extension), extension)]
= Validate.MimeType(nameof(mimeType), mimeType, false);
}
public void PreferCompression(string mimeType, bool preferCompression)
{
EnsureConfigurationNotLocked();
var (type, subtype) = MimeType.UnsafeSplit(
Validate.MimeType(nameof(mimeType), mimeType, true));
if (type == "*")
{
_defaultPreferCompression = preferCompression;
}
else
{
_data[(type, subtype)] = preferCompression;
}
}
public void Lock() => LockConfiguration();
}
}

View File

@@ -0,0 +1,15 @@
using System;
namespace EmbedIO.Internal
{
// This exception is only created and handled internally,
// so it doesn't need all the standard the bells and whistles.
#pragma warning disable CA1032 // Add standard exception constructors
#pragma warning disable CA1064 // Exceptions should be public
internal class RequestHandlerPassThroughException : Exception
{
}
#pragma warning restore CA1032
#pragma warning restore CA1064
}

View File

@@ -0,0 +1,27 @@
using System.Diagnostics;
namespace EmbedIO.Internal
{
/// <summary>
/// Represents a wrapper around Stopwatch.
/// </summary>
public sealed class TimeKeeper
{
private static readonly Stopwatch Stopwatch = Stopwatch.StartNew();
private readonly long _start;
/// <summary>
/// Initializes a new instance of the <see cref="TimeKeeper"/> class.
/// </summary>
public TimeKeeper()
{
_start = Stopwatch.ElapsedMilliseconds;
}
/// <summary>
/// Gets the elapsed time since the class was initialized.
/// </summary>
public long ElapsedTime => Stopwatch.ElapsedMilliseconds - _start;
}
}

View File

@@ -0,0 +1,47 @@
using System;
namespace EmbedIO.Internal
{
internal static class UriUtility
{
public static Uri StringToUri(string str)
{
_ = Uri.TryCreate(str, CanBeAbsoluteUrl(str) ? UriKind.Absolute : UriKind.Relative, out var result);
return result;
}
public static Uri? StringToAbsoluteUri(string str)
{
if (!CanBeAbsoluteUrl(str))
{
return null;
}
_ = Uri.TryCreate(str, UriKind.Absolute, out var result);
return result;
}
// Returns true if string starts with "http:", "https:", "ws:", or "wss:"
private static bool CanBeAbsoluteUrl(string str)
=> !string.IsNullOrEmpty(str)
&& str[0] switch {
'h' => str.Length >= 5
&& str[1] == 't'
&& str[2] == 't'
&& str[3] == 'p'
&& str[4] switch {
':' => true,
's' => str.Length >= 6 && str[5] == ':',
_ => false
},
'w' => str.Length >= 3
&& str[1] == 's'
&& str[2] switch {
':' => true,
's' => str.Length >= 4 && str[3] == ':',
_ => false
},
_ => false
};
}
}

View File

@@ -0,0 +1,46 @@
using System.Threading;
using System.Threading.Tasks;
using EmbedIO.Utilities;
using Swan.Logging;
namespace EmbedIO.Internal
{
internal sealed class WebModuleCollection : DisposableComponentCollection<IWebModule>
{
private readonly string _logSource;
internal WebModuleCollection(string logSource)
{
_logSource = logSource;
}
internal void StartAll(CancellationToken cancellationToken)
{
foreach (var (name, module) in WithSafeNames)
{
$"Starting module {name}...".Debug(_logSource);
module.Start(cancellationToken);
}
}
internal async Task DispatchRequestAsync(IHttpContext context)
{
if (context.IsHandled)
return;
var requestedPath = context.RequestedPath;
foreach (var (name, module) in WithSafeNames)
{
var routeMatch = module.MatchUrlPath(requestedPath);
if (routeMatch == null)
continue;
$"[{context.Id}] Processing with {name}.".Debug(_logSource);
context.GetImplementation().Route = routeMatch;
await module.HandleRequestAsync(context).ConfigureAwait(false);
if (context.IsHandled)
break;
}
}
}
}