416 lines
11 KiB
C#
416 lines
11 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.Security;
|
|
using System.Net.Sockets;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace EmbedIO.Net.Internal
|
|
{
|
|
internal sealed partial class HttpConnection : IDisposable
|
|
{
|
|
private const int BufferSize = 8192;
|
|
|
|
private readonly Timer _timer;
|
|
private readonly EndPointListener _epl;
|
|
private Socket? _sock;
|
|
private MemoryStream? _ms;
|
|
private byte[]? _buffer;
|
|
private HttpListenerContext _context;
|
|
private StringBuilder? _currentLine;
|
|
private RequestStream? _iStream;
|
|
private ResponseStream? _oStream;
|
|
private bool _contextBound;
|
|
private int _sTimeout = 90000; // 90k ms for first request, 15k ms from then on
|
|
private HttpListener? _lastListener;
|
|
private InputState _inputState = InputState.RequestLine;
|
|
private LineState _lineState = LineState.None;
|
|
private int _position;
|
|
private string? _errorMessage;
|
|
|
|
public HttpConnection(Socket sock, EndPointListener epl)
|
|
{
|
|
_sock = sock;
|
|
_epl = epl;
|
|
IsSecure = epl.Secure;
|
|
LocalEndPoint = (IPEndPoint) sock.LocalEndPoint;
|
|
RemoteEndPoint = (IPEndPoint) sock.RemoteEndPoint;
|
|
|
|
Stream = new NetworkStream(sock, false);
|
|
if (IsSecure)
|
|
{
|
|
var sslStream = new SslStream(Stream, true);
|
|
|
|
try
|
|
{
|
|
sslStream.AuthenticateAsServer(epl.Listener.Certificate);
|
|
}
|
|
catch
|
|
{
|
|
CloseSocket();
|
|
throw;
|
|
}
|
|
|
|
Stream = sslStream;
|
|
}
|
|
|
|
_timer = new Timer(OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
|
|
_context = null!; // Silence warning about uninitialized field - _context will be initialized by the Init method
|
|
Init();
|
|
}
|
|
|
|
public int Reuses { get; private set; }
|
|
|
|
public Stream Stream { get; }
|
|
|
|
public IPEndPoint LocalEndPoint { get; }
|
|
|
|
public IPEndPoint RemoteEndPoint { get; }
|
|
|
|
public bool IsSecure { get; }
|
|
|
|
public ListenerPrefix? Prefix { get; set; }
|
|
|
|
public void Dispose()
|
|
{
|
|
Close(true);
|
|
|
|
_timer.Dispose();
|
|
_sock?.Dispose();
|
|
_ms?.Dispose();
|
|
_iStream?.Dispose();
|
|
_oStream?.Dispose();
|
|
Stream?.Dispose();
|
|
_lastListener?.Dispose();
|
|
}
|
|
|
|
public async Task BeginReadRequest()
|
|
{
|
|
_buffer ??= new byte[BufferSize];
|
|
|
|
try
|
|
{
|
|
if (Reuses == 1)
|
|
{
|
|
_sTimeout = 15000;
|
|
}
|
|
|
|
_ = _timer.Change(_sTimeout, Timeout.Infinite);
|
|
|
|
var data = await Stream.ReadAsync(_buffer, 0, BufferSize).ConfigureAwait(false);
|
|
await OnReadInternal(data).ConfigureAwait(false);
|
|
}
|
|
catch
|
|
{
|
|
_ = _timer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
CloseSocket();
|
|
Unbind();
|
|
}
|
|
}
|
|
|
|
public RequestStream GetRequestStream(long contentLength)
|
|
{
|
|
if (_iStream == null)
|
|
{
|
|
var buffer = _ms.ToArray();
|
|
var length = (int) _ms.Length;
|
|
_ms = null;
|
|
|
|
_iStream = new RequestStream(Stream, buffer, _position, length - _position, contentLength);
|
|
}
|
|
|
|
return _iStream;
|
|
}
|
|
|
|
public ResponseStream GetResponseStream() => _oStream ??= new ResponseStream(Stream, _context.HttpListenerResponse, _context.Listener?.IgnoreWriteExceptions ?? true);
|
|
|
|
internal void SetError(string message) => _errorMessage = message;
|
|
|
|
internal void ForceClose() => Close(true);
|
|
|
|
internal void Close(bool forceClose = false)
|
|
{
|
|
if (_sock != null)
|
|
{
|
|
_oStream?.Dispose();
|
|
_oStream = null;
|
|
}
|
|
|
|
if (_sock == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
forceClose = forceClose
|
|
|| !_context.Request.KeepAlive
|
|
|| _context.Response.Headers["connection"] == "close";
|
|
|
|
if (!forceClose)
|
|
{
|
|
if (_context.HttpListenerRequest.FlushInput())
|
|
{
|
|
Reuses++;
|
|
Unbind();
|
|
Init();
|
|
_ = BeginReadRequest();
|
|
return;
|
|
}
|
|
}
|
|
|
|
using (var s = _sock)
|
|
{
|
|
_sock = null;
|
|
try
|
|
{
|
|
s?.Shutdown(SocketShutdown.Both);
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
|
|
Unbind();
|
|
RemoveConnection();
|
|
}
|
|
|
|
private void Init()
|
|
{
|
|
_contextBound = false;
|
|
_iStream = null;
|
|
_oStream = null;
|
|
Prefix = null;
|
|
_ms = new MemoryStream();
|
|
_position = 0;
|
|
_inputState = InputState.RequestLine;
|
|
_lineState = LineState.None;
|
|
_context = new HttpListenerContext(this);
|
|
}
|
|
|
|
private void OnTimeout(object unused)
|
|
{
|
|
CloseSocket();
|
|
Unbind();
|
|
}
|
|
|
|
private async Task OnReadInternal(int offset)
|
|
{
|
|
_ = _timer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
|
|
// Continue reading until full header is received.
|
|
// Especially important for multipart requests when the second part of the header arrives after a tiny delay
|
|
// because the web browser has to measure the content length first.
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
await _ms.WriteAsync(_buffer, 0, offset).ConfigureAwait(false);
|
|
if (_ms.Length > 32768)
|
|
{
|
|
Close(true);
|
|
return;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
CloseSocket();
|
|
Unbind();
|
|
return;
|
|
}
|
|
|
|
if (offset == 0)
|
|
{
|
|
CloseSocket();
|
|
Unbind();
|
|
return;
|
|
}
|
|
|
|
if (ProcessInput(_ms))
|
|
{
|
|
if (_errorMessage is null)
|
|
{
|
|
_context.HttpListenerRequest.FinishInitialization();
|
|
}
|
|
|
|
if (_errorMessage != null || !_epl.BindContext(_context))
|
|
{
|
|
Close(true);
|
|
return;
|
|
}
|
|
|
|
var listener = _context.Listener;
|
|
if (_lastListener != listener)
|
|
{
|
|
RemoveConnection();
|
|
listener.AddConnection(this);
|
|
_lastListener = listener;
|
|
}
|
|
|
|
_contextBound = true;
|
|
listener.RegisterContext(_context);
|
|
return;
|
|
}
|
|
|
|
offset = await Stream.ReadAsync(_buffer, 0, BufferSize).ConfigureAwait(false);
|
|
}
|
|
}
|
|
|
|
private void RemoveConnection()
|
|
{
|
|
if (_lastListener != null)
|
|
{
|
|
_lastListener.RemoveConnection(this);
|
|
}
|
|
else
|
|
{
|
|
_epl.RemoveConnection(this);
|
|
}
|
|
}
|
|
|
|
// true -> done processing
|
|
// false -> need more input
|
|
private bool ProcessInput(MemoryStream ms)
|
|
{
|
|
var buffer = ms.ToArray();
|
|
var len = (int)ms.Length;
|
|
var used = 0;
|
|
|
|
while (true)
|
|
{
|
|
if (_errorMessage != null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (_position >= len)
|
|
{
|
|
break;
|
|
}
|
|
|
|
string? line;
|
|
try
|
|
{
|
|
line = ReadLine(buffer, _position, len - _position, out used);
|
|
_position += used;
|
|
}
|
|
catch
|
|
{
|
|
_errorMessage = "Bad request";
|
|
return true;
|
|
}
|
|
|
|
if (line == null)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(line))
|
|
{
|
|
if (_inputState == InputState.RequestLine)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
_currentLine = null;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (_inputState == InputState.RequestLine)
|
|
{
|
|
_context.HttpListenerRequest.SetRequestLine(line);
|
|
_inputState = InputState.Headers;
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
_context.HttpListenerRequest.AddHeader(line);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
_errorMessage = e.Message;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (used == len)
|
|
{
|
|
ms.SetLength(0);
|
|
_position = 0;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private string? ReadLine(byte[] buffer, int offset, int len, out int used)
|
|
{
|
|
_currentLine ??= new StringBuilder(128);
|
|
|
|
var last = offset + len;
|
|
used = 0;
|
|
for (var i = offset; i < last && _lineState != LineState.Lf; i++)
|
|
{
|
|
used++;
|
|
var b = buffer[i];
|
|
|
|
switch (b)
|
|
{
|
|
case 13:
|
|
_lineState = LineState.Cr;
|
|
break;
|
|
case 10:
|
|
_lineState = LineState.Lf;
|
|
break;
|
|
default:
|
|
_ = _currentLine.Append((char)b);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (_lineState != LineState.Lf)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
_lineState = LineState.None;
|
|
var result = _currentLine.ToString();
|
|
_currentLine.Length = 0;
|
|
return result;
|
|
}
|
|
|
|
private void Unbind()
|
|
{
|
|
if (!_contextBound)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_epl.UnbindContext(_context);
|
|
_contextBound = false;
|
|
}
|
|
|
|
private void CloseSocket()
|
|
{
|
|
if (_sock == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
_sock.Dispose();
|
|
}
|
|
finally
|
|
{
|
|
_sock = null;
|
|
}
|
|
|
|
RemoveConnection();
|
|
}
|
|
}
|
|
} |