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