You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
523 lines
17 KiB
523 lines
17 KiB
14 years ago
|
// **********************************************************************************
|
||
|
// CassiniDev - http://cassinidev.codeplex.com
|
||
|
//
|
||
|
// Copyright (c) 2010 Sky Sanders. All rights reserved.
|
||
|
// Copyright (c) Microsoft Corporation. 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.txt file
|
||
|
// included in this distribution.
|
||
|
//
|
||
|
// You must not remove this notice, or any other, from this software.
|
||
|
//
|
||
|
// **********************************************************************************
|
||
|
|
||
|
#region
|
||
|
|
||
|
using System;
|
||
|
using System.Globalization;
|
||
|
using System.IO;
|
||
|
using System.Net;
|
||
|
using System.Net.Sockets;
|
||
|
using System.Reflection;
|
||
|
using System.Runtime.Remoting;
|
||
|
using System.Security.Permissions;
|
||
|
using System.Security.Principal;
|
||
|
using System.Threading;
|
||
|
using System.Web;
|
||
|
using System.Web.Hosting;
|
||
|
using CassiniDev.ServerLog;
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
namespace CassiniDev
|
||
|
{
|
||
|
[PermissionSet(SecurityAction.LinkDemand, Name = "Everything"),
|
||
|
PermissionSet(SecurityAction.InheritanceDemand, Name = "FullTrust")]
|
||
|
public class Server : MarshalByRefObject, IDisposable
|
||
|
{
|
||
|
private readonly ApplicationManager _appManager;
|
||
|
|
||
|
private readonly bool _disableDirectoryListing;
|
||
|
|
||
|
private readonly string _hostName;
|
||
|
|
||
|
private readonly IPAddress _ipAddress;
|
||
|
|
||
|
private readonly object _lockObject;
|
||
|
|
||
|
private readonly string _physicalPath;
|
||
|
|
||
|
private readonly int _port;
|
||
|
private readonly bool _requireAuthentication;
|
||
|
private readonly int _timeoutInterval;
|
||
|
private readonly string _virtualPath;
|
||
|
private bool _disposed;
|
||
|
|
||
|
private Host _host;
|
||
|
|
||
|
private IntPtr _processToken;
|
||
|
|
||
|
private string _processUser;
|
||
|
|
||
|
private int _requestCount;
|
||
|
|
||
|
private bool _shutdownInProgress;
|
||
|
|
||
|
private Socket _socket;
|
||
|
|
||
|
private Timer _timer;
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath)
|
||
|
: this(port, virtualPath, physicalPath, false, false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(int port, string physicalPath)
|
||
|
: this(port, "/", physicalPath, IPAddress.Loopback)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(string physicalPath)
|
||
|
: this(CassiniNetworkUtils.GetAvailablePort(32768, 65535, IPAddress.Loopback, false), physicalPath)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName,
|
||
|
int timeout, bool requireAuthentication)
|
||
|
: this(port, virtualPath, physicalPath, ipAddress, hostName, timeout, requireAuthentication, false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath, bool requireAuthentication)
|
||
|
: this(port, virtualPath, physicalPath, requireAuthentication, false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName)
|
||
|
: this(port, virtualPath, physicalPath, ipAddress, hostName, 0, false, false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName,
|
||
|
int timeout, bool requireAuthentication, bool disableDirectoryListing)
|
||
|
: this(port, virtualPath, physicalPath, requireAuthentication, disableDirectoryListing)
|
||
|
{
|
||
|
_ipAddress = ipAddress;
|
||
|
_hostName = hostName;
|
||
|
_timeoutInterval = timeout;
|
||
|
}
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress)
|
||
|
: this(port, virtualPath, physicalPath, ipAddress, null, 0, false, false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath, bool requireAuthentication,
|
||
|
bool disableDirectoryListing)
|
||
|
{
|
||
|
_ipAddress = IPAddress.Loopback;
|
||
|
_requireAuthentication = requireAuthentication;
|
||
|
_disableDirectoryListing = disableDirectoryListing;
|
||
|
_lockObject = new object();
|
||
|
_port = port;
|
||
|
_virtualPath = virtualPath;
|
||
|
_physicalPath = Path.GetFullPath(physicalPath);
|
||
|
_physicalPath = _physicalPath.EndsWith("\\", StringComparison.Ordinal)
|
||
|
? _physicalPath
|
||
|
: _physicalPath + "\\";
|
||
|
_appManager = ApplicationManager.GetApplicationManager();
|
||
|
ObtainProcessToken();
|
||
|
}
|
||
|
|
||
|
public Server(string physicalPath, bool requireAuthentication)
|
||
|
: this(
|
||
|
CassiniNetworkUtils.GetAvailablePort(32768, 65535, IPAddress.Loopback, false), "/", physicalPath,
|
||
|
requireAuthentication)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public Server(int port, string virtualPath, string physicalPath, IPAddress ipAddress, string hostName,
|
||
|
int timeout)
|
||
|
: this(port, virtualPath, physicalPath, ipAddress, hostName, timeout, false, false)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
public bool DisableDirectoryListing
|
||
|
{
|
||
|
get { return _disableDirectoryListing; }
|
||
|
}
|
||
|
|
||
|
public bool RequireAuthentication
|
||
|
{
|
||
|
get { return _requireAuthentication; }
|
||
|
}
|
||
|
|
||
|
public int TimeoutInterval
|
||
|
{
|
||
|
get { return _timeoutInterval; }
|
||
|
}
|
||
|
|
||
|
public string HostName
|
||
|
{
|
||
|
get { return _hostName; }
|
||
|
}
|
||
|
|
||
|
public IPAddress IPAddress
|
||
|
{
|
||
|
get { return _ipAddress; }
|
||
|
}
|
||
|
|
||
|
public string PhysicalPath
|
||
|
{
|
||
|
get { return _physicalPath; }
|
||
|
}
|
||
|
|
||
|
public int Port
|
||
|
{
|
||
|
get { return _port; }
|
||
|
}
|
||
|
|
||
|
public string RootUrl
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
string hostname = _hostName;
|
||
|
if (string.IsNullOrEmpty(_hostName))
|
||
|
{
|
||
|
if (_ipAddress.Equals(IPAddress.Loopback) || _ipAddress.Equals(IPAddress.IPv6Loopback) ||
|
||
|
_ipAddress.Equals(IPAddress.Any) || _ipAddress.Equals(IPAddress.IPv6Any))
|
||
|
{
|
||
|
hostname = "localhost";
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hostname = _ipAddress.ToString();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return _port != 80
|
||
|
?
|
||
|
String.Format("http://{0}:{1}{2}", hostname, _port, _virtualPath)
|
||
|
:
|
||
|
//FIX: #12017 - TODO:TEST
|
||
|
string.Format("http://{0}{1}", hostname, _virtualPath);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public string VirtualPath
|
||
|
{
|
||
|
get { return _virtualPath; }
|
||
|
}
|
||
|
|
||
|
#region IDisposable Members
|
||
|
|
||
|
public void Dispose()
|
||
|
{
|
||
|
if (!_disposed)
|
||
|
{
|
||
|
ShutDown();
|
||
|
}
|
||
|
_disposed = true;
|
||
|
GC.SuppressFinalize(this);
|
||
|
}
|
||
|
|
||
|
#endregion
|
||
|
|
||
|
public event EventHandler<RequestEventArgs> RequestComplete;
|
||
|
|
||
|
public event EventHandler TimedOut;
|
||
|
|
||
|
public IntPtr GetProcessToken()
|
||
|
{
|
||
|
return _processToken;
|
||
|
}
|
||
|
|
||
|
public string GetProcessUser()
|
||
|
{
|
||
|
return _processUser;
|
||
|
}
|
||
|
|
||
|
public void HostStopped()
|
||
|
{
|
||
|
_host = null;
|
||
|
}
|
||
|
|
||
|
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.Infrastructure)]
|
||
|
public override object InitializeLifetimeService()
|
||
|
{
|
||
|
// never expire the license
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
// called at the end of request processing
|
||
|
// to disconnect the remoting proxy for Connection object
|
||
|
// and allow GC to pick it up
|
||
|
/// <summary>
|
||
|
/// </summary>
|
||
|
/// <param name="conn"></param>
|
||
|
public void OnRequestEnd(Connection conn)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
OnRequestComplete(conn.Id, conn.RequestLog.Clone(), conn.ResponseLog.Clone());
|
||
|
}
|
||
|
catch
|
||
|
{
|
||
|
// swallow - we don't want consumer killing the server
|
||
|
}
|
||
|
RemotingServices.Disconnect(conn);
|
||
|
DecrementRequestCount();
|
||
|
}
|
||
|
|
||
|
public void Start()
|
||
|
{
|
||
|
_socket = CreateSocketBindAndListen(AddressFamily.InterNetwork, _ipAddress, _port);
|
||
|
|
||
|
//start the timer
|
||
|
DecrementRequestCount();
|
||
|
|
||
|
ThreadPool.QueueUserWorkItem(delegate
|
||
|
{
|
||
|
while (!_shutdownInProgress)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
Socket acceptedSocket = _socket.Accept();
|
||
|
|
||
|
ThreadPool.QueueUserWorkItem(delegate
|
||
|
{
|
||
|
if (!_shutdownInProgress)
|
||
|
{
|
||
|
Connection conn = new Connection(this, acceptedSocket);
|
||
|
|
||
|
if (conn.WaitForRequestBytes() == 0)
|
||
|
{
|
||
|
conn.WriteErrorAndClose(400);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
Host host = GetHost();
|
||
|
|
||
|
if (host == null)
|
||
|
{
|
||
|
conn.WriteErrorAndClose(500);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
IncrementRequestCount();
|
||
|
host.ProcessRequest(conn);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
catch
|
||
|
{
|
||
|
Thread.Sleep(100);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
~Server()
|
||
|
{
|
||
|
Dispose();
|
||
|
}
|
||
|
|
||
|
|
||
|
private static Socket CreateSocketBindAndListen(AddressFamily family, IPAddress address, int port)
|
||
|
{
|
||
|
Socket socket = new Socket(family, SocketType.Stream, ProtocolType.Tcp);
|
||
|
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||
|
socket.Bind(new IPEndPoint(address, port));
|
||
|
socket.Listen((int) SocketOptionName.MaxConnections);
|
||
|
return socket;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
///
|
||
|
/// </summary>
|
||
|
/// <param name="virtualPath"></param>
|
||
|
/// <param name="physicalPath"></param>
|
||
|
/// <param name="hostType"></param>
|
||
|
/// <returns></returns>
|
||
|
/// <remarks>This is Dmitry's hack to enable running outside of GAC</remarks>
|
||
|
private object CreateWorkerAppDomainWithHost(string virtualPath, string physicalPath, Type hostType)
|
||
|
{
|
||
|
// this creates worker app domain in a way that host doesn't need to be in GAC or bin
|
||
|
// using BuildManagerHost via private reflection
|
||
|
string uniqueAppString = string.Concat(virtualPath, physicalPath).ToLowerInvariant();
|
||
|
string appId = (uniqueAppString.GetHashCode()).ToString("x", CultureInfo.InvariantCulture);
|
||
|
|
||
|
// create BuildManagerHost in the worker app domain
|
||
|
//ApplicationManager appManager = ApplicationManager.GetApplicationManager();
|
||
|
Type buildManagerHostType = typeof (HttpRuntime).Assembly.GetType("System.Web.Compilation.BuildManagerHost");
|
||
|
IRegisteredObject buildManagerHost = _appManager.CreateObject(appId, buildManagerHostType, virtualPath,
|
||
|
physicalPath, false);
|
||
|
|
||
|
// call BuildManagerHost.RegisterAssembly to make Host type loadable in the worker app domain
|
||
|
buildManagerHostType.InvokeMember("RegisterAssembly",
|
||
|
BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.NonPublic,
|
||
|
null,
|
||
|
buildManagerHost,
|
||
|
new object[] {hostType.Assembly.FullName, hostType.Assembly.Location});
|
||
|
|
||
|
// create Host in the worker app domain
|
||
|
return _appManager.CreateObject(appId, hostType, virtualPath, physicalPath, false);
|
||
|
}
|
||
|
|
||
|
private void DecrementRequestCount()
|
||
|
{
|
||
|
_requestCount--;
|
||
|
|
||
|
if (_requestCount < 1)
|
||
|
{
|
||
|
_requestCount = 0;
|
||
|
|
||
|
if (_timeoutInterval > 0)
|
||
|
{
|
||
|
_timer = new Timer(TimeOut, null, _timeoutInterval, Timeout.Infinite);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private Host GetHost()
|
||
|
{
|
||
|
if (_shutdownInProgress)
|
||
|
return null;
|
||
|
Host host = _host;
|
||
|
if (host == null)
|
||
|
{
|
||
|
#if NET40
|
||
|
object obj2 = new object();
|
||
|
bool flag = false;
|
||
|
try
|
||
|
{
|
||
|
Monitor.Enter(obj2 = _lockObject, ref flag);
|
||
|
host = _host;
|
||
|
if (host == null)
|
||
|
{
|
||
|
host = (Host)CreateWorkerAppDomainWithHost(_virtualPath, _physicalPath, typeof(Host));
|
||
|
host.Configure(this, _port, _virtualPath, _physicalPath, _requireAuthentication, _disableDirectoryListing);
|
||
|
_host = host;
|
||
|
}
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
if (flag)
|
||
|
{
|
||
|
Monitor.Exit(obj2);
|
||
|
}
|
||
|
}
|
||
|
#else
|
||
|
|
||
|
lock (_lockObject)
|
||
|
{
|
||
|
host = _host;
|
||
|
if (host == null)
|
||
|
{
|
||
|
host = (Host) CreateWorkerAppDomainWithHost(_virtualPath, _physicalPath, typeof (Host));
|
||
|
host.Configure(this, _port, _virtualPath, _physicalPath, _requireAuthentication,
|
||
|
_disableDirectoryListing);
|
||
|
_host = host;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
return host;
|
||
|
}
|
||
|
|
||
|
private void IncrementRequestCount()
|
||
|
{
|
||
|
_requestCount++;
|
||
|
_timer = null;
|
||
|
}
|
||
|
|
||
|
|
||
|
private void ObtainProcessToken()
|
||
|
{
|
||
|
if (Interop.ImpersonateSelf(2))
|
||
|
{
|
||
|
Interop.OpenThreadToken(Interop.GetCurrentThread(), 0xf01ff, true, ref _processToken);
|
||
|
Interop.RevertToSelf();
|
||
|
// ReSharper disable PossibleNullReferenceException
|
||
|
_processUser = WindowsIdentity.GetCurrent().Name;
|
||
|
// ReSharper restore PossibleNullReferenceException
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnRequestComplete(Guid id, LogInfo requestLog, LogInfo responseLog)
|
||
|
{
|
||
|
EventHandler<RequestEventArgs> complete = RequestComplete;
|
||
|
|
||
|
if (complete != null)
|
||
|
{
|
||
|
complete(this, new RequestEventArgs(id, requestLog, responseLog));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public void ShutDown()
|
||
|
{
|
||
|
_shutdownInProgress = true;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (_socket != null)
|
||
|
{
|
||
|
_socket.Close();
|
||
|
}
|
||
|
}
|
||
|
// ReSharper disable EmptyGeneralCatchClause
|
||
|
catch
|
||
|
// ReSharper restore EmptyGeneralCatchClause
|
||
|
{
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_socket = null;
|
||
|
}
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (_host != null)
|
||
|
{
|
||
|
_host.Shutdown();
|
||
|
}
|
||
|
|
||
|
while (_host != null)
|
||
|
{
|
||
|
Thread.Sleep(100);
|
||
|
}
|
||
|
}
|
||
|
// ReSharper disable EmptyGeneralCatchClause
|
||
|
catch
|
||
|
// ReSharper restore EmptyGeneralCatchClause
|
||
|
{
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_host = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void TimeOut(object ignored)
|
||
|
{
|
||
|
TimeOut();
|
||
|
}
|
||
|
|
||
|
public void TimeOut()
|
||
|
{
|
||
|
ShutDown();
|
||
|
OnTimeOut();
|
||
|
}
|
||
|
|
||
|
private void OnTimeOut()
|
||
|
{
|
||
|
EventHandler handler = TimedOut;
|
||
|
if (handler != null) handler(this, EventArgs.Empty);
|
||
|
}
|
||
|
}
|
||
|
}
|