using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Security; using System.Net.Sockets; using System.Security.Authentication; using System.Security.Cryptography.X509Certificates; namespace HttpServer { /// /// Used to create and reuse contexts. /// public class HttpContextFactory : IHttpContextFactory { private readonly int _bufferSize; private readonly Queue _contextQueue = new Queue(); private readonly IRequestParserFactory _factory; private readonly ILogWriter _logWriter; private readonly ContextTimeoutManager _contextTimeoutManager; // by Fumi.Iseki public static RemoteCertificateValidationCallback ClientCertificateValidationCallback = null; private RemoteCertificateValidationCallback _clientCallback = null; /// /// Initializes a new instance of the class. /// /// The writer. /// Amount of bytes to read from the incoming socket stream. /// Used to create a request parser. public HttpContextFactory(ILogWriter writer, int bufferSize, IRequestParserFactory factory) { _logWriter = writer; _bufferSize = bufferSize; _factory = factory; _contextTimeoutManager = new ContextTimeoutManager(ContextTimeoutManager.MonitorType.Thread); // by Fumi.Iseki if (ClientCertificateValidationCallback != null) { _clientCallback = ClientCertificateValidationCallback; ClientCertificateValidationCallback = null; } } /// /// True if detailed trace logs should be written. /// public bool UseTraceLogs { get; set; } /// /// Create a new context. /// /// true if socket is running HTTPS. /// Client that connected /// Network/SSL stream. /// A context. protected HttpClientContext CreateContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) { HttpClientContext context; lock (_contextQueue) { if (_contextQueue.Count > 0) { context = _contextQueue.Dequeue(); if (!context.Available) { context = CreateNewContext(isSecured, endPoint, stream, sock); context.Disconnected += OnFreeContext; context.RequestReceived += OnRequestReceived; context.EndWhenDone = true; } } else { context = CreateNewContext(isSecured, endPoint, stream, sock); context.Disconnected += OnFreeContext; context.RequestReceived += OnRequestReceived; } } context.Stream = stream; context.IsSecured = isSecured; context.RemotePort = endPoint.Port.ToString(); context.RemoteAddress = endPoint.Address.ToString(); _contextTimeoutManager.StartMonitoringContext(context); context.Start(); return context; } /// /// Create a new context. /// /// true if HTTPS is used. /// Remote client /// Network stream, uses . /// A new context (always). protected virtual HttpClientContext CreateNewContext(bool isSecured, IPEndPoint endPoint, Stream stream, Socket sock) { return new HttpClientContext(isSecured, endPoint, stream, _factory, _bufferSize, sock); } private void OnRequestReceived(object sender, RequestEventArgs e) { RequestReceived(sender, e); } private void OnFreeContext(object sender, DisconnectedEventArgs e) { var imp = (HttpClientContext) sender; imp.Cleanup(); if (!imp.EndWhenDone) { lock (_contextQueue) _contextQueue.Enqueue(imp); } else { imp.Close(); } } #region IHttpContextFactory Members /// /// Create a secure . /// /// Client socket (accepted by the ). /// HTTPS certificate to use. /// Kind of HTTPS protocol. Usually TLS or SSL. /// /// A created . /// public IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, SslProtocols protocol) { var networkStream = new ReusableSocketNetworkStream(socket, true); var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; // by Fumi.Iseki //var sslStream = new SslStream(networkStream, false); SslStream sslStream = null; try { //TODO: this may fail // by Fumi.Iseki //sslStream.AuthenticateAsServer(certificate, false, protocol, false); if (_clientCallback == null) { sslStream = new SslStream(networkStream, false); sslStream.AuthenticateAsServer(certificate, false, protocol, false); } else { sslStream = new SslStream(networkStream, false, new RemoteCertificateValidationCallback(_clientCallback)); sslStream.AuthenticateAsServer(certificate, true, protocol, false); } return CreateContext(true, remoteEndPoint, sslStream, socket); } catch (IOException err) { if (UseTraceLogs) _logWriter.Write(this, LogPrio.Trace, err.Message); } catch (ObjectDisposedException err) { if (UseTraceLogs) _logWriter.Write(this, LogPrio.Trace, err.Message); } return null; } /// /// A request have been received from one of the contexts. /// public event EventHandler RequestReceived = delegate{}; /// /// Creates a that handles a connected client. /// /// Client socket (accepted by the ). /// /// A creates . /// public IHttpClientContext CreateContext(Socket socket) { var networkStream = new ReusableSocketNetworkStream(socket, true); var remoteEndPoint = (IPEndPoint) socket.RemoteEndPoint; return CreateContext(false, remoteEndPoint, networkStream, socket); } #endregion /// /// Server is shutting down so shut down the factory /// public void Shutdown() { _contextTimeoutManager.StopMonitoring(); } } /// /// Custom network stream to mark sockets as reusable when disposing the stream. /// internal class ReusableSocketNetworkStream : NetworkStream { private bool disposed = false; /// /// Creates a new instance of the class for the specified . /// /// /// The that the will use to send and receive data. /// /// /// The parameter is null. /// /// /// The parameter is not connected. /// -or- /// The property of the parameter is not . /// -or- /// The parameter is in a nonblocking state. /// public ReusableSocketNetworkStream(Socket socket) : base(socket) { } /// /// Initializes a new instance of the class for the specified with the specified ownership. /// /// /// The that the will use to send and receive data. /// /// /// Set to true to indicate that the will take ownership of the ; otherwise, false. /// /// /// The parameter is null. /// /// /// The parameter is not connected. /// -or- /// the value of the property of the parameter is not . /// -or- /// the parameter is in a nonblocking state. /// public ReusableSocketNetworkStream(Socket socket, bool ownsSocket) : base(socket, ownsSocket) { } /// /// Creates a new instance of the class for the specified with the specified access rights. /// /// /// The that the will use to send and receive data. /// /// /// A bitwise combination of the values that specify the type of access given to the over the provided . /// /// /// The parameter is null. /// /// /// The parameter is not connected. /// -or- /// the property of the parameter is not . /// -or- /// the parameter is in a nonblocking state. /// public ReusableSocketNetworkStream(Socket socket, FileAccess access) : base(socket, access) { } /// /// Creates a new instance of the class for the specified with the specified access rights and the specified ownership. /// /// /// The that the will use to send and receive data. /// /// /// A bitwise combination of the values that specifies the type of access given to the over the provided . /// /// /// Set to true to indicate that the will take ownership of the ; otherwise, false. /// /// /// The parameter is null. /// /// /// The parameter is not connected. /// -or- /// The property of the parameter is not . /// -or- /// The parameter is in a nonblocking state. /// public ReusableSocketNetworkStream(Socket socket, FileAccess access, bool ownsSocket) : base(socket, access, ownsSocket) { } /// /// Closes the current stream and releases any resources (such as sockets and file handles) associated with the current stream. /// public override void Close() { if (Socket != null && Socket.Connected) Socket.Close(); //TODO: Maybe use Disconnect with reuseSocket=true? I tried but it took forever. base.Close(); } /// /// Releases the unmanaged resources used by the and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { try { if (!disposed) { disposed = true; if (Socket != null && Socket.Connected) Socket.Disconnect(true); } base.Dispose(disposing); } catch { } // Best effort, ignore fails } } /// /// Used to create es. /// public interface IHttpContextFactory { /// /// Creates a that handles a connected client. /// /// Client socket (accepted by the ). /// A creates . IHttpClientContext CreateContext(Socket socket); /// /// Create a secure . /// /// Client socket (accepted by the ). /// HTTPS certificate to use. /// Kind of HTTPS protocol. Usually TLS or SSL. /// A created . IHttpClientContext CreateSecureContext(Socket socket, X509Certificate certificate, SslProtocols protocol); /// /// A request have been received from one of the contexts. /// event EventHandler RequestReceived; /// /// Server is shutting down so shut down the factory /// void Shutdown(); } }