public class Subscriber extends UntypedActor
{
public Subscriber() {
ActorRef mediator =
DistributedPubSub.get(getContext().system()).mediator();
// subscribe to the topic named "content"
mediator.tell(new DistributedPubSubMediator.Subscribe("content", getSelf()),
getSelf());
mediator.tell(new DistributedPubSubMediator.Subscribe("content_2", getSelf()),
getSelf());
}
public void onReceive(Object msg) {
if (msg instanceof String)
System.out.println("Message received: " + msg );
else if (msg instanceof DistributedPubSubMediator.SubscribeAck)
System.out.println("subscribing");
else
unhandled(msg);
}
}
Now suppose both the topics have the same structure name(e.g. foo) but with different types. In this case how the subscriber will get to know "foo" message has been received from which topic ?
So the DistributedPubSub (DPS) is just a means to get messages to the actor. The receive loop doesn't care if the message is sent via the tell, ask, or via a DPS, it just knows that a message is in its inbox. What is more the DPS is just a router in that it calls forward() on the messages it receives so it doesn't rewrite the sender information of whomever published the message to the DPS. So the answer to your question is that you wont know what DPS it came in on and I think if that matters there is probably something wrong with the design. I cant think of a valid reason why that would be significant rather than the sender of the original message or the actual type of the message itself. So I would do my switching and checking on the types and if I needed to know where it came from, from the path of the ActorRef.
Related
I am writing an application to consume messages from queue. I am able to successfully bind the sqs and receive the messages. However, when I want to requeue the message, I am using as follows.
message.getHeaders().get(AwsHeaders.ACKNOWLEDGMENT, QueueMessageAcknowledgment.class)
.acknowledge();
I also use to requeue
StaticMessageHeaderAccessor.getAcknowledgmentCallback(message).acknowledge(AcknowledgmentCallback.Status.REQUEUE);
But it is not successful.
I also tried PollableMessage but unclear of how to implement it.
https://docs.spring.io/spring-cloud-stream/docs/3.1.0/reference/html/spring-cloud-stream.html#_overview_2
I've a Consumer like this
public class DefaultChannel implements Channel, Consumer<Message<String>> {
#Override
public void accept(Message<String> message) {
if("success".equals(message.getPayLoad()){
message.getHeaders().get(AwsHeaders.ACKNOWLEDGMENT, QueueMessageAcknowledgment.class)
.acknowledge();
}else{
StaticMessageHeaderAccessor.getAcknowledgmentCallback(message).acknowledge(AcknowledgmentCallback.Status.REQUEUE);
}
}
}
I was able to re-queue succesfully messageDeletionPolicy: ON_SUCCESS properties and throwing Exception from the code.
I have a route that I am testing. I use stub://jms:queue:whatever to send/receive messages and extending CamelTestSupport for my test classes. I am having an issue with one of the routes that has a bean that uses an idempotent repo to store messages by "message id" for which it reads and stores the JMSMessageID property from exchange.
The problem I run into is that I can't figure out a way to set this property on messages sent on stubbed endpoints. Every time the method that requires this prop is called, the id returns null and i have to handle it as a null pointer. I can do this but the cleanest approach would be to just set the header on the test message. I tried includeSentJMSMessageId=true on endpoint, I tried using sendBodyAndHeader on producer and passing "JMSMessageID", "ID: whatever" in arguments, doesn't appear to work? I read that the driver/connectionfactory is supposed to set the header, but I'm not too familiar with how/where to do this. And since I am using a stubbed end points, I'm not creating any brokers/connectionfactories in my uts.
So dont stud out the JMS component replace it with a processor and then add the preferred JMSMessageID in the processor.
Something like this code:
#Test
void testIdempotency() throws Exception {
mockOut.expectedMinimumMessageCount(1);
//specify the route to test
AdviceWithRouteBuilder.adviceWith(context, "your-route-name", enrichRoute -> {
//replace the from with a end point we can call directly.
enrichRoute.replaceFromWith("direct:start");
//replace the jms endpoint with a processor so it can act as the JMS Endpoint.
enrichRoute.weaveById("jms:queue:whatever").replace().process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
//Set that ID to the one I want to test
exchange.getIn().setHeader("JMSMEssageID", "some-value-to-test");
}
});
// add an endpoint at the end to check if received a mesage
enrichRoute.weaveAddLast().to(mockOut);
});
context.start();
//send some message
Map<String,Object> sampleMsg = getSampleMessageAsHashMap("REQUEST.json");
//get the response
Map<String,Object> response = (Map<String,Object>)template.requestBody("direct:start", sampleMsg);
// you will need to check if the response is what you expected.
// Check the headers etc.
mockOut.assertIsSatisfied();
}
The JMSMessageID can only be set by the provider. It cannot be set by a client despite the fact that javax.jms.Message has setJMSMessageId(). As the JavaDoc states:
This method is for use by JMS providers only to set this field when a message is sent. This message cannot be used by clients to configure the message ID. This method is public to allow a JMS provider to set this field when sending a message whose implementation is not its own.
I have the following actor setup:
public class Master extends AbstractActor {
protected Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public Receive createReceive() {
return receiveBuilder()
.match(Init.class, init -> {
log.info("Master received an Init, creating DLW and subscribing it.");
ActorRef deadLetterWatcher = context().actorOf(Props.create(DeadLetterWatcher.class),
"DLW");
context().system().eventStream().subscribe(deadLetterWatcher, DeadLetterWatcher.class);
log.info("Master finished initializing.");
})
.matchAny(message -> {
log.info("Found a {} that Master can't handle.",
message.getClass().getName());
unhandled(message);
}).build();
}
}
public class DeadLetterWatcher extends AbstractActor {
protected Logger log = LoggerFactory.getLogger(this.getClass());
#Override
public Receive createReceive() {
return receiveBuilder()
.matchAny(message -> {
log.info("Got a dead letter!")
}).build();
}
}
At startup the Master actor is created and is sent an Init message, and sure enough, I do see the following log output:
Master received an Init, creating DLW and subscribing it.
Master finished initializing.
However shortly after this, Master is sent a Fizzbuzz message, and I see this in the logs:
Found a com.me.myapp.Fizzbuzz that Master can't handle.
But then I don't see the DeadLetterWatcher log "Got a dead letter!", which tells me I have something wired incorrectly. Any ideas where I'm going awry?
Pass in akka.actor.UnhandledMessage.class, instead of DeadLetterWatcher.class, to the subscribe() method:
context().system().eventStream().subscribe(deadLetterWatcher, akka.actor.UnhandledMessage.class);
Note that unhandled messages are not the same thing as dead letters. For the former, an actor "must provide a pattern match for all messages that it can accept, and if you want to be able to handle unknown messages, then you need to have a default case." Your Master actor handles only Init messages; all other messages that it receives are considered "unhandled" and trigger the publication of an akka.actor.UnhandledMessage to the EventStream. You're explicitly calling the unhandled method for non-Init messages, but unhandled would be called by default if you didn't have the fallback case clause. Also note that you can log unhandled messages via the configuration, without the need of a "monitor" actor:
akka {
actor {
debug {
# enable DEBUG logging of unhandled messages
unhandled = on
}
}
}
Dead letters, on the other hand, are messages that cannot be delivered, such as messages that are sent to a stopped actor, and they also trigger the publication of messages to the EventStream.
Since unhandled messages are different from dead letters, your DeadLetterWatcher is misnamed and should probably be named something like UnhandledMessageWatcher. That being said, if your goal is only to log unhandled messages, then the simplest approach is to do so with the logging configuration mentioned above.
I want to do some server-side events (SSE) to a web app. I think I have all the SSE plumbing up and going. I now need to create a Source on the Akka HTTP side of the house.
I found you can do something like this:
val source = Source.actorRef(5, akka.stream.OverflowStrategy.dropTail)
What I want to do is somehow "publish" to this source, presumably by sending an actor a message. I see from the docs that this call creates Source<T,ActorRef>.
How can I get this ActorRef instance so I can send messages to it?
To obtain the materialized ActorRef from Source.actorRef, the stream has to be running. For example, let's say that you want to send the SSE payload data (in the form of a String) to this actor, which converts that data to ServerSentEvent objects to send to the client. You could do something like:
val (actor, sseSource) =
Source.actorRef[String](5, akka.stream.OverflowStrategy.dropTail)
.map(s => /* convert String to ServerSideEvent */)
.keepAlive(1.second, () => ServerSentEvent.heartbeat)
.toMat(BroadcastHub.sink[ServerSentEvent])(Keep.both)
.run()
// (ActorRef, Source[ServerSentEvent, NotUsed])
Now you can send messages to the materialized actor:
actor ! "quesadilla"
And use sseSource in your route:
path("events") {
get {
complete(sseSource)
}
}
Note that there is no backpressure with this approach (i.e., messages to the actor are fired-and-forgotten).
Lets say I ask (?) the same actor for two responses.
It stores the sender for later.
Later, it gets messages back to go to the senders. We get the right sender (the one hashed to the message) but how does Akka know which message the response is for?
Is there something in the ActorRef that indicates which message each response is for?
Is it the 'channel'?
I'd like to understand the underlying technology better.
I'll try to read the source at the same time but I think this is a really good question.
Code example:
class TestActor
[...]
def onReceive = {
case r: MessageToGoOut ⇒
messageId += 1
val requestId = clientConnectionId + messageId
senders += (requestId -> sender) //store sender for later
anotherActor ! WrappedUpMessage(requestId, MessageOut))
case m: MessageToGoBackToSender ⇒
val requestId = m.requestId
senders.get(requestId) map { client ⇒
client ! Response(m.message)
senders -= requestId
}
}
val futures = for(i <- 1 to 100) yield testActor ? new MessageToGoOut ("HEYO!" + i)
Now how does akka ensure the messages get back to the right actor??
Every Actor has a path. From inside of the Actor, you could say:
context.path
From outside an Actor, if you had an ActorRef, you could just say:
ref.path
This path if the address of that individual actor instance, and it's how I believe the internal routing system routes messages to the mailboxes for actors instances. When you are outside of an Actor, like you are when you are looping and sending messages in your example, when you use ask (the ?), a temporary Actor instance is started up so that when the Actor that received the message needs to response, it has a path to respond to. This is probably a bit of an oversimplification, and it might not be the level of detail that you are looking for, so I apologize if I missed the gist of your question.
Also, the sender var in an Actor is an ActorRef, thus it has a path so you can route back to it.
When a Future is created, akka creates a temporary (and addressable) Actor that is basically servicing that Future. When that temporary Actor sends to another Actor, its ActorRef is transmitted as the sender. When the receiving actor is processing that specific message, the sender var is set to the ActorRef for that temp actor, meaning that you have an address to respond to. Even if you decide to hold on to that sender for later, you still have an address to send back to and eventually complete the Future that the temporary actor is servicing. The point is, as long as you have an ActorRef, whether it's a request or a response, all it's doing is routing a message to the path for that ActorRef.
Ask (?) and tell (!) really aren't much different. Ask is basically a tell where the sender is expecting the receiver to tell a message back to it.