Non racchiudere HttpClient in un using ── guida pratica alla comunicazione HTTP nelle app aziendali C# (pattern di creazione, timeout, retry)

· · CSharp, .NET, HttpClient, IHttpClientFactory, Rete, async/await, Sviluppo Windows, Consulenza tecnica

«Dal primo pomeriggio in poi la connessione alle API esterne inizia a fallire con SocketException» «Abbiamo cambiato server di destinazione, ma l’app continua a connettersi a quello vecchio» — con HttpClient in C#, chiamare GetAsync è semplice, ma se si sbaglia come creare e mantenere l’istanza, si finisce per introdurre proprio questo tipo di difetto: funziona durante lo sviluppo, ma si rompe una volta in produzione.

In questo articolo, partendo dallo scenario in cui un’app aziendale Windows chiama API Web esterne o servizi interni, esaminiamo nell’ordine in cui più spesso si presentano i dubbi pratici: il pattern corretto di creazione di HttpClient, la progettazione del timeout, il retry, la gestione degli errori e infine le insidie specifiche dell’ambiente Windows.

1. Prima la conclusione (tabella decisionale)

Il modo in cui gestire HttpClient cambia a seconda della struttura dell’applicazione. Riassumiamo prima tutto in una tabella decisionale.

Tipo di applicazione Pattern consigliato Motivo
Tool a riga di comando (.NET) che termina in pochi secondi/minuti Una singola istanza HttpClient static/singleton In un processo di breve durata il problema del cambio DNS è di fatto trascurabile
App a lunga esecuzione / servizio Windows (.NET, senza DI) HttpClient static + impostazione SocketsHttpHandler.PooledConnectionLifetime Risolve sia l’esaurimento dei socket sia il problema del cambio DNS
App che usa Generic Host / DI (.NET) IHttpClientFactory (AddHttpClient) Si può delegare alla factory il pooling e la sostituzione degli handler. Con client named/typed si possono separare le impostazioni per ciascuna destinazione
App .NET Framework Introdurre IHttpClientFactory tramite il pacchetto Microsoft.Extensions.Http In .NET Framework la creazione manuale porta facilmente all’esaurimento delle porte, e anche la documentazione ufficiale consiglia l’uso della factory1
Quando proxy, cookie o impostazioni dei certificati differiscono per ogni destinazione Separare HttpClient per ciascuna configurazione (non riutilizzarlo) Le impostazioni di connessione dell’handler non possono essere modificate dopo l’invio della prima richiesta2

Detto questo, ecco subito le conclusioni.

  • Non bisogna creare new HttpClient() a ogni richiesta e distruggerlo con using. HttpClient mantiene internamente un pool di connessioni ed è progettato per essere riutilizzato. Se lo si crea e distrugge ogni volta, sotto carico elevato si esauriscono i socket disponibili e si verifica una SocketException2.
  • Ma non basta nemmeno renderlo semplicemente static. HttpClient risolve il DNS solo al momento della creazione della connessione, quindi anche se l’indirizzo IP di destinazione cambia continua a usare la vecchia connessione. La soluzione ufficialmente consigliata è limitare la durata della connessione con SocketsHttpHandler.PooledConnectionLifetime1.
  • Se si usa Generic Host o la DI, conviene affidarsi a IHttpClientFactory. La factory mantiene un pool di handler e li sostituisce ogni 2 minuti per impostazione predefinita, gestendo così sia l’esaurimento dei socket sia il cambio DNS3.
  • Il timeout predefinito è di 100 secondi. Per un’app aziendale, dal punto di vista dell’utente attendere 100 secondi equivale a un blocco totale, quindi è necessario impostarlo esplicitamente in base ai requisiti di ciascuna destinazione.
  • Per il retry non conviene scrivere codice proprio: meglio partire dall’handler standard di Microsoft.Extensions.Http.Resilience. Permette di introdurre retry, circuit breaker e timeout con un insieme di valori predefiniti collaudati, evitando fin dalla progettazione incidenti tipici dei retry loop scritti a mano, come reinviare incondizionatamente una POST fallita e causare una doppia registrazione4.

2. Perché “crearlo ogni volta con using” non va bene ── l’esaurimento dei socket

Poiché HttpClient implementa IDisposable, un codice come il seguente sembra a prima vista corretto.

// Anti-pattern: crea e distrugge a ogni richiesta
public async Task<string> GetDataAsync(string url)
{
    using var client = new HttpClient();
    return await client.GetStringAsync(url);
}

Il problema è che, anche dopo Dispose, a livello di sistema operativo il socket non viene rilasciato immediatamente. Per le specifiche TCP, il lato che chiude la connessione resta per un po’ in stato TIME_WAIT. Finché la frequenza delle chiamate è bassa non succede nulla, ma quando il carico aumenta i socket non rilasciati si accumulano, e a un certo punto all’improvviso non è più possibile connettersi a causa di una SocketException2.

Ciò che rende questo difetto insidioso è che praticamente non si riproduce durante lo sviluppo o il test. Si manifesta solo nelle fasce orarie di punta in produzione, oppure solo durante i batch di fine mese. Per indagare, quando si verifica il fenomeno è utile controllare il numero di socket in TIME_WAIT con il comando seguente, per farsi un’idea della causa.

# Conta il numero di socket in TIME_WAIT per ciascuna destinazione
netstat -ano | Select-String "TIME_WAIT" | Measure-Object

Da notare che un HttpClient ricevuto tramite IHttpClientFactory non rientra in questo discorso. Un client creato dalla factory non distrugge l’handler (l’entità reale del pool di connessioni) nemmeno con Dispose, quindi è sicuro racchiuderlo in un using3.

3. Renderlo static non risolve tutto ── il problema del cambio DNS

Rendere HttpClient static come contromisura all’esaurimento dei socket è la direzione giusta, ma da sola lascia un altro problema irrisolto. HttpClient risolve il DNS solo al momento della creazione della connessione e non tiene conto del TTL dei record DNS2. Finché la connessione resta viva nel pool, anche se l’indirizzo IP di destinazione cambia, l’app continua a connettersi al vecchio IP.

Il guasto del tipo «abbiamo cambiato il DNS con un failover, ma l’app ha continuato a puntare al vecchio server fino al riavvio» è causato proprio da questo meccanismo. La soluzione raccomandata dalle linee guida ufficiali è limitare la durata delle connessioni con SocketsHttpHandler.PooledConnectionLifetime1.

// Pattern consigliato per .NET (Core) / .NET 5+:
// facendo ricreare periodicamente la connessione si segue il cambio DNS
private static readonly HttpClient SharedClient = new(new SocketsHttpHandler
{
    PooledConnectionLifetime = TimeSpan.FromMinutes(2)
});

Una connessione che ha raggiunto la fine della propria durata viene ricreata alla richiesta successiva, e in quel momento il DNS viene risolto di nuovo. Il valore va scelto in base a quanto rapidamente si vuole seguire un eventuale cambio DNS. Nell’esempio della documentazione ufficiale si usano 2 minuti, ma per un sistema interno la cui destinazione cambia raramente non c’è problema a usare un valore più lungo1.

Da notare che SocketsHttpHandler è un’implementazione disponibile da .NET Core 2.1 in poi e non è utilizzabile in .NET Framework. Per .NET Framework, usare IHttpClientFactory come descritto nel capitolo seguente1.

4. Se si usa la DI: IHttpClientFactory

Nelle app che usano Generic Host o un contenitore DI, la prima scelta è IHttpClientFactory (AddHttpClient). Per una spiegazione di Generic Host in sé, vedi «Che cos’è il Generic Host»; per la sua introduzione in un’app desktop, vedi «Usare Generic Host + BackgroundService in un’app desktop».

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

// Client named: separa le impostazioni per ciascuna destinazione
builder.Services.AddHttpClient("OrderApi", client =>
{
    client.BaseAddress = new Uri("https://order.example.co.jp/");
    client.Timeout = TimeSpan.FromSeconds(10);
});

Ci sono tre punti da tenere a mente sul comportamento della factory.

  • Gli handler vengono messi in pool e sostituiti per impostazione predefinita ogni 2 minuti. A ogni chiamata di CreateClient viene restituito un nuovo HttpClient, ma l’handler sottostante (il pool di connessioni) è condiviso, quindi non si verifica esaurimento dei socket, e la sostituzione periodica permette di seguire anche il cambio DNS3.
  • Un HttpClient creato dalla factory è pensato per un uso di breve durata. Se si conserva l’istanza ricevuta in un campo singleton, non potrà più partecipare alla sostituzione dell’handler e smetterà di seguire i cambi DNS. Per lo stesso motivo, evitare di iniettare un typed client in un servizio singleton3.
  • Occorre fare attenzione nelle app che dipendono dai cookie. Poiché l’handler viene messo in pool, il CookieContainer finisce per essere condiviso in modo non intenzionale. Se si usano i cookie, la documentazione ufficiale consiglia di evitare la factory oppure di disabilitare la gestione dei cookie e impostare le intestazioni manualmente1.

Per l’acquisizione dei token quando si chiamano API autenticate (ad esempio API protette con Microsoft Entra ID), l’argomento è trattato in «Integrare l’autenticazione Entra ID in un’app WinForms/WPF», a cui si rimanda.

5. Progettazione del timeout ── i 100 secondi predefiniti sono troppi per un’app aziendale

Il valore predefinito di HttpClient.Timeout è 100 secondi5. In un’app aziendale che chiama un’API nel prolungamento di un’operazione a schermo, far attendere 100 secondi equivale a un blocco, quindi conviene impostarlo esplicitamente per ciascuna destinazione.

// Timeout predefinito per l'intero client
client.Timeout = TimeSpan.FromSeconds(10);

// Per accorciare/allungare il timeout solo per una richiesta specifica, usare CancellationTokenSource
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3));
HttpResponseMessage response = await client.GetAsync(url, cts.Token);

I punti di attenzione dal punto di vista progettuale sono i seguenti.

  • L’eccezione generata in caso di timeout è TaskCanceledException. Da .NET 5 in poi, per un timeout dovuto a HttpClient.Timeout l’eccezione interna contiene una TimeoutException5. Tuttavia, per un timeout impostato con un proprio CancellationTokenSource come nell’esempio sopra, non viene allegata alcuna eccezione interna. Il modo affidabile per distinguere un timeout da un’annullamento voluto dall’utente non è controllare l’eccezione interna, ma verificare se il token passato dal chiamante è già stato annullato (vedi l’esempio di codice al capitolo 7). Fai attenzione: se nel blocco catch si intercetta solo HttpRequestException, il timeout sfugge.
  • Timeout è un limite sull’intera richiesta. Se si vuole limitare in modo breve solo la fase di stabilimento della connessione, si può usare in combinazione SocketsHttpHandler.ConnectTimeout. Un requisito come «se il server è down vogliamo rinunciare dopo 3 secondi, ma in condizioni normali vogliamo attendere fino a 60 secondi per risposte di grandi dimensioni» si può esprimere combinando questi due valori2.
  • Per il download di file di grandi dimensioni, evitare il buffering predefinito. Per impostazione predefinita HttpClient carica l’intera risposta in memoria, quindi per download superiori a qualche decina di MB conviene specificare HttpCompletionOption.ResponseHeadersRead ed elaborare la risposta come stream2.

Per la configurazione che estrae i valori di timeout in appsettings.json e li fa variare per ambiente, si può usare direttamente la tabella decisionale di «Oltre appsettings.json — gestione pratica della configurazione nelle app aziendali Windows».

Da notare che, quando si chiama una comunicazione HTTP da WinForms/WPF, bloccare con .Result o .Wait() provoca un deadlock del thread UI. Questa insidia classica è spiegata in dettaglio in «Tabella decisionale pratica per async/await in C#», che consigliamo di leggere prima di scrivere codice di comunicazione.

6. Retry ── usare l’handler standard di resilience invece di un loop scritto a mano

La rete è soggetta a fallimenti temporanei, quindi le chiamate a API esterne richiedono un meccanismo di retry. Tuttavia, un retry scritto a mano con un ciclo for e Task.Delay richiede di implementare correttamente da soli tutti i punti seguenti, e non ne vale la pena.

  • Distinguere i fallimenti per cui è opportuno un retry (timeout, HTTP 408/429/5xx) da quelli per cui è inutile (HTTP 400/401/404)
  • Escludere i metodi HTTP per cui rieseguire la richiesta causa problemi (ad esempio la doppia esecuzione di una registrazione tramite POST)
  • Backoff esponenziale e jitter (la variazione casuale che evita che tutti i client rispediscano contemporaneamente e mandino di nuovo giù il server)
  • Un circuit breaker che interrompe del tutto le chiamate quando il guasto persiste

L’handler standard del pacchetto Microsoft.Extensions.Http.Resilience fornisce tutto questo insieme con valori predefiniti collaudati4.

builder.Services.AddHttpClient("OrderApi", client =>
    {
        client.BaseAddress = new Uri("https://order.example.co.jp/");
    })
    .AddStandardResilienceHandler(); // Insieme standard di retry + circuit breaker + timeout

I valori predefiniti dell’handler standard prevedono un timeout complessivo della richiesta di 30 secondi, un massimo di 3 tentativi di retry con backoff esponenziale (ritardo iniziale di 2 secondi, con jitter), un timeout di 10 secondi per ogni singolo tentativo, e trattano HTTP 408/429/5xx e HttpRequestException come errori temporanei4.

Un unico punto a cui prestare attenzione nei valori predefiniti è che l’handler standard, per impostazione predefinita, esegue il retry di tutti i metodi HTTP. Per le API in cui una doppia esecuzione di una POST di registrazione sarebbe un problema, disabilita il retry per i metodi non sicuri4.

httpClientBuilder.AddStandardResilienceHandler(options =>
{
    // Disabilita la riesecuzione per POST/PUT/DELETE ecc.
    options.Retry.DisableForUnsafeHttpMethods();
});

Da notare che il retry risolve soltanto i fallimenti temporanei. Sintomi come una connessione stabilita ma senza flusso di dati, o una risposta estremamente lenta, sono spesso problemi a livello TCP, e per isolarli può essere utile il metodo trattato in «Ritrasmissioni TCP e RFC1323 ── indagine su un’interruzione di comunicazione con una telecamera industriale».

7. Gestione degli errori ── come trattare i codici di stato

HttpClient non genera un’eccezione per un fallimento «a livello HTTP la risposta è comunque arrivata», come un 404 o un 500. Genera un’eccezione solo quando non si ottiene affatto una risposta, ad esempio per un fallimento di connessione, un timeout o una cancellazione. È importante scrivere il codice tenendo distinte queste due categorie.

try
{
    using HttpResponseMessage response = await client.GetAsync(url, ct);

    if (!response.IsSuccessStatusCode)
    {
        // La risposta è arrivata ma indica un fallimento: si può ramificare in base al codice di stato
        if (response.StatusCode == HttpStatusCode.NotFound)
        {
            return null; // Esempio: trattare "non esiste" come caso normale
        }
        response.EnsureSuccessStatusCode(); // Per tutti gli altri casi, genera HttpRequestException
    }

    return await response.Content.ReadFromJsonAsync<Order>(ct);
}
catch (HttpRequestException ex)
{
    // Fallimento di connessione, oppure stato di fallimento rilevato da EnsureSuccessStatusCode.
    // Da .NET 5 in poi è possibile leggere il codice di stato del fallimento tramite ex.StatusCode
    logger.LogError(ex, "Chiamata all'API degli ordini non riuscita. StatusCode={StatusCode}", ex.StatusCode);
    throw;
}
catch (TaskCanceledException) when (ct.IsCancellationRequested)
{
    // Cancellazione tramite il token passato dal chiamante (ad esempio la chiusura della finestra).
    // Non è un errore, quindi va propagata così com'è senza inquinare i log
    throw;
}
catch (TaskCanceledException ex)
{
    // Scadenza di HttpClient.Timeout, oppure di un CTS di timeout personalizzato
    logger.LogError(ex, "Timeout durante la chiamata all'API degli ordini");
    throw;
}

Decisioni come «trattare un 404 come eccezione oppure come null» dipendono dalla semantica dell’API di destinazione. Se si trasforma tutto in eccezione con un semplice EnsureSuccessStatusCode, il blocco catch del chiamante finisce per gonfiarsi. Il criterio per distinguere ciò che va trasformato in eccezione da ciò che va espresso tramite valore di ritorno è lo stesso descritto in «La pratica di catch, log e gestione degli errori delle eccezioni», applicabile senza modifiche.

Per l’invio e la ricezione di JSON, usando GetFromJsonAsync / PostAsJsonAsync / ReadFromJsonAsync di System.Net.Http.Json si evita di dover scrivere manualmente la serializzazione tramite stringhe.

8. Insidie specifiche delle app aziendali Windows

Per concludere, riassumiamo le insidie in cui più spesso ci si imbatte nella pratica su ambiente Windows.

  • Il rilevamento automatico del proxy rallenta la prima richiesta. Per impostazione predefinita, su Windows HttpClient usa le impostazioni di proxy del sistema operativo (incluso il rilevamento automatico). Se si sa che il proxy non serve, disabilitarlo con HttpClientHandler.UseProxy = false elimina l’attesa del rilevamento2. Al contrario, negli ambienti in cui il proxy aziendale è obbligatorio, specificarlo esplicitamente con WebProxy evita il problema del «funziona sulla macchina di sviluppo ma non sul server».
  • Le impostazioni del proxy vanno completate prima della prima richiesta. Le impostazioni di connessione dell’handler non hanno effetto se modificate dopo l’invio di una richiesta2.
  • Il valore predefinito del numero di connessioni simultanee è opposto tra .NET e .NET Framework. In .NET (SocketsHttpHandler) il numero di connessioni simultanee HTTP/1.1 è illimitato per impostazione predefinita, quindi con un gran numero di richieste parallele le connessioni continuano ad aumentare e si può incontrare il limite del firewall o del server. Nei processi ad alta concorrenza, imposta un limite superiore con MaxConnectionsPerServer2. In .NET Framework, invece, il valore predefinito di ServicePointManager.DefaultConnectionLimit è basso, pari a 2 (in ambienti non ASP.NET), e si verifica il problema opposto: le richieste parallele restano in attesa internamente e vanno in timeout. Se in .NET Framework serve aumentare la concorrenza, alza esplicitamente questo limite6.
  • Se si chiama da un servizio Windows, il contesto di proxy e TLS è diverso da quello dell’utente. L’account con cui viene eseguito il servizio non dispone delle impostazioni di proxy né delle credenziali dell’utente interattivo, il che è una causa classica di difetti di comunicazione del tipo «funziona con l’utente interattivo ma non nel servizio». Per il contesto di esecuzione specifico dei servizi, vedi «Come creare e gestire un servizio Windows».
  • Non incorporare nel codice l’URL di destinazione o le chiavi API. Per il cambio di destinazione, affidati a un file di configurazione («Gestione pratica della configurazione»); per la memorizzazione delle informazioni riservate, segui l’approccio descritto in «Evitare le impostazioni in chiaro con DPAPI».

Nella pratica con HttpClient, la qualità dipende più da «come lo si mantiene» che da «come lo si chiama». Crearlo a ogni richiesta porta all’esaurimento dei socket, mentre renderlo ingenuamente static porta a non seguire i cambi DNS: nessuno dei due problemi è visibile durante lo sviluppo. In .NET la risposta è un’istanza condivisa con PooledConnectionLifetime oppure IHttpClientFactory; in .NET Framework è l’introduzione di IHttpClientFactory. A questo si aggiunge impostare esplicitamente il timeout per ciascuna destinazione e affidare il retry all’handler standard di resilience: solo arrivando fin qui si ottiene un’app aziendale progettata assumendo che «la rete a volte fallisce».

La revisione della parte di comunicazione di un’app esistente (comunicazioni che si interrompono solo nelle fasce orarie di punta, riorganizzazione della progettazione del timeout, nuova implementazione dell’integrazione con API esterne) richiede spesso una valutazione basata sul codice reale e sull’ambiente operativo: se hai dubbi, contattaci pure.

Articoli correlati

Aree di consulenza correlate

KomuraSoft LLC si occupa dello sviluppo di app aziendali Windows che includono l’integrazione con API esterne, e della consulenza tecnica per l’indagine e la definizione della strategia di correzione dei difetti di comunicazione di app esistenti (esaurimento dei socket, timeout, fallimenti di connessione intermittenti).

Riferimenti

  1. Microsoft Learn, Guidelines for using HttpClient. Sul fatto che in .NET Core/.NET 5+ conviene usare un client a lunga durata con PooledConnectionLifetime impostato oppure un client di breve durata creato da IHttpClientFactory, che in .NET Framework è consigliato l’uso di IHttpClientFactory, e che nelle app che usano i cookie è meglio evitare IHttpClientFactory per via della condivisione di CookieContainer 2 3 4 5 6

  2. Microsoft Learn, HttpClient Class. Sul fatto che creare un’istanza a ogni richiesta provoca esaurimento dei socket e SocketException, che il DNS viene risolto solo al momento della creazione della connessione senza tener conto del TTL, che le impostazioni di connessione dell’handler non possono essere modificate dopo la prima richiesta, che il numero di connessioni simultanee HTTP/1.1 è illimitato per impostazione predefinita, sulla raccomandazione di usare lo streaming per i download di grandi dimensioni e sul comportamento predefinito del proxy e la sua disabilitazione tramite UseProxy 2 3 4 5 6 7 8 9

  3. Microsoft Learn, IHttpClientFactory with .NET. Sul fatto che la durata predefinita dell’handler è di 2 minuti, che un HttpClient creato dalla factory è pensato per un uso di breve durata, che il Dispose di un client creato dalla factory non distrugge l’handler, e che iniettare un typed client in un servizio singleton impedisce di seguire i cambi DNS.  2 3 4

  4. Microsoft Learn, Build resilient HTTP apps: Key development patterns. Sulle cinque strategie configurate da AddStandardResilienceHandler (rate limiter / timeout complessivo di 30 secondi / retry con backoff esponenziale fino a un massimo di 3 tentativi / circuit breaker / timeout per tentativo di 10 secondi), sui codici di stato e le eccezioni interessati (408/429/5xx) e sulla disabilitazione del retry per POST e simili tramite DisableForUnsafeHttpMethods 2 3 4

  5. Microsoft Learn, HttpClient.Timeout Property. Sul fatto che il valore predefinito è 100 secondi e che, da .NET 5 in poi, in caso di timeout viene lanciata una TaskCanceledException con una TimeoutException come eccezione interna.  2

  6. Microsoft Learn, ServicePointManager.DefaultConnectionLimit Property. Sul fatto che il numero predefinito di connessioni simultanee è 10 nelle applicazioni ospitate da ASP.NET e 2 in tutti gli altri casi (ad esempio le app desktop). 

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

HttpClient implementa IDisposable: perché allora non va racchiuso in un using?
Il problema è l'uso 'crea e distruggi a ogni richiesta'. HttpClient mantiene internamente un pool di connessioni ed è progettato per essere riutilizzato per tutta la durata dell'applicazione. Se lo si crea e distrugge ogni volta, anche dopo il Dispose il socket resta per un po' in stato TIME_WAIT a livello di sistema operativo, e sotto carico elevato si esauriscono i socket disponibili, causando una SocketException. Distruggilo una sola volta alla chiusura dell'applicazione, oppure ottienilo tramite IHttpClientFactory. Un HttpClient ricevuto dalla factory può invece essere tranquillamente racchiuso in un using, perché il Dispose non distrugge l'handler sottostante.
È necessario migrare da WebClient o HttpWebRequest di .NET Framework?
Per il nuovo codice è consigliabile unificare tutto su HttpClient. WebClient e HttpWebRequest sono API più datate mantenute per compatibilità, e anche Microsoft consiglia di usare HttpClient per i nuovi sviluppi. Non è necessario sostituire in blocco il codice esistente, ma man mano che si interviene sulla parte di comunicazione conviene spostarsi gradualmente su HttpClient: ne beneficiano il controllo del timeout, il supporto ad async e la testabilità.
Quante volte e con quale intervallo bisogna eseguire i retry?
Piuttosto che deciderlo da soli, è più sicuro partire dai valori predefiniti dell'handler standard di Microsoft.Extensions.Http.Resilience (massimo 3 tentativi, backoff esponenziale con jitter, ritardo iniziale di 2 secondi). Più che il numero di tentativi, ciò che conta è distinguere se una richiesta può essere ripetuta in sicurezza: per operazioni come una POST, che se rieseguita può causare una doppia registrazione, occorre disabilitare il retry per impostazione predefinita oppure abilitarlo solo dopo aver garantito l'idempotenza lato server (la proprietà per cui ricevere due volte la stessa richiesta non cambia il risultato).
Perché in un ambiente con proxy aziendale solo la prima richiesta è estremamente lenta?
Con le impostazioni predefinite di Windows, HttpClient tenta il rilevamento automatico del proxy, e questo può far sì che la prima connessione richieda del tempo per il rilevamento. Negli ambienti in cui si sa che il proxy non serve (ad esempio comunicazioni interne tra server), impostare UseProxy a false su HttpClientHandler disabilita il rilevamento automatico e risolve il problema. Al contrario, negli ambienti aziendali in cui il proxy è obbligatorio, è più stabile specificarlo esplicitamente con WebProxy invece di affidarsi al rilevamento automatico.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog