gateway是netty开发的天生支持websocket。zuul不支持,但是好像可以配置
注意: 如果网关中有登录验证,可能需要对websocket做放行
**

  1. gateway无任何配置的情况下 websocket (一个websocket服务下,多个服务没有测试)一切正常
    1. 设置跨域可能会出现websocket连接不上 ```yaml globalcors: corsConfigurations:
      1. '[/**]':
      2. allowedHeaders: "*"
      3. allowedOrigins: "*"
      4. allowedMethods:
      5. - GET
      6. POST
      7. DELETE
      8. PUT
      9. OPTION
  1. 1. allowedMethods 改成 allowedMethods: "*" 就好了
  2. 1. gateway配置文件
  3. ```yaml
  4. server:
  5. port: 9005
  6. spring:
  7. application:
  8. name: test-gateway
  9. cloud:
  10. nacos:
  11. discovery:
  12. server-addr: 127.0.0.1:8848 # 注册中心
  13. group: websockt
  14. #路由配置,user-service是我的服务名
  15. gateway:
  16. routes:
  17. # ws表示websocket的转发
  18. - id: test-websocket
  19. uri: ws://127.0.0.1:9006
  20. predicates:
  21. - Path=/ws/**
  22. filters:
  23. - StripPrefix=1
  24. # ws表示websocket的转发
  25. - id: nocas-websocket
  26. uri: ws://test-websocket
  27. predicates:
  28. - Path=/ser/ws/**
  29. filters:
  30. - StripPrefix=2
  31. - id: api-websocket
  32. uri: lb://test-websocket
  33. predicates:
  34. - Path=/api/**
  35. filters:
  36. - StripPrefix=1
  37. # ws表示websocket的转发 -- 可以走 无均衡
  38. - id: test-websocket2
  39. uri: ws://127.0.0.1:9007
  40. predicates:
  41. - Path=/ws2/**
  42. filters:
  43. - StripPrefix=1
  44. # ws表示websocket的转发 - 这是错误的写法
  45. - id: nocas-websocket2
  46. uri: ws://test-websocket2
  47. predicates:
  48. - Path=/ser2/ws/**
  49. filters:
  50. - StripPrefix=2
  51. # ws表示websocket的转发 -- 可以走 有均衡 (还没测试均衡怎么玩)
  52. - id: nocas2-websocket2
  53. uri: lb:ws://test-websocket2
  54. predicates:
  55. - Path=/serlb/ws/**
  56. filters:
  57. - StripPrefix=2
  58. # websocket项目中的正常接口地址
  59. - id: api-websocket2
  60. uri: lb://test-websocket2
  61. predicates:
  62. - Path=/api2/**
  63. filters:
  64. - StripPrefix=1
  65. discovery:
  66. locator:
  67. enabled: true
  68. lower-case-service-id: true

b. websocket spring单机版的配置就行了

  1. <!-- 我的websocket单机版直接使用的依赖 -->
  2. <dependency>
  3. <groupId>com.databstech</groupId>
  4. <artifactId>util-websocket</artifactId>
  5. <version>1.0.0</version>
  6. </dependency>
  7. <!-- 如果是微服务记得加这个o 注册中心 要不然gateway 配置lb:服务名 是访问不到的 -->
  8. <dependency>
  9. <groupId>com.alibaba.cloud</groupId>
  10. <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
  11. <!-- 不放进jar包里 用户自己单独加入 -->
  12. <!--<scope>provided</scope>-->
  13. </dependency>
  1. //util-websocket 配置过程
  2. /**------------------------最重要的注册类-------------------------------*/
  3. package com.databstech.utils.websocket.config;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. import org.springframework.web.socket.server.standard.ServerEndpointExporter;
  7. /**
  8. *
  9. * 往 spring 容器中注入ServerEndpointExporter实例
  10. *
  11. * @ClassName : WebSocketConfig //类名
  12. * @Description : socket配置 //描述
  13. * @Author : tn //作者
  14. * @Date: 2020-07-08 12:33 //时间
  15. */
  16. @Configuration
  17. public class WebSocketConfig {
  18. @Bean
  19. public ServerEndpointExporter serverEndpointExporter(){
  20. return new ServerEndpointExporter();
  21. }
  22. }
  23. /**------------------------开始连接,断开连接,发送消息等-------------------------------*/
  24. package com.databstech.utils.websocket.service;
  25. import org.springframework.stereotype.Component;
  26. import javax.websocket.*;
  27. import javax.websocket.server.PathParam;
  28. import javax.websocket.server.ServerEndpoint;
  29. import java.io.IOException;
  30. import java.util.ArrayList;
  31. import java.util.HashMap;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.concurrent.atomic.AtomicInteger;
  35. /**
  36. *
  37. * 包含接收消息,推送消息等接口
  38. *
  39. * @ClassName : WebSocketServer
  40. * @Description : webSocker服务端
  41. * @Author : tn
  42. * @Date: 2020-07-08 12:36
  43. */
  44. @Component
  45. @ServerEndpoint(value = "/socket/{name}")
  46. public class WebSocketServer {
  47. //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
  48. private static AtomicInteger online = new AtomicInteger();
  49. //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
  50. //不允许多端登陆
  51. // private static Map<String, Session> sessionPools = new HashMap<>();
  52. //允许多端登陆
  53. private static Map<String, List<Session>> sessionPoolsS = new HashMap<>();
  54. /**
  55. * 发送消息方法
  56. * @param session 客户端与socket建立的会话
  57. * @param message 消息
  58. * @throws IOException 抛出异常
  59. */
  60. public void sendMessage(Session session, String message) throws IOException {
  61. if(session != null){
  62. session.getBasicRemote().sendText(message);
  63. }
  64. }
  65. /**
  66. * 连接建立成功调用
  67. * @param session 客户端与socket建立的会话
  68. * @param userName 客户端的userName
  69. */
  70. @OnOpen
  71. public void onOpen(Session session, @PathParam(value = "name") String userName){
  72. List<Session> sessionsArray = new ArrayList<>();
  73. //获取session
  74. List<Session> sessions = sessionPoolsS.get(userName);
  75. //存在session
  76. if(sessions==null){
  77. sessionsArray.add(session);
  78. }else {
  79. sessionsArray.addAll(sessions);
  80. sessionsArray.add(session);
  81. }
  82. sessionPoolsS.put(userName, sessionsArray);
  83. addOnlineCount();
  84. System.out.println(userName + "加入webSocket!当前人数为" + online);
  85. try {
  86. sendMessage(session, "欢迎" + userName + "加入连接!");
  87. } catch (IOException e) {
  88. e.printStackTrace();
  89. }
  90. }
  91. /**
  92. * 关闭连接时调用
  93. * @param userName 关闭连接的客户端的姓名
  94. */
  95. @OnClose
  96. public void onClose(@PathParam(value = "name") String userName, Session session){
  97. //获取session
  98. List<Session> sessions = sessionPoolsS.get(userName);
  99. if(session==null){
  100. sessionPoolsS.remove(userName);
  101. }else {
  102. sessions.remove(session);
  103. sessionPoolsS.put(userName, sessions);
  104. }
  105. subOnlineCount();
  106. System.out.println(userName + "断开webSocket连接!当前人数为" + online);
  107. }
  108. /**
  109. * 收到客户端消息时触发(群发)
  110. * @param message
  111. */
  112. @OnMessage
  113. public void onMessage(String message){
  114. for (List<Session> session: sessionPoolsS.values()) {
  115. try {
  116. session.forEach(sessionf -> {
  117. try {
  118. sendMessage(sessionf, message);
  119. } catch (IOException e) {
  120. e.printStackTrace();
  121. }
  122. });
  123. } catch(Exception e){
  124. e.printStackTrace();
  125. continue;
  126. }
  127. }
  128. }
  129. /**
  130. * 发生错误时候
  131. * @param session session
  132. * @param throwable throwable
  133. */
  134. @OnError
  135. public void onError(Session session, Throwable throwable){
  136. System.out.println("发生错误");
  137. throwable.printStackTrace();
  138. }
  139. /**
  140. * 给指定用户发送消息
  141. * @param userName 用户名
  142. * @param message 消息
  143. */
  144. public void sendInfo(String userName, String message){
  145. List<Session> session = sessionPoolsS.get(userName);
  146. if(session!=null){
  147. try {
  148. session.forEach(sessionf -> {
  149. try {
  150. sendMessage(sessionf, message);
  151. } catch (IOException e) {
  152. e.printStackTrace();
  153. }
  154. });
  155. }catch (Exception e){
  156. e.printStackTrace();
  157. }
  158. }
  159. }
  160. public static void addOnlineCount(){
  161. online.incrementAndGet();
  162. }
  163. public static void subOnlineCount() {
  164. online.decrementAndGet();
  165. }
  166. }
  167. /**------------------------内部默认的接口-----------------------------------------------*/
  168. package com.databstech.utils.websocket.controller;
  169. import com.databstech.utils.websocket.service.WebSocketServer;
  170. import io.swagger.annotations.ApiImplicitParam;
  171. import io.swagger.annotations.ApiImplicitParams;
  172. import io.swagger.annotations.ApiOperation;
  173. import org.springframework.web.bind.annotation.RequestMapping;
  174. import org.springframework.web.bind.annotation.RequestMethod;
  175. import org.springframework.web.bind.annotation.RequestParam;
  176. /**
  177. * @ClassName : SocketController 接口
  178. * @Description : socket //描述
  179. * @Author : tn //作者
  180. * @Date: 2020-07-08 12:40 //时间
  181. */
  182. public interface SocketController {
  183. /**
  184. * 给指定用户推送消息
  185. * @param userName 用户名
  186. * @param message 消息
  187. */
  188. @RequestMapping(value = "/only", method = RequestMethod.GET)
  189. @ApiOperation("给指定用户推送消息")
  190. @ApiImplicitParams({
  191. @ApiImplicitParam(name="userName", value="用户名", dataType="String", required=true),
  192. @ApiImplicitParam(name="message", value="消息", dataType="String", required=true)
  193. })
  194. default void onlyUserSocket(@RequestParam String userName, @RequestParam String message, WebSocketServer webSocketServer){
  195. webSocketServer.sendInfo(userName, message);
  196. }
  197. /**
  198. * 给所有用户推送消息
  199. * @param message 消息
  200. */
  201. @ApiOperation("给所有用户推送消息")
  202. @ApiImplicitParams({
  203. @ApiImplicitParam(name="message", value="消息", dataType="String", required=true)
  204. })
  205. @RequestMapping(value = "/all", method = RequestMethod.GET)
  206. default void allUserSocket(@RequestParam String message, WebSocketServer webSocketServer){
  207. webSocketServer.onMessage(message);
  208. }
  209. }
  210. // 依赖
  211. <!--引入websocket依赖 -->
  212. <dependency>
  213. <groupId>org.springframework.boot</groupId>
  214. <artifactId>spring-boot-starter-websocket</artifactId>
  215. </dependency>
  216. <dependency>
  217. <groupId>io.swagger</groupId>
  218. <artifactId>swagger-annotations</artifactId>
  219. <version>1.5.22</version>
  220. <scope>provided</scope>
  221. </dependency>
  1. 本来运行的也好好的,但是用了 gateway 聚合了swagger之后从网关访问就是一个接着一个的错误!

    1. 错误如下

      1. java.lang.ClassCastException: org.apache.catalina.connector.ResponseFacade cannot be cast to reactor.netty.http.server.HttpServerResponse
      2. at org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy.getNativeResponse(ReactorNettyRequestUpgradeStrategy.java:124) ~[spring-webflux-5.2.4.RELEASE.jar:5.2.4.RELEASE]
      3. Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
      4. Error has been observed at the following site(s):
      5. |_ checkpoint org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
      6. |_ checkpoint org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
      7. |_ checkpoint HTTP GET "/ws/socket/tn" [ExceptionHandlingWebHandler]
    2. 层层测试

      1. gateway 原生配置 - 正常转发了
      2. gateway 结合 nacos 配置动态路由 - 也正常转发了
      3. gateway 配置全局异常 - 正常转发
      4. gateway 配置了swagger后 - 也正常转发
      5. 配置了简单的 GatewayFilter(全局拦截)- 也正常 ```java package com.tn.gateway3.filter;

import org.apache.commons.lang.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.stereotype.Component;

/**

  • @author fsl
  • @description: SwaggerHeaderFilter
  • @date 2019-06-0310:47 */ @Component public class SwaggerHeaderFilter extends AbstractGatewayFilterFactory { private static final String HEADER_NAME = “X-Forwarded-Prefix”;

    private static final String URI = “/v2/api-docs”;

    @Override public GatewayFilter apply(Object config) {

    1. return (exchange, chain) -> {
    2. ServerHttpRequest request = exchange.getRequest();
    3. String path = request.getURI().getPath();
    4. if (!StringUtils.endsWithIgnoreCase(path,URI )) {
    5. return chain.filter(exchange);
    6. }
    7. return chain.filter(exchange);
    8. };

    } }

    1. 1. 配置了复杂的 GatewayFilter(全局拦截)- 也正常
    2. 2. 我将所有配置集成起来打成jar后,第三方使用websocket使用出现报错
    3. 3. 我将自己配置 gateway-doc jar 进行拆解
    4. 1. 发现是我配置的 gateway-doc 里的登录拦截器用的 JWT 发生的问题
    5. 2. **fuck 原来是因为gatewaynetty写的跟web犯冲,我JWT项目中引入了**
    6. ```xml
    7. <dependency>
    8. <groupId>org.apache.tomcat.embed</groupId>
    9. <artifactId>tomcat-embed-core</artifactId>
    10. <version>8.5.43</version>
    11. </dependency>

    因为我jwt中有个 gettoken的方法使用到了 HttpServletRequest ,现在我他这个方法改了改成了 ```java /**

    • 获取token
    • @param request 请求头
    • @return 返回token */ public static String getToken(ServerHttpRequest request) { final String tokenName = “token”; //从请求头获取token return Optional.ofNullable(request.getHeaders().get(tokenName))

      1. .map(t -> t.get(0))
      2. .orElse(null);

      }

依赖

org.springframework spring-web 5.2.8.RELEASE
  1. nacos websockt相关的 gateway.yaml配置
  2. ```yaml
  3. spring:
  4. cloud:
  5. gateway:
  6. routes:
  7. # ws表示websocket的转发
  8. - id: test-websocket
  9. uri: ws://127.0.0.1:9006
  10. predicates:
  11. - Path=/ws/**
  12. - DocName=noSwagger
  13. filters:
  14. - StripPrefix=1
  15. # ws表示websocket的转发
  16. - id: nocas-websocket
  17. uri: ws://test-websocket
  18. predicates:
  19. - Path=/ser/ws/**
  20. - DocName=noSwagger
  21. filters:
  22. - StripPrefix=2
  23. - id: api-websocket
  24. uri: lb://test-websocket
  25. predicates:
  26. - Path=/api/**
  27. - DocName=websocket
  28. filters:
  29. - StripPrefix=1
  30. # ws表示websocket的转发
  31. - id: test-websocket2
  32. uri: ws://127.0.0.1:9007
  33. predicates:
  34. - Path=/ws2/**
  35. - DocName=noSwagger
  36. filters:
  37. - StripPrefix=1
  38. # ws表示websocket的转发 - 这是错误的
  39. - id: nocas-websocket2
  40. uri: ws://test-websocket2
  41. predicates:
  42. - Path=/ser2/ws/**
  43. - DocName=noSwagger
  44. filters:
  45. - StripPrefix=2
  46. - id: nocas2-websocket2
  47. uri: lb:ws://test-websocket2
  48. predicates:
  49. - Path=/serlb/ws/**
  50. - DocName=noSwagger
  51. filters:
  52. - StripPrefix=2
  53. - id: api-websocket2
  54. uri: lb://test-websocket2
  55. predicates:
  56. - Path=/api2/**
  57. - DocName=noSwagger
  58. filters:
  59. - StripPrefix=1
  60. # 测试动态路由
  61. - id: dy-routes
  62. uri: lb://dy-routes
  63. predicates:
  64. - Path=/dy/**
  65. - DocName=noSwagger
  66. filters:
  67. - StripPrefix=1
  68. default-filters:
  69. # - StripPrefix=2
  70. # - RewritePath=/api/(?<segment>.*), /$\{segment}
  71. # - name: SwaggerHeaderFilter
  72. - name: AuthFilter
  73. args:
  74. excludePatterns: #不做权限校验的路径
  75. - /serlb/ws/socket/**