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 foreach (var cnc in connections) {
74 if (cnc.Connection == null)
76 cnc.Connection.Close (false);
77 cnc.Connection = null;
78 OnConnectionClosed ();
84 public WebConnection GetConnection (HttpWebRequest request, out bool created)
87 return CreateOrReuseConnection (request, out created);
91 static void PrepareSharingNtlm (WebConnection cnc, HttpWebRequest request)
93 if (!cnc.NtlmAuthenticated)
96 bool needs_reset = false;
97 NetworkCredential cnc_cred = cnc.NtlmCredential;
99 bool isProxy = (request.Proxy != null && !request.Proxy.IsBypassed (request.RequestUri));
100 ICredentials req_icreds = (!isProxy) ? request.Credentials : request.Proxy.Credentials;
101 NetworkCredential req_cred = (req_icreds != null) ? req_icreds.GetCredential (request.RequestUri, "NTLM") : null;
103 if (cnc_cred == null || req_cred == null ||
104 cnc_cred.Domain != req_cred.Domain || cnc_cred.UserName != req_cred.UserName ||
105 cnc_cred.Password != req_cred.Password) {
110 bool req_sharing = request.UnsafeAuthenticatedConnectionSharing;
111 bool cnc_sharing = cnc.UnsafeAuthenticatedConnectionSharing;
112 needs_reset = (req_sharing == false || req_sharing != cnc_sharing);
115 cnc.Close (false); // closes the authenticated connection
120 ConnectionState FindIdleConnection ()
122 foreach (var cnc in connections) {
123 if (cnc.Busy || cnc.Connection == null)
126 connections.Remove (cnc);
127 connections.AddFirst (cnc);
134 WebConnection CreateOrReuseConnection (HttpWebRequest request, out bool created)
136 var cnc = FindIdleConnection ();
139 PrepareSharingNtlm (cnc.Connection, request);
140 return cnc.Connection;
143 if (sPoint.ConnectionLimit > connections.Count) {
145 cnc = new ConnectionState (this);
146 connections.AddFirst (cnc);
147 return cnc.Connection;
151 cnc = connections.Last.Value;
152 connections.Remove (cnc);
153 connections.AddFirst (cnc);
154 return cnc.Connection;
161 internal Queue Queue {
162 get { return queue; }
165 internal bool TryRecycle (TimeSpan maxIdleTime, ref DateTime idleSince)
167 var now = DateTime.UtcNow;
171 List<WebConnection> connectionsToClose = null;
175 idleSince = DateTime.MinValue;
180 for (var node = connections.First; node != null; node = node.Next) {
181 var cnc = node.Value;
183 if (cnc.Connection == null) {
184 connections.Remove (node);
185 OnConnectionClosed ();
193 if (count <= sPoint.ConnectionLimit && now - cnc.IdleSince < maxIdleTime) {
194 if (cnc.IdleSince > idleSince)
195 idleSince = cnc.IdleSince;
200 * Do not call WebConnection.Close() while holding the ServicePoint lock
201 * as this could deadlock when attempting to take the WebConnection lock.
205 if (connectionsToClose == null)
206 connectionsToClose = new List<WebConnection> ();
207 connectionsToClose.Add (cnc.Connection);
208 cnc.Connection = null;
211 recycled = connections.Count == 0;
214 // Did we find anything that can be closed?
215 if (connectionsToClose == null)
218 // Ok, let's get rid of these!
219 foreach (var cnc in connectionsToClose)
222 // Re-take the lock, then remove them from the connection list.
226 class ConnectionState : IWebConnectionState {
227 public WebConnection Connection;
229 public WebConnectionGroup Group {
234 public ServicePoint ServicePoint {
235 get { return Group.sPoint; }
245 public DateTime IdleSince {
246 get { return idleSince; }
249 public bool TrySetBusy ()
251 lock (ServicePoint) {
255 idleSince = DateTime.UtcNow + TimeSpan.FromDays (3650);
260 public void SetIdle ()
262 lock (ServicePoint) {
264 idleSince = DateTime.UtcNow;
268 public ConnectionState (WebConnectionGroup group)
271 idleSince = DateTime.UtcNow;
272 Connection = new WebConnection (this, group.sPoint);