Niektóre firmy, w tym nasz klient, rozwijają produkt poprzez sieć partnerską. Na przykład, duże sklepy internetowe są zintegrowane z usługą dostawy — zamawiasz produkt, a wkrótce otrzymujesz numer śledzenia przesyłki. Innym przykładem jest to, że razem z biletem lotniczym kupujesz ubezpieczenie lub bilet na aerobus.
Do tego używany jest jeden API, który należy udostępnić partnerom poprzez API Gateway. Ten problem właśnie rozwiązaliśmy. W tym artykule przedstawimy szczegóły.
Dane: ekosystem i portal API z interfejsem, w którym użytkownicy są zarejestrowani, otrzymują informacje itp. Musimy stworzyć wygodny i niezawodny API Gateway. W trakcie prac musieliśmy zapewnić
- rejestrację,
- kontrolę połączeń z API,
- monitorowanie sposobu, w jaki użytkownicy korzystają z systemu końcowego,
- uwzględnienie wskaźników biznesowych.

W artykule opowiemy o naszym doświadczeniu w tworzeniu API Gateway, podczas którego stawialiśmy czoła następującym zadaniom:
- uwierzytelnianie użytkownika,
- autoryzacja użytkownika,
- modyfikacja oryginalnego żądania,
- proxy żądania,
- przetwarzanie odpowiedzi.
Istnieją dwa rodzaje zarządzania API:
1. Standardowe, które działa w następujący sposób. Przed połączeniem użytkownik testuje możliwości, następnie płaci i wbudowuje je na swojej stronie. Najczęściej korzystają z tego małe i średnie przedsiębiorstwa.
2. Duże zarządzanie B2B API, kiedy firma najpierw podejmuje decyzję biznesową o połączeniu, staje się partnerem firmy z umową, a następnie łączy się z API. Dopiero po załatwieniu wszystkich formalności firma otrzymuje dostęp do testów, przechodzi testy i przechodzi do produkcji. Jednak jest to niemożliwe bez decyzji zarządzającej o połączeniu.

Nasze rozwiązanie
W tej części opowiemy o tworzeniu API Gateway.
Końcowi użytkownicy stworzonego API Gateway to partnerzy naszego klienta. Dla każdego z nich już przechowujemy niezbędne umowy. Musimy jedynie rozszerzyć funkcjonalność, zaznaczając przyznany dostęp do bramy. W związku z tym potrzebny jest kontrolowany proces połączenia i zarządzania.
Oczywiście, można było skorzystać z gotowego rozwiązania do zarządzania API i tworzenia API Gateway w szczególności. Na przykład takim rozwiązaniem mogło być. Nie pasowało nam to, ponieważ w naszym przypadku mieliśmy już portal API oraz ogromny ekosystem, który został wokół niego zbudowany. Wszyscy użytkownicy byli już zarejestrowani i wiedzieli, gdzie i jak mogą zdobyć potrzebne informacje. W portalu API istniały już potrzebne interfejsy, potrzebny był nam tylko API Gateway. Właśnie jego opracowaniem się zajęliśmy.
To, co nazywamy API Gateway, to rodzaj proxy. I tu ponownie mieliśmy wybór — można napisać własne proxy lub wybrać coś gotowego. W tym przypadku poszliśmy drugą drogą i wybraliśmy zestawienie nginx+Lua. Dlaczego? Potrzebowaliśmy niezawodnego, sprawdzonego oprogramowania wspierającego skalowanie. Nie chcieliśmy po realizacji testować oraz poprawiać zarówno poprawności logiki biznesowej, jak i działania proxy.
Każdy serwer WWW ma potok przetwarzania żądań. W przypadku nginx wygląda to następująco:

(schemat z )
Naszym celem było wkomponowanie się w ten potok w momencie, w którym możemy modyfikować oryginalne żądanie.
Chcemy stworzyć transparentne proxy, żeby funkcjonalnie żądanie pozostało takim, jakim przyszło. Tylko kontrolujemy dostęp do końcowego API i pomagamy żądaniu do niego dotrzeć. W przypadku, gdy żądanie było niepoprawne, błąd powinno pokazać końcowe API, ale nie my. Jedynym powodem, dla którego możemy odrzucić żądanie, jest brak dostępu u klienta.
Dla nginx już istnieje na . Lua to język skryptowy, jest bardzo lekki i łatwy do opanowania. W ten sposób potrzebną logikę zrealizowaliśmy za pomocą Lua.
Konfiguracja nginx’a (analogicznie do trasy aplikacji), gdzie wykonywana jest cała praca, jest całkowicie zrozumiała. Ciekawa jest tutaj ostatnia dyrektywa — post_action.
location /middleware {
more_clear_input_headers Accept-Encoding;
lua_need_request_body on;
rewrite_by_lua_file 'middleware/rewrite.lua';
access_by_lua_file 'middleware/access.lua';
proxy_pass https://someurl.com;
body_filter_by_lua_file 'middleware/body_filter.lua';
post_action /process_session;
}
Przyjrzyjmy się, co dzieje się w tej konfiguracji:
more_clear_input_headers — oczyszcza wartość wskazanych po dyrektywie nagłówków.
lua_need_request_body — reguluje, czy należy odczytać oryginalne ciało żądania przed wykonaniem dyrektyw rewrite/access/access_by_lua, czy nie. Domyślnie nginx nie odczytuje ciała żądania klienta, a jeśli konieczne jest uzyskanie do niego dostępu, ta dyrektywa musi mieć wartość on.
rewrite_by_lua_file — ścieżka do skryptu, w którym opisana jest logika modyfikacji żądania
access_by_lua_file — ścieżka do skryptu, w którym opisana jest logika sprawdzająca dostęp do zasobu.
proxy_pass — url, na który będzie przekazywane żądanie.
body_filter_by_lua_file — ścieżka do skryptu, w którym opisana jest logika filtrująca żądanie przed zwróceniem go do klienta.
I, w końcu, post_action — oficjalnie niedokumentowana dyrektywa, która pozwala na wykonanie dodatkowych działań po tym, jak odpowiedź została zwrócona klientowi.
Dalej opowiemy krok po kroku, jak rozwiązaliśmy nasze zadania.
Autoryzacja/uwierzytelnienie i modyfikacja żądania
Autoryzacja
Autoryzację i uwierzytelnienie zbudowaliśmy z wykorzystaniem dostępów za pomocą certyfikatu. Istnieje certyfikat główny. Każdemu nowemu klientowi zamawiającego generowany jest jego osobisty certyfikat, z którym może uzyskać dostęp do API. Certyfikat ten jest konfigurowany w sekcji server ustawień nginx.
ssl on;
ssl_certificate /usr/local/openresty/nginx/ssl/cert.pem;
ssl_certificate_key /usr/local/openresty/nginx/ssl/cert.pem;
ssl_client_certificate /usr/local/openresty/nginx/ssl/ca.crt;
ssl_verify_client on;Modyfikacja
Może powstać słuszne pytanie: co zrobić z certyfikowanym klientem, jeśli nagle zechcieliśmy go odciąć od systemu? Nie można przecież ponownie wydawać certyfikatów dla wszystkich innych klientów.
Tak dotarliśmy do kolejnego zadania — modyfikacji pierwotnego żądania. Pierwotne żądanie klienta, ogólnie rzecz biorąc, nie jest ważne dla docelowego systemu. Jednym z zadań jest dodanie brakujących części do żądania, aby uczynić je ważnym. Część brakujących danych różni się dla każdego klienta. Wiemy, że klient przybywa do nas z certyfikatem, z którego możemy uzyskać odcisk i wydobyć z bazy niezbędne dane klienta.
Jeśli w pewnym momencie zajdzie potrzeba odcięcia klienta od naszego serwisu, jego dane znikną z bazy i nie będzie mógł nic zrobić.
Praca z danymi klienta
Musieliśmy zapewnić wysoką dostępność rozwiązania, szczególnie w kwestii, jak zdobywamy dane klienta. Trudność polega na tym, że pierwotnym źródłem tych danych jest zewnętrzna usługa, która nie gwarantuje nieprzerwanej i wystarczająco dużej prędkości działania.
Dlatego musieliśmy zapewnić wysoką dostępność danych klientów. Jako narzędzie wybraliśmy , który dostarcza nam:
- szybki dostęp do danych,
- możliwość zorganizowania klastra z wieloma węzłami z replikowanymi danymi na różnych węzłach.
Zastosowaliśmy najprostsza strategię dostarczania danych do cache:

Praca z systemem końcowym odbywa się w ramach sesji i istnieje limit na maksymalną ich ilość. Jeśli klient nie zamknął sesji, będziemy musieli to zrobić za niego.
Dane o otwartej sesji pochodzą z systemu końcowego i są pierwotnie przetwarzane po stronie Lua. Zdecydowaliśmy się użyć Hazelcast do przechowywania tych danych w zadaniu napisanym w .NET. Następnie co jakiś czas sprawdzamy żywotność otwartych sesji i zamykamy wygasłe.
Dostęp do Hazelcast zarówno z Lua, jak i z .NET
Nie ma klientów Lua do pracy z Hazelcast, ale Hazelcast ma REST API, z którego postanowiliśmy skorzystać. Dla .NET jest , przez co planowaliśmy uzyskać dostęp do danych Hazelcast po stronie .NET. Ale ku naszemu zdziwieniu, było to bardziej skomplikowane.

Podczas zapisywania danych za pomocą REST i odczytywania ich przez klienta .NET używane są różne serializatory-deserializatory. Dlatego nie można zapisać danych za pomocą REST, a odczytać przez klienta .NET i odwrotnie.
Jeśli będą zainteresowani, chętnie opowiemy o tym problemie w osobnym artykule. Spoiler — w schemacie.

Logowanie i monitorowanie
Naszym standardem korporacyjnym do logowania w .NET jest Serilog, wszystkie logi trafiają ostatecznie do Elasticsearch, ich analizę przeprowadzamy przez Kibana. Coś podobnego chcieliśmy osiągnąć w tym przypadku. Jedynym klientem do pracy z Elastic w Lua, który został znaleziony, zepsuł się na pierwszym imporcie. Dlatego użyliśmy Fluentd.
— open source'owe rozwiązanie zapewniające jednolitą warstwę logowania aplikacji. Umożliwia zbieranie logów z różnych warstw aplikacji i ich późniejsze przesyłanie do jednego źródła.
API Gateway działa w K8S, dlatego postanowiliśmy dodać kontener z fluentd w tym samym podzie, aby zapisywać logi w istniejącym otwartym porcie TCP fluentd.
Zbadaliśmy również, jak będzie działał fluentd, jeśli nie będzie miał połączenia z Elasticsearch. Przez dwa dni do bramy stale napływały żądania, logi były przesyłane do fluentd, ale IP Elastic zostało zablokowane. Po przywróceniu połączenia fluentd z powodzeniem przesłał wszystkie logi do Elastic.
Podsumowanie
Wybrana metoda realizacji pozwoliła nam dostarczyć rzeczywiście działający produkt w środowisku produkcyjnym w zaledwie 2,5 miesiąca.
Jeśli kiedykolwiek będziesz zajmować się podobnymi kwestiami, zdecydowanie zalecamy, aby najpierw dokładnie zrozumieć, jaki problem chcesz rozwiązać i jakie zasoby już posiadasz. Zwróć uwagę na trudności związane z integracją z istniejącymi systemami zarządzania API.
Zrozum, co dokładnie zamierzasz rozwijać – tylko logikę biznesową do przetwarzania zapytań czy, jak miało to miejsce w naszym przypadku, cały proxy. Pamiętaj, że wszystko, co zrobisz samodzielnie, powinno być później starannie przetestowane.
Źródło: habr.com
