de690e721cc174d29bec75c0798b4f66e68d6fb3
[mono.git] / mcs / class / System.Runtime.Remoting / System.Runtime.Remoting.Channels.Http / HttpClientChannel.cs
1 //
2 // System.Runtime.Remoting.Channels.Http.HttpClientChannel
3 //
4 // Summary:    Implements a client channel that transmits method calls over HTTP.
5 //
6 // Classes:    public HttpClientChannel
7 //             internal HttpClientTransportSink
8 // Authors:
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)
13 //
14 // (C) 2003 Martin Willemoes Hansen
15 //
16
17 using System;
18 using System.Collections;
19 using System.IO;
20 using System.Net;
21 using System.Runtime.Remoting;
22 using System.Runtime.Remoting.Channels;
23 using System.Runtime.Remoting.Messaging;
24 using System.Threading;
25 using System.Text;
26
27
28
29 namespace System.Runtime.Remoting.Channels.Http
30 {
31         public class HttpClientChannel : IChannelSender,IChannel
32         {
33                 // Property Keys (purposely all lower-case)
34                 private const String ProxyNameKey = "proxyname";
35                 private const String ProxyPortKey = "proxyport";
36
37                 // Settings
38                 private int    _channelPriority = 1;  // channel priority
39                 private String _channelName = "http"; // channel name
40
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;
46
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?
49         
50                 private IClientChannelSinkProvider _sinkProvider = null; // sink chain provider         
51         
52                 private IMessageSink _msgSink;
53
54
55
56                 public HttpClientChannel()
57                 {
58                         
59                         SetupProvider(_sinkProvider);
60                 } 
61
62
63                 
64                 public HttpClientChannel(String name, IClientChannelSinkProvider sinkProvider)
65                 {
66                         if(name != null)
67                                 _channelName = name;
68                         
69                         SetupProvider(sinkProvider);
70                 }
71        
72
73                 // constructor used by config file
74                 public HttpClientChannel(IDictionary properties, IClientChannelSinkProvider sinkProvider)
75                 {       
76                         if (properties != null) 
77                         {
78                                 foreach(DictionaryEntry Dict in properties)
79                                 {
80                                         switch(Dict.Key.ToString())
81                                         {
82                                                 case "name":
83                                                         _channelName = Dict.Value.ToString();
84                                                         break;
85                                                 case "priority":
86                                                         _channelPriority = Convert.ToInt32(Dict.Value);
87                                                         break;
88                                                 case "clientConnectionLimit":
89                                                         _clientConnectionLimit = Convert.ToInt32(Dict.Value);
90                                                         break;
91                                                 case "proxyName":
92                                                         _proxyName = Dict.Value.ToString();
93                                                         break;
94                                                 case "proxyPort":
95                                                         _proxyPort = Convert.ToInt32(Dict.Value);
96                                                         break;
97                                                 case "useDefaultCredentials":
98                                                         _bUseDefaultCredentials = Convert.ToBoolean(Dict.Value);
99                                                         break;
100                                         }
101                                 }
102                         }
103                         
104                         SetupProvider (sinkProvider);
105
106                 } 
107         
108                 
109                 public int ChannelPriority
110                 {
111                         get { return _channelPriority; }    
112                 }
113
114                 public String ChannelName
115                 {
116                         get { return _channelName; }
117                 }
118
119                 // returns channelURI and places object uri into out parameter
120                 
121                 public String Parse(String url, out String objectURI)
122                 {            
123                         return HttpHelper.Parse(url,out objectURI);
124                 }
125
126                 //
127                 // end of IChannel implementation
128                 // 
129
130                 //
131                 // IChannelSender implementation
132                 //
133                 
134
135                 public virtual IMessageSink CreateMessageSink(String url, Object remoteChannelData, out String objectURI)
136                 {
137                         if (url == null && remoteChannelData != null && remoteChannelData as IChannelDataStore != null )
138                         {
139                                 IChannelDataStore ds = (IChannelDataStore) remoteChannelData;
140                                 url = ds.ChannelUris[0];
141                         }
142
143                         if(url != null && HttpHelper.StartsWithHttp(url))
144                         {
145                                 HttpHelper.Parse(url, out objectURI);
146                                 _msgSink = (IMessageSink) _sinkProvider.CreateSink(this,url,remoteChannelData); 
147                                 
148                                 if(_msgSink !=null )
149                                         SetServicePoint(url);
150
151                                 return _msgSink;
152                         }
153                         else
154                         {
155                                 objectURI = null;
156                                 return null;
157                         }
158
159                 }
160
161                 private void UpdateProxy()
162                 {
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);
168                                         
169                         // Either it's default or not it'll have this property
170                         ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
171                 } 
172
173                 private void SetServicePoint(string channelURI)
174                 {
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;
180                 }               
181
182                 internal IWebProxy ProxyObject { get { return _proxyObject; } }
183                 internal bool UseDefaultCredentials { get { return _bUseDefaultCredentials; } }
184
185                 private void SetupProvider(IClientChannelSinkProvider sinkProvider)
186                 {
187                         if(sinkProvider == null)
188                         {
189                                 _sinkProvider = new SoapClientFormatterSinkProvider();
190                                 _sinkProvider.Next = new HttpClientTransportSinkProvider();
191                         }
192                         else
193                         {
194                                 IClientChannelSinkProvider dummySinkProvider;
195                                 dummySinkProvider = sinkProvider;
196                                 _sinkProvider = sinkProvider;
197                                 while(dummySinkProvider.Next != null)
198                                 {
199                                         dummySinkProvider = dummySinkProvider.Next;
200                                 }
201
202                                 dummySinkProvider.Next = new HttpClientTransportSinkProvider();
203                         } 
204                         
205                 }
206         } 
207
208
209         internal class HttpClientTransportSinkProvider : IClientChannelSinkProvider
210         {
211                 internal HttpClientTransportSinkProvider()
212                 {
213                 }    
214    
215                 public IClientChannelSink CreateSink(IChannelSender channel, String url, 
216                         Object remoteChannelData)
217                 {
218                         // url is set to the channel uri in CreateMessageSink        
219                         return new HttpClientTransportSink((HttpClientChannel)channel, url);
220                 }
221
222                 public IClientChannelSinkProvider Next
223                 {
224                         get { return null; }
225                         set { throw new NotSupportedException(); }
226                 }
227         } // class HttpClientTransportSinkProvider
228
229
230
231
232         // transport sender sink used by HttpClientChannel
233         internal class HttpClientTransportSink : BaseChannelSinkWithProperties, IClientChannelSink
234         {
235                 private const String s_defaultVerb = "POST";
236
237                 private static String s_userAgent =
238                         "Mono Remoting Client (Mono CLR " + System.Environment.Version.ToString() + ")";
239         
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";
251
252                 // If above keys get modified be sure to modify, the KeySet property on this
253                 // class.
254                 private static ICollection s_keySet = null;
255
256                 // Property values
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
262
263                 private int  _timeout = System.Threading.Timeout.Infinite; // timeout value in milliseconds (only used if greater than 0)
264                 private bool _bAllowAutoRedirect = false;
265
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;
270
271                 // Other members
272                 private HttpClientChannel _channel; // channel that created this sink
273                 private String            _channelURI; // complete url to remote object        
274
275                 // settings
276                 private bool _useChunked = false; 
277 //              private bool _useKeepAlive = true;
278
279                 internal HttpClientTransportSink(HttpClientChannel channel, String channelURI) : base()
280                 {
281                         string dummy;
282                         _channel = channel;
283                         _channelURI = HttpHelper.Parse(channelURI,out dummy);
284                 } 
285         
286
287                 public void ProcessMessage(IMessage msg,
288                         ITransportHeaders requestHeaders, Stream requestStream,
289                         out ITransportHeaders responseHeaders, out Stream responseStream)
290                 {
291                         string url = null;
292                         string uri = (string)requestHeaders[CommonTransportKeys.RequestUri];
293                         CreateUrl(uri,out url);
294
295                         HttpWebRequest httpWebRequest = CreateWebRequest(url,requestHeaders,requestStream);
296
297                         SendAndRecieve(httpWebRequest,out responseHeaders,out responseStream);
298                 }
299
300
301                 public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg,
302                         ITransportHeaders headers, Stream stream)
303                 {
304                         string url = null;
305                         string uri = (string)headers[CommonTransportKeys.RequestUri];
306                         CreateUrl(uri,out url);
307
308                         HttpWebRequest httpWebRequest = CreateWebRequest(url,headers,stream);
309                         RequestState reqState = new RequestState(httpWebRequest,sinkStack);
310
311                         httpWebRequest.BeginGetResponse(new AsyncCallback(AsyncRequestHandler),reqState);
312                 }
313
314                 private void AsyncRequestHandler(IAsyncResult ar)
315                 {
316                         HttpWebResponse httpWebResponse = null;
317
318                         RequestState reqState = (RequestState) ar.AsyncState;
319                         HttpWebRequest httpWebRequest = reqState.webRquest;
320                         IClientChannelSinkStack sinkStack = reqState.sinkStack;
321
322                         try
323                         {
324                                 httpWebResponse = (HttpWebResponse) httpWebRequest.EndGetResponse(ar);
325                         }
326                         catch (WebException ex)
327                         {
328                                 httpWebResponse = ex.Response as HttpWebResponse;
329                                 if (httpWebResponse == null) sinkStack.DispatchException (ex);
330                         }
331
332                         Stream responseStream;
333                         ITransportHeaders responseHeaders;
334
335                         try
336                         {
337                                 ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
338                                 sinkStack.AsyncProcessResponse(responseHeaders,responseStream);
339                         }
340                         catch (Exception ex)
341                         {
342                                 sinkStack.DispatchException (ex);
343                         }
344                 }
345
346
347                 public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, Object state,
348                         ITransportHeaders headers, Stream stream)
349                 {
350                         // We don't have to implement this since we are always last in the chain.
351                 } // AsyncProcessRequest
352
353
354         
355                 public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
356                 {
357                         return null; 
358                 } // GetRequestStream
359
360
361                 public IClientChannelSink NextChannelSink
362                 {
363                         get { return null; }
364                 }
365     
366
367                 public override Object this[Object key]
368                 {
369                         get
370                         {
371                                 String keyStr = key as String;
372                                 if (keyStr == null)
373                                         return null;
374             
375                                 switch (keyStr.ToLower())
376                                 {
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())
388
389                                 return null; 
390                         }
391         
392                         set
393                         {
394                                 String keyStr = key as String;
395                                 if (keyStr == null)
396                                         return;
397     
398                                 switch (keyStr.ToLower())
399                                 {
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;
407
408                                         case TimeoutKey: 
409                                         {
410                                                 if (value is TimeSpan)
411                                                         _timeout = (int)((TimeSpan)value).TotalMilliseconds;
412                                                 else
413                                                         _timeout = Convert.ToInt32(value); 
414                                                 break;
415                                         } // case TimeoutKey
416
417                                         case AllowAutoRedirectKey: _bAllowAutoRedirect = Convert.ToBoolean(value); break;
418                 
419                                 } // switch (keyStr.ToLower())
420                         }
421                 } // this[]   
422         
423                 public override ICollection Keys
424                 {
425                         get
426                         {
427                                 if (s_keySet == null)
428                                 {
429                                         // No need for synchronization
430                                         ArrayList keys = new ArrayList(6);
431                                         keys.Add(UserNameKey);
432                                         keys.Add(PasswordKey);
433                                         keys.Add(DomainKey);
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);                    
441
442                                         s_keySet = keys;
443                                 }
444
445                                 return s_keySet;
446                         }
447                 } 
448
449                 private void UpdateProxy()
450                         {
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);
456                                         
457                                 // Either it's default or not it'll have this property
458                                 ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
459                         } 
460                 
461                 
462                 internal static String UserAgent
463                 {
464                         get { return s_userAgent; }
465                 }
466
467                 private void CreateUrl(string uri, out string fullURL)
468                 {
469                         if(HttpHelper.StartsWithHttp(uri)) //this is a full url
470                         {
471                                 fullURL = uri;
472                                 return;
473                         }
474
475                         if(_channelURI.EndsWith("/") && uri.StartsWith("/"))
476                         {
477                                 fullURL = _channelURI + uri.Substring(1);
478                                 return;
479                         }
480                         else
481                                 if(_channelURI.EndsWith("/") && !uri.StartsWith("/") ||
482                                 !_channelURI.EndsWith("/") && uri.StartsWith("/") )
483                         {
484                                 fullURL = _channelURI  +uri;
485                                 return;
486                         }
487                         else
488                         {
489                                 fullURL = _channelURI +'/'+ uri;
490                                 return;
491                         }
492
493                 }
494
495                 private HttpWebRequest CreateWebRequest(string url, ITransportHeaders requestHeaders, Stream requestStream)
496                 {
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";
502                         
503                         //This caused us some troubles with the HttpWebResponse class
504                         //maybe its fixed now. TODO
505                         //request.KeepAlive = _useKeepAlive;
506                         request.KeepAlive = false;;
507                         
508                         request.Method = s_defaultVerb;
509                         request.Pipelined = false;
510                         request.SendChunked = _useChunked;
511                         request.UserAgent = s_userAgent;
512
513
514                         // write the remoting headers                   
515                         IEnumerator headerenum = requestHeaders.GetEnumerator();
516                         while (headerenum.MoveNext()) 
517                         {
518                                 DictionaryEntry entry = (DictionaryEntry) headerenum.Current;
519                                 String key = entry.Key as String;
520                                 if(key == "Content-Type")
521                                 {
522                                         request.ContentType = entry.Value.ToString();
523                                         continue;
524                                 }
525
526                                 if (key == null || key.StartsWith("__")) 
527                                 {
528                                         continue;
529                                 }
530
531                                 request.Headers.Add(entry.Key.ToString(),entry.Value.ToString());
532                         }
533
534                         Stream reqStream = request.GetRequestStream();
535                         if (requestStream is MemoryStream)
536                         {
537                                 MemoryStream memStream = (MemoryStream)requestStream;
538                                 reqStream.Write (memStream.GetBuffer(), 0, (int)memStream.Length);
539                         }
540                         else
541                                 HttpHelper.CopyStream(requestStream, reqStream);
542
543                         reqStream.Close();
544                         
545                         return request;
546                 }       
547         
548                 private void SendAndRecieve(HttpWebRequest httpRequest,out ITransportHeaders responseHeaders,out Stream responseStream)
549                 {
550                         responseStream = null;
551                         responseHeaders = null;
552                         HttpWebResponse httpWebResponse = null;
553
554                         try
555                         {
556                                 httpWebResponse = (HttpWebResponse)httpRequest.GetResponse();
557                         }
558                         catch (WebException ex)
559                         {
560                                 httpWebResponse = ex.Response as HttpWebResponse;
561                                 if (httpWebResponse == null) throw ex;
562                         }
563
564                         ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
565                 }
566
567                 private void ReceiveResponse (HttpWebResponse httpWebResponse, out ITransportHeaders responseHeaders, out Stream responseStream)
568                 {
569                         responseHeaders = new TransportHeaders();
570
571                         try
572                         {
573                                 Stream webStream = httpWebResponse.GetResponseStream();
574
575                                 if (httpWebResponse.ContentLength != -1)
576                                 {
577                                         byte[] buffer = new byte [httpWebResponse.ContentLength];
578                                         int nr = 0;
579                                         while (nr < buffer.Length)
580                                                 nr += webStream.Read (buffer, nr, buffer.Length - nr);
581                                         responseStream = new MemoryStream (buffer);
582                                 }
583                                 else
584                                 {
585                                         responseStream = new MemoryStream();
586                                         HttpHelper.CopyStream(webStream, responseStream);
587                                 }
588
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();
593
594                                 responseHeaders["Content-Type"] = httpWebResponse.ContentType;
595                                 responseHeaders["Server"] = httpWebResponse.Server;
596                                 responseHeaders["Content-Length"] = httpWebResponse.ContentLength;
597                         }
598                         finally
599                         {
600                                 if(httpWebResponse!=null)
601                                         httpWebResponse.Close();
602                         }
603                 }
604
605                 private void ProcessErrorCode()
606                 {
607                 }
608
609                 private ICredentials GetCredenentials()
610                 {
611                         if(_credentials!=null)
612                                 return _credentials;
613
614                         //Now use the username , password and domain if provided
615                         if(_securityUserName==null ||_securityUserName=="")
616                                 if(_channel.UseDefaultCredentials)
617                                         return CredentialCache.DefaultCredentials;
618                                 else
619                                         return null;
620
621                         return new NetworkCredential(_securityUserName,_securityPassword,_securityDomain);
622
623                 }
624
625                 
626         } // class HttpClientTransportSink
627
628
629         internal class RequestState
630         {
631                 public HttpWebRequest webRquest;
632                 public IClientChannelSinkStack sinkStack;
633
634                 public RequestState(HttpWebRequest wr,IClientChannelSinkStack ss)
635                 {
636                         webRquest = wr;
637                         sinkStack = ss;
638                 }
639         }
640
641
642 } // namespace System.Runtime.Remoting.Channels.Http