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.