Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[mono.git] / mcs / class / System / System.Net / ServicePoint.cs
1 //
2 // System.Net.ServicePoint
3 //
4 // Authors:
5 //      Lawrence Pit (loz@cable.a2000.nl)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (c) 2002 Lawrence Pit
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 using System;
34 using System.Collections.Generic;
35 using System.Diagnostics;
36 using System.Collections;
37 using System.Net.Sockets;
38 using System.Security.Cryptography.X509Certificates;
39 using System.Threading;
40
41 namespace System.Net 
42 {
43         public class ServicePoint
44         {
45                 Uri uri;
46                 int connectionLimit;
47                 int maxIdleTime;
48                 int currentConnections;
49                 DateTime idleSince;
50                 Version protocolVersion;
51                 X509Certificate certificate;
52                 X509Certificate clientCertificate;
53                 IPHostEntry host;
54                 bool usesProxy;
55                 WebConnectionGroup firstGroup;
56                 Dictionary<string,WebConnectionGroup> groups;
57                 bool sendContinue = true;
58                 bool useConnect;
59                 object hostE = new object ();
60                 bool useNagle;
61                 BindIPEndPoint endPointCallback = null;
62                 bool tcp_keepalive;
63                 int tcp_keepalive_time;
64                 int tcp_keepalive_interval;
65                 Timer idleTimer;
66
67                 // Constructors
68
69                 internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
70                 {
71                         this.uri = uri;  
72                         this.connectionLimit = connectionLimit;
73                         this.maxIdleTime = maxIdleTime; 
74                         this.currentConnections = 0;
75                         this.idleSince = DateTime.UtcNow;
76                 }
77                 
78                 // Properties
79                 
80                 public Uri Address {
81                         get { return uri; }
82                 }
83
84                 static Exception GetMustImplement ()
85                 {
86                         return new NotImplementedException ();
87                 }
88
89                 public BindIPEndPoint BindIPEndPointDelegate
90                 {
91                         get { return endPointCallback; }
92                         set { endPointCallback = value; }
93                 }
94                 
95                 public X509Certificate Certificate {
96                         get { return certificate; }
97                 }
98                 
99                 public X509Certificate ClientCertificate {
100                         get { return clientCertificate; }
101                 }
102
103                 [MonoTODO]
104                 public int ConnectionLeaseTimeout
105                 {
106                         get {
107                                 throw GetMustImplement ();
108                         }
109                         set {
110                                 throw GetMustImplement ();
111                         }
112                 }
113                 
114                 public int ConnectionLimit {
115                         get { return connectionLimit; }
116                         set {
117                                 if (value <= 0)
118                                         throw new ArgumentOutOfRangeException ();
119
120                                 connectionLimit = value;
121                         }
122                 }
123                 
124                 public string ConnectionName {
125                         get { return uri.Scheme; }
126                 }
127
128                 public int CurrentConnections {
129                         get {
130                                 return currentConnections;
131                         }
132                 }
133
134                 public DateTime IdleSince {
135                         get {
136                                 return idleSince.ToLocalTime ();
137                         }
138                 }
139
140                 public int MaxIdleTime {
141                         get { return maxIdleTime; }
142                         set { 
143                                 if (value < Timeout.Infinite || value > Int32.MaxValue)
144                                         throw new ArgumentOutOfRangeException ();
145
146                                 lock (this) {
147                                         maxIdleTime = value;
148                                         if (idleTimer != null)
149                                                 idleTimer.Change (maxIdleTime, maxIdleTime);
150                                 }
151                         }
152                 }
153                 
154                 public virtual Version ProtocolVersion {
155                         get { return protocolVersion; }
156                 }
157
158                 [MonoTODO]
159                 public int ReceiveBufferSize
160                 {
161                         get {
162                                 throw GetMustImplement ();
163                         }
164                         set {
165                                 throw GetMustImplement ();
166                         }
167                 }
168                 
169                 public bool SupportsPipelining {
170                         get { return HttpVersion.Version11.Equals (protocolVersion); }
171                 }
172
173
174                 public bool Expect100Continue {
175                         get { return SendContinue; }
176                         set { SendContinue = value; }
177                 }
178
179                 public bool UseNagleAlgorithm {
180                         get { return useNagle; }
181                         set { useNagle = value; }
182                 }
183
184                 internal bool SendContinue {
185                         get { return sendContinue &&
186                                      (protocolVersion == null || protocolVersion == HttpVersion.Version11); }
187                         set { sendContinue = value; }
188                 }
189                 // Methods
190
191                 public void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
192                 {
193                         if (enabled) {
194                                 if (keepAliveTime <= 0)
195                                         throw new ArgumentOutOfRangeException ("keepAliveTime", "Must be greater than 0");
196                                 if (keepAliveInterval <= 0)
197                                         throw new ArgumentOutOfRangeException ("keepAliveInterval", "Must be greater than 0");
198                         }
199
200                         tcp_keepalive = enabled;
201                         tcp_keepalive_time = keepAliveTime;
202                         tcp_keepalive_interval = keepAliveInterval;
203                 }
204
205                 internal void KeepAliveSetup (Socket socket)
206                 {
207                         if (!tcp_keepalive)
208                                 return;
209
210                         byte [] bytes = new byte [12];
211                         PutBytes (bytes, (uint) (tcp_keepalive ? 1 : 0), 0);
212                         PutBytes (bytes, (uint) tcp_keepalive_time, 4);
213                         PutBytes (bytes, (uint) tcp_keepalive_interval, 8);
214                         socket.IOControl (IOControlCode.KeepAliveValues, bytes, null);
215                 }
216
217                 static void PutBytes (byte [] bytes, uint v, int offset)
218                 {
219                         if (BitConverter.IsLittleEndian) {
220                                 bytes [offset] = (byte) (v & 0x000000ff);
221                                 bytes [offset + 1] = (byte) ((v & 0x0000ff00) >> 8);
222                                 bytes [offset + 2] = (byte) ((v & 0x00ff0000) >> 16);
223                                 bytes [offset + 3] = (byte) ((v & 0xff000000) >> 24);
224                         } else {
225                                 bytes [offset + 3] = (byte) (v & 0x000000ff);
226                                 bytes [offset + 2] = (byte) ((v & 0x0000ff00) >> 8);
227                                 bytes [offset + 1] = (byte) ((v & 0x00ff0000) >> 16);
228                                 bytes [offset] = (byte) ((v & 0xff000000) >> 24);
229                         }
230                 }
231
232                 // Internal Methods
233
234                 internal bool UsesProxy {
235                         get { return usesProxy; }
236                         set { usesProxy = value; }
237                 }
238
239                 internal bool UseConnect {
240                         get { return useConnect; }
241                         set { useConnect = value; }
242                 }
243
244                 WebConnectionGroup GetConnectionGroup (string name)
245                 {
246                         if (name == null)
247                                 name = "";
248
249                         /*
250                          * Optimization:
251                          * 
252                          * In the vast majority of cases, we only have one single WebConnectionGroup per ServicePoint, so we
253                          * don't need to allocate a dictionary.
254                          * 
255                          */
256
257                         WebConnectionGroup group;
258                         if (firstGroup != null && name == firstGroup.Name)
259                                 return firstGroup;
260                         if (groups != null && groups.TryGetValue (name, out group))
261                                 return group;
262
263                         group = new WebConnectionGroup (this, name);
264                         group.ConnectionClosed += (s, e) => currentConnections--;
265
266                         if (firstGroup == null)
267                                 firstGroup = group;
268                         else {
269                                 if (groups == null)
270                                         groups = new Dictionary<string, WebConnectionGroup> ();
271                                 groups.Add (name, group);
272                         }
273
274                         return group;
275                 }
276
277                 void RemoveConnectionGroup (WebConnectionGroup group)
278                 {
279                         if (groups == null || groups.Count == 0) {
280                                 // No more connection groups left.
281                                 if (group != firstGroup)
282                                         throw new InvalidOperationException ();
283                                 else
284                                         firstGroup = null;
285                                 return;
286                         }
287
288                         if (group == firstGroup) {
289                                 // Steal one entry from the dictionary.
290                                 var en = groups.GetEnumerator ();
291                                 en.MoveNext ();
292                                 firstGroup = en.Current.Value;
293                                 groups.Remove (en.Current.Key);
294                         } else {
295                                 groups.Remove (group.Name);
296                         }
297                 }
298
299                 internal bool CheckAvailableForRecycling (out DateTime outIdleSince)
300                 {
301                         outIdleSince = DateTime.MinValue;
302
303                         TimeSpan idleTimeSpan;
304                         WebConnectionGroup singleGroup, singleRemove = null;
305                         List<WebConnectionGroup> groupList = null, removeList = null;
306                         lock (this) {
307                                 if (firstGroup == null) {
308                                         idleSince = DateTime.MinValue;
309                                         return true;
310                                 }
311
312                                 idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
313
314                                 /*
315                                  * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
316                                  * copy the group dictionary if it exists.
317                                  * 
318                                  * In most cases, we only have a single connection group, so we can simply store
319                                  * that in a local variable instead of copying a collection.
320                                  * 
321                                  */
322
323                                 singleGroup = firstGroup;
324                                 if (groups != null)
325                                         groupList = new List<WebConnectionGroup> (groups.Values);
326                         }
327
328                         if (singleGroup.TryRecycle (idleTimeSpan, ref outIdleSince))
329                                 singleRemove = singleGroup;
330
331                         if (groupList != null) {
332                                 foreach (var group in groupList) {
333                                         if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
334                                                 continue;
335                                         if (removeList == null)
336                                                 removeList = new List<WebConnectionGroup> ();
337                                         removeList.Add (group);
338                                 }
339                         }
340
341                         lock (this) {
342                                 idleSince = outIdleSince;
343
344                                 if (singleRemove != null)
345                                         RemoveConnectionGroup (singleRemove);
346
347                                 if (removeList != null) {
348                                         foreach (var group in removeList)
349                                                 RemoveConnectionGroup (group);
350                                 }
351
352                                 if (groups != null && groups.Count == 0)
353                                         groups = null;
354
355                                 if (firstGroup == null) {
356                                         idleTimer.Dispose ();
357                                         idleTimer = null;
358                                         return true;
359                                 }
360
361                                 return false;
362                         }
363                 }
364
365                 void IdleTimerCallback (object obj)
366                 {
367                         DateTime dummy;
368                         CheckAvailableForRecycling (out dummy);
369                 }
370
371                 internal IPHostEntry HostEntry
372                 {
373                         get {
374                                 lock (hostE) {
375                                         if (host != null)
376                                                 return host;
377
378                                         string uriHost = uri.Host;
379
380                                         // There is no need to do DNS resolution on literal IP addresses
381                                         if (uri.HostNameType == UriHostNameType.IPv6 ||
382                                                 uri.HostNameType == UriHostNameType.IPv4) {
383
384                                                 if (uri.HostNameType == UriHostNameType.IPv6) {
385                                                         // Remove square brackets
386                                                         uriHost = uriHost.Substring(1,uriHost.Length-2);
387                                                 }
388
389                                                 // Creates IPHostEntry
390                                                 host = new IPHostEntry();
391                                                 host.AddressList = new IPAddress[] { IPAddress.Parse(uriHost) };
392
393                                                 return host;
394                                         }
395
396                                         // Try DNS resolution on host names
397                                         try  {
398                                                 host = Dns.GetHostByName (uriHost);
399                                         } 
400                                         catch {
401                                                 return null;
402                                         }
403                                 }
404
405                                 return host;
406                         }
407                 }
408
409                 internal void SetVersion (Version version)
410                 {
411                         protocolVersion = version;
412                 }
413
414 #if !TARGET_JVM
415                 internal EventHandler SendRequest (HttpWebRequest request, string groupName)
416                 {
417                         WebConnection cnc;
418                         
419                         lock (this) {
420                                 bool created;
421                                 WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
422                                 cnc = cncGroup.GetConnection (request, out created);
423                                 if (created) {
424                                         ++currentConnections;
425                                         if (idleTimer == null)
426                                                 idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
427                                 }
428                         }
429                         
430                         return cnc.SendRequest (request);
431                 }
432 #endif
433                 public bool CloseConnectionGroup (string connectionGroupName)
434                 {
435                         lock (this) {
436                                 WebConnectionGroup cncGroup = GetConnectionGroup (connectionGroupName);
437                                 if (cncGroup != null) {
438                                         cncGroup.Close ();
439                                         return true;
440                                 }
441                         }
442
443                         return false;
444                 }
445
446                 internal void SetCertificates (X509Certificate client, X509Certificate server) 
447                 {
448                         certificate = server;
449                         clientCertificate = client;
450                 }
451
452                 internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
453                 {
454                         if (endPointCallback == null)
455                                 return true;
456
457                         int count = 0;
458                         for (;;) {
459                                 IPEndPoint local = null;
460                                 try {
461                                         local = endPointCallback (this,
462                                                 remote, count);
463                                 } catch {
464                                         // This is to differentiate from an
465                                         // OverflowException, which should propagate.
466                                         return false;
467                                 }
468
469                                 if (local == null)
470                                         return true;
471
472                                 try {
473                                         sock.Bind (local);
474                                 } catch (SocketException) {
475                                         // This is intentional; the docs say
476                                         // that if the Bind fails, we keep
477                                         // going until there is an
478                                         // OverflowException on the retry
479                                         // count.
480                                         checked { ++count; }
481                                         continue;
482                                 }
483
484                                 return true;
485                         }
486                 }
487         }
488 }
489
490