web-dev-qa-db-fra.com

HttpClient avec un délai d'expiration infini génère une exception de délai d'expiration

Mon HttpClient utilise l'authentification Digest pour se connecter au serveur et attend des requêtes de recherche en réponse. Ces requêtes de recherche peuvent arriver à n'importe quel moment, le client doit donc laisser la connexion ouverte à tout moment.

La connexion est établie à l'aide du code suivant:

public static async void ListenForSearchQueries(int resourceId)
{
    var url = $"xxx/yyy/{resourceId}/waitForSearchRequest?token=abc";

    var httpHandler = new HttpClientHandler { PreAuthenticate = true };

    using (var digestAuthMessageHandler = new DigestAuthMessageHandler(httpHandler, "user", "password"))
    using (var client = new HttpClient(digestAuthMessageHandler))
    {
        client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);

        var request = new HttpRequestMessage(HttpMethod.Get, url);

        var tokenSource = new CancellationTokenSource();
            tokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout.Infinite));

        using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, tokenSource.Token))
        {
            Console.WriteLine("\nResponse code: " + response.StatusCode);

            using (var body = await response.Content.ReadAsStreamAsync())
            using (var reader = new StreamReader(body))
                while (!reader.EndOfStream)
                    Console.WriteLine(reader.ReadLine());
         }
    }
}

Voici comment la méthode est utilisée dans la méthode Main d'une application console.

private static void Main(string[] args)
{
   const int serviceId = 128;
   .
   .
   .
   ListenForSearchQueries(resourceId);
   Console.ReadKey();
}

Voici à quoi ressemble la sortie sur la fenêtre de la console:

Response code: OK
--searchRequestBoundary

Même si le délai d'attente du client est défini sur infini, la connexion expire au bout de cinq minutes environ (ce qui n'est pas le délai d'attente par défaut de HttpClient) après la première sortie, en lançant l'exception suivante.

System.IO.IOException occurred
  HResult=0x80131620
  Message=The read operation failed, see inner exception.
  Source=System.Net.Http
  StackTrace:
   at System.Net.Http.HttpClientHandler.WebExceptionWrapperStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.Net.Http.DelegatingStream.Read(Byte[] buffer, Int32 offset, Int32 count)
   at System.IO.StreamReader.ReadBuffer()
   at System.IO.StreamReader.get_EndOfStream()
   at ConsoleTester.Program.<ListenSearchQueriesDigestAuthMessageHandler>d__10.MoveNext() in C:\Users\xyz\ProjName\ConsoleTester\Program.cs:line 270

Inner Exception 1:
WebException: The operation has timed out.

DelegateHandler utilisé pour l'authentification est une adaptation approximative de this code (voir la section sources).

Pourquoi le client expire-t-il et comment puis-je éviter cela?

Mon but ultime est de faire un appel et d’attendre indéfiniment une réponse. Quand une réponse arrive, je ne veux pas que la connexion se ferme, car davantage de réponses pourraient arriver dans le futur. Malheureusement, je ne peux rien changer du côté du serveur.

6
Ali Zahid

Bien que la valeur par défaut de Stream.CanTimeoutsoit false , le renvoi d'un flux via la response.Content.ReadAsStreamAsync() donne un flux dans lequel la propriété CanTimeout renvoie true.

Le délai d'attente de lecture et d'écriture par défaut pour ce flux est de 5 minutes . C'est après cinq minutes d'inactivité, le flux lèvera une exception. Très semblable à l'exception indiquée dans la question.

Pour modifier ce comportement, ReadTimeout et/ou la propriété WriteTimeout du flux peuvent être ajustés.

Vous trouverez ci-dessous la version modifiée de la méthode ListenForSearchQueries qui modifie ReadTimeout en Infinite.

public static async void ListenForSearchQueries(int resourceId)
{
    var url = $"xxx/yyy/{resourceId}/waitForSearchRequest?token=abc";

    var httpHandler = new HttpClientHandler { PreAuthenticate = true };

    using (var digestAuthMessageHandler = new DigestAuthMessageHandler(httpHandler, "user", "password"))
    using (var client = new HttpClient(digestAuthMessageHandler))
    {
        client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite);

        var request = new HttpRequestMessage(HttpMethod.Get, url);

        var tokenSource = new CancellationTokenSource();
            tokenSource.CancelAfter(TimeSpan.FromMilliseconds(Timeout.Infinite));

        using (var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, tokenSource.Token))
        {
            Console.WriteLine("\nResponse code: " + response.StatusCode);

            using (var body = await response.Content.ReadAsStreamAsync())
            {
                body.ReadTimeout = Timeout.Infinite;

                using (var reader = new StreamReader(body))
                    while (!reader.EndOfStream)
                        Console.WriteLine(reader.ReadLine());
            }
         }
    }
}

Cela corrigeait l'exception qui était en train d'être lancée par le flux mais qui semblait être lancée par le HttpClient.

4
Ali Zahid

Faire en sorte que la méthode retourne une Task

public static async Task ListenForSearchQueries(int resourceId) {
    //...code removed for brevity
}

Mettez à jour la méthode principale de la console sur Wait sur la Task à compléter.

public static void Main(string[] args) {
   const int serviceId = 128;
   .
   .
   .
   ListenForSearchQueries(resourceId).Wait();
   Console.ReadKey();
}
2
Nkosi

J'ai résolu ce problème de la manière suivante:

var stream = await response.Content.ReadAsStreamAsync();
    while (b == 1)
    { 
        var bytes = new byte[1];
        try
        {
            var bytesread = await stream.ReadAsync(bytes, 0, 1);
            if (bytesread > 0)
            {
                text = Encoding.UTF8.GetString(bytes);

                Console.WriteLine(text);
                using (System.IO.StreamWriter escritor = new System.IO.StreamWriter(@"C:\orden\ConSegu.txt", true))
                {
                    if (ctext == 100)
                    {
                        escritor.WriteLine(text);
                        ctext = 0;
                    }
                    escritor.Write(text);
                }
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("error");
            Console.WriteLine(ex.Message);
        }
    }

de cette manière, je reçois octet pour octet la réponse et je l'enregistre dans un txt plus tard, je lis le txt et je l'efface à nouveau. pour le moment, c’est la solution que j’ai trouvée pour recevoir les notifications que le serveur m’a envoyées à partir de la connexion HTTP persistante.