File: channels\http\httpremotinghandler.cs
Project: ndp\clr\src\managedlibraries\remoting\System.Runtime.Remoting.csproj (System.Runtime.Remoting)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//==========================================================================
//  File:       HTTPRemotingHandler.cs
//
//  Summary:    Implements an ASP+ handler that forwards requests to the
//              the remoting HTTP Channel.
//
//  Classes:    Derived from IHttpHandler
//
//
//==========================================================================
 
using System;
using System.DirectoryServices;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using System.Reflection;
using System.Collections;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
using System.Diagnostics;
using System.Web;
using System.Web.UI;
using System.Runtime.Remoting.MetadataServices;
using System.Globalization;
using System.Collections.Specialized;
 
namespace System.Runtime.Remoting.Channels.Http
{
 
    public class HttpRemotingHandler : IHttpHandler
    {
        //Handler Specific
 
        private static String ApplicationConfigurationFile = "web.config";
        private static bool bLoadedConfiguration = false;
        
        private static HttpHandlerTransportSink s_transportSink = null; // transport sink
 
 
        // If an exception occurs while we are configuring the app domain, it is not possible
        // to recover since remoting is in an indeterminate state, so we will return that 
        // exception every time.
        private static Exception s_fatalException = null;         
 
 
        public HttpRemotingHandler()
        {
        }
 
        /// <internalonly/>
        public HttpRemotingHandler(Type type, Object srvID)
        {            
        }
 
        //
        // Process the ASP+ Request
        //
        public void ProcessRequest(HttpContext context)
        {
            InternalProcessRequest(context);
        }
 
        //
        // Internal
        //
        // Transform the ASP+ Request and Response Structures in
        // Channel Structures:
        // ** Request.ServerVariables
        // ** Request.InputStream
        // ** Response.Headers
        //
        // This is needed to reduce the between dependency COR Channels
        // and ASP+
        //
 
        private void InternalProcessRequest(HttpContext context)
        {
            try
            {          
                HttpRequest httpRequest = context.Request;
            
                // check if have previously loaded configuration
                if (!bLoadedConfiguration)
                {
                    // locking a random static variable, so we can lock the class
                    lock(HttpRemotingHandler.ApplicationConfigurationFile)
                    {
                        if (!bLoadedConfiguration)
                        {                                                   
                            // Initialize IIS information
                            IisHelper.Initialize();
 
                            // set application name
                            if (RemotingConfiguration.ApplicationName == null)
                                RemotingConfiguration.ApplicationName = httpRequest.ApplicationPath;
                    
                            String filename = String.Concat(httpRequest.PhysicalApplicationPath, 
                                                            ApplicationConfigurationFile);
 
                            if (File.Exists(filename))
                            {
                                try
                                {
                                    RemotingConfiguration.Configure(filename, false/*enableSecurity*/);
                                }
                                catch (Exception e)
                                {
                                    s_fatalException = e;
                                    WriteException(context, e); 
                                    return;
                                }    
                            }
 
                            try
                            {
                                // do a search for a registered channel that wants to listen
                                IChannelReceiverHook httpChannel = null;
                                IChannel[] channels = ChannelServices.RegisteredChannels;
                                foreach (IChannel channel in channels)
                                {
                                    IChannelReceiverHook hook = channel as IChannelReceiverHook;
                                    if (hook != null)
                                    {
                                        if (String.Compare(hook.ChannelScheme, "http", StringComparison.OrdinalIgnoreCase) == 0)
                                        {
                                            if (hook.WantsToListen)
                                            {
                                                httpChannel = hook;
                                                break;
                                            }
                                        }
                                    }
                                }
                            
                                if (httpChannel == null)
                                {
                                    // No http channel that was listening found.
                                    // Create a new channel.
                                    HttpChannel newHttpChannel = new HttpChannel();
                                    ChannelServices.RegisterChannel(newHttpChannel, false/*enableSecurity*/);
                                    httpChannel = newHttpChannel;
                                }
 
                                String scheme = null;
                                if (IisHelper.IsSslRequired)
                                    scheme = "https";
                                else
                                    scheme = "http";
    
                                String hookChannelUri =
                                    scheme + "://" + CoreChannel.GetMachineIp();
 
                                int port = context.Request.Url.Port;
                                String restOfUri = ":" + port + "/" + RemotingConfiguration.ApplicationName;
                                hookChannelUri += restOfUri;                                   
 
                                // add hook uri for this channel
                                httpChannel.AddHookChannelUri(hookChannelUri);
                                
                                // If it uses ChannelDataStore, re-retrieve updated url in case it was updated.
                                ChannelDataStore cds = ((IChannelReceiver)httpChannel).ChannelData as ChannelDataStore;
                                if (cds != null)
                                    hookChannelUri = cds.ChannelUris[0];
 
                                IisHelper.ApplicationUrl = hookChannelUri;
 
                                // This is a hack to refresh the channel data.
                                //   In V-Next, we will add a ChannelServices.RefreshChannelData() api.
                                ChannelServices.UnregisterChannel(null);
                                                            
                                s_transportSink = new HttpHandlerTransportSink(httpChannel.ChannelSinkChain);
                            }
                            catch (Exception e)
                            {
                                s_fatalException = e;
                                WriteException(context, e);                            
                                return;
                            }
                            bLoadedConfiguration = true;
                        }
                    }
                }  
 
                if (s_fatalException == null) 
                {
                    if (!CanServiceRequest(context))
                        WriteException(context, new RemotingException(CoreChannel.GetResourceString("Remoting_ChnlSink_UriNotPublished")));
                    else                        
                        s_transportSink.HandleRequest(context);       
                }
                else                
                    WriteException(context, s_fatalException);                 
            }
            catch (Exception e)
            {
                WriteException(context, e);
            }
        } // InternalProcessRequest
        
        public bool IsReusable { get { return true; } }        
 
        string ComposeContentType(string contentType, Encoding encoding) {
            if (encoding != null) {
                StringBuilder sb = new StringBuilder(contentType);
                sb.Append("; charset=");
                sb.Append(encoding.WebName);
                return sb.ToString();
            }
            else
                return contentType;
        }
 
        bool CanServiceRequest(HttpContext context) {                        
            //Need to get the object uri first (cannot have query string)
            string requestUri = GetRequestUriForCurrentRequest(context);            
            string objectUri = HttpChannelHelper.GetObjectUriFromRequestUri(requestUri);              
            context.Items["__requestUri"] = requestUri;                                                  
            
            if (String.Compare(context.Request.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase) != 0) {
                //If the request backed by an existing object
                if (RemotingServices.GetServerTypeForUri(requestUri) != null)
                    return true;                                                                                       
            } 
            else {
                if (context.Request.QueryString.Count != 1) 
                    return false;
            
                string[] values =  context.Request.QueryString.GetValues(0);                       
                if (values.Length != 1 || String.Compare(values[0], "wsdl", StringComparison.OrdinalIgnoreCase) != 0)
                    return false;                                                        
            
                //If the request specifically asks for the wildcard                
                if (String.Compare(objectUri, "RemoteApplicationMetadata.rem", StringComparison.OrdinalIgnoreCase) == 0)
                    return true;
            
                // find last index of ?            
                int index = requestUri.LastIndexOf('?');
                if (index != -1) 
                    requestUri =  requestUri.Substring(0, index);
 
                //If the request backed by an existing object
                if (RemotingServices.GetServerTypeForUri(requestUri) != null)
                    return true;                                                                                                                                               
            }
            
            //If the request is backed by an existing file on disk it should be serviced
            if (File.Exists(context.Request.PhysicalPath))
                return true;                                            
            
            return false;      
        }
                
        string GetRequestUriForCurrentRequest(HttpContext context) {
            // we need to pull off any http specific data plus the application v-dir name
            String rawUrl = context.Request.RawUrl;
            // here's where we pull off channel info
            String channelUri;
            String requestUri;
            channelUri = HttpChannelHelper.ParseURL(rawUrl, out requestUri);
            if (channelUri == null)
                requestUri = rawUrl;
    
            // here's where we pull off the application v-dir name
            String appName = RemotingConfiguration.ApplicationName;
            if (appName != null && appName.Length > 0 && requestUri.Length > appName.Length)                                
                //  "/appname" should always be in front, otherwise we wouldn't
                //   be in this handler.                    
                requestUri = requestUri.Substring(appName.Length + 1);            
            
            return requestUri;
        }
        
        string GenerateFaultString(HttpContext context, Exception e) {
            //If the user has specified it's a development server (versus a production server) in ASP.NET config,
            //then we should just return e.ToString instead of extracting the list of messages.                        
            if (!CustomErrorsEnabled(context)) 
                return e.ToString();            
            else {                
                return CoreChannel.GetResourceString("Remoting_InternalError");                                                             
            }            
        }
        
        void WriteException(HttpContext context, Exception e) {
            InternalRemotingServices.RemotingTrace("HttpHandler: Exception thrown...\n");
            InternalRemotingServices.RemotingTrace(e.StackTrace);
            
            Stream outputStream = context.Response.OutputStream;
            context.Response.Clear();
            context.Response.ClearHeaders();
            context.Response.ContentType = ComposeContentType("text/plain", Encoding.UTF8);
            context.Response.TrySkipIisCustomErrors = true;
            context.Response.StatusCode = (int) HttpStatusCode.InternalServerError;
            context.Response.StatusDescription = CoreChannel.GetResourceString("Remoting_InternalError");                                                             
            StreamWriter writer = new StreamWriter(outputStream, new UTF8Encoding(false));
            writer.WriteLine(GenerateFaultString(context, e));
            writer.Flush();            
        }                
        
        internal static bool IsLocal(HttpContext context) {            
            string localAddress = context.Request.ServerVariables["LOCAL_ADDR"];
            string remoteAddress = context.Request.UserHostAddress;
            return (context.Request.Url.IsLoopback || (localAddress != null && remoteAddress != null && localAddress == remoteAddress));            
        }
        
        internal static bool CustomErrorsEnabled(HttpContext context) {
            try {            
                if (!context.IsCustomErrorEnabled)
                    return false;
                    
                return RemotingConfiguration.CustomErrorsEnabled(IsLocal(context));                
            }
            catch {
                return true;
            }                
        }
 
    } // HttpRemotingHandler
 
    public class HttpRemotingHandlerFactory : IHttpHandlerFactory
    {
        internal object _webServicesFactory = null;
        internal static Type s_webServicesFactoryType = null;
        // REMACT: internal static Type s_remActType = null;
 
        internal static Object s_configLock = new Object();
 
        internal static Hashtable s_registeredDynamicTypeTable = Hashtable.Synchronized(new Hashtable());
        
 
        void DumpRequest(HttpContext context)
        {
            HttpRequest request = context.Request;
            InternalRemotingServices.DebugOutChnl("Process Request called.");
            InternalRemotingServices.DebugOutChnl("Path = " + request.Path);
            InternalRemotingServices.DebugOutChnl("PhysicalPath = " + request.PhysicalPath);
            //InternalRemotingServices.DebugOutChnl("QueryString = " + request.Url.QueryString);
            InternalRemotingServices.DebugOutChnl("HttpMethod = " + request.HttpMethod);
            InternalRemotingServices.DebugOutChnl("ContentType = " + request.ContentType);
            InternalRemotingServices.DebugOutChnl("PathInfo = " + request.PathInfo);
 
            /*
            String[] keys = request.Headers.AllKeys;
            String[] values = request.Headers.All;
 
            for (int i=0; i<keys.Length; i++)
            {
                InternalRemotingServices.DebugOutChnl("Header :: " + keys[i] + "/" + values[i]);
            }
            */
        }
 
        private void ConfigureAppName(HttpRequest httpRequest)
        {
            if (RemotingConfiguration.ApplicationName == null)
            {
                lock (s_configLock)
                {
                    if (RemotingConfiguration.ApplicationName == null) 
                        RemotingConfiguration.ApplicationName = httpRequest.ApplicationPath;
                }
            }
        } // ConfigureAppName
 
 
        public IHttpHandler GetHandler(HttpContext context, string verb, string url, string filePath)
        {
            // REMACT: 
            // If this is a request to the root vdir, we will route it to the activation
            //   handler instead.
            //if (context.Request.ApplicationPath.Equals("/"))
            //{
            //    if (s_remActType == null)
            //        s_remActType = Type.GetType("System.Runtime.Remoting.Channels.Http.RemotingActivationHandler, System.Runtime.Remoting.Activation");
            //
            //    if (s_remActType != null)
            //        return (IHttpHandler)Activator.CreateInstance(s_remActType);                
            //}
        
            //if (CompModSwitches.Remote.TraceVerbose) DumpRequest(context);
            //System.Diagnostics.Debugger.Break();
 
            InternalRemotingServices.DebugOutChnl("HttpRemotingHandlderFactory::GetHanlder: IN");
 
            DumpRequest(context);  // 
 
            HttpRequest httpRequest = context.Request;
            ConfigureAppName(httpRequest);
            
            string queryString = httpRequest.QueryString[null];
 
            bool bVerbIsGET = (String.Compare(httpRequest.HttpMethod, "GET", StringComparison.OrdinalIgnoreCase) == 0);
            bool bFileExists = File.Exists(httpRequest.PhysicalPath);
 
            if (bVerbIsGET && bFileExists && queryString == null)
            {
                InternalRemotingServices.DebugOutChnl("HttpRemotingHandlderFactory::GetHanlder: non-post -- send to WebServices");
                return WebServicesFactory.GetHandler(context, verb, url, filePath);
            }
            else
            {
                InternalRemotingServices.DebugOutChnl("HttpRemotingHandlderFactory::GetHandler: post -- handling with Remoting");
                
                if (bFileExists)
                {
                    Type type = WebServiceParser.GetCompiledType(
                       url, context);
 
                    String machineAndAppName = Dns.GetHostName() + httpRequest.ApplicationPath;
        
                    // determine last part of url
                    String[] urlComponents = httpRequest.PhysicalPath.Split(new char[]{'\\'});
                    String uri = urlComponents[urlComponents.Length - 1] ;
 
                    // register the type if it has changed or hasn't been registered yet.
                    Type lastType = (Type)s_registeredDynamicTypeTable[uri];
                    if (lastType != type)
                    {
                        RegistrationHelper.RegisterType(machineAndAppName, type, uri);
                        s_registeredDynamicTypeTable[uri] = type;
                    }
 
                    return new HttpRemotingHandler();
                }
                else
                {
                  return new HttpRemotingHandler();
                }
            }
        }
 
        private IHttpHandlerFactory WebServicesFactory
        {
            get
            {
                if (_webServicesFactory == null)
                {
                    lock(this)
                    {
                        if (_webServicesFactory == null)
                        {
                            _webServicesFactory = Activator.CreateInstance(WebServicesFactoryType);
                        }
                    }
                }
                return (IHttpHandlerFactory)_webServicesFactory;
            }
        }
 
        private static Type WebServicesFactoryType
        {
            get
            {
                if (s_webServicesFactoryType == null)
                {
                    Assembly a = Assembly.Load("System.Web.Services, Version="+ThisAssembly.Version+", Culture=neutral, PublicKeyToken= "+AssemblyRef.MicrosoftPublicKey);
                    if (a == null)
                    {
                        throw new RemotingException(String.Format(CultureInfo.CurrentCulture, CoreChannel.GetResourceString("Remoting_AssemblyLoadFailed"), "System.Web.Services"));
                    }
        
                    s_webServicesFactoryType = a.GetType("System.Web.Services.Protocols.WebServiceHandlerFactory");
 
                }
                return s_webServicesFactoryType;
            }
        }
 
 
        public void ReleaseHandler(IHttpHandler handler)
        {
            if (_webServicesFactory != null)
            {
                ((IHttpHandlerFactory)_webServicesFactory).ReleaseHandler(handler);
                _webServicesFactory = null;
            }
        }
    } //class HttpRemotingHandlerFactory    
 
 
 
 
 
    internal static class RegistrationHelper
    {    
        public static void RegisterType(String machineAndAppName, Type type, String uri)
        {
            RemotingConfiguration.RegisterWellKnownServiceType(type, uri, WellKnownObjectMode.SingleCall);
 
            Type[] allTypes = type.Assembly.GetTypes();
            foreach (Type asmType in allTypes)
            {
                RegisterSingleType(machineAndAppName, asmType);
            }            
        } // RegisterType
 
        private static void RegisterSingleType(String machineAndAppName, Type type)
        {
            String xmlName = type.Name;            
            String xmlNamespace = "http://" + machineAndAppName + "/" + type.FullName;
            SoapServices.RegisterInteropXmlElement(xmlName, xmlNamespace, type);
            SoapServices.RegisterInteropXmlType(xmlName, xmlNamespace, type);
            
            if (typeof(MarshalByRefObject).IsAssignableFrom(type))
            {
                // register soap action for all methods if this is a MarshalByRefObject type
                MethodInfo[] methods = type.GetMethods();
                foreach (MethodInfo mi in methods)
                {
                    SoapServices.RegisterSoapActionForMethodBase(mi, xmlNamespace + "#" + mi.Name);
                }
            }
            
        } // RegisterSingleType
        
    } // class RegistrationHelper
    
 
 
 
 
    // channel sink for interfacing with a sink chain
    internal class HttpHandlerTransportSink : IServerChannelSink    
    {
        private const int _defaultChunkSize = 2048;
    
        // sink state
        public IServerChannelSink _nextSink;
 
 
        public HttpHandlerTransportSink(IServerChannelSink nextSink)
        {
            _nextSink = nextSink;
        } // HttpHandlerTransportSink
        
 
        public void HandleRequest(HttpContext context)
        {
            HttpRequest httpRequest = context.Request;
            HttpResponse httpResponse = context.Response;
 
            // get headers
            BaseTransportHeaders requestHeaders = new BaseTransportHeaders();
 
            requestHeaders["__RequestVerb"] = httpRequest.HttpMethod;
            requestHeaders["__CustomErrorsEnabled"] = HttpRemotingHandler.CustomErrorsEnabled(context);
            requestHeaders.RequestUri = (string)context.Items["__requestUri"];
 
            NameValueCollection headers = httpRequest.Headers;          
            String[] allKeys = headers.AllKeys;
 
            for (int httpKeyCount=0; httpKeyCount< allKeys.Length; httpKeyCount++)
            {
                String headerName = allKeys[httpKeyCount];
                String headerValue = headers[headerName];
                requestHeaders[headerName] = headerValue;
            }
 
            // add ip address to headers list
            requestHeaders.IPAddress = IPAddress.Parse(httpRequest.UserHostAddress);
 
            // get request stream
            Stream requestStream = httpRequest.InputStream;
 
            // process message
            ServerChannelSinkStack sinkStack = new ServerChannelSinkStack();
            sinkStack.Push(this, null);
            
            IMessage responseMessage;
            ITransportHeaders responseHeaders;
            Stream responseStream;
 
            ServerProcessing processing = 
                _nextSink.ProcessMessage(sinkStack, null, requestHeaders, requestStream, 
                                         out responseMessage,
                                         out responseHeaders, out responseStream);
                
            // handle response
            switch (processing)
            {                    
 
            case ServerProcessing.Complete:
            {
                // Send the response. Call completed synchronously.             
                SendResponse(httpResponse, 200, responseHeaders, responseStream);                
                break;
            } // case ServerProcessing.Complete
            
            case ServerProcessing.OneWay:
            {
                // Just send back a 202 Accepted
                SendResponse(httpResponse, 202, responseHeaders, responseStream);                
                break;
            } // case ServerProcessing.OneWay
 
            case ServerProcessing.Async:
            {
                // Async dispatching was cut from V.1.
                //sinkStack.StoreAndDispatch(this, streamManager);
                break;
            }// case ServerProcessing.Async
 
            } // switch (processing)
 
            
        } // HandleRequest
 
 
        private void SendResponse(HttpResponse httpResponse, int statusCode,
                                  ITransportHeaders responseHeaders, Stream httpContentStream)
        {
            // store headers
            if (responseHeaders != null)
            {
                // set server string
                String serverHeader = (String)responseHeaders["Server"];
                if (serverHeader != null)
                    serverHeader = HttpServerTransportSink.ServerHeader + ", " + serverHeader;
                else
                    serverHeader = HttpServerTransportSink.ServerHeader;
                responseHeaders["Server"] = serverHeader;
 
                // set status code
                Object userStatusCode = responseHeaders["__HttpStatusCode"]; // someone might have stored an int
 
                if (userStatusCode != null)
                    statusCode = Convert.ToInt32(userStatusCode, CultureInfo.InvariantCulture);           
 
                // see if stream has a content length
                if (httpContentStream != null)
                {
                    int length = -1;
                    try
                    {
                        if (httpContentStream != null)
                            length = (int)httpContentStream.Length;
                    } 
                    catch {}
 
                    if (length != -1)
                        responseHeaders["Content-Length"] = length;
                }
                else
                    responseHeaders["Content-Length"] = 0;
 
                // add headers to the response
                foreach (DictionaryEntry entry in responseHeaders)
                {
                    String key = (String)entry.Key;                
                    if (!key.StartsWith("__", StringComparison.Ordinal))
                        httpResponse.AppendHeader(key, entry.Value.ToString());
                }
            }
 
            httpResponse.TrySkipIisCustomErrors = true;
            httpResponse.StatusCode = statusCode;
 
            // send stream
            Stream httpResponseStream = httpResponse.OutputStream;            
 
            if(httpContentStream != null)
            {
                StreamHelper.CopyStream(httpContentStream, httpResponseStream);
                httpContentStream.Close();
            }
            
        } // SendResponse                 
 
 
        //
        // IServerChannelSink implementation
        //
 
        public ServerProcessing ProcessMessage(IServerChannelSinkStack sinkStack,
            IMessage requestMsg,
            ITransportHeaders requestHeaders, Stream requestStream,
            out IMessage responseMsg, out ITransportHeaders responseHeaders,
            out Stream responseStream)
        {
            throw new NotSupportedException();
        }
           
 
        public void AsyncProcessResponse(IServerResponseChannelSinkStack sinkStack, Object state,
                                         IMessage msg, ITransportHeaders headers, Stream stream)                 
        {
            // 
 
 
            throw new NotSupportedException();   
        } // AsyncProcessResponse
 
 
        public Stream GetResponseStream(IServerResponseChannelSinkStack sinkStack, Object state,
                                        IMessage msg, ITransportHeaders headers)
        {
            // we always want a stream to read from
            return null;
        } // GetResponseStream
 
 
        public IServerChannelSink NextChannelSink
        {
            get { return _nextSink; }
        } // Next
 
 
        public IDictionary Properties
        {
            get { return null; }
        } // Properties
        
        //
        // end of IServerChannelSink implementation
        //
        
    } // class HttpHandlerTransportSink
        
}//nameSpace