CassiniDevServerOP.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // /* **********************************************************************************
  2. // *
  3. // * Copyright (c) Sky Sanders. All rights reserved.
  4. // *
  5. // * This source code is subject to terms and conditions of the Microsoft Public
  6. // * License (Ms-PL). A copy of the license can be found in the license.htm file
  7. // * included in this distribution.
  8. // *
  9. // * You must not remove this notice, or any other, from this software.
  10. // *
  11. // * **********************************************************************************/
  12. using System;
  13. using System.Diagnostics;
  14. using System.IO;
  15. using System.Net;
  16. using System.Threading;
  17. namespace CassiniDev
  18. {
  19. /// <summary>
  20. /// Made a go at spinning the server up from this process but after dealing with
  21. /// irratic behaviour regarding apartment state, platform concerns, unloaded app domains,
  22. /// and all the other issues that you can find that people struggle with I just decided
  23. /// to strictly format the console app's output and just spin up an external process.
  24. /// Seems robust so far.
  25. /// </summary>
  26. public class CassiniDevServerOP
  27. {
  28. //private bool _disposed;
  29. private string _hostname;
  30. private StreamWriter _input;
  31. private IPAddress _ipAddress;
  32. private Thread _outputThread;
  33. private string _rootUrl;
  34. private Process _serverProcess;
  35. private const int TimeOut = 60000;
  36. private const int WaitForPort = 5000;
  37. /// <summary>
  38. /// </summary>
  39. public void Dispose()
  40. {
  41. {
  42. if (_serverProcess != null)
  43. {
  44. StopServer();
  45. }
  46. }
  47. }
  48. /// <summary>
  49. /// The root URL of the running web application
  50. /// </summary>
  51. public string RootUrl
  52. {
  53. get { return _rootUrl; }
  54. }
  55. /// <summary>
  56. /// Combine the RootUrl of the running web application with the relative url specified.
  57. /// </summary>
  58. /// <param name="relativeUrl"></param>
  59. /// <returns></returns>
  60. public string NormalizeUrl(string relativeUrl)
  61. {
  62. return CassiniNetworkUtils.NormalizeUrl(RootUrl, relativeUrl);
  63. }
  64. /// <summary>
  65. /// Will start specified application as "localhost" on loopback and first available port in the range 8000-10000 with vpath "/"
  66. /// </summary>
  67. /// <param name="applicationPath">Physical path to application.</param>
  68. /// <param name="virtualPath">Optional. defaults to "/"</param>
  69. public void StartServer(string applicationPath, string virtualPath)
  70. {
  71. StartServer(applicationPath, CassiniNetworkUtils.GetAvailablePort(8000, 10000, IPAddress.Loopback, true), virtualPath, "localhost");
  72. }
  73. /// <summary>
  74. /// Will start specified application as "localhost" on loopback and first available port in the range 8000-10000 with vpath "/"
  75. /// </summary>
  76. /// <param name="applicationPath">Physical path to application.</param>
  77. public void StartServer(string applicationPath)
  78. {
  79. StartServer(applicationPath, CassiniNetworkUtils.GetAvailablePort(8000, 10000, IPAddress.Loopback, true), "/", "localhost");
  80. }
  81. /// <summary>
  82. /// Will start specified application on loopback
  83. /// </summary>
  84. /// <param name="applicationPath">Physical path to application.</param>
  85. /// <param name="port">Port to listen on.</param>
  86. /// <param name="virtualPath">Optional. defaults to "/"</param>
  87. /// <param name="hostName">Optional. Is used to construct RootUrl. Defaults to "localhost"</param>
  88. public void StartServer(string applicationPath, int port, string virtualPath, string hostName)
  89. {
  90. // WebHost.Server will not run on any other IP
  91. IPAddress ipAddress = IPAddress.Loopback;
  92. if (!CassiniNetworkUtils.IsPortAvailable(ipAddress, port))
  93. {
  94. throw new Exception(string.Format("Port {0} is in use.", port));
  95. }
  96. applicationPath = Path.GetFullPath(applicationPath);
  97. virtualPath = String.Format("/{0}/", (virtualPath ?? string.Empty).Trim('/')).Replace("//", "/");
  98. hostName = string.IsNullOrEmpty(hostName) ? "localhost" : hostName;
  99. StartServer(applicationPath, ipAddress, port, virtualPath, hostName);
  100. }
  101. /// <summary>
  102. /// </summary>
  103. /// <param name="applicationPath">Physical path to application.</param>
  104. /// <param name="ipAddress">IP to listen on.</param>
  105. /// <param name="port">Port to listen on.</param>
  106. /// <param name="virtualPath">Optional. default value '/'</param>
  107. /// <param name="hostName">Optional. Used to construct RootUrl. Defaults to 'localhost'</param>
  108. public virtual void StartServer(string applicationPath, IPAddress ipAddress, int port, string virtualPath, string hostName)
  109. {
  110. _hostname = hostName;
  111. _ipAddress = ipAddress;
  112. // massage and validate arguments
  113. if (string.IsNullOrEmpty(virtualPath))
  114. {
  115. virtualPath = "/";
  116. }
  117. if (!virtualPath.StartsWith("/"))
  118. {
  119. virtualPath = "/" + virtualPath;
  120. }
  121. if (_serverProcess != null)
  122. {
  123. throw new InvalidOperationException("Server is running");
  124. }
  125. string commandLine = (new CommandLineArguments
  126. {
  127. Port = port,
  128. ApplicationPath = string.Format("\"{0}\"", Path.GetFullPath(applicationPath).Trim('\"').TrimEnd('\\')),
  129. HostName = hostName,
  130. IPAddress = ipAddress.ToString(),
  131. VirtualPath = string.Format("\"{0}\"", virtualPath),
  132. TimeOut = TimeOut,
  133. WaitForPort = WaitForPort,
  134. IPMode = IPMode.Specific,
  135. PortMode = PortMode.Specific
  136. }).ToString();
  137. _serverProcess = new Process
  138. {
  139. StartInfo = new ProcessStartInfo
  140. {
  141. UseShellExecute = false,
  142. ErrorDialog = false,
  143. CreateNoWindow = true,
  144. RedirectStandardOutput = true,
  145. RedirectStandardInput = true,
  146. #if NET40 //TODO: find out the real flag
  147. FileName = "CassiniDev4-console.exe",
  148. #else
  149. FileName = "CassiniDev-console.exe",
  150. #endif
  151. Arguments = commandLine,
  152. WorkingDirectory = Environment.CurrentDirectory
  153. }
  154. };
  155. // we are going to monitor each line of the output until we get a start or error signal
  156. // and then just ignore the rest
  157. string line = null;
  158. _serverProcess.Start();
  159. _outputThread = new Thread(() =>
  160. {
  161. string l = _serverProcess.StandardOutput.ReadLine();
  162. while (l != null)
  163. {
  164. if (l.StartsWith("started:") || l.StartsWith("error:"))
  165. {
  166. line = l;
  167. }
  168. l = _serverProcess.StandardOutput.ReadLine();
  169. }
  170. });
  171. _outputThread.Start();
  172. // use StandardInput to send the newline to stop the server when required
  173. _input = _serverProcess.StandardInput;
  174. // block until we get a signal
  175. while (line == null)
  176. {
  177. Thread.Sleep(10);
  178. }
  179. if (!line.StartsWith("started:"))
  180. {
  181. throw new Exception(string.Format("Could not start server: {0}", line));
  182. }
  183. // line is the root url
  184. _rootUrl = line.Substring(line.IndexOf(':') + 1);
  185. }
  186. /// <summary>
  187. /// <para>Stops the server, if running.</para>
  188. /// </summary>
  189. public virtual void StopServer()
  190. {
  191. StopServer(100);
  192. }
  193. /// <summary>
  194. /// <para>Stops the server, if running.</para>
  195. /// </summary>
  196. protected virtual void StopServer(int delay)
  197. {
  198. Thread.Sleep(delay);
  199. if (_serverProcess != null)
  200. {
  201. try
  202. {
  203. _input.WriteLine();
  204. _serverProcess.WaitForExit(10000);
  205. Thread.Sleep(10);
  206. }
  207. catch
  208. {
  209. }
  210. finally
  211. {
  212. _serverProcess.Dispose();
  213. _serverProcess = null;
  214. }
  215. }
  216. }
  217. }
  218. }