|
// ==++==
//
// 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
|