After some time I got my WebSocket running with this config:
#Configuration
//#EnableWebSocket
public class WebSocketServerConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/var").setAllowedOrigins("*");
}
#Bean
public WebSocketHandler myHandler() {
return new WebsocketServer();
}
}
But I'm unable to find a way to launch this WebSocket from my code.
Is there a way to launch this WebSocket later in my program?
I found this document but it does not provide a way to implement a startWebSocket() function or something similar.
The document you linked shows an example, where it returns an instance of EchoWebSocketHandler.
You could accept connections and implement your own logic for afterConnectionEstablished or handleMessage using some internal status to determine whether you should accept requests, i.e.
#Configuration
#EnableWebSocket
public class MyConfiguration implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(), "/echo").withSockJS();
}
#Bean
public WebSocketHandler echoWebSocketHandler() {
//FooWebSocketHandler implements your specific logic
return new FooWebSocketHandler();
}
}
public class FooWebSocketHandler extends AbstractWebSocketHandler {
private boolean enabled;
...
public void handleMessage(WebSocketSession session,
WebSocketMessage<?> message)
throws Exception {
if (enabled) {//work
} else {
//disabled, i.e. throw exception or send data according to your api
}
}
}
Related
cannot establish connection to my websocket server by browser client.
configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic/", "/queue/");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting");
}
}
And controller:
#MessageMapping("/message")
#SendToUser("/queue/reply")
public String processMessageFromClient(#Payload String message, Principal principal) throws Exception {
String name = new Gson().fromJson(message, Map.class).get("name").toString();
System.out.println(name);
//messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/reply", name);
return name;
}
I start server, and then open index.html in browser then make connect to ws://localhost:8080/greeting
and after that sending message to /app/message
but actually happens nothing. Browser inspector shows 404. What's wronng i do?
Here is the way that use to implement WebSocket in Spring. First, you should configure the Web socket message broker and register the stomp endpoint as below. Here I use setAllowedOrigins("*").withSockJS() to access this endpoint to any host.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/greeting")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/topic", "/queue/");
}
}
Then I create the controller as below.
#Controller
public class WebSocketController {
private final SimpMessagingTemplate template;
#Autowired
WebSocketController(SimpMessagingTemplate template){
this.template = template;
}
#MessageMapping("/queue/reply")
public void sendMessage(String message){
System.out.println(message);
this.template.convertAndSend("/topic", message);
}
}
Use #MessageMapping("/queue/reply") instead of #SendToUser("/queue/reply") as above.
From that Simple Messaging Template, I used convertAndSend() method to
asynchronous data communication with that message broker. If there is
any data comes to that message broker it will automatically send that
data using the above configured endpoint called /socket with SockJS
and Stomp.
You can refer this article to learn more about the Spring web socket.
I'm trying to allow cors from the same server, with port 3000.
As I don't know where my server is going to be deployed, I tried to access the server name as string than concat it with my desired port.
So here is my approach :
#SpringBootApplication
public class TestApplication {
#Autowired
private HttpServletRequest request;
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
System.out.println(request.getServerName());
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins(request.getServerName()+":3000");
}
};
}
}
I think these are the highlights of my error message :
Error creating bean with name 'corsConfigurer'
org.springframework.beans.BeanInstantiationException: Failed to instantiate
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread?
A good practice is to specify the allowed origins in the application.properties file and read it in your WebConfig.class. That allows you to easy add your production address later.
application.yml
web:
allowedOrigins:
- "http://localhost:4200"
- "some other"
#Configuration
public class WebMvcConfig implements WebMvcConfigurer {
private final String[] allowedOrigins;
public WebMvcConfig(#Value("${web.allowedOrigins:}") String[] allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //
.allowedMethods("*") //
.allowCredentials(true) //
.allowedOrigins(allowedOrigins);
}
}
just use localhost, no need to access server name
I have to add support for a custom WebSocket subprotocol (so not STOMP) in a Spring Boot application, but I'm having a very hard time understanding what I need to provide and what Spring already has.
This is how far I got:
#Configuration
#EnableWebSocket
public class WebSocketAutoConfiguration implements WebSocketConfigurer {
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(this.webSocketHandler(), new String[]{endpointUrl});
}
#Bean
public WebSocketHandler webSocketHandler() {
ExecutorSubscribableChannel clientInboundChannel = new ExecutorSubscribableChannel();
ExecutorSubscribableChannel clientOutboundChannel = new ExecutorSubscribableChannel();
SubProtocolWebSocketHandler subProtocolWebSocketHandler = new SubProtocolWebSocketHandler(clientInboundChannel, clientOutboundChannel);
subProtocolWebSocketHandler.addProtocolHandler(new SubProtocolHandler() {
public List<String> getSupportedProtocols() {
return Collections.singletonList("custom-protocol");
}
public void handleMessageFromClient(WebSocketSession session, WebSocketMessage<?> message, MessageChannel outputChannel) throws Exception {
session.sendMessage(new TextMessage("some message"));
}
public void handleMessageToClient(WebSocketSession session, Message<?> message) throws Exception {
}
public String resolveSessionId(Message<?> message) {
return UUID.randomUUID().toString();
}
public void afterSessionStarted(WebSocketSession session, MessageChannel outputChannel) throws Exception {
System.out.println("SESSION STARTED");
}
public void afterSessionEnded(WebSocketSession session, CloseStatus closeStatus, MessageChannel outputChannel) throws Exception {
session.close();
System.out.println("SESSION ENDED");
}
});
return subProtocolWebSocketHandler;
}
}
This works, in the sense that handleMessageFromClient does get triggered on a web socket message, but I fail to understand the purpose of MessageChannel outputChannel and handleMessageToClient.
Is it possible to get the PerConnectionWebSocketHandler semantics with SubProtocolWebSocketHandler?
The documentation around this is basically non-existent e.g. the docs for handleMessageToClient say:
Handle the given {#link Message} to the client associated with the given WebSocket session.
Well, fantastic. And the STOMP implementations are mind-boggling, so they're not very usable as a guideline.
Any example, broad steps or anything, really, would be much appreciated.
Turns out it is exceptionally easy. No need to mess with SubProtocolWebSocketHandler at all. The only requirement is that the provided WebSocketHandler implements SubProtocolCapable.
public class CustomHandler implements WebSocketHandler, SubProtocolCapable {
...
}
That's all. To make a PerConnectionWebSocketHandler, it's enough to simply extend it and implement SubProtocolCapable:
public class CustomHandler extends PerConnectionWebSocketHandler implements SubProtocolCapable {
...
}
Probably something simple, but I can't figure it out on my own. I have some sample of Spring Boot WebSockets implementation and wanted to display total active sessions. So I created #Scheduled activeSessions task, which should display actual count, but it's always 0. When afterConnectionEstablished is called I get expected sessions size. Whats the catch?
#Configuration
public class Monitoring extends TextWebSocketHandler {
private List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();
#Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
String clientMessage = message.getPayload();
System.out.println(clientMessage);
sessions.forEach(s -> {
try {
s.sendMessage(new TextMessage("Hello! You session id is: " + s.getId()));
activeSessions();
} catch (IOException e) {
e.printStackTrace();
}
});
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
//the messages will be broadcasted to all users.
System.out.println("Adding new session.");
sessions.add(session);
System.out.println("Current session count: " + sessions.size());
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
sessions.remove(session);
}
#Scheduled(fixedRate = 2000)
public void activeSessions() {
System.out.println("Total sessions: " + sessions.size());
}
}
Configuration part:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(#NotNull WebSocketHandlerRegistry registry) {
registry.addHandler(new Monitoring(), "/socket");
}
}
Due conflict described, custom scheduler:
#Configuration
#EnableScheduling
public class SchedulingConfig {
// https://stackoverflow.com/questions/49343692/websocketconfigurer-and-scheduled-are-not-work-well-in-an-application
#Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(10);
taskScheduler.initialize();
return taskScheduler;
}
}
You currently have 2 separate instances of the Monitoring class. One created by yourself, doing the request handling (which isn't a Spring managed bean!) and another one detected by Spring due to the #Configuration (shouldn't that be an #Component?).
Remove the #Configuration and replace it with an #Bean method, such that your WebSocketConfig looks like the following
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Override
public void registerWebSocketHandlers(#NotNull WebSocketHandlerRegistry registry) {
registry.addHandler(monitoring(), "/socket");
}
#Bean
public Monitoring monitoring() {
return new Monitoring();
}
}
You now have a single instance of the bean, managed by Spring.
The FailedMessageAspect.afterMethod() below gets called successfully during RabbitConsumerMain.main() below. However, it doesn't get called when it's used in the context of listening for a RabbitMQ message - when MessageHandlerImpl.handleMesasge() receives a message from a RabbitMQ queue. Any idea why?
FailedMessageAspect.java
#Aspect
#Component
public class FailedMessageAspect {
#AfterReturning("execution(* com..MessageHandlerImpl.testAspect(..))")
private void afterMethod() {
System.out.println("aspect foo");
}
}
MessageHandlerImpl.java
#Component
public class MessageHandlerImpl implements MessageHandler {
#Override
public void testAspect() {
System.out.println("handler foo");
}
#Override
public void handleMessage(String message) {
// handleMessage is called successfully when message is received
testAspect();
// FailedMessageAspect.afterMethod() does not get called
}
}
RabbitConsumerMain.java
#Controller
#SpringBootApplication
public class RabbitConsumerMain implements CommandLineRunner {
#Autowired
private MessageHandler messageHandler;
public static void main(String[] args) throws Exception {
SpringApplication.run(RabbitConsumerMain.class, args);
}
#Override
public void run(String... args) {
messageHandler.testAspect();
//FailedMessageSpect.afterMethod() gets called right here
}
}
ConsumerConfiguration.java
#Configuration
public class ConsumerConfiguration {
#Autowired #Lazy
private MessageHandler messageHandler;
//other standard AMQP configs
#Bean
public MessageListenerContainer messageListenerContainer() {
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.setConnectionFactory(connectionFactory());
container.setQueues(workQueue());
MessageListenerAdapter adapter = new MessageListenerAdapter(messageHandler, new Jackson2JsonMessageConverter());
container.setMessageListener(adapter);
return container;
}
}
You don't show all your configuration but, just to be clear, Spring AOP does not advise internal method calls such as handleMessage calling testAspect() within the same class instance.
You need to use AspectJ for that; otherwise, all methods you advise must be public methods invoked via bean definitions, so Spring can invoke the method via a proxy. Internal calls within a bean are never advised.
See the reference manual for a complete explanation.