spring cloud gateway简称spring gateway,它可以转发请求到后端微服务。spring gateway除了转发http请求,也支持websocket请求。我们看下它是怎么实现的吧。
配置支持websocket转发
支持websocket转发,需要用到spring-cloud-starter-gateway
,不要搞错成spring-cloud-starter-gateway-web
。引入maven配置:
<dependency>
<groupid>org.springframework.cloud</groupid>
<artifactid>spring-cloud-starter-gateway</artifactid>
<version>4.1.4</version>
</dependency>
然后注册需要路由的规则,可以通过yml配置。
spring:
cloud:
gateway:
routes:
- id: ws1
uri: ws://localhost:8080
predicates:
- path=/ws
java配置方式,与yml方式等效。
@bean
public routelocator customroutelocator(routelocatorbuilder builder) {
return builder.routes()
.route("ws1", r -> r.path("/ws")
.uri("ws://localhost:8080"))
.build();
}
websocket转发原理
处理websocket协议转发的类是org.springframework.cloud.gateway.filter.websocketroutingfilter。它的filter方法会过滤出ws和wss两类请求。
@override
public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
changeschemeifiswebsocketupgrade(exchange);
uri requesturl = exchange.getrequiredattribute(gateway_request_url_attr);
string scheme = requesturl.getscheme();
if (isalreadyrouted(exchange) || (!"ws".equals(scheme) && !"wss".equals(scheme))) {
return chain.filter(exchange);
}
setalreadyrouted(exchange);
httpheaders headers = exchange.getrequest().getheaders();
httpheaders filtered = filterrequest(getheadersfilters(), exchange);
list<string> protocols = getprotocols(headers);
return this.websocketservice.handlerequest(exchange,
new proxywebsockethandler(requesturl, this.websocketclient, filtered, protocols));
}
可以看到方法的最后一行的handlerequest()方法,exchange是当前发给网关的ws握手请求,proxywebsockethandler用来处理网关和客户端建立完websocket链接成功后的事件。重点看看proxywebsockethandler的handle()方法。
@override
public mono<void> handle(websocketsession session) {
// pass headers along so custom headers can be sent through
return client.execute(url, this.headers, new websockethandler() {
// 省略部分代码...
});
}
handle()方法里用client(websocketclient
)给后端websocket地址发来一个握手请求。
到这里,网关握手的流程就清晰了。前端客户端 —[连接]—>网关—[连接]—>后端websocket服务,总共会产生2条websocket连接。
然后就是发消息和断开连接的方式,就在上面省略代码里。
new websockethandler() {
@override
public mono<void> handle(websocketsession proxysession) {
mono<void> serverclose = proxysession.closestatus().filter(__ -> session.isopen())
.map(this::adaptclosestatus).flatmap(session::close);
mono<void> proxyclose = session.closestatus().filter(__ -> proxysession.isopen())
.map(this::adaptclosestatus).flatmap(proxysession::close);
// use retain() for reactor netty
mono<void> proxysessionsend = proxysession
.send(session.receive().doonnext(websocketmessage::retain).doonnext(websocketmessage -> {
if (log.istraceenabled()) {
log.trace("proxysession(send from client): " + proxysession.getid()
+ ", corresponding session:" + session.getid() + ", packet: "
+ websocketmessage.getpayloadastext());
}
}));
// .log("proxysessionsend", level.fine);
mono<void> serversessionsend = session.send(
proxysession.receive().doonnext(websocketmessage::retain).doonnext(websocketmessage -> {
if (log.istraceenabled()) {
log.trace("session(send from backend): " + session.getid()
+ ", corresponding proxysession:" + proxysession.getid() + " packet: "
+ websocketmessage.getpayloadastext());
}
}));
// .log("sessionsend", level.fine);
// ensure closestatus from one propagates to the other
mono.when(serverclose, proxyclose).subscribe();
// complete when both sessions are done
return mono.zip(proxysessionsend, serversessionsend).then();
}
}
websocket连接关闭,是serverclose和proxyclose这两行代码,当后端的websocket连接断开时,就会把断开的转发设置到网关的websocket连接上;网关的websocket连接断开时,就会把断开的转发设置到后端的websocket连接上。这样,两个websocket连接的断开状态就一致了。
websocket发送消息,是proxysessionsend和serversessionsend这两行代码,当网关收到客户端消息时,就会把消息发送给后端websocket服务;当网关收到后端websocket发来的消息时,就会把消息转发给客户端。
至此,网关在websocket连接、发消息、断开连接就和后端websocket服务保持一致了。
发表评论