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;
52 X509Certificate certificate;
53 X509Certificate clientCertificate;
56 Dictionary<string,WebConnectionGroup> groups;
57 bool sendContinue = true;
59 object hostE = new object ();
61 BindIPEndPoint endPointCallback = null;
63 int tcp_keepalive_time;
64 int tcp_keepalive_interval;
69 internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
72 this.connectionLimit = connectionLimit;
73 this.maxIdleTime = maxIdleTime;
74 this.currentConnections = 0;
75 this.idleSince = DateTime.UtcNow;
84 static Exception GetMustImplement ()
86 return new NotImplementedException ();
89 public BindIPEndPoint BindIPEndPointDelegate
91 get { return endPointCallback; }
92 set { endPointCallback = value; }
95 public X509Certificate Certificate {
96 get { return certificate; }
99 public X509Certificate ClientCertificate {
100 get { return clientCertificate; }
104 public int ConnectionLeaseTimeout
107 throw GetMustImplement ();
110 throw GetMustImplement ();
114 public int ConnectionLimit {
115 get { return connectionLimit; }
118 throw new ArgumentOutOfRangeException ();
120 connectionLimit = value;
124 public string ConnectionName {
125 get { return uri.Scheme; }
128 public int CurrentConnections {
130 return currentConnections;
134 public DateTime IdleSince {
136 return idleSince.ToLocalTime ();
140 public int MaxIdleTime {
141 get { return maxIdleTime; }
143 if (value < Timeout.Infinite || value > Int32.MaxValue)
144 throw new ArgumentOutOfRangeException ();
148 if (idleTimer != null)
149 idleTimer.Change (maxIdleTime, maxIdleTime);
154 public virtual Version ProtocolVersion {
155 get { return protocolVersion; }
159 public int ReceiveBufferSize
162 throw GetMustImplement ();
165 throw GetMustImplement ();
169 public bool SupportsPipelining {
170 get { return HttpVersion.Version11.Equals (protocolVersion); }
174 public bool Expect100Continue {
175 get { return SendContinue; }
176 set { SendContinue = value; }
179 public bool UseNagleAlgorithm {
180 get { return useNagle; }
181 set { useNagle = value; }
184 internal bool SendContinue {
185 get { return sendContinue &&
186 (protocolVersion == null || protocolVersion == HttpVersion.Version11); }
187 set { sendContinue = value; }
191 public void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
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");
200 tcp_keepalive = enabled;
201 tcp_keepalive_time = keepAliveTime;
202 tcp_keepalive_interval = keepAliveInterval;
205 internal void KeepAliveSetup (Socket socket)
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);
217 static void PutBytes (byte [] bytes, uint v, int offset)
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);
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);
234 internal bool UsesProxy {
235 get { return usesProxy; }
236 set { usesProxy = value; }
239 internal bool UseConnect {
240 get { return useConnect; }
241 set { useConnect = value; }
244 WebConnectionGroup GetConnectionGroup (string name)
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.
257 WebConnectionGroup group;
258 if (groups != null && groups.TryGetValue (name, out group))
261 group = new WebConnectionGroup (this, name);
262 group.ConnectionClosed += (s, e) => currentConnections--;
265 groups = new Dictionary<string, WebConnectionGroup> ();
266 groups.Add (name, group);
271 void RemoveConnectionGroup (WebConnectionGroup group)
273 if (groups == null || groups.Count == 0)
274 throw new InvalidOperationException ();
276 groups.Remove (group.Name);
279 bool CheckAvailableForRecycling (out DateTime outIdleSince)
281 outIdleSince = DateTime.MinValue;
283 TimeSpan idleTimeSpan;
284 List<WebConnectionGroup> groupList = null, removeList = null;
286 if (groups == null || groups.Count == 0) {
287 idleSince = DateTime.MinValue;
291 idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
294 * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
295 * copy the group dictionary if it exists.
297 * In most cases, we only have a single connection group, so we can simply store
298 * that in a local variable instead of copying a collection.
302 groupList = new List<WebConnectionGroup> (groups.Values);
305 foreach (var group in groupList) {
306 if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
308 if (removeList == null)
309 removeList = new List<WebConnectionGroup> ();
310 removeList.Add (group);
314 idleSince = outIdleSince;
316 if (removeList != null && groups != null) {
317 foreach (var group in removeList)
318 if (groups.ContainsKey (group.Name))
319 RemoveConnectionGroup (group);
322 if (groups != null && groups.Count == 0)
325 if (groups == null) {
326 if (idleTimer != null) {
327 idleTimer.Dispose ();
337 void IdleTimerCallback (object obj)
340 CheckAvailableForRecycling (out dummy);
343 private bool HasTimedOut
346 int timeout = ServicePointManager.DnsRefreshTimeout;
347 return timeout != Timeout.Infinite &&
348 (lastDnsResolve + TimeSpan.FromMilliseconds (timeout)) < DateTime.UtcNow;
352 internal IPHostEntry HostEntry
356 string uriHost = uri.Host;
358 if (host == null || HasTimedOut) {
359 lastDnsResolve = DateTime.UtcNow;
362 host = Dns.GetHostEntry (uriHost);
374 internal void SetVersion (Version version)
376 protocolVersion = version;
379 internal EventHandler SendRequest (HttpWebRequest request, string groupName)
385 WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
386 cnc = cncGroup.GetConnection (request, out created);
388 ++currentConnections;
389 if (idleTimer == null)
390 idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
394 return cnc.SendRequest (request);
396 public bool CloseConnectionGroup (string connectionGroupName)
398 WebConnectionGroup cncGroup = null;
401 cncGroup = GetConnectionGroup (connectionGroupName);
402 if (cncGroup != null) {
403 RemoveConnectionGroup (cncGroup);
407 // WebConnectionGroup.Close() must *not* be called inside the lock
408 if (cncGroup != null) {
416 internal void SetServerCertificate (X509Certificate server)
418 var cloned = server != null ? new X509Certificate (server) : null;
419 var old = Interlocked.Exchange (ref certificate, cloned);
424 internal void SetClientCertificate (X509Certificate clientCertificate)
426 var cloned = clientCertificate != null ? new X509Certificate (clientCertificate) : null;
427 var old = Interlocked.Exchange (ref clientCertificate, cloned);
432 internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
434 if (endPointCallback == null)
439 IPEndPoint local = null;
441 local = endPointCallback (this,
444 // This is to differentiate from an
445 // OverflowException, which should propagate.
454 } catch (SocketException) {
455 // This is intentional; the docs say
456 // that if the Bind fails, we keep
457 // going until there is an
458 // OverflowException on the retry