Diversity w polskim IT
Bishoy Labib
Bishoy Labibechnical Architect @ Integrant

Łatwiejsza praca z HTTP w .NET Core

Poznaj 3 rozwiązania ułatwiające pracę z HTTP w aplikacjach .NET Core, czyli, logowanie błędów bad request, logowanie żądań i odpowiedzi klienta HTTP oraz propagacja nagłówków między mirkoserwisami.
25.03.20204 min
Łatwiejsza praca z HTTP w .NET Core

W tym artykule skupię się na kilku rozwiązaniach związanych z HTTP, które ułatwiają pracę z API oraz mikroserwisami. Stworzyłem też pakiet NuGet o nazwie CoreX.Extensions.Http, który zawiera w sobie te wszystkie rozwiązania. Możesz go dodać do swojego projektu oraz skonfigurować w pliku startup.cs, aby korzystać z tych rozwiązań. 

Lepsze logowanie dla bad requests (400)

Zacznijmy od najprostszej rzeczy. Zdarzyło się pewnie wiele razy, że ktoś wywołuje Twoje API i robi coś niepoprawnie, na przykład, zapomina o polu lub, nieświadomy tego, że Twoje API było aktualizowane, zapomina o wysłaniu nowego parametru.

Ten ktoś dostaje wtedy błąd, ale nie wie, dlaczego i prosi o pomoc. Pierwszą rzeczą, którą robisz, jest sprawdzenie logów API: 

Domyślne logi .NET Core dla bad request 400

Jedyną wskazówką, że coś poszło nie tak, jest ostatnia linijka (jako informacja):

Request finished in … 400 application/json; charset=utf-8


Jedynie 400 status code wskazuje na to, że coś poszło nie tak, ale wywołujący otrzymał też przydatną odpowiedź JSON-ie:

Odpowiedź JSON w związku z 400 bad request

Dlaczego więc właściciel API nie otrzymał jasnego logu tego błędu? Myślę, że dotnet powinien domyślnie zarejestrować ten błąd oraz szczegóły żądania, aby właściciel mógł powiedzieć wywołującemu, co poszło nie tak i, jak to naprawić.

Stworzyłem metodę rozszerzającą, która to zrobi. Należy ją dodać do startup.cs w metodzie ConfigureServices:

// Enable the logging for common 400 bad request errors
services.EnableLoggingForBadRequests();

Oto dodatkowy warning w logach:

Log 400 bad request po umożliwieniu lepszego logowania

Proste, prawda?

Automatyczne logowanie dla HTTPClient

Twoja aplikacja wywołuje teraz inne API. Musimy śledzić te wywołania w logach, żeby mieć świadomość tego, co wysyłamy i jaką odpowiedź otrzymujemy. Wydaje mi się, że to podstawa. Można sobie z tym łatwo poradzić, tak jak w tym wątku na StackOverflow. Rozwiązanie to jednak działa tylko dla jednego klienta HTTP, a ja chciałem, żeby tak zachowywały się wszystkie utworzone klienty HTTP. 

Jednym ze wspaniałych dodatków do dotnet core 2.1 był IHttpClientFactory, który jest interfejsem do tworzenia HttpClient. Ogólnie nie powinno się w ogóle używać HttpClient();.

Jeśli więc wszyscy klienci HTTP zostaną utworzeni przy użyciu IHttpClientFactory, istnieje sposób, aby sprecyzować, w jaki sposób Ci klienci mają być tworzeni, bez względu na to, ile razy robimy wywołania.

Problem polega na tym, że stworzenie takiego klienta przyszło mi naprawdę łatwo, ale trudno było znaleźć sposób na dopasowanie naszej polityki tworzenia klientów do wszystkich nazwanych lub nienazwanych klientów HTTP.

Dlatego dodałem taki rodzaj logowania do wszystkich klientów HTTP przez zarejestrowanie tego w metodzie ConfigureServices w startup.cs:

// Register HttpClientFactory
services.AddHttpClient();
// Register Logging for HttpClient
services.AddHttpClientLogging(Configuration);


Możesz także skonfigurować, to co ma być logowane, a także zalogować nagłówki, treść żądania i odpowiedzi czy też format logów (HTML/plaintext). Konfiguracja jest możliwa przez dodanie tej sekcji w appsettings.json:

"HttpClientLogging": {
  "Enabled": true,
  "Html": true,
  "Headers": true,
  "Body":  false
},

W zamian dostajesz fajne logi:

Logowanie żądania i odpowiedzi klienta HTTP

Propagacja nagłówków

Innym bardzo częstym problemem jest to, że użytkownik wysyła jedno żądanie, które powoduje wiele wywołań API za pośrednictwem różnych mikroserwisów. Zwykle chcemy śledzić to w logach jako część pojedynczego żądania. Musimy więc wiedzieć, jakie inne wywołania i zależności zostały wykonane na podstawie początkowego żądania tego użytkownika, bez względu na ich liczbę. Robimy to, tworząc correlation ID (losowy unikalny identyfikator) dla pierwszego żądania i za każdym razem, gdy wywołujemy HTTP do innych API, umieszczamy ten sam correlation ID jako nagłówek we wszystkich żądaniach. W taki sposób ten sam identyfikator będzie propagowany we wszystkich kolejnych żądaniach i ich logach.

Kolejnym częstym scenariuszem jest to, że gdy użytkownik wysyła wywołanie API ze swoim tokenem w nagłówku Authorization, to za każdym razem, gdy wykonujemy inne wywołania API do wielu usług, to powinniśmy używać tego samego nagłówka, aby wszystkie usługi wiedziały, kto wykonał dane połączenie. To również odbywa się poprzez propagowanie nagłówka autoryzacji.

Problem polega na tym, że istnieje kilka nagłówków (które mogą różnić się w zależności od aplikacji), które musimy propagować podczas wykonywania jakichkolwiek wywołań HTTP. Rozwiązaniem jest tutaj używanie IHttpClientFactory do automatycznego dodawania tych nagłówków do każdego nowego klienta.

Użyłem tutaj rozwiązania Davida Fowlera. Jego implementację umieściłem w metodzie rozszerzającej, żeby było mi łatwiej. Wykorzystałem pakiet NuGet o nazwie CorrelationId, aby dodać specjalną obsługę generowania nowego ID, jeśli nie został wysłany.

Dodajemy to w startup.cs metody ConfigureServices:

// Register global header propagation for any HttpClient that comes from HttpClientFactory
services.AddHeaderPropagation(options => { options.HeaderNames.Add("X-My-Header"); });


Domyślne nagłówki są już zawarte w x-correlation-id. Pełną listę można sprawdzić tutaj.

Podsumowanie

Staraliśmy się tutaj znaleźć rozwiązania czasochłonnych problemów, z którymi często spotykamy się podczas pracy z HTTP. Są to rzeczy, które powinny być domyślnie włączone, żeby programiści nie musieli się nimi przejmować podczas budowania mikroserwisów z .NET.

A czy Wy natknęliście się na podobne problemy?

<p>Loading...</p>