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 Dictionary<string,WebConnectionGroup> groups;
56 bool sendContinue = true;
58 object hostE = new object ();
60 BindIPEndPoint endPointCallback = null;
62 int tcp_keepalive_time;
63 int tcp_keepalive_interval;
68 internal ServicePoint (Uri uri, int connectionLimit, int maxIdleTime)
71 this.connectionLimit = connectionLimit;
72 this.maxIdleTime = maxIdleTime;
73 this.currentConnections = 0;
74 this.idleSince = DateTime.UtcNow;
83 static Exception GetMustImplement ()
85 return new NotImplementedException ();
88 public BindIPEndPoint BindIPEndPointDelegate
90 get { return endPointCallback; }
91 set { endPointCallback = value; }
94 public X509Certificate Certificate {
95 get { return certificate; }
98 public X509Certificate ClientCertificate {
99 get { return clientCertificate; }
103 public int ConnectionLeaseTimeout
106 throw GetMustImplement ();
109 throw GetMustImplement ();
113 public int ConnectionLimit {
114 get { return connectionLimit; }
117 throw new ArgumentOutOfRangeException ();
119 connectionLimit = value;
123 public string ConnectionName {
124 get { return uri.Scheme; }
127 public int CurrentConnections {
129 return currentConnections;
133 public DateTime IdleSince {
135 return idleSince.ToLocalTime ();
139 public int MaxIdleTime {
140 get { return maxIdleTime; }
142 if (value < Timeout.Infinite || value > Int32.MaxValue)
143 throw new ArgumentOutOfRangeException ();
147 if (idleTimer != null)
148 idleTimer.Change (maxIdleTime, maxIdleTime);
153 public virtual Version ProtocolVersion {
154 get { return protocolVersion; }
158 public int ReceiveBufferSize
161 throw GetMustImplement ();
164 throw GetMustImplement ();
168 public bool SupportsPipelining {
169 get { return HttpVersion.Version11.Equals (protocolVersion); }
173 public bool Expect100Continue {
174 get { return SendContinue; }
175 set { SendContinue = value; }
178 public bool UseNagleAlgorithm {
179 get { return useNagle; }
180 set { useNagle = value; }
183 internal bool SendContinue {
184 get { return sendContinue &&
185 (protocolVersion == null || protocolVersion == HttpVersion.Version11); }
186 set { sendContinue = value; }
190 public void SetTcpKeepAlive (bool enabled, int keepAliveTime, int keepAliveInterval)
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");
199 tcp_keepalive = enabled;
200 tcp_keepalive_time = keepAliveTime;
201 tcp_keepalive_interval = keepAliveInterval;
204 internal void KeepAliveSetup (Socket socket)
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);
216 static void PutBytes (byte [] bytes, uint v, int offset)
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);
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);
233 internal bool UsesProxy {
234 get { return usesProxy; }
235 set { usesProxy = value; }
238 internal bool UseConnect {
239 get { return useConnect; }
240 set { useConnect = value; }
243 WebConnectionGroup GetConnectionGroup (string name)
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.
256 WebConnectionGroup group;
257 if (groups != null && groups.TryGetValue (name, out group))
260 group = new WebConnectionGroup (this, name);
261 group.ConnectionClosed += (s, e) => currentConnections--;
264 groups = new Dictionary<string, WebConnectionGroup> ();
265 groups.Add (name, group);
270 void RemoveConnectionGroup (WebConnectionGroup group)
272 if (groups == null || groups.Count == 0)
273 throw new InvalidOperationException ();
275 groups.Remove (group.Name);
278 internal bool CheckAvailableForRecycling (out DateTime outIdleSince)
280 outIdleSince = DateTime.MinValue;
282 TimeSpan idleTimeSpan;
283 List<WebConnectionGroup> groupList = null, removeList = null;
285 if (groups == null || groups.Count == 0) {
286 idleSince = DateTime.MinValue;
290 idleTimeSpan = TimeSpan.FromMilliseconds (maxIdleTime);
293 * WebConnectionGroup.TryRecycle() must run outside the lock, so we need to
294 * copy the group dictionary if it exists.
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.
301 groupList = new List<WebConnectionGroup> (groups.Values);
304 foreach (var group in groupList) {
305 if (!group.TryRecycle (idleTimeSpan, ref outIdleSince))
307 if (removeList == null)
308 removeList = new List<WebConnectionGroup> ();
309 removeList.Add (group);
313 idleSince = outIdleSince;
315 if (removeList != null) {
316 foreach (var group in removeList)
317 RemoveConnectionGroup (group);
320 if (groups != null && groups.Count == 0)
323 if (groups == null) {
324 if (idleTimer != null) {
325 idleTimer.Dispose ();
335 void IdleTimerCallback (object obj)
338 CheckAvailableForRecycling (out dummy);
341 internal IPHostEntry HostEntry
348 string uriHost = uri.Host;
350 // There is no need to do DNS resolution on literal IP addresses
351 if (uri.HostNameType == UriHostNameType.IPv6 ||
352 uri.HostNameType == UriHostNameType.IPv4) {
354 if (uri.HostNameType == UriHostNameType.IPv6) {
355 // Remove square brackets
356 uriHost = uriHost.Substring(1,uriHost.Length-2);
359 // Creates IPHostEntry
360 host = new IPHostEntry();
361 host.AddressList = new IPAddress[] { IPAddress.Parse(uriHost) };
366 // Try DNS resolution on host names
368 host = Dns.GetHostByName (uriHost);
379 internal void SetVersion (Version version)
381 protocolVersion = version;
385 internal EventHandler SendRequest (HttpWebRequest request, string groupName)
391 WebConnectionGroup cncGroup = GetConnectionGroup (groupName);
392 cnc = cncGroup.GetConnection (request, out created);
394 ++currentConnections;
395 if (idleTimer == null)
396 idleTimer = new Timer (IdleTimerCallback, null, maxIdleTime, maxIdleTime);
400 return cnc.SendRequest (request);
403 public bool CloseConnectionGroup (string connectionGroupName)
406 WebConnectionGroup cncGroup = GetConnectionGroup (connectionGroupName);
407 if (cncGroup != null) {
416 internal void SetCertificates (X509Certificate client, X509Certificate server)
418 certificate = server;
419 clientCertificate = client;
422 internal bool CallEndPointDelegate (Socket sock, IPEndPoint remote)
424 if (endPointCallback == null)
429 IPEndPoint local = null;
431 local = endPointCallback (this,
434 // This is to differentiate from an
435 // OverflowException, which should propagate.
444 } catch (SocketException) {
445 // This is intentional; the docs say
446 // that if the Bind fails, we keep
447 // going until there is an
448 // OverflowException on the retry