123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- // /* **********************************************************************************
- // *
- // * 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
- {
- /// <summary>
- /// 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.
- /// </summary>
- 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;
- /// <summary>
- /// </summary>
- public void Dispose()
- {
- {
- if (_serverProcess != null)
- {
- StopServer();
- }
- }
- }
- /// <summary>
- /// The root URL of the running web application
- /// </summary>
- public string RootUrl
- {
- get { return _rootUrl; }
- }
- /// <summary>
- /// Combine the RootUrl of the running web application with the relative url specified.
- /// </summary>
- /// <param name="relativeUrl"></param>
- /// <returns></returns>
- public string NormalizeUrl(string relativeUrl)
- {
- return CassiniNetworkUtils.NormalizeUrl(RootUrl, relativeUrl);
- }
- /// <summary>
- /// Will start specified application as "localhost" on loopback and first available port in the range 8000-10000 with vpath "/"
- /// </summary>
- /// <param name="applicationPath">Physical path to application.</param>
- /// <param name="virtualPath">Optional. defaults to "/"</param>
- public void StartServer(string applicationPath, string virtualPath)
- {
- StartServer(applicationPath, CassiniNetworkUtils.GetAvailablePort(8000, 10000, IPAddress.Loopback, true), virtualPath, "localhost");
- }
- /// <summary>
- /// Will start specified application as "localhost" on loopback and first available port in the range 8000-10000 with vpath "/"
- /// </summary>
- /// <param name="applicationPath">Physical path to application.</param>
- public void StartServer(string applicationPath)
- {
- StartServer(applicationPath, CassiniNetworkUtils.GetAvailablePort(8000, 10000, IPAddress.Loopback, true), "/", "localhost");
- }
- /// <summary>
- /// Will start specified application on loopback
- /// </summary>
- /// <param name="applicationPath">Physical path to application.</param>
- /// <param name="port">Port to listen on.</param>
- /// <param name="virtualPath">Optional. defaults to "/"</param>
- /// <param name="hostName">Optional. Is used to construct RootUrl. Defaults to "localhost"</param>
- 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);
- }
- /// <summary>
- /// </summary>
- /// <param name="applicationPath">Physical path to application.</param>
- /// <param name="ipAddress">IP to listen on.</param>
- /// <param name="port">Port to listen on.</param>
- /// <param name="virtualPath">Optional. default value '/'</param>
- /// <param name="hostName">Optional. Used to construct RootUrl. Defaults to 'localhost'</param>
- 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);
- }
- /// <summary>
- /// <para>Stops the server, if running.</para>
- /// </summary>
- public virtual void StopServer()
- {
- StopServer(100);
- }
- /// <summary>
- /// <para>Stops the server, if running.</para>
- /// </summary>
- 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;
- }
- }
- }
- }
- }
|