HttpListener.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. using System;
  2. using System.Net;
  3. using System.Net.Sockets;
  4. using System.Net.Security;
  5. using System.Security.Authentication;
  6. using System.Security.Cryptography.X509Certificates;
  7. using System.Threading;
  8. namespace OSHttpServer
  9. {
  10. public class OSHttpListener: IDisposable
  11. {
  12. private readonly IPAddress m_address;
  13. private readonly X509Certificate m_certificate;
  14. private readonly IHttpContextFactory m_contextFactory;
  15. private readonly int m_port;
  16. private readonly ManualResetEvent m_shutdownEvent = new ManualResetEvent(false);
  17. private readonly SslProtocols m_sslProtocol = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Ssl3 | SslProtocols.Ssl2;
  18. private TcpListener m_listener;
  19. private ILogWriter m_logWriter = NullLogWriter.Instance;
  20. private int m_pendingAccepts;
  21. private bool m_shutdown;
  22. protected RemoteCertificateValidationCallback m_clientCertValCallback = null;
  23. public event EventHandler<ClientAcceptedEventArgs> Accepted;
  24. public event ExceptionHandler ExceptionThrown;
  25. public event EventHandler<RequestEventArgs> RequestReceived;
  26. /// <summary>
  27. /// Listen for regular HTTP connections
  28. /// </summary>
  29. /// <param name="address">IP Address to accept connections on</param>
  30. /// <param name="port">TCP Port to listen on, default HTTP port is 80.</param>
  31. /// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
  32. /// <exception cref="ArgumentNullException"><c>address</c> is null.</exception>
  33. /// <exception cref="ArgumentException">Port must be a positive number.</exception>
  34. protected OSHttpListener(IPAddress address, int port)
  35. {
  36. m_address = address;
  37. m_port = port;
  38. m_contextFactory = new HttpContextFactory(m_logWriter);
  39. m_contextFactory.RequestReceived += OnRequestReceived;
  40. }
  41. /// <summary>
  42. /// Initializes a new instance of the <see cref="OSHttpListener"/> class.
  43. /// </summary>
  44. /// <param name="address">IP Address to accept connections on</param>
  45. /// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
  46. /// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
  47. /// <param name="certificate">Certificate to use</param>
  48. protected OSHttpListener(IPAddress address, int port, X509Certificate certificate)
  49. : this(address, port)
  50. {
  51. m_certificate = certificate;
  52. }
  53. /// <summary>
  54. /// Initializes a new instance of the <see cref="OSHttpListener"/> class.
  55. /// </summary>
  56. /// <param name="address">IP Address to accept connections on</param>
  57. /// <param name="port">TCP Port to listen on, default HTTPS port is 443</param>
  58. /// <param name="factory">Factory used to create <see cref="IHttpClientContext"/>es.</param>
  59. /// <param name="certificate">Certificate to use</param>
  60. /// <param name="protocol">which HTTPS protocol to use, default is TLS.</param>
  61. protected OSHttpListener(IPAddress address, int port, X509Certificate certificate,
  62. SslProtocols protocol)
  63. : this(address, port)
  64. {
  65. m_certificate = certificate;
  66. m_sslProtocol = protocol;
  67. }
  68. public static OSHttpListener Create(IPAddress address, int port)
  69. {
  70. return new OSHttpListener(address, port);
  71. }
  72. public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate)
  73. {
  74. return new OSHttpListener(address, port, certificate);
  75. }
  76. public static OSHttpListener Create(IPAddress address, int port, X509Certificate certificate, SslProtocols protocol)
  77. {
  78. return new OSHttpListener(address, port, certificate, protocol);
  79. }
  80. private void OnRequestReceived(object sender, RequestEventArgs e)
  81. {
  82. RequestReceived?.Invoke(sender, e);
  83. }
  84. public RemoteCertificateValidationCallback CertificateValidationCallback
  85. {
  86. set { m_clientCertValCallback = value; }
  87. }
  88. /// <summary>
  89. /// Gives you a change to receive log entries for all internals of the HTTP library.
  90. /// </summary>
  91. /// <remarks>
  92. /// You may not switch log writer after starting the listener.
  93. /// </remarks>
  94. public ILogWriter LogWriter
  95. {
  96. get { return m_logWriter; }
  97. set
  98. {
  99. m_logWriter = value ?? NullLogWriter.Instance;
  100. if (m_certificate != null)
  101. m_logWriter.Write(this, LogPrio.Info,
  102. "HTTPS(" + m_sslProtocol + ") listening on " + m_address + ":" + m_port);
  103. else
  104. m_logWriter.Write(this, LogPrio.Info, "HTTP listening on " + m_address + ":" + m_port);
  105. }
  106. }
  107. /// <summary>
  108. /// True if we should turn on trace logs.
  109. /// </summary>
  110. public bool UseTraceLogs { get; set; }
  111. /// <exception cref="Exception"><c>Exception</c>.</exception>
  112. private void OnAccept(IAsyncResult ar)
  113. {
  114. bool beginAcceptCalled = false;
  115. try
  116. {
  117. int count = Interlocked.Decrement(ref m_pendingAccepts);
  118. if (m_shutdown)
  119. {
  120. if (count == 0)
  121. m_shutdownEvent.Set();
  122. return;
  123. }
  124. Interlocked.Increment(ref m_pendingAccepts);
  125. m_listener.BeginAcceptSocket(OnAccept, null);
  126. beginAcceptCalled = true;
  127. Socket socket = m_listener.EndAcceptSocket(ar);
  128. if (!socket.Connected)
  129. {
  130. socket.Dispose();
  131. return;
  132. }
  133. if (!OnAcceptingSocket(socket))
  134. {
  135. socket.Disconnect(true);
  136. return;
  137. }
  138. if(socket.Connected)
  139. {
  140. socket.NoDelay = true;
  141. m_logWriter.Write(this, LogPrio.Debug, "Accepted connection from: " + socket.RemoteEndPoint);
  142. if (m_certificate != null)
  143. m_contextFactory.CreateSecureContext(socket, m_certificate, m_sslProtocol, m_clientCertValCallback);
  144. else
  145. m_contextFactory.CreateContext(socket);
  146. }
  147. else
  148. socket.Dispose();
  149. }
  150. catch (Exception err)
  151. {
  152. m_logWriter.Write(this, LogPrio.Debug, err.Message);
  153. ExceptionThrown?.Invoke(this, err);
  154. if (!beginAcceptCalled)
  155. RetryBeginAccept();
  156. }
  157. }
  158. /// <summary>
  159. /// Will try to accept connections one more time.
  160. /// </summary>
  161. /// <exception cref="Exception">If any exceptions is thrown.</exception>
  162. private void RetryBeginAccept()
  163. {
  164. try
  165. {
  166. m_logWriter.Write(this, LogPrio.Error, "Trying to accept connections again.");
  167. m_listener.BeginAcceptSocket(OnAccept, null);
  168. }
  169. catch (Exception err)
  170. {
  171. m_logWriter.Write(this, LogPrio.Fatal, err.Message);
  172. ExceptionThrown?.Invoke(this, err);
  173. }
  174. }
  175. /// <summary>
  176. /// Can be used to create filtering of new connections.
  177. /// </summary>
  178. /// <param name="socket">Accepted socket</param>
  179. /// <returns>true if connection can be accepted; otherwise false.</returns>
  180. protected bool OnAcceptingSocket(Socket socket)
  181. {
  182. ClientAcceptedEventArgs args = new ClientAcceptedEventArgs(socket);
  183. Accepted?.Invoke(this, args);
  184. return !args.Revoked;
  185. }
  186. /// <summary>
  187. /// Start listen for new connections
  188. /// </summary>
  189. /// <param name="backlog">Number of connections that can stand in a queue to be accepted.</param>
  190. /// <exception cref="InvalidOperationException">Listener have already been started.</exception>
  191. public void Start(int backlog)
  192. {
  193. if (m_listener != null)
  194. throw new InvalidOperationException("Listener have already been started.");
  195. m_listener = new TcpListener(m_address, m_port);
  196. m_listener.Start(backlog);
  197. Interlocked.Increment(ref m_pendingAccepts);
  198. m_listener.BeginAcceptSocket(OnAccept, null);
  199. }
  200. /// <summary>
  201. /// Stop the listener
  202. /// </summary>
  203. /// <exception cref="SocketException"></exception>
  204. public void Stop()
  205. {
  206. m_shutdown = true;
  207. m_contextFactory.Shutdown();
  208. m_listener.Stop();
  209. if (!m_shutdownEvent.WaitOne())
  210. m_logWriter.Write(this, LogPrio.Error, "Failed to shutdown listener properly.");
  211. m_listener = null;
  212. Dispose();
  213. }
  214. public void Dispose()
  215. {
  216. Dispose(true);
  217. GC.SuppressFinalize(this);
  218. }
  219. protected void Dispose(bool disposing)
  220. {
  221. if (m_shutdownEvent != null)
  222. {
  223. m_shutdownEvent.Dispose();
  224. }
  225. }
  226. }
  227. }