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 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39 using System.Collections;
42 using System.Runtime.Remoting;
43 using System.Runtime.Remoting.Channels;
44 using System.Runtime.Remoting.Messaging;
45 using System.Threading;
50 namespace System.Runtime.Remoting.Channels.Http
52 public class HttpClientChannel : BaseChannelWithProperties, IChannelSender,IChannel
54 // Property Keys (purposely all lower-case)
55 private const String ProxyNameKey = "proxyname";
56 private const String ProxyPortKey = "proxyport";
59 private int _channelPriority = 1; // channel priority
60 private String _channelName = "http"; // channel name
62 // Proxy settings (_proxyObject gets recreated when _proxyName and _proxyPort are updated)
63 //private IWebProxy _proxyObject = WebProxy.GetDefaultProxy(); // proxy object for request, can be overridden in transport sink
64 private IWebProxy _proxyObject = null;
65 private String _proxyName = null;
66 private int _proxyPort = -1;
68 private int _clientConnectionLimit = 0; // bump connection limit to at least this number (only meaningful if > 0)
69 private bool _bUseDefaultCredentials = false; // should default credentials be used?
71 private IClientChannelSinkProvider _sinkProvider = null; // sink chain provider
73 public HttpClientChannel()
75 SetupProvider (null,null);
78 public HttpClientChannel(String name, IClientChannelSinkProvider sinkProvider)
83 SetupProvider (sinkProvider, null);
86 // constructor used by config file
87 public HttpClientChannel(IDictionary properties, IClientChannelSinkProvider sinkProvider)
89 if (properties != null)
91 foreach(DictionaryEntry Dict in properties)
93 switch(Dict.Key.ToString())
96 _channelName = Dict.Value.ToString();
99 _channelPriority = Convert.ToInt32(Dict.Value);
101 case "clientConnectionLimit":
102 _clientConnectionLimit = Convert.ToInt32(Dict.Value);
105 _proxyName = Dict.Value.ToString();
108 _proxyPort = Convert.ToInt32(Dict.Value);
110 case "useDefaultCredentials":
111 _bUseDefaultCredentials = Convert.ToBoolean(Dict.Value);
117 SetupProvider (sinkProvider, properties);
120 public int ChannelPriority
122 get { return _channelPriority; }
125 public String ChannelName
127 get { return _channelName; }
130 // returns channelURI and places object uri into out parameter
132 public String Parse(String url, out String objectURI)
134 return HttpHelper.Parse(url,out objectURI);
138 // end of IChannel implementation
142 // IChannelSender implementation
146 public virtual IMessageSink CreateMessageSink(String url, Object remoteChannelData, out String objectURI)
148 if ((url == null || !HttpHelper.StartsWithHttp (url)) && remoteChannelData != null && remoteChannelData as IChannelDataStore != null )
150 IChannelDataStore ds = (IChannelDataStore) remoteChannelData;
151 url = ds.ChannelUris[0];
154 if(url != null && HttpHelper.StartsWithHttp(url))
156 HttpHelper.Parse(url, out objectURI);
157 IMessageSink msgSink = (IMessageSink) _sinkProvider.CreateSink(this,url,remoteChannelData);
160 SetServicePoint(url);
171 private void UpdateProxy()
173 // If the user values for the proxy object are valid , then the proxy
174 // object will be created based on these values , if not it'll have the
175 // value given when declared , as a default proxy object
176 if(_proxyName!=null && _proxyPort !=-1)
177 _proxyObject = new WebProxy(_proxyName,_proxyPort);
179 // Either it's default or not it'll have this property
180 ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
183 private void SetServicePoint(string channelURI)
185 // Find a ServicePoint for the given url and assign the connection limit
186 // to the user given value only if it valid
187 ServicePoint sp = ServicePointManager.FindServicePoint(channelURI,ProxyObject);
188 if(_clientConnectionLimit> 0)
189 sp.ConnectionLimit = _clientConnectionLimit;
192 internal IWebProxy ProxyObject { get { return _proxyObject; } }
193 internal bool UseDefaultCredentials { get { return _bUseDefaultCredentials; } }
195 private void SetupProvider (IClientChannelSinkProvider sinkProvider, IDictionary properties)
197 if (properties == null) properties = new Hashtable ();
198 HttpClientTransportSinkProvider httpSink = new HttpClientTransportSinkProvider (properties);
199 SinksWithProperties = httpSink;
201 if(sinkProvider == null)
203 _sinkProvider = new SoapClientFormatterSinkProvider();
204 _sinkProvider.Next = httpSink;
208 IClientChannelSinkProvider dummySinkProvider;
209 dummySinkProvider = sinkProvider;
210 _sinkProvider = sinkProvider;
211 while(dummySinkProvider.Next != null)
213 dummySinkProvider = dummySinkProvider.Next;
216 dummySinkProvider.Next = httpSink;
220 public override object this [object key]
222 get { return Properties[key]; }
223 set { Properties[key] = value; }
226 public override ICollection Keys
228 get { return Properties.Keys; }
233 internal class HttpClientTransportSinkProvider : IClientChannelSinkProvider, IChannelSinkBase
235 IDictionary _properties;
237 internal HttpClientTransportSinkProvider (IDictionary properties)
239 _properties = properties;
242 public IClientChannelSink CreateSink(IChannelSender channel, String url,
243 Object remoteChannelData)
245 // url is set to the channel uri in CreateMessageSink
246 return new HttpClientTransportSink((HttpClientChannel)channel, url);
249 public IClientChannelSinkProvider Next
252 set { throw new NotSupportedException(); }
255 public IDictionary Properties
257 get { return _properties; }
260 } // class HttpClientTransportSinkProvider
265 // transport sender sink used by HttpClientChannel
266 internal class HttpClientTransportSink : BaseChannelSinkWithProperties, IClientChannelSink
268 private const String s_defaultVerb = "POST";
270 private static String s_userAgent =
271 "Mono Remoting Client (Mono CLR " + System.Environment.Version.ToString() + ")";
273 // Property keys (purposely all lower-case)
274 private const String UserNameKey = "username";
275 private const String PasswordKey = "password";
276 private const String DomainKey = "domain";
277 private const String PreAuthenticateKey = "preauthenticate";
278 private const String CredentialsKey = "credentials";
279 private const String ClientCertificatesKey = "clientcertificates";
280 private const String ProxyNameKey = "proxyname";
281 private const String ProxyPortKey = "proxyport";
282 private const String TimeoutKey = "timeout";
283 private const String AllowAutoRedirectKey = "allowautoredirect";
285 // If above keys get modified be sure to modify, the KeySet property on this
287 private static ICollection s_keySet = null;
290 private String _securityUserName = null;
291 private String _securityPassword = null;
292 private String _securityDomain = null;
293 private bool _bSecurityPreAuthenticate = false;
294 private ICredentials _credentials = null; // this overrides all of the other security settings
296 private int _timeout = System.Threading.Timeout.Infinite; // timeout value in milliseconds (only used if greater than 0)
297 private bool _bAllowAutoRedirect = false;
299 // Proxy settings (_proxyObject gets recreated when _proxyName and _proxyPort are updated)
300 private IWebProxy _proxyObject = null; // overrides channel proxy object if non-null
301 private String _proxyName = null;
302 private int _proxyPort = -1;
305 private HttpClientChannel _channel; // channel that created this sink
306 private String _channelURI; // complete url to remote object
309 private bool _useChunked = false;
310 // private bool _useKeepAlive = true;
312 internal HttpClientTransportSink(HttpClientChannel channel, String channelURI) : base()
316 _channelURI = HttpHelper.Parse(channelURI,out dummy);
320 public void ProcessMessage(IMessage msg,
321 ITransportHeaders requestHeaders, Stream requestStream,
322 out ITransportHeaders responseHeaders, out Stream responseStream)
325 string uri = ((IMethodCallMessage)msg).Uri;
326 requestHeaders [CommonTransportKeys.RequestUri] = uri;
327 CreateUrl(uri,out url);
329 HttpWebRequest httpWebRequest = CreateWebRequest(url,requestHeaders,requestStream);
331 SendAndRecieve(httpWebRequest,out responseHeaders,out responseStream);
335 public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg,
336 ITransportHeaders headers, Stream stream)
339 string uri = ((IMethodCallMessage)msg).Uri;
340 headers [CommonTransportKeys.RequestUri] = uri;
341 CreateUrl(uri,out url);
343 HttpWebRequest httpWebRequest = CreateWebRequest(url,headers,stream);
344 RequestState reqState = new RequestState(httpWebRequest,sinkStack);
346 httpWebRequest.BeginGetResponse(new AsyncCallback(AsyncRequestHandler),reqState);
349 private void AsyncRequestHandler(IAsyncResult ar)
351 HttpWebResponse httpWebResponse = null;
353 RequestState reqState = (RequestState) ar.AsyncState;
354 HttpWebRequest httpWebRequest = reqState.webRquest;
355 IClientChannelSinkStack sinkStack = reqState.sinkStack;
359 httpWebResponse = (HttpWebResponse) httpWebRequest.EndGetResponse(ar);
361 catch (WebException ex)
363 httpWebResponse = ex.Response as HttpWebResponse;
364 if (httpWebResponse == null) sinkStack.DispatchException (ex);
367 Stream responseStream;
368 ITransportHeaders responseHeaders;
372 ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
373 sinkStack.AsyncProcessResponse(responseHeaders,responseStream);
377 sinkStack.DispatchException (ex);
382 public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, Object state,
383 ITransportHeaders headers, Stream stream)
385 // We don't have to implement this since we are always last in the chain.
386 } // AsyncProcessRequest
390 public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
393 } // GetRequestStream
396 public IClientChannelSink NextChannelSink
402 public override Object this[Object key]
406 String keyStr = key as String;
410 switch (keyStr.ToLower())
412 case UserNameKey: return _securityUserName;
413 case PasswordKey: return null; // Intentionally refuse to return password.
414 case DomainKey: return _securityDomain;
415 case PreAuthenticateKey: return _bSecurityPreAuthenticate;
416 case CredentialsKey: return _credentials;
417 case ClientCertificatesKey: return null; // Intentionally refuse to return certificates
418 case ProxyNameKey: return _proxyName;
419 case ProxyPortKey: return _proxyPort;
420 case TimeoutKey: return _timeout;
421 case AllowAutoRedirectKey: return _bAllowAutoRedirect;
422 } // switch (keyStr.ToLower())
429 String keyStr = key as String;
433 switch (keyStr.ToLower())
435 case UserNameKey: _securityUserName = (String)value; break;
436 case PasswordKey: _securityPassword = (String)value; break;
437 case DomainKey: _securityDomain = (String)value; break;
438 case PreAuthenticateKey: _bSecurityPreAuthenticate = Convert.ToBoolean(value); break;
439 case CredentialsKey: _credentials = (ICredentials)value; break;
440 case ProxyNameKey: _proxyName = (String)value; UpdateProxy(); break;
441 case ProxyPortKey: _proxyPort = Convert.ToInt32(value); UpdateProxy(); break;
445 if (value is TimeSpan)
446 _timeout = (int)((TimeSpan)value).TotalMilliseconds;
448 _timeout = Convert.ToInt32(value);
452 case AllowAutoRedirectKey: _bAllowAutoRedirect = Convert.ToBoolean(value); break;
454 } // switch (keyStr.ToLower())
458 public override ICollection Keys
462 if (s_keySet == null)
464 // No need for synchronization
465 ArrayList keys = new ArrayList(6);
466 keys.Add(UserNameKey);
467 keys.Add(PasswordKey);
469 keys.Add(PreAuthenticateKey);
470 keys.Add(CredentialsKey);
471 keys.Add(ClientCertificatesKey);
472 keys.Add(ProxyNameKey);
473 keys.Add(ProxyPortKey);
474 keys.Add(TimeoutKey);
475 keys.Add(AllowAutoRedirectKey);
484 private void UpdateProxy()
486 // If the user values for the proxy object are valid , then the proxy
487 // object will be created based on these values , if not it'll have the
488 // value given when declared , as a default proxy object
489 if(_proxyName!=null && _proxyPort !=-1)
490 _proxyObject = new WebProxy(_proxyName,_proxyPort);
492 // Either it's default or not it'll have this property
493 ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
497 internal static String UserAgent
499 get { return s_userAgent; }
502 private void CreateUrl(string uri, out string fullURL)
504 if(HttpHelper.StartsWithHttp(uri)) //this is a full url
510 if(_channelURI.EndsWith("/") && uri.StartsWith("/"))
512 fullURL = _channelURI + uri.Substring(1);
516 if(_channelURI.EndsWith("/") && !uri.StartsWith("/") ||
517 !_channelURI.EndsWith("/") && uri.StartsWith("/") )
519 fullURL = _channelURI +uri;
524 fullURL = _channelURI +'/'+ uri;
530 private HttpWebRequest CreateWebRequest(string url, ITransportHeaders requestHeaders, Stream requestStream)
532 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);;
533 request.AllowAutoRedirect = _bAllowAutoRedirect;
534 request.ContentLength = requestStream.Length;
535 request.Credentials = GetCredenentials();
536 //request.Expect = "100-Continue";
538 //This caused us some troubles with the HttpWebResponse class
539 //maybe its fixed now. TODO
540 //request.KeepAlive = _useKeepAlive;
541 request.KeepAlive = false;;
543 request.Method = s_defaultVerb;
544 request.Pipelined = false;
545 request.SendChunked = _useChunked;
546 request.UserAgent = s_userAgent;
549 // write the remoting headers
550 IEnumerator headerenum = requestHeaders.GetEnumerator();
551 while (headerenum.MoveNext())
553 DictionaryEntry entry = (DictionaryEntry) headerenum.Current;
554 String key = entry.Key as String;
555 if(key == "Content-Type")
557 request.ContentType = entry.Value.ToString();
561 if (key == null || key.StartsWith("__"))
566 request.Headers.Add(entry.Key.ToString(),entry.Value.ToString());
569 Stream reqStream = request.GetRequestStream();
570 if (requestStream is MemoryStream)
572 MemoryStream memStream = (MemoryStream)requestStream;
573 reqStream.Write (memStream.GetBuffer(), 0, (int)memStream.Length);
576 HttpHelper.CopyStream(requestStream, reqStream);
583 private void SendAndRecieve(HttpWebRequest httpRequest,out ITransportHeaders responseHeaders,out Stream responseStream)
585 responseStream = null;
586 responseHeaders = null;
587 HttpWebResponse httpWebResponse = null;
591 httpWebResponse = (HttpWebResponse)httpRequest.GetResponse();
593 catch (WebException ex)
595 httpWebResponse = ex.Response as HttpWebResponse;
596 if (httpWebResponse == null || httpWebResponse.StatusCode == HttpStatusCode.InternalServerError) throw ex;
599 ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
602 private void ReceiveResponse (HttpWebResponse httpWebResponse, out ITransportHeaders responseHeaders, out Stream responseStream)
604 responseHeaders = new TransportHeaders();
608 Stream webStream = httpWebResponse.GetResponseStream();
610 if (httpWebResponse.ContentLength != -1)
612 byte[] buffer = new byte [httpWebResponse.ContentLength];
614 while (nr < buffer.Length)
615 nr += webStream.Read (buffer, nr, buffer.Length - nr);
616 responseStream = new MemoryStream (buffer);
620 responseStream = new MemoryStream();
621 HttpHelper.CopyStream(webStream, responseStream);
624 //Use the two commented lines below instead of the 3 below lines when HttpWebResponse
625 //class is fully implemented in order to support custom headers
626 //for(int i=0; i < httpWebResponse.Headers.Count; ++i)
627 // responseHeaders[httpWebResponse.Headers.Keys[i].ToString()] = httpWebResponse.Headers[i].ToString();
629 responseHeaders["Content-Type"] = httpWebResponse.ContentType;
630 responseHeaders["Server"] = httpWebResponse.Server;
631 responseHeaders["Content-Length"] = httpWebResponse.ContentLength;
635 if(httpWebResponse!=null)
636 httpWebResponse.Close();
640 private void ProcessErrorCode()
644 private ICredentials GetCredenentials()
646 if(_credentials!=null)
649 //Now use the username , password and domain if provided
650 if(_securityUserName==null ||_securityUserName=="")
651 if(_channel.UseDefaultCredentials)
652 return CredentialCache.DefaultCredentials;
656 return new NetworkCredential(_securityUserName,_securityPassword,_securityDomain);
661 } // class HttpClientTransportSink
664 internal class RequestState
666 public HttpWebRequest webRquest;
667 public IClientChannelSinkStack sinkStack;
669 public RequestState(HttpWebRequest wr,IClientChannelSinkStack ss)
677 } // namespace System.Runtime.Remoting.Channels.Http