!clt #13523 Don't expose sender in cluster client replies

This commit is contained in:
Patrik Nordwall 2015-08-18 17:27:42 +02:00
parent e19d3cb3e8
commit 7533df48cf
5 changed files with 41 additions and 25 deletions

View file

@ -524,7 +524,7 @@ object ClusterReceptionist {
def receive = { def receive = {
case Ping // keep alive from client case Ping // keep alive from client
case ReceiveTimeout context stop self case ReceiveTimeout context stop self
case msg client forward msg case msg client.tell(msg, Actor.noSender)
} }
} }
} }
@ -546,10 +546,11 @@ object ClusterReceptionist {
* *
* Response messages from the destination actor are tunneled via the receptionist * Response messages from the destination actor are tunneled via the receptionist
* to avoid inbound connections from other cluster nodes to the client, i.e. * to avoid inbound connections from other cluster nodes to the client, i.e.
* the `sender`, as seen by the destination actor, is not the client itself. * the `sender()`, as seen by the destination actor, is not the client itself.
* The `sender` of the response messages, as seen by the client, is preserved * The `sender()` of the response messages, as seen by the client, is `deadLetters`
* as the original sender, so the client can choose to send subsequent messages * since the client should normally send subsequent messages via the `ClusterClient`.
* directly to the actor in the cluster. * It is possible to pass the the original sender inside the reply messages if
* the client is supposed to communicate directly to the actor in the cluster.
* *
*/ */
final class ClusterReceptionist(pubSubMediator: ActorRef, settings: ClusterReceptionistSettings) final class ClusterReceptionist(pubSubMediator: ActorRef, settings: ClusterReceptionistSettings)

View file

@ -41,11 +41,13 @@ object ClusterClientSpec extends MultiNodeConfig {
testTransport(on = true) testTransport(on = true)
case class Reply(msg: Any, node: Address)
class TestService(testActor: ActorRef) extends Actor { class TestService(testActor: ActorRef) extends Actor {
def receive = { def receive = {
case msg case msg
testActor forward msg testActor forward msg
sender() ! msg + "-ack" sender() ! Reply(msg + "-ack", Cluster(context.system).selfAddress)
} }
} }
@ -116,7 +118,7 @@ class ClusterClientSpec extends MultiNodeSpec(ClusterClientSpec) with STMultiNod
val c = system.actorOf(ClusterClient.props( val c = system.actorOf(ClusterClient.props(
ClusterClientSettings(system).withInitialContacts(initialContacts)), "client1") ClusterClientSettings(system).withInitialContacts(initialContacts)), "client1")
c ! ClusterClient.Send("/user/testService", "hello", localAffinity = true) c ! ClusterClient.Send("/user/testService", "hello", localAffinity = true)
expectMsg("hello-ack") expectMsgType[Reply].msg should be("hello-ack")
system.stop(c) system.stop(c)
} }
runOn(fourth) { runOn(fourth) {
@ -190,18 +192,18 @@ class ClusterClientSpec extends MultiNodeSpec(ClusterClientSpec) with STMultiNod
ClusterClientSettings(system).withInitialContacts(initialContacts)), "client2") ClusterClientSettings(system).withInitialContacts(initialContacts)), "client2")
c ! ClusterClient.Send("/user/service2", "bonjour", localAffinity = true) c ! ClusterClient.Send("/user/service2", "bonjour", localAffinity = true)
expectMsg("bonjour-ack") val reply = expectMsgType[Reply]
val lastSenderAddress = lastSender.path.address reply.msg should be("bonjour-ack")
val receptionistRoleName = roleName(lastSenderAddress) match { val receptionistRoleName = roleName(reply.node) match {
case Some(r) r case Some(r) r
case None fail("unexpected missing roleName: " + lastSender.path.address) case None fail("unexpected missing roleName: " + reply.node)
} }
testConductor.exit(receptionistRoleName, 0).await testConductor.exit(receptionistRoleName, 0).await
remainingServerRoleNames -= receptionistRoleName remainingServerRoleNames -= receptionistRoleName
within(remaining - 3.seconds) { within(remaining - 3.seconds) {
awaitAssert { awaitAssert {
c ! ClusterClient.Send("/user/service2", "hi again", localAffinity = true) c ! ClusterClient.Send("/user/service2", "hi again", localAffinity = true)
expectMsg(1 second, "hi again-ack") expectMsgType[Reply](1 second).msg should be("hi again-ack")
} }
} }
system.stop(c) system.stop(c)
@ -220,11 +222,11 @@ class ClusterClientSpec extends MultiNodeSpec(ClusterClientSpec) with STMultiNod
ClusterClientSettings(system).withInitialContacts(initialContacts)), "client3") ClusterClientSettings(system).withInitialContacts(initialContacts)), "client3")
c ! ClusterClient.Send("/user/service2", "bonjour2", localAffinity = true) c ! ClusterClient.Send("/user/service2", "bonjour2", localAffinity = true)
expectMsg("bonjour2-ack") val reply = expectMsgType[Reply]
val lastSenderAddress = lastSender.path.address reply.msg should be("bonjour2-ack")
val receptionistRoleName = roleName(lastSenderAddress) match { val receptionistRoleName = roleName(reply.node) match {
case Some(r) r case Some(r) r
case None fail("unexpected missing roleName: " + lastSender.path.address) case None fail("unexpected missing roleName: " + reply.node)
} }
// shutdown all but the one that the client is connected to // shutdown all but the one that the client is connected to
remainingServerRoleNames.foreach { r remainingServerRoleNames.foreach { r
@ -244,9 +246,9 @@ class ClusterClientSpec extends MultiNodeSpec(ClusterClientSpec) with STMultiNod
val expectedAddress = node(receptionistRoleName).address val expectedAddress = node(receptionistRoleName).address
awaitAssert { awaitAssert {
c ! ClusterClient.Send("/user/service2", "bonjour3", localAffinity = true) c ! ClusterClient.Send("/user/service2", "bonjour3", localAffinity = true)
expectMsg(1 second, "bonjour3-ack") val reply = expectMsgType[Reply](1 second)
val lastSenderAddress = lastSender.path.address reply.msg should be("bonjour3-ack")
lastSenderAddress should be(expectedAddress) reply.node should be(expectedAddress)
} }
system.stop(c) system.stop(c)
} }

View file

@ -54,9 +54,10 @@ to the named topic.
Response messages from the destination actor are tunneled via the receptionist Response messages from the destination actor are tunneled via the receptionist
to avoid inbound connections from other cluster nodes to the client, i.e. to avoid inbound connections from other cluster nodes to the client, i.e.
the ``sender()``, as seen by the destination actor, is not the client itself. the ``sender()``, as seen by the destination actor, is not the client itself.
The ``sender()`` of the response messages, as seen by the client, is preserved The ``sender()`` of the response messages, as seen by the client, is ``deadLetters``
as the original sender(), so the client can choose to send subsequent messages since the client should normally send subsequent messages via the ``ClusterClient``.
directly to the actor in the cluster. It is possible to pass the the original sender inside the reply messages if
the client is supposed to communicate directly to the actor in the cluster.
While establishing a connection to a receptionist the ``ClusterClient`` will buffer While establishing a connection to a receptionist the ``ClusterClient`` will buffer
messages and send them when the connection is established. If the buffer is full messages and send them when the connection is established. If the buffer is full

View file

@ -297,6 +297,17 @@ is now started as a ``system`` actor instead of a ``user`` actor, i.e. the defau
the ``ClusterClient`` initial contacts has changed to the ``ClusterClient`` initial contacts has changed to
``"akka.tcp://system@hostname:port/system/receptionist"``. ``"akka.tcp://system@hostname:port/system/receptionist"``.
ClusterClient sender
====================
In 2.3 the ``sender()`` of the response messages, as seen by the client, was the
actor in cluster.
In 2.4 the ``sender()`` of the response messages, as seen by the client, is ``deadLetters``
since the client should normally send subsequent messages via the ``ClusterClient``.
It is possible to pass the the original sender inside the reply messages if
the client is supposed to communicate directly to the actor in the cluster.
Akka Persistence Akka Persistence
================ ================

View file

@ -54,9 +54,10 @@ to the named topic.
Response messages from the destination actor are tunneled via the receptionist Response messages from the destination actor are tunneled via the receptionist
to avoid inbound connections from other cluster nodes to the client, i.e. to avoid inbound connections from other cluster nodes to the client, i.e.
the ``sender()``, as seen by the destination actor, is not the client itself. the ``sender()``, as seen by the destination actor, is not the client itself.
The ``sender()`` of the response messages, as seen by the client, is preserved The ``sender()`` of the response messages, as seen by the client, is ``deadLetters``
as the original sender(), so the client can choose to send subsequent messages since the client should normally send subsequent messages via the ``ClusterClient``.
directly to the actor in the cluster. It is possible to pass the the original sender inside the reply messages if
the client is supposed to communicate directly to the actor in the cluster.
While establishing a connection to a receptionist the ``ClusterClient`` will buffer While establishing a connection to a receptionist the ``ClusterClient`` will buffer
messages and send them when the connection is established. If the buffer is full messages and send them when the connection is established. If the buffer is full