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 Version protocolVersion;
51 X509Certificate certificate;
52 X509Certificate clientCertificate;
55 WebConnectionGroup firstGroup;
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 (firstGroup != null && name == firstGroup.Name)
260 if (groups != null && groups.TryGetValue (name, out group))
263 group = new WebConnectionGroup (this, name);
264 group.ConnectionClosed += (s, e) => currentConnections--;
266 if (firstGroup == null)
270 groups = new Dictionary<string, WebConnectionGroup> ();
271 groups.Add (name, group);
277 void RemoveConnectionGroup (WebConnectionGroup group)
279 if (groups == null || groups.Count == 0) {
280 // No more connection groups left.
281 if (group != firstGroup)
282 throw new InvalidOperationException ();
288 if (group == firstGroup) {
289 // Steal one entry from the dictionary.
290 var en = groups.GetEnumerator ();
292 firstGroup = en.Current.Value;
293 groups.Remove (en.Current.Key);
295 groups.Remove (group.Name);
299 internal bool CheckAvailableForRecycling (out DateTime outIdleSince)
301 outIdleSince = DateTime.MinValue;
303 TimeSpan idleTimeSpan;
304 WebConnectionGroup singleGroup, singleRemove = null;
305 List<WebConnectionGroup> groupList = null, removeList = null;
307 if (firstGroup == null) {
308 idleSince = DateTime.MinValue;
312 idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
315 * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
316 * copy the group dictionary if it exists.
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.
323 singleGroup = firstGroup;
325 groupList = new List<WebConnectionGroup> (groups.Values);
328 if (singleGroup.TryRecycle (idleTimeSpan, ref outIdleSince))
329 singleRemove = singleGroup;
331 if (groupList != null) {
332 foreach (var group in groupList) {
333 if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
335 if (removeList == null)
336 removeList = new List<WebConnectionGroup> ();
337 removeList.Add (group);
342 idleSince = outIdleSince;
344 if (singleRemove != null)
345 RemoveConnectionGroup (singleRemove);
347 if (removeList != null) {
348 foreach (var group in removeList)
349 RemoveConnectionGroup (group);
352 if (groups != null && groups.Count == 0)
355 if (firstGroup == null) {
356 idleTimer.Dispose ();
365 void IdleTimerCallback (object obj)
368 CheckAvailableForRecycling (out dummy);
371 internal IPHostEntry HostEntry
378 string uriHost = uri.Host;
380 // There is no need to do DNS resolution on literal IP addresses
381 if (uri.HostNameType == UriHostNameType.IPv6 ||
382 uri.HostNameType == UriHostNameType.IPv4) {
384 if (uri.HostNameType == UriHostNameType.IPv6) {
385 // Remove square brackets
386 uriHost = uriHost.Substring(1,uriHost.Length-2);
389 // Creates IPHostEntry
390 host = new IPHostEntry();
391 host.AddressList = new IPAddress[] { IPAddress.Parse(uriHost) };
396 // Try DNS resolution on host names
398 host = Dns.GetHostByName (uriHost);
409 internal void SetVersion (Version version)
411 protocolVersion = version;
415 internal EventHandler SendRequest (HttpWebRequest request, string groupName)
421 WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
422 cnc = cncGroup.GetConnection (request, out created);
424 ++currentConnections;
425 if (idleTimer == null)
426 idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
430 return cnc.SendRequest (request);
433 public bool CloseConnectionGroup (string connectionGroupName)
436 WebConnectionGroup cncGroup = GetConnectionGroup (connectionGroupName);
437 if (cncGroup != null) {
446 internal void SetCertificates (X509Certificate client, X509Certificate server)
448 certificate = server;
449 clientCertificate = client;
452 internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
454 if (endPointCallback == null)
459 IPEndPoint local = null;
461 local = endPointCallback (this,
464 // This is to differentiate from an
465 // OverflowException, which should propagate.
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