Egyéni tavaszi felhő átjáró szűrők írása

1. Áttekintés

Ebben az oktatóanyagban megtanuljuk, hogyan kell egyéni Spring Cloud Gateway szűrőket írni.

Ezt a keretrendszert előző bejegyzésünkben, az Új tavaszi felhő átjáró felfedezése című cikkünkben ismertettük, ahol sok beépített szűrőt szemügyre vettünk.

Ez alkalommal elmélyülünk, egyéni szűrőket írunk, hogy a lehető legtöbbet hozzuk ki az API-átjárónkból.

Először meglátjuk, hogyan hozhatunk létre globális szűrőket, amelyek hatással lesznek az átjáró által kezelt minden egyes kérelemre. Ezután átjárószűrő gyárakat írunk, amelyek részletesen alkalmazhatók bizonyos útvonalakon és kéréseken.

Végül fejlettebb forgatókönyveken fogunk dolgozni, megtanulva, hogyan lehet a kérést vagy a választ módosítani, és még azt is, hogyan lehet a kérést reaktív módon összekapcsolni más szolgáltatások hívásaival.

2. Projekt beállítása

Először egy alapalkalmazás felállításával kezdjük, amelyet API-átjáróként fogunk használni.

2.1. Maven konfiguráció

Amikor a Spring Cloud könyvtárakkal dolgozik, mindig jó választás egy függőségkezelő konfiguráció beállítása a számunkra lévő függőségek kezelésére:

   org.springframework.cloud tavaszi-felhő-függőségek Hoxton.SR4 pom import 

Most hozzáadhatjuk a Spring Cloud könyvtárakat anélkül, hogy megadnánk a tényleges verziót, amelyet használunk:

 org.springframework.cloud spring-cloud-starter-gateway 

A Spring Spring Release Train legújabb verziója megtalálható a Maven Central keresőmotor segítségével. Természetesen mindig ellenőriznünk kell, hogy a verzió kompatibilis-e a Spring Boot verzióval, amelyet a Spring Cloud dokumentációjában használunk.

2.2. API-átjáró konfigurációja

Feltételezzük, hogy van egy második alkalmazás, amely helyben fut a kikötőben 8081, amely kitesz egy erőforrást (az egyszerűség kedvéért csak egy egyszerű Húr) ütéskor /forrás.

Ezt szem előtt tartva konfiguráljuk az átjárónkat a szolgáltatás proxy kéréseihez. Dióhéjban, amikor az a-val kérést küldünk az átjáróhoz /szolgáltatás előtagot az URI elérési útvonalán, továbbítjuk a hívást erre a szolgáltatásra.

Tehát, amikor hívunk / szolgáltatás / erőforrás az átjárónkon meg kell kapnunk a Húr válasz.

Ennek elérése érdekében konfiguráljuk ezt az útvonalat a alkalmazás tulajdonságai:

tavasz: felhő: átjáró: útvonalak: - id: service_route uri: // localhost: 8081 predikátum: - Path = / service / ** szűrők: - RewritePath = / service (? /?. ​​*), $ \ {szegmens}

Ezenkívül az átjáró folyamat megfelelő nyomon követése érdekében engedélyezünk néhány naplót is:

naplózás: szint: org.springframework.cloud.gateway: DEBUG reaktor.netty.http.client: DEBUG

3. Globális szűrők létrehozása

Miután az átjárókezelő megállapította, hogy a kérés egyezik az útvonallal, a keretrendszer átadja a kérést egy szűrőláncon keresztül. Ezek a szűrők végrehajthatják a logikát a kérelem elküldése előtt vagy utána.

Ebben a szakaszban egyszerű globális szűrők megírásával kezdjük. Ez azt jelenti, hogy minden egyes kérésre hatással lesz.

Először meglátjuk, hogyan tudjuk végrehajtani a logikát a proxy kérelem elküldése előtt (más néven „előzetes” szűrő)

3.1. Globális „Pre” szűrő logika írása

Mint mondtuk, ezen a ponton egyszerű szűrőket hozunk létre, mivel a fő cél itt csak az, hogy lássuk, a szűrő valóban a megfelelő pillanatban kerül végrehajtásra; csak egy egyszerű üzenet naplózása fogja megcsinálni.

Egyéni globális szűrő létrehozásához csak a Spring Cloud Gateway megvalósítása szükséges GlobalFilter interfészt, és vegye fel a kontextusba babként:

@Component public class LoggingGlobalPreFilter megvalósítja a GlobalFilter {final Logger logger = LoggerFactory.getLogger (LoggingGlobalPreFilter.class); @Override public Mono filter (ServerWebExchange exchange, GatewayFilterChain chain) {logger.info ("Globális előszűrő végrehajtva"); visszatérő lánc.szűrő (csere); }}

Könnyen láthatjuk, mi folyik itt; Miután meghívta ezt a szűrőt, naplózunk egy üzenetet, és folytatjuk a szűrő lánc végrehajtását.

Most definiáljunk egy „post” szűrőt, amely kicsit bonyolultabb lehet, ha nem ismerkedünk meg a reaktív programozási modellel és a Spring Webflux API-val.

3.2. Globális „Post” szűrő logika írása

Egy másik dolog, amit az imént definiált globális szűrőn észre kell venni, az az GlobalFilter interfész csak egy módszert határoz meg. Így kifejezhető lambda kifejezésként, lehetővé téve számunkra a szűrők kényelmes meghatározását.

Például meghatározhatjuk a „post” szűrőnket egy konfigurációs osztályban:

@Configuration public class LoggingGlobalFiltersConfigurations {final Logger logger = LoggerFactory.getLogger (LoggingGlobalFiltersConfigurations.class); @Bean public GlobalFilter postGlobalFilter () {return (exchange, chain) -> {return chain.filter (exchange) .then (Mono.fromRunnable (() -> {logger.info ("Globális postaszűrő végrehajtva");}) ); }; }}

Egyszerűen fogalmazva: itt futunk egy új Monó például a lánc befejezése után.

Próbáljuk ki most a / szolgáltatás / erőforrás URL az átjáró szolgáltatásunkban, és a naplókonzol megtekintése:

DEBUG --- oscghRoutePredicateHandlerMapping: Egyeztetett útvonal: service_route DEBUG --- oscghRoutePredicateHandlerMapping: Az [Exchange: GET // localhost / service / resource] hozzárendelése az {id = 'service_route', uri = // localhost: 8081, order = útvonalhoz 0, predikátum = Elérési útvonalak: [/ service / **], egyező záró perjel: true, gatewayFilters = [[[[RewritePath /service(?/?.*) = '$ {segment}'], rendelés = 1]]} INFO --- cbscfglobal.LoggingGlobalPreFilter: Globális előszűrő végrehajtva DEBUG --- r.netty.http.client.HttpClientConnect: [id: 0x58f7e075, L: /127.0.0.1: 57215 - R: localhost / 127.0.0.1: 8081 ] A kezelőt alkalmazzák: {uri = // localhost: 8081 / resource, method = GET} DEBUG --- rnhttp.client.HttpClientOperations: [id: 0x58f7e075, L: /127.0.0.1: 57215 - R: localhost / 127.0.0.1:8081] Fogadott válasz (automatikus olvasás: hamis): [Content-Type = text / html; charset = UTF-8, Content-Length = 16] INFO --- cfgLoggingGlobalFiltersConfigurations: Global Post Filter végrehajtva DEBUG - - rnhttp.client.HttpClientOperations: [id: 0x58f7e075, L: / 127 .0.0.1: 57215 - R: localhost / 127.0.0.1: 8081] Fogadott utolsó HTTP csomag

Mint láthatjuk, a szűrőket hatékonyan hajtják végre, mielőtt és miután az átjáró továbbítja a kérést a szolgáltatásnak.

Természetesen az „pre” és a „post” logikát egyetlen szűrőben kombinálhatjuk:

@Component public class FirstPreLastPostGlobalFilter implementálja a GlobalFilter, rendezett {final Logger logger = LoggerFactory.getLogger (FirstPreLastPostGlobalFilter.class); @Orride public Mono filter (ServerWebExchange exchange, GatewayFilterChain chain) {logger.info ("First Pre Global Filter"); return chain.filter (exchange) .the (Mono.fromRunnable (() -> {logger.info ("Utolsó üzenet globális szűrő");})); } @Orride public int getOrder () {return -1; }}

Megjegyezzük, hogy a Rendelt interfész, ha érdekel a szűrő elhelyezése a láncban.

A szűrőlánc jellegéből adódóan egy alacsonyabb elsőbbségű (alacsonyabb rendű a láncban) szűrő egy korábbi szakaszban végrehajtja „pre” logikáját, de a „post” megvalósítását később hívják meg:

4. Teremtés GatewayFilters

A globális szűrők meglehetősen hasznosak, de gyakran részletes, egyedi átjárószűrő műveleteket kell végrehajtanunk, amelyek csak néhány útvonalra vonatkoznak.

4.1. A GatewayFilterFactory

Megvalósítása érdekében a GatewayFilter, végre kell hajtanunk a GatewayFilterFactory felület. A Spring Cloud Gateway egy elvont osztályt is biztosít a folyamat leegyszerűsítésére, a AbstractGatewayFilterFactory osztály:

A @Component public class LoggingGatewayFilterFactory kiterjeszti az AbstractGatewayFilterFactory {final Logger logger = LoggerFactory.getLogger (LoggingGatewayFilterFactory.class); public LoggingGatewayFilterFactory () {super (Config.class); } @Orride public GatewayFilter Apply (Config config) {// ...} public static class Config {// ...}}

Itt definiáltuk alapvető struktúránkat GatewayFilterFactory. Használjuk a Konfig osztály testreszabhatja szűrőnket, amikor inicializáljuk.

Ebben az esetben például három alapvető mezőt határozhatunk meg a konfigurációnkban:

public static class Config {private String baseMessage; privát logikai preLogger; privát logikai postaLogger; // kivitelezők, szerelők és beállítók ...}

Egyszerűen fogalmazva, ezek a mezők a következők:

  1. egy egyéni üzenet, amelyet a naplóbejegyzés tartalmaz
  2. egy jelző, amely jelzi, hogy a szűrőnek be kell-e jelentkeznie a kérelem továbbítása előtt
  3. egy jelző, amely jelzi, hogy a szűrőnek naplóznia kell-e, miután megkapta a választ a proxy szolgáltatástól

És most ezeket a konfigurációkat használhatjuk a GatewayFilter példány, amely ismét lambda függvénnyel ábrázolható:

@ Nyissa meg a nyilvános GatewayFilter alkalmazást (Config config) {return (csere, lánc) -> {// Előkészítés if (config.isPreLogger ()) {logger.info ("Pre GatewayFilter naplózása:" + config.getBaseMessage ()) ; } return chain.filter (exchange) .then (Mono.fromRunnable (() -> {// Utófeldolgozás if (config.isPostLogger ()) {logger.info ("Post GatewayFilter naplózása:" + config.getBaseMessage () );}})); }; }

4.2. A GatewayFilter a Tulajdonságokkal

Most már könnyen regisztrálhatjuk szűrőnket arra az útvonalra, amelyet korábban az alkalmazás tulajdonságaiban definiáltunk:

... szűrők: - RewritePath = / service (? /?. ​​*), $ \ {segment} - név: Logging args: baseMessage: Saját egyéni üzenet preLogger: true postLogger: true

Egyszerűen meg kell jelölnünk a konfigurációs argumentumokat. Fontos szempont, hogy szükségünk van egy argumentum nélküli konstruktorra és a konfigurálókra LoggingGatewayFilterFactory.Config osztályban, hogy ez a megközelítés megfelelően működjön.

Ha inkább a kompakt jelöléssel akarjuk konfigurálni a szűrőt, akkor megtehetjük:

szűrők: - RewritePath = / service (? /?. ​​*), $ \ {segment} - Naplózás = Saját üzenet, true, true

Még egy kicsit módosítanunk kell a gyárat. Röviden, felül kell írnunk a shortcutFieldOrder metódus, a sorrend és a argumentum tulajdonságának megjelölésére:

@Override public List shortcutFieldOrder () {return Arrays.asList ("baseMessage", "preLogger", "postLogger"); }

4.3. Megrendelése GatewayFilter

Ha konfigurálni akarjuk a szűrő pozícióját a szűrőláncban, akkor lekérhetünk egy OrderedGatewayFilter példa tól AbstractGatewayFilterFactory # érvényes módszer sima lambda kifejezés helyett:

@Override public GatewayFilter Apply (Config config) {return new OrderedGatewayFilter ((csere, lánc) -> {// ...}, 1); }

4.4. A GatewayFilter Programozatosan

Ezenkívül szoftveresen is regisztrálhatjuk a szűrőnket. Határozzuk meg újra az általunk használt utat, ezúttal az a beállításával RouteLocator bab:

@Bean public RouteLocator útvonalak (RouteLocatorBuilder builder, LoggingGatewayFilterFactory loggingFactory) {return builder.routes () .route ("service_route_java_config", r -> r.path ("/ service / **") .filters (f -> f.rewrite ("/service(?/?.*)", "$ \ {szegmens}") .filter (loggingFactory.apply (új Config ("Saját egyéni üzenet", igaz, igaz)))) .uri ("/ / localhost: 8081 ")) .build (); }

5. Haladó forgatókönyvek

Eddig csak annyit tettünk, hogy naplózunk egy üzenetet az átjáró folyamatának különböző szakaszaiban.

Általában a fejlettebb funkciók biztosításához szükségünk van a szűrőinkre. Előfordulhat például, hogy ellenőriznünk vagy manipulálnunk kell a beérkezett kérést, módosítanunk kell a visszakeresett választ, vagy akár a reaktív folyamot más, különböző szolgáltatásokra irányuló hívásokkal kell láncolni.

Ezután meglátjuk a különböző forgatókönyvek példáit.

5.1. A kérelem ellenőrzése és módosítása

Képzeljünk el egy hipotetikus forgatókönyvet. Szolgáltatásunk korábban az a alapján szolgáltatta tartalmát területi beállítás lekérdezési paraméter. Ezután megváltoztattuk az API-t a Elfogadás-Nyelv fejléc helyett, de egyes ügyfelek továbbra is a lekérdezési paramétert használják.

Ezért az átjárót a logika szerint normalizálni szeretnénk:

  1. ha megkapjuk a Elfogadás-Nyelv fejléc, ezt meg akarjuk tartani
  2. különben használja a területi beállítás lekérdezési paraméter értéke
  3. ha ez sem szerepel, használjon alapértelmezett területi beállításokat
  4. végül el akarjuk távolítani a területi beállítás lekérdezés param

Megjegyzés: A dolgok egyszerűsége érdekében itt csak a szűrő logikára fogunk koncentrálni; hogy áttekinthessük a teljes megvalósítást, az oktatóanyag végén találunk egy linket a kódbázisra.

Konfiguráljuk az átjáró szűrőnket „előzetes” szűrőként, majd:

(csere, lánc) -> {if (exchange.getRequest () .getHeaders () .getAcceptLanguage () .isEmpty ()) {// töltse ki az Accept-Language fejlécet ...} // távolítsa el a lekérdezési param ... visszatérő lánc.szűrő (csere); };

Itt a logika első aspektusával foglalkozunk. Láthatjuk, hogy a ServerHttpRequest objektum nagyon egyszerű. Ezen a ponton csak a fejlécekhez jutottunk hozzá, de amint a későbbiekben látni fogjuk, ugyanolyan egyszerűen beszerezhetünk más attribútumokat is:

Karakterlánc lekérdezésParamLocale = exchange.getRequest () .getQueryParams () .getFirst ("locale"); Locale requestLocale = Optional.ofNullable (queryParamLocale) .map (l -> Locale.forLanguageTag (l)) .orElse (config.getDefaultLocale ());

Most kitértünk a viselkedés következő két pontjára. De még nem módosítottuk a kérést. Ezért, ki kell használnunk a mutálódik képesség.

Ezzel a keretrendszer létrehozza a Díszítő az entitás változatlan formában tartása mellett.

A fejlécek módosítása egyszerű, mert hivatkozást szerezhetünk a HttpHeaders térkép objektum:

exchange.getRequest () .mutate () .headers (h -> h.setAcceptLanguageAsLocales (Collections.singletonList (requestLocale)))

Másrészt az URI módosítása nem jelentéktelen feladat.

Meg kell szereznünk egy újat ServerWebExchange példány az eredetitől csere objektum, módosítva az eredetit ServerHttpRequest példa:

ServerWebExchange modifiedExchange = exchange.mutate () // Itt módosítjuk az eredeti kérést: .request (originalRequest -> originalRequest) .build (); visszatérő lánc.szűrő (módosítottcsere);

Itt az ideje frissíteni az eredeti kérés URI-t a lekérdezési paraméterek eltávolításával:

originalRequest -> originalRequest.uri (UriComponentsBuilder.fromUri (exchange.getRequest () .getURI ()) .replaceQueryParams (új LinkedMultiValueMap ()) .build () .toUri ())

Oda megyünk, most kipróbálhatjuk. A kódbázisban naplóbejegyzéseket adtunk hozzá, mielőtt felhívtuk a következő láncszűrőt, hogy lássuk, pontosan mit küldenek a kérésben.

5.2. A válasz módosítása

Ugyanazon esettel folytatva most meghatározunk egy „post” szűrőt. Képzeletbeli szolgáltatásunk egy egyedi fejléc beolvasására használta, hogy jelezze a végül választott nyelvet a hagyományos helyett Tartalom-Nyelv fejléc.

Ezért azt akarjuk, hogy új szűrőnk adja hozzá ezt a válaszfejlécet, de csak akkor, ha a kérelem tartalmazza a területi beállítás fejlécet, amelyet az előző szakaszban vezettünk be.

(csere, lánc) -> {visszatérési lánc.szűrő (csere) .majd (Mono.fromRunnable (() -> {ServerHttpResponse response = exchange.getResponse (); Opcionális.FNullable (exchange.getRequest () .getQueryParams (). getFirst ("locale")) .ifPresent (qp -> {String responseContentLanguage = response.getHeaders () .getContentLanguage () .getLanguage (); response.getHeaders () .add ("Bael-Custom-Language-Header", responseContentLanguage );});})); }

Könnyen beszerezhetünk hivatkozást a válaszobjektumra, és a módosításhoz nem kell másolatot készítenünk, mint a kérésnél.

Ez jó példa a szűrők sorrendjének fontosságára a láncban; ha az előző szakaszban létrehozott után konfiguráljuk ennek a szűrőnek a végrehajtását, akkor a csere Az itt található objektum hivatkozást tartalmaz a ServerHttpRequest aminek soha nem lesz lekérdezési paramétere.

Még az sem számít, hogy ez ténylegesen az összes „elő” szűrő végrehajtása után vált ki, mert az eredeti kérésre még mindig van utalásunk, a mutálódik logika.

5.3. Láncolási kérelmek más szolgáltatásokhoz

Hipotetikus forgatókönyvünk következő lépése egy harmadik szolgáltatásra támaszkodik, melyik jelzésére Elfogadás-Nyelv fejlécet kell használnunk.

Így létrehozunk egy új szűrőt, amely felhívja ezt a szolgáltatást, és annak választörzsét használja a proxy szolgáltatás API kérési fejléceként.

Reaktív környezetben ez azt jelenti, hogy a kéréseket láncolják az aszinkron végrehajtás blokkolásának elkerülése érdekében.

Szűrőnkben azzal kezdjük, hogy a kérelmet elküldjük a nyelvi szolgáltatásnak:

(csere, lánc) -> {return WebClient.create (). get () .uri (config.getLanguageEndpoint ()) .exchange () // ...}

Vegyük észre, hogy visszatérünk erre a gördülékeny műveletre, mert mint mondtuk, a hívás kimenetét a proxykeresésünkkel láncoljuk be.

A következő lépés a nyelv kibontása - vagy a válasz törzséből, vagy a konfigurációból, ha a válasz nem volt sikeres - és elemzi azt:

// ... .flatMap (response -> {return (response.statusCode () .is2xxSuccessful ())? response.bodyToMono (String.class): Mono.just (config.getDefaultLanguage ());}). map ( LanguageRange :: parse) // ...

Végül beállítjuk a LanguageRange értéket a kérés fejléceként, mint korábban, és folytassuk a szűrő láncot:

.map (tartomány -> {exchange.getRequest () .mutate () .headers (h -> h.setAcceptLanguage (tartomány)) .build (); return exchange;}). flatMap (lánc :: szűrő);

Ez az, most az interakciót nem blokkoló módon hajtják végre.

6. Következtetés

Most, hogy megtanultuk az egyéni Spring Cloud Gateway szűrők írását és láttuk, hogyan kell kezelni a kérelem és a válasz entitásokat, készen állunk arra, hogy a legtöbbet hozzuk ki ebből a keretrendszerből.

Mint mindig, az összes teljes példa megtalálható a GitHub oldalán. Ne feledje, hogy teszteléséhez integrációt és élő teszteket kell futtatnunk a Mavenen keresztül.