Broadcast messages with websockets and SpringBoot - java

I'm trying to understand how to publish/broadcast messages using websockets with Spring Boot to a Javascript application. All examples I can find are making use of a StompJs client - I however am unable to use StompJs in my client code, and I'm not sure my backend is correct which doesn't help.
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/subscribe")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
Just using a simple #Scheduled to produce the time every 5 seconds, and send it to the time topic (Well, I believe that's what it's doing...)
#Component
#Slf4j
public class TimeSender {
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("HH:mm:ss");
private SimpMessagingTemplate broker;
#Autowired
public TimeSender(final SimpMessagingTemplate broker) {
this.broker = broker;
}
#Scheduled(fixedRate = 5000)
public void run() {
String time = LocalTime.now().format(TIME_FORMAT);
log.info("Time broadcast: {}", time);
broker.convertAndSend("/topic/time", "Current time is " + time);
}
}
There are a few points I'm a little confused about when trying to test this. Using the Simple websocket client plugin for Chrome, I have to add websocket to the end of my request in order to connect. A connection would like ws://localhost:8080/subscribe/websocket Without the websocket I can't connect, but I can't find this mentioned in any examples or Spring documentation?
The second question is how do I subscribe to the time topic? All StompJs clients call something like client.subscribe("time") etc.
I've tried ws://localhost:8080/subscribe/topic/time/websocket but no luck in receiving any timestamps.
I'm not sure if my backend code is just wrong, my URL is wrong, or I'm just missing something else.
Note: My #Controller is missing from above as I'm just focused on pushing messages from Spring to clients at this stage, not receiving messages and It's my understanding controllers just deal with incoming?

Well, I suppose if one searches obsessively enough the answer eventually turns up. Almost immediately after finding your post I found the answer I needed at http://www.marcelustrojahn.com/2016/08/spring-boot-websocket-example/. There is a really good example that essentially does what you are describing. The difference is they are using a Spring SimpMessagingTemplate to send messages to the queue. Once I followed his pattern, it all worked like a charm. Here is the relevant code snippet:
#Autowired
SimpMessagingTemplate template
#Scheduled(fixedDelay = 20000L)
#SendTo("/topic/pingpong")
public void sendPong() {
template.convertAndSend("/topic/pingpong", "pong (periodic)")
}
The method is void so the convertAndSend() method handles publishing to the topic, not the return statement as just about every other tutorial I've seen on the web indicates. This helped solve my problem.

Related

Is it possible to have a rest end point and a rabbitMQ receiver both in single service?

I have one microservice in which I have one rest controller and one rabbitMQ receiver, sometimes when I start the server receiver and API both are working fine but after some time receiver is not working it stops receiving new messages from the queue.
This is my rabbit configuration bean class:
#Configuration
public class RabbitMQConfiguration {
#Bean
public Queue claimQueue() {
return new Queue("queue");
}
}
other credentials-related configurations are in the application.properties file. And this is my receiver part
#RabbitListener(queues = "queue")
public class ClaimValidatorReciever {
#RabbitHandler
public void reciever(RabbitMQValidatorModel validatorModel)
throws InterruptedException, ExecutionException { }
Is it possible to have both in one microservice, and if possible any idea about this issue?
I tried to restart my server when this thing happen but after restarting this is working but after some time again facing this same issue of not fetching data from queue

Spring RestTemplate: How to repeatedly check Restful API Service?

I am attempting to create a SpringBoot application that will consume data from a 3rd party REST API and push Websocket notifications to my own clients based on events/changes to that data. The data I am consuming changes frequently, sometimes dozens of times a second (crypto currency price fluctuations behave similarly to this data). I want to repeatedly call the API on a fixed interval (every 1-10 seconds for example), watch for certain events/changes and trigger a Websocket push when those events occur.
I've been able to build a simple Spring Boot app that can push Websocket Notifications and consume the API by following these guides:
Spring.IO Websockets
Spring.IO Consuming REST
The Problem: I can only get the Application to request the data from the API once. I've spent hours searching every variation of "Spring RestTemplate multiple/repeated/persistent calls" I can think of, but I cannot find a solution that addresses my specific use. The closest examples I've found use retries but even those will eventually give up. I want my application to continually request this data until I shut the application down. I know I could wrap it in a while(true) statement or something like that, but that really doesn't seem right for a framework like SpringBoot and still has problems when trying to instantiate the RestTemplate.
How can I implement persistent querying of a RESTful API resource?
Below is what I have in my Application class
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.client.RestTemplate;
#SpringBootApplication
#EnableScheduling
public class Application {
private static final String API_URL = "http://hostname.com/api/v1/endpoint";
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
RestTemplate restTemplate(RestTemplateBuilder builder){
return builder.build();
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
Response response= restTemplate.getForObject(API_URL, Response.class);
System.out.println(response.toString());
};
}
}
CommandLineRunner only runs once per application start. Instead, you want to use the #Scheduled annotation to perform repeated operations at fixed intervals like
#Scheduled(fixedDelay = 1000L)
public void checkApi() {
Response response = restTemplate.getForObject(API_URL, Response.class);
System.out.println(response.toString())
}
It does not need to be a Bean, it can just be a simple method. See the Spring guide for more information https://spring.io/guides/gs/scheduling-tasks/
Add #EnableScheduling annotation on your SpringConfig class or Main class.
You can use Scheduled fixedDelay OR fixedRate
#Scheduled(fixedDelay = 10000)
public void test() {
System.out.println("Scheduler called.");
}
OR
#Scheduled(fixedRate = 10000)
public void test() {
System.out.println("Scheduler called.");
}
Difference between fixedDelay and fixedRate:
fixedDelay - makes sure that there is a delay of n millisecond between the finish time of an execution of a task and the start time of the next execution of the task.
fixedRate - runs the scheduled task at every n milliseconds.
Ideally, you should externalise the fixedDelay or fixedRate value as well in application.properties file:
#Scheduled(fixedDelayString = "${scheduler.fixed.delay}")
public void test() {
System.out.println("Scheduler called.");
}
In your application.properties file add below config:
scheduler.fixed.delay = 10000
Hope this helps.

Spring Boot #ExceptionHandler working on dev server but not locally

I know this is usually an issue that happens the other way around, so I am caught a little of guard here :D
I have built a user-management backend that provides a UI with data. When this architecture is deployed on our dev-server, everything works beautifully. However, as soon as I try to run the integration tests (which we do using a maven cargo tomcat) or if I use the war file in a local tomcat, the exception handlers aren't used at all. Spring simply displays a standard 500 response with the exception transformed into the body.
Perusing SO for similar issues has only resulted in the advice that I should use #EnableWebMVC, but that is neither applicable to what my backend is trying to accomplish, nor does it change anything.
How should I go about looking for the solution to this issue? Specifically, can I somehow observe if my controlleradvice is even scanned, and is there a reason why it might not be?
EDIT: These are the relevant files:
SpringConfiguration:
#Configuration
#ComponentScan(basePackageClasses = {UserManagementSpringConfiguration.class})
#EnableWebSecurity
public class UserManagementSpringConfiguration {
#Configuration
public static class ResourceMappingConfig implements WebMvcConfigurer {
#Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/ui/*/usermanagement").setViewName("forward:/usermanagement-ui/index.html");
// registry.addViewController("/ui/*/*/generator/").setViewName("forward:/generator-ui/index.html");
registry.addViewController("/ui/*/usermanagement/*").setViewName("forward:/usermanagement-ui/index.html");
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// cache setting, otherwise fonts are not loaded in IE over https
CacheControl cacheControl = CacheControl.noCache().mustRevalidate();
registry.addResourceHandler("/ui/**/*").addResourceLocations("/usermanagement-ui/")
.setCacheControl(cacheControl);
}
}
}
ControllerAdvice:
#ControllerAdvice
public class CustomResponseEntityExceptionHandler {
public static final Logger LOG = EISLoggerFactory.getLogger(CustomResponseEntityExceptionHandler.class);
#PostConstruct
public void postConstruct() {
LOG.debug("CustomExceptionHandler loaded and ready for use");
}
#ExceptionHandler(PasswordMismatchException.class)
public final ResponseEntity<ErrorDetails> handlePasswordChangeMismatch(
PasswordMismatchException ex,
WebRequest request) {
ErrorDetails errorDetails = new ErrorDetails(
new Date(),
ex.getMessage(),
request.getDescription(false),
MessageKeys.mismatchedPassword);
return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
}
}
It turns out that one of the modules that we wrote and that my project contains defines an ExceptionHandler for Throwable.class. On my machine, this ControllerAdvice is registered before my own ControllerAdvice, which causes Spring to look there first. Since Throwable fits the bill, Spring asks no further questions and just uses that handler.
The solution to my immediate problem was to add #Order(Ordered.HIGHEST_PRECEDENCE) to my ControllerAdvice. Since the exceptions I define within are quite specific, this will not cause any issues.
I have yet to find an explanation for why the order in which the two ControllerAdvice classes are registered is so consistently different between my machine and our dev server. Will update if I find anything. For now, I consider this issue to be answered.
This SO question was essential to solving this particular problem. Perhaps it helps someone in the future to link it here: Setting Precedence of Multiple #ControllerAdvice #ExceptionHandlers
Thanks to ValentinCarnu for pointing me to it!

Sending STOMP messages from other layers of an application

I am building an application using Spring Websockets on a clustered tomcat environment with a RabbitMQ broker. I have an API module which needs to register the endpoint to listen to. I followed the normal examples and came up with this config:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void configureMessageBroker(final MessageBrokerRegistry config)
{
config.enableStompBrokerRelay("/topic/")
.setRelayHost("localhost")
.setRelayPort(61613)
.setClientLogin("guest")
.setClientPasscode("guest");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry)
{
registry.addEndpoint("/updates")
.setAllowedOrigins("*")
.withSockJS();
}
}
While this works, it doesn't solve my issue as it appears the WebSocket and relay config are all bundled into the API module therefore leaving other layers unable to reuse the broker. I need the stomp message broker relay configuration to happen at the service layer so that other modules of our app can push messages to topics in RabbitMQ which then turn around and notify the API module to update all open websockets.
Below is a sample diagram of the relevant layers in our application and what I am trying to accomplish. I need to allow the module "Cron Message Sender" to push messages to everyone who is subscribed to a message topic through our other API modules.
So the second approach did in fact work. I configured the websockets to be run independently (no relay) and then I made a separate AMQP message broker connection at the service layer to allow communication between services. In the API module, I simply listened to the AMQP message broker and then manually forwarded those messages to the SimpMessagingTemplate which notified the websocket subscribers. I am not sure if this is technically the "right" way to do it but it seems to be working great and I do not yet see any issues with the implementation. In fact, I actually think I may prefer this approach as I now just gave all my services the ability to talk to each other with more types of messages than what I originally needed for the websockets.
Here is the new configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void configureMessageBroker(final MessageBrokerRegistry config)
{
config.enableSimpleBroker("/topic");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry)
{
registry.addEndpoint("/updates")
.setAllowedOrigins("*")
.withSockJS();
}
}
And here is where I listen to the message broker and forward the messages to the websocket subscribers:
#Component
public class SendWebSocketUpdates
{
private static final Logger logger = LoggerFactory.getLogger(SendWebSocketUpdates.class);
private final Gson gson;
#Autowired
private SimpMessagingTemplate messagingTemplate;
#Autowired
private MessageBrokerConsumer<String> messageBrokerConsumer;
public SendWebSocketUpdates()
{
this.gson = new Gson();
}
#PostConstruct
public void init()
{
//listen for incoming AMQP messages from the rabbitmq server and forward them to the websocket subscribers
messageBrokerConsumer.addListener((message, topicName) -> {
final String destination = "/topic/" + topicName;
final String messageJson = gson.toJson(message.getBody());
//check to see if trace logging is enabled
if (logger.isTraceEnabled())
{
logger.trace("Sending Message to \"{}\": {}", destination, messageJson);
}
//broadcast the via a STOMP message to subscribers of this topic
messagingTemplate.convertAndSend(destination, messageJson);
});
}
}
It's easy to solve this problem. I waste a whole day to find the solution.
Here 's my answer for the same problem.
The key is setUserDestinationBroadcast and setUserRegistryBroadcast:
registry.enableStompBrokerRelay("/topic/", "/queue/", "/exchange/")
.setUserDestinationBroadcast("/topic/log-unresolved-user")
.setUserRegistryBroadcast("/topic/log-user-registry")

Spring server send realtime Objects to client

I have a question.
I got a Spring server and a Client (Angularjs).
In my client i got a Rickshaw realtime chart.
When my server get some objects from a 3rd party software, i need it to send it to the client. This happens each 28th seconds.
I tried with websocket, and with SSE and i cannot get it to work.
I need someone to point me at a direction, since im lost atm.
Example.
Our server got a class called Animal.
Then a 3rd party software is sending a new object of Animal (E.g dog-cat) each 28th second.
When the server retrieves this object it, insert it in the DB and send it to the client.
The last part is what i can't make work.
Websocket tryout:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config){
config.enableSimpleBroker("/MashData/data");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry){
registry.addEndpoint("/mashData").setAllowedOrigins("*").withSockJS();
}
}
And in my controller
#MessageMapping("/mashData")
#SendTo("/MashData/data")
#Async
public MashData insertData(#PathVariable Long id, #RequestBody MashData INCmashData){
*Data created here*
return insertData
}

Categories

Resources