书接上文,Spring Chapter4 WebSocket 胡乱翻译 (一)

4.4.4. 消息流

一旦暴露了STOMP端点,Spring应用程序就成为连接客户端的STOMP代理。 本节介绍服务器端的消息流。

Spring-messaging模块包含对源自Spring Integration的消息传递应用程序的基础支持,后来被提取并整合到Spring Framework中,以便在许多Spring项目和应用程序场景中得到更广泛的使用。 下面列出了一些可用的消息传递抽象:

  • Message - 包含标头和内容的消息的简单表示。
  • MessageHandler - 处理消息的合同。
  • MessageChannel - 发送消息的合同,该消息允许生成者和使用者之间的松散耦合。
  • SubscribableChannel - MessageChannel和MessageHandler订阅者。
  • ExecutorSubscribableChannel - 使用Executor传递消息的SubscribableChannel。

@EnableWebSocketMessageBroker使用上面的组件来实现消息的工作流。

下图示意了spring自带的简单消息代理:

“clientInboundChannel” - 用于传递从WebSocket客户端收到的消息。
“clientOutboundChannel” - 用于将服务器消息发送到WebSocket客户端。
“brokerChannel” - 用于从服务器端的应用程序代码向消息代理发送消息。

下图显示了Spring使用外部的代理,比如ActiveMQ:

当从WebSocket连接接收消息时,它们被解码为STOMP帧,然后变为Spring消息表示,并发送到“clientInboundChannel”以进行进一步处理。 例如,目标头以“/ app”开头的STOMP消息可以被路由到带注释的控制器中的@MessageMapping方法,而“/ topic”和“/ queue”消息可以直接路由到消息代理。

处理来自客户端的STOMP消息的带注释的@Controller可以通过“brokerChannel”向消息代理发送消息,并且代理将通过“clientOutboundChannel”将消息广播给匹配的订户。 相同的控制器也可以响应HTTP请求执行相同的操作,因此客户端可以执行HTTP POST,然后@PostMapping方法可以向消息代理发送消息以向订阅的客户端广播。

让我们通过一个简单的例子来追踪消息流。 鉴于以下服务器设置:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
} @Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
} @Controller
public class GreetingController {
@MessageMapping("/greeting"){
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}

1.客户端连接到"http://localhost:8080/portfolio",一旦建立了WebSocket连接,STOMP帧就会开始流动。
2.客户端发送带有目标头"/topic/greeting"的SUBSCRIBE帧。一旦接收并解码,该消息就被发送到“clientInboundChannel”,然后被路由到存储客户端订阅的消息代理。
3.客户端将SEND帧发送到"/app/greeting"。 "/app"前缀有助于将其路由到带注释的控制器。删除“/ app”前缀后,目标的剩余“/ greeting”部分将映射到GreetingController中的@MessageMapping方法。
4.从GreetingController返回的值变为Spring消息,其中消息内容基于返回值和默认目标头"/topic/greeting"(从输入目标派生,"/app"替换为"/topic" )。生成的消息将发送到"brokerChannel" 并由消息代理处理。
5.消息代理找到所有匹配的订户,并通过“clientOutboundChannel”向每个订户发送MESSAGE帧,消息被编码为STOMP帧并在WebSocket连接上发送。

4.4.5. Annotated Controllers

应用程序可以使用带@Controller注释的类来处理来自客户端的消息。 这些类可以声明@MessageMapping,@ SubscribeMapping和@ExceptionHandler方法,如下所述。

@MessageMapping

@MessageMapping注释可用于根据目标路由消息的方法。 它在方法级别和类型级别受支持。 在类型级别,@ MessessMapping用于表示控制器中所有方法的共享映射。

默认情况下,目标映射应为Ant样式,路径模式,例如, “/foo *”,“/foo/**”。 模式包括对模板变量的支持,例如 “/foo /{id}”,可以使用@DestinationVariable方法参数引用。

当@MessageMapping方法返回一个值时,默认情况下,该值通过配置的MessageConverter序列化为有效负载,然后作为消息发送到“brokerChannel”,从那里向用户广播。 出站消息的目的地与入站消息的目的地相同,但前缀为“/ topic”。

您可以使用@SendTo方法批注来自定义将消息内容发送到的目标。 @SendTo也可以在类级别使用,以共享发送消息的默认目标。 @SendToUser是仅向与消息关联的用户发送消息的变体。 有关详细信息,请参阅用户目标。

@MessageMapping方法的返回值可以用ListenableFuture,CompletableFuture或CompletionStage包装,以便异步生成消息内容。

作为从@MessageMapping方法返回消息内容的替代方法,您还可以使用SimpMessagingTemplate发送消息,这也是在封面下处理返回值的方式。 请参阅发送消息。

@SubscribeMapping

@SubscribeMapping类似于@MessageMapping,但仅将映射缩小为订阅消息。 它支持与@MessageMapping相同的方法参数。 但是对于返回值,默认情况下,消息通过“clientOutboundChannel”直接发送到客户端以响应订阅,而不是通过“brokerChannel”作为对匹配订阅的广播发送给代理。 添加@SendTo或@SendToUser会覆盖此行为并发送给代理。

什么时候有用? 假设应用程序控制器映射到“/app”时代理映射到“/topic”和“/queue”。 在此设置中,代理将所有订阅存储到旨在用于重复广播的“/topic”和“/queue”,并且不需要应用程序参与。 客户端还可以订阅一些“/app”目的地,并且控制器可以返回响应该订阅的值而不涉及代理,实际上是一次性的请求 - 回复交换,而无需再次存储或使用订阅。 一种情况是在启动时使用初始数据填充UI。

什么时候这没用? 不要尝试将代理和控制器映射到相同的目标前缀,除非您希望由于某种原因单独处理消息(包括订阅)。 入站消息是并行处理的。 无法保证代理或控制器是否将首先处理给定的消息。 如果在存储订阅并准备好广播时通知目标,则客户端应该在服务器支持时询问收据(简单代理不支持)。 例如,使用Java STOMP Client:

@Autowired
private TaskScheduler messageBrokerTaskScheduler; // During initialization..
stompClient.setTaskScheduler(this.messageBrokerTaskScheduler); // When subscribing..
StompHeaders headers = new StompHeaders();
headers.setDestination("/topic/...");
headers.setReceipt("r1");
FrameHandler handler = ...;
stompSession.subscribe(headers, handler).addReceiptTask(() -> {
// Subscription ready...
});

一个服务器端的选择是在brokerChannel上注册ExecutorChannelInterceptor,并实现在处理完消息(包括订阅)后调用的afterMessageHandled方法。

@MessageExceptionHandler

应用程序可以使用@MessageExceptionHandler方法来处理来自@MessageMapping方法的异常。 感兴趣的异常可以在注释本身中声明,或者如果要获取对异常实例的访问权限,则可以通过方法参数声明:

@Controller
public class MyController {
// ...
@MessageExceptionHandler
public ApplicationError handleException(MyException exception) {
// ...
return appError;
}
}

@MessageExceptionHandler方法支持灵活的方法签名,并支持与@MessageMapping方法相同的方法参数类型和返回值。

通常,@ MessessExceptionHandler方法在声明它们的@Controller类(或类层次结构)中应用。如果您希望这些方法在控制器之间全局应用更多,则可以在标有@ControllerAdvice的类中声明它们。 这与Spring MVC中的类似支持相当。

4.4.6. 发送消息

如果要从应用程序的任何部分向连接的客户端发送消息,该怎么办? 任何应用程序组件都可以向“brokerChannel”发送消息。 最简单的方法是注入一个SimpMessagingTemplate,并使用它来发送消息。 通常,应该很容易按类型注入,例如:

@Controller
public class GreetingController {
private SimpMessagingTemplate template; @Autowired
public GreetingController(SimpMessagingTemplate template) {
this.template = template;
} @RequestMapping(path = "/greetings", method = POST)
public void greet(String greeting) {
String text = "[" + getTimestamp() + "]:" + greeting;
this.template.convertAndSend("/topic/greetings", text);
}
}

但如果存在相同类型的另一个bean,它也可以通过其名称“brokerMessagingTemplate”进行限定。

4.4.7. Simple Broker

内置的简单消息代理处理来自客户端的订阅请求,将它们存储在内存中,并将消息广播到具有匹配目标的连接客户端。 代理支持类似路径的目标,包括对Ant样式目标模式的订阅。

4.4.8. External Broker

简单的代理非常适合入门但仅支持STOMP命令的子集(例如,没有ack,收据等),依赖于简单的消息发送循环,并且不适合于群集。 作为替代方案,应用程序可以升级到使用功能齐全的消息代理。

检查STOMP文档以查找您选择的消息代理(例如RabbitMQ,ActiveMQ等),安装代理,并在启用STOMP支持的情况下运行它。 然后在Spring配置中启用STOMP代理中继而不是简单代理。

以下是启用功能齐全的代理的示例配置:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio").withSockJS();
} @Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
}
}

上述配置中的“STOMP代理中继”是Spring MessageHandler,它通过将消息转发到外部消息代理来处理消息。 为此,它建立到代理的TCP连接,将所有消息转发给它,然后通过其WebSocket会话将从代理接收的所有消息转发给客户端。 从本质上讲,它充当“转发”,可以在两个方向上转发消息。

注意:请将io.projectreactor.ipc:reactor-netty和io.netty:netty-all dependencies添加到项目中以进行TCP连接管理。

此外,应用程序组件(例如,HTTP请求处理方法,业务服务等)也可以向代理中继发送消息,如发送消息中所述,以便向订阅的WebSocket客户端广播消息。

实际上,代理中继实现了健壮且可扩展的消息广播。

4.4.9. Connect to Broker

STOMP代理中继维护与代理的单个“系统”TCP连接。 此连接仅用于源自服务器端应用程序的消息,而不用于接收消息。 您可以为此连接配置STOMP凭据,即STOMP帧登录和密码标头。 这在XML命名空间和Java配置中都显示为systemLogin / systemPasscode属性,默认值为guest / guest。

STOMP代理中继还为每个连接的WebSocket客户端创建单独的TCP连接。 您可以配置STOMP凭据以用于代表客户端创建的所有TCP连接。 它在XML命名空间和Java配置中作为clientLogin / clientPasscode属性公开,默认值为guest / guest。

提示:STOMP代理中继始终在每个CONNECT帧上设置登录和密码头,它代表客户端转发给代理。 因此,WebSocket客户端无需设置这些标头; 他们会被忽略。 正如身份验证部分所述,WebSocket客户端应该依赖HTTP身份验证来保护WebSocket端点并建立客户端身份。

STOMP代理中继还通过“system”TCP连接向消息代理发送和接收心跳。 您可以配置发送和接收心跳的间隔(默认情况下每个10秒)。 如果与代理的连接丢失,代理中继将继续尝试每5秒重新连接一次,直到成功为止。

任何Spring bean都可以实现ApplicationListener <BrokerAvailabilityEvent>,以便在与代理的“系统”连接丢失并重新建立时接收通知。 例如,股票报价服务广播股票报价可以在没有活动的“system”连接时停止尝试发送消息。

默认情况下,STOMP代理中继始终连接,并在连接丢失时根据需要重新连接到同一主机和端口。 如果您希望提供多个地址,则在每次尝试连接时,您都可以配置地址供应商,而不是固定的主机和端口。 例如:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
// ...
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/", "/topic/").setTcpClient(createTcpClient());
registry.setApplicationDestinationPrefixes("/app");
} private ReactorNettyTcpClient<byte[]> createTcpClient() {
Consumer<ClientOptions.Builder<?>> builderConsumer = builder -> {
builder.connectAddress(() -> {
// Select address to connect to ...
});
};
return new ReactorNettyTcpClient<>(builderConsumer, new StompReactorNettyCodec());
}
}

还可以使用virtualHost属性配置STOMP代理中继。 此属性的值将被设置为每个CONNECT帧的主机头,并且可能在例如云环境中有用,其中建立TCP连接的实际主机与提供基于云的STOMP服务的主机不同。

4.4.10 点作为分隔符

当消息路由到@MessageMapping方法时,它们与AntPathMatcher匹配,并且默认模式应使用斜杠“/”作为分隔符。 这是Web应用程序中的一个很好的约定,类似于HTTP URL。 但是,如果您更习惯于消息传递约定,则可以切换到使用点“.” 作为分隔符。

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { // ... @Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setPathMatcher(new AntPathMatcher("."));
registry.enableStompBrokerRelay("/queue", "/topic");
registry.setApplicationDestinationPrefixes("/app");
}
}

之后,控制器可以使用点“.” 作为@MessageMapping方法中的分隔符:

@Controller
@MessageMapping("foo")
public class FooController {
@MessageMapping("bar.{baz}")
public void handleBaz(@DestinationVariable String baz) {
// ...
}
}

客户端现在可以向“/app/foo.bar.baz123”发送消息。

在上面的示例中,我们没有更改“代理中继”上的前缀,因为它们完全依赖于外部消息代理。 检查您正在使用的代理的STOMP文档页面,以查看它为目标标头支持的约定。

另一方面,“简单代理”确实依赖于配置的PathMatcher,因此如果您切换也将应用于代理的分隔符,并且将消息中的目标与订阅中的模式匹配。

4.4.11认证 (Authentication)

WebSocket消息传递会话中的每个STOMP都以HTTP请求开始 - 可以是升级到WebSockets的请求(即WebSocket握手),或者在SockJS回退一系列SockJS HTTP传输请求的情况下。

Web应用程序已经具有用于保护HTTP请求的身份验证和授权。 通常,用户通过Spring Security使用某种机制(例如登录页面,HTTP基本身份验证或其他)进行身份验证。 经过身份验证的用户的安全上下文保存在HTTP会话中,并与同一个基于cookie的会话中的后续请求相关联。

因此,对于WebSocket握手或SockJS HTTP传输请求,通常已经存在可通过HttpServletRequest#getUserPrincipal()访问的经过身份验证的用户。 Spring自动将该用户与为其创建的WebSocket或SockJS会话相关联,随后通过用户头与该会话上传输的所有STOMP消息相关联。

简而言之,为了安全性,典型的Web应用程序不需要做任何其他特殊的事情。 用户在HTTP请求级别进行身份验证,并通过基于cookie的HTTP会话维护安全上下文,然后将该会话与为该用户创建的WebSocket或SockJS会话相关联,并在流经应用程序的每个Message上标记用户标头。

请注意,STOMP协议在CONNECT帧上确实有“登录”和“密码”标头。 这些最初设计用于并且仍然需要例如用于TCP上的STOMP。 但是,对于STOMP over WebSocket,Spring默认忽略STOMP协议级别的授权标头,并假定用户已在HTTP传输级别进行了身份验证,并期望WebSocket或SockJS会话包含经过身份验证的用户。

提示:Spring Security提供WebSocket子协议授权,该授权使用ChannelInterceptor根据其中的用户头来授权消息。 此外,Spring Session还提供WebSocket集成,以确保在WebSocket会话仍处于活动状态时,用户HTTP会话不会过期。

4.4.12令牌认证

Spring Security OAuth支持基于令牌的安全性,包括JSON Web Token(JWT)。 这可以用作Web应用程序中的身份验证机制,包括STOMP over WebSocket交互,正如上一节所述,即通过基于cookie的会话维护身份。

同时,基于cookie的会话并不总是最适合,例如在压根不希望维护服务器端会话的应用程序中,或者在通常使用标头进行身份验证的手机应用程序中。

WebSocket协议RFC 6455“没有规定服务器在WebSocket握手期间可以对客户端进行身份验证的任何特定方式。” 实际上,浏览器客户端只能使用标准身份验证标头(即基本HTTP身份验证)或cookie,并且不能提供自定义标头。 同样,SockJS JavaScript客户端没有提供使用SockJS传输请求发送HTTP标头的方法,请参阅sockjs-client问题196.相反,它确实允许发送可用于发送令牌但具有其自身缺点的查询参数,例如 因为令牌可能无意中使用服务器日志中的URL进行了记录。

提示:以上限制适用于基于浏览器的客户端,不适用于基于Spring Java的STOMP客户端,该客户端支持使用WebSocket和SockJS请求发送头文件

因此,希望避免使用cookie的应用程序可能无法在HTTP协议级别进行身份验证。 他们可能更喜欢在STOMP消息传递协议级别使用标头进行身份验证,而不是使用Cookie。有两个简单的步骤可以做到这一点:

  1. 使用STOMP客户端在连接时传递身份验证标头。
  2. 使用ChannelInterceptor处理身份验证标头。

下面是注册自定义身份验证拦截器的示例服务器端配置。 请注意,拦截器只需要在CONNECT消息上进行身份验证并设置用户头。 Spring将记录并保存经过身份验证的用户,并将其与同一会话中的后续STOMP消息相关联:

@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
Authentication user = ...; // access authentication header(s)
accessor.setUser(user);
}
return message;
}
});
}
}

还要注意,当使用Spring Security的消息授权时,目前您需要确保在Spring Security之前申请认证ChannelInterceptor配置。 最好通过在自己的标记为@Order(Ordered.HIGHEST_PRECEDENCE + 99)的WebSocketMessageBrokerConfigurer实现中声明自定义拦截器来完成。

4.4.13 用户目的地

应用程序可以发送针对特定用户的消息,Spring的STOMP支持可识别以“/user/”为前缀的目标。 例如,客户端可能订阅目标“/user/queue/position-updates”。 该目的地将由UserDestinationMessageHandler处理并转换为用户会话唯一的目的地,例如,“/queue/position-updates-user123”。 这提供了订阅一般命名的目的地的便利性,同时确保不与订阅相同目的地的其他用户发生冲突,使得每个用户可以接收唯一的库存位置更新。

在发送方,消息可以发送到目的地,例如“/user/{username}/queue/position-updates”,然后由UserDestinationMessageHandler将其转换为一个或多个目的地,每个目的地对应用户会话。 这允许应用程序中的任何组件发送针对特定用户的消息,而不必知道除其名称和通用目标之外的任何内容。 通过注释和消息传递模板也支持这一点。

例如,消息处理方法可以向与通过@SendToUser注释处理的消息相关联的用户发送消息(在类级别上也支持共享公共目的地):

@Controller
public class PortfolioController {
@MessageMapping("/trade")
@SendToUser("/queue/position-updates")
public TradeResult executeTrade(Trade trade, Principal principal) {
// ...
return tradeResult;
}
}

如果用户有多个会话,则默认情况下,所有订阅给定目标的会话都是目标。 但是,有时可能需要仅定位发送正在处理的消息的会话。 这可以通过将broadcast属性设置为false来完成,例如:

@Controller
public class MyController {
@MessageMapping("/action")
public void handleAction() throws Exception {
// raise MyBusinessException here
} @MessageExceptionHandler
@SendToUser(destinations = "/queue/errors", broadcast = false)
public ApplicationError handleException(MyBusinessException exception) {
// ...
return appError;
}
}

虽然用户目的地通常意味着经过身份验证的用户,但并不严格要求。 与经过身份验证的用户无关的WebSocket会话可以订阅用户目标。 在这种情况下,@ SendToUser注释的行为与broadcast = false完全相同,即仅针对发送正在处理的消息的会话。

例如,通过注入由Java配置或XML命名空间创建的SimpMessagingTemplate,也可以从任何应用程序组件向用户目标发送消息(如果需要使用@Qualifier进行限定,则bean名称为“brokerMessagingTemplate”):

@Service
public class TradeServiceImpl implements TradeService {
private final SimpMessagingTemplate messagingTemplate; @Autowired
public TradeServiceImpl(SimpMessagingTemplate messagingTemplate) {
this.messagingTemplate = messagingTemplate;
} // ...
public void afterTradeExecuted(Trade trade) {
this.messagingTemplate.convertAndSendToUser(trade.getUserName(), "/queue/position-updates", trade.getResult());
}
}

提示:将用户目标与外部消息代理一起使用时,请检查代理文档,了解如何管理非活动队列,以便在用户会话结束时删除所有唯一用户队列。 例如,当使用/exchange/amq.direct/position-updates等目标时,RabbitMQ会创建自动删除队列。 因此,在这种情况下,客户端可以订阅/user/exchange/amq.direct/position-updates。 同样,ActiveMQ具有用于清除非活动目标的配置选项

在多应用程序服务器方案中,用户目标可能仍未解析,因为用户连接到不同的服务器。 在这种情况下,您可以配置目标以广播未解析的消息,以便其他服务器有机会尝试。 这可以通过Java config中的MessageBrokerRegistryuserDestinationBroadcast属性和XML中的message-broker元素的userdestination-broadcast属性来完成。

4.4.14消息顺序

来自代理的消息将发布到“clientOutboundChannel”,从那里将它们写入WebSocket会话。 由于通道由ThreadPoolExecutor支持,因此消息在不同的线程中处理,并且客户端接收的结果序列可能与发布的确切顺序不匹配。

如果这是一个问题,请启用以下标志:

@Configuration
@EnableWebSocketMessageBroker
public class MyConfig implements WebSocketMessageBrokerConfigurer {
@Override
protected void configureMessageBroker(MessageBrokerRegistry registry) {
// ...
registry.setPreservePublishOrder(true);
}
}

设置标志后,同一客户端会话每次只发送一个消息发布到“clientOutboundChannel”,以便保证发布顺序。 请注意,这会导致额外性能开销,因此仅在需要时才启用它。

4.4.15. 事件

发布了几个ApplicationContext事件(如下所列),可以通过实现Spring的ApplicationListener接口来接收它们。

  • BrokerAvailabilityEvent
  • SessionConnectEvent
  • SessionConnectedEvent
  • SessionSubscribeEvent
  • SessionUnsubscribeEvent
  • SessionDisconnectEvent

注意:使用功能齐全的代理时,STOMP“代理中继”会自动重新连接“system”连接,以防代理暂时不可用。 但是,客户端连接不会自动重新连接。 假设启用了心跳,客户端通常会注意到代理在10秒内没有响应。 客户端需要实现自己的重新连接逻辑。

4.4.16拦截

事件提供STOMP连接生命周期的通知,而不是每个客户端消息的通知。 应用程序还可以注册ChannelInterceptor来拦截任何消息,以及处理链的任何部分。 例如,拦截来自客户端的入站消息:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new MyChannelInterceptor());
}
}

自定义的ChannelInterceptor可以使用StompHeaderAccessor或SimpMessageHeaderAccessor来访问有关消息的信息。

public class MyChannelInterceptor implements ChannelInterceptor {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
StompCommand command = accessor.getStompCommand();
// ...
return message;
}
}

应用程序还可以实现ExecutorChannelInterceptor,它是ChannelInterceptor的子接口,在处理消息的线程中具有回调。 虽然为发送到通道的每个消息调用一次ChannelInterceptor,但ExecutorChannelInterceptor在订阅来自通道的消息的每个MessageHandler的线程中提供挂钩。

请注意,就像上面的SesionDisconnectEvent一样,可能已从客户端发送DISCONNECT消息,或者也可能在WebSocket会话关闭时自动生成。 在某些情况下,拦截器可能会在每个会话中多次拦截此消息。 对于多个断开连接事件,组件应该是幂等的。

4.4.17 STOMP客户端

Spring可以通过WebSocket客户端或者通过TCP客户端这2种方式提供STOMP。
要开始创建和配置WebSocketStompClient:

WebSocketClient webSocketClient = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
stompClient.setTaskScheduler(taskScheduler); // for heartbeats

在上面的示例中,StandardWebSocketClient可以替换为SockJsClient,因为它也是WebSocketClient的实现。 SockJsClient可以使用WebSocket或基于HTTP的传输作为后备。 有关更多详细信息,请参阅SockJsClient。

接下来建立连接并为STOMP会话提供处理程序:

String url = "ws://127.0.0.1:8080/endpoint";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(url, sessionHandler);

当会话准备好使用时,会通知处理程序:

public class MyStompSessionHandler extends StompSessionHandlerAdapter {
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
// ...
}
}

建立会话后,可以发送任何消息内容,并使用配置的MessageConverter对其进行序列化:

session.send("/topic/foo", "payload");

您也可以订阅目的地。 订阅方法需要处理订阅消息的处理程序,并返回可用于取消订阅的订阅句柄。 对于每个收到的消息,处理程序可以指定消息内容应该反序列化的目标对象类型:

session.subscribe("/topic/foo", new StompFrameHandler() {
@Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
// ...
}
});

要启用STOMP心跳,请使用TaskScheduler配置WebSocketStompClient,并可选择自定义心跳间隔,写入不活动10秒,导致发送心跳,10秒读取不活动,关闭连接。

STOMP协议还支持收据,其中客户端必须添加“收据”标头,服务器在处理发送或订阅后用RECEIPT帧响应。 为了支持这一点,StompSession提供了setAutoReceipt(boolean),它导致在每个后续发送或订阅时添加“收据”标头。 或者,您也可以手动将“收据”标题添加到StompHeaders。 发送和订阅都返回一个Receiptable实例,可用于注册接收成功和失败回调。 对于此功能,客户端必须配置TaskScheduler和收据到期前的时间(默认为15秒)。

请注意,StompSessionHandler本身是一个StompFrameHandler,它允许它处理ERROR帧以及处理消息的异常的handleException回调,以及包含ConnectionLostException的传输级错误的handleTransportError。

4.4.18. WebSocket Scope

每个WebSocket会话都有一个属性映射。 映射作为标头附加到入站客户端消息,并且可以从控制器方法访问,例如:

@Controller
public class MyController {
@MessageMapping("/action")
public void handle(SimpMessageHeaderAccessor headerAccessor) {
Map<String, Object> attrs = headerAccessor.getSessionAttributes();
// ...
}
}

也可以在websocket范围中声明一个Spring管理的bean。 WebSocket范围的bean可以注入控制器和“clientInboundChannel”上注册的任何通道拦截器。 这些通常是单例,比任何单独的WebSocket会话都更长寿。 因此,您需要为WebSocket范围的bean使用范围代理模式:

@Component
@Scope(scopeName = "websocket", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBean {
@PostConstruct
public void init() {
// Invoked after dependencies injected
} // ...
@PreDestroy
public void destroy() {
// Invoked when the WebSocket session ends
}
} @Controller
public class MyController {
private final MyBean myBean; @Autowired
public MyController(MyBean myBean) {
this.myBean = myBean;
} @MessageMapping("/action")
public void handle() {
// this.myBean from the current WebSocket session
}
}

与任何自定义作用域一样,Spring在第一次从控制器访问时初始化一个新的MyBean实例,并将该实例存储在WebSocket会话属性中。 随后返回相同的实例,直到会话结束。 WebSocket范围的bean将调用所有Spring生命周期方法,如上面的示例所示。

4.4.19. Performance

4.4.20. Monitoring

4.4.21. Testing

Spring Chapter4 WebSocket 胡乱翻译 (二)的更多相关文章

  1. Spring Chapter4 WebSocket 胡乱翻译 (一)

    4. WebSocket 包含了Servlet stack,原生WebSocket交互,通过SockJS模拟,并且通过STOMP在WebSocket之上订阅.发布消息. 4.1 简介 不扯了,看到这个 ...

  2. Spring Chapter4 WebSocket 胡乱翻译 (一) 一个例子

    因为没有基础,不知道从哪里入手. 文档里的例子,https://github.com/rstoyanchev/spring-websocket-portfolio,这个除了WebSocket,还整了S ...

  3. 玩转spring boot——websocket

    前言 QQ这类即时通讯工具多数是以桌面应用的方式存在.在没有websocket出现之前,如果开发一个网页版的即时通讯应用,则需要定时刷新页面或定时调用ajax请求,这无疑会加大服务器的负载和增加了客户 ...

  4. spring boot websocket stomp 实现广播通信和一对一通信聊天

    一.前言 玩.net的时候,在asp.net下有一个叫 SignalR 的框架,可以在ASP .NET的Web项目中实现实时通信.刚接触java寻找相关替代品,发现 java 体系中有一套基于stom ...

  5. Spring Boot WebSocket从入门到放弃

    在构建Spring boot项目时已经提供webSocket依赖的勾选.webSocket是TCP之上的一个非常薄的轻量级层 ,webSocket主要的应用场景离不开即时通讯与消息推送,但只要应用程序 ...

  6. Java Spring Boot VS .NetCore (二)实现一个过滤器Filter

    Java Spring Boot VS .NetCore (一)来一个简单的 Hello World Java Spring Boot VS .NetCore (二)实现一个过滤器Filter Jav ...

  7. spring事务详解(二)简单样例

    系列目录 spring事务详解(一)初探事务 spring事务详解(二)简单样例 spring事务详解(三)源码详解 spring事务详解(四)测试验证 spring事务详解(五)总结提高 一.引子 ...

  8. idea+maven+spring+cxf创建webservice应用(二)生成客户端程序

    idea+maven+spring+cxf创建webservice应用(二)生成客户端程序,以上一篇为基础"idea+maven+spring+cxf创建webservice应用" ...

  9. spring集成webSocket实现服务端向前端推送消息

    原文:https://blog.csdn.net/ya_nuo/article/details/79612158 spring集成webSocket实现服务端向前端推送消息   1.前端连接webso ...

随机推荐

  1. 单元测试unittest

    unittest.TestSuite():用例集合 uinttest.defaultTestLoader.discover():寻找测试用例,去哪个目录下寻找测试用例,加载测试用例

  2. 写excel

    一.写excel import xlwt book = xlwt.Workbook()# 创建excel sheet = book.add_sheet('stu_info')# 加一个sheet sh ...

  3. 【转】如何不让DataGridView自动生成列

    源地址:https://www.cnblogs.com/hailexuexi/p/3983856.html

  4. loj #2508. 「AHOI / HNOI2018」游戏

    #2508. 「AHOI / HNOI2018」游戏 题目描述 一次小 G 和小 H 在玩寻宝游戏,有 nnn 个房间排成一列,编号为 1,2,…,n,相邻房间之间都有 111 道门.其中一部分门上有 ...

  5. rpm命令-以jenkins为例

    1.列出所有安装的Jenkins rpm -qa | grep jenkins 2.软件是否安装:例如:jenkins是否安装 rpm -qa | grep jenkins 3.rpm -ql 列出软 ...

  6. DataRow[]转DataTable

    DataRow[]有个扩展方法CopyToDataTable()

  7. 2A - Stone

    任意一堆移动过后的石子都是整数x的倍数, 那么石子总数显然也应该是x的倍数, 换句话说,x必为石子数总和的一个质因子. 题目要求移动次数尽量小,那么x也应该尽量小. 所以选择石子数总和的最小质因子. ...

  8. springcloud微服务总结四 负载均衡

    一:Ribbon简介 Ribbon是Netflix公司开源的一个负载均衡的项目,是一个客户端负载均衡器,运行在客户端上.它是一个经过了云端测试的IPC库,可以很好地控制HTTP和TCP客户端的一些行为 ...

  9. abp + angular 前端使用 hash ,登录界面不跳转问题

    abp 项目默认的路由没有使用hash,这会导致手动刷新浏览器时,页面404错误: 解决方法网上很多,就是在路由里添加一个{useHash: true},就行了. #用Hash带来的新问题# abp框 ...

  10. 危险系数(枚举点+bfs)--------蓝桥备战系列

    标题:危险系数 抗日战争时期,冀中平原的地道战曾发挥重要作用. 地道的多个站点间有通道连接,形成了庞大的网络.但也有隐患,当敌人发现了某个站点后,其它站点间可能因此会失去联系.        我们来定 ...