Server.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623
  1. // **********************************************************************************
  2. // CassiniDev - http://cassinidev.codeplex.com
  3. //
  4. // Copyright (c) 2010 Sky Sanders. All rights reserved.
  5. // Copyright (c) Microsoft Corporation. All rights reserved.
  6. //
  7. // This source code is subject to terms and conditions of the Microsoft Public
  8. // License (Ms-PL). A copy of the license can be found in the license.txt file
  9. // included in this distribution.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // **********************************************************************************
  14. #region
  15. using System;
  16. using System.Collections.Generic;
  17. using System.Diagnostics;
  18. using System.Globalization;
  19. using System.IO;
  20. using System.Net;
  21. using System.Net.Sockets;
  22. using System.Reflection;
  23. using System.Runtime.Remoting;
  24. using System.Security.Permissions;
  25. using System.Security.Principal;
  26. using System.Text;
  27. using System.Threading;
  28. using System.Web;
  29. using System.Web.Hosting;
  30. using CassiniDev.Configuration;
  31. using CassiniDev.ServerLog;
  32. #endregion
  33. namespace CassiniDev
  34. {
  35. [PermissionSet(SecurityAction.LinkDemand, Name = "Everything"),
  36. PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
  37. public class Server : MarshalByRefObject, IDisposable
  38. {
  39. private bool _useLogger;
  40. public List<string> Plugins = new List<string>();
  41. private readonly ApplicationManager _appManager;
  42. private readonly bool _disableDirectoryListing;
  43. private readonly string _hostName;
  44. private readonly IPAddress _ipAddress;
  45. private readonly object _lockObject;
  46. private readonly string _physicalPath;
  47. private readonly int _port;
  48. private readonly bool _requireAuthentication;
  49. private readonly int _timeoutInterval;
  50. private readonly string _virtualPath;
  51. private bool _disposed;
  52. private Host _host;
  53. private IntPtr _processToken;
  54. private string _processUser;
  55. private int _requestCount;
  56. private bool _shutdownInProgress;
  57. private Socket _socket;
  58. private Timer _timer;
  59. public Server(int port, string virtualPath, string physicalPath)
  60. : this(port, virtualPath, physicalPath, false, false)
  61. {
  62. }
  63. public Server(int port, string physicalPath)
  64. : this(port, "/", physicalPath, IPAddress.Loopback)
  65. {
  66. }
  67. public Server(string physicalPath)
  68. : this(CassiniNetworkUtils.GetAvailablePort(32768, 65535, IPAddress.Loopback, false), physicalPath)
  69. {
  70. }
  71. public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName,
  72. int timeout, bool requireAuthentication)
  73. : this(port, virtualPath, physicalPath, ipAddress, hostName, timeout, requireAuthentication, false)
  74. {
  75. }
  76. public Server(int port, string virtualPath, string physicalPath, bool requireAuthentication)
  77. : this(port, virtualPath, physicalPath, requireAuthentication, false)
  78. {
  79. }
  80. public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName)
  81. : this(port, virtualPath, physicalPath, ipAddress, hostName, 0, false, false)
  82. {
  83. }
  84. public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName,
  85. int timeout, bool requireAuthentication, bool disableDirectoryListing)
  86. : this(port, virtualPath, physicalPath, requireAuthentication, disableDirectoryListing)
  87. {
  88. _ipAddress = ipAddress;
  89. _hostName = hostName;
  90. _timeoutInterval = timeout;
  91. }
  92. public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress)
  93. : this(port, virtualPath, physicalPath, ipAddress, null, 0, false, false)
  94. {
  95. }
  96. public Server(int port, string virtualPath, string physicalPath, bool requireAuthentication,
  97. bool disableDirectoryListing)
  98. {
  99. try
  100. {
  101. // Assembly.ReflectionOnlyLoad("Common.Logging");
  102. //_useLogger = true;
  103. }
  104. // ReSharper disable EmptyGeneralCatchClause
  105. catch
  106. // ReSharper restore EmptyGeneralCatchClause
  107. {
  108. }
  109. _ipAddress = IPAddress.Any; //IPAddress.Loopback;
  110. _requireAuthentication = requireAuthentication;
  111. _disableDirectoryListing = disableDirectoryListing;
  112. _lockObject = new object();
  113. _port = port;
  114. _virtualPath = virtualPath;
  115. _physicalPath = Path.GetFullPath(physicalPath);
  116. _physicalPath = _physicalPath.EndsWith("\\", StringComparison.Ordinal)
  117. ? _physicalPath
  118. : _physicalPath + "\\";
  119. ProcessConfiguration();
  120. _appManager = ApplicationManager.GetApplicationManager();
  121. ObtainProcessToken();
  122. }
  123. private void ProcessConfiguration()
  124. {
  125. var config = CassiniDevConfigurationSection.Instance;
  126. if (config != null)
  127. {
  128. foreach (CassiniDevProfileElement profile in config.Profiles)
  129. {
  130. if (profile.Port == "*" || Convert.ToInt64(profile.Port) == _port)
  131. {
  132. foreach (PluginElement plugin in profile.Plugins)
  133. {
  134. Plugins.Insert(0, plugin.Type);
  135. }
  136. }
  137. }
  138. }
  139. }
  140. public Server(string physicalPath, bool requireAuthentication)
  141. : this(
  142. CassiniNetworkUtils.GetAvailablePort(32768, 65535, IPAddress.Loopback, false), "/", physicalPath,
  143. requireAuthentication)
  144. {
  145. }
  146. public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName,
  147. int timeout)
  148. : this(port, virtualPath, physicalPath, ipAddress, hostName, timeout, false, false)
  149. {
  150. }
  151. public bool DisableDirectoryListing
  152. {
  153. get { return _disableDirectoryListing; }
  154. }
  155. public bool RequireAuthentication
  156. {
  157. get { return _requireAuthentication; }
  158. }
  159. public int TimeoutInterval
  160. {
  161. get { return _timeoutInterval; }
  162. }
  163. public string HostName
  164. {
  165. get { return _hostName; }
  166. }
  167. public IPAddress IPAddress
  168. {
  169. get { return _ipAddress; }
  170. }
  171. public string PhysicalPath
  172. {
  173. get { return _physicalPath; }
  174. }
  175. public int Port
  176. {
  177. get { return _port; }
  178. }
  179. public string RootUrl
  180. {
  181. get
  182. {
  183. string hostname = _hostName;
  184. if (string.IsNullOrEmpty(_hostName))
  185. {
  186. if (_ipAddress.Equals(IPAddress.Loopback) || _ipAddress.Equals(IPAddress.IPv6Loopback) ||
  187. _ipAddress.Equals(IPAddress.Any) || _ipAddress.Equals(IPAddress.IPv6Any))
  188. {
  189. hostname = "localhost";
  190. }
  191. else
  192. {
  193. hostname = _ipAddress.ToString();
  194. }
  195. }
  196. return _port != 80
  197. ?
  198. String.Format("http://{0}:{1}{2}", hostname, _port, _virtualPath)
  199. :
  200. //FIX: #12017 - TODO:TEST
  201. string.Format("http://{0}{1}", hostname, _virtualPath);
  202. }
  203. }
  204. public string VirtualPath
  205. {
  206. get { return _virtualPath; }
  207. }
  208. #region IDisposable Members
  209. public void Dispose()
  210. {
  211. if (!_disposed)
  212. {
  213. ShutDown();
  214. }
  215. _disposed = true;
  216. GC.SuppressFinalize(this);
  217. }
  218. #endregion
  219. public event EventHandler<RequestEventArgs> RequestComplete;
  220. public event EventHandler TimedOut;
  221. public IntPtr GetProcessToken()
  222. {
  223. return _processToken;
  224. }
  225. public string GetProcessUser()
  226. {
  227. return _processUser;
  228. }
  229. public void HostStopped()
  230. {
  231. _host = null;
  232. }
  233. [SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
  234. public override object InitializeLifetimeService()
  235. {
  236. // never expire the license
  237. return null;
  238. }
  239. // called at the end of request processing
  240. // to disconnect the remoting proxy for Connection object
  241. // and allow GC to pick it up
  242. /// <summary>
  243. /// </summary>
  244. /// <param name="conn"></param>
  245. public void OnRequestEnd(Connection conn)
  246. {
  247. try
  248. {
  249. OnRequestComplete(conn.Id, conn.RequestLog.Clone(), conn.ResponseLog.Clone());
  250. }
  251. catch
  252. {
  253. // swallow - we don't want consumer killing the server
  254. }
  255. RemotingServices.Disconnect(conn);
  256. DecrementRequestCount();
  257. }
  258. public void Start()
  259. {
  260. _socket = CreateSocketBindAndListen(AddressFamily.InterNetwork, _ipAddress, _port);
  261. //start the timer
  262. DecrementRequestCount();
  263. ThreadPool.QueueUserWorkItem(delegate
  264. {
  265. while (!_shutdownInProgress)
  266. {
  267. try
  268. {
  269. Socket acceptedSocket = _socket.Accept();
  270. ThreadPool.QueueUserWorkItem(delegate
  271. {
  272. if (!_shutdownInProgress)
  273. {
  274. Connection conn = new Connection(this, acceptedSocket);
  275. if (conn.WaitForRequestBytes() == 0)
  276. {
  277. conn.WriteErrorAndClose(400);
  278. return;
  279. }
  280. Host host = GetHost();
  281. if (host == null)
  282. {
  283. conn.WriteErrorAndClose(500);
  284. return;
  285. }
  286. IncrementRequestCount();
  287. host.ProcessRequest(conn);
  288. }
  289. });
  290. }
  291. catch
  292. {
  293. Thread.Sleep(100);
  294. }
  295. }
  296. });
  297. }
  298. ~Server()
  299. {
  300. Dispose();
  301. }
  302. private static Socket CreateSocketBindAndListen(AddressFamily family, IPAddress address, int port)
  303. {
  304. Socket socket = new Socket(family, SocketType.Stream, ProtocolType.Tcp);
  305. socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
  306. socket.Bind(new IPEndPoint(address, port));
  307. socket.Listen((int)SocketOptionName.MaxConnections);
  308. return socket;
  309. }
  310. /// <summary>
  311. ///
  312. /// </summary>
  313. /// <param name="virtualPath"></param>
  314. /// <param name="physicalPath"></param>
  315. /// <param name="hostType"></param>
  316. /// <returns></returns>
  317. /// <remarks>
  318. /// This is Dmitry's hack to enable running outside of GAC.
  319. /// There are some errors being thrown when running in proc
  320. /// </remarks>
  321. private object CreateWorkerAppDomainWithHost(string virtualPath, string physicalPath, Type hostType)
  322. {
  323. // this creates worker app domain in a way that host doesn't need to be in GAC or bin
  324. // using BuildManagerHost via private reflection
  325. string uniqueAppString = string.Concat(virtualPath, physicalPath).ToLowerInvariant();
  326. string appId = (uniqueAppString.GetHashCode()).ToString("x", CultureInfo.InvariantCulture);
  327. // create BuildManagerHost in the worker app domain
  328. //ApplicationManager appManager = ApplicationManager.GetApplicationManager();
  329. Type buildManagerHostType = typeof(HttpRuntime).Assembly.GetType("System.Web.Compilation.BuildManagerHost");
  330. IRegisteredObject buildManagerHost = _appManager.CreateObject(appId, buildManagerHostType, virtualPath,
  331. physicalPath, false);
  332. // call BuildManagerHost.RegisterAssembly to make Host type loadable in the worker app domain
  333. buildManagerHostType.InvokeMember("RegisterAssembly",
  334. BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
  335. null,
  336. buildManagerHost,
  337. new object[] { hostType.Assembly.FullName, hostType.Assembly.Location });
  338. // create Host in the worker app domain
  339. // FIXME: getting FileLoadException Could not load file or assembly 'WebDev.WebServer20, Version=4.0.1.6, Culture=neutral, PublicKeyToken=f7f6e0b4240c7c27' or one of its dependencies. Failed to grant permission to execute. (Exception from HRESULT: 0x80131418)
  340. // when running dnoa 3.4 samples - webdev is registering trust somewhere that we are not
  341. return _appManager.CreateObject(appId, hostType, virtualPath, physicalPath, false);
  342. }
  343. private void DecrementRequestCount()
  344. {
  345. lock (_lockObject)
  346. {
  347. _requestCount--;
  348. if (_requestCount < 1)
  349. {
  350. _requestCount = 0;
  351. if (_timeoutInterval > 0 && _timer == null)
  352. {
  353. _timer = new Timer(TimeOut, null, _timeoutInterval, Timeout.Infinite);
  354. }
  355. }
  356. }
  357. }
  358. private Host GetHost()
  359. {
  360. if (_shutdownInProgress)
  361. return null;
  362. Host host = _host;
  363. if (host == null)
  364. {
  365. #if NET40
  366. object obj2 = new object();
  367. bool flag = false;
  368. try
  369. {
  370. Monitor.Enter(obj2 = _lockObject, ref flag);
  371. host = _host;
  372. if (host == null)
  373. {
  374. host = (Host)CreateWorkerAppDomainWithHost(_virtualPath, _physicalPath, typeof(Host));
  375. host.Configure(this, _port, _virtualPath, _physicalPath, _requireAuthentication, _disableDirectoryListing);
  376. _host = host;
  377. }
  378. }
  379. finally
  380. {
  381. if (flag)
  382. {
  383. Monitor.Exit(obj2);
  384. }
  385. }
  386. #else
  387. lock (_lockObject)
  388. {
  389. host = _host;
  390. if (host == null)
  391. {
  392. host = (Host)CreateWorkerAppDomainWithHost(_virtualPath, _physicalPath, typeof(Host));
  393. host.Configure(this, _port, _virtualPath, _physicalPath, _requireAuthentication, _disableDirectoryListing);
  394. _host = host;
  395. }
  396. }
  397. #endif
  398. }
  399. return host;
  400. }
  401. private void IncrementRequestCount()
  402. {
  403. lock (_lockObject)
  404. {
  405. _requestCount++;
  406. if (_timer != null)
  407. {
  408. _timer.Dispose();
  409. _timer = null;
  410. }
  411. }
  412. }
  413. private void ObtainProcessToken()
  414. {
  415. if (Interop.ImpersonateSelf(2))
  416. {
  417. Interop.OpenThreadToken(Interop.GetCurrentThread(), 0xf01ff, true, ref _processToken);
  418. Interop.RevertToSelf();
  419. // ReSharper disable PossibleNullReferenceException
  420. _processUser = WindowsIdentity.GetCurrent().Name;
  421. // ReSharper restore PossibleNullReferenceException
  422. }
  423. }
  424. private void OnRequestComplete(Guid id, LogInfo requestLog, LogInfo responseLog)
  425. {
  426. PublishLogToCommonLogging(requestLog);
  427. PublishLogToCommonLogging(responseLog);
  428. EventHandler<RequestEventArgs> complete = RequestComplete;
  429. if (complete != null)
  430. {
  431. complete(this, new RequestEventArgs(id, requestLog, responseLog));
  432. }
  433. }
  434. private void PublishLogToCommonLogging(LogInfo item)
  435. {
  436. if(!_useLogger )
  437. {
  438. return;
  439. }
  440. /* Common.Logging.ILog logger = Common.Logging.LogManager.GetCurrentClassLogger();
  441. var bodyAsString = String.Empty;
  442. try
  443. {
  444. bodyAsString = Encoding.UTF8.GetString(item.Body);
  445. }
  446. // ReSharper disable EmptyGeneralCatchClause
  447. catch (Exception e)*/
  448. // ReSharper restore EmptyGeneralCatchClause
  449. {
  450. /* empty bodies should be allowed */
  451. }
  452. /* var type = item.RowType == 0 ? "" : item.RowType == 1 ? "Request" : "Response";
  453. logger.Debug(type + " | " +
  454. item.Created + " | " +
  455. item.StatusCode + " | " +
  456. item.Url + " | " +
  457. item.PathTranslated + " | " +
  458. item.Identity + " | " +
  459. "\n===>Headers<====\n" + item.Headers +
  460. "\n===>Body<=======\n" + bodyAsString
  461. );*/
  462. }
  463. public void ShutDown()
  464. {
  465. if (_shutdownInProgress)
  466. {
  467. return;
  468. }
  469. _shutdownInProgress = true;
  470. try
  471. {
  472. if (_socket != null)
  473. {
  474. _socket.Close();
  475. }
  476. }
  477. // ReSharper disable EmptyGeneralCatchClause
  478. catch
  479. // ReSharper restore EmptyGeneralCatchClause
  480. {
  481. // TODO: why the swallow?
  482. }
  483. finally
  484. {
  485. _socket = null;
  486. }
  487. try
  488. {
  489. if (_host != null)
  490. {
  491. _host.Shutdown();
  492. }
  493. // the host is going to raise an event that this class uses to null the field.
  494. // just wait until the field is nulled and continue.
  495. while (_host != null)
  496. {
  497. new AutoResetEvent(false).WaitOne(100);
  498. }
  499. }
  500. // ReSharper disable EmptyGeneralCatchClause
  501. catch
  502. // ReSharper restore EmptyGeneralCatchClause
  503. {
  504. // TODO: what am i afraid of here?
  505. }
  506. }
  507. private void TimeOut(object ignored)
  508. {
  509. TimeOut();
  510. }
  511. public void TimeOut()
  512. {
  513. ShutDown();
  514. OnTimeOut();
  515. }
  516. private void OnTimeOut()
  517. {
  518. EventHandler handler = TimedOut;
  519. if (handler != null) handler(this, EventArgs.Empty);
  520. }
  521. }
  522. }