gateway是netty开发的天生支持websocket。zuul不支持,但是好像可以配置
注意: 如果网关中有登录验证,可能需要对websocket做放行
**
- gateway无任何配置的情况下 websocket (一个websocket服务下,多个服务没有测试)一切正常
- 设置跨域可能会出现websocket连接不上
```yaml
globalcors:
corsConfigurations:
'[/**]':
allowedHeaders: "*"
allowedOrigins: "*"
allowedMethods:
- GET
POST
DELETE
PUT
OPTION
- 设置跨域可能会出现websocket连接不上
```yaml
globalcors:
corsConfigurations:
1. 把 allowedMethods 改成 allowedMethods: "*" 就好了
1. gateway配置文件
```yaml
server:
port: 9005
spring:
application:
name: test-gateway
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # 注册中心
group: websockt
#路由配置,user-service是我的服务名
gateway:
routes:
# ws表示websocket的转发
- id: test-websocket
uri: ws://127.0.0.1:9006
predicates:
- Path=/ws/**
filters:
- StripPrefix=1
# ws表示websocket的转发
- id: nocas-websocket
uri: ws://test-websocket
predicates:
- Path=/ser/ws/**
filters:
- StripPrefix=2
- id: api-websocket
uri: lb://test-websocket
predicates:
- Path=/api/**
filters:
- StripPrefix=1
# ws表示websocket的转发 -- 可以走 无均衡
- id: test-websocket2
uri: ws://127.0.0.1:9007
predicates:
- Path=/ws2/**
filters:
- StripPrefix=1
# ws表示websocket的转发 - 这是错误的写法
- id: nocas-websocket2
uri: ws://test-websocket2
predicates:
- Path=/ser2/ws/**
filters:
- StripPrefix=2
# ws表示websocket的转发 -- 可以走 有均衡 (还没测试均衡怎么玩)
- id: nocas2-websocket2
uri: lb:ws://test-websocket2
predicates:
- Path=/serlb/ws/**
filters:
- StripPrefix=2
# websocket项目中的正常接口地址
- id: api-websocket2
uri: lb://test-websocket2
predicates:
- Path=/api2/**
filters:
- StripPrefix=1
discovery:
locator:
enabled: true
lower-case-service-id: true
b. websocket spring单机版的配置就行了
<!-- 我的websocket单机版直接使用的依赖 -->
<dependency>
<groupId>com.databstech</groupId>
<artifactId>util-websocket</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 如果是微服务记得加这个o 注册中心 要不然gateway 配置lb:服务名 是访问不到的 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<!-- 不放进jar包里 用户自己单独加入 -->
<!--<scope>provided</scope>-->
</dependency>
//util-websocket 配置过程
/**------------------------最重要的注册类-------------------------------*/
package com.databstech.utils.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
*
* 往 spring 容器中注入ServerEndpointExporter实例
*
* @ClassName : WebSocketConfig //类名
* @Description : socket配置 //描述
* @Author : tn //作者
* @Date: 2020-07-08 12:33 //时间
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
/**------------------------开始连接,断开连接,发送消息等-------------------------------*/
package com.databstech.utils.websocket.service;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* 包含接收消息,推送消息等接口
*
* @ClassName : WebSocketServer
* @Description : webSocker服务端
* @Author : tn
* @Date: 2020-07-08 12:36
*/
@Component
@ServerEndpoint(value = "/socket/{name}")
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger online = new AtomicInteger();
//concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
//不允许多端登陆
// private static Map<String, Session> sessionPools = new HashMap<>();
//允许多端登陆
private static Map<String, List<Session>> sessionPoolsS = new HashMap<>();
/**
* 发送消息方法
* @param session 客户端与socket建立的会话
* @param message 消息
* @throws IOException 抛出异常
*/
public void sendMessage(Session session, String message) throws IOException {
if(session != null){
session.getBasicRemote().sendText(message);
}
}
/**
* 连接建立成功调用
* @param session 客户端与socket建立的会话
* @param userName 客户端的userName
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "name") String userName){
List<Session> sessionsArray = new ArrayList<>();
//获取session
List<Session> sessions = sessionPoolsS.get(userName);
//存在session
if(sessions==null){
sessionsArray.add(session);
}else {
sessionsArray.addAll(sessions);
sessionsArray.add(session);
}
sessionPoolsS.put(userName, sessionsArray);
addOnlineCount();
System.out.println(userName + "加入webSocket!当前人数为" + online);
try {
sendMessage(session, "欢迎" + userName + "加入连接!");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 关闭连接时调用
* @param userName 关闭连接的客户端的姓名
*/
@OnClose
public void onClose(@PathParam(value = "name") String userName, Session session){
//获取session
List<Session> sessions = sessionPoolsS.get(userName);
if(session==null){
sessionPoolsS.remove(userName);
}else {
sessions.remove(session);
sessionPoolsS.put(userName, sessions);
}
subOnlineCount();
System.out.println(userName + "断开webSocket连接!当前人数为" + online);
}
/**
* 收到客户端消息时触发(群发)
* @param message
*/
@OnMessage
public void onMessage(String message){
for (List<Session> session: sessionPoolsS.values()) {
try {
session.forEach(sessionf -> {
try {
sendMessage(sessionf, message);
} catch (IOException e) {
e.printStackTrace();
}
});
} catch(Exception e){
e.printStackTrace();
continue;
}
}
}
/**
* 发生错误时候
* @param session session
* @param throwable throwable
*/
@OnError
public void onError(Session session, Throwable throwable){
System.out.println("发生错误");
throwable.printStackTrace();
}
/**
* 给指定用户发送消息
* @param userName 用户名
* @param message 消息
*/
public void sendInfo(String userName, String message){
List<Session> session = sessionPoolsS.get(userName);
if(session!=null){
try {
session.forEach(sessionf -> {
try {
sendMessage(sessionf, message);
} catch (IOException e) {
e.printStackTrace();
}
});
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void addOnlineCount(){
online.incrementAndGet();
}
public static void subOnlineCount() {
online.decrementAndGet();
}
}
/**------------------------内部默认的接口-----------------------------------------------*/
package com.databstech.utils.websocket.controller;
import com.databstech.utils.websocket.service.WebSocketServer;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* @ClassName : SocketController 接口
* @Description : socket //描述
* @Author : tn //作者
* @Date: 2020-07-08 12:40 //时间
*/
public interface SocketController {
/**
* 给指定用户推送消息
* @param userName 用户名
* @param message 消息
*/
@RequestMapping(value = "/only", method = RequestMethod.GET)
@ApiOperation("给指定用户推送消息")
@ApiImplicitParams({
@ApiImplicitParam(name="userName", value="用户名", dataType="String", required=true),
@ApiImplicitParam(name="message", value="消息", dataType="String", required=true)
})
default void onlyUserSocket(@RequestParam String userName, @RequestParam String message, WebSocketServer webSocketServer){
webSocketServer.sendInfo(userName, message);
}
/**
* 给所有用户推送消息
* @param message 消息
*/
@ApiOperation("给所有用户推送消息")
@ApiImplicitParams({
@ApiImplicitParam(name="message", value="消息", dataType="String", required=true)
})
@RequestMapping(value = "/all", method = RequestMethod.GET)
default void allUserSocket(@RequestParam String message, WebSocketServer webSocketServer){
webSocketServer.onMessage(message);
}
}
// 依赖
<!--引入websocket依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>1.5.22</version>
<scope>provided</scope>
</dependency>
本来运行的也好好的,但是用了 gateway 聚合了swagger之后从网关访问就是一个接着一个的错误!
错误如下
java.lang.ClassCastException: org.apache.catalina.connector.ResponseFacade cannot be cast to reactor.netty.http.server.HttpServerResponse
at org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy.getNativeResponse(ReactorNettyRequestUpgradeStrategy.java:124) ~[spring-webflux-5.2.4.RELEASE.jar:5.2.4.RELEASE]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ org.springframework.boot.actuate.metrics.web.reactive.server.MetricsWebFilter [DefaultWebFilterChain]
|_ checkpoint ⇢ HTTP GET "/ws/socket/tn" [ExceptionHandlingWebHandler]
层层测试
- gateway 原生配置 - 正常转发了
- gateway 结合 nacos 配置动态路由 - 也正常转发了
- gateway 配置全局异常 - 正常转发
- gateway 配置了swagger后 - 也正常转发
- 配置了简单的 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) {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
if (!StringUtils.endsWithIgnoreCase(path,URI )) {
return chain.filter(exchange);
}
return chain.filter(exchange);
};
} }
1. 配置了复杂的 GatewayFilter(全局拦截)- 也正常
2. 我将所有配置集成起来打成jar后,第三方使用websocket使用出现报错
3. 我将自己配置 gateway-doc 的 jar 包 进行拆解
1. 发现是我配置的 gateway-doc 里的登录拦截器用的 JWT包 发生的问题
2. **fuck 原来是因为gateway是netty写的跟web犯冲,我JWT项目中引入了**
```xml
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>8.5.43</version>
</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))
.map(t -> t.get(0))
.orElse(null);
}
依赖
nacos 上 websockt相关的 gateway.yaml配置
```yaml
spring:
cloud:
gateway:
routes:
# ws表示websocket的转发
- id: test-websocket
uri: ws://127.0.0.1:9006
predicates:
- Path=/ws/**
- DocName=noSwagger
filters:
- StripPrefix=1
# ws表示websocket的转发
- id: nocas-websocket
uri: ws://test-websocket
predicates:
- Path=/ser/ws/**
- DocName=noSwagger
filters:
- StripPrefix=2
- id: api-websocket
uri: lb://test-websocket
predicates:
- Path=/api/**
- DocName=websocket
filters:
- StripPrefix=1
# ws表示websocket的转发
- id: test-websocket2
uri: ws://127.0.0.1:9007
predicates:
- Path=/ws2/**
- DocName=noSwagger
filters:
- StripPrefix=1
# ws表示websocket的转发 - 这是错误的
- id: nocas-websocket2
uri: ws://test-websocket2
predicates:
- Path=/ser2/ws/**
- DocName=noSwagger
filters:
- StripPrefix=2
- id: nocas2-websocket2
uri: lb:ws://test-websocket2
predicates:
- Path=/serlb/ws/**
- DocName=noSwagger
filters:
- StripPrefix=2
- id: api-websocket2
uri: lb://test-websocket2
predicates:
- Path=/api2/**
- DocName=noSwagger
filters:
- StripPrefix=1
# 测试动态路由
- id: dy-routes
uri: lb://dy-routes
predicates:
- Path=/dy/**
- DocName=noSwagger
filters:
- StripPrefix=1
default-filters:
# - StripPrefix=2
# - RewritePath=/api/(?<segment>.*), /$\{segment}
# - name: SwaggerHeaderFilter
- name: AuthFilter
args:
excludePatterns: #不做权限校验的路径
- /serlb/ws/socket/**