Unable to broadcast to single connection using Atmosphere runtime - atmosphere

I am using Atmosphere runtime 0.6 Snapshot. Tomcat 7 is logging correctly that I am using the Http11 Nio connector and there is no warning that BlockingIO will be used.
I am trying to send messages to three kinds of channels.
Global Broadcaster - broadcast to all suspended resources. (All)
Broadcast to a particular resource (say, Partner)
Broadcast to current resource (Self)
When a login action occurs, what all do I have to store in session in order to achieve this kind of broadcasting?
Some details of my code are as follows:
My Handler implements AtmosphereHandler
In the constructor, I instantiate the globalBroadcaster as follows:
globalBroadcaster = new DefaultBroadcaster();
On login,
resource.getAtmosphereConfig().getServletContext().setAttribute(name, selfBroadcaster);
where name is the user name from request parameter and selfBroadcaster is a new instance of DefaultBroadcaster.
Here is the code for sendMessageToPartner,
private synchronized void sendMessageToPartner(Broadcaster selfBroadcaster,
AtmosphereResource<HttpServletRequest, HttpServletResponse> resource,String name, String message) {
// this gives the partner's name
String partner= (String) resource.getAtmosphereConfig().getServletContext().getAttribute(name + PARTNER_NAME_TOKEN);
// get partner's broadcaster
Broadcaster outsiderBroadcaster = (Broadcaster) resource
.getAtmosphereConfig().getServletContext()
.getAttribute(partner);
if (outsiderBroadcaster == null) {
sendMessage(selfBroadcaster, "Invalid user " + partner);
return;
}
// broadcast to partner
outsiderBroadcaster.broadcast(" **" + message);
I hope I have given all the required information. I can provide more information if required.
The problem is, the global message gets sent. When message to partner is sent, sometimes it gets blocked, the message is not received in the client at all. This happens consistently after 3-4 messages.
Is there some threading problem? What am I doing wrong?
I hope somebody helps me out with this.

Ok, I figured out how this can be achieved with Atmosphere runtime.
First, I upgraded to 0.7 SNAPSHOT, but I think the same logic would work with 0.6 as well.
So, to create a broadcaster for a single user:
In GET request,
// Use one Broadcaster per AtmosphereResource
try {
atmoResource.setBroadcaster(BroadcasterFactory.getDefault().get());
} catch (Throwable t) {
throw new IOException(t);
}
// Create a Broadcaster based on this session id.
selfBroadcaster = atmoResource.getBroadcaster();
// add to the selfBroadcaster
selfBroadcaster.addAtmosphereResource(atmoResource);
atmoResource.suspend();
When login action is invoked,
//Get this broadcaster from session and add it to BroadcasterFactory.
Broadcaster selfBroadcaster = (Broadcaster) session.getAttribute(sessionId);
BroadcasterFactory.getDefault().add(selfBroadcaster, name);
Now the global broadcaster. The logic here is, you create a broadcaster from the first resource and then add each resource as they log in.
Broadcaster globalBroadcaster;
globalBroadcaster = BroadcasterFactory.getDefault().lookup(DefaultBroadcaster.class, GLOBAL_TOKEN, false);
if (globalBroadcaster == null) {
globalBroadcaster = selfBroadcaster;
} else {
BroadcasterFactory.getDefault().remove(
globalBroadcaster, GLOBAL_TOKEN);
AtmosphereResource r = (AtmosphereResource) session
.getAttribute("atmoResource");
globalBroadcaster.addAtmosphereResource(r);
}
BroadcasterFactory.getDefault().add(globalBroadcaster,
GLOBAL_TOKEN);
Finally, you can broadcast to Single connection or Globally to all connections as follows:
// Single Connection/Session
Broadcaster singleBroadcaster= BroadcasterFactory.getDefault().lookup(
DefaultBroadcaster.class, name);
singleBroadcaster.broadcast("Only for you");
// Global
Broadcaster globalBroadcaster = BroadcasterFactory.getDefault().lookup(DefaultBroadcaster.class,GLOBAL_TOKEN, false);
globalBroadcaster.broadcast("Global message to all");
To send message to partner, just lookup the broadcaster for the partner and do the same as above for single connection.
Hope this helps someone who tries to achieve the same.
There may be better ways of doing this.
I think I will have to use this approach until someone suggests a better solution.

Related

Symfony: Logging all Mails created by Mailer

For various reasons I want/need to log all emails sent through my website which runs on Symfony 5.
What I have so far is a subscriber that creates an Entity of type EmailLogEntry when a MessageEvent class is created (at least that's what I understand from (MessageEvent::class) - correct me if I'm wrong). I also use this subscriber to fill in missing emailadresses with the default system address.
Now, after sending the email, I'd like to adjust my entity and call $email->setSent(true);, but I can't figure out how to subscribe to the event that tries to send the email. And for the reusability of the code I don't want to do that in the Services (yes, it's multiple since there's multiple sources that generate mails) where I actually call $this->mailer->send($email);.
My questions now are:
Can someone tell me how I can subscribe to the Mailers send event?
How, in general, do I figure out what events I can subscribe to? The kernel events are listed in the documentation, but what about all the other events that are fired?
Btw, my subscriber code at the moment:
class SendMailSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
MessageEvent::class => [
['onMessage', 255],
['logMessage', 0],
],
];
}
public function logMessage(MessageEvent $event) {
$email = new EmailLogEntry();
[...]
}
}
Thanks.
The answer to my question is: at the moment you can not subscribe to the send() event of Mailer.
As a workaround, that's my code for now:
It's an extract from my Mailer service.
// send email and log the whole message
private function logSendEmail($email) {
$log = new EmailLog();
// sender
$log->setSender(($email->getFrom()[0]->getName() ?:"")." <".$email->getFrom()[0]->getAddress().">");
// get recipient list
foreach($email->getTo() AS $to) {
$recipient[] = "To: ".$to->getName()." <".$to->getAddress().">";
}
foreach($email->getCc() AS $cc) {
$recipient[] = "CC: ".$cc->getName()." <".$cc->getAddress().">";
}
foreach($email->getBcc() AS $bcc) {
$recipient[] = "Bcc: ".$bcc->getName()." <".$bcc->getAddress().">";
}
$log->setRecipient(implode(";",$recipient));
// other message data
$log->setSubject($email->getSubject());
$log->setMessage(serialize($email->__serialize()));
$log->setMessageTime(new \DateTime("now"));
$log->setSent(0); // set Sent to 0 since mail has not been sent yet
// try to send email
try {
$this->mailer->send($email);
$log->setSent(1); // set sent to 1 if mail was sent successfully
}
// catch(Exception $e) {
// to be determined
// }
// and finally persist entity to database
finally {
$this->em->persist($log);
$this->em->flush();
}
}
EmailLog is an Entity I created. There's a slight overhang as I save the sender, recipients and subject seperatly. However, in compliance with consumer data protection, I plan to clear the message field automatically after 30 days while holding on to the other fields for 6 months.
I'm also not using a subscriber as of now, mostly because website visitors can request a message copy and I'm not interested in logging that as well. Also, since I want to produce reusable code, I might at some point face the problem that mails could contain personal messages and I certainly will not want to log this mails.

gRPC: How can I distinguish bi-streaming clients at server side?

In this tutorial and example code, a server can call onNext() method on every stream observer, which will broadcast messages to all clients bi-streaming with the server. But there is no method to identify which observer corresponds to which client. How can a server push a message to specific client instead of broadcasting?
According to this answer it is possible to map each observer if client id is provided by metadata. It seems const auto clientMetadata = context->client_metadata(); part does the trick, but I'm working with Java, not C++. Are there any Java equivalent for getting the metadata at server side?
The answer depends a bit on how the clients will be identified. If the initial request provided a handle (like a username, but not registered ahead-of-time), then you could just wait for the first onNext():
public StreamObserver<Chat.ChatMessage> chat(StreamObserver<Chat.ChatMessageFromServer> responseObserver) {
return new StreamObserver<Chat.ChatMessage>() {
#Override
public void onNext(Chat.ChatMessage value) {
String userHandle = value.getHandle();
// observers would now be a map, not a set
observers.put(userHandle, responseObserver);
...
Let's say instead that all users are logged in, and provide a token in the headers, like OAuth. Then you would use an interceptor to authenticate the user and Context to propagate it to the application, as in https://stackoverflow.com/a/40113309/4690866 .
public StreamObserver<Chat.ChatMessage> chat(StreamObserver<Chat.ChatMessageFromServer> responseObserver) {
// USER_IDENTITY is a Context.Key, also used by the interceptor
User user = USER_IDENTITY.get();
observers.put(user.getName(), responseObserver);
return new StreamObserver<Chat.ChatMessage>() {
...
The first one is easier/nicer when the identification only applies to this one RPC. The second one is easier/nicer when the identification applies to many RPCs.

Send to certain connections only Spring Websockets

I am using grails/groovy so excuse the odd syntax, i am also new to using websockets so please let me know if i am going about this in the wrong way:
Using spring websockets i am able to send messages to certain subscribed users via
SimpMessagingTemplate brokerMessagingTemplate
users.each {
brokerMessagingTemplate.convertAndSendToUser(it.id,"/topic/path",data)
}
However, i want to send messages only to subscribed users have passed to the server a certain value/id over and above their user id. A connection is initialised on wepage load so i figured perhaps that i could add a STOMP header value which passes this information to the server, and the server only sends messages to connections which match this.
var socket = new SockJS("/url/stomp");
var client = Stomp.over(socket);
var headers = {'additionalId': additionalId};
client.connect({}, function() {
client.subscribe("/user/topic/path", function (data) {
}, headers);
firstly, i dont know whether adding a header value is the right way to do this, and secondly im not sure how to make the SimpMessagingTemplate send to those that have specifically provided the additional Id in the header.
Instead of using a header you can use DestinationVariable as so:
brokerMessagingTemplate.convertAndSend("/topic/something.${additionalId}".toString(), data)
and use
#MessageMapping("/something.{additionalId}")
protected String chatMessage(#DestinationVariable String additionalId, Principal principal, String data) { ... }
Additionally you may want to limit who subscribe to a specific /something.{additionalId} by implementing a TopicSubscriptionInterceptor() where you can validate the Principal

sending messages to single clients NOT identified by name (Identity)

I know similar questions have been asked before, but here goes
I have an ASP.NET app that serves images to connected clients. All clients are connected via owin with username and password and there could multiple clients connected with the same username and password. However, each client may need to be served with unique images. This means that I need to use a unique "hub ID" to serve each image.
The problem comes from retrieving this "hub ID" on the GetUserID method of the CustomUserProvider class. The IRequest parameter doesn't provide me with enough information to uniquely identify the connection. If I can get (which I can't (??)) to the Session state of the page then problem solved.
Has anyone got any ideas. I'm thinking of perhaps using the url - which I can make unique for each connection....
(Does anyone know how to get the original url of the page in the GetUserID)
I solved this as follows. I append a unique id on the URL. Then in the GetUserID of the CustomUserProvider
public string GetUserId(IRequest request)
{
string id = "";
try
{
HttpContextBase requestContext = request.Environment[typeof(HttpContextBase).FullName] as HttpContextBase;
string url = requestContext.Request.UrlReferrer.AbsoluteUri;
var parsedQuery = HttpUtility.ParseQueryString(url);
id = parsedQuery["HUBID"];
}
catch { }
return id;
This HUBID is the one referenced in the code behind:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<Hubs.MimicHub>();
hubContext.Clients.User(HubID).addImage(MimicImage,
ImageWidth, ImageHeight
);
Every Signalr connection (client) will have its own ConnectionId.
You could use this ID to Identify the same user foreach connection.
You can receive this unique connectionId:
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
}
more info:
http://www.asp.net/signalr/overview/guide-to-the-api/mapping-users-to-connections
Please see amended question. There may have been a better one, but this works perfectly.

Implementing asynchronous publish subscribe topic in tibco ems

my scenario is that me as a movie distributor, need to update my clients on new movies, I publish this information on a topic with durable subscribers and clients who want to buy the movie will express their interest.
However, this is where things go south, my implementation of the publisher stops listening as soon as it receives the first reply. Any help would be greatly appreciated. Thank you.
request(Message message)
Sends a request and waits for a reply.
The temporary topic is used for the JMSReplyTo destination; the first reply is returned, and any following replies are discarded.
https://docs.oracle.com/javaee/6/api/javax/jms/TopicRequestor.html
First thing first... I have questions regarding the scenario. Is this some kind of test/exercice, or are we talking about a real world scenario ?
Are all client interested in the movie SEPARATE topic subscribers ? How does that scale ? I the plan to have a topic for every movie, and possible interested parties declaring durable subscribers (one each, for every movie) ? This seems to be abuse of durable subcribers... I would suggest using ONLY one subscriber (in system B) to a "Movie Released" event/topic (from system A), and have some code (in system B) reading all the clients from a DB to send emails/messages/whatever. (If system A and B are the same, it may or not be a good idea to use EMS at all... depends.)
If it is not an exercise, I must comment : Don't use a MOM (EMS, ActiveMQ) to do a DBMS' (Oracle, PostGreSQL) work !
With the disclaimer section done, I suggest an asynchronous subscription approach (These two clips are taken for the EMS sample directory. File tibjmsAsyncMsgConsumer.java).
Extract from the constructor (The main class must implements ExceptionListener, MessageListener):
ConnectionFactory factory = new com.tibco.tibjms.TibjmsConnectionFactory(serverUrl);
/* create the connection */
connection = factory.createConnection(userName,password);
/* create the session */
session = connection.createSession();
/* set the exception listener */
connection.setExceptionListener(this);
/* create the destination */
if (useTopic)
destination = session.createTopic(name);
else
destination = session.createQueue(name);
System.err.println("Subscribing to destination: "+name);
/* create the consumer */
msgConsumer = session.createConsumer(destination);
/* set the message listener */
msgConsumer.setMessageListener(this);
/* start the connection */
connection.start();
The method is then called every time a message arrives.
public void onMessage(Message msg)
{
try
{
System.err.println("Received message: " + msg);
}
catch (Exception e)
{
System.err.println("Unexpected exception in the message callback!");
e.printStackTrace();
System.exit(-1);
}
}
You want to continue reading messages in a loop. Here is an example:
/* read messages */
while (true)
{
/* receive the message */
msg = msgConsumer.receive();
if (msg == null)
break;
if (ackMode == Session.CLIENT_ACKNOWLEDGE ||
ackMode == Tibjms.EXPLICIT_CLIENT_ACKNOWLEDGE ||
ackMode == Tibjms.EXPLICIT_CLIENT_DUPS_OK_ACKNOWLEDGE)
{
msg.acknowledge();
}
System.err.println("Received message: "+ msg);
}
You may want to also consider a possible issue with durable consumers. If your consumers never pick up their messages, storage will continue to grow at the server side. For this reason you may want to send your messages with an a expiration time, and/or limit maximum number of messages (or size in KB/MB/GB) of the JMS topics you are using.

Resources