Spring get open websocket connections - spring-boot

I am using Spring Boot Websocket to enable my Spring Boot 2 microservice to deal with websocket connections.
My config:
#Configuration
#EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
#Autowired
WebSocketHandler webSocketHandler;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
DefaultHandshakeHandler handshakeHandler = new DefaultHandshakeHandler();
handshakeHandler.setSupportedProtocols(HANDSHAKE_PROTOCOL);
registry.addHandler(webSocketHandler, WS_HANDLER_PATH + WILDCARD)
.setAllowedOrigins("*")
.setHandshakeHandler(handshakeHandler);
}
}
My clients are able to connect to my Service via Websocket. I am implementing WebSocketHandler interface to handle messages and log connections.
Now my question: Is there a way to show all current websocket users/sessions?
I was trying to use the SimpUserRegistry:
#Configuration
public class UserConfig {
final private SimpUserRegistry userRegistry = new DefaultSimpUserRegistry();
#Bean
public SimpUserRegistry userRegistry() {
return userRegistry;
}
}
and to show the users via a REST endpoint
#RestController
public class WebSocketManager {
private final SimpUserRegistry userRegistry;
public WebSocketManager(SimpUserRegistry userRegistry) {
this.userRegistry = userRegistry;
}
#GetMapping(path = "/users")
public List<String> getConnectedUsers() {
userRegistry.getUsers().stream()
.map(SimpUser::getName)
.forEach(System.out::println);
System.out.println("Users " + userRegistry.getUsers());
System.out.println("UsersCount " + userRegistry.getUserCount());
return this.userRegistry
.getUsers()
.stream()
.map(SimpUser::getName)
.collect(Collectors.toList());
}
}
But this always gives my an empty list: [] even when obviously WS connections are established.
Is this SimpUserRegistry working with the Websocket system of Spring which is configured with the WebSocketConfigurer and #EnableWebSocket? What am I doing wrong? Any tips or alternatives?
Thank you in advance!

Related

How to send message to websocket connected user by a Servcie

I have a client side app ( ReatJS ) which instantiates a webSocket connection with the server ( Spring Boot ) and waits to receive data.
At a certain moment the server has to send data to a specific user on the WebSocket channel, the call to the function that starts the sending is inside a class annotated with #Service
How do I send a message using WebFlux via WebSocket to a specific user? I identify the user by id or by his connection-id
These are some classes
#Configuration
public class WebSocketConfig {
#Bean
public HandlerMapping handlerMapping() {
Map<String, WebSocketHandler> map = new HashMap<>(){{
put("/data", new MyHandler());
}};
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
mapping.setUrlMap(map);
mapping.setOrder(Ordered.HIGHEST_PRECEDENCE);
return mapping;
}
}
public class MyHandler implements WebSocketHandler {
#Override
public Mono<Void> handle(WebSocketSession webSocketSession) {
return webSocketSession.send( ... );
}
}
#Service
public class MyService {
public void sendToUser(String userId, String connectionId, String message) {
// send message to connected user through WebSocket
}
}

Springboot websocket give 404 error in postman

I am writing a chatroom service by springboot websocket.And i want to build multiple chatrooms for the clients base on the url.But it fail when testing postman with 404 not found
My controller :
#Controller
public class ChatroomController {
private final ChatroomService chatroomService;
private final SimpMessageSendingOperations messagingTemplate;
public ChatroomController(ChatroomService chatroomService, SimpMessageSendingOperations messagingTemplate) {
this.chatroomService = chatroomService;
this.messagingTemplate = messagingTemplate;
}
//send chat
#MessageMapping("/chat/{roomId}/sendMessage")
public ChatMessage sendMessage(#DestinationVariable String roomId, #Payload ChatMessage chatMessage) {
return chatroomService.sendMessage(roomId,chatMessage);
}
My service:
#Service
#Slf4j
public class ChatroomService {
private final ChatroomRepository chatroomRepository;
private final SimpMessageSendingOperations messagingTemplate;
public ChatroomService(ChatroomRepository chatroomRepository, SimpMessageSendingOperations messagingTemplate) {
this.chatroomRepository = chatroomRepository;
this.messagingTemplate = messagingTemplate;
}
public ChatMessage sendMessage(String roomId, ChatMessage chatMessage) {
//check chatroom is existed
chatMessage.setDateTime(Instant.now());
chatMessage.setOrder_id(roomId);
messagingTemplate.convertAndSend(format("/channel/%s", roomId), chatMessage);
ChatMessage savedchat=chatroomRepository.save(chatMessage);
return savedchat;
}
My config:
#Configuration
#EnableWebSocketMessageBroker
public class WebsocketConfig implements WebSocketMessageBrokerConfigurer {
#Override //register the endpoint
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
registry.addEndpoint("/ws");
//sockJs is for setting the STOMP =>send message to who(subscribe)
}
#Override //control with "/app" can access
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/Chatroom");
// '/topic' is access the broker
registry.enableSimpleBroker("/channel");
}
When i test with: ws://localhost:8084/ws/chat/12/sendMessage, it give the 404 error, but when i test with ws://localhost:8084/ws, it connected.Is there any problem on my url?
Error :
Invalid SockJS path '/chat/12' - required to have 3 path segments"
try configuring your application to run on a different port by adding this to your application.properties
server.port = 8081

Spring Boot - WebSocket - Doesn't show subscribers [duplicate]

This question already has answers here:
Principal is null for every Spring websocket event
(2 answers)
Closed last month.
I was going over the basic Spring Boot WebSocket Tutorial: https://spring.io/guides/gs/messaging-stomp-websocket/
I decided to modify it to print out how many users are subscribed to a channel in the console but couldn't figure it out for hours. I've seen a few StackOverflow posts but they don't help. The last one I check was this: https://stackoverflow.com/a/51113021/11200149 which says to add try this:
#Autowired private SimpUserRegistry simpUserRegistry;
public Set<SimpUser> getUsers() {
return simpUserRegistry.getUsers();
}
So, I added the above to my controller, and here is the change:
#Controller
public class GreetingController {
#Autowired
private SimpUserRegistry userRegistry;
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Set<SimpUser> subscribedUsers = userRegistry.getUsers();
System.out.println("User amount: " + subscribedUsers.size()); // always prints: 0
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
This always prints 0:
System.out.println("User amount: " + subscribedUsers.size());
I'm coming from Socket.IO so maybe things work a bit differently because I've seen people implement their own manual Subscription Service classes. In socket.io this would be a piece of cake so I would assume Spring Boot would have this, but I just can't seem to find it.
Edit: This post does a great explanation for this problem.
Principal is null for every Spring websocket event
Maybe you can try to add custom HandshakeHandler class into registry and override the determineUser method to return the Principal object that containing subscriber name so that the SimpUserRegistry can work properly.
If you would like to see the effect, the below is what I'm trying.
app.js (sending out a user name through request parameter)
function connect() {
var socket = new SockJS('/gs-guide-websocket?name=' + $('#name').val());
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
custom class extends DefaultHandshakeHandler.class
#Component
public class WebSocketHandShakeHandler extends DefaultHandshakeHandler {
#Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
String name = httpServletRequest.getParameter("name");
return new MyPrincipal(name);
}
}
custom object implement Principal.class
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyPrincipal implements Principal {
private String name;
}
WebSocketConfig.class
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Autowired
private WebSocketHandShakeHandler webSocketHandShakeHandler;
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket")
.setHandshakeHandler(webSocketHandShakeHandler)
.withSockJS();
}
}
Show all subscribers
#RestController
public class ApiController {
#Autowired
private SimpUserRegistry simpUserRegistry;
#GetMapping("/users")
public List<String> connectedEquipments() {
return this.simpUserRegistry
.getUsers()
.stream()
.map(SimpUser::getName).toList();
}
}
Result
By the way, you can check the DefaultSimpUserRegistry.class to observe the process of putting name into subscribers user map.

How do I spring cloud gateway custom filter e2e test?

I have implemented custom GatewayFilterFactory filter. But I don't know how to test this filter with e2e setup.
I have referenced official spring-cloud-gateway AddRequestHeaderGatewayFilterFactoryTests test case code.
This is my custom filter code:
#Component
public class MyCustomFilter implements GatewayFilterFactory<MyCustomFilter.Config>, Ordered {
#Override
public GatewayFilter apply(Config config) {
return new OrderedGatewayFilter((this::filter), getOrder());
}
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
/* do some filtering */
}
#Override
public int getOrder() {
return 1000;
}
#Override
public Config newConfig() {
return new Config(MyCustomFilter.class.getSimpleName());
}
public static getConfig() {
return
}
#Getter
#Setter
public static class Config {
private String name;
Config(String name) {
this.name = name;
}
}
}
And this is my test code:
BaseWebClientTests class look exactly the same as official BaseWebClientTests class code
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = RANDOM_PORT)
#DirtiesContext
#ActiveProfiles("my-custom-filter")
public class MyCustomFilterTests extends BaseWebClientTests {
#LocalServerPort
protected int port = 0;
protected WebTestClient testClient;
protected WebClient webClient;
protected String baseUri;
#Before
public void setup() throws Exception {
setup(new ReactorClientHttpConnector(), "http://localhost:" + port);
}
protected void setup(ClientHttpConnector httpConnector, String baseUri) {
this.baseUri = baseUri;
this.webClient = WebClient.builder().clientConnector(httpConnector)
.baseUrl(this.baseUri).build();
this.testClient = WebTestClient
.bindToServer(httpConnector)
.baseUrl(this.baseUri)
.build();
}
#Test
public void shouldFailByFilterTests() {
/* This test should be failed but success :( */
testClient.get().uri("/api/path")
.exchange().expectBody(Map.class).consumeWith(result -> {
/* do assertion */
});
}
#EnableAutoConfiguration
#SpringBootConfiguration
#Import(DefaultTestConfig.class)
public static class TestConfig {
#Value("${test.uri}")
String uri;
#Bean
public MyCustomFilter myCustomFilter() {
return new MyCustomFilter();
}
#Bean
public RouteLocator testRouteLocator(RouteLocatorBuilder builder, MyCustomFilter myCustomFilter) {
return builder.routes().route("my_custom_filter",
r -> r.path("/api/path")
.filters(f -> f.filter(myCustomFilter.apply(new MyCustomFilter.Config("STRING"))))
.uri(uri))
.build();
}
}
}
Lastly Target controller looks like this:
#RestController
#RequestMapping("/api/path")
public class HttpBinCompatibleController {
#GetMapping("/")
public Mono<BodyData> identity() {
return Mono.just(new BodyData("api success"));
}
#NoArgsConstructor
#AllArgsConstructor
#Getter
static class BodyData {
private String message;
}
}
What I understand how this filter factory test code works is that
custom filter: custom filter is setup inside TestConfig class testRouteLocator method
target controller: target controller is defined as HttpBinCompatibleController class
testClient sends the request, and custom should do some filtering, then target controller should receive the request from testClient.
What I expect from this shouldFailByFilterTests TC is that before request from testClient is sent to target controller, that request should be rejected by MyCustomFilter. But the request is sent to the target controller.
I think the request from testClient is not proxied by testRouteLocator but I'm not sure
Question
What is the cause of this problem?
Is there another way to test my own custom filter?
This problem was related to the version incompatibility between Spring Boot and Spring Cloud.
I was using Spring Boot version 2.1.7 and Spring Cloud version Greenwich.SR2.
Then I found this 'Release train Spring Boot compatibility' table on this link
Before I've noticed version incompatibility, for using #Configuration(proxyBeanMethods = false) feature, upgraded Spring Boot version to 2.2.x.
The solution is using 2.1.x branch BaseWebClientTests class.

WebSockets JSR 356 Spring integration #ServerEndpoint

Problem: #Autowired beans in #ServerEndpoint class are null
How can I make sure that this WebSocketController class below will be injected with beans, that is how can I make it managed by Spring? I can connect to the websocket so it works but gameService is always null inside the WebSocketController class instance, so I think that it is created by tomcat somehow and not Spring.
I'm using Spring boot. I just need to figure out how to inject beans into this websocket controller class.
WebSocketController class
#Component
#ServerEndpoint("/sock")
public class WebSocketController {
#Autowired
private GameService gameService;
private static Set<Session> clients = Collections.synchronizedSet(new HashSet<Session>());
#OnMessage
public void handleMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(
"Reversed: " + new StringBuilder(message).reverse());
}
#OnOpen
public void onOpen(Session session) {
clients.add(session);
System.out.println("New client #"+session.getId());
if (gameService == null) System.out.println("game service null");
}
#OnClose
public void onClose(Session session) {
clients.remove(session);
System.out.println("Client disconnected #" + session.getId());
}
}
GameService interface and implementation
public interface GameService {
List<Character> getCharacters();
}
#Service
public class GameServiceMockImpl implements GameService {
#Override
public List<Character> getCharacters() {
List<Character> list = new ArrayList<>();
list.add(new Character("aaa","1.png",100));
list.add(new Character("aaa","2.jpg",100));
list.add(new Character("aaa","3.jpg",100));
return list;
}
}
Application class
#SpringBootApplication
public class App {
public static void main(String args[]){
SpringApplication.run(App.class,args);
}
#Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
EDIT:
Using Spring 4 WebSockets doesn't work at all, I can't even connect via a browser.
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
#Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
public class MyHandler extends TextWebSocketHandler {
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
System.out.println(message.getPayload());
}
}
You are trying to integrate Spring and Java WebSocket API. A class annotated by #Component is registered to a spring bean and its instance is managed by spring but if a class is annotated by #ServerEndpoint it is registered to a server-side WebSocket endpoint and every time the corresponding endpoint's WebSocket is connected to the server, its instance is created and managed by JWA implementation. We you can't use both annotations together.
Either you can use CDI injection(your server should also support)
#ServerEndpoint("/sock")
public class WebSocketController {
#Inject
private GameService gameService;
Or have a look on this doc, Spring 4 has support for WebSocket
Maybe this article can help:
https://spring.io/blog/2013/05/23/spring-framework-4-0-m1-websocket-support
You can use dependency (with spring version > 4)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>${spring.version}</version>
</dependency>
And then simply
#ServerEndpoint(value = "/echo", configurator = SpringConfigurator.class)
public class WebSocketEndpoint {
#Inject
private BroadcastService broadcaster;

Resources