Spring Websocket

Spring Websocket

https://www.yuque.com/wangji-yunque/xrbpeo/iyw8ce
Spring Websocket 出现的背景是什么?spring 秉承一贯的作风,将我们的工作变得更加的简单方便,好使用。使用原生的API太麻烦,而且对于不同的Web 容器拥有对于API的不同的实现,这个对于我们使用者来说十分的不友好,spring 将这些不同点进行屏蔽,抽象出一套自己的称呼比如WebsocketSession,将这一套东西转换为Tomcat 支持的WebSocket 或者Jboss 支持的Websocket 或者Jetty 支持的Websocket。等等,反正就是简化了我们的操作,让我们使用起来更加的简单方便!除了处理这些数据信息之外,还给我们添加了一些不一样的功能,比如支持Sockjs协议,支持Stomp协议,这种协议,将比原生的使用起来更加的简单,而且连Session的连接的这种操作都给我们一并的处理啦,让我们释放了我们的双手哦哈哈!但是这样不太好,很多的底层的原理的实现都被屏蔽了,理解问题要在学会使用之后,慢慢的了解其原理,方便我们进一步的定制或者排查问题。

简单Websocket的使用

要向学习并理解一个东西的原理,能够简单的使用是十分的有必要的!自己亲手实践,才能不断的逐步了解其内部的世界,切莫心急,一开始就想知道所有的东西,那个是不可能的。

@EnableWebSocket 注解是打开Websocket 世界的一个大门

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebSocketConfiguration.class)
public @interface EnableWebSocket {
}

仔细查看这个注解,其实详细的用法,以及实现的逻辑(不懂的看看@Import的妙用)

   #Add this annotation to an @Configuration class to configure processing WebSocket requests:
   @Configuration
   @EnableWebSocket
   public class MyWebSocketConfig {
  
   }
   
  #Customize the imported configuration by implementing the WebSocketConfigurer interface:
   @Configuration
   @EnableWebSocket
   public class MyConfiguration implements WebSocketConfigurer {
  
   	@Override
   	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
   		registry.addHandler(echoWebSocketHandler(), "/socket-connect").withSockJS();
   	}
  
  	@Bean
  	public WebSocketHandler echoWebSocketHandler() {
  		return new EchoWebSocketHandler();
  	}
   }

前端如何处理呢?这个就是使用SocketJs 还是不适用SocketJs啦

  • SocketJs 和使用原生的类似哦
<html>
<head>
  <title>11</title>
  <script src="jquery.js"></script>
  <script src="sockjs.min.js"></script>
  <script>
      $(function () {
          socket = new SockJS("/socket-connect"); //创建连接
          socket.onopen = function() {
              socket.send("你好世界")
          };
          socket.onmessage = function(e) {
              //有消息传递到了
              console.log('message', e.data);
              alert(e.data)
          };
          socket.onclose = function() {
              console.log('close');
          };
          $("#send").click(function () {
              if(socket !=undefined){
                  socket.send("message");
              }else{
                  console.log("socket is null")
              }
          });
      });

  </script>
</head>
<body>
</body>
</html>
  • 使用原生的Websocket,下面这个需要这么写哦!
registry.addHandler(echoWebSocketHandler(), "/socket-connect")
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
</head>

<script>
    var socket;
    if(typeof(WebSocket) == "undefined") {
        console.log("您的浏览器不支持WebSocket");
    }else{

        //实现化WebSocket对象,指定要连接的服务器地址与端口  建立连接
   
        socket = new WebSocket("ws://localhost:8080/socket-connect");
 
        //打开事件
        socket.onopen = function() {
            console.log("Socket 已打开");
            //socket.send("这是来自客户端的消息" + location.href + new Date());
        };
        //获得消息事件
        socket.onmessage = function(msg) {
            console.log(msg.data);
            //发现消息进入    开始处理前端触发逻辑
        };
        //关闭事件
        socket.onclose = function() {
            console.log("Socket已关闭");
        };
        //发生了错误事件
        socket.onerror = function() {
            alert("Socket发生了错误");
            //此时可以尝试刷新页面
        }
    }
</script>

<body>
</body>
</html>

Websocket 的原理呢?

Spring WebSokcet 处理背后的故事

从@Import说起

不知道你有么有仔细的查看了解到@EnableWebsocket 这个注解中有个@import 这个注解,在spring早期的时候,这个注解使用的还是比较的少的,不过有个兄弟xml版本的,在spring boot的盛行的时代中注解的使用更加的频发,使用越来越高级啦,不过都是基本的包装,说白了就是导入具体的Bean到Spring IOC中去处理,有一些特殊的接口的实现,就是处理一种筛选的作用,有时间写一篇。这里导入的类就是整个Spring websocket处理在spring中的入口,看源码看代码,spring的代码特别的庞大,我们只能从其中的一些小的部分去慢慢的了解,对于基本的spring核心的东西还是有必要了解其思想哦。

Spring Websocket的整体实现,从配置到Websocket

从上面的Websocket的原理我们可以简单的了解到,Websocket处理过程中,握手的时候要走Http处理的哦。

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

要走Http的处理?那我们应该能够如何猜测Spring在处理过程中做了什么事情

  • 最少要暴露出Http请求的接口吧,也就是我们可以连接的 ws://localhost:8080/socket-connect 这个连接,也就是说有一次Http请求哦 http://localhost:8080/socket-connect,对于这个请求必须要暴露给spring mvc中去处理吧!这个让我们想到了?HandlerMapping HandlerMappingAdapter ??
  • 握手成功之后会存在哪些操作?我们心里面要清楚架构,假设为Tomcat服务器,Tomcat提供了Websocket 的实现逻辑。Spring 处理的握手拦截逻辑成功后,Spring将与Tomcat提供的API进行握手,让握手成功后,响应给客户端相应的信息,同时呢Tomcat在成功和客户端浏览器建立连接之后(建立连接之后使用TCP 上增加协议,具体可以参考Websocket协议原理)会回调相应的API,通过回调的API获取到当前的Session信息,由于SPring也有自己的一套进行相应的Adapter处理,同理前端发送websocket信息,Tomcat接受到信息之后也会调用相应的回调,这个时候我们配置类中的回调类就可以了获取信息,进行相应的处理,不知道你发现了没有,我们自己必须对相应的Session进行管理,才能有效的随心所欲的处理对于特定的客户端响应的消息。
    有了上面的简单的理解,对于我们更好的理解Spring的实现方式是非常的又必要的,这样我们看起源码的时候也会比较的简单,个人比较懒哈哈!不想画图。

从配置看起来

@Configuration
@EnableWebSocket
@Slf4j
public class WebSocketConfig implements WebSocketConfigurer {


    @Autowired
    private WebSocketHandler myWebSocketMessageHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(myWebSocketMessageHandler,"socket-connect");
    }   
}

  • socket-connect,这个就是我们自己配置暴露给Spring Mvc的,通过读取这个配置,然后让它能够被spring mvc发现并处理,哈哈。
  • WebsocketHandler 这个类就是和Tomcat 实现的Websocket 回调处理啦,只是没有使用Tomcat原生的逻辑,进行了相应的转换处理,看看实现的接口就懂了
public interface WebSocketHandler {

	/**
	 * 在WebSocket协商成功并且WebSocket连接打开并准备好使用后调用
	 * Invoked after WebSocket negotiation has succeeded and the WebSocket connection is
	 * opened and ready for use.
	 * @throws Exception this method can handle or propagate exceptions[此方法可以处理或传播异常]; see class-level
	 * Javadoc for details.
	 */
	void afterConnectionEstablished(WebSocketSession session) throws Exception;

	/**
	 * Invoked when a new WebSocket message arrives. 当新的WebSocket消息到达时调用
	 * @throws Exception this method can handle or propagate exceptions; see class-level
	 * Javadoc for details.
	 */
	void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;

	/**
	 * Handle an error from the underlying WebSocket message transport.
	 * 处理底层WebSocket消息传输中的错误
	 * @throws Exception this method can handle or propagate exceptions; see class-level
	 * Javadoc for details.
	 */
	void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;

	/**
	 * 在任何一方关闭WebSocket连接后或在发生传输错误后调用。
	 * 尽管会话在技术上可能仍然是打开的,取决于底层实现,在者种情况下上发送消息是不鼓励的,并且很可能不会成功
	 * Invoked after the WebSocket connection has been closed by either side, or after a
	 * transport error has occurred. Although the session may technically still be open,
	 * depending on the underlying implementation, sending messages at this point is
	 * discouraged and most likely will not succeed.
	 * @throws Exception this method can handle or propagate exceptions; see class-level
	 * Javadoc for details.
	 */
	void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;

	/**
	 * WebSocketHandler是否处理部分消息。 如果这个标志被设置为true,并且底层WebSocket服务器支持部分消息,
	 * 那么一个大的WebSocket消息或者一个未知大小的消息可能会被拆分,并且可能通过多次调用
	 * Whether the WebSocketHandler handles partial messages. If this flag is set to
	 * {@code true} and the underlying WebSocket server supports partial messages,
	 * then a large WebSocket message, or one of an unknown size may be split and
	 * maybe received over multiple calls to
	 * {@link #handleMessage(WebSocketSession, WebSocketMessage)}. The flag
	 * {@link org.springframework.web.socket.WebSocketMessage#isLast()} indicates if
	 * the message is partial and whether it is the last part.
	 */
	boolean supportsPartialMessages();

} 

从spring的整体说起,里面提到的两个点在这里都被说完了…这些都被说完了还怎么玩啊?

从源码中了解其实现逻辑(最好的体验就是下载spring 源码)

注意:查看源码不是在maven中不可以编辑的查看哦,最好是自己能够编辑,注释,像spring这么大的代码,不是一两次就能看懂,增加点标注能够下次快速的反应过来。

第一点:暴露握手的路径解析

配置类@EnableWebsocket

  • 所有的一切都是从配置类开始处理的,
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebSocketConfiguration.class)
public @interface EnableWebSocket {
}
  • 这里自动注入了WebSocketConfigurer,也就是我们配置暴露 websocket 连接地址的,以及处理器其回调逻辑的这个类,这里的意思就是收集所有的Websocket配置信息,收集起来保存在Configures中,在父类模板方法中留下了一个口子,也就是回调处理让WebSocketHandlerRegistry这个Websocket的Handler收集仓库能够收集到所有的信息,这样所有配置的信息都放在了仓库中去处理啦。
@Configuration
public class DelegatingWebSocketConfiguration extends WebSocketConfigurationSupport {

 
   private final List<WebSocketConfigurer> configurers = new ArrayList<WebSocketConfigurer>();


   @Autowired(required = false)
   public void setConfigurers(List<WebSocketConfigurer> configurers) {
      if (!CollectionUtils.isEmpty(configurers)) {
         this.configurers.addAll(configurers);
      }
   }


   @Override
   protected void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
      for (WebSocketConfigurer configurer : this.configurers) {
         configurer.registerWebSocketHandlers(registry);
      }
   }

}
  • 不知道你看到@Bean 这个方法没有哈哈,这里暴露了一个HandlerMapping,这样在Spring mvc就能够处理啦!ServletWebSocketHandlerRegistry 这个里面处理了手机WebsocketHandler处理类,以及对应的url路径,是否使用SocketJs进行特殊的逻辑处理。如果是SocketJs的话这里还需要添加定时器处理,这些不是特别的关心,所有的重点都转移到了ServletWebSocketHandlerRegistry。
public class WebSocketConfigurationSupport {

   /**
    * 这里就是暴露出来所有的握手需要的路径信息
    * {@link WebSocketHandlerMapping}
    * @return
    */
   @Bean
   public HandlerMapping webSocketHandlerMapping() {
      ServletWebSocketHandlerRegistry registry = new ServletWebSocketHandlerRegistry(defaultSockJsTaskScheduler());
      registerWebSocketHandlers(registry);
      return registry.getHandlerMapping();
   }

   protected void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
   }

   /**
    * 如果没有通过配置,则使用默认的TaskScheduler
    * The default TaskScheduler to use if none is configured via
    * {@link SockJsServiceRegistration#setTaskScheduler}, i.e.
    * <pre class="code">
    * &#064;Configuration
    * &#064;EnableWebSocket
    * public class WebSocketConfig implements WebSocketConfigurer {
    *
    *   public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
    *     registry.addHandler(myWsHandler(), "/echo").withSockJS().setTaskScheduler(myScheduler());
    *   }
    *
    *   // ...
    * }
    * </pre>
    */
   @Bean
   public ThreadPoolTaskScheduler defaultSockJsTaskScheduler() {
      ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
      scheduler.setThreadNamePrefix("SockJS-");
      scheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
      scheduler.setRemoveOnCancelPolicy(true);
      return scheduler;
   }

}

Handler Mapping 收集仓库(ServletWebSocketHandlerRegistry)

是不是有点了Spring Mvc的那一套啦,了解到这里感觉至少第一步我们是知道啦!

  • ServletWebSocketHandlerRegistry仓库的父类
/**
 * 提供配置WebSocketHandler请求映射的方法
 *
 * Provides methods for configuring {@link WebSocketHandler} request mappings.
 *
 * @author Rossen Stoyanchev
 * @since 4.0
 */
public interface WebSocketHandlerRegistry {

	/**
	 * Configure a WebSocketHandler at the specified URL paths.
	 * 在指定的URL路径上配置WebSocketHandler
	 */
	WebSocketHandlerRegistration addHandler(WebSocketHandler webSocketHandler, String... paths);

}
  • 仓库中管理的重要的东西就是它 ServletWebSocketHandlerRegistration,也就是每一个注册项,存在有多个addHandler… 仓库中就增加了不少的成员变量信息。
private final List<ServletWebSocketHandlerRegistration> registrations =
			new ArrayList<ServletWebSocketHandlerRegistration>();
  • 成功的实现向仓管中添加成员,每个成员有一些特殊的处理!对于如果是使用socketjs的可能websocketHandler包装处理可能会不一样哦
@Override
public WebSocketHandlerRegistration addHandler(WebSocketHandler webSocketHandler, String... paths) {
   // 配置每一个Handler的握手处理和区分Websocket 和SocketJS的区分处理
   ServletWebSocketHandlerRegistration registration =
         new ServletWebSocketHandlerRegistration(this.sockJsTaskScheduler);
   registration.addHandler(webSocketHandler, paths);
   this.registrations.add(registration);
   return registration;
}
  • ServletWebSocketHandlerRegistration 继承逻辑图

ServletWebSocketHandlerRegistration.png | center | 747x467

从上面的逻辑图中可以看的出来,对于仓库中的每一项,都有握手的处理器、握手拦截链、跨域控制、SocketJs区别处理,如果跨域不合法,根据就跑不到和Tomcat交互就已经被拒绝啦!由于添加了Handl二可能对应多个路径,所以一个仓库项目中获取到多个HttPRequestHandler和路径的键值对,虽然key都是一样的哈哈, LinkedMultiValueMap<HttpRequestHandler, String>,之后对于所有的参考的转换为HandlerMapping的时候要传递一些路径和Handler的Map信息的时候就是从这里遍历所有的仓库项,就可以了获取到信息啦。因为在配置Handler的时候返回值WebSocketHandlerRegistration也就是ServletWebSocketHandlerRegistration的父类,这样配置类中就可以自由的添加上面说的握手、拦截器链、跨域控制等等功能。

WebSocketHttpRequestHandler之前配置的又被包装成了这个玩意,才是真正的配合HanderMapping处理握手的逻辑,基本的拦截调用完了才开始调用真正的HandlerShake处理逻辑哦。

/**
 * 当前类主要是为了配置将WebsocketHandler将HttpRequestHandler一一对应过来进行请求的处理操作
 * 用于配置{@link WebSocketHandler}请求处理的辅助类,包括SockJS后备选项。
 * A helper class for configuring {@link WebSocketHandler} request handling
 * including SockJS fallback options.
 *
 * @author Rossen Stoyanchev
 * @since 4.0
 */
public class ServletWebSocketHandlerRegistration
		extends AbstractWebSocketHandlerRegistration<MultiValueMap<HttpRequestHandler, String>> {

	public ServletWebSocketHandlerRegistration(TaskScheduler sockJsTaskScheduler) {
		super(sockJsTaskScheduler);
	}


	/**
	 * 一个requestHandler 对应啦多个 地址的处理
	 * @return
	 */
	@Override
	protected MultiValueMap<HttpRequestHandler, String> createMappings() {
		return new LinkedMultiValueMap<HttpRequestHandler, String>();
	}

	@Override
	protected void addSockJsServiceMapping(MultiValueMap<HttpRequestHandler, String> mappings,
			SockJsService sockJsService, WebSocketHandler handler, String pathPattern) {

		SockJsHttpRequestHandler httpHandler = new SockJsHttpRequestHandler(sockJsService, handler);
		mappings.add(httpHandler, pathPattern);
	}
    
    //这里是仓库遍历所有的获取所有的路径和Handler的Map的时候,调用父类getMapping的时候被触发
	@Override
	protected void addWebSocketHandlerMapping(MultiValueMap<HttpRequestHandler, String> mappings,
			WebSocketHandler wsHandler, HandshakeHandler handshakeHandler,
			HandshakeInterceptor[] interceptors, String path) {
		// 用于处理WebSocket握手请求  {@link HttpRequestHandler}
		WebSocketHttpRequestHandler httpHandler = new WebSocketHttpRequestHandler(wsHandler, handshakeHandler);
		if (!ObjectUtils.isEmpty(interceptors)) {
			httpHandler.setHandshakeInterceptors(Arrays.asList(interceptors));
		}
		mappings.add(httpHandler, path);
	}

}
  • 直接贴代码方面了解其实现的细节哦
/**
 *
 * {@link WebSocketHandlerRegistration}的基类收集所有配置选项,但允许子类将实际的HTTP请求映射放在一起。
 * Base class for {@link WebSocketHandlerRegistration}s that gathers all the configuration
 * options but allows sub-classes to put together the actual HTTP request mappings.
 *
 * @author Rossen Stoyanchev
 * @author Sebastien Deleuze
 * @since 4.0
 */
public abstract class AbstractWebSocketHandlerRegistration<M> implements WebSocketHandlerRegistration {

	private final TaskScheduler sockJsTaskScheduler;

	/**
	 * 一个WebsocketHandler 运行映射多个地址信息,所有这里使用啦LinkedMultiValueMap
	 */
	private final MultiValueMap<WebSocketHandler, String> handlerMap =
			new LinkedMultiValueMap<WebSocketHandler, String>();

	/**
	 * 配置Websocket 握手处理程序
	 */
	private HandshakeHandler handshakeHandler;

	/**
	 * 配置握手处理的拦截器
	 */
	private final List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();

	/**
	 * 配置运行跨域的地址的信息列表
	 */
	private final List<String> allowedOrigins = new ArrayList<String>();

	/**
	 * 用于配置SockJS后备选项的助手类
	 */
	private SockJsServiceRegistration sockJsServiceRegistration;

	/**
	 * 设置SocketJs心跳处理的定制执行器
	 * @param defaultTaskScheduler
	 */
	public AbstractWebSocketHandlerRegistration(TaskScheduler defaultTaskScheduler) {
		this.sockJsTaskScheduler = defaultTaskScheduler;
	}


	@Override
	public WebSocketHandlerRegistration addHandler(WebSocketHandler handler, String... paths) {
		Assert.notNull(handler, "WebSocketHandler must not be null");
		Assert.notEmpty(paths, "Paths must not be empty");
		// 这里体现了一个handler对应啦多个地址的信息的处理
		this.handlerMap.put(handler, Arrays.asList(paths));
		return this;
	}

	@Override
	public WebSocketHandlerRegistration setHandshakeHandler(HandshakeHandler handshakeHandler) {
		this.handshakeHandler = handshakeHandler;
		return this;
	}

	protected HandshakeHandler getHandshakeHandler() {
		return this.handshakeHandler;
	}

	/**
	 * 配置过滤器
	 * @param interceptors
	 * @return
	 */
	@Override
	public WebSocketHandlerRegistration addInterceptors(HandshakeInterceptor... interceptors) {
		if (!ObjectUtils.isEmpty(interceptors)) {
			this.interceptors.addAll(Arrays.asList(interceptors));
		}
		return this;
	}

	/**
	 * 设置允许跨域的问题的处理
	 * @param allowedOrigins
	 * @return
	 */
	@Override
	public WebSocketHandlerRegistration setAllowedOrigins(String... allowedOrigins) {
		this.allowedOrigins.clear();
		if (!ObjectUtils.isEmpty(allowedOrigins)) {
			this.allowedOrigins.addAll(Arrays.asList(allowedOrigins));
		}
		return this;
	}

	@Override
	public SockJsServiceRegistration withSockJS() {
		// 如果添加了这句话,将以前一般的WebsocketHandler的注册采用SocketJS来完成处理信息
		// 当前没有具体的查看SocketJs的处理方式到底是怎么样的回事
		this.sockJsServiceRegistration = new SockJsServiceRegistration(this.sockJsTaskScheduler);
		HandshakeInterceptor[] interceptors = getInterceptors();
		if (interceptors.length > 0) {
			this.sockJsServiceRegistration.setInterceptors(interceptors);
		}
		if (this.handshakeHandler != null) {
			WebSocketTransportHandler transportHandler = new WebSocketTransportHandler(this.handshakeHandler);
			this.sockJsServiceRegistration.setTransportHandlerOverrides(transportHandler);
		}
		if (!this.allowedOrigins.isEmpty()) {
			this.sockJsServiceRegistration.setAllowedOrigins(StringUtils.toStringArray(this.allowedOrigins));
		}
		return this.sockJsServiceRegistration;
	}

	/**
	 * 获取所有的拦截器,并把OriginHandshakeInterceptor 添加到当前已经存在的处理逻辑之中
	 * @return
	 */
	protected HandshakeInterceptor[] getInterceptors() {
		List<HandshakeInterceptor> interceptors =
				new ArrayList<HandshakeInterceptor>(this.interceptors.size() + 1);
		interceptors.addAll(this.interceptors);
		interceptors.add(new OriginHandshakeInterceptor(this.allowedOrigins));
		return interceptors.toArray(new HandshakeInterceptor[interceptors.size()]);
	}

	protected final M getMappings() {
		M mappings = createMappings();
		if (this.sockJsServiceRegistration != null) {
			// 处理SocketJs的情况
			SockJsService sockJsService = this.sockJsServiceRegistration.getSockJsService();
			for (WebSocketHandler wsHandler : this.handlerMap.keySet()) {
				for (String path : this.handlerMap.get(wsHandler)) {
					String pathPattern = (path.endsWith("/") ? path + "**" : path + "/**");
					addSockJsServiceMapping(mappings, sockJsService, wsHandler, pathPattern);
				}
			}
		}
		else {
			// 处理原生的websocket的情况
			HandshakeHandler handshakeHandler = getOrCreateHandshakeHandler();
			HandshakeInterceptor[] interceptors = getInterceptors();
			for (WebSocketHandler wsHandler : this.handlerMap.keySet()) {
				for (String path : this.handlerMap.get(wsHandler)) {
					addWebSocketHandlerMapping(mappings, wsHandler, handshakeHandler, interceptors, path);
				}
			}
		}

		return mappings;
	}

	private HandshakeHandler getOrCreateHandshakeHandler() {
		return (this.handshakeHandler != null ? this.handshakeHandler : new DefaultHandshakeHandler());
	}

	/**
	 * 这个方法没有看得太懂,是获取对应的MappingHandler???
	 * @return
	 */
	protected abstract M createMappings();

	/**
	 * 这里的实现不同,具体要看子类
	 * @param mappings
	 * @param sockJsService
	 * @param handler
	 * @param pathPattern
	 */
	protected abstract void addSockJsServiceMapping(M mappings, SockJsService sockJsService,
			WebSocketHandler handler, String pathPattern);

	/**
	 * 原生的Websocket构造的WebsocketHandler的处理方法
	 * @param mappings
	 * @param wsHandler
	 * @param handshakeHandler
	 * @param interceptors
	 * @param path
	 */
	protected abstract void addWebSocketHandlerMapping(M mappings, WebSocketHandler wsHandler,
			HandshakeHandler handshakeHandler, HandshakeInterceptor[] interceptors, String path);

}

仓库项收集完毕啦~仓库(ServletWebSocketHandlerRegistry)就可以获取Mapping信息

  • 有兴趣的自己看看全部的代码
/**
	 * Return a {@link HandlerMapping} with mapped {@link HttpRequestHandler}s.
	 * 这里暴露一个HandlerMapping Bean 让web容器发现,配置中配置的HttpRequestHandler对应的URL
	 * 就可能获取到对应的处理器进行区里
	 */
	public AbstractHandlerMapping getHandlerMapping() {
		//这里找到所有的路径的信息,然后将对应的处理器放置处理,暴露给唯一的WebSocketHandlerMapping
		Map<String, Object> urlMap = new LinkedHashMap<String, Object>();
		for (ServletWebSocketHandlerRegistration registration : this.registrations) {
			MultiValueMap<HttpRequestHandler, String> mappings = registration.getMappings();
			for (HttpRequestHandler httpHandler : mappings.keySet()) {
				for (String pattern : mappings.get(httpHandler)) {
					urlMap.put(pattern, httpHandler);
				}
			}
		}
		WebSocketHandlerMapping hm = new WebSocketHandlerMapping();
		hm.setUrlMap(urlMap);
		hm.setOrder(this.order);
		if (this.urlPathHelper != null) {
			hm.setUrlPathHelper(this.urlPathHelper);
		}
		return hm;
	}

暴露的Handler and Mapping的实现类都出来了

  • Handler : SockJsHttpRequestHandler 或者WebSocketHttpRequestHandler
  • Mapping: WebSocketHandlerMapping
WebSocketHandlerMapping

WebSocketHandlerMapping.png | center | 747x606

WebSocketHandlerMapping2.png | center | 747x845

一张复杂,一张简单!但是都是一个意思,整理的Map中的数据就是为了暴露给Spring Mvc !找到Handler之后就可以处理啦。

WebSocketHttpRequestHandler 只讲这个一个了!处理逻辑类似,那个伪 websocket的Handler就不说他了

看图就懂了,这个是最简单的Handler哈哈,其他的还有HandlerAdapter呢,蛮复杂的!

WebSocketHttpRequestHandler.png | center | 747x405

  • 注意看握手的处理逻辑!!!
/**
 * 用于处理WebSocket握手请求的{@link HttpRequestHandler}。这是在特定URL上配置服务器WebSocket时使用的主要类。
 * 这是围绕{@link WebSocketHandler}和{@link HandshakeHandler}的非常简单的封装,
 * 还将{@link HttpServletRequest}和{@link HttpServletResponse}调整为{@ ServerHttpRequest}和{@link ServerHttpResponse}
 * 分别{@link HttpRequestHandler}用于处理WebSocket握手请求
 *
 * <p>This is the main class to use when configuring a server WebSocket at a specific URL.
 * It is a very thin wrapper around a {@link WebSocketHandler} and a {@link HandshakeHandler},
 * also adapting the {@link HttpServletRequest} and {@link HttpServletResponse} to
 * {@link ServerHttpRequest} and {@link ServerHttpResponse}, respectively.
 *
 * @author Rossen Stoyanchev
 * @since 4.0
 */
public class WebSocketHttpRequestHandler implements HttpRequestHandler, Lifecycle, ServletContextAware {

   private final Log logger = LogFactory.getLog(WebSocketHttpRequestHandler.class);

   private final WebSocketHandler wsHandler;

   private final HandshakeHandler handshakeHandler;

   private final List<HandshakeInterceptor> interceptors = new ArrayList<HandshakeInterceptor>();

   private volatile boolean running = false;


   public WebSocketHttpRequestHandler(WebSocketHandler wsHandler) {
      this(wsHandler, new DefaultHandshakeHandler());
   }

   public WebSocketHttpRequestHandler(WebSocketHandler wsHandler, HandshakeHandler handshakeHandler) {
      Assert.notNull(wsHandler, "wsHandler must not be null");
      Assert.notNull(handshakeHandler, "handshakeHandler must not be null");
      this.wsHandler = new ExceptionWebSocketHandlerDecorator(new LoggingWebSocketHandlerDecorator(wsHandler));
      this.handshakeHandler = handshakeHandler;
   }


   /**
    * Return the WebSocketHandler.
    */
   public WebSocketHandler getWebSocketHandler() {
      return this.wsHandler;
   }

   /**
    * Return the HandshakeHandler.
    */
   public HandshakeHandler getHandshakeHandler() {
      return this.handshakeHandler;
   }

   /**
    * Configure one or more WebSocket handshake request interceptors.
    */
   public void setHandshakeInterceptors(List<HandshakeInterceptor> interceptors) {
      this.interceptors.clear();
      if (interceptors != null) {
         this.interceptors.addAll(interceptors);
      }
   }

   /**
    * Return the configured WebSocket handshake request interceptors.
    */
   public List<HandshakeInterceptor> getHandshakeInterceptors() {
      return this.interceptors;
   }

   @Override
   public void setServletContext(ServletContext servletContext) {
      if (this.handshakeHandler instanceof ServletContextAware) {
         ((ServletContextAware) this.handshakeHandler).setServletContext(servletContext);
      }
   }


   @Override
   public void start() {
      if (!isRunning()) {
         this.running = true;
         if (this.handshakeHandler instanceof Lifecycle) {
            ((Lifecycle) this.handshakeHandler).start();
         }
      }
   }

   @Override
   public void stop() {
      if (isRunning()) {
         this.running = false;
         if (this.handshakeHandler instanceof Lifecycle) {
            ((Lifecycle) this.handshakeHandler).stop();
         }
      }
   }

   @Override
   public boolean isRunning() {
      return this.running;
   }


   @Override
   public void handleRequest(HttpServletRequest servletRequest, HttpServletResponse servletResponse)
         throws ServletException, IOException {
      // 基于HttpServletRequest的封装,包装了一些使用的方法,比如获取到当前的请求的地址的信息
      ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
      ServerHttpResponse response = new ServletServerHttpResponse(servletResponse);

      // 握手拦截器链条
      HandshakeInterceptorChain chain = new HandshakeInterceptorChain(this.interceptors, this.wsHandler);
      HandshakeFailureException failure = null;

      try {
         if (logger.isDebugEnabled()) {
            logger.debug(servletRequest.getMethod() + " " + servletRequest.getRequestURI());
         }
         Map<String, Object> attributes = new HashMap<String, Object>();
         if (!chain.applyBeforeHandshake(request, response, attributes)) {
            return;
         //真实的处理逻辑握手哦
         this.handshakeHandler.doHandshake(request, response, this.wsHandler, attributes);
         chain.applyAfterHandshake(request, response, null);
         response.close();
      }
      catch (HandshakeFailureException ex) {
         failure = ex;
      }
      catch (Throwable ex) {
         failure = new HandshakeFailureException("Uncaught failure for request " + request.getURI(), ex);
      }
      finally {
         if (failure != null) {
            chain.applyAfterHandshake(request, response, failure);
            throw failure;
         }
      }
   }

}

第二点:处理Handler与Tomcat交互,不止这个哦

默认的HandlerShake

DefaultHandshakeHandler.png | center | 747x349

DefaultHandshakeHandler1.png | center | 747x188

处理握手的逻辑,根据当前环境采用具体的web容器策略

/**
	 * 当前是在哪个容器中的?
	 */
	private static final boolean jettyWsPresent = ClassUtils.isPresent(
			"org.eclipse.jetty.websocket.server.WebSocketServerFactory", AbstractHandshakeHandler.class.getClassLoader());

	private static final boolean tomcatWsPresent = ClassUtils.isPresent(
			"org.apache.tomcat.websocket.server.WsHttpUpgradeHandler", AbstractHandshakeHandler.class.getClassLoader());

	private static final boolean undertowWsPresent = ClassUtils.isPresent(
			"io.undertow.websockets.jsr.ServerWebSocketContainer", AbstractHandshakeHandler.class.getClassLoader());

	private static final boolean glassfishWsPresent = ClassUtils.isPresent(
			"org.glassfish.tyrus.servlet.TyrusHttpUpgradeHandler", AbstractHandshakeHandler.class.getClassLoader());

	private static final boolean weblogicWsPresent = ClassUtils.isPresent(
			"weblogic.websocket.tyrus.TyrusServletWriter", AbstractHandshakeHandler.class.getClassLoader());

	private static final boolean websphereWsPresent = ClassUtils.isPresent(
			"com.ibm.websphere.wsoc.WsWsocServerContainer", AbstractHandshakeHandler.class.getClassLoader());
  • 根据策略进行处理采用不同的Websocket实现方式采用不同的策略
	/**
	 * 请求升级策略 是使用的是哪个服务器实现的Websocket ?
	 */
	private final RequestUpgradeStrategy requestUpgradeStrategy;

RequestUpgradeStrategy.png | center | 747x257

  • AbstractHandshakeHandler 类的实现握手逻辑,看到实现的握手逻辑中有各种标准类型转换的Adapter接口,然后交给特定的容器策略实现握手啦。

	@Override
	public final boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response,
			WebSocketHandler wsHandler, Map<String, Object> attributes) throws HandshakeFailureException {
		// 方便获取特定的字段的值的情况
		WebSocketHttpHeaders headers = new WebSocketHttpHeaders(request.getHeaders());
		if (logger.isTraceEnabled()) {
			logger.trace("Processing request " + request.getURI() + " with headers=" + headers);
		}
		try {
			if (HttpMethod.GET != request.getMethod()) {
				response.setStatusCode(HttpStatus.METHOD_NOT_ALLOWED);
				response.getHeaders().setAllow(Collections.singleton(HttpMethod.GET));
				if (logger.isErrorEnabled()) {
					logger.error("Handshake failed due to unexpected HTTP method: " + request.getMethod());
				}
				return false;
			}
			// 协议错误~
			if (!"WebSocket".equalsIgnoreCase(headers.getUpgrade())) {
				handleInvalidUpgradeHeader(request, response);
				return false;
			}
			if (!headers.getConnection().contains("Upgrade") && !headers.getConnection().contains("upgrade")) {
				handleInvalidConnectHeader(request, response);
				return false;
			}
			// 检测当前的处理协议是否支持~
			if (!isWebSocketVersionSupported(headers)) {
				handleWebSocketVersionNotSupported(request, response);
				return false;
			}
			// 查看当前的网域是否正常~
			if (!isValidOrigin(request)) {
				response.setStatusCode(HttpStatus.FORBIDDEN);
				return false;
			}
			// 检测wsKey 是否存在
			String wsKey = headers.getSecWebSocketKey();
			if (wsKey == null) {
				if (logger.isErrorEnabled()) {
					logger.error("Missing \"Sec-WebSocket-Key\" header");
				}
				response.setStatusCode(HttpStatus.BAD_REQUEST);
				return false;
			}
		}
		catch (IOException ex) {
			throw new HandshakeFailureException(
					"Response update failed during upgrade to WebSocket: " + request.getURI(), ex);
		}

		//选择子协议
		String subProtocol = selectProtocol(headers.getSecWebSocketProtocol(), wsHandler);
		//扩展的转换
		List<WebSocketExtension> requested = headers.getSecWebSocketExtensions();
		List<WebSocketExtension> supported = this.requestUpgradeStrategy.getSupportedExtensions(request);
		// 过滤扩展
		List<WebSocketExtension> extensions = filterRequestedExtensions(request, requested, supported);
		//当前认证的用户的信息
		Principal user = determineUser(request, wsHandler, attributes);

		if (logger.isTraceEnabled()) {
			logger.trace("Upgrading to WebSocket, subProtocol=" + subProtocol + ", extensions=" + extensions);
		}
		this.requestUpgradeStrategy.upgrade(request, response, subProtocol, extensions, user, wsHandler, attributes);
		return true;
	}
  • AbstractStandardUpgradeStrategy 类中upgrade 方法
@Override
	public void upgrade(ServerHttpRequest request, ServerHttpResponse response,
			String selectedProtocol, List<WebSocketExtension> selectedExtensions, Principal user,
			WebSocketHandler wsHandler, Map<String, Object> attrs) throws HandshakeFailureException {

		HttpHeaders headers = request.getHeaders();
		InetSocketAddress localAddr = null;
		try {
			localAddr = request.getLocalAddress();
		}
		catch (Exception ex) {
			// Ignore
		}
		InetSocketAddress remoteAddr = null;
		try {
			remoteAddr = request.getRemoteAddress();
		}
		catch (Exception ex) {
			// Ignore
		}
		// spring将各种信息混合制造属于自己的Session
		StandardWebSocketSession session = new StandardWebSocketSession(headers, attrs, localAddr, remoteAddr, user);

		//原生EndPoint适配器,适配当WebSocket建立连接,发送消息,接收消息通过原生的API接收到后转发给具体的WebsocketHandler
		StandardWebSocketHandlerAdapter endpoint = new StandardWebSocketHandlerAdapter(wsHandler, session);

		List<Extension> extensions = new ArrayList<Extension>();
		for (WebSocketExtension extension : selectedExtensions) {
			extensions.add(new WebSocketToStandardExtensionAdapter(extension));
		}

		upgradeInternal(request, response, selectedProtocol, extensions, endpoint);
	}


TomcatRequestUpgradeStrategy tomact实现websocketd的逻辑

@Override
	public void upgradeInternal(ServerHttpRequest request, ServerHttpResponse response,
			String selectedProtocol, List<Extension> selectedExtensions, Endpoint endpoint)
			throws HandshakeFailureException {

		HttpServletRequest servletRequest = getHttpServletRequest(request);
		HttpServletResponse servletResponse = getHttpServletResponse(response);

		StringBuffer requestUrl = servletRequest.getRequestURL();
		String path = servletRequest.getRequestURI();  // shouldn't matter 应该没关系
		Map<String, String> pathParams = Collections.<String, String> emptyMap();

		//这里是真实通过Websocket API进行调用拦截处理的信息
		ServerEndpointRegistration endpointConfig = new ServerEndpointRegistration(path, endpoint);
		//设置子协议
		endpointConfig.setSubprotocols(Collections.singletonList(selectedProtocol));
		//设置扩展
		endpointConfig.setExtensions(selectedExtensions);

		try {
			getContainer(servletRequest).doUpgrade(servletRequest, servletResponse, endpointConfig, pathParams);
		}
		catch (ServletException ex) {
			throw new HandshakeFailureException(
					"Servlet request failed to upgrade to WebSocket: " + requestUrl, ex);
		}
		catch (IOException ex) {
			throw new HandshakeFailureException(
					"Response update failed during upgrade to WebSocket: " + requestUrl, ex);
		}
	}

总结

写了很久,想表述清楚,又想粘贴代码!不过让我更加了了解清楚了spring实现的整个逻辑,思路!更加的清楚了他是怎么让代码变得这么分工很清晰,不会让各种杂糅在一起的。2018-9-9 凌晨 2:44

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值