# WebSockets 支持

## WebSockets 支持

从版本 4.1 开始， Spring 集成有 WebSocket 支持。它基于 Spring 框架的`web-socket`模块中的体系结构、基础设施和 API。因此， Spring  WebSocket 的许多组件（例如`SubProtocolHandler`或`WebSocketClient`）和配置选项（例如`@EnableWebSocketMessageBroker`）可以在 Spring 集成内重用。有关更多信息，请参见 Spring 框架参考手册中的[Spring Framework WebSocket Support](https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket)章节。

你需要在项目中包含此依赖项：

Maven

```
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-websocket</artifactId>
    <version>5.5.9</version>
</dependency>
```

Gradle

```
compile "org.springframework.integration:spring-integration-websocket:5.5.9"
```

对于服务器端，必须显式地包含`org.springframework:spring-webmvc`依赖项。

Spring 框架 WebSocket 基础设施是基于 Spring 消息传递基础并且提供了基于与 Spring 集成使用的相同的`MessageChannel`实现和`MessageHandler`实现（以及一些 POJO-method 注释映射）的基本消息传递框架。因此， Spring 集成可以直接参与到 WebSocket 流中，即使没有 WebSocket 适配器也是如此。为此，你可以使用适当的注释来配置 Spring 集成`@MessagingGateway`，如下例所示：

```
@MessagingGateway
@Controller
public interface WebSocketGateway {

    @MessageMapping("/greeting")
    @SendToUser("/queue/answer")
    @Gateway(requestChannel = "greetingChannel")
    String greeting(String payload);

}
```

### 概述

由于 WebSocket 协议根据定义是流的并且我们可以同时向 WebSocket 发送和接收消息，因此我们可以处理适当的`WebSocketSession`，而不管是在客户端还是服务器端。为了封装连接管理和`WebSocketSession`注册中心，`IntegrationWebSocketContainer`提供了`ClientWebSocketContainer`和`ServerWebSocketContainer`实现。由于[WebSocket API](https://www.jcp.org/en/jsr/detail?id=356)及其在 Spring 框架中的实现（具有许多扩展），在服务器端和客户端都使用了相同的类（当然，从 Java 的角度来看）。因此，大多数连接和`WebSocketSession`注册表选项在两边都是相同的。这使我们能够重用许多配置项和基础设施挂钩，以便在服务器端和客户端构建 WebSocket 应用程序。下面的示例展示了组件如何实现这两个目的：

```
//Client side
@Bean
public WebSocketClient webSocketClient() {
    return new SockJsClient(Collections.singletonList(new WebSocketTransport(new JettyWebSocketClient())));
}

@Bean
public IntegrationWebSocketContainer clientWebSocketContainer() {
    return new ClientWebSocketContainer(webSocketClient(), "ws://my.server.com/endpoint");
}

//Server side
@Bean
public IntegrationWebSocketContainer serverWebSocketContainer() {
    return new ServerWebSocketContainer("/endpoint").withSockJs();
}
```

WebSocket 该被设计用于实现双向消息传递，并且可以在入站和出站通道适配器之间共享（见下文），在使用单向消息传递时可以仅从其中一个引用 WebSocket。它可以在没有任何通道适配器的情况下使用，但是，在这种情况下，`IntegrationWebSocketContainer`仅作为`WebSocketSession`注册中心发挥作用。

|   |`ServerWebSocketContainer`实现`WebSocketConfigurer`将一个内部`IntegrationWebSocketContainer.IntegrationWebSocketHandler`注册为`Endpoint`。<br/>它在提供的`paths`下这样做和其他服务器 WebSocket 选项（例如`HandshakeHandler`或`SockJS fallback`）内的`ServletWebSocketHandlerRegistry`用于目标供应商 WebSocket 容器。<br/>该注册是通过一个基础设施`WebSocketIntegrationConfigurationInitializer`组件实现的，其执行与`@EnableWebSocket`注释相同的操作。<br/>这意味着，通过使用`@EnableIntegration`（或应用程序上下文中的任何 Spring 集成名称空间），你可以省略`@EnableWebSocket`声明，因为 Spring 集成基础结构检测所有 WebSocket 端点。|
|---| |

###  WebSocket 入站通道适配器

`WebSocketInboundChannelAdapter`实现了`WebSocketSession`交互的接收部分。你必须为它提供`IntegrationWebSocketContainer`，并且适配器将自身注册为`WebSocketListener`以处理传入消息和`WebSocketSession`事件。

|   |只有一个`WebSocketListener`可以在`IntegrationWebSocketContainer`中注册。|
|---|--------------------------------------------------------------------------------------|

对于 WebSocket 子协议，`WebSocketInboundChannelAdapter`可以配置`SubProtocolHandlerRegistry`作为第二个构造函数参数。适配器委托给`SubProtocolHandlerRegistry`，以确定适用于已接受的`WebSocketSession`的`SubProtocolHandler`，并根据子协议实现将`WebSocketMessage`转换为`Message`。

|   |默认情况下，`WebSocketInboundChannelAdapter`仅依赖于 RAW`PassThruSubProtocolHandler`实现，该实现将`WebSocketMessage`转换为`Message`。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

`WebSocketInboundChannelAdapter`只接受并发送到底层集成流`Message`实例，这些实例具有`SimpMessageType.MESSAGE`或空的`simpMessageType`头。所有其他`Message`类型都是通过`ApplicationEvent`实现发出的`SubProtocolHandler`实例来处理的（例如`StompSubProtocolHandler`）。

在服务器端，如果存在`@EnableWebSocketMessageBroker`配置，则可以使用`useBroker = true`选项配置`WebSocketInboundChannelAdapter`。在这种情况下，所有`non-MESSAGE``Message`类型都被委托给所提供的`AbstractBrokerMessageHandler`。此外，如果代理中继配置了目标前缀，那么那些匹配代理目标的消息将路由到`AbstractBrokerMessageHandler`，而不是`WebSocketInboundChannelAdapter`的`outputChannel`。

如果`useBroker = false`并且接收到的消息是`SimpMessageType.CONNECT`类型的，则`WebSocketInboundChannelAdapter`立即向`SimpMessageType.CONNECT_ACK`消息发送`WebSocketSession`，而不将其发送到信道。

|   |Spring 的 WebSocket 支持只允许配置一个代理中继。<br/>因此，我们不需要`AbstractBrokerMessageHandler`引用。<br/>它是在应用程序上下文中检测到的。|
|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

有关更多配置选项，请参见[WebSockets 名称空间支持](#web-sockets-namespace)。

###  WebSocket 出站通道适配器

the`WebSocketOutboundChannelAdapter`:

1. 接受 Spring 来自其`MessageChannel`的集成消息

2. 从`MessageHeaders`中确定`WebSocketSession``id`

3. 从提供的`IntegrationWebSocketContainer`检索`WebSocketSession`

4. 从提供的`SubProtocolHandlerRegistry`将`WebSocketMessage`工作的转换和发送委托给相应的`SubProtocolHandler`。

在客户端，`WebSocketSession``id`消息头不是必需的，因为`ClientWebSocketContainer`仅处理单个连接及其`WebSocketSession`分别。

要使用 STOMP 子协议，你应该使用`StompSubProtocolHandler`配置此适配器。然后，你可以使用`StompHeaderAccessor.create(StompCommand…​)`和`MessageBuilder`向此适配器发送任何 Stomp 消息类型，或者只使用`HeaderEnricher`（参见[页眉 Enricher](./content-enrichment.html#header-enricher)）。

本章的其余部分主要介绍了附加的配置选项。

### WebSockets 名称空间支持

Spring 集成 WebSocket 命名空间包括在本章的其余部分中描述的几个组件。要将其包含在配置中，请在应用程序上下文配置文件中使用以下名称空间声明：

```
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:int="http://www.springframework.org/schema/integration"
  xmlns:int-websocket="http://www.springframework.org/schema/integration/websocket"
  xsi:schemaLocation="
    http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration
    https://www.springframework.org/schema/integration/spring-integration.xsd
    http://www.springframework.org/schema/integration/websocket
    https://www.springframework.org/schema/integration/websocket/spring-integration-websocket.xsd">
    ...
</beans>
```

#### `<int-websocket:client-container>`属性

下面的清单显示了`<int-websocket:client-container>`元素可用的属性：

```
<int-websocket:client-container
                  id=""                        (1)
                  client=""                    (2)
                  uri=""                       (3)
                  uri-variables=""             (4)
                  origin=""                    (5)
                  send-time-limit=""           (6)
                  send-buffer-size-limit=""    (7)
                  auto-startup=""              (8)
                  phase="">                    (9)
                <int-websocket:http-headers>
                  <entry key="" value=""/>
                </int-websocket:http-headers>  (10)
</int-websocket:client-container>
```

|**1** |组件 Bean 名称。|
|------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2** |参考文献`WebSocketClient` Bean。|
|**3** |将`uri`或`uriTemplate`发送到目标 WebSocket 服务。<br/>如果将其用作带有 URI 变量占位符的`uriTemplate`，则需要`uri-variables`属性。|
|**4** |<br/>属性值内的 URI 变量占位符的逗号分隔的值。<br/>这些值根据它们在`uri`中的顺序被替换到占位符中。<br/>参见[`UriComponents.expand(Object…​uriVariableValues)`（https://DOCS. Spring.io/ Spring//DOCS/current/javadoc-api/org/SpringFramework/web/util/utilComponents.html expand-uribot）。|
|**5** |`Origin`握手 HTTP 标头值。|
|**6** |WebSocket 会话“发送”超时限制。<br/>默认为`10000`。|
|**7** |WebSocket 会话“发送”消息大小限制。<br/>默认为`524288`。|
|**8** |布尔值，表示此端点是否应自动启动。<br/>默认为`false`，假设此容器是从[WebSocket inbound adapter](#web-socket-inbound-adapter)启动的。|
|**9** |这个端点应该在其中开始和停止的生命周期阶段。<br/>值越低，这个端点开始得越早，停止得越晚。<br/>默认值是`Integer.MAX_VALUE`。<br/>值可以是负数。<br/>参见[`SmartLifeCycle`（https:/DOCS. Spring.io/ Spring//DOCS/current/javadoc-api/org/api/context/smartlifycle.html）。|
|**10**|a`Map`of`HttpHeaders`用于握手请求。|

#### `<int-websocket:server-container>`属性

下面的清单显示了`<int-websocket:server-container>`元素可用的属性：

```
<int-websocket:server-container
          id=""                         (1)
          path=""                       (2)
          handshake-handler=""          (3)
          handshake-interceptors=""     (4)
          decorator-factories=""        (5)
          send-time-limit=""            (6)
          send-buffer-size-limit=""     (7)
          allowed-origins="">           (8)
          <int-websocket:sockjs
            client-library-url=""       (9)
            stream-bytes-limit=""       (10)
            session-cookie-needed=""    (11)
            heartbeat-time=""           (12)
            disconnect-delay=""         (13)
            message-cache-size=""       (14)
            websocket-enabled=""        (15)
            scheduler=""                (16)
            message-codec=""            (17)
            transport-handlers=""       (18)
            suppress-cors="true"="" />  (19)
</int-websocket:server-container>
```

|**1** |组件 Bean 名称。|
|------| |
|**2** |一种将特定请求映射到`WebSocketHandler`的路径（或以逗号分隔的路径）。<br/>支持精确的路径映射 URI（例如`/myPath`）和 Ant 样式的路径模式（例如`/myPath/**`）。|
|**3** |`HandshakeHandler` Bean 引用.<br/>默认为`DefaultHandshakeHandler`。|
|**4** |参考文献列表`HandshakeInterceptor` Bean。|
|**5** |用于修饰用于处理 WebSocket 消息的处理程序的一个或多个工厂的列表（`WebSocketHandlerDecoratorFactory`）。<br/>这对于某些高级用例（例如，以允许 Spring 安全性在相应的 HTTP会话过期时强制关闭 WebSocket 会话。有关更多信息，请参见。|
|**6** |请参阅[`<int-websocket:client-container>`]（# WebSocket-client-container-attributes）上的相同选项。|
|**7** |请参阅[`<int-websocket:client-container>`]（# WebSocket-client-container-attributes）上的相同选项。|
|**8** |允许的源头值。<br/>你可以将多个源代码指定为逗号分隔的列表。<br/>此检查主要是为浏览器客户端设计的。<br/>没有什么可以阻止其他类型的客户端修改源头值。<br/>当启用 Sockjs 并且限制允许的源代码时，不使用起源头来处理跨起源请求（`jsonp-polling`，`iframe-xhr-polling`，`iframe-eventsource`，和`iframe-htmlfile`）的传输类型将被禁用。因此，不支持 IE6 和 IE7，并且只支持 IE8 和 IE9 而不支持 cookie。<br/>默认情况下，所有起源都是允许的。|
|**9** |没有本机跨域通信的传输（例如`eventsource`和`htmlfile`）必须在不可见的 iframe 中从“foreign”域获得一个简单的页面，以便 IFRAME 中的代码可以从本地域运行到 Sockjs 服务器。<br/>由于 IFRAME 需要加载 Sockjs JavaScript 客户库，因此该属性允许你指定加载它的位置。<br/>默认情况下，它指向`[https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js](https://d1fxtkz8shb9d2.cloudfront.net/sockjs-0.3.4.min.js)`。，但是，<br/>，还可以将它设置为指向应用程序提供的 URL。<br/>注意，可以指定一个相对 URL，在这种情况下，URL 必须与 IFRAME URL 相对。<br/>，例如，假设一个 Sockjs 端点映射到`/sockjs`，并且得到的 iFrame URL 是`/sockjs/iframe.html`，那么相对 URL 必须以“.././”开头，才能遍历到 Sockjs 映射上方的位置。<br/>对于基于前缀的 Servlet 映射，你可能需要再遍历一次。|
|**10**|在关闭单个 HTTP 流请求之前，可以通过该请求发送的最小字节数。<br/>默认为`128K`（即 128\*1024 或 131072 字节）。|
|**11**|来自 Sockjs 的响应中的`cookie_needed`值`/info`Endpoint.<br/>此属性指示应用程序是否需要`JSESSIONID`cookie 才能正常工作（例如，用于负载平衡或在 Java Servlet 容器中使用 HTTP会话）。|
|**12**|当服务器没有发送任何消息时的时间量（以毫秒为单位），在此之后服务器应该<br/>向客户端发送心跳帧，以防止连接中断。<br/>默认值为`25,000`（25 秒）。|
|**13**|客户机在没有接收连接（即服务器可以在其上向客户机发送数据的活动连接）之后被认为断开连接之前的时间量（以毫秒为单位）。<br/>默认值为`5000`。|
|**14**|会话在等待来自客户端的下一个 HTTP 轮询请求时可以缓存的服务器到客户端消息的数量。<br/>默认大小为`100`。|
|**15**|有些负载均衡器不支持 WebSockets。<br/>将此选项设置为`false`，以禁用服务器端的 WebSocket 传输。<br/>默认值为`true`。|
|**16**|`TaskScheduler` Bean 引用。<br/>如果不提供值，将创建一个新的`ThreadPoolTaskScheduler`实例。<br/>此调度程序实例用于调度心跳消息。|
|**17**|该 Bean 引用用于编码和解码 Sockjs 消息。默认情况下，使用，这要求在 Classpath 上存在 Jackson 库。|
|**18**|参考文献列表`TransportHandler` Bean。|
|**19**|是否禁用自动添加 SOCKJS 请求的 CORS 头。<br/>默认值为`false`。|

#### `<int-websocket:outbound-channel-adapter>`属性

下面的清单显示了`<int-websocket:outbound-channel-adapter>`元素可用的属性：

```
<int-websocket:outbound-channel-adapter
                          id=""                             (1)
                          channel=""                        (2)
                          container=""                      (3)
                          default-protocol-handler=""       (4)
                          protocol-handlers=""              (5)
                          message-converters=""             (6)
                          merge-with-default-converters=""  (7)
                          auto-startup=""                   (8)
                          phase=""/>                        (9)
```

|**1**|组件 Bean name.<br/>如果不提供`channel`属性，则在应用程序上下文中创建并注册一个`DirectChannel`，并将该`id`属性作为 Bean 名称。<br/>在这种情况下，端点以 Bean 名称`id`加上`.adapter`注册。<br/>而`MessageHandler`则以 Bean 别名`id`加上`.handler`注册。|
|-----|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2**|标识连接到此适配器的通道。|
|**3**|对`IntegrationWebSocketContainer` Bean 的引用，它封装了低级连接和`WebSocketSession`处理操作。<br/>需要。|
|**4**|对`SubProtocolHandler`实例的可选引用。<br/>当客户端未请求子协议或它是单个协议处理程序时使用。<br/>如果未提供此引用或`protocol-handlers`列表，则默认使用`PassThruSubProtocolHandler`。|
|**5**|该通道适配器的`SubProtocolHandler` Bean 引用列表。<br/>如果只提供单个 Bean 引用而不提供`default-protocol-handler`，则该单个`SubProtocolHandler`用作`default-protocol-handler`。<br/>如果不设置此属性或`default-protocol-handler`，默认情况下使用`PassThruSubProtocolHandler`。|
|**6**|此通道适配器的`MessageConverter` Bean 引用列表。|
|**7**|布尔值，表示是否应在任何自定义转换器之后注册默认转换器。<br/>只有在提供`message-converters`时才使用此标志。<br/>否则，将注册所有默认转换器。<br/>默认为`false`。<br/>默认转换器（按顺序排列）：`StringMessageConverter`，`ByteArrayMessageConverter`，和（如果 Jackson 库存在于 Classpath 上）。|
|**8**|布尔值，表示此端点是否应自动启动。<br/>默认为`true`。|
|**9**|这个端点应该在其中开始和停止的生命周期阶段。<br/>值越低，这个端点开始得越早，停止得越晚。<br/>默认值是`Integer.MIN_VALUE`。<br/>值可以是负数。<br/>参见[`SmartLifeCycle`（https:/DOCS. Spring.io/ Spring/DOCS/current/javadoc-apf/org/api/context/smartlifycle.html）。|

#### `<int-websocket:inbound-channel-adapter>`属性

下面的清单显示了`<int-websocket:outbound-channel-adapter>`元素可用的属性：

```
<int-websocket:inbound-channel-adapter
                            id=""  (1)
                            channel=""  (2)
                            error-channel=""  (3)
                            container=""  (4)
                            default-protocol-handler=""  (5)
                            protocol-handlers=""  (6)
                            message-converters=""  (7)
                            merge-with-default-converters=""  (8)
                            send-timeout=""  (9)
                            payload-type=""  (10)
                            use-broker=""  (11)
                            auto-startup=""  (12)
                            phase=""/>  (13)
```

|**1** |组件 Bean name.<br/>如果不设置`channel`属性，则在应用程序上下文中创建并注册一个`DirectChannel`，并将该`id`属性作为 Bean 名称。<br/>在本例中，端点以 Bean 名称`id`加上`.adapter`进行注册。|
|------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|**2** |标识连接到此适配器的通道。|
|**3** |应该发送`ErrorMessage`实例的`MessageChannel` Bean 引用。|
|**4** |请参阅[`<int-websocket:outbound-channel-adapter>`]（# WebSocket-出站-通道-适配器-属性）上的相同选项。|
|**5** |请参阅[`<int-websocket:outbound-channel-adapter>`]（# WebSocket-出站-通道-适配器-属性）上的相同选项。|
|**6** |请参阅[`<int-websocket:outbound-channel-adapter>`]（# WebSocket-出站-通道-适配器-属性）上的相同选项。|
|**7** |请参阅[`<int-websocket:outbound-channel-adapter>`]（# WebSocket-出站-通道-适配器-属性）上的相同选项。|
|**8** |请参阅[`<int-websocket:outbound-channel-adapter>`]（# WebSocket-出站-通道-适配器-属性）上的相同选项。|
|**9** |如果信道可以阻塞，则在向信道发送消息时等待的最大时间量（以毫秒为单位）。<br/>例如，如果已达到其最大容量，则`QueueChannel`可以阻塞直到可用空间。|
|**10**|用于从传入的`WebSocketMessage`转换为目标`payload`的完全限定的 Java 类型名称。<br/>默认为`java.lang.String`。|
|**11**|指示此适配器是否从应用程序上下文向`AbstractBrokerMessageHandler`发送带有代理目的地的`AbstractBrokerMessageHandler`实例和消息。<br/>当此属性为`true`时，需要`Broker Relay`配置。<br/>此属性仅在服务器端使用。<br/>在客户端，它被忽略。<br/>默认为`false`。|
|**12**|请参阅[`<int-websocket:outbound-channel-adapter>`]（# WebSocket-出站-通道-适配器-属性）上的相同选项。|
|**13**|参见[`<int-websocket:outbound-channel-adapter>`]（# WebSocket-出站-通道-适配器-属性）上的相同选项。|

### 使用`ClientStompEncoder`

从版本 4.3.13 开始， Spring 集成提供了`ClientStompEncoder`（作为标准`StompEncoder`的扩展），用于在 WebSocket 通道适配器的客户端上使用。为了进行正确的客户端消息准备，你必须将`ClientStompEncoder`的一个实例注入`StompSubProtocolHandler`中。默认`StompSubProtocolHandler`的一个问题是，它是为服务器端设计的，因此它将`SEND``stompCommand`报头更新为`MESSAGE`（根据服务器端的 stomp 协议的要求）。如果客户机没有在适当的`SEND`Web 套接字框架中发送消息，则某些 STOMP 代理不接受它们。在这种情况下，`ClientStompEncoder`的目的是覆盖`stompCommand`头并将其设置为`SEND`值，然后将消息编码为`byte[]`。

### 动态 WebSocket 端点注册

从版本 5.5 开始， WebSocket 服务器端点（基于`ServerWebSocketContainer`的通道适配器）现在可以在运行时注册（并删除）-映射的`paths`a`ServerWebSocketContainer`通过`HandlerMapping`公开到`DispatcherServlet`中，并可供 WebSocket 客户端访问。[动态和运行时集成流](./dsl.html#java-dsl-runtime-flows)支持有助于以透明的方式注册这些端点：

```
@Autowired
IntegrationFlowContext integrationFlowContext;

@Autowired
HandshakeHandler handshakeHandler;
...
ServerWebSocketContainer serverWebSocketContainer =
       new ServerWebSocketContainer("/dynamic")
               .setHandshakeHandler(this.handshakeHandler);

WebSocketInboundChannelAdapter webSocketInboundChannelAdapter =
       new WebSocketInboundChannelAdapter(serverWebSocketContainer);

QueueChannel dynamicRequestsChannel = new QueueChannel();

IntegrationFlow serverFlow =
       IntegrationFlows.from(webSocketInboundChannelAdapter)
               .channel(dynamicRequestsChannel)
               .get();

IntegrationFlowContext.IntegrationFlowRegistration dynamicServerFlow =
       this.integrationFlowContext.registration(serverFlow)
               .addBean(serverWebSocketContainer)
               .register();
...
dynamicServerFlow.destroy();
```

|   |在动态流注册中调用`.addBean(serverWebSocketContainer)`将`ServerWebSocketContainer`的实例添加到`ApplicationContext`中以进行端点注册是很重要的，<br/>当动态流注册被破坏时，相关的`ServerWebSocketContainer`实例也被破坏，以及相应的端点注册也被破坏，包括 URL 路径映射。|
|---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |动态 WebSocket 端点只能通过 Spring 集成机制进行注册：当使用常规 Spring 时， Spring 集成配置退出，并且不注册用于动态端点的基础设施。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|