Java - Atmosphere long polling sends only first message - java

I'm trying to implement a notification mechanism where a client connects to the server and is receiving updates.
Each user connects to a service end point like this
#ManagedService(path = "/chat/{userId}")
When they connect, they are registered in the broadcaster like this
#Ready
public void onReady(final AtmosphereResource resource) {
Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(userId,true);
broadcaster.addAtmosphereResource(resource);
}
When i want to send a message from a REST end point for example, i do it like this
#RequestMapping(value = "/ws2/{userId}", method = RequestMethod.GET)
public void test(#PathVariable("userId") String userId) {
Broadcaster broadcaster = BroadcasterFactory.getDefault().lookup(userId,true);
broadcaster.broadcast(new Message(userId, "User id : "));
}
It works very well when i'm using the web-socket implementation.
When i change to long-polling and calling this REST method, only the first message is sent, others are ignored with no errors or logs of any kind.
What can i do in this case?

Related

WebSocketSession.send does not do anything

I'm coding a game, when a player end its turn, I want to notify the opponent that it's his turn to play.
So I'm storing WebSocketSessions in "Player" classes, so I just need to get an instance of a player to have access to his websocketsession.
The problem is that nothing is happening when I use the "send" method of a websocketsession stored in a "player" instance.
Here is my code to store a WebSocketSession in a player object, it actually receive properly messages from front end, and it is able to send a message back and it works:
#Component("ReactiveWebSocketHandler")
public class ReactiveWebSocketHandler implements WebSocketHandler {
#Autowired
private AuthenticationService authenticationService;
#Override
public Mono<Void> handle(WebSocketSession webSocketSession) {
Flux<WebSocketMessage> output = webSocketSession.receive()
.map(msg -> {
String payloadAsText = msg.getPayloadAsText();
Account account = authenticationService.getAccountByToken(payloadAsText);
Games.getInstance().getGames().get(account.getIdCurrentGame()).getPlayerById(account.getId()).setSession(webSocketSession);
return "WebSocketSession id: " + webSocketSession.getId();
})
.map(webSocketSession::textMessage);
return webSocketSession
.send(output);
}
}
And here is the code I use to notify the opponent player that it is its turn to play, the "opponentSession.send" method seems to produce no result, there is no error message, and it looks like I receive nothing on the front end. The sessions has the same ID than in the handle method so I think the session object is good, also the websocket session was opened and ready when I did my tests:
#RequestMapping(value = "/game/endTurn", method = RequestMethod.POST)
GameBean endTurn(
#RequestHeader(value = "token", required = true) String token) {
ObjectMapper mapper = new ObjectMapper();
Account account = authenticationService.getAccountByToken(token);
gameService.endTurn(account);
Game game = gameService.getGameByAccount(account);
//GameBean opponentGameBean = game.getOpponentGameState(account.getId());
//WebSocketMessage webSocketMessage = opponentSession.textMessage(mapper.writeValueAsString(opponentGameBean));
WebSocketSession opponentSession = game.getPlayerById(game.getOpponentId(account.getId())).getSession();
WebSocketMessage webSocketMessage = opponentSession.textMessage("test message");
opponentSession.send(Mono.just(webSocketMessage));
return gameService.getGameStateByAccount(account);
}
}
You can see on the screenshot that the handle method is working correctly, I'm able to send and receive message.
Websocket input and output
Does someone know how can I make the opponentSession.send method works correctly so that I can receive messages on the front end?
You are using the reactive stack for your websocket and WebSocketSession#send return a Mono<Void> but you don't subscribe to this Mono (you just assembled it) so nothing will happen until something subscribe to it.
In your endpoint it doesn't look like you are using webflux so you are in synchronous world so you don't have other choice than to block
opponentSession.send(Mono.just(webSocketMessage)).block();
If you are using webflux then you should change your method to return a Mono and do something like:
return opponentSession.send(Mono.just(webSocketMessage)).then(gameService.getGameStateByAccount(account));
If you are not familiar with this you should look into projectreactor and WebFlux

AKKA - How can I unit test my actor that handle a TCP connection?

I have an actor that binds a port a the preStart and then expect the Tcp.Bound message. Then, it will just wait for a Tcp.Connected to happen. This actor does not provides anything to its creator so I would like to receive the Tcp Message and/or mock the Tcp Manager
So far I tried to subscribe my TestKit Probe to the tcp messages. Other than that I am looking to create a class that would override the manager, but still don't know how to do it. I am using Java 8 and JUnit 5.
#Override
public void preStart() {
this.connection = Tcp.get(getContext().getSystem()).manager();
this.connection.tell(TcpMessage.bind(getSelf(), remoteAddress, 100), getSelf());
}
#Override
public AbstractActor.Receive createReceive() {
return receiveBuilder()
.match(Tcp.Bound.class, msg -> {
log.debug("Port Bound : [{}]", msg.localAddress());
this.sessionHandler = getContext().actorOf(RmiSessionHandler.props(getSelf(), settings));
this.buffer = getContext().actorOf(RmiBuffer.props(this.sessionHandler, settings));
this.connection = getSender();
}).match(Tcp.Connected.class, msg -> {
log.debug("Port Connected to : [{}])", msg.remoteAddress());
this.sessionHandler.tell(msg, getSelf());
sender().tell(TcpMessage.register(getSelf()), getSelf()); // Register ourselves
this.session = getSender();
this.isConnectedToClient = true;
You can see that my actor just creates other actors, but I don't want to go through them to test that he established the connection.
Would really like to know when my actor is sending Tcp.Bind or when a port is bound and on which port.
Here is what I've tried :
system = ActorSystem.create("sessionHandlerTest");
testProbe = new TestKit(system);
system.eventStream().subscribe(testProbe.getRef(), Tcp.Bound.class);
rmiSocket = system.actorOf(RmiSocket.props(testProbe.getRef(), settings));
Tcp.Bound bindingMessage = testProbe.expectMsgClass(Tcp.Bound.class);
Also I tried to register my probe to the tcp manager :
ActorRef tcpManager = Tcp.get(system).manager();
tcpManager.tell(TcpMessage.register(testProbe.getRef()), testProbe.getRef());
So, in short let's assume you have a class A that needs to connect to a database. Instead of letting A actually connect, you provide A with an interface that A may use to connect. For testing you implement this interface with some stuff - without connecting of course. If class B instantiates A it has to pass a "real" database connection to A. But that means B opens a database connection. That means to test B you inject the connection into B. But B is instantiated in class C and so forth.
So at which point do I have to say "here I fetch data from a database and I will not write a unit test for this piece of code"?
In other words: Somewhere in the code in some class I must call sqlDB.connect() or something similar. How do I test this class?
And is it the same with code that has to deal with a GUI or a file system?

Detect destination channel of SessionUnsubscribeEvent

My Situation
I'm building a small web chat to learn about Spring and Spring WebSocket. You can create different rooms, and each room has it's own channel at /topic/room/{id}.
My goal is to detect when users join and leave a chat room and I thought I could use Spring WebSocket's SessionSubscribeEvent and SessionUnsubscribeEvent for this.
Getting the Destination from the SessionSubscribeEvent is trivial:
#EventListener
public void handleSubscribe(final SessionSubscribeEvent event) {
final String destination =
SimpMessageHeaderAccessor.wrap(event.getMessage()).getDestination();
//...
}
However, the SessionUnsubscribeEvent does not seem to carry the destination channel, destination is null in the following snippet:
#EventListener
public void handleUnsubscribe(final SessionUnsubscribeEvent event) {
final String destination =
SimpMessageHeaderAccessor.wrap(event.getMessage()).getDestination();
//...
}
My Question
Is there a better way to watch for subscribe/unsubscribe events and should I even be using those as a way for a user to "log in" to a chat room, or should I rather use a separate channel to send separate "log in"/"log out" messages and work with those?
I thought using subscribe/unsubscribe would've been very convenient, but apparently Spring makes it very hard, so I feel like there has to be a better way.
STOMP Headers only appear in the frames relevant to your question as described here: https://stomp.github.io/stomp-specification-1.2.html#SUBSCRIBE and here: https://stomp.github.io/stomp-specification-1.2.html#UNSUBSCRIBE
Only the SUBSCRIBE frame has both destination and id, the UNSUBSCRIBE frame has only an id.
This means you have to remember the subscription id with the destination for future lookup. Care must be taken because different Websocket connections usually use/assign the same subscription ids, so to save destinations reliably, you have to include the websocket session id in your storage key.
I wrote the following method to get it:
protected String getWebsocketSessionId(StompHeaderAccessor headerAccessor)
{
// SimpMessageHeaderAccessor.SESSION_ID_HEADER seems to be set in StompSubProtocolHandler.java:261 ("headerAccessor.setSessionId(session.getId());")
return headerAccessor.getHeader(SimpMessageHeaderAccessor.SESSION_ID_HEADER).toString();
}
StompHeaderAccessor is created like this:
StompHeaderAccessor headerAccessor=StompHeaderAccessor.wrap(((SessionSubscribeEvent)event).getMessage());
StompHeaderAccessor headerAccessor=StompHeaderAccessor.wrap(((SessionUnsubscribeEvent)event).getMessage());
This can then be used to create a unique subscription id which can be used as a key for a map to save data about the subscription, including the destination:
protected String getUniqueSubscriptionId(StompHeaderAccessor headerAccessor)
{
return getWebsocketSessionId(headerAccessor)+"--"+headerAccessor.getSubscriptionId();
}
Like this:
Map<String, String> destinationLookupTable=...;
// on subscribe:
destinationLookupTable.put(getUniqueSubscriptionId(headerAccessor), destination);
// on other occasions, including unsubscribe:
destination=destinationLookupTable.get(getUniqueSubscriptionId(headerAccessor));
I think using SessionSubscribeEvent and SessionUnsubscribeEvent is a good idea for that matter. You can get the destination if you keep track of the SessionID:
private Map<String, String> destinationTracker = new HashMap<>();
#EventListener
public void handleSubscribe(final SessionSubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
destinationTracker.put(headers.getSessionId(), headers.getDestination());
//...
}
#EventListener
public void handleUnsubscribe(final SessionUnsubscribeEvent event) {
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.wrap(event.getMessage());
final String destination = destinationTracker.get(headers.getSessionId());
//...
}

JDA - send message

I have my own Discord BOT based on JDA. I need to send a text message to the specific channel. I know how to send the message as onEvent response, but in my situation I do not have such event.
I have: Author (BOT), Token and Channel number.
My question is: how to send the message to this channel without an event?
Ok I think I know what you mean. You don't need to have an event to get an ID of a channel and send a message. The only thing you need to do is to instantiate the JDA, call awaitReady(), from the instance you can get all channels (MessageChannels, TextChannels, VoiceChannels, either by calling
get[Text]Channels()
get[Text]ChannelById(id=..)
get[Text]ChannelsByName(name, ignore case))
So 1. Instantiate JDA
JDABuilder builder;
JDA jda = builder.build();
jda.awaitReady();
Get Channel
List<TextChannel> channels = jda.getTextChannelsByName("general", true);
for(TextChannel ch : channels)
{
sendMessage(ch, "message");
}
Send message
static void sendMessage(TextChannel ch, String msg)
{
ch.sendMessage(msg).queue();
}
Hope it helps.
You need only one thing to make this happen, that is an instance of JDA. This can be retrieved from most entities like User/Guild/Channel and every Event instance. With that you can use JDA.getTextChannelById to retrieve the TextChannel instance for sending your message.
class MyClass {
private final JDA api;
private final long channelId;
private final String content;
public MyClass(JDA api) {
this.api = api;
}
public void doThing() {
TextChannel channel = api.getTextChannelById(this.channelId);
if (channel != null) {
channel.sendMessage(this.content).queue();
}
}
}
If you don't have a JDA instance you would have to manually do an HTTP request to send the message, for this lookup the discord documentation or jda source code. The JDA source code might be a little too complicated to take as an example as its more abstract to allow using any endpoint.

Trying to retrieve HttpSession object

For some background, I'm using JBoss AS 7 with EJB. I'm sending a message to my server from the client using errai message bus when it initially connects to retrieve its session ID so that I can make requests from it later on and have the server respond to the specific client.
How do I go about doing this? Can I inject a HttpSession object server side somehow? I'm very new to this so please bear with me. If I'm too vague let me know and I'll try to elaborate more.
If you are sending a message to an ErraiBus service method, you will have the Message object available. You can retrieve the session from it and get that session's ID like this:
#Service
public class ClientHelloService implements MessageCallback {
#Override
public void callback(final Message message) {
HttpSession session = message.getResource(
HttpServletRequest.class, HttpServletRequest.class.getName()).getSession();
System.out.println("Client said hello. Session ID: " + session.getId());
}
}
If you are instead sending the message to an Errai RPC endpoint, you will not have such easy access to the message. In this case, you will have to use the RpcContext.getSession() method:
#Service
public class ClientHelloRpcServiceImpl implements ClientHelloRpcService {
#Override
public void hello() {
HttpSession session = RpcContext.getHttpSession();
System.out.println("Client said hello. Session ID: " + session.getId());
}
}
The way this works is simple but ugly: RpcContext class stores the Message object that contained the RPC request in a ThreadLocal, and it just retrieves the HttpSession from that.
// the following variable is in the Http servlet service() method arguments
// only shown here this way to demonstrate the process
javax.servlet.http.HttpServletRequest serviceRequest;
javax.servlet.http.HttpServletResponse serviceResp; // addCookie()
javax.servlet.http.HttpSession cecil;
javax.servlet.http.Cookie[] reqCk;
// "(boolean overload) "true" creates the session" or call the other overload version method with no argument
// to retrieve the session getSession() "the server container stores and creates sessions"
// false in that version is to avoid bothering for a session to cut down uneeded processing
cecil = serviceRequest.getSession();//creates a session if it does not have one
String httpSession_ID = cecil.getID();
if((reqCk = serviceRequest.getCookies()) == null){
// perhaps create a cookie here using "new class "
// cookiePiece = new javax.servlet.http.Cookie("COOKIENAME",....); ....YOU MUST LEARN THE COOKIE PARTS WRITING RULES FOR BROWSER COOKIES !!! ; ; ;
serviceResp.addCookie(cookiePiece); // now is on the servers array "reqCk"
}else{
// process the cookie here using javax.servlet.http.Cookie methods
}
Other ways of storing and retrieving data are session scoped JSP or JSF beans.

Categories