Today I've searched a couple of hours for an implementation or tutorial in how to keep track of websocket connections in Spring.
I've done the (very good) Spring tutorial about websockets and STOMP.
link here
So what's my setup, I have an Ionic Hybrid app with an Spring backend and I want to send a notification to the client whenever a new notification-event arises in the backend. All this code is already implemented and the connection works, but right now there is no way to specify where the notifications need to go to.
There is no tutorial or explanation on this matter that follows the structure in the Spring tutorial (at least not after 5 hours of research) and I am a little overwhelmed by all the information about websockets and security on the web. (I've been learning about websockets for just 2 days)
So for all that been before me and will come after me, I think it can be very usefull to have a compact and lightweight answer following the structure taught by the Spring Tutorial.
I've found this unanswered question on StackOverflow about the same problems as I have, so I'm sure this questions will prove it's worth.
TL;DR
How to implement a list in the backend that keeps track of the connections based on the Spring WebSocket Tutorial?
How to send data from the client to the backend when the connection is established? (for example a userid or token)
So I figured it out myself.
My notifications have a recipient id (the user id where the notifications needs to be send to)
So I'm going to send to '/ws-user/'+id+'/greetings' where the id is the user that is logged in.
On the clientside this is fairly easy to achieve.
var stompClient = null;
// init
function init() {
/**
* Note that you need to specify your ip somewhere globally
**/
var socket = new SockJS('http://127.0.0.1:9080/ws-notification');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log('Connected: ' + frame);
/**
* This is where I get the id of the logged in user
**/
barService.currentBarAccountStore.getValue().then(function (barAccount) {
subscribeWithId(stompClient,barAccount.user.id);
});
});
}
/**
* subscribe at the url with the userid
**/
function subscribeWithId(stompClient,id){
stompClient.subscribe('/ws-user/'+id+'/greetings', function(){
showNotify();
});
}
/**
* Broadcast over the rootscope to update the angular view
**/
function showNotify(){
$rootScope.$broadcast('new-notification');
}
function disconnect() {
if (stompClient != null) {
stompClient.disconnect();
}
// setConnected(false);
console.log("Disconnected");
}
Next up we add "setUserDestinationPrefix" to the MessageBrokerRegistry in the WebSocketConfig.java class :
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
private final static String userDestinationPrefix = "/ws-user/";
#Override
public void configureMessageBroker(MessageBrokerRegistry config){
config.enableSimpleBroker("/ws-topic","/ws-user");
config.setApplicationDestinationPrefixes("/ws-app");
config.setUserDestinationPrefix(userDestinationPrefix);
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-notification").setAllowedOrigins("*").withSockJS();
}
}
Note that I'm using internal RestTemplate calls to access my controllermethod that sends out a notification to the subscribed client. This is done by a Event Consumer class (ask to see code, it's just to trigger controller function, could be done differently)
#RequestMapping(value = "/test-notification", method = RequestMethod.POST)
public void testNotification(#RequestBody String recipientId) throws InterruptedException {
this.template.convertAndSendToUser(recipientId,"/greetings", new Notify("ALERT: There is a new notification for you!"));
}
Please review my code and warn me if you see any issues and/or security concerns.
For user based delivery in websocket you can use Principle objects with spring security. Here is a nice example implemented:
https://github.com/rstoyanchev/spring-websocket-portfolio
Spring security will check for SAME ORIGIN and from your client you can send the stomp header with used-id specified.
Hope this might help you.
Take a look at this answer: How to get all active sessions in Spring 5 WebSocket API?
You can retrieve connected users using Spring's SimpUserRegistry API.
Related
My application contains rabbitmq,hazelcast,dynamodb,elastic I want build a mechanism that can tell me whenever any of my application get down I should be notified on an email.Can we include any service which i can track to get status of all application that are integrated in my spring boot application
I tried using try catch block but line of code got increased and it made my code very cumbersome as it difficult for me to add try catch at every method
Spring Boot actuator provides a lot of useful endpoints, one of which is the health endpoint. The health endpoint returns the health status of your application based on its dependencies (databases, third party APIs, ...).
There are already builtin health indicators for RabbitMQ, Hazelcast and Elastic. There is no builtin health indicator for DynamoDB as far as I know, but you can also write your own health indicator, as seen in this question.
Now, to send you an email there are a two options:
You can use an external program (eg. monitoring software) to regularly check the health actuator
You can write it as part of your Spring boot application
Using an external program
If you use an external program, you can make it consume the /actuator/health endpoint. To enable this endpoint, configure the following property:
management.endpoints.web.exposure.include=health
By default this endpoint only exposes a single aggregated status of all health indicators combined. If you want to individually see which service is down, you can show more details by setting the following property to always or when_authorized:
management.endpoint.health.show-details=always | when_authorized
The response will look something like this:
{
"status": "DOWN",
"rabbit": {
"status": "DOWN",
"error": "org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused (Connection refused)"
}
}
Writing your own
You can write a scheduled task by using the #Scheduled annotation to regularly check the health status. To obtain the health status, you can autowire HealthEndpoint as seen in this question.
Then you can send an email with Spring.
#Component
public class HealthEmailSender {
private final HealthEndpoint healthEndpoint;
private final JavaMailSender mailSender;
// TODO: Implement constructor to autowire healthEndpoint + mailSender
#Scheduled(fixedRate = 10 * 60 * 1000) // Check once every 10 minutes
public void sendEmailWhenBadHealth() {
if (isHealthDown()) {
sendMail();
}
}
private boolean isHealthDown() {
return Status.DOWN.equals(healthEndpoint.health().getStatus());
}
private void sendMail() {
MimeMessage message = mailsender.createMimeMessage();
// TODO: Set from / to / title / content
mailSender.send(message);
}
}
This code snippet above would send an e-mail as soon as any health indicator goes down (not only from the services you mentioned).
To obtain the health status of one of the services you're interested in, you can use healthEndpoint.healthForPath("rabbit").getStatus().
We try to publish and subscribe to MQTT protocol using smallrye reactive messaging. We managed to actually publish a message into a specific topic/channel through the following simple code
import io.smallrye.mutiny.Multi;
import org.eclipse.microprofile.reactive.messaging.Outgoing;
import javax.enterprise.context.ApplicationScoped;
import java.time.Duration;
#ApplicationScoped
public class Publish {
#Outgoing("pao")
public Multi<String> generate() {
return Multi.createFrom().ticks().every(Duration.ofSeconds(1))
.map(x -> "A Message in here");
}
}
What we want to do is to call whenever we want the generate() method somehow with a dynamic topic, where the user will define it. That one was our problem but then we found these classes from that repo in github. Package name io.smallrye.reactive.messaging.mqtt
For example we found that there is a class that says it makes a publish call to a MQTT broker(Mosquitto server up).
Here in that statement SendingMqttMessage<String> message = new SendingMqttMessage<String>("myTopic","A message in here",0,false);
We get the a red underline under the SendingMqttMessage<String> saying 'SendingMqttMessage(java.lang.String, java.lang.String, io.netty.handler.codec.mqtt.MqttQoS, boolean)' is not public in 'io.smallrye.reactive.messaging.mqtt.SendingMqttMessage'. Cannot be accessed from outside package
UPDATE(Publish done)
Finally made a Publish request to the mqtt broker(a mosquitto server) and all this with a dynamic topic configured from user. As we found out the previous Class SendingMqttMessage was not supposed to be used at all. And we found out that we also needed and emitter to actually make a publish request with a dynamic topic.
#Inject
#Channel("panatha")
Emitter<String> emitter;
#POST
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public Response createUser(Device device) {
System.out.println("New Publish request: message->"+device.getMessage()+" & topic->"+device.getTopic());
emitter.send(MqttMessage.of(device.getTopic(), device.getMessage()));
return Response.ok().status(Response.Status.CREATED).build();
}
Now we need to find out about making a Subscription to a topic dynamically.
first to sett us to the same page:
Reactive messaging does not work with topics, but with channels.
That is important to note, because you can exclusively read or write to a channel. So if you want to provide both, you need to configure two channels pointing at the same topic, one incoming and one outgoing
To answer your question:
You made a pretty good start with Emitters, but you still lack the dynamic nature you'd like.
In your example, you acquired that Emitter thru CDI.
Now that is all we need, to make this dynamic, since we cann dynamically inject Beans at runtime using CDI like this:
Sending Messages
private Emitter<byte[]> dynamicEmitter(String topic){
return CDI.current().select(new TypeLiteral<Emitter<byte[]>>() {}, new ChannelAnnotation(topic)).get();
}
please also note, that i am creating a Emitter of type byte[], as this is the only currently supportet type of the smallrye-mqtt connector (version 3.4.0) according to its documentation.
Receiving Messages
To read messages from a reactive messaging channel, you can use the counterpart of the Emitter, which is the Publisher.
It can be used analog:
private Publisher<byte[]> dynamicReceiver(String topic){
return CDI.current().select(new TypeLiteral<Publisher<byte[]>>() {}, new ChannelAnnotation(topic)).get();
}
You can then process these Date in any way you like.
As demo, it hung it on a simple REST Endpoint
#GET
#Produces(MediaType.SERVER_SENT_EVENTS)
public Multi<String> stream(#QueryParam("topic") String topic) {
return Multi.createFrom().publisher(dynamicReceiver(topic)).onItem().transform(String::new);
}
#GET
#Path("/publish")
public boolean publish(#QueryParam("msg") String msg, #QueryParam("topic") String topic) {
dynamicEmitter(topic).send(msg.getBytes());
return true;
}
One more Thing
When creating this solution I hit a few pitfalls you should know about:
Quarkus removes any CDI-Beans that are "unused". So if you want to inject them dynamically, you need to exclude those, or turne off that feature.
All channels injected that way must be configured. Otherwise the injection will fail.
For some Reason, (even with removal completely disabled) I was unable to inject Emitters dynamically, unless they are ever injected elsewhere.
Could anyone tell me if the server-side implementation is using stomp WebSocket, is the client also expected to implement stomp?
I am trying to implement a spring boot application and I am confused if I should go with or without stomp implementation. From my research, I understand, if you want to scale the application, it is better to use stomp and embedded broker( RabbitMQ for eg.) as it will handle the sessions, heartbeat etc. instead of an in-memory broker.
The examples available online just shows implementations with and without stomp.
I am basically trying to get different datasets from the table upon client request and write to a WebSocket continuously.
Could anyone please confirm if my understanding so far is correct?
What are the essential things I will have to take care of if I go with stomp + websocket?
Updating the usecase below:
The mobile client would be displaying charts upon user login. There would be links in the left panel for eg. Sales, Discounts etc. which upon clicking, the request will reach server through websocket channel. Server will check the datatype in the request, generate the model using data from DB and write the data to the websocket.
Updating code - v1
MyWebSocketHandler:
#Component
public class MyWebSocketHandler extends TextWebSocketHandler {
Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
DashboardUtil dashboardutil;
#Resource(name = "socketSessionsMap")
private Map<String, WebSocketSession> socketSessionsMap;
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message)
throws InterruptedException, IOException {
try {
//Gets the socket session from map and writes a json to that socket - did for testing purpose.
socketSessionsMap.put("session", session);
//String payload = message.getPayload();
String jsonString = dashboardutil.getDataInJSON(); // gets hardcoded json model
session.sendMessage(new TextMessage(jsonString));
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
}
}
WebSecurityConfig:
#Configuration
#EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
#Autowired
private MyWebSocketHandler myWebSocketHandler;
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myWebSocketHandler, "/socketHandler").setAllowedOrigins("*").withSockJS();
}
}
Could anyone tell me if the server-side implementation is using stomp
WebSocket, is the client also expected to implement stomp?
You can register multiple handlers in your web socket configuration. So in theory you can provide a handler for STOMP and another one for plain web socket. If you only provide a STOMP handler then the handshake from a standard web socket client will fail.
From my research, I understand, if you want to scale the application,
it is better to use stomp and embedded broker( RabbitMQ for eg.) as it
will handle the sessions, heartbeat etc. instead of an in-memory
broker.
That's correct. STOMP also offers a few more nice features especially the subscription to certain endpoints.
I am basically trying to get different datasets from the table upon
client request and write to a WebSocket continuously.
That's a really simple description ...
You should think about if you need to share sessions across multiple instances or if you need to send messages between web socket sessions.
From your description it sounds like you just accept a web socket connection and continuously push data to the client. If you want to scale this application you can just put a load balancer in front of your instances and you are good to go.
I have a hub and spoke architecture similar to this:
where a GET request comes into the hub and it routes it to one of the spokes for processing. On the hub I also put the request in a map with a UUID so that I can return the proper response when I get the data back from processing. The spokes are identical and are used to balance the load. I then need to pass the information back to the hub from the spoke and return the proper reponse.
I would like to do the messaging using JMS.
What is the best combination of integration patterns to accomplish this?
You already have Request/Reply within Vert.x, so you can achieve this behavior with about 20 lines of code:
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
Router router = Router.router(vertx);
router.get("/").handler((request) -> {
// When hub receives request, it dispatches it to one of the Spokes
String requestUUID = UUID.randomUUID().toString();
vertx.eventBus().send("processMessage", requestUUID, (spokeResponse) -> {
if (spokeResponse.succeeded()) {
request.response().end("Request " + requestUUID + ":" + spokeResponse.result().body().toString());
}
// Handle errors
});
});
// We create two Spokes
vertx.deployVerticle(new SpokeVerticle());
vertx.deployVerticle(new SpokeVerticle());
// This is your Hub
vertx.createHttpServer().requestHandler(router::accept).listen(8888);
}
And here's what Spoke looks like:
/**
* Static only for the sake of example
*/
static class SpokeVerticle extends AbstractVerticle {
private String id;
#Override
public void start() {
this.id = UUID.randomUUID().toString();
vertx.eventBus().consumer("processMessage", (request) -> {
// Do something smart
// Reply
request.reply("I'm Spoke " + id + " and my reply is 42");
});
}
}
Try accessing it in your browser on http://localhost:8888/
You should see that request ID is generated every time, while only one of two Spokes answers your request.
Well if I understand your design correctly this seems to request/reply scenario since the spoke is actually returning some response. If it didn't it would be publish/subscribe.
You can use ActiveMQ for jms and request/reply. See here:
http://activemq.apache.org/how-should-i-implement-request-response-with-jms.html
As for the details it all depends on your requirements, will the response be sent fairly immediately or is it a long running process?
If it is a long running process you can avoid request/reply and use a fire and forget scenario.
Basically, the hub fires a message on a queue which is being listened by one of the spoke components. Once the backend processing is done it returns the response to a queue monitored by the hub. You can correlate the request/response via some correlationId. During the request part, you can save the correlationId in a cache to match against the response. In a request/reply scenario this is done for you by the infrastructure but don't use for long running process.
To summarise:
Use ActiveMQ for your message processing with JMS.
Use Camel for the REST bits.
Use request/reply if you are sure you expect a response fairly rapidly.
Use fire and forget if you expect the response to take a long time but have to match the message correlationIds.
If you wish to use Camel with JMS, then you should use Request-Reply EIP, and as far as examples go, you have a pretty good one provided via Camel's official examples - it may be a bit old but it's still very much valid.
While you can just ignore the example's Camel configuration through Spring, its route definitions provide sufficient information:
public class SpokeRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("jms:queue:spoke")
.process(e -> {
Object result = ...; // do some processing
e.getIn().setBody(result); // publish the result
// Camel will automatically reply if it finds a ReplyTo and CorrelationId headers
});
}
}
Then all HUB needs to do is invoke:
ProducerTemplate camelTemplate = camelContext.createProducerTemplate();
Object response = camelTemplate.sendBody("jms:queue:spoke", ExchangePattern.InOut, input);
This question already has an answer here:
How to invoke a REST service
(1 answer)
Closed 7 years ago.
I have this web service http://qa-takehome-creyzna.dev.aetion.com:4440 that I would like to test. I have the authentication details (username and password) and the service has the following end points: /login, /user/, /user/{id} and /user/search. For all endpoints other than /login an authorization token needs to be passed as an HTTP Header.
The service exposes a login endpoint ( /login) which takes a POST request with two parameters: username and password. Following a successful login, an authentication token will be returned which must be used to make additional requests to the service. For example,if the request is as following,
{
"username": "admin",
"password": "admin"
}
It may return { "token": "1234-0009-999" } and this token will required for making additional request.
I will need to authenticate the web service, create 10 users and then, retrieve that information to validate the users was created correctly. I would like to develop a test plan and implement in Eclipse. How can I get started ?
A web service is basically an extension of the Java Servlet, where the input is processed a bit more and the output is rarely an HTML page.
Netbeans has an excellent tutorial on how to stand up a web service, and if you follow it, you can have a basic web service running within the hour.
https://netbeans.org/features/java-on-server/web-services.html
Don't be fooled by thinking that you must use one IDE (I like netbeans, but others don't) or another. The fancy GUI tools are just writing plain Java classes that might use other plain Java facilities (like JAXB if using XML, etc).
A web service is not much more than a web server that accepts particular kinds of requests, and responds with particular kinds of responses. In Java, web servers are made easier to use by leveraging Servlets. The internal contents of the Servlet will look like
Unpack the request
Validate the request is complete, report an error response if not
Act on the reqeust
Generate a response in the appropriate format
Send the response back as the reply.
--- Edited in response to request ---
Sorry, It seemed too obvious to me. Let me fill in the gaps. Sorry for glossing over the details.
public class MockHttpServletRequest implements HttpServletRequest {
#Override
public String getAuthType() {
throw new UnsupportedOpertationException("unexpected method use");
}
#Override
public String getContextPath() {
throw new UnsupportedOpertationException("unexpected method use");
}
... repeat for all methods ....
}
public class ItemRequestWithBadEncoding extends MockHttpServletRequest {
#Override
public String getMethod() {
return "GET";
}
#Override
public String getHeader(String name) {
if ("content-type".equals(name)) {
return "text/plain-ish"; // this is not a mime-type
}
throw new IllegalArgumentException(String.format("this mock doesn't support %s", name);
}
... fill out the rest of the required request details ...
}
public class CapturingServletResponse implements HttpServletRespose {
private final ArrayList<Cookie> cookies = new ArrayList<Cookie>();
#Override
public void addCookie(Cookie cookie) {
cookies.add(cookie);
}
public List<Cookie> getCookies() {
return Collections.unmodifiableList(cookies);
}
... override other methods and capture them into per-instance fields
with ability to return unmodifiable references or copies to them ...
}
Now back in the testing framework
#Test
public void testItemFetch() {
try {
MockRequest request= ItemRequestWithBadEncoding();
CapturingServletResponse response = new CapturingServletResponse();
Servlet itemRequestServlet = new ItemRequestServlet();
itemRequestServlet.service(request, response);
Assert.assertEquals("unexpected cookies in response", 0, response.getCookies().size());
... other asssertations ....
} catch (Exception e) {
Assert.assertFail(String.format("unexpected exception: %s", e.getMessage());
}
}
Depending on what items you care about, and how much work you need to put into it, you can then flesh out the needed parts of the capture and perhaps parameterize and refine the way you construct your input handling.
Look into spring frameworks.
They go well with other testing frameworks like Mockito and Junits.
Use something like SpringMVCTest or SpringRestAssured, note RestAssured would let you write integration tests.
Read this and this