Merge pull request #1936 from esdrubal/DotNetRelativeOrAbsolute
[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                 Dictionary<string,WebConnectionGroup> groups;
56                 bool sendContinue = true;
57                 bool useConnect;
58                 object hostE = new object ();
59                 bool useNagle;
60                 BindIPEndPoint endPointCallback = null;
61                 bool tcp_keepalive;
62                 int tcp_keepalive_time;
63                 int tcp_keepalive_interval;
64                 Timer idleTimer;
65
66                 // Constructors
67
68                 internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
69                 {
70                         this.uri = uri;  
71                         this.connectionLimit = connectionLimit;
72                         this.maxIdleTime = maxIdleTime; 
73                         this.currentConnections = 0;
74                         this.idleSince = DateTime.UtcNow;
75                 }
76                 
77                 // Properties
78                 
79                 public Uri Address {
80                         get { return uri; }
81                 }
82
83                 static Exception GetMustImplement ()
84                 {
85                         return new NotImplementedException ();
86                 }
87
88                 public BindIPEndPoint BindIPEndPointDelegate
89                 {
90                         get { return endPointCallback; }
91                         set { endPointCallback = value; }
92                 }
93                 
94                 public X509Certificate Certificate {
95                         get { return certificate; }
96                 }
97                 
98                 public X509Certificate ClientCertificate {
99                         get { return clientCertificate; }
100                 }
101
102                 [MonoTODO]
103                 public int ConnectionLeaseTimeout
104                 {
105                         get {
106                                 throw GetMustImplement ();
107                         }
108                         set {
109                                 throw GetMustImplement ();
110                         }
111                 }
112                 
113                 public int ConnectionLimit {
114                         get { return connectionLimit; }
115                         set {
116                                 if (value <= 0)
117                                         throw new ArgumentOutOfRangeException ();
118
119                                 connectionLimit = value;
120                         }
121                 }
122                 
123                 public string ConnectionName {
124                         get { return uri.Scheme; }
125                 }
126
127                 public int CurrentConnections {
128                         get {
129                                 return currentConnections;
130                         }
131                 }
132
133                 public DateTime IdleSince {
134                         get {
135                                 return idleSince.ToLocalTime ();
136                         }
137                 }
138
139                 public int MaxIdleTime {
140                         get { return maxIdleTime; }
141                         set { 
142                                 if (value < Timeout.Infinite || value > Int32.MaxValue)
143                                         throw new ArgumentOutOfRangeException ();
144
145                                 lock (this) {
146                                         maxIdleTime = value;
147                                         if (idleTimer != null)
148                                                 idleTimer.Change (maxIdleTime, maxIdleTime);
149                                 }
150                         }
151                 }
152                 
153                 public virtual Version ProtocolVersion {
154                         get { return protocolVersion; }
155                 }
156
157                 [MonoTODO]
158                 public int ReceiveBufferSize
159                 {
160                         get {
161                                 throw GetMustImplement ();
162                         }
163                         set {
164                                 throw GetMustImplement ();
165                         }
166                 }
167                 
168                 public bool SupportsPipelining {
169                         get { return HttpVersion.Version11.Equals (protocolVersion); }
170                 }
171
172
173                 public bool Expect100Continue {
174                         get { return SendContinue; }
175                         set { SendContinue = value; }
176                 }
177
178                 public bool UseNagleAlgorithm {
179                         get { return useNagle; }
180                         set { useNagle = value; }
181                 }
182
183                 internal bool SendContinue {
184                         get { return sendContinue &&
185                                      (protocolVersion == null || protocolVersion == HttpVersion.Version11); }
186                         set { sendContinue = value; }
187                 }
188                 // Methods
189
190                 public void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
191                 {
192                         if (enabled) {
193                                 if (keepAliveTime <= 0)
194                                         throw new ArgumentOutOfRangeException ("keepAliveTime", "Must be greater than 0");
195                                 if (keepAliveInterval <= 0)
196                                         throw new ArgumentOutOfRangeException ("keepAliveInterval", "Must be greater than 0");
197                         }
198
199                         tcp_keepalive = enabled;
200                         tcp_keepalive_time = keepAliveTime;
201                         tcp_keepalive_interval = keepAliveInterval;
202                 }
203
204                 internal void KeepAliveSetup (Socket socket)
205                 {
206                         if (!tcp_keepalive)
207                                 return;
208
209                         byte [] bytes = new byte [12];
210                         PutBytes (bytes, (uint) (tcp_keepalive ? 1 : 0), 0);
211                         PutBytes (bytes, (uint) tcp_keepalive_time, 4);
212                         PutBytes (bytes, (uint) tcp_keepalive_interval, 8);
213                         socket.IOControl (IOControlCode.KeepAliveValues, bytes, null);
214                 }
215
216                 static void PutBytes (byte [] bytes, uint v, int offset)
217                 {
218                         if (BitConverter.IsLittleEndian) {
219                                 bytes [offset] = (byte) (v & 0x000000ff);
220                                 bytes [offset + 1] = (byte) ((v & 0x0000ff00) >> 8);
221                                 bytes [offset + 2] = (byte) ((v & 0x00ff0000) >> 16);
222                                 bytes [offset + 3] = (byte) ((v & 0xff000000) >> 24);
223                         } else {
224                                 bytes [offset + 3] = (byte) (v & 0x000000ff);
225                                 bytes [offset + 2] = (byte) ((v & 0x0000ff00) >> 8);
226                                 bytes [offset + 1] = (byte) ((v & 0x00ff0000) >> 16);
227                                 bytes [offset] = (byte) ((v & 0xff000000) >> 24);
228                         }
229                 }
230
231                 // Internal Methods
232
233                 internal bool UsesProxy {
234                         get { return usesProxy; }
235                         set { usesProxy = value; }
236                 }
237
238                 internal bool UseConnect {
239                         get { return useConnect; }
240                         set { useConnect = value; }
241                 }
242
243                 WebConnectionGroup GetConnectionGroup (string name)
244                 {
245                         if (name == null)
246                                 name = "";
247
248                         /*
249                          * Optimization:
250                          * 
251                          * In the vast majority of cases, we only have one single WebConnectionGroup per ServicePoint, so we
252                          * don't need to allocate a dictionary.
253                          * 
254                          */
255
256                         WebConnectionGroup group;
257                         if (groups != null && groups.TryGetValue (name, out group))
258                                 return group;
259
260                         group = new WebConnectionGroup (this, name);
261                         group.ConnectionClosed += (s, e) => currentConnections--;
262
263                         if (groups == null)
264                                 groups = new Dictionary<string, WebConnectionGroup> ();
265                         groups.Add (name, group);
266
267                         return group;
268                 }
269
270                 void RemoveConnectionGroup (WebConnectionGroup group)
271                 {
272                         if (groups == null || groups.Count == 0)
273                                 throw new InvalidOperationException ();
274
275                         groups.Remove (group.Name);
276                 }
277
278                 bool CheckAvailableForRecycling (out DateTime outIdleSince)
279                 {
280                         outIdleSince = DateTime.MinValue;
281
282                         TimeSpan idleTimeSpan;
283                         List<WebConnectionGroup> groupList = null, removeList = null;
284                         lock (this) {
285                                 if (groups == null || groups.Count == 0) {
286                                         idleSince = DateTime.MinValue;
287                                         return true;
288                                 }
289
290                                 idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
291
292                                 /*
293                                  * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
294                                  * copy the group dictionary if it exists.
295                                  * 
296                                  * In most cases, we only have a single connection group, so we can simply store
297                                  * that in a local variable instead of copying a collection.
298                                  * 
299                                  */
300
301                                 groupList = new List<WebConnectionGroup> (groups.Values);
302                         }
303
304                         foreach (var group in groupList) {
305                                 if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
306                                         continue;
307                                 if (removeList == null)
308                                         removeList = new List<WebConnectionGroup> ();
309                                 removeList.Add (group);
310                         }
311
312                         lock (this) {
313                                 idleSince = outIdleSince;
314
315                                 if (removeList != null && groups != null) {
316                                         foreach (var group in removeList)
317                                                 if (groups.ContainsKey (group.Name))
318                                                         RemoveConnectionGroup (group);
319                                 }
320
321                                 if (groups != null && groups.Count == 0)
322                                         groups = null;
323
324                                 if (groups == null) {
325                                         if (idleTimer != null) {
326                                                 idleTimer.Dispose ();
327                                                 idleTimer = null;
328                                         }
329                                         return true;
330                                 }
331
332                                 return false;
333                         }
334                 }
335
336                 void IdleTimerCallback (object obj)
337                 {
338                         DateTime dummy;
339                         CheckAvailableForRecycling (out dummy);
340                 }
341
342                 internal IPHostEntry HostEntry
343                 {
344                         get {
345                                 lock (hostE) {
346                                         if (host != null)
347                                                 return host;
348
349                                         string uriHost = uri.Host;
350
351                                         // There is no need to do DNS resolution on literal IP addresses
352                                         if (uri.HostNameType == UriHostNameType.IPv6 ||
353                                                 uri.HostNameType == UriHostNameType.IPv4) {
354
355                                                 if (uri.HostNameType == UriHostNameType.IPv6) {
356                                                         // Remove square brackets
357                                                         uriHost = uriHost.Substring(1,uriHost.Length-2);
358                                                 }
359
360                                                 // Creates IPHostEntry
361                                                 host = new IPHostEntry();
362                                                 host.AddressList = new IPAddress[] { IPAddress.Parse(uriHost) };
363
364                                                 return host;
365                                         }
366
367                                         // Try DNS resolution on host names
368                                         try  {
369                                                 host = Dns.GetHostByName (uriHost);
370                                         } 
371                                         catch {
372                                                 return null;
373                                         }
374                                 }
375
376                                 return host;
377                         }
378                 }
379
380                 internal void SetVersion (Version version)
381                 {
382                         protocolVersion = version;
383                 }
384
385                 internal EventHandler SendRequest (HttpWebRequest request, string groupName)
386                 {
387                         WebConnection cnc;
388                         
389                         lock (this) {
390                                 bool created;
391                                 WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
392                                 cnc = cncGroup.GetConnection (request, out created);
393                                 if (created) {
394                                         ++currentConnections;
395                                         if (idleTimer == null)
396                                                 idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
397                                 }
398                         }
399                         
400                         return cnc.SendRequest (request);
401                 }
402                 public bool CloseConnectionGroup (string connectionGroupName)
403                 {
404                         WebConnectionGroup cncGroup = null;
405
406                         lock (this) {
407                                 cncGroup = GetConnectionGroup (connectionGroupName);
408                                 if (cncGroup != null) {
409                                         RemoveConnectionGroup (cncGroup);
410                                 }
411                         }
412
413                         // WebConnectionGroup.Close() must *not* be called inside the lock
414                         if (cncGroup != null) {
415                                 cncGroup.Close ();
416                                 return true;
417                         }
418
419                         return false;
420                 }
421
422                 internal void SetCertificates (X509Certificate client, X509Certificate server) 
423                 {
424                         certificate = server;
425                         clientCertificate = client;
426                 }
427
428                 internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
429                 {
430                         if (endPointCallback == null)
431                                 return true;
432
433                         int count = 0;
434                         for (;;) {
435                                 IPEndPoint local = null;
436                                 try {
437                                         local = endPointCallback (this,
438                                                 remote, count);
439                                 } catch {
440                                         // This is to differentiate from an
441                                         // OverflowException, which should propagate.
442                                         return false;
443                                 }
444
445                                 if (local == null)
446                                         return true;
447
448                                 try {
449                                         sock.Bind (local);
450                                 } catch (SocketException) {
451                                         // This is intentional; the docs say
452                                         // that if the Bind fails, we keep
453                                         // going until there is an
454                                         // OverflowException on the retry
455                                         // count.
456                                         checked { ++count; }
457                                         continue;
458                                 }
459
460                                 return true;
461                         }
462                 }
463         }
464 }
465
466