Skip to content

Instantly share code, notes, and snippets.

@ranma42
Last active May 24, 2024 10:16
Show Gist options
  • Save ranma42/a528555972f16c17ed1b840bfe7fbf5c to your computer and use it in GitHub Desktop.
Save ranma42/a528555972f16c17ed1b840bfe7fbf5c to your computer and use it in GitHub Desktop.
Partial response hack
public class LazyStream(
long length,
Func<Task<Stream>> factory
) : Stream {
public override long Position {
get => _position;
set {
if (value < 0 || value > length) {
throw new ArgumentOutOfRangeException(nameof(value), value, "Position must be between 0 and the length of the stream.");
}
if (value != _position) {
_position = value;
ResetStream();
}
}
}
public override long Length => length;
public override bool CanWrite => false;
public override bool CanSeek => true;
public override bool CanRead => true;
private long _position;
private Stream? _stream;
public override int Read(byte[] buffer, int offset, int count) =>
throw new NotSupportedException("Synchronous reads are not supported.");
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken) {
if (buffer.IsEmpty || Position >= Length) {
return 0;
}
_stream ??= await factory();
var result = await _stream.ReadAsync(buffer, cancellationToken);
_position += result;
return result;
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) =>
ReadAsync(buffer.AsMemory().Slice(offset, count), cancellationToken).AsTask();
public override long Seek(long offset, SeekOrigin origin) =>
Position = origin switch {
SeekOrigin.Begin => offset,
SeekOrigin.Current => Position + offset,
SeekOrigin.End => length + offset,
};
public override void SetLength(long value) => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
public override void Flush() => throw new NotSupportedException();
private void ResetStream() {
_stream?.Dispose();
_stream = null;
}
protected override void Dispose(bool disposing) {
ResetStream();
base.Dispose(disposing);
}
}
app.MapGet("/example1/{key}", async (string key, IStore store) =>
TypedResults.Stream
new LazyStream(
await store.GetSize(key),
() => httpContext.Response.GetTypedHeaders().ContentRange is { } contentRange
? store.GetPartialContent(key, new(contentRange.From, contentRange.To))
: store.GetContent(key),
)
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment