2 // System.Net.WebConnectionGroup
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // Martin Baulig (martin.baulig@xamarin.com)
8 // (C) 2003 Ximian, Inc (http://www.ximian.com)
9 // Copyright 2011-2014 Xamarin, Inc (http://www.xamarin.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.Threading;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.Net.Configuration;
38 using System.Net.Sockets;
39 using System.Diagnostics;
43 class WebConnectionGroup
47 LinkedList<ConnectionState> connections;
51 public WebConnectionGroup (ServicePoint sPoint, string name)
55 connections = new LinkedList<ConnectionState> ();
59 public event EventHandler ConnectionClosed;
61 void OnConnectionClosed ()
63 if (ConnectionClosed != null)
64 ConnectionClosed (this, null);
69 //TODO: what do we do with the queue? Empty it out and abort the requests?
70 //TODO: abort requests or wait for them to finish
73 var iter = connections.First;
74 while (iter != null) {
75 var cnc = iter.Value.Connection;
79 connections.Remove (node);
81 OnConnectionClosed ();
86 public WebConnection GetConnection (HttpWebRequest request, out bool created)
89 return CreateOrReuseConnection (request, out created);
93 static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
95 if (!cnc.NtlmAuthenticated)
98 bool needs_reset = false;
99 NetworkCredential cnc_cred = cnc.NtlmCredential;
101 bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
102 ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
103 NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
105 if (cnc_cred == null || req_cred == null ||
106 cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
107 cnc_cred.Password != req_cred.Password) {
112 bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
113 bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
114 needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
117 cnc.Close (false); // closes the authenticated connection
122 ConnectionState FindIdleConnection ()
124 foreach (var cnc in connections) {
128 connections.Remove (cnc);
129 connections.AddFirst (cnc);
136 WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
138 var cnc = FindIdleConnection ();
141 PrepareSharingNtlm (cnc.Connection, request);
142 return cnc.Connection;
145 if (sPoint.ConnectionLimit > connections.Count || connections.Count == 0) {
147 cnc = new ConnectionState (this);
148 connections.AddFirst (cnc);
149 return cnc.Connection;
153 cnc = connections.Last.Value;
154 connections.Remove (cnc);
155 connections.AddFirst (cnc);
156 return cnc.Connection;
163 internal Queue Queue {
164 get { return queue; }
167 internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
169 var now = DateTime.UtcNow;
173 List<WebConnection> connectionsToClose = null;
177 idleSince = DateTime.MinValue;
182 var iter = connections.First;
183 while (iter != null) {
184 var cnc = iter.Value;
192 if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
193 if (cnc.IdleSince > idleSince)
194 idleSince = cnc.IdleSince;
199 * Do not call WebConnection.Close() while holding the ServicePoint lock
200 * as this could deadlock when attempting to take the WebConnection lock.
204 if (connectionsToClose == null)
205 connectionsToClose = new List<WebConnection> ();
206 connectionsToClose.Add (cnc.Connection);
207 connections.Remove (node);
210 recycled = connections.Count == 0;
213 // Did we find anything that can be closed?
214 if (connectionsToClose == null)
217 // Ok, let's get rid of these!
218 foreach (var cnc in connectionsToClose)
221 // Re-take the lock, then remove them from the connection list.
225 class ConnectionState : IWebConnectionState {
226 public WebConnection Connection {
231 public WebConnectionGroup Group {
236 public ServicePoint ServicePoint {
237 get { return Group.sPoint; }
247 public DateTime IdleSince {
248 get { return idleSince; }
251 public bool TrySetBusy ()
253 lock (ServicePoint) {
257 idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
262 public void SetIdle ()
264 lock (ServicePoint) {
266 idleSince = DateTime.UtcNow;
270 public ConnectionState (WebConnectionGroup group)
273 idleSince = DateTime.UtcNow;
274 Connection = new WebConnection (this, group.sPoint);