YMNNALFT: WebSockets

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

欢迎来到另一期 你可能不需要另一个库 (YMNNALFT) 系列!自 2016 年以来,我花了很多时间在 我的 Spring Tips 视频 中阐明 (或者至少努力阐明!) Spring 生态系统中一些巨大的机会。然而,今天我以不同的精神来到这里,希望专注于那些有时隐藏着的小珍宝,它们能够完成出色的工作,并且可能让你免去额外的第三方依赖及其隐含的复杂性。

开放网络长期以来为那些希望构建和大规模部署服务和应用的通用平台的人们带来了希望。我们知道,一旦一些事情得到改进,网络就会变得引人注目。人们可以提供丰富的客户端,这些客户端可以通过刷新浏览器页面进行升级。他们可以提供以数据和多媒体为中心的沉浸式体验。我们知道,如果人们拥有正确的构建网站和服务的范式,他们就能做到这些。但俗话说,没有苦就没有甜,所以社区开始了一项任务,寻找构建网站和服务的 最糟糕 方法,而孩子们,这就是我们如何得到 PHP 的故事。

完。 ...

好了,事情还有 一点点 补充。起初,后端和客户端都存在严重的限制。然而,客户端的问题比后端的问题持续的时间要长得多。到 2000 年代初,每个重要的编程语言社区都可以构建 HTTP 服务,但客户端的能力停滞不前。(这几乎就像某种主要力量在恶意阻碍开放网络的发展。但为什么?是谁?这是一个谜,一个我想我们永远无法解开的谜...)

开放网络自行演变。它对自身进行了 HTTP PATCH。在 90 年代末,我们有了 PayPal,这使得安全商务成为可能。在 2000 年代初,我们得到了 HTTP 的一种约束,叫做 REST (它代表 Really Easy Service Transactions,还是 REcent Software Trend?不。那不对。Representational State Transfer!这听起来对了...)。然后 "Ajax" (不,不是 清洁产品) 出现了,它允许客户端在原地向服务发起请求,而无需强制进行另一次 HTTP 往返以获取新页面。太好了。然后我们花费了大约五年痛苦的时间试图找到将数据从服务器推送到客户端的方法,而不是在响应客户端请求时向客户端发送数据。

我们确实尝试了 一切。补丁层出不穷。(这是一个有趣的事实:糟糕的 JavaScript 在 Node.js 出现之前就存在 多年 了!) 最终,在 2011 年,我们得到了一个所有 HTTP 浏览器厂商和 HTTP 服务器厂商都能一致支持的标准,它解决了我们 70% 的需求:WebSockets。WebSockets 太棒了!它们是向基于浏览器的应用程序引入异步通信的最佳方式。它们快速、轻量且易于实现。

虽然有很多框架可以用来实现 WebSocket 端点,但你不必舍近求远,Spring 本身就为响应式和非响应式服务提供了开箱即用的支持。让我们看一个使用 Spring Webflux(响应式 Web 运行时)的服务示例。

你需要以下依赖。

  • Spring Initializr 上的响应式 Web - org.springframework.boot : spring-boot-starter-webflux

这是我放入 application.properties 的内容

spring.main.web-application-type=reactive

这是代码

package bootiful.websockets.service;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.HandlerMapping;
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
import org.springframework.web.reactive.socket.WebSocketHandler;
import org.springframework.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.server.support.WebSocketHandlerAdapter;
import reactor.core.publisher.Flux;

import java.util.Map;

@SpringBootApplication
public class BootifulApplication {

	public static void main(String[] args) {
		System.setProperty("spring.profiles.active", "wsserver");
		SpringApplication.run(BootifulApplication.class, args);
	}

	@Bean
	SimpleUrlHandlerMapping greetingsHm() {
		return new SimpleUrlHandlerMapping(Map.of("/ws/greetings", greetingsWsh()), 10);
	}

	@Bean
	WebSocketHandler greetingsWsh() {
		return session -> {

			Flux<WebSocketMessage> out = session.receive().map(WebSocketMessage::getPayloadAsText)
					.flatMap(name -> Flux.just("Hi, " + name).map(session::textMessage));

			return session.send(out);
		};
	}

}

SimpleUrlHandlerMappingWebSocketHandler 映射到一个 HTTP URI。WebSocketHandler 提供了响应式 WebSocket 端点的逻辑,将传入的载荷(一个名字)转化为一个问候语(Hi, NAME!)发送给客户端。

现在,我要做一件我通常不会做的事情。朋友们,如果还有 任何 其他方法,我肯定会选择那种方法,而不是这种相当不体面的做法。我在礼貌的场合不会这样做,但我感觉没有其他方法可以完成这件事,没有其他方法可以展示与 WebSocket 端点通信是多么微不足道。我做这个决定并不容易。

我.. 将要使用 JavaScript

    window.addEventListener('load', () => {
        const ws = new WebSocket('ws://localhost:8080/ws/greetings')
        ws.addEventListener('open', () => {
            ws.send('JavaScript Fans')
        })
        ws.addEventListener('message', (message) => {
            console.log(message.data)
        })
    })


在这个例子中,我们在 JavaScript 中设置了一个 WebSocket 对象,注册了一个回调函数,以便在 WebSocket 最终准备就绪时发送数据,并注册了另一个回调函数来接收任何到达的回复。打开你的浏览器 开发者工具(不同浏览器说明不同,但如果你在 Mac 上使用 Chrome 或 Firefox,可以尝试 OPTION + CMD + I),然后选择 控制台。你将在那里看到 WebSocket 端点的响应。

通过更多代码,我们还可以使用 Spring WebSocketClient 与该服务通信。

你需要以下依赖。

  • Spring Initializr 上的响应式 Web - org.springframework.boot : spring-boot-starter-webflux

这是代码

package bootiful.websockets.client;

import lombok.SneakyThrows;
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.web.reactive.socket.WebSocketMessage;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import reactor.core.publisher.Mono;

import java.net.URI;

@SpringBootApplication
public class BootifulApplication {

	@SneakyThrows
	public static void main(String[] args) {
		System.setProperty("spring.profiles.active", "wsclient");
		SpringApplication.run(BootifulApplication.class, args);
		Thread.sleep(5_000);
	}

	@Bean
	WebSocketClient webSocketClient() {
		return new ReactorNettyWebSocketClient();
	}

	@Bean
	ApplicationListener<ApplicationReadyEvent> ready(WebSocketClient client) {
		return event -> client.execute(URI.create("ws://localhost:8080/ws/greetings"), webSocketSession -> {
			WebSocketMessage world = webSocketSession.textMessage("Spring Fans");
			return webSocketSession.send(Mono.just(world))
					.thenMany(webSocketSession.receive().map(WebSocketMessage::getPayloadAsText).log()).then();
		})//
				.subscribe();
	}

}

这是我放入 application.properties 的内容

spring.main.web-application-type=none

WebSocket 让我们的基于浏览器的客户端更加生动。然而,我更喜欢使用 RSocket 进行服务间通信。

你喜欢这种一瞥珍宝的方法吗?你学到了什么吗?一如既往,我渴望听到你的意见,所以 请在 Twitter (@starbuxman) 上畅所欲言!我将带来 YMNNALFT 的下一期,所以务必不要错过。

获取 Spring 新闻邮件

订阅 Spring 新闻邮件保持联系

订阅

领先一步

VMware 提供培训和认证,助你加速前进。

了解更多

获取支持

Tanzu Spring 通过一个简单的订阅提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看全部