Spring Cloud Gateway 結合 WebSocket 進行實時推送

已經有好長一段時間沒有寫文章。主要仍是我的比較隨性,也在學習別的東西,就顧不上了。今天主要講一下如何經過使用SpringCloud Gateway + WebSocker整合和本身在實踐當中遇到的問題講解一下,但願與你們一塊兒來學習。javascript

1.建立Spring gateway工程

想要了解gateway,能夠取SpringCloud官方網站下載個列子。網關的主要做用我在這裏就再也不講解了,就問度娘吧。css

  • 個人網關 pom.xml maven 依賴

網關的配置:html

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");//這個也是重點(點對點)
        }
    }
複製代碼

這樣一個簡單的後端推送服務完成。java

3.編寫一個測試頁面(注意個人頁面是放在後端推送服務中),而後經過網關,再Broker 後端推送服務的matches 端口

  • 在static下建立index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8" />
    <title>Spring Boot WebSocket+廣播式</title>
</head>
<body onload="disconnect()">
<noscript>
    <h2 style="color:#ff0000">貌似你的瀏覽器不支持websocket</h2>
</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。 這裏會存在兩個問題jquery

  • 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工程就完成了。web

參考文章

Spring Cloud Gateway轉發Spring WebSocketspring

相關文章
相關標籤/搜索