Bagikan melalui


Masalah penggunaan umum IHttpClientFactory

Dalam artikel ini, Anda akan mempelajari beberapa masalah paling umum yang dapat Anda temui saat menggunakan IHttpClientFactory untuk membuat HttpClient instans.

IHttpClientFactory adalah cara mudah untuk menyiapkan beberapa HttpClient konfigurasi dalam kontainer DI, mengonfigurasi pengelogan, menyiapkan strategi ketahanan, dan banyak lagi. IHttpClientFactory juga merangkum manajemen HttpClient masa pakai instans dan HttpMessageHandler , untuk mencegah masalah seperti kelelahan soket dan kehilangan perubahan DNS. Untuk gambaran umum tentang cara menggunakan IHttpClientFactory di aplikasi .NET Anda, lihat IHttpClientFactory dengan .NET.

Karena sifat IHttpClientFactory integrasi yang kompleks dengan DI, Anda dapat mengalami beberapa masalah yang mungkin sulit ditangkap dan memecahkan masalah. Skenario yang tercantum dalam artikel ini juga berisi rekomendasi, yang dapat Anda terapkan secara proaktif untuk menghindari potensi masalah.

HttpClient tidak menghormati Scoped masa pakai

Anda dapat mengalami masalah jika Anda perlu mengakses layanan terlingkup apa pun, misalnya, HttpContext, atau beberapa cache cakupan, dari dalam HttpMessageHandler. Data yang disimpan di sana dapat "menghilang", atau, sebaliknya, "bertahan" ketika seharusnya tidak. Ini disebabkan oleh ketidakcocokan cakupan Dependency Injection (DI) antara konteks aplikasi dan instans handler, dan itu adalah batasan yang diketahui dalam IHttpClientFactory.

IHttpClientFactory membuat cakupan DI terpisah per setiap HttpMessageHandler instans. Cakupan handler ini berbeda dari cakupan konteks aplikasi (misalnya, ASP.NET cakupan permintaan masuk Core, atau cakupan DI manual yang dibuat pengguna), sehingga mereka tidak akan berbagi instans layanan yang tercakup.

Sebagai hasil dari batasan ini:

  • Data apa pun yang di-cache "secara eksternal" dalam layanan terlingkup tidak akan tersedia dalam HttpMessageHandler.
  • Setiap data yang di-cache "secara internal" dalam HttpMessageHandler atau dependensi cakupannya dapat diamati dari beberapa cakupan DI aplikasi (misalnya, dari permintaan masuk yang berbeda) karena dapat berbagi handler yang sama.

Pertimbangkan rekomendasi berikut untuk membantu meringankan batasan yang diketahui ini:

❌ JANGAN menyimpan informasi terkait cakupan apa pun (seperti data dari HttpContext) di dalam HttpMessageHandler instans atau dependensinya untuk menghindari kebocoran informasi sensitif.

❌ JANGAN gunakan cookie, karena CookieContainer akan dibagikan bersama dengan handler.

✔️ PERTIMBANGKAN untuk tidak menyimpan informasi, atau hanya meneruskannya dalam HttpRequestMessage instans.

Untuk meneruskan HttpRequestMessageinformasi arbitrer bersama , Anda dapat menggunakan HttpRequestMessage.Options properti .

✔️ PERTIMBANGKAN untuk merangkum semua logika terkait cakupan (misalnya, autentikasi) secara terpisah DelegatingHandler yang tidak dibuat oleh IHttpClientFactory, dan gunakan untuk membungkus IHttpClientFactoryhandler -create.

Untuk membuat hanya tanpa HttpMessageHandlerHttpClient, panggil IHttpMessageHandlerFactory.CreateHandler klien bernama terdaftar apa pun. Dalam hal ini, Anda harus membuat HttpClient instans sendiri menggunakan handler gabungan. Anda dapat menemukan contoh yang dapat dijalankan sepenuhnya untuk solusi ini di GitHub.

Untuk informasi selengkapnya, lihat bagian Cakupan Penanganan Pesan di IHttpClientFactory dalam IHttpClientFactory panduan.

HttpClient tidak menghormati perubahan DNS

Bahkan jika IHttpClientFactory digunakan, masih mungkin untuk mencapai masalah DNS kedaluarsa. Ini biasanya dapat terjadi jika HttpClient instans ditangkap dalam Singleton layanan, atau, secara umum, disimpan di suatu tempat untuk jangka waktu yang lebih lama dari yang ditentukan HandlerLifetime. HttpClientjuga akan ditangkap jika klien jenis masing-masing ditangkap oleh singleton.

❌ JANGAN cache HttpClient instans yang dibuat oleh IHttpClientFactory untuk jangka waktu yang lama.

❌ JANGAN menyuntikkan instans klien yang ditik ke dalam Singleton layanan.

✔️ PERTIMBANGKAN untuk meminta klien secara IHttpClientFactory tepat waktu atau setiap kali Anda membutuhkannya. Klien yang dibuat pabrik aman untuk dibuang.

HttpClientinstans yang dibuat oleh IHttpClientFactory dimaksudkan untuk berumur pendek.

  • Mendaur ulang dan membuat HttpMessageHandlerulang ketika masa pakainya kedaluwarsa sangat penting untuk IHttpClientFactory memastikan handler bereaksi terhadap perubahan DNS. HttpClient terkait dengan instans handler tertentu setelah pembuatannya, sehingga instans baru HttpClient harus diminta tepat waktu untuk memastikan klien akan mendapatkan handler yang diperbarui.

  • Membuang instans tersebut HttpClient yang dibuat oleh pabrik IHttpClientFactory melacak dan membuang sumber daya yang digunakan untuk membuat HttpClient instans, khususnya HttpMessageHandler instans, segera setelah masa pakainya kedaluwarsa dan tidak HttpClient ada penggunaannya lagi.

ke dalam konstruktor, sehingga akan berbagi masa pakai klien yang ditik.

Untuk informasi selengkapnya, lihat HttpClient bagian manajemen seumur hidup dan Hindari klien yang ditik di layanan singleton dalam IHttpClientFactory panduan.

HttpClient menggunakan terlalu banyak soket

Bahkan jika IHttpClientFactory digunakan, masih mungkin untuk mengalami masalah kelelahan soket dengan skenario penggunaan tertentu. Secara default, HttpClient tidak membatasi jumlah permintaan bersamaan. Jika sejumlah besar permintaan HTTP/1.1 dimulai secara bersamaan pada saat yang sama, masing-masing permintaan akan akhirnya memicu upaya koneksi HTTP baru, karena tidak ada koneksi gratis di kumpulan dan tidak ada batas yang ditetapkan.

❌ JANGAN memulai sejumlah besar permintaan HTTP/1.1 secara bersamaan pada saat yang sama tanpa menentukan batas.

✔️ PERTIMBANGKAN pengaturan HttpClientHandler.MaxConnectionsPerServer (atau SocketsHttpHandler.MaxConnectionsPerServer, jika Anda menggunakannya sebagai handler utama) ke nilai yang wajar. Perhatikan bahwa batas ini hanya berlaku untuk instans handler tertentu.

✔️ PERTIMBANGKAN untuk menggunakan HTTP/2, yang memungkinkan permintaan multipleks melalui satu koneksi TCP.

Klien yang HttpClient diekstrak salah disuntikkan

Mungkin ada berbagai situasi di mana dimungkinkan untuk disuntikkan secara tidak terduga HttpClient ke klien yang ditik. Sebagian besar waktu, akar penyebabnya akan berada dalam konfigurasi yang salah, karena, dengan desain DI, pendaftaran layanan berikutnya mengambil alih yang sebelumnya.

Klien yang di ketik menggunakan klien bernama "di bawah tenda": menambahkan klien yang ditik secara implisit mendaftar dan menautkannya ke klien bernama. Nama klien, kecuali disediakan secara eksplisit, akan diatur ke nama jenis TClient. Ini akan menjadi yang pertama dari TClient,TImplementation pasangan jika AddHttpClient<TClient,TImplementation> kelebihan beban digunakan.

Oleh karena itu, mendaftarkan klien yang dititik melakukan dua hal terpisah:

  1. Mendaftarkan klien bernama (dalam kasus default sederhana, namanya adalah typeof(TClient).Name).
  2. Transient Mendaftarkan layanan menggunakan TClient atau TClient,TImplementation disediakan.

Dua pernyataan berikut secara teknis sama:

services.AddHttpClient<ExampleClient>(c => c.BaseAddress = new Uri("http://example.com"));

// -OR-

services.AddHttpClient(nameof(ExampleClient), c => c.BaseAddress = new Uri("http://example.com")) // register named client
    .AddTypedClient<ExampleClient>(); // link the named client to a typed client

Dalam kasus sederhana, ini juga akan mirip dengan yang berikut:

services.AddHttpClient(nameof(ExampleClient), c => c.BaseAddress = new Uri("http://example.com")); // register named client

// register plain Transient service and link it to the named client
services.AddTransient<ExampleClient>(s =>
    new ExampleClient(
        s.GetRequiredService<IHttpClientFactory>().CreateClient(nameof(ExampleClient))));

Pertimbangkan contoh berikut tentang bagaimana tautan antara klien yang ditik dan bernama bisa rusak.

Klien yang ditik didaftarkan untuk kedua kalinya

❌JANGAN mendaftarkan klien yang di ketik secara terpisah — klien sudah terdaftar secara otomatis melalui AddHttpClient<T> panggilan.

Jika klien yang ditik secara keliru mendaftarkan untuk kedua kalinya sebagai layanan Sementara biasa, ini akan menimpa pendaftaran yang ditambahkan oleh IHttpClientFactory, melanggar tautan ke klien bernama. Ini akan bermanifestasi seolah-olah HttpClientkonfigurasi 's hilang, karena yang tidak dikonfigurasi HttpClient akan dimasukkan ke klien yang diketik sebagai gantinya.

Mungkin membingungkan bahwa, alih-alih melempar pengecualian, "salah" HttpClient digunakan. Ini terjadi karena "default" tidak dikonfigurasi HttpClient — klien dengan Options.DefaultName nama (string.Empty) — terdaftar sebagai layanan Sementara biasa, untuk mengaktifkan skenario penggunaan paling dasar IHttpClientFactory . Itu sebabnya setelah tautan rusak dan klien yang di ketik hanya menjadi layanan biasa, "default" HttpClient ini secara alami akan disuntikkan ke dalam parameter konstruktor masing-masing.

Klien jenis yang berbeda terdaftar pada antarmuka umum

Jika dua klien jenis yang berbeda terdaftar pada antarmuka umum, mereka berdua akan menggunakan kembali klien bernama yang sama. Ini bisa tampak seperti klien pertama yang diketik mendapatkan klien bernama kedua "salah" yang disuntikkan.

❌ JANGAN mendaftarkan beberapa klien yang dititik pada satu antarmuka tanpa secara eksplisit menentukan nama.

✔️ PERTIMBANGKAN untuk mendaftarkan dan mengonfigurasi klien bernama secara terpisah, lalu menautkannya ke satu atau beberapa klien yang ditik, baik dengan menentukan nama dalam AddHttpClient<T> panggilan atau dengan memanggil AddTypedClient selama penyiapan klien bernama.

Secara desain, mendaftarkan dan mengonfigurasi klien bernama dengan nama yang sama beberapa kali hanya menambahkan tindakan konfigurasi ke daftar yang ada. Perilaku IHttpClientFactory ini mungkin tidak jelas, tetapi merupakan pendekatan yang sama yang digunakan oleh pola Opsi dan API konfigurasi seperti Configure.

Ini sebagian besar berguna untuk konfigurasi handler tingkat lanjut, misalnya, menambahkan handler kustom ke klien bernama yang ditentukan secara eksternal, atau meniru handler utama untuk pengujian, tetapi juga berfungsi untuk HttpClient konfigurasi instans. Misalnya, tiga contoh berikut akan menghasilkan dikonfigurasi dengan HttpClient cara yang sama (keduanya BaseAddress dan DefaultRequestHeaders diatur):

// one configuration callback
services.AddHttpClient("example", c =>
    {
        c.BaseAddress = new Uri("http://example.com");
        c.DefaultRequestHeaders.UserAgent.ParseAdd("HttpClient/8.0");
    });

// -OR-

// two configuration callbacks
services.AddHttpClient("example", c => c.BaseAddress = new Uri("http://example.com"))
    .ConfigureHttpClient(c => c.DefaultRequestHeaders.UserAgent.ParseAdd("HttpClient/8.0"));

// -OR-

// two configuration callbacks in separate AddHttpClient calls
services.AddHttpClient("example", c => c.BaseAddress = new Uri("http://example.com"));
services.AddHttpClient("example")
    .ConfigureHttpClient(c => c.DefaultRequestHeaders.UserAgent.ParseAdd("HttpClient/8.0"));

Ini memungkinkan penautan klien yang ditik ke klien bernama yang sudah ditentukan, dan juga menautkan beberapa klien yang ditik ke satu klien bernama. Lebih jelas ketika kelebihan beban dengan name parameter digunakan:

services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress));

services.AddHttpClient<FooLogger>("LogClient");
services.AddHttpClient<BarLogger>("LogClient");

Hal yang sama juga dapat dicapai dengan memanggil AddTypedClient selama konfigurasi klien bernama:

services.AddHttpClient("LogClient", c => c.BaseAddress = new Uri(LogServerAddress))
    .AddTypedClient<FooLogger>()
    .AddTypedClient<BarLogger>();

Namun, jika Anda tidak ingin menggunakan kembali klien bernama yang sama, tetapi Anda masih ingin mendaftarkan klien pada antarmuka yang sama, Anda dapat melakukannya dengan secara eksplisit menentukan nama yang berbeda untuk mereka:

services.AddHttpClient<ITypedClient, ExampleClient>(nameof(ExampleClient),
    c => c.BaseAddress = new Uri("http://example.com"));
services.AddHttpClient<ITypedClient, GithubClient>(nameof(GithubClient),
    c => c.BaseAddress = new Uri("https://github.com"));

Lihat juga