2 // System.Net.ServicePoint
5 // Lawrence Pit (loz@cable.a2000.nl)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
8 // (c) 2002 Lawrence Pit
9 // (c) 2003 Ximian, Inc. (http://www.ximian.com)
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:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
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.
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;
43 public class ServicePoint
48 int currentConnections;
50 DateTime lastDnsResolve;
51 Version protocolVersion;
54 Dictionary<string,WebConnectionGroup> groups;
55 bool sendContinue = true;
57 object hostE = new object ();
59 BindIPEndPoint endPointCallback = null;
61 int tcp_keepalive_time;
62 int tcp_keepalive_interval;
67 internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
70 this.connectionLimit = connectionLimit;
71 this.maxIdleTime = maxIdleTime;
72 this.currentConnections = 0;
73 this.idleSince = DateTime.UtcNow;
82 static Exception GetMustImplement ()
84 return new NotImplementedException ();
87 public BindIPEndPoint BindIPEndPointDelegate
89 get { return endPointCallback; }
90 set { endPointCallback = value; }
94 public int ConnectionLeaseTimeout
97 throw GetMustImplement ();
100 throw GetMustImplement ();
104 public int ConnectionLimit {
105 get { return connectionLimit; }
108 throw new ArgumentOutOfRangeException ();
110 connectionLimit = value;
114 public string ConnectionName {
115 get { return uri.Scheme; }
118 public int CurrentConnections {
120 return currentConnections;
124 public DateTime IdleSince {
126 return idleSince.ToLocalTime ();
130 public int MaxIdleTime {
131 get { return maxIdleTime; }
133 if (value < Timeout.Infinite || value > Int32.MaxValue)
134 throw new ArgumentOutOfRangeException ();
138 if (idleTimer != null)
139 idleTimer.Change (maxIdleTime, maxIdleTime);
144 public virtual Version ProtocolVersion {
145 get { return protocolVersion; }
149 public int ReceiveBufferSize
152 throw GetMustImplement ();
155 throw GetMustImplement ();
159 public bool SupportsPipelining {
160 get { return HttpVersion.Version11.Equals (protocolVersion); }
164 public bool Expect100Continue {
165 get { return SendContinue; }
166 set { SendContinue = value; }
169 public bool UseNagleAlgorithm {
170 get { return useNagle; }
171 set { useNagle = value; }
174 internal bool SendContinue {
175 get { return sendContinue &&
176 (protocolVersion == null || protocolVersion == HttpVersion.Version11); }
177 set { sendContinue = value; }
181 public void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
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");
190 tcp_keepalive = enabled;
191 tcp_keepalive_time = keepAliveTime;
192 tcp_keepalive_interval = keepAliveInterval;
195 internal void KeepAliveSetup (Socket socket)
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);
207 static void PutBytes (byte [] bytes, uint v, int offset)
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);
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);
224 internal bool UsesProxy {
225 get { return usesProxy; }
226 set { usesProxy = value; }
229 internal bool UseConnect {
230 get { return useConnect; }
231 set { useConnect = value; }
234 WebConnectionGroup GetConnectionGroup (string name)
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.
247 WebConnectionGroup group;
248 if (groups != null && groups.TryGetValue (name, out group))
251 group = new WebConnectionGroup (this, name);
252 group.ConnectionClosed += (s, e) => currentConnections--;
255 groups = new Dictionary<string, WebConnectionGroup> ();
256 groups.Add (name, group);
261 void RemoveConnectionGroup (WebConnectionGroup group)
263 if (groups == null || groups.Count == 0)
264 throw new InvalidOperationException ();
266 groups.Remove (group.Name);
269 bool CheckAvailableForRecycling (out DateTime outIdleSince)
271 outIdleSince = DateTime.MinValue;
273 TimeSpan idleTimeSpan;
274 List<WebConnectionGroup> groupList = null, removeList = null;
276 if (groups == null || groups.Count == 0) {
277 idleSince = DateTime.MinValue;
281 idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
284 * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
285 * copy the group dictionary if it exists.
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.
292 groupList = new List<WebConnectionGroup> (groups.Values);
295 foreach (var group in groupList) {
296 if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
298 if (removeList == null)
299 removeList = new List<WebConnectionGroup> ();
300 removeList.Add (group);
304 idleSince = outIdleSince;
306 if (removeList != null && groups != null) {
307 foreach (var group in removeList)
308 if (groups.ContainsKey (group.Name))
309 RemoveConnectionGroup (group);
312 if (groups != null && groups.Count == 0)
315 if (groups == null) {
316 if (idleTimer != null) {
317 idleTimer.Dispose ();
327 void IdleTimerCallback (object obj)
330 CheckAvailableForRecycling (out dummy);
333 private bool HasTimedOut
336 int timeout = ServicePointManager.DnsRefreshTimeout;
337 return timeout != Timeout.Infinite &&
338 (lastDnsResolve + TimeSpan.FromMilliseconds (timeout)) < DateTime.UtcNow;
342 internal IPHostEntry HostEntry
346 string uriHost = uri.Host;
348 if (host == null || HasTimedOut) {
349 lastDnsResolve = DateTime.UtcNow;
352 host = Dns.GetHostEntry (uriHost);
364 internal void SetVersion (Version version)
366 protocolVersion = version;
369 internal EventHandler SendRequest (HttpWebRequest request, string groupName)
375 WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
376 cnc = cncGroup.GetConnection (request, out created);
378 ++currentConnections;
379 if (idleTimer == null)
380 idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
384 return cnc.SendRequest (request);
386 public bool CloseConnectionGroup (string connectionGroupName)
388 WebConnectionGroup cncGroup = null;
391 cncGroup = GetConnectionGroup (connectionGroupName);
392 if (cncGroup != null) {
393 RemoveConnectionGroup (cncGroup);
397 // WebConnectionGroup.Close() must *not* be called inside the lock
398 if (cncGroup != null) {
407 // Copied from the referencesource
410 object m_ServerCertificateOrBytes;
411 object m_ClientCertificateOrBytes;
415 /// Gets the certificate received for this <see cref='System.Net.ServicePoint'/>.
418 public X509Certificate Certificate {
420 object chkCert = m_ServerCertificateOrBytes;
421 if (chkCert != null && chkCert.GetType() == typeof(byte[]))
422 return (X509Certificate)(m_ServerCertificateOrBytes = new X509Certificate((byte[]) chkCert));
424 return chkCert as X509Certificate;
427 internal void UpdateServerCertificate(X509Certificate certificate)
429 if (certificate != null)
430 m_ServerCertificateOrBytes = certificate.GetRawCertData();
432 m_ServerCertificateOrBytes = null;
437 /// Gets the Client Certificate sent by us to the Server.
440 public X509Certificate ClientCertificate {
442 object chkCert = m_ClientCertificateOrBytes;
443 if (chkCert != null && chkCert.GetType() == typeof(byte[]))
444 return (X509Certificate)(m_ClientCertificateOrBytes = new X509Certificate((byte[]) chkCert));
446 return chkCert as X509Certificate;
449 internal void UpdateClientCertificate(X509Certificate certificate)
451 if (certificate != null)
452 m_ClientCertificateOrBytes = certificate.GetRawCertData();
454 m_ClientCertificateOrBytes = null;
457 internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
459 if (endPointCallback == null)
464 IPEndPoint local = null;
466 local = endPointCallback (this,
469 // This is to differentiate from an
470 // OverflowException, which should propagate.
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