I am trying to do the following with Play Framework 2.6:
The browser targets the server and a WebSocket is created
Later on (after some other request is performed), the servers sends a message to the browser via the WebSocket previously created
Point 1 can be easily done with a route:
public WebSocket socket() {
return WebSocket.Text.accept(request -> {
// Log events to the console
Sink<String, ?> in = Sink.foreach(System.out::println);
// Send a single 'Hello!' message and then leave the socket open
Source<String, ?> out = Source.single("Hello!").concat(Source.maybe());
return Flow.fromSinkAndSource(in, out);
});
}
and the WebSocket can be saved server side.
But then how can I send data via the WebSocket? (triggered server side)
This was easy to do with 2.5 but the documentation is not very helpful for Play 2.6.
I've managed to implement websocket with help of Akka actors. At first step define actor that will handle messages
public class WebSocketActor extends AbstractActor {
private final ActorRef out;
#Inject
public WebSocketActor(ActorRef out) {
this.out = out;
}
#Override
public Receive createReceive() {
return receiveBuilder()
.match(String.class, message ->
out.tell("Sending message at " + LocalDateTime.now().toString(), self())
)
.build();
}
public static Props props(final ActorRef out) {
return Props.create(WebSocketActor.class, out);
}
}
This actor will be created per client. ActorRef out will send message to connected client. In this example response is send to client on each string message passed to WebSocketActor.
Now define API endpoint to open access to websocket for clients. Define ActorFlow that will create new instance of actor on new connection
public WebSocket ws() {
return WebSocket.Text.accept(request -> ActorFlow.actorRef((out) -> WebSocketActor.props(out), actorSystem, materializer));
}
According to source code ActorFlow creates actors with flowActor name. So to send message to websockets somewhere in the code we can find actors by their path. This will broadcast message to all connected clients
actorSystem.actorSelection("/user/*/flowActor").tell("Hello", ActorRef.noSender());
Unfortunately I didn't find easy way to change ActorFlow default name but maybe this answer may help you play-scala-akka-websockets-change-actor-path.
Also you can check play-java-websocket-example project from playframework examples.
Related
I'm studying RabbitMQ and I was wondering how to return the data it processes to the client (website user).
The process I'm thinking consists of a REST endpoint where the user requests something to be processed which takes about 5 seconds to finish. So the user requests it, the webservice forward the request to the RabbitMQ queue which will have up to 10 consumers in docker containers listening to it and ready to process the request. So far so good, I can do this. The thing is, how can I return the data to the user after the consumer has finished processing it?
Having the design below in mind, there's the image for better understanding:
So in other words, it'll be something like:
1 - The producer (rest) receives the request and sends the message to the RabbitMQ.
2 - The RabbitMQ forward the message to any consumer listening.
3 - The consumer process the data.
4 - This is where I'm lost. How can I return the generated data to the client (website user)? One more thing, the user will be waiting for the request to end, no need to send it later.
For the sake of better details, I'm using java and for the rest part is spring boot.
Image:
See Request Reply Messaging.
Use one of the RabbitTemplate's sendAndReceive() methods.
On the consumer side simply return a result from your #RabbitListener method.
#RabbitListener(queues = "foo")
public String process(String in) {
return in.toUpperCase();
}
EDIT
#SpringBootApplication
public class So56025184Application {
public static void main(String[] args) {
SpringApplication.run(So56025184Application.class, args);
}
#Bean
public ApplicationRunner runner(RabbitTemplate template) {
return args -> {
Scanner scanner = new Scanner(System.in);
String toSend = scanner.nextLine();
while (!"quit".equals(toSend)) {
System.out.println(template.convertSendAndReceive("q1", toSend));
toSend = scanner.nextLine();
}
scanner.close();
};
}
#RabbitListener(queues = "q1")
public String listen(String in) {
return in.toUpperCase();
}
#Bean
public Queue queue() { // RabbitAdmin will add this to the broker
return new Queue("q1");
}
}
You can send/receive rich objects (rather than simple strings), using Java serialization (and the default SimpleMessageConverter, or conversion to JSON, using the Jackson2JsonMessageConverter.
I am developing a client and server communication system using Netty NIO in Java. My code can be found in the following repository. Currently I am having one server and two clients and I am sending information from server to the clients and the opposite.
What I am trying to figure out, when I am receiving a message form the first client to the server, how can i send that message to the second client (and the opposite from client 2 to client 1). How can I send a message to a specific client?
I have noticed that my issues arised because of the way that I am trying to send the messages from the server. My code in serverHandler is the following:
for (Channel ch : channels1) {
responseData.setIntValue(channels1.size());
remoteAddr.add(ch.remoteAddress().toString());
future = ch.writeAndFlush(responseData);
//future.addListener(ChannelFutureListener.CLOSE);
System.out.println("the requested data from the clients are: "+requestData);
responseData1.setStringValue(requestData.toString());
future = ch.writeAndFlush(responseData1);
System.out.println(future);
}
By default am sending a message about the number of the connections, but also when I am receiving message from the client 1 or 2 I want to send it back to 2 and 1. So I want to perform the communication between the two components. How can I send from the server to a specific client? I am not sure how can I send the messages back to the clients.
General approach
Let's describe an approach to the problem.
When receiving data on the server side, use the remote address of the channel (the java.net.SocketAddress Channel.remoteAddress() method) to identify the client.
Such identification may be done using a map like: Map<SocketAddress, Client>, where the Client class or interface should contain the appropriate client connection (channel) associated context, including its Channel. Be sure to keep the map up-to-date: handle the «client connected» and «client disconnected» events appropriately.
After a client is identified, you may just send the appropriate messages to the clients, except the current sending client, using the client connection (channel) map.
Additionally, I would like to recommend you to find a good implementation of a chat application using Netty and to take a look at it.
Netty-specific solution
Let's consider the server side implementation, in particular, the implementation of the ProcessingHandler class.
It already manages the active channels by representing them as the channel group:
static final ChannelGroup channels1 =
new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
Keeping the channel group up-to-date
The current implementation handles the «channel becomes active» event to keep the channel group up-to-date:
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
channels1.add(ctx.channel());
// ...
}
But this is only a half: it is necessary to handle the «channel becomes inactive» event symmetrically as well. The implementation should look like:
#Override
public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
channels1.remove(ctx.channel());
}
Broadcasting: Sending the received message to all channels, except the current one
To implement the desired behaviour, just update the implementation by introducing the appropriate check as follows:
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ...
for (Channel ch : channels1) {
// Does `ch` represent the channel of the current sending client?
if (ch.equals(ctx.channel())) {
// Skip.
continue;
}
// Send the message to the `ch` channel.
// ...
}
// ...
}
Sending and receiving string problem
Currently, the functionality around the ResponseData class is not present (not implemented).
The following draft changes are required to make both the client and the server work.
The ResponseData class: the getStringValue and toString methods should be corrected:
String getStringValue() {
return this.strValue;
}
#Override
public String toString() {
return intValue + ";" + strValue;
}
The ResponseDataEncoder class: it should use the string value:
private final Charset charset = Charset.forName("UTF-8");
#Override
protected void encode(final ChannelHandlerContext ctx, final ResponseData msg, final ByteBuf out) throws Exception {
out.writeInt(msg.getIntValue());
out.writeInt(msg.getStringValue().length());
out.writeCharSequence(msg.getStringValue(), charset);
}
The ResponseDataDecoder class: it should use the string value:
private final Charset charset = Charset.forName("UTF-8");
#Override
protected void decode(final ChannelHandlerContext ctx, final ByteBuf in, final List<Object> out) throws Exception {
ResponseData data = new ResponseData();
data.setIntValue(in.readInt());
int strLen = in.readInt();
data.setStringValue(in.readCharSequence(strLen, charset).toString());
out.add(data);
}
The ClientHandler class: it should correctly receive and handle the message:
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
final ResponseData responseData = (ResponseData) msg;
System.out.println("The message sent from the server " + responseData);
update.accept(responseData.getIntValue());
}
Additional references
«SecureChat ‐ an TLS-based chat server, derived from the Telnet example», Netty Documentation. In particular, the implementation of the SecureChatServerHandler class.
«Netty in Action», Norman Maurer, Marvin Allen Wolfthal (ISBN-13: 978-1617291470), «Part 3 — Network protocols», the «12.2 Our example WebSocket application» subchapter. Covers implementation of «a browser-based chat application».
I need to stop publishing the stream in the publish() method and send the client a message such as "This name is incorrect, choose another."
I've looked at sendClientOnErrorStatus() but haven't found any examples showing how to handle it on the client side.
Also, can anyone explain what the sendResult(..) method does?
sendClientOnErrorStatus() and sendResult() only work in a Flash context.
sendClientOnErrorStatus()
On the Wowza server you can override the publish and releaseStream (for streams being published) methods and do something like:
sendClientOnStatusError(client, "NetStream.Publish.Denied", "Stream name is invalid: " + streamName);
On a Flash client using ActionScript you would add a NetStatusEvent listener:
nc = new NetConnection();
nc.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
private function netStatusHandler(event:NetStatusEvent):void {
switch (event.info.code) {
// do something with the event.info
}
}
sendResult()
This is used to send the result for a NetConnection.call which calls a custom method on the Wowza server.
Example:
Wowza server:
public void someMethod(IClient client, RequestFunction function,
AMFDataList params) {
{
sendResult(client, params, "response");
}
Client:
nc = new NetConnection();
nc.call("someMethod",new Responder(function(data:String):void {
trace(data);
})
);
Source: AS3 Reference
I am trying to use websockets in my app. I have followed this tutorial:
http://spring.io/guides/gs/messaging-stomp-websocket/
It works perfectly.
When one of connected clients press button, this method is called:
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting() throws Exception {
System.out.println("Sending message...");
Thread.sleep(1000); // simulated delay
return new Greeting("hello!");
}
and message is broadcasted to all of connected clients.
Now i want to modify my server app, that it will broadcast messages periodically (each hour) to all of my connected clients, without interaction from clients.
Something like this(but this is not working obviously):
#Scheduled(fixedRate = 3600000)
public void sendMessage(){
try {
#SendTo("/topic/greetings")
greeting();
} catch (Exception e) {
e.printStackTrace();
}
}
Thx for advices.
#SendTo works only in the SimpAnnotationMethodMessageHandler, which is initiated only through the SubProtocolWebSocketHandler, hance when the WebSocketMessage is received from clients.
To achieve your requirements you should inject to the your #Scheduled service SimpMessagingTemplate brokerMessagingTemplate and use it directly:
#Autowired
private SimpMessagingTemplate brokerMessagingTemplate;
.......
this.brokerMessagingTemplate.convertAndSend("/topic/greetings", "foo");
I am coming from Sidekiq and am now moving to a Java solution for distributed jobs. I came across RabbitMQ and ActiveMQ, but it seems those brokers use plaintext or raw byte[] messages. I was wondering if it's possible to send custom messages using these frameworks?
Ideally I would just define a Java class for each specific message type and use that in both worker and producer. Is such a thing possible? Or should I look at other types of middleware?
MyOwnMessageFormat message = new MyOwnMessageFormat(content)
channel.send(message)
Message message = channel.receive()
if (message.class == MyOwnMessageFormat)
{
doSomething();
}
Exchanging messages through message brokers in Java is much easier when done through a service bus like Camel. You dont loose the flexibility of configuring your broker endpoint and still your code is isolated from the particular transport used or the message format. E.g. you can deploy ActiveMQ and later switch to RabbitMQ without having to update your code - just the service bus configuration. Or you can switch from plain Java serialization to JSON when sending messages to the broker by adding a message transformer. Again your business layer does not have to be modified.
Here's a sample that uses POJO producing/consuming where the producer calls a regular Java interface and the consumer implements the interface. The sample assumes that the sender/receiver are instantiated with Spring in order for the Camel endpoints to be injected
Message sender:
interface MyService {
MyResult addTask(MyTask task);
}
class Sender {
#Produce(uri="activemq:queue:myservice")
MyService service;
public void run() {
MyTask task = new MyTask();
MyResult result = service.addTask(task);
}
Message receiver:
class Receiver {
#Consume(uri="activemq:queue:myservice")
public MyResult addTask(MyTask task) {
return new MyResult();
}
}
MyTask & MyResult need to be serializable.
I think learning the Camel framework is not very hard while it can be very rewarding.
In JMS,as long as your custom message object is Serializable, you can use an ObjectMessage,Stream Message or Map Message according to your requirement like below to send(Object Message):
MessageProducer producer = session.createProducer( destination );
ObjectMessage message = session.createObjectMessage( getMyObject() );
producer.send( message );
For receiving:
Message message = consumer.receive();
if (message instanceof ObjectMessage) {
Object object = ((ObjectMessage) message).getObject();
Hope this helps you!
Yes, Before publishing the message to RabbitMQ, convert your message to a Json String.
Now on the receiving end, when the message is received, Parse it to convert into the same format.
In ruby this can be done with
while sending => Message.to_json
on Receiving => message = JSON.parse(received_msg)
To get the class of data, you can send a variable specifying the class of the data along with the data.
This is what worked for me with the latest version of ActiveMQ
...
MessageConsumer consumer=session.createConsumer(destination);
while(true) {
javax.jms.Message message=consumer.receive();
ActiveMQObjectMessage queueMessage=(ActiveMQObjectMessage)message;
Object payload=queueMessage.getObject();
if(payload instanceof NotificationMessage) {
this.sendMessage((NotificationMessage)payload);
}
}
The NotificationMessage object extends Serializable and looks like this
public static class NotificationMessage implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1631373969001850200L;
public String to;
public String data;
}