File: channels\http\httpclientchannel.cs
Project: ndp\clr\src\managedlibraries\remoting\System.Runtime.Remoting.csproj (System.Runtime.Remoting)
// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
//==========================================================================
//  File:       HttpClientChannel.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.Cache;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.ComponentModel;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Messaging;
#if !FEATURE_PAL
using System.Security.Cryptography.X509Certificates;
#endif
using System.Threading;
using System.Globalization;
using System.Security.Permissions;
 
namespace System.Runtime.Remoting.Channels.Http
{
 
 
 
    public class HttpClientChannel : BaseChannelWithProperties, IChannelSender, ISecurableChannel
    {
        // Property Keys (purposely all lower-case)
        private const String ProxyNameKey = "proxyname";
        private const String ProxyPortKey = "proxyport";
 
        // If above keys get modified be sure to modify, the KeySet property on this
        // class.
        private static ICollection s_keySet = null;
        
    
        // Settings
        private int    _channelPriority = 1;  // channel priority
        private String _channelName = "http client"; // channel name
 
        // Proxy settings (_proxyObject gets recreated when _proxyName and _proxyPort are updated)
        private IWebProxy _proxyObject = null; // proxy object for request, can be overridden in transport sink
        private String    _proxyName = null;
        private int       _proxyPort = -1;
        private int _timeout = System.Threading.Timeout.Infinite;       // default timeout is infinite
 
        private int _clientConnectionLimit = 0; // bump connection limit to at least this number (only meaningful if > 0)
        private bool _bUseDefaultCredentials = false; // should default credentials be used?
        private bool _bAuthenticatedConnectionSharing = true;
        private bool _secure = false;
        
        private IClientChannelSinkProvider _sinkProvider = null; // sink chain provider                       
 
 
        public HttpClientChannel()
        {
            SetupChannel();
        } // HttpClientChannel()
 
 
        public HttpClientChannel(String name, IClientChannelSinkProvider sinkProvider)
        {
            _channelName = name;
            _sinkProvider = sinkProvider;
 
            SetupChannel();
        } // HttpClientChannel(IClientChannelSinkProvider sinkProvider)
       
 
        // constructor used by config file
        public HttpClientChannel(IDictionary properties, IClientChannelSinkProvider sinkProvider)
        {
            if (properties != null)
            {
                foreach (DictionaryEntry entry in properties)
                {
                    switch ((String)entry.Key)
                    {
                    case "name": _channelName = (String)entry.Value; break;
                    case "priority": _channelPriority = Convert.ToInt32(entry.Value, CultureInfo.InvariantCulture); break;
 
                    case "proxyName": this["proxyName"] = entry.Value; break;
                    case "proxyPort": this["proxyPort"] = entry.Value; break;
                    case "timeout": _timeout = Convert.ToInt32(entry.Value, CultureInfo.InvariantCulture); break;
 
                    case "clientConnectionLimit": 
                    {
                        _clientConnectionLimit = Convert.ToInt32(entry.Value, CultureInfo.InvariantCulture); 
                        break;
                    }
 
                    case "useDefaultCredentials":
                    {
                        _bUseDefaultCredentials = Convert.ToBoolean(entry.Value, CultureInfo.InvariantCulture);
                        break;
                    }
                    
                    case "useAuthenticatedConnectionSharing":
                    {
                        _bAuthenticatedConnectionSharing = Convert.ToBoolean(entry.Value, CultureInfo.InvariantCulture); 
                        break;
                    }
 
                    default: 
                         break;
                    }
                }
            }
 
            _sinkProvider = sinkProvider;
 
            SetupChannel();
        } // HttpClientChannel
        
 
        private void SetupChannel()
        {
            if (_sinkProvider != null)
            {
                CoreChannel.AppendProviderToClientProviderChain(
                    _sinkProvider, new HttpClientTransportSinkProvider(_timeout));                                                
            }
            else
                _sinkProvider = CreateDefaultClientProviderChain();
                
        } // SetupChannel()
 
 
 
        //
        // ISecurableChannel implementation
        //
        public bool IsSecured
        {
            [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
            get { return _secure; }
            [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
            set { _secure = value; }
        }
        
        //
        // 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
        // 
 
 
 
        //
        // IChannelSender implementation
        //
 
        [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure, Infrastructure=true)]
        public virtual IMessageSink CreateMessageSink(String url, Object remoteChannelData, out String objectURI)
        {
            // Set the out parameters
            objectURI = null;
            String channelURI = null;
 
            
            if (url != null) // Is this a well known object?
            {
                // Parse returns null if this is not one of our url's
                channelURI = Parse(url, out objectURI);
            }
            else // determine if we want to connect based on the channel data
            {
                if (remoteChannelData != null)
                {
                    if (remoteChannelData is IChannelDataStore)
                    {
                        IChannelDataStore cds = (IChannelDataStore)remoteChannelData;
 
                        // see if this is an http uri
                        String simpleChannelUri = Parse(cds.ChannelUris[0], out objectURI);
                        if (simpleChannelUri != null)
                            channelURI = cds.ChannelUris[0];
                    }
                }
            }
 
            if (channelURI != null)
            {
                if (url == null)
                    url = channelURI;
 
                if (_clientConnectionLimit > 0)
                {
                    ServicePoint sp = ServicePointManager.FindServicePoint(new Uri(channelURI));
                    if (sp.ConnectionLimit < _clientConnectionLimit)
                        sp.ConnectionLimit = _clientConnectionLimit;
                }
 
                // This will return null if one of the sink providers decides it doesn't
                // want to allow (or can't provide) a connection through this channel.
                IClientChannelSink sink = _sinkProvider.CreateSink(this, url, remoteChannelData);
                
                // return sink after making sure that it implements IMessageSink
                IMessageSink msgSink = sink as IMessageSink;
                if ((sink != null) && (msgSink == null))
                {
                    throw new RemotingException(
                        CoreChannel.GetResourceString("Remoting_Channels_ChannelSinkNotMsgSink"));
                }
                    
                return msgSink;
            }
 
            return null;
        } // CreateMessageSink
 
 
        //
        // end of IChannelSender implementation
        //
 
 
        private IClientChannelSinkProvider CreateDefaultClientProviderChain()
        {
            IClientChannelSinkProvider chain = new SoapClientFormatterSinkProvider();            
            IClientChannelSinkProvider sink = chain;
            
            sink.Next = new HttpClientTransportSinkProvider(_timeout);
            
            return chain;
        } // CreateDefaultClientProviderChain
        
 
 
        //
        // Support for properties (through BaseChannelSinkWithProperties)
        //
        
        public override Object this[Object key]
        {
            get
            {
                String keyStr = key as String;
                if (keyStr == null)
                    return null;
            
                switch (keyStr.ToLower(CultureInfo.InvariantCulture))
                {
                    case ProxyNameKey: return _proxyName;
                    case ProxyPortKey: return _proxyPort;
                } // switch (keyStr.ToLower(CultureInfo.InvariantCulture))
 
                return null;
            }
 
            set
            {
                String keyStr = key as String;
                if (keyStr == null)
                    return;
    
                switch (keyStr.ToLower(CultureInfo.InvariantCulture))
                {
                    case ProxyNameKey: _proxyName = (String)value; UpdateProxy(); break;
                    case ProxyPortKey: _proxyPort = Convert.ToInt32(value, CultureInfo.InvariantCulture); UpdateProxy(); break;                        
                } // switch (keyStr.ToLower(CultureInfo.InvariantCulture))
            }
        } // this[]   
 
 
        public override ICollection Keys
        {
            get
            {
                if (s_keySet == null)
                {
                    // Don't need to synchronize. Doesn't matter if the list gets
                    // generated twice.
                    ArrayList keys = new ArrayList(2);
                    keys.Add(ProxyNameKey);
                    keys.Add(ProxyPortKey);
                    
                    s_keySet = keys;
                }
 
                return s_keySet;
            }
        } // Keys
 
 
        //
        // end of Support for properties
        //
 
 
        //
        // Helper functions for processing settings and properties
        //
 
        // Called to recreate proxy object whenever the proxy name or port is changed.
        private void UpdateProxy()
        {
            if ((_proxyName != null) && (_proxyName.Length > 0) &&                
                (_proxyPort > 0))
            {
                WebProxy proxy = new WebProxy(_proxyName, _proxyPort);
                
                // disable proxy use when the host is local. i.e. without periods
                proxy.BypassProxyOnLocal = true;
 
                // setup bypasslist to include local ip address
                String[] bypassList = new String[]{ CoreChannel.GetMachineIp() };
                proxy.BypassList = bypassList;
 
                _proxyObject = proxy;
            }
            else
            {
                _proxyObject = new WebProxy();
            }
        } // UpdateProxy
 
        //
        // end of Helper functions for processing settings and properties
        //
 
        //
        // Methods to access properties (internals are for use by the transport sink)     
        //
 
        internal IWebProxy ProxyObject { get { return _proxyObject; } }
        internal bool UseDefaultCredentials { get { return _secure || _bUseDefaultCredentials; } }
        internal bool UseAuthenticatedConnectionSharing { get { return _bAuthenticatedConnectionSharing; } }
 
        //
        // end of Methods to access properties
        //
 
    } // class HttpClientChannel
 
 
 
 
    internal class HttpClientTransportSinkProvider : IClientChannelSinkProvider
    {
        int _timeout;
        
        internal HttpClientTransportSinkProvider(int timeout)
        {
            _timeout = timeout;
        }    
   
        public IClientChannelSink CreateSink(IChannelSender channel, String url, 
                                             Object remoteChannelData)
        {
            // url is set to the channel uri in CreateMessageSink        
            HttpClientTransportSink sink = new HttpClientTransportSink((HttpClientChannel)channel, url);
            sink["timeout"] = _timeout;
            return sink;
        }
 
        public IClientChannelSinkProvider Next
        {
            get { return null; }
            set { throw new NotSupportedException(); }
        }
    } // class HttpClientTransportSinkProvider
 
 
 
 
    // transport sender sink used by HttpClientChannel
    internal class HttpClientTransportSink : BaseChannelSinkWithProperties, IClientChannelSink
    {
        private const String s_defaultVerb = "POST";
 
        private static String s_userAgent =
            "Mozilla/4.0+(compatible; MSIE 6.0; Windows " + 
#if !FEATURE_PAL
            System.Environment.OSVersion.Version +
#endif
            "; MS .NET Remoting; MS .NET CLR " + System.Environment.Version.ToString() + " )";
        
        // Property keys (purposely all lower-case)
        private const String UserNameKey = "username";
        private const String PasswordKey = "password";
        private const String DomainKey = "domain";
        private const String PreAuthenticateKey = "preauthenticate";
        private const String CredentialsKey = "credentials";
        private const String ClientCertificatesKey = "clientcertificates";
        private const String ProxyNameKey = "proxyname";
        private const String ProxyPortKey = "proxyport";
        private const String TimeoutKey = "timeout";
        private const String AllowAutoRedirectKey = "allowautoredirect";
        private const String UnsafeAuthenticatedConnectionSharingKey = "unsafeauthenticatedconnectionsharing";        
        private const String ConnectionGroupNameKey = "connectiongroupname";        
 
        // If above keys get modified be sure to modify, the KeySet property on this
        // class.
        private static ICollection s_keySet = null;
 
        // Property values
        private String _securityUserName = null;
        private String _securityPassword = null;
        private String _securityDomain = null;
        private bool   _bSecurityPreAuthenticate = false;
        private bool   _bUnsafeAuthenticatedConnectionSharing = false;
        private String _connectionGroupName = null;
        private ICredentials _credentials = null; // this overrides all of the other security settings
#if !FEATURE_PAL
        private X509CertificateCollection _certificates = null;
#endif
 
        private int  _timeout = System.Threading.Timeout.Infinite; // timeout value in milliseconds (only used if greater than 0)
        private bool _bAllowAutoRedirect = false;
 
        // Proxy settings (_proxyObject gets recreated when _proxyName and _proxyPort are updated)
        private IWebProxy _proxyObject = null; // overrides channel proxy object if non-null
        private String    _proxyName = null;
        private int       _proxyPort = -1;
        private static RequestCachePolicy s_requestCachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
 
        // Other members
        private HttpClientChannel _channel; // channel that created this sink
        private String            _channelURI; // complete url to remote object        
 
        // settings
        private bool _useChunked = false; // 
        private bool _useKeepAlive = true;
 
        internal HttpClientTransportSink(HttpClientChannel channel, String channelURI) : base()
        {
            _channel = channel;
        
            _channelURI = channelURI;
            
            // make sure channel uri doesn't end with a slash.
            if (_channelURI.EndsWith("/", StringComparison.Ordinal))
                _channelURI = _channelURI.Substring(0, _channelURI.Length - 1);
                
        } // HttpClientTransportSink
        
 
        public void ProcessMessage(IMessage msg,
                                   ITransportHeaders requestHeaders, Stream requestStream,
                                   out ITransportHeaders responseHeaders, out Stream responseStream)
        {
 
            InternalRemotingServices.RemotingTrace("HttpTransportSenderSink::ProcessMessage");
 
            HttpWebRequest httpWebRequest = ProcessAndSend(msg, requestHeaders, requestStream);
 
            // receive server response
            HttpWebResponse response = null;
            try
            {
                response = (HttpWebResponse)httpWebRequest.GetResponse();
            }
            catch (WebException webException)
            {
                ProcessResponseException(webException, out response);
            }
    
            ReceiveAndProcess(response, out responseHeaders, out responseStream);
        } // ProcessMessage
 
        public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg,
                                        ITransportHeaders headers, Stream stream)
        {
            // Send the webrequest, headers, request stream, and retry count.
            AsyncHttpClientRequestState asyncRequestState =
                new AsyncHttpClientRequestState(this, sinkStack, msg, headers, stream, 1);
                       
            asyncRequestState.StartRequest();
        } // AsyncProcessRequest                      
 
 
        private static void ProcessResponseException(WebException webException, out HttpWebResponse response)
        {
            // if a timeout occurred throw a RemotingTimeoutException
            if (webException.Status == WebExceptionStatus.Timeout)
                throw new RemotingTimeoutException(
                    CoreChannel.GetResourceString(
                        "Remoting_Channels_RequestTimedOut"),
                    webException);
        
            response = webException.Response as HttpWebResponse;
            if ((response == null))
                throw webException;                
                
            // if server error (500-599 continue with processing the soap fault);
            //   otherwise, rethrow the exception.
 
            int statusCode = (int)(response.StatusCode);
            if ((statusCode < 500) || 
                (statusCode > 599))
            {
                throw webException;
            }   
        } // ProcessResponseException
 
 
        public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, Object state,
                                         ITransportHeaders headers, Stream stream)
        {
            // We don't have to implement this since we are always last in the chain.
        } // AsyncProcessRequest
        
 
        
        public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
        {
            // <
 
            return null; 
        } // GetRequestStream
 
 
        public IClientChannelSink NextChannelSink
        {
            get { return null; }
        }
    
 
 
        private HttpWebRequest SetupWebRequest(IMessage msg, ITransportHeaders headers)
        {
            IMethodCallMessage mcMsg = msg as IMethodCallMessage;        
 
            String msgUri = (String)headers[CommonTransportKeys.RequestUri];
            InternalRemotingServices.RemotingTrace("HttpClientChannel::SetupWebRequest Message uri is " + msgUri);
 
            if (msgUri == null)
            {
                if (mcMsg != null)
                    msgUri = mcMsg.Uri;
                else
                    msgUri = (String)msg.Properties["__Uri"];
            }
            
            String fullPath;
            if (HttpChannelHelper.StartsWithHttp(msgUri) != -1)
            {
                // this is the full path
                fullPath = msgUri;
            }
            else
            {
                // this is not the full path (_channelURI never has trailing slash)
                if (!msgUri.StartsWith("/", StringComparison.Ordinal))
                    msgUri = "/" + msgUri;
             
                fullPath = _channelURI + msgUri;                
            }
            InternalRemotingServices.RemotingTrace("HttpClientChannel::SetupWebRequest FullPath " + fullPath);
 
            // based on headers, initialize the network stream
 
            String verb = (String)headers["__RequestVerb"];
            if (verb == null)
                verb = s_defaultVerb;            
 
            HttpWebRequest httpWebRequest = (HttpWebRequest)WebRequest.Create(fullPath);
            httpWebRequest.AllowAutoRedirect = _bAllowAutoRedirect;
            httpWebRequest.Method = verb;
            httpWebRequest.SendChunked = _useChunked; 
            httpWebRequest.KeepAlive = _useKeepAlive;
            httpWebRequest.Pipelined = false;
            httpWebRequest.UserAgent = s_userAgent;
            httpWebRequest.Timeout = _timeout;
            httpWebRequest.CachePolicy = s_requestCachePolicy; 
 
            // see if we should use a proxy object
            IWebProxy proxy = _proxyObject;
            if (proxy == null) // use channel proxy if one hasn't been explicity set for this sink
                proxy = _channel.ProxyObject;
            if (proxy != null)
                httpWebRequest.Proxy = proxy; 
                                            
            // see if security should be used
            //   order of applying credentials is:
            //   1. check for explicitly set credentials
            //   2. else check for explicitly set username, password, domain
            //   3. else use default credentials if channel is configured to do so.
            if (_credentials != null)
            {                            
                httpWebRequest.Credentials = _credentials;
                httpWebRequest.PreAuthenticate = _bSecurityPreAuthenticate;
                httpWebRequest.UnsafeAuthenticatedConnectionSharing = _bUnsafeAuthenticatedConnectionSharing;
                if (_connectionGroupName != null)
                    httpWebRequest.ConnectionGroupName = _connectionGroupName;
            }
            else
            if (_securityUserName != null)
            {
                if (_securityDomain == null)
                    httpWebRequest.Credentials = new NetworkCredential(_securityUserName, _securityPassword);
                else
                    httpWebRequest.Credentials = new NetworkCredential(_securityUserName, _securityPassword, _securityDomain);
                                                
                httpWebRequest.PreAuthenticate = _bSecurityPreAuthenticate;                
                httpWebRequest.UnsafeAuthenticatedConnectionSharing = _bUnsafeAuthenticatedConnectionSharing;
                if (_connectionGroupName != null)
                    httpWebRequest.ConnectionGroupName = _connectionGroupName;
                
            }
            else
            if (_channel.UseDefaultCredentials)
            {
                if (_channel.UseAuthenticatedConnectionSharing) 
                {
#if !FEATURE_PAL                
                    httpWebRequest.ConnectionGroupName = CoreChannel.GetCurrentSidString();
#endif
                    httpWebRequest.UnsafeAuthenticatedConnectionSharing = true;
                }                    
 
#if !FEATURE_PAL
                httpWebRequest.Credentials = CredentialCache.DefaultCredentials;
#endif // !FEATURE_PAL
                httpWebRequest.PreAuthenticate = _bSecurityPreAuthenticate;                
            }
 
#if !FEATURE_PAL
            if (_certificates != null)
            {
                // attach certificates to the outgoing web request
                foreach (X509Certificate certificate in _certificates)
                {
                    httpWebRequest.ClientCertificates.Add(certificate);
                }                
                httpWebRequest.PreAuthenticate = _bSecurityPreAuthenticate;
            }
#endif
 
            InternalRemotingServices.RemotingTrace("HttpClientTransportSink::SetupWebRequest - Get Http Request Headers");
 
            // add headers
            foreach (DictionaryEntry header in headers)
            {
                String key = header.Key as String;
                
                // if header name starts with "__", it is a special value that shouldn't be
                //   actually sent out.
                if ((key != null) && !key.StartsWith("__", StringComparison.Ordinal)) 
                {
                    if (key.Equals("Content-Type"))
                        httpWebRequest.ContentType = header.Value.ToString();
                    else
                        httpWebRequest.Headers[key] = header.Value.ToString();
                }
            }
 
            return httpWebRequest;
        } // SetupWebRequest
 
 
        private HttpWebRequest ProcessAndSend(IMessage msg, ITransportHeaders headers, 
                                              Stream inputStream)
        {      
            // If the stream is seekable, we can retry once on a failure to write.
            long initialPosition = 0;
            bool bCanSeek = false;
            if (inputStream != null)
            {
                bCanSeek = inputStream.CanSeek;
                if (bCanSeek)
                    initialPosition = inputStream.Position;
            }
        
            HttpWebRequest httpWebRequest = null;
            Stream writeStream = null;
            try
            {
                httpWebRequest = SetupWebRequest(msg, headers);
 
                if (inputStream != null)
                {
                    if (!_useChunked)
                        httpWebRequest.ContentLength = (int)inputStream.Length;
          
                    writeStream = httpWebRequest.GetRequestStream();
                    StreamHelper.CopyStream(inputStream, writeStream);                  
                }
            }    
            catch {
                // try to send one more time if possible
                if (bCanSeek)
                {
                    httpWebRequest = SetupWebRequest(msg, headers);
 
                    if (inputStream != null)
                    {
                        inputStream.Position = initialPosition;
                    
                        if (!_useChunked)
                            httpWebRequest.ContentLength = (int)inputStream.Length;
          
                        writeStream = httpWebRequest.GetRequestStream();
                        StreamHelper.CopyStream(inputStream, writeStream);                  
                    }                
                } // end of "try to send one more time"
            }
 
            if (inputStream != null)
                inputStream.Close();                
 
            if (writeStream != null)
                writeStream.Close(); 
 
            return httpWebRequest;
        } // ProcessAndSend
 
        private void ReceiveAndProcess(HttpWebResponse response, 
                                       out ITransportHeaders returnHeaders,
                                       out Stream returnStream)
        {
            //
            // Read Response Message
 
            // Just hand back the network stream
            //   (NOTE: The channel sinks are responsible for calling Close() on a stream
            //    once they are done with it).
            int bufferSize;
            if (response == null)
                bufferSize = 4096;
            else {
                int contentLength = (int)response.ContentLength;
                if (contentLength == -1 || contentLength == 0)
                    bufferSize = 4096;
                else if (contentLength <= 16000)
                    bufferSize = contentLength;
                else
                    bufferSize = 16000;
            }
            returnStream = new BufferedStream(response.GetResponseStream(), bufferSize);  
 
            // collect headers
            returnHeaders = CollectResponseHeaders(response);
        } // ReceiveAndProcess
 
        private static ITransportHeaders CollectResponseHeaders(HttpWebResponse response)
        {
            TransportHeaders responseHeaders = new TransportHeaders();
            foreach (Object key in response.Headers)
            {
                String keyString = key.ToString();
                responseHeaders[keyString] = response.Headers[keyString];
            }
 
            return responseHeaders;
        } // CollectResponseHeaders
 
        //
        // Support for properties (through BaseChannelSinkWithProperties)
        //
 
        public override Object this[Object key]
        {
            get
            {
                String keyStr = key as String;
                if (keyStr == null)
                    return null;
            
                switch (keyStr.ToLower(CultureInfo.InvariantCulture))
                {
                case UserNameKey: return _securityUserName; 
                case PasswordKey: return null; // Intentionally refuse to return password.
                case DomainKey: return _securityDomain;
                case PreAuthenticateKey: return _bSecurityPreAuthenticate; 
                case CredentialsKey: return _credentials;
                case ClientCertificatesKey: return null; // Intentionally refuse to return certificates
                case ProxyNameKey: return _proxyName; 
                case ProxyPortKey: return _proxyPort; 
                case TimeoutKey: return _timeout;
                case AllowAutoRedirectKey: return _bAllowAutoRedirect;
                case UnsafeAuthenticatedConnectionSharingKey: return _bUnsafeAuthenticatedConnectionSharing;                
                case ConnectionGroupNameKey: return _connectionGroupName;                  
                } // switch (keyStr.ToLower(CultureInfo.InvariantCulture))
 
                return null; 
            }
        
            set
            {
                String keyStr = key as String;
                if (keyStr == null)
                    return;
    
                switch (keyStr.ToLower(CultureInfo.InvariantCulture))
                {
                case UserNameKey: _securityUserName = (String)value; break;
                case PasswordKey: _securityPassword = (String)value; break;    
                case DomainKey: _securityDomain = (String)value; break;                
                case PreAuthenticateKey: _bSecurityPreAuthenticate = Convert.ToBoolean(value, CultureInfo.InvariantCulture); break;
                case CredentialsKey: _credentials = (ICredentials)value; break;
#if !FEATURE_PAL
                case ClientCertificatesKey: _certificates = (X509CertificateCollection)value; break;
#endif
                case ProxyNameKey: _proxyName = (String)value; UpdateProxy(); break;
                case ProxyPortKey: _proxyPort = Convert.ToInt32(value, CultureInfo.InvariantCulture); UpdateProxy(); break;
 
                case TimeoutKey: 
                {
                    if (value is TimeSpan)
                        _timeout = (int)((TimeSpan)value).TotalMilliseconds;
                    else
                        _timeout = Convert.ToInt32(value, CultureInfo.InvariantCulture); 
                    break;
                } // case TimeoutKey
 
                case AllowAutoRedirectKey: _bAllowAutoRedirect = Convert.ToBoolean(value, CultureInfo.InvariantCulture); break;                
                case UnsafeAuthenticatedConnectionSharingKey: _bUnsafeAuthenticatedConnectionSharing = Convert.ToBoolean(value, CultureInfo.InvariantCulture); break;                
                case ConnectionGroupNameKey: _connectionGroupName = (String)value; break;  
                
                } // switch (keyStr.ToLower(CultureInfo.InvariantCulturey))
            }
        } // this[]   
        
 
        public override ICollection Keys
        {
            get
            {
                if (s_keySet == null)
                {
                    // Don't need to synchronize. Doesn't matter if the list gets
                    // generated twice.
                    ArrayList keys = new ArrayList(6);
                    keys.Add(UserNameKey);
                    keys.Add(PasswordKey);
                    keys.Add(DomainKey);
                    keys.Add(PreAuthenticateKey);
                    keys.Add(CredentialsKey);
                    keys.Add(ClientCertificatesKey);
                    keys.Add(ProxyNameKey);
                    keys.Add(ProxyPortKey);
                    keys.Add(TimeoutKey);
                    keys.Add(AllowAutoRedirectKey);                            
                    keys.Add(UnsafeAuthenticatedConnectionSharingKey);
                    keys.Add(ConnectionGroupNameKey);
                    
                    s_keySet = keys;
                }
 
                return s_keySet;
            }
        } // Keys
 
 
        //
        // end of Support for properties
        //
 
 
        //
        // Helper functions for processing settings and properties
        //
 
        // Called to recreate proxy object whenever the proxy name or port is changed.
        private void UpdateProxy()
        {
            if ((_proxyName != null) && (_proxyPort > 0))
            {
                WebProxy proxy = new WebProxy(_proxyName, _proxyPort);
                
                // disable proxy use when the host is local. i.e. without periods
                proxy.BypassProxyOnLocal = true;
 
                _proxyObject = proxy;
            }
        } // UpdateProxy
 
        //
        // end of Helper functions for processing settings and properties
        //
 
 
        internal static String UserAgent
        {
            get { return s_userAgent; }
        }       
                            
        // Used for maintaining async request state
        private class AsyncHttpClientRequestState
        {
            private static AsyncCallback s_processGetRequestStreamCompletionCallback = new AsyncCallback(ProcessGetRequestStreamCompletion);
            private static AsyncCallback s_processAsyncCopyRequestStreamCompletionCallback = new AsyncCallback(ProcessAsyncCopyRequestStreamCompletion);
            private static AsyncCallback s_processGetResponseCompletionCallback = new AsyncCallback(ProcessGetResponseCompletion);
            private static AsyncCallback s_processAsyncCopyRequestStreamCompletion = new AsyncCallback(ProcessAsyncCopyResponseStreamCompletion);
 
            
            internal HttpWebRequest WebRequest;
            internal HttpWebResponse WebResponse;
            internal IClientChannelSinkStack SinkStack;            
            internal Stream RequestStream;
            internal Stream ActualResponseStream; // stream that will be passed to channel sinks
 
            private HttpClientTransportSink _transportSink;
            private int _retryCount;
            private long _initialStreamPosition;
            private IMessage _msg;
            private ITransportHeaders _requestHeaders;
 
            internal AsyncHttpClientRequestState(
                HttpClientTransportSink transportSink,
                IClientChannelSinkStack sinkStack,
                IMessage msg, 
                ITransportHeaders headers, 
                Stream stream,
                int retryCount)
            {
                _transportSink = transportSink;
                SinkStack = sinkStack;
                _msg = msg;
                _requestHeaders = headers;
                RequestStream = stream;
                _retryCount = retryCount;
 
                if (RequestStream.CanSeek)
                    _initialStreamPosition = RequestStream.Position;
            } // AsyncHttpClientRequestState
 
            internal void StartRequest()
            {
                WebRequest = _transportSink.SetupWebRequest(_msg, _requestHeaders);
                if (!_transportSink._useChunked)
                {
                    try
                    {
                        WebRequest.ContentLength = (int)RequestStream.Length;
                    }
                    catch
                    {
                        // ---- exception if RequestStream.Length throws; just
                        // means that WebRequest will have to buffer the stream.
                    }
                }
            
                // Chain of methods called is as follows:
                //  1. StartRequest (this one)
                //  2. ProcessGetRequestStreamCompletion
                //  3. ProcessAsyncCopyRequestStreamCompletion
                //  2. ProcessGetResponseCompletion
                //  3. ProcessAsyncCopyResponseStreamCompletion
            
                WebRequest.BeginGetRequestStream(s_processGetRequestStreamCompletionCallback, this);    
            } // StartRequest
 
            // This should only be done when the send fails.
            internal void RetryOrDispatchException(Exception e)
            {
                bool bRetry = false;
                try
                {
                    if (_retryCount > 0)
                    {
                        _retryCount--;
    
                        if (RequestStream.CanSeek)
                        {
                            RequestStream.Position = _initialStreamPosition;   
 
                            StartRequest();
                            bRetry = true;
                        }
                    }
                }
                catch
                {
                }
 
                if (!bRetry)
                {
                    RequestStream.Close();
                    SinkStack.DispatchException(e);
                }
            } // DispatchExceptionOrRetry
 
 
            // called from StartRequest
            private static void ProcessGetRequestStreamCompletion(IAsyncResult iar)
            {        
                // We've just received a request stream.
            
                AsyncHttpClientRequestState asyncRequestState = (AsyncHttpClientRequestState)iar.AsyncState;
 
                try
                {
                    HttpWebRequest httpWebRequest = asyncRequestState.WebRequest;
                    Stream sourceRequestStream = asyncRequestState.RequestStream;
 
                    Stream webRequestStream = httpWebRequest.EndGetRequestStream(iar);
 
                    StreamHelper.BeginAsyncCopyStream(
                        sourceRequestStream, webRequestStream,
                        false, true, // sync read, async write
                        false, true, // leave source open, close target
                        s_processAsyncCopyRequestStreamCompletionCallback, 
                        asyncRequestState);                    
                }
                catch (Exception e)
                {
                    asyncRequestState.RetryOrDispatchException(e);
                }
            } // ProcessGetRequestStreamCompletion
 
 
            // called from ProcessGetRequestStreamCompletion
            private static void ProcessAsyncCopyRequestStreamCompletion(IAsyncResult iar)
            {       
                // We've just finished copying the original request stream into the network stream.
            
                AsyncHttpClientRequestState asyncRequestState = (AsyncHttpClientRequestState)iar.AsyncState;
 
                try            
                {
                    StreamHelper.EndAsyncCopyStream(iar);   
                               
                    asyncRequestState.WebRequest.BeginGetResponse(
                        s_processGetResponseCompletionCallback, asyncRequestState);         
                }
                catch (Exception e)
                {
                    // This is the last point where we should retry.
                    asyncRequestState.RetryOrDispatchException(e);
                }
            } // ProcessAsyncCopyRequestStreamCompletion
 
 
            // called from ProcessAsyncCopyRequestStreamCompletion
            private static void ProcessGetResponseCompletion(IAsyncResult iar)
            {       
                // We've just received a response.
            
                AsyncHttpClientRequestState asyncRequestState = (AsyncHttpClientRequestState)iar.AsyncState;
        
                try
                {
                    // close the request stream since we are done with it.
                    asyncRequestState.RequestStream.Close();
 
                    HttpWebResponse httpWebResponse = null;
                    HttpWebRequest httpWebRequest = asyncRequestState.WebRequest;
                    try
                    {
                        httpWebResponse = (HttpWebResponse)httpWebRequest.EndGetResponse(iar);        
                    }
                    catch (WebException webException)
                    {
                        ProcessResponseException(webException, out httpWebResponse);
                    }
 
                    asyncRequestState.WebResponse = httpWebResponse;
 
                    // Asynchronously pump the web response stream into a memory stream.
                    ChunkedMemoryStream responseStream = new ChunkedMemoryStream(CoreChannel.BufferPool);
                    asyncRequestState.ActualResponseStream = responseStream;
 
                    StreamHelper.BeginAsyncCopyStream(
                        httpWebResponse.GetResponseStream(), responseStream, 
                        true, false, // async read, sync write
                        true, false, // close source, leave target open
                        s_processAsyncCopyRequestStreamCompletion,
                        asyncRequestState);
                }
                catch (Exception e)
                {
                    asyncRequestState.SinkStack.DispatchException(e);
                }
            } // ProcessGetResponseCompletion
 
 
            // called from ProcessGetResponseCompletion
            private static void ProcessAsyncCopyResponseStreamCompletion(IAsyncResult iar)
            {
                // We've just finished copying the network response stream into a memory stream.
            
                AsyncHttpClientRequestState asyncRequestState = (AsyncHttpClientRequestState)iar.AsyncState;         
 
                try
                {
                    StreamHelper.EndAsyncCopyStream(iar);
 
                    HttpWebResponse webResponse = asyncRequestState.WebResponse;
                    Stream responseStream = asyncRequestState.ActualResponseStream;
 
                    ITransportHeaders responseHeaders = CollectResponseHeaders(webResponse);
        
                    // call down the sink chain
                    asyncRequestState.SinkStack.AsyncProcessResponse(responseHeaders, responseStream);
                }
                catch (Exception e)
                {
                    asyncRequestState.SinkStack.DispatchException(e);
                }
            } // ProcessAsyncResponseStreamCompletion
 
        
        } // class AsyncHttpClientRequest
 
                
    } // class HttpClientTransportSink
    
} // namespace System.Runtime.Remoting.Channels.Http