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;
}
///
public override bool CanRead => false;
///
public override bool CanSeek => false;
///
public override bool CanWrite => true;
///
public override long Length => throw new NotSupportedException();
///
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
///
public override void Flush()
{
}
///
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);
}
}
///
public override int Read([In, Out] byte[] buffer, int offset, int count) => throw new NotSupportedException();
///
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
///
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);
}
}
}
}