2 // System.Runtime.Remoting.Channels.Http.HttpClientChannel
4 // Summary: Implements a client channel that transmits method calls over HTTP.
6 // Classes: public HttpClientChannel
7 // internal HttpClientTransportSink
9 // Martin Willemoes Hansen (mwh@sysrq.dk)
10 // Ahmad Tantawy (popsito82@hotmail.com)
11 // Ahmad Kadry (kadrianoz@hotmail.com)
12 // Hussein Mehanna (hussein_mehanna@hotmail.com)
14 // (C) 2003 Martin Willemoes Hansen
18 using System.Collections;
21 using System.Runtime.Remoting;
22 using System.Runtime.Remoting.Channels;
23 using System.Runtime.Remoting.Messaging;
24 using System.Threading;
29 namespace System.Runtime.Remoting.Channels.Http
31 public class HttpClientChannel : IChannelSender,IChannel
33 // Property Keys (purposely all lower-case)
34 private const String ProxyNameKey = "proxyname";
35 private const String ProxyPortKey = "proxyport";
38 private int _channelPriority = 1; // channel priority
39 private String _channelName = "http"; // channel name
41 // Proxy settings (_proxyObject gets recreated when _proxyName and _proxyPort are updated)
42 //private IWebProxy _proxyObject = WebProxy.GetDefaultProxy(); // proxy object for request, can be overridden in transport sink
43 private IWebProxy _proxyObject = null;
44 private String _proxyName = null;
45 private int _proxyPort = -1;
47 private int _clientConnectionLimit = 0; // bump connection limit to at least this number (only meaningful if > 0)
48 private bool _bUseDefaultCredentials = false; // should default credentials be used?
50 private IClientChannelSinkProvider _sinkProvider = null; // sink chain provider
52 private IMessageSink _msgSink;
56 public HttpClientChannel()
59 SetupProvider(_sinkProvider);
64 public HttpClientChannel(String name, IClientChannelSinkProvider sinkProvider)
69 SetupProvider(sinkProvider);
73 // constructor used by config file
74 public HttpClientChannel(IDictionary properties, IClientChannelSinkProvider sinkProvider)
76 if (properties != null)
78 foreach(DictionaryEntry Dict in properties)
80 switch(Dict.Key.ToString())
83 _channelName = Dict.Value.ToString();
86 _channelPriority = Convert.ToInt32(Dict.Value);
88 case "clientConnectionLimit":
89 _clientConnectionLimit = Convert.ToInt32(Dict.Value);
92 _proxyName = Dict.Value.ToString();
95 _proxyPort = Convert.ToInt32(Dict.Value);
97 case "useDefaultCredentials":
98 _bUseDefaultCredentials = Convert.ToBoolean(Dict.Value);
104 SetupProvider (sinkProvider);
109 public int ChannelPriority
111 get { return _channelPriority; }
114 public String ChannelName
116 get { return _channelName; }
119 // returns channelURI and places object uri into out parameter
121 public String Parse(String url, out String objectURI)
123 return HttpHelper.Parse(url,out objectURI);
127 // end of IChannel implementation
131 // IChannelSender implementation
135 public virtual IMessageSink CreateMessageSink(String url, Object remoteChannelData, out String objectURI)
137 if (url == null && remoteChannelData != null && remoteChannelData as IChannelDataStore != null )
139 IChannelDataStore ds = (IChannelDataStore) remoteChannelData;
140 url = ds.ChannelUris[0];
143 if(url != null && HttpHelper.StartsWithHttp(url))
145 HttpHelper.Parse(url, out objectURI);
146 _msgSink = (IMessageSink) _sinkProvider.CreateSink(this,url,remoteChannelData);
149 SetServicePoint(url);
161 private void UpdateProxy()
163 // If the user values for the proxy object are valid , then the proxy
164 // object will be created based on these values , if not it'll have the
165 // value given when declared , as a default proxy object
166 if(_proxyName!=null && _proxyPort !=-1)
167 _proxyObject = new WebProxy(_proxyName,_proxyPort);
169 // Either it's default or not it'll have this property
170 ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
173 private void SetServicePoint(string channelURI)
175 // Find a ServicePoint for the given url and assign the connection limit
176 // to the user given value only if it valid
177 ServicePoint sp = ServicePointManager.FindServicePoint(channelURI,ProxyObject);
178 if(_clientConnectionLimit> 0)
179 sp.ConnectionLimit = _clientConnectionLimit;
182 internal IWebProxy ProxyObject { get { return _proxyObject; } }
183 internal bool UseDefaultCredentials { get { return _bUseDefaultCredentials; } }
185 private void SetupProvider(IClientChannelSinkProvider sinkProvider)
187 if(sinkProvider == null)
189 _sinkProvider = new SoapClientFormatterSinkProvider();
190 _sinkProvider.Next = new HttpClientTransportSinkProvider();
194 IClientChannelSinkProvider dummySinkProvider;
195 dummySinkProvider = sinkProvider;
196 _sinkProvider = sinkProvider;
197 while(dummySinkProvider.Next != null)
199 dummySinkProvider = dummySinkProvider.Next;
202 dummySinkProvider.Next = new HttpClientTransportSinkProvider();
209 internal class HttpClientTransportSinkProvider : IClientChannelSinkProvider
211 internal HttpClientTransportSinkProvider()
215 public IClientChannelSink CreateSink(IChannelSender channel, String url,
216 Object remoteChannelData)
218 // url is set to the channel uri in CreateMessageSink
219 return new HttpClientTransportSink((HttpClientChannel)channel, url);
222 public IClientChannelSinkProvider Next
225 set { throw new NotSupportedException(); }
227 } // class HttpClientTransportSinkProvider
232 // transport sender sink used by HttpClientChannel
233 internal class HttpClientTransportSink : BaseChannelSinkWithProperties, IClientChannelSink
235 private const String s_defaultVerb = "POST";
237 private static String s_userAgent =
238 "Mono Remoting Client (Mono CLR " + System.Environment.Version.ToString() + ")";
240 // Property keys (purposely all lower-case)
241 private const String UserNameKey = "username";
242 private const String PasswordKey = "password";
243 private const String DomainKey = "domain";
244 private const String PreAuthenticateKey = "preauthenticate";
245 private const String CredentialsKey = "credentials";
246 private const String ClientCertificatesKey = "clientcertificates";
247 private const String ProxyNameKey = "proxyname";
248 private const String ProxyPortKey = "proxyport";
249 private const String TimeoutKey = "timeout";
250 private const String AllowAutoRedirectKey = "allowautoredirect";
252 // If above keys get modified be sure to modify, the KeySet property on this
254 private static ICollection s_keySet = null;
257 private String _securityUserName = null;
258 private String _securityPassword = null;
259 private String _securityDomain = null;
260 private bool _bSecurityPreAuthenticate = false;
261 private ICredentials _credentials = null; // this overrides all of the other security settings
263 private int _timeout = System.Threading.Timeout.Infinite; // timeout value in milliseconds (only used if greater than 0)
264 private bool _bAllowAutoRedirect = false;
266 // Proxy settings (_proxyObject gets recreated when _proxyName and _proxyPort are updated)
267 private IWebProxy _proxyObject = null; // overrides channel proxy object if non-null
268 private String _proxyName = null;
269 private int _proxyPort = -1;
272 private HttpClientChannel _channel; // channel that created this sink
273 private String _channelURI; // complete url to remote object
276 private bool _useChunked = false;
277 // private bool _useKeepAlive = true;
279 internal HttpClientTransportSink(HttpClientChannel channel, String channelURI) : base()
283 _channelURI = HttpHelper.Parse(channelURI,out dummy);
287 public void ProcessMessage(IMessage msg,
288 ITransportHeaders requestHeaders, Stream requestStream,
289 out ITransportHeaders responseHeaders, out Stream responseStream)
292 string uri = (string)requestHeaders[CommonTransportKeys.RequestUri];
293 CreateUrl(uri,out url);
295 HttpWebRequest httpWebRequest = CreateWebRequest(url,requestHeaders,requestStream);
297 SendAndRecieve(httpWebRequest,out responseHeaders,out responseStream);
301 public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg,
302 ITransportHeaders headers, Stream stream)
305 string uri = (string)headers[CommonTransportKeys.RequestUri];
306 CreateUrl(uri,out url);
308 HttpWebRequest httpWebRequest = CreateWebRequest(url,headers,stream);
309 RequestState reqState = new RequestState(httpWebRequest,sinkStack);
311 httpWebRequest.BeginGetResponse(new AsyncCallback(AsyncRequestHandler),reqState);
314 private void AsyncRequestHandler(IAsyncResult ar)
316 HttpWebResponse httpWebResponse = null;
318 RequestState reqState = (RequestState) ar.AsyncState;
319 HttpWebRequest httpWebRequest = reqState.webRquest;
320 IClientChannelSinkStack sinkStack = reqState.sinkStack;
324 httpWebResponse = (HttpWebResponse) httpWebRequest.EndGetResponse(ar);
326 catch (WebException ex)
328 httpWebResponse = ex.Response as HttpWebResponse;
329 if (httpWebResponse == null) sinkStack.DispatchException (ex);
332 Stream responseStream;
333 ITransportHeaders responseHeaders;
337 ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
338 sinkStack.AsyncProcessResponse(responseHeaders,responseStream);
342 sinkStack.DispatchException (ex);
347 public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, Object state,
348 ITransportHeaders headers, Stream stream)
350 // We don't have to implement this since we are always last in the chain.
351 } // AsyncProcessRequest
355 public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
358 } // GetRequestStream
361 public IClientChannelSink NextChannelSink
367 public override Object this[Object key]
371 String keyStr = key as String;
375 switch (keyStr.ToLower())
377 case UserNameKey: return _securityUserName;
378 case PasswordKey: return null; // Intentionally refuse to return password.
379 case DomainKey: return _securityDomain;
380 case PreAuthenticateKey: return _bSecurityPreAuthenticate;
381 case CredentialsKey: return _credentials;
382 case ClientCertificatesKey: return null; // Intentionally refuse to return certificates
383 case ProxyNameKey: return _proxyName;
384 case ProxyPortKey: return _proxyPort;
385 case TimeoutKey: return _timeout;
386 case AllowAutoRedirectKey: return _bAllowAutoRedirect;
387 } // switch (keyStr.ToLower())
394 String keyStr = key as String;
398 switch (keyStr.ToLower())
400 case UserNameKey: _securityUserName = (String)value; break;
401 case PasswordKey: _securityPassword = (String)value; break;
402 case DomainKey: _securityDomain = (String)value; break;
403 case PreAuthenticateKey: _bSecurityPreAuthenticate = Convert.ToBoolean(value); break;
404 case CredentialsKey: _credentials = (ICredentials)value; break;
405 case ProxyNameKey: _proxyName = (String)value; UpdateProxy(); break;
406 case ProxyPortKey: _proxyPort = Convert.ToInt32(value); UpdateProxy(); break;
410 if (value is TimeSpan)
411 _timeout = (int)((TimeSpan)value).TotalMilliseconds;
413 _timeout = Convert.ToInt32(value);
417 case AllowAutoRedirectKey: _bAllowAutoRedirect = Convert.ToBoolean(value); break;
419 } // switch (keyStr.ToLower())
423 public override ICollection Keys
427 if (s_keySet == null)
429 // No need for synchronization
430 ArrayList keys = new ArrayList(6);
431 keys.Add(UserNameKey);
432 keys.Add(PasswordKey);
434 keys.Add(PreAuthenticateKey);
435 keys.Add(CredentialsKey);
436 keys.Add(ClientCertificatesKey);
437 keys.Add(ProxyNameKey);
438 keys.Add(ProxyPortKey);
439 keys.Add(TimeoutKey);
440 keys.Add(AllowAutoRedirectKey);
449 private void UpdateProxy()
451 // If the user values for the proxy object are valid , then the proxy
452 // object will be created based on these values , if not it'll have the
453 // value given when declared , as a default proxy object
454 if(_proxyName!=null && _proxyPort !=-1)
455 _proxyObject = new WebProxy(_proxyName,_proxyPort);
457 // Either it's default or not it'll have this property
458 ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
462 internal static String UserAgent
464 get { return s_userAgent; }
467 private void CreateUrl(string uri, out string fullURL)
469 if(HttpHelper.StartsWithHttp(uri)) //this is a full url
475 if(_channelURI.EndsWith("/") && uri.StartsWith("/"))
477 fullURL = _channelURI + uri.Substring(1);
481 if(_channelURI.EndsWith("/") && !uri.StartsWith("/") ||
482 !_channelURI.EndsWith("/") && uri.StartsWith("/") )
484 fullURL = _channelURI +uri;
489 fullURL = _channelURI +'/'+ uri;
495 private HttpWebRequest CreateWebRequest(string url, ITransportHeaders requestHeaders, Stream requestStream)
497 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);;
498 request.AllowAutoRedirect = _bAllowAutoRedirect;
499 request.ContentLength = requestStream.Length;
500 request.Credentials = GetCredenentials();
501 //request.Expect = "100-Continue";
503 //This caused us some troubles with the HttpWebResponse class
504 //maybe its fixed now. TODO
505 //request.KeepAlive = _useKeepAlive;
506 request.KeepAlive = false;;
508 request.Method = s_defaultVerb;
509 request.Pipelined = false;
510 request.SendChunked = _useChunked;
511 request.UserAgent = s_userAgent;
514 // write the remoting headers
515 IEnumerator headerenum = requestHeaders.GetEnumerator();
516 while (headerenum.MoveNext())
518 DictionaryEntry entry = (DictionaryEntry) headerenum.Current;
519 String key = entry.Key as String;
520 if(key == "Content-Type")
522 request.ContentType = entry.Value.ToString();
526 if (key == null || key.StartsWith("__"))
531 request.Headers.Add(entry.Key.ToString(),entry.Value.ToString());
534 Stream reqStream = request.GetRequestStream();
535 if (requestStream is MemoryStream)
537 MemoryStream memStream = (MemoryStream)requestStream;
538 reqStream.Write (memStream.GetBuffer(), 0, (int)memStream.Length);
541 HttpHelper.CopyStream(requestStream, reqStream);
548 private void SendAndRecieve(HttpWebRequest httpRequest,out ITransportHeaders responseHeaders,out Stream responseStream)
550 responseStream = null;
551 responseHeaders = null;
552 HttpWebResponse httpWebResponse = null;
556 httpWebResponse = (HttpWebResponse)httpRequest.GetResponse();
558 catch (WebException ex)
560 httpWebResponse = ex.Response as HttpWebResponse;
561 if (httpWebResponse == null) throw ex;
564 ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
567 private void ReceiveResponse (HttpWebResponse httpWebResponse, out ITransportHeaders responseHeaders, out Stream responseStream)
569 responseHeaders = new TransportHeaders();
573 Stream webStream = httpWebResponse.GetResponseStream();
575 if (httpWebResponse.ContentLength != -1)
577 byte[] buffer = new byte [httpWebResponse.ContentLength];
579 while (nr < buffer.Length)
580 nr += webStream.Read (buffer, nr, buffer.Length - nr);
581 responseStream = new MemoryStream (buffer);
585 responseStream = new MemoryStream();
586 HttpHelper.CopyStream(webStream, responseStream);
589 //Use the two commented lines below instead of the 3 below lines when HttpWebResponse
590 //class is fully implemented in order to support custom headers
591 //for(int i=0; i < httpWebResponse.Headers.Count; ++i)
592 // responseHeaders[httpWebResponse.Headers.Keys[i].ToString()] = httpWebResponse.Headers[i].ToString();
594 responseHeaders["Content-Type"] = httpWebResponse.ContentType;
595 responseHeaders["Server"] = httpWebResponse.Server;
596 responseHeaders["Content-Length"] = httpWebResponse.ContentLength;
600 if(httpWebResponse!=null)
601 httpWebResponse.Close();
605 private void ProcessErrorCode()
609 private ICredentials GetCredenentials()
611 if(_credentials!=null)
614 //Now use the username , password and domain if provided
615 if(_securityUserName==null ||_securityUserName=="")
616 if(_channel.UseDefaultCredentials)
617 return CredentialCache.DefaultCredentials;
621 return new NetworkCredential(_securityUserName,_securityPassword,_securityDomain);
626 } // class HttpClientTransportSink
629 internal class RequestState
631 public HttpWebRequest webRquest;
632 public IClientChannelSinkStack sinkStack;
634 public RequestState(HttpWebRequest wr,IClientChannelSinkStack ss)
642 } // namespace System.Runtime.Remoting.Channels.Http