View Javadoc
1   /*-
2    * #%L
3    * Spring Stomp Server
4    * ===============================================================
5    * Copyright (C) 2020 - 2023 Brabenetz Harald, Austria
6    * ===============================================================
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package net.brabenetz.app.springstompserver.config;
21  
22  import org.springframework.beans.factory.annotation.Autowired;
23  import org.springframework.context.ApplicationListener;
24  import org.springframework.http.HttpEntity;
25  import org.springframework.http.HttpHeaders;
26  import org.springframework.http.HttpMethod;
27  import org.springframework.http.ResponseEntity;
28  import org.springframework.messaging.Message;
29  import org.springframework.messaging.simp.SimpMessagingTemplate;
30  import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
31  import org.springframework.messaging.support.MessageBuilder;
32  import org.springframework.messaging.support.NativeMessageHeaderAccessor;
33  import org.springframework.stereotype.Component;
34  import org.springframework.util.ObjectUtils;
35  import org.springframework.web.client.RestTemplate;
36  import org.springframework.web.socket.messaging.SessionSubscribeEvent;
37  
38  import java.util.Collections;
39  import java.util.List;
40  import java.util.Map;
41  import java.util.regex.Matcher;
42  
43  /**
44   * Listen on all subscriptions, and if a subscription starts with {@link WebSocketInitLoadConfigProperties#getDestinationPatterns()}, then directly send an
45   * initial load from {@link WebSocketInitLoadConfigProperties#getProxyUrl()} to the subscribed topic.
46   */
47  @Component
48  public class StompSubscriptionEventListener implements ApplicationListener<SessionSubscribeEvent> {
49  
50      @Autowired
51      private SimpMessagingTemplate messagingTemplate;
52  
53      @Autowired
54      private WebSocketInitLoadConfigProperties initLoadConfigProperties;
55  
56      private final RestTemplate restTemplate = new RestTemplate();
57  
58      @Override
59      public void onApplicationEvent(final SessionSubscribeEvent event) {
60          if (ObjectUtils.isEmpty(initLoadConfigProperties.getProxyUrl())) {
61              // nothing to proxy
62              return;
63          }
64  
65          StompHeaderAccessor sha = StompHeaderAccessor.wrap(event.getMessage());
66          String destination = sha.getDestination();
67          @SuppressWarnings("unchecked")
68          Map<String, List<String>> nativHeaders = (Map<String, List<String>>) sha.getHeader(NativeMessageHeaderAccessor.NATIVE_HEADERS);
69  
70          Matcher userDestMatcher = initLoadConfigProperties.getDestinationPatterns().matcher(destination);
71          if (userDestMatcher.matches()) {
72              // call proxy-server
73              String proxyUrl = getProxyUrl(userDestMatcher, initLoadConfigProperties.getProxyUrl());
74              HttpHeaders headers = new HttpHeaders();
75              nativHeaders.forEach((k, v) -> headers.addAll(k, v));
76              ResponseEntity<byte[]> response = restTemplate.exchange(proxyUrl, HttpMethod.GET, new HttpEntity<>(headers), byte[].class);
77  
78              // relay response to the subscribed subject:
79              HttpHeaders respHeaders = response.getHeaders();
80              Message<byte[]> stompMessage = MessageBuilder
81                      .withPayload(response.getBody())
82                      .copyHeaders(Collections.singletonMap(NativeMessageHeaderAccessor.NATIVE_HEADERS, respHeaders))
83                      .build();
84              messagingTemplate.send(destination, stompMessage);
85          }
86      }
87  
88      private String getProxyUrl(final Matcher userDestMatcher, final String proxyUrlTemplate) {
89          userDestMatcher.groupCount();
90          String proxyUrl = proxyUrlTemplate;
91          for (int i = 1; i <= userDestMatcher.groupCount(); i++) {
92              proxyUrl = proxyUrl.replaceAll("\\$\\{group-" + i + "\\}", userDestMatcher.group(i));
93          }
94          return proxyUrl;
95      }
96  
97  }