File: channels\http\httpserverchannel.cs
Project: ndp\clr\src\managedlibraries\remoting\System.Runtime.Remoting.csproj (System.Runtime.Remoting)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//==========================================================================
//  File:       HttpServerChannel.cs
//
//  Summary:    Implements a client channel that transmits method calls over HTTP.
//
//  Classes:    public HttpClientChannel
//              internal HttpClientTransportSink
//
//==========================================================================
 
using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Metadata;
using System.Runtime.Remoting.MetadataServices;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;
using System.Threading;
 
using System.Runtime.InteropServices;
using System.Globalization;
using System.Security.Permissions;
 
 
namespace System.Runtime.Remoting.Channels.Http
{
 
    public class HttpServerChannel : BaseChannelWithProperties,
                                     IChannelReceiver, IChannelReceiverHook
    {
        private int               _channelPriority = 1;  // priority of channel (default=1)
        private String            _channelName = "http server"; // channel name
        private String            _machineName = null;   // machine name
        private int               _port = -1;            // port to listen on
        private ChannelDataStore  _channelData = null;   // channel data
 
        private String _forcedMachineName = null; // an explicitly configured machine name
        private bool _bUseIpAddress = true; // by default, we'll use the ip address.
        //Assumption is Socket.OSSupportsIPv4 will be false only on OS >= Vista with IPv4 turned off.
        private IPAddress _bindToAddr = (Socket.OSSupportsIPv4) ? IPAddress.Any : IPAddress.IPv6Any; // address to bind to.
        private bool _bSuppressChannelData = false; // should we hand out null for our channel data
        
        private IServerChannelSinkProvider _sinkProvider = null;
        private HttpServerTransportSink    _transportSink = null;
        private IServerChannelSink         _sinkChain = null;
 
        private bool _wantsToListen = true;
        private bool _bHooked = false; // has anyone hooked into the channel?       
        
        
        private ExclusiveTcpListener  _tcpListener;
        private bool                  _bExclusiveAddressUse = true;
        private Thread                _listenerThread;
        private bool                  _bListening = false; // are we listening at the moment?
        private Exception             _startListeningException = null; // if an exception happens on the listener thread when attempting
                                                                       //   to start listening, that will get set here.
        private AutoResetEvent  _waitForStartListening = new AutoResetEvent(false);
 
 
        public HttpServerChannel() : base()
        {
            SetupMachineName();
            SetupChannel();
        }
 
        public HttpServerChannel(int port) : base()
        {
            _port = port;
            SetupMachineName();
            SetupChannel();
        } // HttpServerChannel()
    
        public HttpServerChannel(String name, int port) : base()
        {
            _channelName = name;
            _port = port;
            SetupMachineName();
            SetupChannel();
        } // HttpServerChannel()
 
        public HttpServerChannel(String name, int port, IServerChannelSinkProvider sinkProvider) : base()
        {
            _channelName = name;
            _port = port;
            _sinkProvider = sinkProvider;
            SetupMachineName();
            SetupChannel();
        } // HttpServerChannel()
 
 
        public HttpServerChannel(IDictionary properties, IServerChannelSinkProvider sinkProvider) : base()
        {               
            if (properties != null)
            {
                foreach (DictionaryEntry entry in properties)
                {
                    switch ((String)entry.Key)
                    {
                    case "name": _channelName = (String)entry.Value; break; 
                    case "bindTo": _bindToAddr = IPAddress.Parse((String)entry.Value); break;
                    case "listen": _wantsToListen = Convert.ToBoolean(entry.Value, CultureInfo.InvariantCulture); break;   
                    case "machineName": _forcedMachineName = (String)entry.Value; break;
                    case "port": _port = Convert.ToInt32(entry.Value, CultureInfo.InvariantCulture); break;
                    case "priority": _channelPriority = Convert.ToInt32(entry.Value, CultureInfo.InvariantCulture); break;
                    case "suppressChannelData": _bSuppressChannelData = Convert.ToBoolean(entry.Value, CultureInfo.InvariantCulture); break;
                    case "useIpAddress": _bUseIpAddress = Convert.ToBoolean(entry.Value, CultureInfo.InvariantCulture); break;
                    case "exclusiveAddressUse": _bExclusiveAddressUse = Convert.ToBoolean(entry.Value, CultureInfo.InvariantCulture); break;
                
                    default: 
                         break;
                    }
                }
            }
 
            _sinkProvider = sinkProvider;
            SetupMachineName();
            SetupChannel();
        } // HttpServerChannel
 
 
        internal bool IsSecured
        {
            get { return false; }
            set {
                if (_port >= 0 && value == true)
                    throw new RemotingException(CoreChannel.GetResourceString("Remoting_Http_UseIISToSecureHttpServer"));
            }
        }
        
        private void SetupMachineName()
        {
            if (_forcedMachineName != null)
            {
                // an explicitly configured machine name was used
                _machineName = CoreChannel.DecodeMachineName(_forcedMachineName);
            }
            else
            {
                if (!_bUseIpAddress)
                    _machineName = CoreChannel.GetMachineName();
                else
                {
                    if (_bindToAddr == IPAddress.Any || _bindToAddr == IPAddress.IPv6Any)
                    {
                        _machineName = CoreChannel.GetMachineIp();
                    }
                    else
                    {
                        _machineName = _bindToAddr.ToString();
                    }
                 
                    // Add [] around the ipadress for IPv6
                    if (_bindToAddr.AddressFamily == AddressFamily.InterNetworkV6)
                        _machineName = "[" + _machineName + "]";
 
                }
            }
        } // SetupMachineName
 
 
        private void SetupChannel()
        {   
            // set channel data
            // (These get changed inside of StartListening(), in the case where the listen
            //   port is 0, because we can't determine the port number until after the
            //   TcpListener starts.)
 
            _channelData = new ChannelDataStore(null);
            if (_port > 0)
            {
                String channelUri = GetChannelUri();
                _channelData.ChannelUris = new String[1];
                _channelData.ChannelUris[0] = channelUri;
 
                _wantsToListen = false;
            }
 
            // set default provider (soap formatter) if no provider has been set
            if (_sinkProvider == null)
                _sinkProvider = CreateDefaultServerProviderChain();
 
            CoreChannel.CollectChannelDataFromServerSinkProviders(_channelData, _sinkProvider);
 
            // construct sink chain
            _sinkChain = ChannelServices.CreateServerChannelSinkChain(_sinkProvider, this);
            _transportSink = new HttpServerTransportSink(_sinkChain);
 
            // set sink properties on base class, so that properties will be chained.
            SinksWithProperties = _sinkChain;
            
            if (_port >= 0)
            {
                // Open a TCP port and create a thread to start listening
                _tcpListener = new ExclusiveTcpListener(_bindToAddr, _port);
                ThreadStart t = new ThreadStart(this.Listen);
                _listenerThread = new Thread(t);
                _listenerThread.IsBackground = true;
 
                // Wait for thread to spin up
                StartListening(null);
            }
        } // SetupChannel
 
 
        private IServerChannelSinkProvider CreateDefaultServerProviderChain()
        {
            IServerChannelSinkProvider chain = new SdlChannelSinkProvider();            
            IServerChannelSinkProvider sink = chain;
            
            sink.Next = new SoapServerFormatterSinkProvider();
            sink = sink.Next;
            sink.Next = new BinaryServerFormatterSinkProvider();
            
            return chain;
        } // CreateDefaultServerProviderChain
 
 
        //
        // IChannel implementation
        //
 
        public int ChannelPriority
        {
            [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
            get { return _channelPriority; }
        }
 
        public String ChannelName
        {
            [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
            get { return _channelName; }
        }
 
        // returns channelURI and places object uri into out parameter
        [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
        public String Parse(String url, out String objectURI)
        {            
            return HttpChannelHelper.ParseURL(url, out objectURI);
        } // Parse
 
        //
        // end of IChannel implementation
        //
 
 
        //
        // IChannelReceiver implementation
        //
 
        public Object ChannelData
        {
            [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
            get
            {
                if (!_bSuppressChannelData && 
                        (_bListening || _bHooked))
                {
                    return _channelData;
                }
                else
                {
                    return null;
                }
            }
        } // ChannelData
 
 
        public String GetChannelUri()
        {
            if ((_channelData != null) && (_channelData.ChannelUris != null))
            {
                return _channelData.ChannelUris[0];
            }
            else
            {
                return "http://" + _machineName + ":" + _port;
            }
        } // GetChannelURI
 
 
        [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
        public virtual String[] GetUrlsForUri(String objectUri)
        {
            String[] retVal = new String[1];
 
            if (!objectUri.StartsWith("/", StringComparison.Ordinal))
                objectUri = "/" + objectUri;
            retVal[0] = GetChannelUri() + objectUri;
 
            return retVal;
        } // GetURLsforURI
 
 
        [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
        public void StartListening(Object data)
        {
            InternalRemotingServices.RemotingTrace("HttpChannel.StartListening");
 
            if (_port >= 0)
            {
                if (_listenerThread.IsAlive == false)
                {
                    _listenerThread.Start();
                    _waitForStartListening.WaitOne(); // listener thread will signal this after starting TcpListener
 
                    if (_startListeningException != null)
                    {
                        // An exception happened when we tried to start listening (such as "socket already in use)
                        Exception e = _startListeningException;
                        _startListeningException = null;
                        throw e;
                    }
 
                    _bListening = true;
 
                    // get new port assignment if a port of 0 was used to auto-select a port
                    if (_port == 0)
                    {
                        _port = ((IPEndPoint)_tcpListener.LocalEndpoint).Port;
                        
                        if (_channelData != null)
                        {
                            String channelUri = GetChannelUri();
                            _channelData.ChannelUris = new String[1];
                            _channelData.ChannelUris[0] = channelUri;
                        }
                    }
                }
            }
        } // StartListening
 
 
        [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
        public void StopListening(Object data)
        {
            InternalRemotingServices.RemotingTrace("HTTPChannel.StopListening");
 
            if (_port > 0)
            {
                _bListening = false;
            
                // Ask the TCP listener to stop listening on the port
                if(null != _tcpListener)
                {
                    _tcpListener.Stop();
                }
            }
        } // StopListening
 
        //
        // end of IChannelReceiver implementation
        //
 
        //
        // IChannelReceiverHook implementation
        //
 
        public String ChannelScheme {
            [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure)] 
            get { return "http"; }
        }
 
        public bool WantsToListen 
        { 
            [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
            get { return _wantsToListen; } 
            set { _wantsToListen = value; }
        } // WantsToListen
 
        public IServerChannelSink ChannelSinkChain { 
            [SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure)] 	
            get { return _sinkChain; }
        }
 
        [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
        public void AddHookChannelUri(String channelUri)
        {
            if (_channelData.ChannelUris != null)
            {
                throw new RemotingException(
                    CoreChannel.GetResourceString("Remoting_Http_LimitListenerOfOne"));
            }
            else
            {
                // replace machine name with explicitly configured
                //   machine name or ip address if necessary
                if (_forcedMachineName != null)
                {
                    channelUri = 
                        HttpChannelHelper.ReplaceMachineNameWithThisString(channelUri, _forcedMachineName);
                }
                else
                if (_bUseIpAddress)
                {
                    channelUri = 
                        HttpChannelHelper.ReplaceMachineNameWithThisString(channelUri, CoreChannel.GetMachineIp());
                }
            
                _channelData.ChannelUris = new String[] { channelUri };
                _wantsToListen = false;
                _bHooked = true;
            }
        } // AddHookChannelUri
        
        
        //
        // end of IChannelReceiverHook implementation
        //
 
        // Thread for listening
        void Listen()
        {
            bool bOkToListen = false;
        
            try
            {
                _tcpListener.Start(_bExclusiveAddressUse);
                bOkToListen = true;
            }
            catch (Exception e)
            {
                _startListeningException = e;
            }
 
            _waitForStartListening.Set(); // allow main thread to continue now that we have tried to start the socket                
 
            InternalRemotingServices.RemotingTrace( "Waiting to Accept the Socket on Port: " + _port);
 
            //
            // Wait for an incoming socket
            //
            Socket socket;
            
            while (bOkToListen)
            {
                InternalRemotingServices.RemotingTrace("TCPChannel::Listen - tcpListen.Pending() == true");                
 
                try
                {
                    socket = _tcpListener.AcceptSocket();
 
                    if (socket == null)
                    {
                        throw new RemotingException(
                            String.Format(
                                CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_Socket_Accept"),
                                Marshal.GetLastWin32Error().ToString(CultureInfo.InvariantCulture)));
                    }
                    else
                    {
                        // disable nagle delay
                        socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, 1);
                        // Set keepalive flag, so that inactive sockets can be cleaned up
                        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, 1);
 
                        // set linger option
                        LingerOption lingerOption = new LingerOption(true, 3);
                        socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, lingerOption);
                        
                        Stream netStream = new SocketStream(socket);
                        HttpServerSocketHandler streamManager = null;
 
                        //Create the socket Handler
                        streamManager = new HttpServerSocketHandler(socket, CoreChannel.RequestQueue, netStream);
 
                        // @
 
 
 
                        streamManager.DataArrivedCallback = new WaitCallback(_transportSink.ServiceRequest);
                        streamManager.BeginReadMessage();               
                    }
                } 
                catch (Exception e)
                {
                    if (!_bListening)
                    {
                        // We called Stop() on the tcp listener, so gracefully exit.
                        bOkToListen = false;                        
                    }
                    else
                    {
                        // we want the exception to show up as unhandled since this
                        //   is an unexpected failure.
                        if (!(e is SocketException))
                        {
                            // <
 
 
 
                        }
                    }
                }
            } // while (bOkToListen)
        }
 
        //
        // Support for properties (through BaseChannelWithProperties)
        //
 
        public override Object this[Object key]
        {
            get { return null; }
        
            set
            {
            }
        } // this[]
    
        public override ICollection Keys 
        {
            get
            {
                return new ArrayList(); 
            }
        }
 
    } // HttpServerChannel
 
    
 
 
    internal class HttpServerTransportSink : IServerChannelSink
    {
        private static String s_serverHeader =
            "MS .NET Remoting, MS .NET CLR " + System.Environment.Version.ToString();
    
        // sink state
        private IServerChannelSink _nextSink;
        
 
        public HttpServerTransportSink(IServerChannelSink nextSink)
        {
            _nextSink = nextSink;
        } // IServerChannelSink
        
    
        internal void ServiceRequest(Object state)
        {        
            HttpServerSocketHandler streamManager = (HttpServerSocketHandler)state;
 
            ITransportHeaders headers = streamManager.ReadHeaders();
            Stream requestStream = streamManager.GetRequestStream();
            headers["__CustomErrorsEnabled"] = streamManager.CustomErrorsEnabled();
 
            // process request
            ServerChannelSinkStack sinkStack = new ServerChannelSinkStack();
            sinkStack.Push(this, streamManager);
 
            IMessage responseMessage;
            ITransportHeaders responseHeaders;
            Stream responseStream;
 
            ServerProcessing processing = 
                _nextSink.ProcessMessage(sinkStack, null, headers, requestStream, 
                                         out responseMessage,
                                         out responseHeaders, out responseStream);
 
            // handle response
            switch (processing)
            {                    
 
            case ServerProcessing.Complete:
            {
                // Send the response. Call completed synchronously.
                sinkStack.Pop(this);
                streamManager.SendResponse(responseStream, "200", "OK", responseHeaders);
                break;
            } // case ServerProcessing.Complete
            
            case ServerProcessing.OneWay:
            {
                // Just send back a 200 OK
                streamManager.SendResponse(null, "202", "Accepted", responseHeaders);
                break;
            } // case ServerProcessing.OneWay
 
            case ServerProcessing.Async:
            {
                sinkStack.StoreAndDispatch(this, streamManager);
                break;
            }// case ServerProcessing.Async
 
            } // switch (processing)
 
 
            // async processing will take care if handling this later
            if (processing != ServerProcessing.Async)
            {
                if (streamManager.CanServiceAnotherRequest())
                    streamManager.BeginReadMessage();
                else
                    streamManager.Close();
            }
            
        } // ServiceRequest
      
 
 
 
        //
        // IServerChannelSink implementation
        //
 
        public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
            IMessage requestMsg,
            ITransportHeaders requestHeaders, Stream requestStream,
            out IMessage responseMsg, out ITransportHeaders responseHeaders,
            out Stream responseStream)
        {
            // NOTE: This doesn't have to be implemented because the server transport
            //   sink is always first.
            throw new NotSupportedException();
        } // ProcessMessage
           
 
        public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, Object state,
                                         IMessage msg, ITransportHeaders headers, Stream stream)                 
        {
            HttpServerSocketHandler streamManager = null;
 
            streamManager = (HttpServerSocketHandler)state;
 
            // send the response
            streamManager.SendResponse(stream, "200", "OK", headers);
 
            if (streamManager.CanServiceAnotherRequest())
                streamManager.BeginReadMessage();
            else
                streamManager.Close();            
        } // AsyncProcessResponse
 
 
        public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, Object state,
                                        IMessage msg, ITransportHeaders headers)
        {
            HttpServerSocketHandler streamManager = (HttpServerSocketHandler)state;
 
            if (streamManager.AllowChunkedResponse)
                return streamManager.GetResponseStream("200", "OK", headers);
            else
                return null;
        } // GetResponseStream
 
 
        public IServerChannelSink NextChannelSink
        {
            get { return _nextSink; }
        }
 
 
        public IDictionary Properties
        {
            get { return null; }
        } // Properties
        
        //
        // end of IServerChannelSink implementation
        //
 
 
        internal static String ServerHeader
        {
            get { return s_serverHeader; }
        }
 
        
        
    } // HttpServerTransportSink
 
 
 
    internal class ErrorMessage: IMethodCallMessage
    {
 
        // IMessage
        public IDictionary Properties     { get{ return null;} }
 
        // IMethodMessage
        public String Uri                      { get{ return m_URI; } }
        public String MethodName               { get{ return m_MethodName; }}
        public String TypeName                 { get{ return m_TypeName; } }
        public Object MethodSignature          { get { return m_MethodSignature;} }
        public MethodBase MethodBase           { get { return null; }}
        public int ArgCount                    { get { return m_ArgCount;} }
        public String GetArgName(int index)    { return m_ArgName; }
        public Object GetArg(int argNum)       { return null;}
        public Object[] Args                   { get { return null;} }
 
        public bool HasVarArgs                 { get { return false;} }
        public LogicalCallContext LogicalCallContext { get { return null; }}
 
 
        // IMethodCallMessage
        public int InArgCount                  { get { return m_ArgCount;} }
        public String GetInArgName(int index)   { return null; }
        public Object GetInArg(int argNum)      { return null;}
        public Object[] InArgs                { get { return null; }}
 
        String m_URI = "Exception";
        String m_MethodName = "Unknown";
        String m_TypeName = "Unknown";
        Object m_MethodSignature = null;
        int m_ArgCount = 0;
        String m_ArgName = "Unknown";
    } // ErrorMessage
 
 
 
 
} // namespace System.Runtime.Remoting.Channels.Http