Je rencontre des difficultés à déterminer s'il existe un moyen de gérer les problèmes de connectivité potentiels lors de l'utilisation de la classe HttpWebRequest de .NET pour appeler un serveur distant (en particulier un service web REST). D'après mes investigations, le comportement de la classe WebClient est le même, ce qui est quelque peu attendu puisqu'elle semble offrir simplement une interface plus simple à HttpWebRequest.
À des fins de simulation, j'ai écrit un serveur HTTP très simple qui ne se comporte pas conformément au RFC HTTP 1.1. Il accepte une connexion client, envoie ensuite des en-têtes HTTP 1.1 appropriés et une charge utile "Hello World!" au client, puis ferme le socket. Le thread acceptant les connexions client du côté serveur ressemble à ceci :
private const string m_defaultResponse = "Hello World!";
private void Listen()
{
while (true)
{
using (TcpClient clientConnection = m_listener.AcceptTcpClient())
{
NetworkStream stream = clientConnection.GetStream();
StringBuilder httpData = new StringBuilder("HTTP/1.1 200 OK\r\nServer: ivy\r\nContent-Type: text/html\r\n");
httpData.AppendFormat("Content-Length: {0}\r\n\r\n", m_defaultResponse.Length);
httpData.AppendFormat(m_defaultResponse);
Thread.Sleep(3000); // Sleep to simulate latency
stream.Write(Encoding.ASCII.GetBytes(httpData.ToString()), 0, httpData.Length);
stream.Close();
clientConnection.Close();
}
}
}
Étant donné que le RFC HTTP 1.1 indique que HTTP 1.1 conserve par défaut les connexions actives et qu'un serveur doit envoyer un en-tête de réponse "Connection: Close" s'il souhaite fermer une connexion, ce comportement est inattendu du côté client. Le client utilise HttpWebRequest de la manière suivante :
private static void SendRequest(object _state)
{
WebResponse resp = null;
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://192.168.0.32:7070/asdasd");
request.Timeout = 50 * 1000;
DateTime requestStart = DateTime.Now;
resp = request.GetResponse();
TimeSpan requestDuration = DateTime.Now - requestStart;
Console.WriteLine("OK. La requête a pris : " + (int)requestDuration.TotalMilliseconds + " ms.");
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.Timeout)
{
Console.WriteLine("Un dépassement de délai s'est produit");
}
else
{
Console.WriteLine(ex);
}
}
finally
{
if (resp != null)
{
resp.Close();
}
((ManualResetEvent)_state).Set();
}
}
La méthode ci-dessus est mise en file d'attente via ThreadPool.QueueUserWorkItem(waitCallback, stateObject). Le ManualResetEvent est utilisé pour contrôler le comportement de mise en file d'attente afin que l'ensemble du pool de threads ne soit pas saturé de tâches en attente (puisque HttpWebRequest utilise implicitement des threads de travail car il fonctionne de manière asynchrone en interne pour mettre en œuvre la fonctionnalité de délai d'attente).
Le problème avec tout cela est qu'une fois que toutes les connexions du ServicePoint sous-jacent de HttpWebRequest sont "utilisées" (c'est-à-dire fermées par le serveur distant), aucune nouvelle connexion n'est ouverte. Il n'est pas non plus important que le ConnectionLeaseTimeout du ServicePoint soit réglé sur une valeur faible (10 secondes). Une fois que le système se retrouve dans cet état, il ne fonctionnera plus correctement car il ne se reconnecte pas automatiquement et toutes les HttpWebRequests ultérieures expireront. Maintenant, la vraie question est de savoir s'il existe un moyen de résoudre ce problème en détruisant d'une certaine manière un ServicePoint dans certaines conditions ou en fermant les connexions sous-jacentes (je n'ai pas eu de chance avec ServicePoint.CloseConnectionGroup() pour l'instant, la méthode est également assez peu documentée en termes d'utilisation correcte).
Quelqu'un a-t-il une idée de comment je pourrais aborder ce problème ?