Pelanggan Web Spring 5

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan memeriksa WebClient , yang merupakan pelanggan web reaktif yang diperkenalkan pada Spring 5.

Kami juga akan melihat WebTestClient, yang WebClient direka untuk digunakan dalam ujian.

2. Apakah itu Pelanggan Web ?

Secara sederhana , WebClient adalah antara muka yang mewakili titik masuk utama untuk melaksanakan permintaan web.

Ia dibuat sebagai sebahagian daripada modul Spring Web Reactive, dan akan menggantikan RestTemplate klasik dalam senario ini. Sebagai tambahan, klien baru adalah penyelesaian reaktif dan tidak menyekat yang berfungsi melalui protokol HTTP / 1.1.

Akhirnya, antara muka mempunyai satu pelaksanaan, kelas DefaultWebClient , yang akan kami bekerjasama.

3. Kebergantungan

Oleh kerana kita menggunakan aplikasi Spring Boot, kita memerlukan pergantungan spring-boot-starter-webflux , dan juga projek Reactor.

3.1. Membangun dengan Maven

Mari tambahkan kebergantungan berikut ke fail pom.xml :

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

3.2. Membangun dengan Gradle

Dengan Gradle, kita perlu menambahkan entri berikut ke fail build.gradle :

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

4. Bekerja dengan Pelanggan Web

Untuk bekerja dengan pelanggan dengan betul, kita perlu tahu bagaimana:

  • buat contoh
  • buat permintaan
  • menangani tindak balas

4.1. Membuat Instance Pelanggan Web

Terdapat tiga pilihan untuk dipilih. Yang pertama adalah membuat objek WebClient dengan tetapan lalai:

WebClient client1 = WebClient.create(); 

Pilihan kedua adalah memulakan instance WebClient dengan URI asas yang diberikan:

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

Pilihan ketiga (dan yang paling maju) ialah membina pelanggan dengan menggunakan kelas DefaultWebClientBuilder , yang membolehkan penyesuaian penuh:

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

4.2. Membuat Contoh WebClient dengan Timeout

Sering kali, waktu tunggu HTTP lalai selama 30 saat terlalu lambat untuk keperluan kita, jadi mari kita lihat bagaimana mengkonfigurasinya untuk contoh WebClient kami .

Kelas teras yang kami gunakan adalah TcpClient.

Di sana kita dapat menetapkan masa tamat sambungan melalui nilai ChannelOption.CONNECT_TIMEOUT_MILLIS . Kami juga dapat menetapkan batas waktu membaca dan menulis menggunakan ReadTimeoutHandler dan WriteTimeoutHandler , masing-masing:

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

Perhatikan bahawa walaupun kami dapat memanggil waktu tunggu atas permintaan pelanggan kami juga, ini adalah tamat masa isyarat, bukan sambungan HTTP, atau tamat / baca tamat; ini adalah masa tamat untuk penerbit Mono / Flux.

4.3. Menyiapkan Permintaan

Mula-mula kita perlu menentukan kaedah permintaan HTTP dengan menggunakan kaedah (kaedah HttpMode) atau memanggil kaedah pintasannya seperti mendapatkan , mengepos , dan menghapus :

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

Langkah seterusnya adalah memberikan URL. Kita boleh menyebarkannya ke API uri sebagai String atau contoh java.net.URL :

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

Kemudian kita boleh menetapkan badan permintaan, jenis kandungan, panjang, kuki, atau tajuk jika perlu.

Sebagai contoh, jika kita ingin menetapkan badan permintaan, ada dua cara yang tersedia: mengisinya dengan BodyInserter atau mendelegasikan karya ini kepada Penerbit :

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"));

The BodyInserter adalah satu antara muka bertanggungjawab populating yang ReactiveHttpOutputMessage badan dengan mesej output diberikan dan konteks digunakan semasa pemasukan. A Penerbit merupakan komponen reaktif yang bertanggungjawab dalam menyediakan beberapa berpotensi kurnia yang amat besar unsur-unsur disusun.

Cara kedua adalah kaedah badan , iaitu jalan pintas untuk kaedah badan asal (BodyInserter inserter) .

Untuk mengurangkan proses mengisi BodyInserter, ada kelas BodyInserters dengan beberapa kaedah utiliti berguna:

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

Ia juga boleh dilakukan dengan MultiValueMap :

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

Atau dengan menggunakan satu objek:

BodyInserter inserter3 = BodyInserters.fromObject(new Object()); 

Setelah kita menetapkan isi badan, kita dapat menetapkan tajuk, kuki, dan jenis media yang boleh diterima. Nilai akan ditambahkan ke nilai yang telah ditetapkan ketika memberi contoh kepada klien.

Juga, ada dukungan tambahan untuk header yang paling umum digunakan seperti "If-None-Match", "If-Modified-Sejak", "Terima", dan "Accept-Charset".

Here's an example of how these values can be used:

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

4.4. Getting a Response

The final stage is sending the request and receiving a response. This can be done with either the exchange or the retrieve method.

These methods differ in return types; the exchange method provides a ClientResponse along with its status and headers, while the retrieve method is the shortest path to fetching a body directly:

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

It's important to pay attention to the bodyToMono method, which will throw a WebClientException if the status code is 4xx (client error) or 5xx (server error). We use the block method on Monos to subscribe and retrieve actual data that was sent with the response.

5. Working with the WebTestClient

The WebTestClient is the main entry point for testing WebFlux server endpoints. It has a very similar API to the WebClient, and it delegates most of the work to an internal WebClient instance focusing mainly on providing a test context. The DefaultWebTestClient class is a single interface implementation.

The client for testing can be bound to a real server or work with specific controllers or functions.

5.1. Binding to a Server

To complete end-to-end integration tests with actual requests to a running server, we can use the bindToServer method:

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

5.2. Binding to a Router

We can test a particular RouterFunction by passing it to the bindToRouterFunction method:

RouterFunction function = RouterFunctions.route( RequestPredicates.GET("/resource"), request -> ServerResponse.ok().build() ); WebTestClient .bindToRouterFunction(function) .build().get().uri("/resource") .exchange() .expectStatus().isOk() .expectBody().isEmpty(); 

5.3. Binding to a Web Handler

The same behavior can be achieved with the bindToWebHandler method, which takes a WebHandler instance:

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

5.4. Binding to an Application Context

A more interesting situation occurs when we're using the bindToApplicationContext method. It takes an ApplicationContext and analyses the context for controller beans and @EnableWebFlux configurations.

If we inject an instance of the ApplicationContext, a simple code snippet may look like this:

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

5.5. Binding to a Controller

A shorter approach would be providing an array of controllers we want to test by the bindToController method. Assuming we've got a Controller class and we injected it into a needed class, we can write:

@Autowired private Controller controller; WebTestClient testClient = WebTestClient.bindToController(controller).build(); 

5.6. Making a Request

After building a WebTestClient object, all following operations in the chain are going to be similar to the WebClient until the exchange method (one way to get a response), which provides the WebTestClient.ResponseSpec interface to work with useful methods like the expectStatus, expectBody, and expectHeader:

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

6. Conclusion

Dalam artikel ini, kami menjelajahi WebClient, mekanisme Spring baru yang disempurnakan untuk membuat permintaan di pihak pelanggan.

Kami juga melihat manfaat yang diberikannya dengan melakukan konfigurasi klien, menyiapkan permintaan, dan memproses respons.

Semua coretan kod yang disebutkan dalam artikel boleh didapati di repositori GitHub kami.