正如以前我所写的那样,WebSocket API只是WebSocket形式消息应用的起点。许多实际的挑战仍然存在。这也正是一个Tomcat邮件列表用户最近苦思冥想的:
确实对我来说Websocket仍然不是一个真正的“已经准备好的产品”,(我不讨论Tomcat本身的实现,只是更一般性地讨论)...IE里的本地WebSocket功能只是自IE-10开始才可用,而且允许低版本的IE运行WebSocket的方案是有些“不确定的”(例如转到取决于Adobe的FlashPlayer而不是取决于IE本身)。(我们的大多数客户都是相当大的公司,它们不打算更新浏览器,也不在防火墙上开放特殊的端口,只是请我们去做)。
Spring框架4.0的第一个发布版提供了SockJS服务器端的支持,以及最好的和最多的综合的WebSocket浏览器后备选项。在那些不支持WebSocket的浏览器里,我们需要这些后备选项。这种情况下网络代理将阻止使用WebSocket。今天,简单地把SockJS放置在浏览器里就能让你创建WebSocket应用,而且在必要的时候由未被察觉的后备选项决定该怎么做。
股票组合案例应用, 可自Github获得, 加载一个用户的投资组合头寸,允许购买和出售股票,消费价格行情,以及显示位置更新。这是一个相当简单的应用。但是它解决了基于浏览器的消息应用可能会遇到的 许多一般性任务。
那么我们怎样建立起一个这样的应用呢?从使用HTTP和REST以后,我们就已习惯于基于URL和HTTP动作来表达需要做的事情。这里我们有一个socket和许多的消息。你又怎样去告诉谁一个消息,并说明这个消息的意思呢?
浏览器与服务器必须在语义表达以前,对公共的消息格式达成一致。有几个已存在的协议可以帮助做到这一点。我们为这个里程碑选择了STOMP,这归因于它的简单与广泛的支持。
SUBSCRIBE id:sub-1 destination:/topic/price.stock.*
当股票报价有效时,服务器发送含有匹配目的和订阅id以及内容类型头和内容体的MESSAGE帧:
MESSAGE subscription:sub-1 message-id:wm2si1tj-4 content-type: application/json destination:/topic/stocks.PRICE.STOCK.NASDAQ.EMC {\"ticker\":\"EMC\",\"price\":24.19}为了在浏览器里实现这些,我们使用了 stomp.js 和 SockJS客户端:
varsocket =newSockJS('/spring-websocket-portfolio/portfolio'); varclient = Stomp.over(socket); varonConnect =function() { client.subscribe("/topic/price.stock.*",function(message) { // process quote }); }; client.connect('guest','guest', onConnect);这已经获得巨大收获!我们拥有了标准的消息格式和客户端支持。
装载投资组合应用的状态
订阅股票报价
接收股票报价
执行交易
接收位置更新
stompClient.subscribe("/app/positions",function(message) { self.portfolio().loadPositions(JSON.parse(message.body)); });在服务器端,PortfolioController检测请求,然后返回投资组合应用的状态,这解释了互联网应用里非常普通的请求-应答交互。由于我们使用Spring Security来保护HTTP请求,它包括产生WebSocket握手的请求。下面的principal方法参数是从用户HttpServeletRequest的principal Spring Security集里提取出来的。
@Controller publicclassPortfolioController { // ... @SubscribeEvent("/app/positions") publicList<PortfolioPosition> getPortfolios(Principal principal) { String user = principal.getName(); Portfolio portfolio =this.portfolioService.findPortfolio(user); returnportfolio.getPositions(); } }下面发送交易请求的protfolio.js:
stompClient.send("/app/trade", {}, JSON.stringify(trade));在服务器端,PortfolioController发送执行的交易:
@Controller publicclassPortfolioController { // ... @MessageMapping(value="/app/trade") publicvoidexecuteTrade(Trade trade, Principal principal) { trade.setUsername(principal.getName()); this.tradeService.executeTrade(trade); } }PortfolioController还可以处理不期望的例外,并发送消息给用户。
@Controller publicclassPortfolioController { // ... @MessageExceptionHandler @ReplyToUser(value="/queue/errors") publicString handleException(Throwable exception) { returnexception.getMessage(); } }从应用内部发送消息给订阅的用户意味着什么呢?下面是报价服务如何发送报价的:
@Service publicclassQuoteService { privatefinalMessageSendingOperations<String> messagingTemplate; @Scheduled(fixedDelay=1000) publicvoidsendQuotes() { for(Quote quote :this.quoteGenerator.generateQuotes()) { String destination ="/topic/price.stock."+ quote.getTicker(); this.messagingTemplate.convertAndSend(destination, quote); } } }而下面是交易服务器在交易执行完成后是如何发送状态更新的:
@Service publicclassTradeService { // ... @Scheduled(fixedDelay=1500) publicvoidsendTradeNotifications() { for(TradeResult tr :this.tradeResults) { String queue ="/queue/position-updates"; this.messagingTemplate.convertAndSendToUser(tr.user, queue, tr.position); } } }而且以防你疑惑....不要疑惑,根据以前构建在线游戏应用的开发者在文档里所提的建议PortfolioController还可以包含Spring的MVC方法(例如@RequestMapping):
是的,把[消息]映射和Spring的MVC映射统一是很好的。没有理由不统一 它们。就像报价服务和交易服务一样,Spring的MVC控制器方法也可以发布消息。
Spring Integration已经为众所周知的企业集成模式和轻量级消息处理提供一流的抽象很长时间了。当我们在Spring Integration上面工作的时候,我们认识到后者才真正是我们需要构建的基础。
因此,我很高兴地宣布我们已经把精选出来的整合到Spring框架的Spring Integration原型转换为一个新的提前称为spring消息处理的模块。除了像Message,MessageChannel,MessageHandler以及其他核心抽象外,新的模块还包括所有支持这篇文章中所阐释的新特性的注释脚本和类。
有了这种思想,现在我们就可以看看股票投资组合应用的内部架构图:
评论删除后,数据将无法恢复
评论(22)