190 lines
5.6 KiB
C#
190 lines
5.6 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
|
|
namespace EmbedIO.Net.Internal
|
|
{
|
|
internal class ResponseStream : Stream
|
|
{
|
|
private static readonly byte[] CrLf = { 13, 10 };
|
|
private readonly object _headersSyncRoot = new ();
|
|
|
|
private readonly Stream _stream;
|
|
private readonly HttpListenerResponse _response;
|
|
private readonly bool _ignoreErrors;
|
|
private bool _disposed;
|
|
private bool _trailerSent;
|
|
|
|
internal ResponseStream(Stream stream, HttpListenerResponse response, bool ignoreErrors)
|
|
{
|
|
_response = response;
|
|
_ignoreErrors = ignoreErrors;
|
|
_stream = stream;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override bool CanRead => false;
|
|
|
|
/// <inheritdoc />
|
|
public override bool CanSeek => false;
|
|
|
|
/// <inheritdoc />
|
|
public override bool CanWrite => true;
|
|
|
|
/// <inheritdoc />
|
|
public override long Length => throw new NotSupportedException();
|
|
|
|
/// <inheritdoc />
|
|
public override long Position
|
|
{
|
|
get => throw new NotSupportedException();
|
|
set => throw new NotSupportedException();
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Flush()
|
|
{
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Write(byte[] buffer, int offset, int count)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
throw new ObjectDisposedException(nameof(ResponseStream));
|
|
}
|
|
|
|
byte[] bytes;
|
|
var ms = GetHeaders(false);
|
|
var chunked = _response.SendChunked;
|
|
|
|
if (ms != null)
|
|
{
|
|
var start = ms.Position; // After the possible preamble for the encoding
|
|
ms.Position = ms.Length;
|
|
if (chunked)
|
|
{
|
|
bytes = GetChunkSizeBytes(count, false);
|
|
ms.Write(bytes, 0, bytes.Length);
|
|
}
|
|
|
|
var newCount = Math.Min(count, 16384 - (int)ms.Position + (int)start);
|
|
ms.Write(buffer, offset, newCount);
|
|
count -= newCount;
|
|
offset += newCount;
|
|
InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start));
|
|
ms.SetLength(0);
|
|
ms.Capacity = 0; // 'dispose' the buffer in ms.
|
|
}
|
|
else if (chunked)
|
|
{
|
|
bytes = GetChunkSizeBytes(count, false);
|
|
InternalWrite(bytes, 0, bytes.Length);
|
|
}
|
|
|
|
if (count > 0)
|
|
{
|
|
InternalWrite(buffer, offset, count);
|
|
}
|
|
|
|
if (chunked)
|
|
{
|
|
InternalWrite(CrLf, 0, 2);
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override int Read([In, Out] byte[] buffer, int offset, int count) => throw new NotSupportedException();
|
|
|
|
/// <inheritdoc />
|
|
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
|
|
|
|
/// <inheritdoc />
|
|
public override void SetLength(long value) => throw new NotSupportedException();
|
|
|
|
internal void InternalWrite(byte[] buffer, int offset, int count)
|
|
{
|
|
if (_ignoreErrors)
|
|
{
|
|
try
|
|
{
|
|
_stream.Write(buffer, offset, count);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_stream.Write(buffer, offset, count);
|
|
}
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (_disposed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_disposed = true;
|
|
|
|
if (!disposing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
using var ms = GetHeaders(true);
|
|
var chunked = _response.SendChunked;
|
|
|
|
if (_stream.CanWrite)
|
|
{
|
|
try
|
|
{
|
|
byte[] bytes;
|
|
if (ms != null)
|
|
{
|
|
var start = ms.Position;
|
|
if (chunked && !_trailerSent)
|
|
{
|
|
bytes = GetChunkSizeBytes(0, true);
|
|
ms.Position = ms.Length;
|
|
ms.Write(bytes, 0, bytes.Length);
|
|
}
|
|
|
|
InternalWrite(ms.ToArray(), (int)start, (int)(ms.Length - start));
|
|
_trailerSent = true;
|
|
}
|
|
else if (chunked && !_trailerSent)
|
|
{
|
|
bytes = GetChunkSizeBytes(0, true);
|
|
InternalWrite(bytes, 0, bytes.Length);
|
|
_trailerSent = true;
|
|
}
|
|
}
|
|
catch (ObjectDisposedException)
|
|
{
|
|
// Ignored
|
|
}
|
|
catch (IOException)
|
|
{
|
|
// Ignore error due to connection reset by peer
|
|
}
|
|
}
|
|
|
|
_response.Close();
|
|
}
|
|
|
|
private static byte[] GetChunkSizeBytes(int size, bool final) => WebServer.DefaultEncoding.GetBytes($"{size:x}\r\n{(final ? "\r\n" : string.Empty)}");
|
|
|
|
private MemoryStream? GetHeaders(bool closing)
|
|
{
|
|
lock (_headersSyncRoot)
|
|
{
|
|
return _response.HeadersSent ? null : _response.SendHeaders(closing);
|
|
}
|
|
}
|
|
}
|
|
} |