WebSocketServer.cs 32 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172
  1. #region License
  2. /*
  3. * WebSocketServer.cs
  4. *
  5. * The MIT License
  6. *
  7. * Copyright (c) 2012-2023 sta.blockhead
  8. *
  9. * Permission is hereby granted, free of charge, to any person obtaining a copy
  10. * of this software and associated documentation files (the "Software"), to deal
  11. * in the Software without restriction, including without limitation the rights
  12. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. * copies of the Software, and to permit persons to whom the Software is
  14. * furnished to do so, subject to the following conditions:
  15. *
  16. * The above copyright notice and this permission notice shall be included in
  17. * all copies or substantial portions of the Software.
  18. *
  19. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. * THE SOFTWARE.
  26. */
  27. #endregion
  28. #region Contributors
  29. /*
  30. * Contributors:
  31. * - Juan Manuel Lallana <juan.manuel.lallana@gmail.com>
  32. * - Jonas Hovgaard <j@jhovgaard.dk>
  33. * - Liryna <liryna.stark@gmail.com>
  34. * - Rohan Singh <rohan-singh@hotmail.com>
  35. */
  36. #endregion
  37. using System;
  38. using System.Collections.Generic;
  39. using System.Net.Sockets;
  40. using System.Security.Cryptography.X509Certificates;
  41. using System.Security.Principal;
  42. using System.Text;
  43. using System.Threading;
  44. using WebSocketSharp.Net;
  45. using WebSocketSharp.Net.WebSockets;
  46. namespace WebSocketSharp.Server
  47. {
  48. /// <summary>
  49. /// Provides a WebSocket protocol server.
  50. /// </summary>
  51. /// <remarks>
  52. /// This class can provide multiple WebSocket services.
  53. /// </remarks>
  54. public class WebSocketServer
  55. {
  56. #region Private Fields
  57. private System.Net.IPAddress _address;
  58. private AuthenticationSchemes _authSchemes;
  59. private static readonly string _defaultRealm;
  60. private bool _dnsStyle;
  61. private string _hostname;
  62. private TcpListener _listener;
  63. private Logger _log;
  64. private int _port;
  65. private string _realm;
  66. private string _realmInUse;
  67. private Thread _receiveThread;
  68. private bool _reuseAddress;
  69. private bool _secure;
  70. private WebSocketServiceManager _services;
  71. private ServerSslConfiguration _sslConfig;
  72. private ServerSslConfiguration _sslConfigInUse;
  73. private volatile ServerState _state;
  74. private object _sync;
  75. private Func<IIdentity, NetworkCredential> _userCredFinder;
  76. #endregion
  77. #region Static Constructor
  78. static WebSocketServer ()
  79. {
  80. _defaultRealm = "SECRET AREA";
  81. }
  82. #endregion
  83. #region Public Constructors
  84. /// <summary>
  85. /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
  86. /// </summary>
  87. /// <remarks>
  88. /// The new instance listens for incoming handshake requests on
  89. /// <see cref="System.Net.IPAddress.Any"/> and port 80.
  90. /// </remarks>
  91. public WebSocketServer ()
  92. {
  93. var addr = System.Net.IPAddress.Any;
  94. init (addr.ToString (), addr, 80, false);
  95. }
  96. /// <summary>
  97. /// Initializes a new instance of the <see cref="WebSocketServer"/> class
  98. /// with the specified port.
  99. /// </summary>
  100. /// <remarks>
  101. /// <para>
  102. /// The new instance listens for incoming handshake requests on
  103. /// <see cref="System.Net.IPAddress.Any"/> and <paramref name="port"/>.
  104. /// </para>
  105. /// <para>
  106. /// It provides secure connections if <paramref name="port"/> is 443.
  107. /// </para>
  108. /// </remarks>
  109. /// <param name="port">
  110. /// An <see cref="int"/> that specifies the number of the port on which
  111. /// to listen.
  112. /// </param>
  113. /// <exception cref="ArgumentOutOfRangeException">
  114. /// <paramref name="port"/> is less than 1 or greater than 65535.
  115. /// </exception>
  116. public WebSocketServer (int port)
  117. : this (port, port == 443)
  118. {
  119. }
  120. /// <summary>
  121. /// Initializes a new instance of the <see cref="WebSocketServer"/> class
  122. /// with the specified URL.
  123. /// </summary>
  124. /// <remarks>
  125. /// <para>
  126. /// The new instance listens for incoming handshake requests on
  127. /// the IP address and port of <paramref name="url"/>.
  128. /// </para>
  129. /// <para>
  130. /// Either port 80 or 443 is used if <paramref name="url"/> includes
  131. /// no port. Port 443 is used if the scheme of <paramref name="url"/>
  132. /// is wss; otherwise, port 80 is used.
  133. /// </para>
  134. /// <para>
  135. /// The new instance provides secure connections if the scheme of
  136. /// <paramref name="url"/> is wss.
  137. /// </para>
  138. /// </remarks>
  139. /// <param name="url">
  140. /// A <see cref="string"/> that specifies the WebSocket URL of the server.
  141. /// </param>
  142. /// <exception cref="ArgumentNullException">
  143. /// <paramref name="url"/> is <see langword="null"/>.
  144. /// </exception>
  145. /// <exception cref="ArgumentException">
  146. /// <para>
  147. /// <paramref name="url"/> is an empty string.
  148. /// </para>
  149. /// <para>
  150. /// -or-
  151. /// </para>
  152. /// <para>
  153. /// <paramref name="url"/> is invalid.
  154. /// </para>
  155. /// </exception>
  156. public WebSocketServer (string url)
  157. {
  158. if (url == null)
  159. throw new ArgumentNullException ("url");
  160. if (url.Length == 0)
  161. throw new ArgumentException ("An empty string.", "url");
  162. Uri uri;
  163. string msg;
  164. if (!tryCreateUri (url, out uri, out msg))
  165. throw new ArgumentException (msg, "url");
  166. var host = uri.DnsSafeHost;
  167. var addr = host.ToIPAddress ();
  168. if (addr == null) {
  169. msg = "The host part could not be converted to an IP address.";
  170. throw new ArgumentException (msg, "url");
  171. }
  172. if (!addr.IsLocal ()) {
  173. msg = "The IP address of the host is not a local IP address.";
  174. throw new ArgumentException (msg, "url");
  175. }
  176. init (host, addr, uri.Port, uri.Scheme == "wss");
  177. }
  178. /// <summary>
  179. /// Initializes a new instance of the <see cref="WebSocketServer"/> class
  180. /// with the specified port and boolean if secure or not.
  181. /// </summary>
  182. /// <remarks>
  183. /// The new instance listens for incoming handshake requests on
  184. /// <see cref="System.Net.IPAddress.Any"/> and <paramref name="port"/>.
  185. /// </remarks>
  186. /// <param name="port">
  187. /// An <see cref="int"/> that specifies the number of the port on which
  188. /// to listen.
  189. /// </param>
  190. /// <param name="secure">
  191. /// A <see cref="bool"/>: <c>true</c> if the new instance provides
  192. /// secure connections; otherwise, <c>false</c>.
  193. /// </param>
  194. /// <exception cref="ArgumentOutOfRangeException">
  195. /// <paramref name="port"/> is less than 1 or greater than 65535.
  196. /// </exception>
  197. public WebSocketServer (int port, bool secure)
  198. {
  199. if (!port.IsPortNumber ()) {
  200. var msg = "Less than 1 or greater than 65535.";
  201. throw new ArgumentOutOfRangeException ("port", msg);
  202. }
  203. var addr = System.Net.IPAddress.Any;
  204. init (addr.ToString (), addr, port, secure);
  205. }
  206. /// <summary>
  207. /// Initializes a new instance of the <see cref="WebSocketServer"/> class
  208. /// with the specified IP address and port.
  209. /// </summary>
  210. /// <remarks>
  211. /// <para>
  212. /// The new instance listens for incoming handshake requests on
  213. /// <paramref name="address"/> and <paramref name="port"/>.
  214. /// </para>
  215. /// <para>
  216. /// It provides secure connections if <paramref name="port"/> is 443.
  217. /// </para>
  218. /// </remarks>
  219. /// <param name="address">
  220. /// A <see cref="System.Net.IPAddress"/> that specifies the local IP
  221. /// address on which to listen.
  222. /// </param>
  223. /// <param name="port">
  224. /// An <see cref="int"/> that specifies the number of the port on which
  225. /// to listen.
  226. /// </param>
  227. /// <exception cref="ArgumentNullException">
  228. /// <paramref name="address"/> is <see langword="null"/>.
  229. /// </exception>
  230. /// <exception cref="ArgumentException">
  231. /// <paramref name="address"/> is not a local IP address.
  232. /// </exception>
  233. /// <exception cref="ArgumentOutOfRangeException">
  234. /// <paramref name="port"/> is less than 1 or greater than 65535.
  235. /// </exception>
  236. public WebSocketServer (System.Net.IPAddress address, int port)
  237. : this (address, port, port == 443)
  238. {
  239. }
  240. /// <summary>
  241. /// Initializes a new instance of the <see cref="WebSocketServer"/> class
  242. /// with the specified IP address, port, and boolean if secure or not.
  243. /// </summary>
  244. /// <remarks>
  245. /// The new instance listens for incoming handshake requests on
  246. /// <paramref name="address"/> and <paramref name="port"/>.
  247. /// </remarks>
  248. /// <param name="address">
  249. /// A <see cref="System.Net.IPAddress"/> that specifies the local IP
  250. /// address on which to listen.
  251. /// </param>
  252. /// <param name="port">
  253. /// An <see cref="int"/> that specifies the number of the port on which
  254. /// to listen.
  255. /// </param>
  256. /// <param name="secure">
  257. /// A <see cref="bool"/>: <c>true</c> if the new instance provides
  258. /// secure connections; otherwise, <c>false</c>.
  259. /// </param>
  260. /// <exception cref="ArgumentNullException">
  261. /// <paramref name="address"/> is <see langword="null"/>.
  262. /// </exception>
  263. /// <exception cref="ArgumentException">
  264. /// <paramref name="address"/> is not a local IP address.
  265. /// </exception>
  266. /// <exception cref="ArgumentOutOfRangeException">
  267. /// <paramref name="port"/> is less than 1 or greater than 65535.
  268. /// </exception>
  269. public WebSocketServer (System.Net.IPAddress address, int port, bool secure)
  270. {
  271. if (address == null)
  272. throw new ArgumentNullException ("address");
  273. if (!address.IsLocal ()) {
  274. var msg = "Not a local IP address.";
  275. throw new ArgumentException (msg, "address");
  276. }
  277. if (!port.IsPortNumber ()) {
  278. var msg = "Less than 1 or greater than 65535.";
  279. throw new ArgumentOutOfRangeException ("port", msg);
  280. }
  281. init (address.ToString (), address, port, secure);
  282. }
  283. #endregion
  284. #region Public Properties
  285. /// <summary>
  286. /// Gets the IP address of the server.
  287. /// </summary>
  288. /// <value>
  289. /// A <see cref="System.Net.IPAddress"/> that represents the local IP
  290. /// address on which to listen for incoming handshake requests.
  291. /// </value>
  292. public System.Net.IPAddress Address {
  293. get {
  294. return _address;
  295. }
  296. }
  297. /// <summary>
  298. /// Gets or sets the scheme used to authenticate the clients.
  299. /// </summary>
  300. /// <remarks>
  301. /// The set operation works if the current state of the server is
  302. /// Ready or Stop.
  303. /// </remarks>
  304. /// <value>
  305. /// <para>
  306. /// One of the <see cref="WebSocketSharp.Net.AuthenticationSchemes"/>
  307. /// enum values.
  308. /// </para>
  309. /// <para>
  310. /// It represents the scheme used to authenticate the clients.
  311. /// </para>
  312. /// <para>
  313. /// The default value is
  314. /// <see cref="WebSocketSharp.Net.AuthenticationSchemes.Anonymous"/>.
  315. /// </para>
  316. /// </value>
  317. public AuthenticationSchemes AuthenticationSchemes {
  318. get {
  319. return _authSchemes;
  320. }
  321. set {
  322. lock (_sync) {
  323. if (!canSet ())
  324. return;
  325. _authSchemes = value;
  326. }
  327. }
  328. }
  329. /// <summary>
  330. /// Gets a value indicating whether the server has started.
  331. /// </summary>
  332. /// <value>
  333. /// <c>true</c> if the server has started; otherwise, <c>false</c>.
  334. /// </value>
  335. public bool IsListening {
  336. get {
  337. return _state == ServerState.Start;
  338. }
  339. }
  340. /// <summary>
  341. /// Gets a value indicating whether the server provides secure connections.
  342. /// </summary>
  343. /// <value>
  344. /// <c>true</c> if the server provides secure connections; otherwise,
  345. /// <c>false</c>.
  346. /// </value>
  347. public bool IsSecure {
  348. get {
  349. return _secure;
  350. }
  351. }
  352. /// <summary>
  353. /// Gets or sets a value indicating whether the server cleans up
  354. /// the inactive sessions periodically.
  355. /// </summary>
  356. /// <remarks>
  357. /// The set operation works if the current state of the server is
  358. /// Ready or Stop.
  359. /// </remarks>
  360. /// <value>
  361. /// <para>
  362. /// <c>true</c> if the server cleans up the inactive sessions
  363. /// every 60 seconds; otherwise, <c>false</c>.
  364. /// </para>
  365. /// <para>
  366. /// The default value is <c>true</c>.
  367. /// </para>
  368. /// </value>
  369. public bool KeepClean {
  370. get {
  371. return _services.KeepClean;
  372. }
  373. set {
  374. _services.KeepClean = value;
  375. }
  376. }
  377. /// <summary>
  378. /// Gets the logging function for the server.
  379. /// </summary>
  380. /// <remarks>
  381. /// The default logging level is <see cref="LogLevel.Error"/>.
  382. /// </remarks>
  383. /// <value>
  384. /// A <see cref="Logger"/> that provides the logging function.
  385. /// </value>
  386. public Logger Log {
  387. get {
  388. return _log;
  389. }
  390. }
  391. /// <summary>
  392. /// Gets the port of the server.
  393. /// </summary>
  394. /// <value>
  395. /// An <see cref="int"/> that represents the number of the port on which
  396. /// to listen for incoming handshake requests.
  397. /// </value>
  398. public int Port {
  399. get {
  400. return _port;
  401. }
  402. }
  403. /// <summary>
  404. /// Gets or sets the name of the realm associated with the server.
  405. /// </summary>
  406. /// <remarks>
  407. /// The set operation works if the current state of the server is
  408. /// Ready or Stop.
  409. /// </remarks>
  410. /// <value>
  411. /// <para>
  412. /// A <see cref="string"/> that represents the name of the realm.
  413. /// </para>
  414. /// <para>
  415. /// "SECRET AREA" is used as the name of the realm if the value is
  416. /// <see langword="null"/> or an empty string.
  417. /// </para>
  418. /// <para>
  419. /// The default value is <see langword="null"/>.
  420. /// </para>
  421. /// </value>
  422. public string Realm {
  423. get {
  424. return _realm;
  425. }
  426. set {
  427. lock (_sync) {
  428. if (!canSet ())
  429. return;
  430. _realm = value;
  431. }
  432. }
  433. }
  434. /// <summary>
  435. /// Gets or sets a value indicating whether the server is allowed to
  436. /// be bound to an address that is already in use.
  437. /// </summary>
  438. /// <remarks>
  439. /// <para>
  440. /// You should set this property to <c>true</c> if you would like to
  441. /// resolve to wait for socket in TIME_WAIT state.
  442. /// </para>
  443. /// <para>
  444. /// The set operation works if the current state of the server is
  445. /// Ready or Stop.
  446. /// </para>
  447. /// </remarks>
  448. /// <value>
  449. /// <para>
  450. /// <c>true</c> if the server is allowed to be bound to an address
  451. /// that is already in use; otherwise, <c>false</c>.
  452. /// </para>
  453. /// <para>
  454. /// The default value is <c>false</c>.
  455. /// </para>
  456. /// </value>
  457. public bool ReuseAddress {
  458. get {
  459. return _reuseAddress;
  460. }
  461. set {
  462. lock (_sync) {
  463. if (!canSet ())
  464. return;
  465. _reuseAddress = value;
  466. }
  467. }
  468. }
  469. /// <summary>
  470. /// Gets the configuration for secure connection.
  471. /// </summary>
  472. /// <remarks>
  473. /// The configuration is used when the server attempts to start,
  474. /// so it must be configured before the start method is called.
  475. /// </remarks>
  476. /// <value>
  477. /// A <see cref="ServerSslConfiguration"/> that represents the
  478. /// configuration used to provide secure connections.
  479. /// </value>
  480. /// <exception cref="InvalidOperationException">
  481. /// The server does not provide secure connections.
  482. /// </exception>
  483. public ServerSslConfiguration SslConfiguration {
  484. get {
  485. if (!_secure) {
  486. var msg = "The server does not provide secure connections.";
  487. throw new InvalidOperationException (msg);
  488. }
  489. return getSslConfiguration ();
  490. }
  491. }
  492. /// <summary>
  493. /// Gets or sets the delegate used to find the credentials for an identity.
  494. /// </summary>
  495. /// <remarks>
  496. /// The set operation works if the current state of the server is
  497. /// Ready or Stop.
  498. /// </remarks>
  499. /// <value>
  500. /// <para>
  501. /// A <see cref="T:System.Func{IIdentity, NetworkCredential}"/>
  502. /// delegate.
  503. /// </para>
  504. /// <para>
  505. /// The delegate invokes the method called when the server finds
  506. /// the credentials used to authenticate a client.
  507. /// </para>
  508. /// <para>
  509. /// The method must return <see langword="null"/> if the credentials
  510. /// are not found.
  511. /// </para>
  512. /// <para>
  513. /// <see langword="null"/> if not necessary.
  514. /// </para>
  515. /// <para>
  516. /// The default value is <see langword="null"/>.
  517. /// </para>
  518. /// </value>
  519. public Func<IIdentity, NetworkCredential> UserCredentialsFinder {
  520. get {
  521. return _userCredFinder;
  522. }
  523. set {
  524. lock (_sync) {
  525. if (!canSet ())
  526. return;
  527. _userCredFinder = value;
  528. }
  529. }
  530. }
  531. /// <summary>
  532. /// Gets or sets the time to wait for the response to the WebSocket
  533. /// Ping or Close.
  534. /// </summary>
  535. /// <remarks>
  536. /// The set operation works if the current state of the server is
  537. /// Ready or Stop.
  538. /// </remarks>
  539. /// <value>
  540. /// <para>
  541. /// A <see cref="TimeSpan"/> that represents the time to wait for
  542. /// the response.
  543. /// </para>
  544. /// <para>
  545. /// The default value is the same as 1 second.
  546. /// </para>
  547. /// </value>
  548. /// <exception cref="ArgumentOutOfRangeException">
  549. /// The value specified for a set operation is zero or less.
  550. /// </exception>
  551. public TimeSpan WaitTime {
  552. get {
  553. return _services.WaitTime;
  554. }
  555. set {
  556. _services.WaitTime = value;
  557. }
  558. }
  559. /// <summary>
  560. /// Gets the management function for the WebSocket services provided by
  561. /// the server.
  562. /// </summary>
  563. /// <value>
  564. /// A <see cref="WebSocketServiceManager"/> that manages the WebSocket
  565. /// services provided by the server.
  566. /// </value>
  567. public WebSocketServiceManager WebSocketServices {
  568. get {
  569. return _services;
  570. }
  571. }
  572. #endregion
  573. #region Private Methods
  574. private void abort ()
  575. {
  576. lock (_sync) {
  577. if (_state != ServerState.Start)
  578. return;
  579. _state = ServerState.ShuttingDown;
  580. }
  581. try {
  582. _listener.Stop ();
  583. }
  584. catch (Exception ex) {
  585. _log.Fatal (ex.Message);
  586. _log.Debug (ex.ToString ());
  587. }
  588. try {
  589. _services.Stop (1006, String.Empty);
  590. }
  591. catch (Exception ex) {
  592. _log.Fatal (ex.Message);
  593. _log.Debug (ex.ToString ());
  594. }
  595. _state = ServerState.Stop;
  596. }
  597. private bool authenticateClient (TcpListenerWebSocketContext context)
  598. {
  599. if (_authSchemes == AuthenticationSchemes.Anonymous)
  600. return true;
  601. if (_authSchemes == AuthenticationSchemes.None)
  602. return false;
  603. var chal = new AuthenticationChallenge (_authSchemes, _realmInUse)
  604. .ToString ();
  605. var retry = -1;
  606. Func<bool> auth = null;
  607. auth =
  608. () => {
  609. retry++;
  610. if (retry > 99)
  611. return false;
  612. if (context.SetUser (_authSchemes, _realmInUse, _userCredFinder))
  613. return true;
  614. context.SendAuthenticationChallenge (chal);
  615. return auth ();
  616. };
  617. return auth ();
  618. }
  619. private bool canSet ()
  620. {
  621. return _state == ServerState.Ready || _state == ServerState.Stop;
  622. }
  623. private bool checkHostNameForRequest (string name)
  624. {
  625. return !_dnsStyle
  626. || Uri.CheckHostName (name) != UriHostNameType.Dns
  627. || name == _hostname;
  628. }
  629. private string getRealm ()
  630. {
  631. var realm = _realm;
  632. return realm != null && realm.Length > 0 ? realm : _defaultRealm;
  633. }
  634. private ServerSslConfiguration getSslConfiguration ()
  635. {
  636. if (_sslConfig == null)
  637. _sslConfig = new ServerSslConfiguration ();
  638. return _sslConfig;
  639. }
  640. private void init (
  641. string hostname, System.Net.IPAddress address, int port, bool secure
  642. )
  643. {
  644. _hostname = hostname;
  645. _address = address;
  646. _port = port;
  647. _secure = secure;
  648. _authSchemes = AuthenticationSchemes.Anonymous;
  649. _dnsStyle = Uri.CheckHostName (hostname) == UriHostNameType.Dns;
  650. _listener = new TcpListener (address, port);
  651. _log = new Logger ();
  652. _services = new WebSocketServiceManager (_log);
  653. _sync = new object ();
  654. }
  655. private void processRequest (TcpListenerWebSocketContext context)
  656. {
  657. if (!authenticateClient (context)) {
  658. context.Close (HttpStatusCode.Forbidden);
  659. return;
  660. }
  661. var uri = context.RequestUri;
  662. if (uri == null) {
  663. context.Close (HttpStatusCode.BadRequest);
  664. return;
  665. }
  666. var name = uri.DnsSafeHost;
  667. if (!checkHostNameForRequest (name)) {
  668. context.Close (HttpStatusCode.NotFound);
  669. return;
  670. }
  671. var path = uri.AbsolutePath;
  672. if (path.IndexOfAny (new[] { '%', '+' }) > -1)
  673. path = HttpUtility.UrlDecode (path, Encoding.UTF8);
  674. WebSocketServiceHost host;
  675. if (!_services.InternalTryGetServiceHost (path, out host)) {
  676. context.Close (HttpStatusCode.NotImplemented);
  677. return;
  678. }
  679. host.StartSession (context);
  680. }
  681. private void receiveRequest ()
  682. {
  683. while (true) {
  684. TcpClient cl = null;
  685. try {
  686. cl = _listener.AcceptTcpClient ();
  687. ThreadPool.QueueUserWorkItem (
  688. state => {
  689. try {
  690. var ctx = new TcpListenerWebSocketContext (
  691. cl, null, _secure, _sslConfigInUse, _log
  692. );
  693. processRequest (ctx);
  694. }
  695. catch (Exception ex) {
  696. _log.Error (ex.Message);
  697. _log.Debug (ex.ToString ());
  698. cl.Close ();
  699. }
  700. }
  701. );
  702. }
  703. catch (SocketException ex) {
  704. if (_state == ServerState.ShuttingDown)
  705. return;
  706. _log.Fatal (ex.Message);
  707. _log.Debug (ex.ToString ());
  708. break;
  709. }
  710. catch (InvalidOperationException ex) {
  711. if (_state == ServerState.ShuttingDown)
  712. return;
  713. _log.Fatal (ex.Message);
  714. _log.Debug (ex.ToString ());
  715. break;
  716. }
  717. catch (Exception ex) {
  718. _log.Fatal (ex.Message);
  719. _log.Debug (ex.ToString ());
  720. if (cl != null)
  721. cl.Close ();
  722. if (_state == ServerState.ShuttingDown)
  723. return;
  724. break;
  725. }
  726. }
  727. abort ();
  728. }
  729. private void start ()
  730. {
  731. lock (_sync) {
  732. if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
  733. return;
  734. if (_secure) {
  735. var src = getSslConfiguration ();
  736. var conf = new ServerSslConfiguration (src);
  737. if (conf.ServerCertificate == null) {
  738. var msg = "There is no server certificate for secure connection.";
  739. throw new InvalidOperationException (msg);
  740. }
  741. _sslConfigInUse = conf;
  742. }
  743. _realmInUse = getRealm ();
  744. _services.Start ();
  745. try {
  746. startReceiving ();
  747. }
  748. catch {
  749. _services.Stop (1011, String.Empty);
  750. throw;
  751. }
  752. _state = ServerState.Start;
  753. }
  754. }
  755. private void startReceiving ()
  756. {
  757. if (_reuseAddress) {
  758. _listener.Server.SetSocketOption (
  759. SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true
  760. );
  761. }
  762. try {
  763. _listener.Start ();
  764. }
  765. catch (Exception ex) {
  766. var msg = "The underlying listener has failed to start.";
  767. throw new InvalidOperationException (msg, ex);
  768. }
  769. var receiver = new ThreadStart (receiveRequest);
  770. _receiveThread = new Thread (receiver);
  771. _receiveThread.IsBackground = true;
  772. _receiveThread.Start ();
  773. }
  774. private void stop (ushort code, string reason)
  775. {
  776. lock (_sync) {
  777. if (_state != ServerState.Start)
  778. return;
  779. _state = ServerState.ShuttingDown;
  780. }
  781. try {
  782. var timeout = 5000;
  783. stopReceiving (timeout);
  784. }
  785. catch (Exception ex) {
  786. _log.Fatal (ex.Message);
  787. _log.Debug (ex.ToString ());
  788. }
  789. try {
  790. _services.Stop (code, reason);
  791. }
  792. catch (Exception ex) {
  793. _log.Fatal (ex.Message);
  794. _log.Debug (ex.ToString ());
  795. }
  796. _state = ServerState.Stop;
  797. }
  798. private void stopReceiving (int millisecondsTimeout)
  799. {
  800. _listener.Stop ();
  801. _receiveThread.Join (millisecondsTimeout);
  802. }
  803. private static bool tryCreateUri (
  804. string uriString, out Uri result, out string message
  805. )
  806. {
  807. if (!uriString.TryCreateWebSocketUri (out result, out message))
  808. return false;
  809. if (result.PathAndQuery != "/") {
  810. result = null;
  811. message = "It includes either or both path and query components.";
  812. return false;
  813. }
  814. return true;
  815. }
  816. #endregion
  817. #region Public Methods
  818. /// <summary>
  819. /// Adds a WebSocket service with the specified behavior and path.
  820. /// </summary>
  821. /// <param name="path">
  822. /// <para>
  823. /// A <see cref="string"/> that specifies an absolute path to
  824. /// the service to add.
  825. /// </para>
  826. /// <para>
  827. /// / is trimmed from the end of the string if present.
  828. /// </para>
  829. /// </param>
  830. /// <typeparam name="TBehavior">
  831. /// <para>
  832. /// The type of the behavior for the service.
  833. /// </para>
  834. /// <para>
  835. /// It must inherit the <see cref="WebSocketBehavior"/> class.
  836. /// </para>
  837. /// <para>
  838. /// Also it must have a public parameterless constructor.
  839. /// </para>
  840. /// </typeparam>
  841. /// <exception cref="ArgumentNullException">
  842. /// <paramref name="path"/> is <see langword="null"/>.
  843. /// </exception>
  844. /// <exception cref="ArgumentException">
  845. /// <para>
  846. /// <paramref name="path"/> is an empty string.
  847. /// </para>
  848. /// <para>
  849. /// -or-
  850. /// </para>
  851. /// <para>
  852. /// <paramref name="path"/> is not an absolute path.
  853. /// </para>
  854. /// <para>
  855. /// -or-
  856. /// </para>
  857. /// <para>
  858. /// <paramref name="path"/> includes either or both
  859. /// query and fragment components.
  860. /// </para>
  861. /// <para>
  862. /// -or-
  863. /// </para>
  864. /// <para>
  865. /// <paramref name="path"/> is already in use.
  866. /// </para>
  867. /// </exception>
  868. public void AddWebSocketService<TBehavior> (string path)
  869. where TBehavior : WebSocketBehavior, new ()
  870. {
  871. _services.AddService<TBehavior> (path, null);
  872. }
  873. /// <summary>
  874. /// Adds a WebSocket service with the specified behavior, path,
  875. /// and initializer.
  876. /// </summary>
  877. /// <param name="path">
  878. /// <para>
  879. /// A <see cref="string"/> that specifies an absolute path to
  880. /// the service to add.
  881. /// </para>
  882. /// <para>
  883. /// / is trimmed from the end of the string if present.
  884. /// </para>
  885. /// </param>
  886. /// <param name="initializer">
  887. /// <para>
  888. /// An <see cref="T:System.Action{TBehavior}"/> delegate.
  889. /// </para>
  890. /// <para>
  891. /// The delegate invokes the method called when the service
  892. /// initializes a new session instance.
  893. /// </para>
  894. /// <para>
  895. /// <see langword="null"/> if not necessary.
  896. /// </para>
  897. /// </param>
  898. /// <typeparam name="TBehavior">
  899. /// <para>
  900. /// The type of the behavior for the service.
  901. /// </para>
  902. /// <para>
  903. /// It must inherit the <see cref="WebSocketBehavior"/> class.
  904. /// </para>
  905. /// <para>
  906. /// Also it must have a public parameterless constructor.
  907. /// </para>
  908. /// </typeparam>
  909. /// <exception cref="ArgumentNullException">
  910. /// <paramref name="path"/> is <see langword="null"/>.
  911. /// </exception>
  912. /// <exception cref="ArgumentException">
  913. /// <para>
  914. /// <paramref name="path"/> is an empty string.
  915. /// </para>
  916. /// <para>
  917. /// -or-
  918. /// </para>
  919. /// <para>
  920. /// <paramref name="path"/> is not an absolute path.
  921. /// </para>
  922. /// <para>
  923. /// -or-
  924. /// </para>
  925. /// <para>
  926. /// <paramref name="path"/> includes either or both
  927. /// query and fragment components.
  928. /// </para>
  929. /// <para>
  930. /// -or-
  931. /// </para>
  932. /// <para>
  933. /// <paramref name="path"/> is already in use.
  934. /// </para>
  935. /// </exception>
  936. public void AddWebSocketService<TBehavior> (
  937. string path, Action<TBehavior> initializer
  938. )
  939. where TBehavior : WebSocketBehavior, new ()
  940. {
  941. _services.AddService<TBehavior> (path, initializer);
  942. }
  943. /// <summary>
  944. /// Removes a WebSocket service with the specified path.
  945. /// </summary>
  946. /// <remarks>
  947. /// The service is stopped with close status 1001 (going away)
  948. /// if the current state of the service is Start.
  949. /// </remarks>
  950. /// <returns>
  951. /// <c>true</c> if the service is successfully found and removed;
  952. /// otherwise, <c>false</c>.
  953. /// </returns>
  954. /// <param name="path">
  955. /// <para>
  956. /// A <see cref="string"/> that specifies an absolute path to
  957. /// the service to remove.
  958. /// </para>
  959. /// <para>
  960. /// / is trimmed from the end of the string if present.
  961. /// </para>
  962. /// </param>
  963. /// <exception cref="ArgumentNullException">
  964. /// <paramref name="path"/> is <see langword="null"/>.
  965. /// </exception>
  966. /// <exception cref="ArgumentException">
  967. /// <para>
  968. /// <paramref name="path"/> is an empty string.
  969. /// </para>
  970. /// <para>
  971. /// -or-
  972. /// </para>
  973. /// <para>
  974. /// <paramref name="path"/> is not an absolute path.
  975. /// </para>
  976. /// <para>
  977. /// -or-
  978. /// </para>
  979. /// <para>
  980. /// <paramref name="path"/> includes either or both
  981. /// query and fragment components.
  982. /// </para>
  983. /// </exception>
  984. public bool RemoveWebSocketService (string path)
  985. {
  986. return _services.RemoveService (path);
  987. }
  988. /// <summary>
  989. /// Starts receiving incoming handshake requests.
  990. /// </summary>
  991. /// <remarks>
  992. /// This method works if the current state of the server is Ready or Stop.
  993. /// </remarks>
  994. /// <exception cref="InvalidOperationException">
  995. /// <para>
  996. /// There is no server certificate for secure connection.
  997. /// </para>
  998. /// <para>
  999. /// -or-
  1000. /// </para>
  1001. /// <para>
  1002. /// The underlying <see cref="TcpListener"/> has failed to start.
  1003. /// </para>
  1004. /// </exception>
  1005. public void Start ()
  1006. {
  1007. if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
  1008. return;
  1009. start ();
  1010. }
  1011. /// <summary>
  1012. /// Stops receiving incoming handshake requests.
  1013. /// </summary>
  1014. /// <remarks>
  1015. /// This method works if the current state of the server is Start.
  1016. /// </remarks>
  1017. public void Stop ()
  1018. {
  1019. if (_state != ServerState.Start)
  1020. return;
  1021. stop (1001, String.Empty);
  1022. }
  1023. #endregion
  1024. }
  1025. }