Spring 5 WebClient

1. Overzicht

In deze tutorial gaan we de Web cliënt, een reactieve webclient die in Spring 5 is geïntroduceerd.

We gaan ook kijken naar de WebTestClient, een Web cliënt ontworpen om te worden gebruikt in tests.

2. Wat is het Web cliënt?

Simpel gezegd, Web cliënt is een interface die het belangrijkste toegangspunt voor het uitvoeren van webverzoeken vertegenwoordigt.

Het is gemaakt als onderdeel van de Spring Web Reactive-module en zal de klassieker vervangen RestTemplate in deze scenario's. Bovendien is de nieuwe client een reactieve, niet-blokkerende oplossing die werkt via het HTTP / 1.1-protocol.

Ten slotte heeft de interface een enkele implementatie, de DefaultWebClient klasse, waarmee we zullen werken.

3. Afhankelijkheden

Omdat we een Spring Boot-applicatie gebruiken, hebben we de spring-boot-starter-webflux afhankelijkheid, evenals het Reactor-project.

3.1. Bouwen met Maven

Laten we de volgende afhankelijkheden toevoegen aan het pom.xml het dossier:

 org.springframework.boot spring-boot-starter-webflux org.projectreactor reactor-spring 1.0.1.RELEASE 

3.2. Bouwen met Gradle

Met Gradle moeten we de volgende vermeldingen toevoegen aan het build.gradle het dossier:

afhankelijkheden {compileer 'org.springframework.boot: spring-boot-starter-webflux' compileer 'org.projectreactor: reactor-spring: 1.0.1.RELEASE'}

4. Werken met de Web cliënt

Om goed samen te werken met de klant, moeten we weten hoe we:

  • maak een instantie
  • een verzoek doen
  • behandel het antwoord

4.1. Een Web cliënt Voorbeeld

Er zijn drie opties om uit te kiezen. De eerste is het maken van een Web cliënt object met standaardinstellingen:

WebClient client1 = WebClient.create (); 

De tweede optie is om een Web cliënt instantie met een bepaalde basis-URI:

WebClient client2 = WebClient.create ("// localhost: 8080"); 

De derde optie (en de meest geavanceerde) is het bouwen van een client met behulp van de DefaultWebClientBuilder class, die volledige aanpassing mogelijk maakt:

WebClient client3 = WebClient .builder () .baseUrl ("// localhost: 8080") .defaultCookie ("cookieKey", "cookieValue") .defaultHeader (HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .defaultUriVariables (Collections.URL) "," // localhost: 8080 ")) .build ();

4.2. Een Web cliënt Instantie met time-outs

Vaak zijn de standaard HTTP-time-outs van 30 seconden te traag voor onze behoeften, dus laten we kijken hoe we ze kunnen configureren voor onze Web cliënt voorbeeld.

De kernklasse die we gebruiken is TcpClient.

Daar kunnen we stel de time-out voor de verbinding in via de Kanaaloptie.CONNECT_TIMEOUT_MILLIS waarde. We kunnen ook stel de time-outs voor lezen en schrijven in met een ReadTimeoutHandler en een WriteTimeoutHandler, respectievelijk:

TcpClient tcpClient = TcpClient .create () .option (ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000) .doOnConnected (verbinding -> {connection.addHandlerLast (nieuwe ReadTimeoutHandler (5000, TimeUnit.MILLISECONDS)); WriteoutLastTime, Writeout (5000, TimeUnit.MILLISECONDS)). MILLISECONDS));}); WebClient-client = WebClient.builder () .clientConnector (nieuwe ReactorClientHttpConnector (HttpClient.from (tcpClient))) .build ();

Let daar op terwijl we kunnen bellen time-out ook op verzoek van onze klant is dit een time-out voor het signaal, geen HTTP-verbinding of een time-out voor lezen / schrijven; het is een time-out voor de Mono / Flux-uitgever.

4.3. Een verzoek voorbereiden

Eerst moeten we een HTTP-methode van een verzoek specificeren door aan te roepen methode (HttpMethod-methode) of door de sneltoetsmethoden aan te roepen, zoals krijgen, post, en verwijderen:

WebClient.UriSpec request1 = client3.method (HttpMethod.POST); WebClient.UriSpec request2 = client3.post ();

De volgende stap is om een ​​URL op te geven. We kunnen het doorgeven aan de uri API als een Draad of een java.net.URL voorbeeld:

WebClient.RequestBodySpec uri1 = client3 .method (HttpMethod.POST) .uri ("/ resource"); WebClient.RequestBodySpec uri2 = client3 .post () .uri (URI.create ("/ resource"));

Vervolgens kunnen we een verzoektekst, inhoudstype, lengte, cookies of kopteksten instellen als dat nodig is.

Als we bijvoorbeeld een verzoektekst willen instellen, zijn er twee beschikbare manieren: het vullen met een BodyInserter of dit werk te delegeren aan een Uitgever:

WebClient.RequestHeadersSpec requestSpec1 = WebClient .create () .method (HttpMethod.POST) .uri ("/ resource") .body (BodyInserters.fromPublisher (Mono.just ("data")), String.class); WebClient.RequestHeadersSpec requestSpec2 = WebClient .create ("// localhost: 8080") .post () .uri (URI.create ("/ resource")) .body (BodyInserters.fromObject ("data"));

De BodyInserter is een interface die verantwoordelijk is voor het vullen van een ReactiveHttpOutputMessage body met een bepaald uitvoerbericht en een context die tijdens het invoegen wordt gebruikt. EEN Uitgever is een reactieve component die verantwoordelijk is voor het leveren van een potentieel onbegrensd aantal opeenvolgende elementen.

De tweede manier is de lichaam methode, wat een snelkoppeling is naar het origineel body (BodyInserter inserter) methode.

Om het vullen van een BodyInserter, er is een BodyInserters klasse met een aantal handige hulpprogramma's:

BodyInserter inserter1 = BodyInserters .fromPublisher (Abonnee :: onComplete, String.class); 

Het is ook mogelijk met een MultiValueMap:

LinkedMultiValueMap map = nieuwe LinkedMultiValueMap (); map.add ("key1", "value1"); map.add ("key2", "value2"); BodyInserter inserter2 = BodyInserters.fromMultipartData (kaart); 

Of door een enkel object te gebruiken:

BodyInserter inserter3 = BodyInserters.fromObject (nieuw object ()); 

Nadat we de body hebben ingesteld, kunnen we headers, cookies en acceptabele mediatypen instellen. Waarden worden toegevoegd aan de waarden die al zijn ingesteld bij het instantiëren van de client.

Er is ook extra ondersteuning voor de meest gebruikte kopteksten, zoals "If-None-Match", "If-Modified-Since", "Accepteren", en "Accept-Charset".

Hier is een voorbeeld van hoe deze waarden kunnen worden gebruikt:

WebClient.ResponseSpec response1 = uri1 .body (inserter3) .header (HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .accept (MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML) .acceptCharset (Charset. "*") .ifModifiedSince (ZonedDateTime.now ()) .retrieve ();

4.4. Een reactie krijgen

De laatste fase is het verzenden van het verzoek en het ontvangen van een antwoord. Dit kan worden gedaan met de uitwisseling of de ophalen methode.

Deze methoden verschillen in retourtypen; de uitwisseling methode biedt een ClientResponse samen met de status en headers, terwijl de ophalen methode is de kortste weg om een ​​lichaam rechtstreeks op te halen:

String response2 = request1.exchange () .block () .bodyToMono (String.class) .block (); String response3 = request2 .retrieve () .bodyToMono (String.class) .block ();

Het is belangrijk om aandacht te besteden aan de bodyToMono methode, die een WebClientException als de statuscode is 4xx (clientfout) of 5xx (Serverfout). Wij gebruiken de blok methode op Monos om zich te abonneren en actuele gegevens op te halen die met het antwoord zijn verzonden.

5. Werken met de WebTestClient

De WebTestClient is het belangrijkste toegangspunt voor het testen van WebFlux-server-eindpunten. Het heeft een zeer vergelijkbare API als de Web cliënt, en het delegeert het meeste werk aan een intern Web cliënt instantie die zich voornamelijk richt op het bieden van een testcontext. De DefaultWebTestClient class is een enkele interface-implementatie.

De client voor testen kan worden gebonden aan een echte server of werken met specifieke controllers of functies.

5.1. Binden met een server

Om end-to-end integratietests te voltooien met daadwerkelijke verzoeken aan een actieve server, kunnen we de bindToServer methode:

WebTestClient testClient = WebTestClient .bindToServer () .baseUrl ("// localhost: 8080") .build (); 

5.2. Binden met een router

We kunnen een bepaald testen RouterFunctie door het door te geven aan de bindToRouterFunction methode:

RouterFunction-functie = RouterFunctions.route (RequestPredicates.GET ("/ resource"), verzoek -> ServerResponse.ok (). Build ()); WebTestClient .bindToRouterFunction (functie) .build (). Get (). Uri ("/ resource") .exchange () .expectStatus (). IsOk () .expectBody (). IsEmpty (); 

5.3. Binden aan een webhandler

Hetzelfde gedrag kan worden bereikt met de bindToWebHandler methode, waarvoor een WebHandler voorbeeld:

WebHandler-handler = exchange -> Mono.empty (); WebTestClient.bindToWebHandler (handler) .build ();

5.4. Binden aan een toepassingscontext

Een interessantere situatie doet zich voor wanneer we de bindToApplicationContext methode. Er is een ApplicationContext en analyseert de context voor controllerbeans en @EnableWebFlux configuraties.

Als we een instantie van de ApplicationContextkan een eenvoudig codefragment er als volgt uitzien:

@Autowired private ApplicationContext-context; WebTestClient testClient = WebTestClient.bindToApplicationContext (context) .build (); 

5.5. Binden aan een controller

Een kortere benadering zou zijn om een ​​reeks controllers te leveren die we willen testen door de bindToController methode. Ervan uitgaande dat we een Controller klasse en we hebben het in een benodigde klasse geïnjecteerd, we kunnen schrijven:

@Autowired privé Controller-controller; WebTestClient testClient = WebTestClient.bindToController (controller) .build (); 

5.6. Een verzoek doen

Na het bouwen van een WebTestClient object, zullen alle volgende bewerkingen in de keten vergelijkbaar zijn met de Web cliënt tot de uitwisseling methode (een manier om een ​​antwoord te krijgen), die de WebTestClient.ResponseSpec interface om te werken met handige methoden zoals de verwachtenStatus, Verwachtlichaam, en verwachtenHeader:

WebTestClient .bindToServer () .baseUrl ("// localhost: 8080") .build () .post () .uri ("/ resource") .exchange () .expectStatus (). IsCreated () .expectHeader (). ValueEquals ("Content-Type", "application / json") .expectBody (). IsEmpty (); 

6. Conclusie

In dit artikel hebben we de Web cliënt, een nieuw verbeterd veermechanisme voor het doen van verzoeken aan de klantzijde.

We hebben ook gekeken naar de voordelen die het biedt door de client te configureren, het verzoek voor te bereiden en het antwoord te verwerken.

Alle codefragmenten die in het artikel worden genoemd, zijn te vinden in onze GitHub-repository.