【Web应用架构】使用WebSocket构建交互式web应用程序

简介: 【Web应用架构】使用WebSocket构建交互式web应用程序

本指南引导您完成创建“Hello, world”应用程序的过程,该应用程序在浏览器和服务器之间来回发送消息。WebSocket是TCP之上的一个轻薄的层。这使得它适合使用“子协议”来嵌入消息。在本指南中,我们使用STOMP消息传递和Spring创建交互式web应用程序。

 

你将建立什么

您将构建一个接收带有用户名称的消息的服务器。作为响应,服务器将把问候语推送到客户机订阅的队列中。

 

你需要的东西

  • 大约15分钟
  • 最喜欢的文本编辑器或IDE
  • JDK 1.8或更高版本
  • Gradle 4+或Maven 3.2+
  • 你也可以直接导入代码到你的IDE:
  • Spring Tool Suite(STS)
  • IntelliJ IDEA

 

如何完成本指南

与大多数Spring入门指南一样,您可以从头开始并完成每个步骤,或者可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到工作代码。

 

要从头开始,先从 Starting with Spring Initializr开始。

 

要跳过基本步骤,请做以下步骤:

 

下载并解压缩本指南的源存储库,或者使用Git克隆它:

 

  • 向前跳转Create a Resource Representation Class.
  • 完成后,可以对照gs-messaging-stomp-websocket/complete中的代码检查结果。

 

从Spring Initializr开始

对于所有Spring应用程序,都应该从Spring Initializr开始。Initializr提供了一种快速获取应用程序所需的所有依赖项的方法,并为您进行了大量设置。这个例子只需要Websocket依赖。下图显示了为这个示例项目设置的Initializr:

前面的图像显示了选择Maven作为构建工具的Initializr。你也可以使用Gradle。它还显示了com的值。示例和消息传递-stomp-websocket分别作为组和工件。您将在本示例的其余部分使用这些值。

下面的清单显示了当你选择Maven时创建的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.2.2.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.example</groupId>

<artifactId>messaging-stomp-websocket</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>messaging-stomp-websocket</name>

<description>Demo project for Spring Boot</description>


<properties>

<java.version>1.8</java.version>

</properties>


<dependencies>

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>


<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-test</artifactId>

  <scope>test</scope>

  <exclusions>

  <exclusion>

    <groupId>org.junit.vintage</groupId>

    <artifactId>junit-vintage-engine</artifactId>

  </exclusion>

  </exclusions>

</dependency>

</dependencies>


<build>

<plugins>

  <plugin>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-maven-plugin</artifactId>

  </plugin>

</plugins>

</build>


</project>

 

下面的清单显示了构建过程。gradle文件是创建时,你选择gradle:

plugins {

id 'org.springframework.boot' version '2.2.2.RELEASE'

id 'io.spring.dependency-management' version '1.0.8.RELEASE'

id 'java'

}


group = 'com.example'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'


repositories {

mavenCentral()

}


dependencies {

implementation 'org.springframework.boot:spring-boot-starter-websocket'

testImplementation('org.springframework.boot:spring-boot-starter-test') {

exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'

}

}


test {

useJUnitPlatform()

}

 

添加依赖关系

在这种情况下,Spring Initializr并没有提供您需要的一切。对于Maven,您需要添加以下依赖项:

<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>webjars-locator-core</artifactId>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>sockjs-client</artifactId>
  <version>1.0.2</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>stomp-websocket</artifactId>
  <version>2.3.3</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>bootstrap</artifactId>
  <version>3.3.7</version>
</dependency>
<dependency>
  <groupId>org.webjars</groupId>
  <artifactId>jquery</artifactId>
  <version>3.1.1-1</version>
</dependency>

下面的清单显示了完成的pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-parent</artifactId>

<version>2.2.2.RELEASE</version>

<relativePath/> <!-- lookup parent from repository -->

</parent>

<groupId>com.example</groupId>

<artifactId>messaging-stomp-websocket</artifactId>

<version>0.0.1-SNAPSHOT</version>

<name>messaging-stomp-websocket</name>

<description>Demo project for Spring Boot</description>


<properties>

<java.version>1.8</java.version>

</properties>


<dependencies>

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-websocket</artifactId>

</dependency>


<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-test</artifactId>

  <scope>test</scope>

  <exclusions>

  <exclusion>

    <groupId>org.junit.vintage</groupId>

    <artifactId>junit-vintage-engine</artifactId>

  </exclusion>

  </exclusions>

</dependency>

</dependencies>


<build>

<plugins>

  <plugin>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-maven-plugin</artifactId>

  </plugin>

</plugins>

</build>


</project>

如果你使用Gradle,你需要添加以下依赖:

implementation 'org.webjars:webjars-locator-core'
implementation 'org.webjars:sockjs-client:1.0.2'
implementation 'org.webjars:stomp-websocket:2.3.3'
implementation 'org.webjars:bootstrap:3.3.7'
implementation 'org.webjars:jquery:3.1.1-1'

下面的清单显示了完成的构建。gradle文件:

plugins {

id 'org.springframework.boot' version '2.2.2.RELEASE'

id 'io.spring.dependency-management' version '1.0.8.RELEASE'

id 'java'

}


group = 'com.example'

version = '0.0.1-SNAPSHOT'

sourceCompatibility = '1.8'


repositories {

mavenCentral()

}


dependencies {

implementation 'org.springframework.boot:spring-boot-starter-websocket'

testImplementation('org.springframework.boot:spring-boot-starter-test') {

exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'

}

}


test {

useJUnitPlatform()

}

 

创建资源表示类

现在已经设置了项目和构建系统,可以创建STOMP消息服务了。

 

通过考虑服务交互来开始流程。

 

服务将接受主体为JSON对象的STOMP消息中包含名称的消息。如果名称是Fred,则消息可能类似于以下内容:

 

{

   "name": "Fred"

}

要对带有名称的消息进行建模,您可以创建一个普通的旧Java对象,该对象带有name属性和相应的getName()方法,如下所示(来自src/main/ Java /com/example/messagingstompwebsocket/HelloMessage.java):

 

package com.example.messagingstompwebsocket;
public class HelloMessage {
  private String name;
  public HelloMessage() {
  }
  public HelloMessage(String name) {
    this.name = name;
  }
  public String getName() {
    return name;
  }
  public void setName(String name) {
    this.name = name;
  }
}

在接收到消息并提取名称后,服务将通过创建一个问候语并将该问候语发布到客户机订阅的单独队列中来处理它。问候语也是一个JSON对象,如下所示:

 

{
    "content": "Hello, Fred!"
}

要对问候表示建模,添加另一个具有内容属性和相应getContent()方法的普通Java对象,如下所示(来自src/main/ Java /com/example/messagingstompwebsocket/Greeting.java):

 

package com.example.messagingstompwebsocket;
public class Greeting {
  private String content;
  public Greeting() {
  }
  public Greeting(String content) {
    this.content = content;
  }
  public String getContent() {
    return content;
  }
}

Spring将使用Jackson JSON库自动将Greeting类型的实例编组为JSON。

 

接下来,您将创建一个控制器来接收hello消息并发送问候消息。

 

创建一个消息处理控制器

在Spring处理STOMP消息传递的方法中,可以将STOMP消息路由到@Controller类。例如,GreetingController(来自src/main/java/com/example/messagingstompwebsocket/GreetingController.java)被映射为处理/hello目的地的消息,如下所示:

 

package com.example.messagingstompwebsocket;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
import org.springframework.web.util.HtmlUtils;
@Controller
public class GreetingController {
  @MessageMapping("/hello")
  @SendTo("/topic/greetings")
  public Greeting greeting(HelloMessage message) throws Exception {
    Thread.sleep(1000); // simulated delay
    return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
  }
}

这个控制器是简洁和简单的,但是还有很多工作要做。我们一步一步地把它分解。

 

@MessageMapping注释确保,如果消息被发送到/hello目的地,则调用greeting()方法。

 

消息的有效负载被绑定到HelloMessage对象,该对象被传递到greeting()。

 

在内部,该方法的实现通过导致线程休眠一秒来模拟处理延迟。这是为了演示,在客户机发送消息后,服务器可以按照需要花费多长时间来异步处理消息。客户机可以继续执行它需要执行的任何工作,而无需等待响应。

 

延迟一秒之后,greeting()方法创建一个greeting对象并返回该对象。返回值广播给/topic/greetings的所有订阅者,如@SendTo注释中指定的那样。注意,来自输入消息的名称是经过清理的,因为在本例中,它将回显并在客户端浏览器DOM中重新呈现。

为STOMP消息传递配置Spring

现在已经创建了服务的基本组件,您可以配置Spring来启用WebSocket和STOMP消息传递。

 

创建一个名为WebSocketConfig的Java类,它类似于下面的清单(来自src/main/ Java /com/example/messagingstompwebsocket/WebSocketConfig.java):

 

package com.example.messagingstompwebsocket;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
  @Override
  public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic");
    config.setApplicationDestinationPrefixes("/app");
  }
  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/gs-guide-websocket").withSockJS();
  }
}

WebSocketConfig带有@Configuration注释,表示它是一个Spring配置类。它还使用@EnableWebSocketMessageBroker进行了注释。顾名思义,@EnableWebSocketMessageBroker支持由消息代理支持的WebSocket消息处理。

 

configureMessageBroker()方法实现了WebSocketMessageBrokerConfigurer中的默认方法来配置消息代理。它首先调用enableSimpleBroker(),使一个简单的基于内存的消息代理能够将问候消息带回以/topic为前缀的目的地上的客户机。它还为绑定带有@MessageMapping注释的方法的消息指定/app前缀。这个前缀将用于定义所有的消息映射。例如,/app/hello是GreetingController.greeting()方法映射要处理的端点。

 

registerStompEndpoints()方法注册/gs-guide-websocket端点,启用SockJS回退选项,以便在WebSocket不可用的情况下使用替代传输。SockJS客户机将尝试连接到/gs-guide-websocket,并使用最好的传输(websocket、xhr-streaming、xhr-polling,等等)。

创建浏览器客户端

有了服务器端部分,您就可以将注意力转向将消息发送到服务器端和从服务器端接收消息的JavaScript客户机了。

 

创建一个类似如下清单的index.html文件(来自src/main/resources/static/index.html):

 

<!DOCTYPE html>
<html>
<head>
    <title>Hello WebSocket</title>
    <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet">
    <link href="/main.css" rel="stylesheet">
    <script src="/webjars/jquery/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/stomp.min.js"></script>
    <script src="/app.js"></script>
</head>
<body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being
    enabled. Please enable
    Javascript and reload this page!</h2></noscript>
<div id="main-content" class="container">
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="connect">WebSocket connection:</label>
                    <button id="connect" class="btn btn-default" type="submit">Connect</button>
                    <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect
                    </button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">What is your name?</label>
                    <input type="text" id="name" class="form-control" placeholder="Your name here...">
                </div>
                <button id="send" class="btn btn-default" type="submit">Send</button>
            </form>
        </div>
    </div>
    <div class="row">
        <div class="col-md-12">
            <table id="conversation" class="table table-striped">
                <thead>
                <tr>
                    <th>Greetings</th>
                </tr>
                </thead>
                <tbody id="greetings">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

这个HTML文件导入了SockJS和STOMP javascript库,这些库将用于通过STOMP over websocket与我们的服务器通信。我们还导入app.js,它包含客户端应用程序的逻辑。下面的清单(来自src/main/resources/static/app.js)显示了该文件:

 

var stompClient = null;
function setConnected(connected) {
    $("#connect").prop("disabled", connected);
    $("#disconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#greetings").html("");
}
function connect() {
    var socket = new SockJS('/gs-guide-websocket');
    stompClient = Stomp.over(socket);
    stompClient.connect({}, function (frame) {
        setConnected(true);
        console.log('Connected: ' + frame);
        stompClient.subscribe('/topic/greetings', function (greeting) {
            showGreeting(JSON.parse(greeting.body).content);
        });
    });
}
function disconnect() {
    if (stompClient !== null) {
        stompClient.disconnect();
    }
    setConnected(false);
    console.log("Disconnected");
}
function sendName() {
    stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()}));
}
function showGreeting(message) {
    $("#greetings").append("<tr><td>" + message + "</td></tr>");
}
$(function () {
    $("form").on('submit', function (e) {
        e.preventDefault();
    });
    $( "#connect" ).click(function() { connect(); });
    $( "#disconnect" ).click(function() { disconnect(); });
    $( "#send" ).click(function() { sendName(); });
});

这个JavaScript文件需要理解的主要部分是connect()和sendName()函数。

 

connect()函数使用SockJS和stomp.js打开到/gs-guide-websocket的连接,这是我们的SockJS服务器等待连接的地方。成功连接后,客户端订阅/topic/greetings目的地,服务器将在其中发布问候消息。当目的地接收到问候语时,它将向DOM追加一个段落元素以显示问候语消息。

 

sendName()函数检索用户输入的名称,并使用STOMP客户机将其发送到/app/hello目的地(在那里GreetingController.greeting()将接收它)。

使应用程序可执行

Spring Boot为您创建一个应用程序类。在这种情况下,它不需要进一步修改。您可以使用它来运行此应用程序。下面的清单(来自src/main/java/com/example/messagingstompwebsocket/MessagingStompWebsocketApplication.java)显示了应用程序类:

 

package com.example.messagingstompwebsocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MessagingStompWebsocketApplication {
  public static void main(String[] args) {
    SpringApplication.run(MessagingStompWebsocketApplication.class, args);
  }
}

@SpringBootApplication是一个方便的注释,添加了以下所有内容:

 

  • @Configuration:标记类作为应用程序上下文bean定义的源。

  • @EnableAutoConfiguration:告诉Spring Boot根据类路径设置、其他bean和各种属性设置开始添加bean。例如,如果spring-webmvc在类路径中,这个注释将应用程序标记为web应用程序并激活关键行为,比如设置一个DispatcherServlet。

  • 告诉Spring在com - example包中寻找其他组件、配置和服务,让它找到控制器。

 

main()方法使用Spring引导的Spring application. run()方法来启动应用程序。您注意到没有一行XML吗?也没有web.xml文件。这个web应用程序是100%纯Java的,您不必配置任何管道或基础设施。

 

构建一个可执行JAR

您可以使用Gradle或Maven从命令行运行该应用程序。您还可以构建一个包含所有必要的依赖项、类和资源的可执行JAR文件并运行它。构建可执行jar使得在整个开发生命周期中,跨不同环境,等等,将服务作为应用程序进行发布、版本和部署变得更加容易。

 

如果你使用Gradle,你可以使用./gradlew bootRun来运行这个应用程序。或者,您可以使用./gradlew build构建JAR文件,然后运行JAR文件,如下所示:

 

java -jar build/libs/gs-messaging-stomp-websocket-0.1.0.jar

如果使用Maven,可以使用./mvnw spring-boot:run来运行应用程序。或者,您可以使用./mvnw clean包构建JAR文件,然后运行JAR文件,如下所示:

java -jar target/gs-messaging-stomp-websocket-0.1.0.jar

这里描述的步骤创建了一个可运行的JAR。您还可以构建一个经典的WAR文件。

将显示日志输出。服务应该在几秒钟内启动并运行。

 

测试服务

现在服务已经运行,将浏览器指向http://localhost:8080并单击Connect按钮。

 

在打开连接时,会询问您的姓名。输入您的姓名并单击Send。您的名字将通过STOMP以JSON消息的形式发送到服务器。经过一秒钟的模拟延迟后,服务器返回一条消息,其中包含在页面上显示的“Hello”问候语。此时,您可以发送另一个名称,或者单击Disconnect按钮关闭连接。

 

总结

恭喜你!您刚刚用Spring开发了一个基于stomp的消息传递服务。

相关文章
|
前端开发 算法 API
构建高性能图像处理Web应用:Next.js与TailwindCSS实践
本文分享了构建在线图像黑白转换工具的技术实践,涵盖技术栈选择、架构设计与性能优化。项目采用Next.js提供优秀的SSR性能和SEO支持,TailwindCSS加速UI开发,WebAssembly实现高性能图像处理算法。通过渐进式处理、WebWorker隔离及内存管理等策略,解决大图像处理性能瓶颈,并确保跨浏览器兼容性和移动设备优化。实际应用案例展示了其即时处理、高质量输出和客户端隐私保护等特点。未来计划引入WebGPU加速、AI增强等功能,进一步提升用户体验。此技术栈为Web图像处理应用提供了高效可行的解决方案。
|
11月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。该方案设计目标是实现通用性,适用于各种网络架构和业务场景。
367 1
|
12月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。
454 7
|
中间件 关系型数据库 数据库
docker快速部署OS web中间件 数据库 编程应用
通过Docker,可以轻松地部署操作系统、Web中间件、数据库和编程应用。本文详细介绍了使用Docker部署这些组件的基本步骤和命令,展示了如何通过Docker Compose编排多容器应用。希望本文能帮助开发者更高效地使用Docker进行应用部署和管理。
426 19
|
Web App开发 编解码 vr&ar
使用Web浏览器访问UE应用的最佳实践
在3D/XR应用开发中,尤其是基于UE(虚幻引擎)开发的高精度场景,传统终端因硬件局限难以流畅运行高帧率、复杂效果的三维应用。实时云渲染技术,将渲染任务转移至云端服务器,降低终端硬件要求,确保用户获得流畅体验。具备弹性扩展、优化传输协议、跨平台支持和安全性等优势,适用于多种终端和场景,特别集成像素流送技术,帮助UE开发者实现低代码上云操作,简化部署流程,保留UE引擎的强大开发能力,确保画面精美且终端轻量化。
696 17
使用Web浏览器访问UE应用的最佳实践
|
存储 消息中间件 缓存
支持百万人超大群聊的Web端IM架构设计与实践
本文将回顾实现一个支持百万人超大群聊的Web端IM架构时遇到的技术挑战和解决思路,内容包括:通信方案选型、消息存储、消息有序性、消息可靠性、未读数统计。希望能带给你启发。
675 0
支持百万人超大群聊的Web端IM架构设计与实践
|
弹性计算 Java 关系型数据库
Web应用上云经典架构实践教学
Web应用上云经典架构实践教学
379 2
Web应用上云经典架构实践教学
|
弹性计算 Java 数据库
Web应用上云经典架构实战
本课程详细介绍了Web应用上云的经典架构实战,涵盖前期准备、配置ALB、创建服务器组和监听、验证ECS公网能力、环境配置(JDK、Maven、Node、Git)、下载并运行若依框架、操作第二台ECS以及验证高可用性。通过具体步骤和命令,帮助学员快速掌握云上部署的全流程。
445 1
|
弹性计算 负载均衡 安全
云端问道-Web应用上云经典架构方案教学
本文介绍了企业业务上云的经典架构设计,涵盖用户业务现状及挑战、阿里云业务托管架构设计、方案选型配置及业务初期低门槛使用等内容。通过详细分析现有架构的问题,提出了高可用、安全、可扩展的解决方案,并提供了按量付费的低成本选项,帮助企业在业务初期顺利上云。
446 0
|
前端开发 网络协议 JavaScript
在Spring Boot中实现基于WebSocket的实时通信
在Spring Boot中实现基于WebSocket的实时通信