# 4.2 WebSocket API

Spring框架提供了一个WebSocket API，可用于编写处理WebSocket消息的客户端和服务器端应用程序。

## 4.2.1 WebSocketHandler

创建WebSocket服务器就像实现WebSocketHandler一样简单，或者更可能地，扩展TextWebSocketHandler或BinaryWebSocketHandler。 以下示例使用TextWebSocketHandler：

```java
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.TextMessage;

public class MyHandler extends TextWebSocketHandler {

    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) {
        // ...
    }

}
```

有专用的WebSocket Java配置和XML名称空间支持，用于将前面的WebSocket处理程序映射到特定的URL，如以下示例所示：

```java
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}
```

下面的示例显示与前面的示例等效的XML配置：

```markup
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>
```

前面的示例用于Spring MVC应用程序，应包含在DispatcherServlet的配置中。 但是，Spring的WebSocket支持不依赖于Spring MVC。 在WebSocketHttpRequestHandler的帮助下将WebSocketHandler集成到其他HTTP服务环境中相对简单。

当直接或间接使用WebSocketHandler API时，例如 通过STOMP消息传递，由于基础标准WebSocket会话（JSR-356）不允许并发发送，因此应用程序必须同步消息的发送。 一种选择是用ConcurrentWebSocketSessionDecorator包装WebSocketSession。

## 4.2.2 WebSocket Handshake

定制初始HTTP WebSocket握手请求的最简单方法是通过HandshakeInterceptor，它公开了“握手之前”和“握手之后”的方法。 您可以使用此类拦截器来阻止握手或使任何属性对WebSocketSession可用。 以下示例使用内置的拦截器将HTTP会话属性传递到WebSocket会话：

```java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

}
```

下面的示例显示与前面的示例等效的XML配置：

```markup
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:handshake-interceptors>
            <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>
```

一个更高级的选项是扩展DefaultHandshakeHandler，它执行WebSocket握手的步骤，包括验证客户端来源，协商子协议以及其他详细信息。 如果应用程序需要配置自定义RequestUpgradeStrategy以便适应尚不支持的WebSocket服务器引擎和版本，则它可能还需要使用此选项（有关此主题的更多信息，请参阅部署）。 Java配置和XML名称空间都使配置自定义HandshakeHandler成为可能。

> Spring提供了一个WebSocketHandlerDecorator基类，您可以使用该基类来装饰WebSocketHandler并具有其他行为。 使用WebSocket Java配置或XML名称空间时，默认情况下会提供并添加日志记录和异常处理实现。 ExceptionWebSocketHandlerDecorator捕获由任何WebSocketHandler方法引起的所有未捕获的异常，并关闭状态为1011（指示服务器错误）的WebSocket会话。

## 4.2.3 Deployment

易于将Spring WebSocket API集成到Spring MVC应用程序中，在该应用程序中，DispatcherServlet同时提供HTTP WebSocket握手和其他HTTP请求。通过调用WebSocketHttpRequestHandler，也很容易将其集成到其他HTTP处理方案中。这是方便且易于理解的。但是，对于JSR-356运行时，需要特别注意。

Java WebSocket API（JSR-356）提供了两种部署机制。第一个涉及启动时的Servlet容器类路径扫描（Servlet 3功能）。另一个是在Servlet容器初始化时使用的注册API。这两种机制都无法对所有HTTP处理（包括WebSocket握手和所有其他HTTP请求）（例如Spring MVC的DispatcherServlet）使用单个“前端控制器”。

这是JSR-356的一个重要限制，即使在JSR-356运行时中运行，Spring的WebSocket支持也可以通过服务器特定的RequestUpgradeStrategy实现解决。 Tomcat，Jetty，GlassFish，WebLogic，WebSphere和Undertow（和WildFly）目前存在此类策略。

已经创建了克服Java WebSocket API中的上述限制的请求，可以在eclipse-ee4j / websocket-api＃211中进行跟踪。 Tomcat，Undertow和WebSphere提供了自己的API替代方案，使之可以做到这一点，而Jetty也可以实现。 我们希望更多的服务器可以做到这一点。

第二个考虑因素是，期望支持JSR-356的Servlet容器执行ServletContainerInitializer（SCI）扫描，这可能会减慢应用程序的启动速度，在某些情况下会大大降低速度。 如果在升级到支持JSR-356的Servlet容器版本后观察到重大影响，则应该可以通过使用web.xml中的\<absolute-ordering />元素有选择地启用或禁用Web片段（和SCI扫描），如以下示例所示：

```markup
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering/>

</web-app>
```

然后，您可以按名称选择性地启用Web片段，例如Spring自己的SpringServletContainerInitializer，它提供对Servlet 3 Java初始化API的支持。 以下示例显示了如何执行此操作：

```markup
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://java.sun.com/xml/ns/javaee
        https://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">

    <absolute-ordering>
        <name>spring_web</name>
    </absolute-ordering>

</web-app>
```

## 4.2.4 Server Configuration

每个基础的WebSocket引擎都公开控制运行时特征的配置属性，例如消息缓冲区大小的大小，空闲超时等。

对于Tomcat，WildFly和GlassFish，可以将ServletServerContainerFactoryBean添加到WebSocket Java配置中，如以下示例所示：

```java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }

}
```

下面的示例显示与前面的示例等效的XML配置：

```markup
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <bean class="org.springframework...ServletServerContainerFactoryBean">
        <property name="maxTextMessageBufferSize" value="8192"/>
        <property name="maxBinaryMessageBufferSize" value="8192"/>
    </bean>

</beans>
```

> 对于客户端WebSocket配置，应使用WebSocketContainerFactoryBean（XML）或ContainerProvider.getWebSocketContainer（）（Java配置）。

对于Jetty，您需要提供一个预先配置的Jetty WebSocketServerFactory，并将其通过WebSocket Java配置插入Spring的DefaultHandshakeHandler中。 以下示例显示了如何执行此操作：

```java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoWebSocketHandler(),
            "/echo").setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }

}
```

下面的示例显示与前面的示例等效的XML配置：

```java
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/echo" handler="echoHandler"/>
        <websocket:handshake-handler ref="handshakeHandler"/>
    </websocket:handlers>

    <bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
        <constructor-arg ref="upgradeStrategy"/>
    </bean>

    <bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
        <constructor-arg ref="serverFactory"/>
    </bean>

    <bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
        <constructor-arg>
            <bean class="org.eclipse.jetty...WebSocketPolicy">
                <constructor-arg value="SERVER"/>
                <property name="inputBufferSize" value="8092"/>
                <property name="idleTimeout" value="600000"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>
```

## 4.2.5 Allowed Origins

从Spring Framework 4.1.5开始，WebSocket和SockJS的默认行为是仅接受同源请求。也可以允许所有或指定的来源列表。此检查主要用于浏览器客户端。没有任何措施可以阻止其他类型的客户端修改Origin头值（有关更多详细信息，请参阅RFC 6454：Web Origin概念）。

三种可能的行为是：

* 仅允许相同来源的请求（默认）：在此模式下，启用SockJS时，Iframe HTTP响应标头X-Frame-Options设置为SAMEORIGIN，并且JSONP传输被禁用，因为它不允许检查请求。因此，启用此模式时，不支持IE6和IE7。
* 允许指定的来源列表：每个允许的来源必须以http：//或https：//开头。在此模式下，启用SockJS时，将禁用IFrame传输。因此，启用此模式时，不支持IE6到IE9。
* 允许所有origins：要启用此模式，应提供\*作为允许的origins值。在这种模式下，所有传输都可用。

您可以配置WebSocket和SockJS允许的来源，如以下示例所示：

```java
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("https://mydomain.com");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}
```

下面的示例显示与前面的示例等效的XML配置：

```markup
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        https://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers allowed-origins="https://mydomain.com">
        <websocket:mapping path="/myHandler" handler="myHandler" />
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.flydean.com/spring-framework-documentation5/webservlet/4.websockets/4.2websocketapi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
