2004-12-17 Lluis Sanchez Gual <lluis@ximian.com>
[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 //
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:
25 // 
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 // 
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.
36 //
37
38 using System;
39 using System.Collections;
40 using System.IO;
41 using System.Net;
42 using System.Runtime.Remoting;
43 using System.Runtime.Remoting.Channels;
44 using System.Runtime.Remoting.Messaging;
45 using System.Threading;
46 using System.Text;
47
48
49
50 namespace System.Runtime.Remoting.Channels.Http
51 {
52         public class HttpClientChannel : BaseChannelWithProperties, IChannelSender,IChannel
53         {
54                 // Property Keys (purposely all lower-case)
55                 private const String ProxyNameKey = "proxyname";
56                 private const String ProxyPortKey = "proxyport";
57
58                 // Settings
59                 private int    _channelPriority = 1;  // channel priority
60                 private String _channelName = "http"; // channel name
61
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;
67
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?
70         
71                 private IClientChannelSinkProvider _sinkProvider = null; // sink chain provider
72
73                 public HttpClientChannel()
74                 {
75                         SetupProvider (null,null);
76                 } 
77                 
78                 public HttpClientChannel(String name, IClientChannelSinkProvider sinkProvider)
79                 {
80                         if(name != null)
81                                 _channelName = name;
82                         
83                         SetupProvider (sinkProvider, null);
84                 }
85
86                 // constructor used by config file
87                 public HttpClientChannel(IDictionary properties, IClientChannelSinkProvider sinkProvider)
88                 {       
89                         if (properties != null) 
90                         {
91                                 foreach(DictionaryEntry Dict in properties)
92                                 {
93                                         switch(Dict.Key.ToString())
94                                         {
95                                                 case "name":
96                                                         _channelName = Dict.Value.ToString();
97                                                         break;
98                                                 case "priority":
99                                                         _channelPriority = Convert.ToInt32(Dict.Value);
100                                                         break;
101                                                 case "clientConnectionLimit":
102                                                         _clientConnectionLimit = Convert.ToInt32(Dict.Value);
103                                                         break;
104                                                 case "proxyName":
105                                                         _proxyName = Dict.Value.ToString();
106                                                         break;
107                                                 case "proxyPort":
108                                                         _proxyPort = Convert.ToInt32(Dict.Value);
109                                                         break;
110                                                 case "useDefaultCredentials":
111                                                         _bUseDefaultCredentials = Convert.ToBoolean(Dict.Value);
112                                                         break;
113                                         }
114                                 }
115                         }
116                         
117                         SetupProvider (sinkProvider, properties);
118                 } 
119         
120                 public int ChannelPriority
121                 {
122                         get { return _channelPriority; }    
123                 }
124
125                 public String ChannelName
126                 {
127                         get { return _channelName; }
128                 }
129
130                 // returns channelURI and places object uri into out parameter
131                 
132                 public String Parse(String url, out String objectURI)
133                 {            
134                         return HttpHelper.Parse(url,out objectURI);
135                 }
136
137                 //
138                 // end of IChannel implementation
139                 // 
140
141                 //
142                 // IChannelSender implementation
143                 //
144                 
145
146                 public virtual IMessageSink CreateMessageSink(String url, Object remoteChannelData, out String objectURI)
147                 {
148                         if ((url == null || !HttpHelper.StartsWithHttp (url)) && remoteChannelData != null && remoteChannelData as IChannelDataStore != null )
149                         {
150                                 IChannelDataStore ds = (IChannelDataStore) remoteChannelData;
151                                 url = ds.ChannelUris[0];
152                         }
153
154                         if(url != null && HttpHelper.StartsWithHttp(url))
155                         {
156                                 HttpHelper.Parse(url, out objectURI);
157                                 IMessageSink msgSink = (IMessageSink) _sinkProvider.CreateSink(this,url,remoteChannelData); 
158                                 
159                                 if(msgSink !=null )
160                                         SetServicePoint(url);
161
162                                 return msgSink;
163                         }
164                         else
165                         {
166                                 objectURI = null;
167                                 return null;
168                         }
169                 }
170
171                 private void UpdateProxy()
172                 {
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);
178                                         
179                         // Either it's default or not it'll have this property
180                         ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
181                 } 
182
183                 private void SetServicePoint(string channelURI)
184                 {
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;
190                 }               
191
192                 internal IWebProxy ProxyObject { get { return _proxyObject; } }
193                 internal bool UseDefaultCredentials { get { return _bUseDefaultCredentials; } }
194
195                 private void SetupProvider (IClientChannelSinkProvider sinkProvider, IDictionary properties)
196                 {
197                         if (properties == null) properties = new Hashtable ();
198                         HttpClientTransportSinkProvider httpSink = new HttpClientTransportSinkProvider (properties);
199                         SinksWithProperties = httpSink;
200                         
201                         if(sinkProvider == null)
202                         {
203                                 _sinkProvider = new SoapClientFormatterSinkProvider();
204                                 _sinkProvider.Next = httpSink;
205                         }
206                         else
207                         {
208                                 IClientChannelSinkProvider dummySinkProvider;
209                                 dummySinkProvider = sinkProvider;
210                                 _sinkProvider = sinkProvider;
211                                 while(dummySinkProvider.Next != null)
212                                 {
213                                         dummySinkProvider = dummySinkProvider.Next;
214                                 }
215
216                                 dummySinkProvider.Next = httpSink;
217                         } 
218                 }
219                 
220                 public override object this [object key]
221                 {
222                         get { return Properties[key]; }
223                         set { Properties[key] = value; }
224                 }
225                 
226                 public override ICollection Keys 
227                 {
228                         get { return Properties.Keys; }
229                 }
230         } 
231
232
233         internal class HttpClientTransportSinkProvider : IClientChannelSinkProvider, IChannelSinkBase
234         {
235                 IDictionary _properties;
236                 
237                 internal HttpClientTransportSinkProvider (IDictionary properties)
238                 {
239                         _properties = properties;
240                 }    
241    
242                 public IClientChannelSink CreateSink(IChannelSender channel, String url, 
243                         Object remoteChannelData)
244                 {
245                         // url is set to the channel uri in CreateMessageSink        
246                         return new HttpClientTransportSink((HttpClientChannel)channel, url);
247                 }
248
249                 public IClientChannelSinkProvider Next
250                 {
251                         get { return null; }
252                         set { throw new NotSupportedException(); }
253                 }
254                 
255                 public IDictionary Properties
256                 {
257                         get { return _properties; }
258                 }
259                 
260         } // class HttpClientTransportSinkProvider
261
262
263
264
265         // transport sender sink used by HttpClientChannel
266         internal class HttpClientTransportSink : BaseChannelSinkWithProperties, IClientChannelSink
267         {
268                 private const String s_defaultVerb = "POST";
269
270                 private static String s_userAgent =
271                         "Mono Remoting Client (Mono CLR " + System.Environment.Version.ToString() + ")";
272         
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";
284
285                 // If above keys get modified be sure to modify, the KeySet property on this
286                 // class.
287                 private static ICollection s_keySet = null;
288
289                 // Property values
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
295
296                 private int  _timeout = System.Threading.Timeout.Infinite; // timeout value in milliseconds (only used if greater than 0)
297                 private bool _bAllowAutoRedirect = false;
298
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;
303
304                 // Other members
305                 private HttpClientChannel _channel; // channel that created this sink
306                 private String            _channelURI; // complete url to remote object        
307
308                 // settings
309                 private bool _useChunked = false; 
310 //              private bool _useKeepAlive = true;
311
312                 internal HttpClientTransportSink(HttpClientChannel channel, String channelURI) : base()
313                 {
314                         string dummy;
315                         _channel = channel;
316                         _channelURI = HttpHelper.Parse(channelURI,out dummy);
317                 } 
318         
319
320                 public void ProcessMessage(IMessage msg,
321                         ITransportHeaders requestHeaders, Stream requestStream,
322                         out ITransportHeaders responseHeaders, out Stream responseStream)
323                 {
324                         string url = null;
325                         string uri = ((IMethodCallMessage)msg).Uri;
326                         requestHeaders [CommonTransportKeys.RequestUri] = uri;
327                         CreateUrl(uri,out url);
328
329                         HttpWebRequest httpWebRequest = CreateWebRequest (url,requestHeaders,requestStream);
330
331                         SendAndRecieve (httpWebRequest,out responseHeaders,out responseStream);
332                 }
333
334
335                 public void AsyncProcessRequest(IClientChannelSinkStack sinkStack, IMessage msg,
336                         ITransportHeaders headers, Stream stream)
337                 {
338                         string url = null;
339                         string uri = ((IMethodCallMessage)msg).Uri;
340                         headers [CommonTransportKeys.RequestUri] = uri;
341                         CreateUrl(uri,out url);
342
343                         HttpWebRequest httpWebRequest = CreateWebRequest(url,headers,stream);
344                         RequestState reqState = new RequestState(httpWebRequest,sinkStack);
345
346                         httpWebRequest.BeginGetResponse(new AsyncCallback(AsyncRequestHandler),reqState);
347                 }
348
349                 private void AsyncRequestHandler(IAsyncResult ar)
350                 {
351                         HttpWebResponse httpWebResponse = null;
352
353                         RequestState reqState = (RequestState) ar.AsyncState;
354                         HttpWebRequest httpWebRequest = reqState.webRquest;
355                         IClientChannelSinkStack sinkStack = reqState.sinkStack;
356
357                         try
358                         {
359                                 httpWebResponse = (HttpWebResponse) httpWebRequest.EndGetResponse(ar);
360                         }
361                         catch (WebException ex)
362                         {
363                                 httpWebResponse = ex.Response as HttpWebResponse;
364                                 if (httpWebResponse == null) sinkStack.DispatchException (ex);
365                         }
366
367                         Stream responseStream;
368                         ITransportHeaders responseHeaders;
369
370                         try
371                         {
372                                 ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
373                                 sinkStack.AsyncProcessResponse(responseHeaders,responseStream);
374                         }
375                         catch (Exception ex)
376                         {
377                                 sinkStack.DispatchException (ex);
378                         }
379                 }
380
381
382                 public void AsyncProcessResponse(IClientResponseChannelSinkStack sinkStack, Object state,
383                         ITransportHeaders headers, Stream stream)
384                 {
385                         // We don't have to implement this since we are always last in the chain.
386                 } // AsyncProcessRequest
387
388
389         
390                 public Stream GetRequestStream(IMessage msg, ITransportHeaders headers)
391                 {
392                         return null; 
393                 } // GetRequestStream
394
395
396                 public IClientChannelSink NextChannelSink
397                 {
398                         get { return null; }
399                 }
400     
401
402                 public override Object this[Object key]
403                 {
404                         get
405                         {
406                                 String keyStr = key as String;
407                                 if (keyStr == null)
408                                         return null;
409             
410                                 switch (keyStr.ToLower())
411                                 {
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())
423
424                                 return null; 
425                         }
426         
427                         set
428                         {
429                                 String keyStr = key as String;
430                                 if (keyStr == null)
431                                         return;
432     
433                                 switch (keyStr.ToLower())
434                                 {
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;
442
443                                         case TimeoutKey: 
444                                         {
445                                                 if (value is TimeSpan)
446                                                         _timeout = (int)((TimeSpan)value).TotalMilliseconds;
447                                                 else
448                                                         _timeout = Convert.ToInt32(value); 
449                                                 break;
450                                         } // case TimeoutKey
451
452                                         case AllowAutoRedirectKey: _bAllowAutoRedirect = Convert.ToBoolean(value); break;
453                 
454                                 } // switch (keyStr.ToLower())
455                         }
456                 } // this[]   
457         
458                 public override ICollection Keys
459                 {
460                         get
461                         {
462                                 if (s_keySet == null)
463                                 {
464                                         // No need for synchronization
465                                         ArrayList keys = new ArrayList(6);
466                                         keys.Add(UserNameKey);
467                                         keys.Add(PasswordKey);
468                                         keys.Add(DomainKey);
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);                    
476
477                                         s_keySet = keys;
478                                 }
479
480                                 return s_keySet;
481                         }
482                 } 
483
484                 private void UpdateProxy()
485                         {
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);
491                                         
492                                 // Either it's default or not it'll have this property
493                                 ((WebProxy)_proxyObject).BypassProxyOnLocal = true;
494                         } 
495                 
496                 
497                 internal static String UserAgent
498                 {
499                         get { return s_userAgent; }
500                 }
501
502                 private void CreateUrl(string uri, out string fullURL)
503                 {
504                         if(HttpHelper.StartsWithHttp(uri)) //this is a full url
505                         {
506                                 fullURL = uri;
507                                 return;
508                         }
509
510                         if(_channelURI.EndsWith("/") && uri.StartsWith("/"))
511                         {
512                                 fullURL = _channelURI + uri.Substring(1);
513                                 return;
514                         }
515                         else
516                                 if(_channelURI.EndsWith("/") && !uri.StartsWith("/") ||
517                                 !_channelURI.EndsWith("/") && uri.StartsWith("/") )
518                         {
519                                 fullURL = _channelURI  +uri;
520                                 return;
521                         }
522                         else
523                         {
524                                 fullURL = _channelURI +'/'+ uri;
525                                 return;
526                         }
527
528                 }
529
530                 private HttpWebRequest CreateWebRequest(string url, ITransportHeaders requestHeaders, Stream requestStream)
531                 {
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";
537                         
538                         //This caused us some troubles with the HttpWebResponse class
539                         //maybe its fixed now. TODO
540                         //request.KeepAlive = _useKeepAlive;
541                         request.KeepAlive = false;;
542                         
543                         request.Method = s_defaultVerb;
544                         request.Pipelined = false;
545                         request.SendChunked = _useChunked;
546                         request.UserAgent = s_userAgent;
547
548
549                         // write the remoting headers                   
550                         IEnumerator headerenum = requestHeaders.GetEnumerator();
551                         while (headerenum.MoveNext()) 
552                         {
553                                 DictionaryEntry entry = (DictionaryEntry) headerenum.Current;
554                                 String key = entry.Key as String;
555                                 if(key == "Content-Type")
556                                 {
557                                         request.ContentType = entry.Value.ToString();
558                                         continue;
559                                 }
560
561                                 if (key == null || key.StartsWith("__")) 
562                                 {
563                                         continue;
564                                 }
565
566                                 request.Headers.Add(entry.Key.ToString(),entry.Value.ToString());
567                         }
568
569                         Stream reqStream = request.GetRequestStream();
570                         try {
571                                 HttpHelper.CopyStream(requestStream, reqStream);
572                         } finally {
573                                 reqStream.Close();
574                         }
575                         
576                         return request;
577                 }       
578         
579                 private void SendAndRecieve(HttpWebRequest httpRequest,out ITransportHeaders responseHeaders,out Stream responseStream)
580                 {
581                         responseStream = null;
582                         responseHeaders = null;
583                         HttpWebResponse httpWebResponse = null;
584
585                         try
586                         {
587                                 httpWebResponse = (HttpWebResponse)httpRequest.GetResponse();
588                         }
589                         catch (WebException ex)
590                         {
591                                 httpWebResponse = ex.Response as HttpWebResponse;
592                                 if (httpWebResponse == null || httpWebResponse.StatusCode == HttpStatusCode.InternalServerError) throw ex;
593                         }
594
595                         ReceiveResponse (httpWebResponse, out responseHeaders, out responseStream);
596                 }
597
598                 private void ReceiveResponse (HttpWebResponse httpWebResponse, out ITransportHeaders responseHeaders, out Stream responseStream)
599                 {
600                         responseHeaders = new TransportHeaders();
601                         Stream webStream = httpWebResponse.GetResponseStream();
602
603                         try
604                         {
605                                 if (httpWebResponse.ContentLength != -1)
606                                 {
607                                         byte[] buffer = new byte [httpWebResponse.ContentLength];
608                                         int nr = 0;
609                                         while (nr < buffer.Length) {
610                                                 int pr = webStream.Read (buffer, nr, buffer.Length - nr);
611                                                 if (pr == 0) throw new RemotingException ("Connection closed");
612                                                 nr += pr;
613                                         }
614                                         responseStream = new MemoryStream (buffer);
615                                 }
616                                 else
617                                 {
618                                         responseStream = new MemoryStream();
619                                         HttpHelper.CopyStream(webStream, responseStream);
620                                 }
621
622                                 //Use the two commented lines below instead of the 3 below lines when HttpWebResponse
623                                 //class is fully implemented in order to support custom headers
624                                 //for(int i=0; i < httpWebResponse.Headers.Count; ++i)
625                                 //      responseHeaders[httpWebResponse.Headers.Keys[i].ToString()] = httpWebResponse.Headers[i].ToString();
626
627                                 responseHeaders["Content-Type"] = httpWebResponse.ContentType;
628                                 responseHeaders["Server"] = httpWebResponse.Server;
629                                 responseHeaders["Content-Length"] = httpWebResponse.ContentLength;
630                         }
631                         finally
632                         {
633                                 webStream.Close ();
634                                 httpWebResponse.Close();
635                         }
636                 }
637
638                 private void ProcessErrorCode()
639                 {
640                 }
641
642                 private ICredentials GetCredenentials()
643                 {
644                         if(_credentials!=null)
645                                 return _credentials;
646
647                         //Now use the username , password and domain if provided
648                         if(_securityUserName==null ||_securityUserName=="")
649                                 if(_channel.UseDefaultCredentials)
650                                         return CredentialCache.DefaultCredentials;
651                                 else
652                                         return null;
653
654                         return new NetworkCredential(_securityUserName,_securityPassword,_securityDomain);
655
656                 }
657
658                 
659         } // class HttpClientTransportSink
660
661
662         internal class RequestState
663         {
664                 public HttpWebRequest webRquest;
665                 public IClientChannelSinkStack sinkStack;
666
667                 public RequestState(HttpWebRequest wr,IClientChannelSinkStack ss)
668                 {
669                         webRquest = wr;
670                         sinkStack = ss;
671                 }
672         }
673
674
675 } // namespace System.Runtime.Remoting.Channels.Http