YMNNALFT:HTTP 客户端

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

欢迎来到 你可能不需要另一个库 (YMNNALFT) 的另一期! 自 2016 年以来,我花了很多时间在我的 Spring Tips 视频中阐明(或者试图这样做!)Spring 生态系统中的一些更大的机会。 然而,今天,我以不同的精神来到你身边,希望专注于那些小而有时隐藏的宝石,它们做着很棒的事情,并且可以让你免于额外的第三方依赖及其隐含的复杂性。

今天,我们将看看一个多合一的、方便的 HTTP 客户端,WebClient

HTTP 服务是常见的数据来源。 Web 是 HTTP 的可伸缩性和弹性的存在证明,并且在构建网络服务时,它为 HTTP 的约束(如 REST)提出了一个非常令人信服的案例。 如果 HTTP 是开放 Web 的通用语言,那么我们必须用 HTTP 提出问题。

那里有一些很棒的库(例如 Apache HttpComponents ClientOkHttp)以几乎相同的方式工作。 如果你没有特别想到哪一个,但想要一个世界级的选择,并且已经在使用 Spring,你可以使用 Spring Webflux 中基于 Netty 的非阻塞 HTTP 客户端 WebClient

WebClientRestTemplate 的响应式、非阻塞替代方案。 我喜欢 WebClient,因为它是非阻塞的:用于发出网络请求的客户端线程不会被挂起等待网络服务响应。 这意味着更好的可伸缩性 - 线程可以自由地用于其他用途。 我也喜欢 WebClient,因为它使用 Reactive Streams API,使组合容易。 我们刚刚在最后一个示例中看到了一些。

你可以使用 WebClient 与任何旧的 HTTP 端点通信,而不仅仅是那些使用服务器端的非阻塞或响应式 API 编写的端点。 而且,更好的是,即使在其他非响应式代码中,你也可以使用 WebClient。 如果 - 并且在这里听我说完 - 如果有人想使用 WebClient 但不能使用完整的响应式 Spring Web 堆栈怎么办?

搜遍我吧! 我无法想象你为什么不想使用响应式 HTTP 堆栈。 即使你不这样做也没关系,因为单独使用 WebClient 仍然有很多价值。 你可以使用 WebClient 发出一个或多个 HTTP 调用,然后并发地组合结果。 它非常适合轻松的 scatter-gather 类型的组合。 即使你没有使用响应式 HTTP 运行时(如果你在 Servlet 环境中运行),这也可能是你想要做的自然的事情。

你需要以下依赖项才能在类路径上获取 WebClient

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

让我们看一些代码。 这个例子做了两件不同的事情(并发地)

  • 使用 HTTP API 初始化一个新项目,该 API 为 Spring Initializr 提供支持
  • 它使用 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://springframework.org.cn/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, 但_不想_使用响应式 Web 堆栈的其余部分,那么你需要告诉 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 社区中所有即将举行的活动。

查看全部