已经有好长一段时间没有写文章。主要还是个人比较随性,也在学习别的东西,就顾不上了。今天主要讲一下如何通过使用SpringCloud Gateway + WebSocker整合和自己在实践当中遇到的问题讲解一下,希望与大家一起来学习。
1.创建Spring gateway工程
想要了解gateway,可以取SpringCloud官方网站下载个列子。网关的主要作用我在这里就不再讲解了,就问度娘吧。
- 我的网关
pom.xml maven
依赖
网关的配置:
server:
port: ${USER_PORT:8555}
http2:
enabled: true
compression:
enabled: true
error:
whitelabel:
enabled: false
spring:
cloud:
gateway:
routes:
# =====================================
# to run server
# $ wscat --listen 9000
# to run client
# $ wscat --connect ws://localhost:8080/echo
# - id: websocket_test
# uri: ws://localhost:9000
# order: 9000
# predicates:
# - Path=/echo
# =====================================
- id: grabservice-websocket
uri: lb:ws://bilibili-grabservice
order: 9000
predicates:
- Path=/api/grabservice/mq/**
filters:
- StripPrefix=2
复制代码
2.再创建一个数据采集实时推送的服务名字可以自己定,贴上我的pom.xml maven 的主要依赖
- 编写个Configuration
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketAutoConfig implements WebSocketMessageBrokerConfigurer{
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/mq")
.setAllowedOrigins("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/matches");
registry.setPreservePublishOrder(true);
}
}
复制代码
- 编写
matches
Broker的端口业务
@Slf4j
@RestController
public class LiveMatchesController {
@Autowired
private SimpMessagingTemplate messagingTemplate;//这个是重点
@Scheduled(cron = "0/15 * * * * ?")
@SendTo("/matches")
public void matches() {
messagingTemplate.convertAndSend("/matches", "hell world");//这个也是重点(点对点)
}
}
复制代码
这样一个简单的后端推送服务完成。
3.编写一个测试页面(注意我的页面是放在后端推送服务中),然后通过网关,再Broker 后端推送服务的matches 端口
- 在static下创建index.html
<html>
<head>
<meta charset="UTF-8" />
<title>Spring Boot WebSocket+广播式title>
head>
<body onload="disconnect()">
<noscript>
<h2 style="color:#ff0000">貌似你的浏览器不支持websocketh2>
noscript>
<div>
<div>
<button id="connect" onclick="connect()">连接button>
<button id="disconnect" onclick="disconnect();">断开连接button>
div>
<div id="conversationDiv">
<label>输入你的名字label> <input type="text" id="name" />
<br>
<label>输入消息label> <input type="text" id="messgae" />
<button id="send" onclick="send();">发送button>
<p id="response">p>
div>
div>
<script src="https://cdn.bootcss.com/sockjs-client/1.3.0/sockjs.min.js">script>
<script src="https://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js">script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js">script>
<script type="text/javascript">
var stompClient = null;
var host="http://192.168.0.249:8555/api/grabservice";
function setConnected(connected) {
document.getElementById('connect').disabled = connected;
document.getElementById('disconnect').disabled = !connected;
document.getElementById('conversationDiv').style.visibility = connected ? 'visible' : 'hidden';
$('#response').html();
}
function connect() {
var socket = new SockJS(host+'/mq');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
setConnected(true);
console.log('Connected:' + frame);
stompClient.subscribe('/matches', function(response) {
showResponse(response.body);
});
});
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
setConnected(false);
console.log("Disconnected");
}
function send() {
var name = $('#name').val();
var message = $('#messgae').val();
stompClient.send("/chat", {}, JSON.stringify({username:name,message:message}));
}
function showResponse(message) {
var response = $('#response');
response.html(message);
}
script>
body>
html>
复制代码
http://192.168.0.249:8555/api/grabservice,是我的网关请求地址,api/grabservice
这是我的请求路由,真正请求的地址看网关的配置,我的后端推送服务端口是8553。 这里会存在两个问题
- 1.跨域的问题。解决办法(本机调试如何浏览器中报:The 'Access-Control-Allow-Origin' header contains multiple values 'http://localhost:8553, http://localhost:8553', but only one is allowed,可以注释 headers.setAccessControlAllowOrigin((request.getHeaders().get(HttpHeaders.ORIGIN)).get(0));):
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (CorsUtils.isCorsRequest(request)) {
ServerHttpResponse response = ctx.getResponse();
HttpHeaders headers = response.getHeaders();
log.debug(" origin : {}", request.getHeaders().get(HttpHeaders.ORIGIN).get(0));
headers.setAccessControlAllowOrigin((request.getHeaders().get(HttpHeaders.ORIGIN)).get(0));
headers.setAccessControlAllowCredentials(true);
headers.setAccessControlMaxAge(Integer.MAX_VALUE);
headers.setAccessControlAllowHeaders(Arrays.asList("*"));
headers.setAccessControlAllowMethods(Arrays.asList(HttpMethod.OPTIONS,
HttpMethod.GET, HttpMethod.HEAD, HttpMethod.POST,
HttpMethod.DELETE, HttpMethod.PUT));
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
}
return chain.filter(ctx);
};
}
复制代码
- 2.默认SockJS请求会自动添加类似info?t=150xxxxx 的请求,来获取服务端的信息是否是websocket,然后才会发送websocket真正的请求。如果不处理info请求,会报websocket请求头相关错误。解决办法在网关添加个全局过滤器,把我的
http://ws://bilibili-grabservice/mq/info
类似的请求中的ws(如果是https://wss),修改为http(wss就修改为https),但必须在WebsocketRoutingFilter之前org.springframework.cloud.gateway.filter.WebsocketRoutingFilter:
@Component
public class WebsocketHandler implements GlobalFilter, Ordered {
private final static String DEFAULT_FILTER_PATH = "/mq/info";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String upgrade = exchange.getRequest().getHeaders().getUpgrade();
log.debug("Upgrade : {}", upgrade);
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
log.debug("path: {}", requestUrl.getPath());
String scheme = requestUrl.getScheme();
if (!"ws".equals(scheme) && !"wss".equals(scheme)) {
return chain.filter(exchange);
} else if (DEFAULT_FILTER_PATH.equals(requestUrl.getPath())) {
String wsScheme = convertWsToHttp(scheme);
URI wsRequestUrl = UriComponentsBuilder.fromUri(requestUrl).scheme(wsScheme).build().toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, wsRequestUrl);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 2;
}
static String convertWsToHttp(String scheme) {
scheme = scheme.toLowerCase();
return "ws".equals(scheme) ? "http" : "wss".equals(scheme) ? "https" : scheme;
}
复制代码
好了一个基本的wobsocket工程就完成了。
参考文章
Spring Cloud Gateway转发Spring WebSocket