YMNNALFT: HTTP 客戶端

工程 | Josh Long | 2021 年 1 月 11 日 | ...

歡迎來到另一期的您可能不需要另一個函式庫 (YMNNALFT)!自 2016 年以來,我花了很多時間在 我的 Spring Tips 影片 中闡明 (或者試圖闡明!) Spring 生態系統中一些更巨大的機會。然而,今天,我以不同的精神來到這裡,想要關注那些小而有時隱藏的瑰寶,它們可以完成出色的工作,並且可能會讓您免於額外的第三方依賴及其隱含的複雜性。

今天我們將看看一個多合一、方便好用的 HTTP 客戶端,WebClient

HTTP 服務是常見的資料來源。網路是 HTTP 可擴展性和彈性的存在證明,並且在建構網路服務時,它為 HTTP 的約束 (例如 REST) 提供了一個非常引人注目的案例。如果 HTTP 是開放網路的通用語,那麼我們必須以 HTTP 提出問題。

市面上有一些很棒的函式庫 (例如 Apache HttpComponents ClientOkHttp),它們的工作方式大致相同。如果您沒有特別考慮某一個,但想要一個世界級的選擇並且已經在使用 Spring,則可以使用 Spring Webflux 中基於 Netty 的非阻塞 HTTP 客戶端,即 WebClient

WebClientRestTemplate 的反應式、非阻塞替代方案。我喜歡 WebClient,因為它是非阻塞的:用於發出網路請求的客戶端線程不會被掛起等待網路服務回應。這意味著更好的可擴展性 - 線程可以自由地用於其他用途。我也喜歡 WebClient,因為它使用 Reactive Streams API,使得組合更加容易。我們剛剛在最後一個範例中看到了一些。

您可以使用 WebClient 與任何舊的 HTTP 端點通信,而不僅僅是那些使用伺服器端的非阻塞或反應式 API 撰寫的端點。而且,更好的是,即使在其他非反應式程式碼中,您也可以使用 WebClient。如果 - 而且請聽我說完 - 如果有人想使用 WebClient,但無法使用完整的反應式 Spring 網路堆疊怎麼辦?

考倒我了!我無法想像為什麼您不想使用反應式 HTTP 堆疊。即使您不使用也沒關係,因為僅使用 WebClient 仍然有很多價值。您可以使用 WebClient 發出一個或多個 HTTP 呼叫,然後同時組合結果。它是輕鬆的 scatter-gather 類型的組合的理想選擇。即使您沒有使用反應式 HTTP 運行時,這也是一件很自然的事情,如果您在 Servlet 環境中運行,情況可能就是如此。

您需要以下依賴項才能在類別路徑上取得 WebClient

  • Spring Initializr 上的 Reactive Web - org.springframework.boot : spring-boot-starter-webflux

讓我們看一些程式碼。此範例執行兩件不同的事情 (同時)

  • 使用支援 Spring Initializr 的 HTTP API 初始化新專案
  • 它使用 Spring API 檢索所有活動的開放原始碼 Spring 專案
package bootiful.httpclient.webclient;

import lombok.ToString;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

import java.net.URI;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;

import static java.nio.file.StandardOpenOption.CREATE;

@EnableAsync
@SpringBootApplication
public class BootifulApplication {

	public static void main(String[] args) {
		SpringApplication.run(BootifulApplication.class, args);
	}

	@Bean
	WebClient webClient(WebClient.Builder builder) {
		return builder//
				.filter(//
						(clientRequest, exchangeFunction) -> exchangeFunction//
								.exchange(clientRequest)//
								.doOnNext(response -> System.out.println("got a WebClient response: " + response))//
				) //
				.build();
	}

	@Bean
	ApplicationListener<ApplicationReadyEvent> ready(@Value("file://${user.home}/Desktop/output.zip") Path output,
			WebClient client) {

		return event -> {

			// initialize a new Spring Boot project .zip archive
			Mono<DataBuffer> db = client.get()//
					.uri(URI.create("https://start.spring.io/starter.zip"))//
					.accept(MediaType.APPLICATION_OCTET_STREAM)//
					.retrieve()//
					.bodyToMono(DataBuffer.class);

			// gets written out to ~/output.zip
			Mono<Boolean> write = DataBufferUtils.write(db, output, CREATE).thenReturn(true);

			// enumerate all the active Spring projects using the
			// JSON API while we're at it...
			Mono<ProjectsResponse> json = client//
					.get()//
					.uri(URI.create("https://spring.dev.org.tw/api/projects"))//
					.retrieve()//
					.bodyToMono(ProjectsResponse.class);

			// look ma! No threading code! this will launch both network
			// calls (the .zip and the json) at the same time
			Mono.zip(write, json).subscribe(tuple -> enumerate(tuple.getT2()));
		};
	}

	private void enumerate(ProjectsResponse pr) {
		pr._embedded //
				.projects //
						.stream() //
						.filter(p -> p.status.equalsIgnoreCase("active")) //
						.forEach(project -> System.out.println(project.toString()));
	}

}

@ToString
class ProjectsResponse {

	public Embedded _embedded = new Embedded();

	@ToString
	public static class Project {

		public String name, slug, status, repositoryUrl;

	}

	@ToString
	public static class Embedded {

		public Collection<Project> projects = new ArrayList<>();

	}

}

如果您想引入 WebClient,想使用反應式網路堆疊的其餘部分,那麼您需要告訴 Spring Boot。 否則,Spring Boot 將嘗試啟動基於 Netty 的 Spring Webflux 環境。您需要在 application.properties 中進行以下配置。

spring.main.web-application-type=none

您喜歡這種一目了然的方法嗎?您學到什麼了嗎?與往常一樣,我很想收到您的來信,所以 請在 Twitter 上發聲 (@starbuxman) !我將帶著另一期的 YMNNALFT 回來,所以請務必不要錯過。

取得 Spring 電子報

隨時關注 Spring 電子報

訂閱

領先一步

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

檢視全部