// /* ********************************************************************************** // * // * Copyright (c) Sky Sanders. All rights reserved. // * // * This source code is subject to terms and conditions of the Microsoft Public // * License (Ms-PL). A copy of the license can be found in the license.htm file // * included in this distribution. // * // * You must not remove this notice, or any other, from this software. // * // * **********************************************************************************/ using System; using System.Diagnostics; using System.IO; using System.Net; using System.Threading; namespace CassiniDev { /// /// Made a go at spinning the server up from this process but after dealing with /// irratic behaviour regarding apartment state, platform concerns, unloaded app domains, /// and all the other issues that you can find that people struggle with I just decided /// to strictly format the console app's output and just spin up an external process. /// Seems robust so far. /// public class CassiniDevServerOP { //private bool _disposed; private string _hostname; private StreamWriter _input; private IPAddress _ipAddress; private Thread _outputThread; private string _rootUrl; private Process _serverProcess; private const int TimeOut = 60000; private const int WaitForPort = 5000; /// /// public void Dispose() { { if (_serverProcess != null) { StopServer(); } } } /// /// The root URL of the running web application /// public string RootUrl { get { return _rootUrl; } } /// /// Combine the RootUrl of the running web application with the relative url specified. /// /// /// public string NormalizeUrl(string relativeUrl) { return CassiniNetworkUtils.NormalizeUrl(RootUrl, relativeUrl); } /// /// Will start specified application as "localhost" on loopback and first available port in the range 8000-10000 with vpath "/" /// /// Physical path to application. public void StartServer(string applicationPath) { StartServer(applicationPath, CassiniNetworkUtils.GetAvailablePort(8000, 10000, IPAddress.Loopback, true), "/", "localhost"); } /// /// Will start specified application on loopback /// /// Physical path to application. /// Port to listen on. /// Optional. defaults to "/" /// Optional. Is used to construct RootUrl. Defaults to "localhost" public void StartServer(string applicationPath, int port, string virtualPath, string hostName) { // WebHost.Server will not run on any other IP IPAddress ipAddress = IPAddress.Loopback; if (!CassiniNetworkUtils.IsPortAvailable(ipAddress, port)) { throw new Exception(string.Format("Port {0} is in use.", port)); } applicationPath = Path.GetFullPath(applicationPath); virtualPath = String.Format("/{0}/", (virtualPath ?? string.Empty).Trim('/')).Replace("//", "/"); hostName = string.IsNullOrEmpty(hostName) ? "localhost" : hostName; StartServer(applicationPath, ipAddress, port, virtualPath, hostName); } /// /// /// Physical path to application. /// IP to listen on. /// Port to listen on. /// Optional. default value '/' /// Optional. Used to construct RootUrl. Defaults to 'localhost' public virtual void StartServer(string applicationPath, IPAddress ipAddress, int port, string virtualPath, string hostName) { _hostname = hostName; _ipAddress = ipAddress; // massage and validate arguments if (string.IsNullOrEmpty(virtualPath)) { virtualPath = "/"; } if (!virtualPath.StartsWith("/")) { virtualPath = "/" + virtualPath; } if (_serverProcess != null) { throw new InvalidOperationException("Server is running"); } string commandLine = (new CommandLineArguments { Port = port, ApplicationPath = string.Format("\"{0}\"", Path.GetFullPath(applicationPath).Trim('\"').TrimEnd('\\')), HostName = hostName, IPAddress = ipAddress.ToString(), VirtualPath = string.Format("\"{0}\"", virtualPath), TimeOut = TimeOut, WaitForPort = WaitForPort, IPMode = IPMode.Specific, PortMode = PortMode.Specific }).ToString(); _serverProcess = new Process { StartInfo = new ProcessStartInfo { UseShellExecute = false, ErrorDialog = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardInput = true, #if NET40 //TODO: find out the real flag FileName = "CassiniDev4-console.exe", #else FileName = "CassiniDev-console.exe", #endif Arguments = commandLine, WorkingDirectory = Environment.CurrentDirectory } }; // we are going to monitor each line of the output until we get a start or error signal // and then just ignore the rest string line = null; _serverProcess.Start(); _outputThread = new Thread(() => { string l = _serverProcess.StandardOutput.ReadLine(); while (l != null) { if (l.StartsWith("started:") || l.StartsWith("error:")) { line = l; } l = _serverProcess.StandardOutput.ReadLine(); } }); _outputThread.Start(); // use StandardInput to send the newline to stop the server when required _input = _serverProcess.StandardInput; // block until we get a signal while (line == null) { Thread.Sleep(10); } if (!line.StartsWith("started:")) { throw new Exception(string.Format("Could not start server: {0}", line)); } // line is the root url _rootUrl = line.Substring(line.IndexOf(':') + 1); } /// /// Stops the server, if running. /// public virtual void StopServer() { StopServer(100); } /// /// Stops the server, if running. /// protected virtual void StopServer(int delay) { Thread.Sleep(delay); if (_serverProcess != null) { try { _input.WriteLine(); _serverProcess.WaitForExit(10000); Thread.Sleep(10); } catch { } finally { _serverProcess.Dispose(); _serverProcess = null; } } } } }