add SslTlsSupport (ported from spray-io), see #3236
- also added TcpPipelineHandler for wrapping a pipeline - added Java & Scala docs with a complete example - test verify interop with standard blocking java SSL client and server - test is placed in akka-remote to benefit from AkkaProvider for SecureRandom; should be moved into akka-actor eventually
This commit is contained in:
parent
3569886bbe
commit
9ba8b115ec
12 changed files with 938 additions and 11 deletions
|
|
@ -58,7 +58,7 @@ public class EchoManager extends UntypedActor {
|
|||
} else if (msg instanceof Tcp.CommandFailed) {
|
||||
final CommandFailed failed = (CommandFailed) msg;
|
||||
if (failed.cmd() instanceof Bind) {
|
||||
log.warning("cannot bind to [{}]", ((Bind) failed.cmd()).endpoint());
|
||||
log.warning("cannot bind to [{}]", ((Bind) failed.cmd()).localAddress());
|
||||
getContext().stop(getSelf());
|
||||
} else {
|
||||
log.warning("unknown command failed [{}]", failed.cmd());
|
||||
|
|
|
|||
218
akka-docs/rst/java/code/docs/io/japi/SslDocTest.java
Normal file
218
akka-docs/rst/java/code/docs/io/japi/SslDocTest.java
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* Copyright (C) 2013 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.io.japi;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.actor.ActorContext;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.io.AbstractPipelineContext;
|
||||
import akka.io.HasLogging;
|
||||
import akka.io.SslTlsSupport;
|
||||
import akka.io.Tcp;
|
||||
import akka.io.Tcp.Bound;
|
||||
import akka.io.Tcp.Command;
|
||||
import akka.io.Tcp.CommandFailed;
|
||||
import akka.io.Tcp.Connected;
|
||||
import akka.io.Tcp.Event;
|
||||
import akka.io.Tcp.Received;
|
||||
import akka.io.TcpMessage;
|
||||
import akka.io.TcpPipelineHandler;
|
||||
import akka.io.TcpPipelineHandler.Init;
|
||||
import akka.io.ssl.SslTlsSupportSpec;
|
||||
import akka.testkit.AkkaSpec;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteString;
|
||||
|
||||
public class SslDocTest {
|
||||
|
||||
static
|
||||
//#client
|
||||
public class SslClient extends UntypedActor {
|
||||
final InetSocketAddress remote;
|
||||
final SSLContext sslContext;
|
||||
final ActorRef listener;
|
||||
|
||||
final LoggingAdapter log = Logging
|
||||
.getLogger(getContext().system(), getSelf());
|
||||
|
||||
public SslClient(InetSocketAddress remote, SSLContext sslContext, ActorRef listener) {
|
||||
this.remote = remote;
|
||||
this.sslContext = sslContext;
|
||||
this.listener = listener;
|
||||
|
||||
// open a connection to the remote TCP port
|
||||
Tcp.get(getContext().system()).getManager()
|
||||
.tell(TcpMessage.connect(remote), getSelf());
|
||||
}
|
||||
|
||||
class Context extends AbstractPipelineContext implements HasLogging {
|
||||
@Override
|
||||
public LoggingAdapter getLogger() {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
|
||||
Init<HasLogging, Command, Event> init = null;
|
||||
|
||||
@Override
|
||||
public void onReceive(Object msg) {
|
||||
if (msg instanceof CommandFailed) {
|
||||
getContext().stop(getSelf());
|
||||
|
||||
} else if (msg instanceof Connected) {
|
||||
// create a javax.net.ssl.SSLEngine for our peer in client mode
|
||||
final SSLEngine engine = sslContext.createSSLEngine(
|
||||
remote.getHostName(), remote.getPort());
|
||||
engine.setUseClientMode(true);
|
||||
final SslTlsSupport ssl = new SslTlsSupport(engine);
|
||||
|
||||
// set up the context for communicating with TcpPipelineHandler
|
||||
init = new Init<HasLogging, Command, Event>(ssl) {
|
||||
@Override
|
||||
public HasLogging makeContext(ActorContext ctx) {
|
||||
return new Context();
|
||||
}
|
||||
};
|
||||
// create handler for pipeline, setting ourselves as payload recipient
|
||||
final ActorRef handler = getContext().actorOf(
|
||||
TcpPipelineHandler.create(init, getSender(), getSelf()));
|
||||
|
||||
// register the SSL handler with the connection
|
||||
getSender().tell(TcpMessage.register(handler), getSelf());
|
||||
// and send a message across the SSL channel
|
||||
handler.tell(
|
||||
init.command(TcpMessage.write(ByteString.fromString("hello"))),
|
||||
getSelf());
|
||||
|
||||
} else if (msg instanceof Init.Event) {
|
||||
// unwrap TcpPipelineHandler’s event into a Tcp.Event
|
||||
final Event recv = init.event(msg);
|
||||
if (recv instanceof Received) {
|
||||
// and inform someone of the received payload
|
||||
listener.tell(((Received) recv).data().utf8String(), getSelf());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//#client
|
||||
|
||||
static
|
||||
//#server
|
||||
public class SslServer extends UntypedActor {
|
||||
final SSLContext sslContext;
|
||||
final ActorRef listener;
|
||||
|
||||
final LoggingAdapter log = Logging
|
||||
.getLogger(getContext().system(), getSelf());
|
||||
|
||||
public SslServer(SSLContext sslContext, ActorRef listener) {
|
||||
this.sslContext = sslContext;
|
||||
this.listener = listener;
|
||||
|
||||
// bind to a socket, registering ourselves as incoming connection handler
|
||||
Tcp.get(getContext().system()).getManager().tell(
|
||||
TcpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0), 100),
|
||||
getSelf());
|
||||
}
|
||||
|
||||
class Context extends AbstractPipelineContext implements HasLogging {
|
||||
@Override
|
||||
public LoggingAdapter getLogger() {
|
||||
return log;
|
||||
}
|
||||
}
|
||||
|
||||
Init<HasLogging, Command, Event> init = null;
|
||||
|
||||
@Override
|
||||
public void onReceive(Object msg) {
|
||||
if (msg instanceof CommandFailed) {
|
||||
getContext().stop(getSelf());
|
||||
|
||||
} else if (msg instanceof Bound) {
|
||||
listener.tell(msg, getSelf());
|
||||
|
||||
} else if (msg instanceof Connected) {
|
||||
// create a javax.net.ssl.SSLEngine for our peer in server mode
|
||||
final InetSocketAddress remote = ((Connected) msg).remoteAddress();
|
||||
final SSLEngine engine = sslContext.createSSLEngine(
|
||||
remote.getHostName(), remote.getPort());
|
||||
engine.setUseClientMode(false);
|
||||
final SslTlsSupport ssl = new SslTlsSupport(engine);
|
||||
|
||||
// set up the context for communicating with TcpPipelineHandler
|
||||
init = new Init<HasLogging, Command, Event>(ssl) {
|
||||
@Override
|
||||
public HasLogging makeContext(ActorContext ctx) {
|
||||
return new Context();
|
||||
}
|
||||
};
|
||||
// create handler for pipeline, setting ourselves as payload recipient
|
||||
final ActorRef handler = getContext().actorOf(
|
||||
TcpPipelineHandler.create(init, getSender(), getSelf()));
|
||||
|
||||
// register the SSL handler with the connection
|
||||
getSender().tell(TcpMessage.register(handler), getSelf());
|
||||
|
||||
} else if (msg instanceof Init.Event) {
|
||||
// unwrap TcpPipelineHandler’s event to get a Tcp.Event
|
||||
final Event recv = init.event(msg);
|
||||
if (recv instanceof Received) {
|
||||
// inform someone of the received message
|
||||
listener.tell(((Received) recv).data().utf8String(), getSelf());
|
||||
// and reply (sender is the SSL handler created above)
|
||||
getSender().tell(init.command(
|
||||
TcpMessage.write(ByteString.fromString("world"))), getSelf());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//#server
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("IODocTest", AkkaSpec.testConf());
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void teardown() {
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateSslClient() {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final SSLContext ctx = SslTlsSupportSpec.createSslContext("/keystore", "/truststore", "changeme");
|
||||
|
||||
final ActorRef server = system.actorOf(Props.create(SslServer.class, ctx, getRef()));
|
||||
final Bound bound = expectMsgClass(Bound.class);
|
||||
assert getLastSender() == server;
|
||||
|
||||
final ActorRef client = system.actorOf(Props.create(SslClient.class, bound.localAddress(), ctx, getRef()));
|
||||
expectMsgEquals("hello");
|
||||
assert getLastSender() == server;
|
||||
expectMsgEquals("world");
|
||||
assert getLastSender() == client;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -582,6 +582,53 @@ The helper functions are very similar to the ACK-based case:
|
|||
|
||||
.. includecode:: code/docs/io/japi/EchoHandler.java#helpers
|
||||
|
||||
Usage Example: TcpPipelineHandler and SSL
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This example shows the different parts described above working together. Let us
|
||||
first look at the SSL server:
|
||||
|
||||
.. includecode:: code/docs/io/japi/SslDocTest.java#server
|
||||
|
||||
The actor above binds to a local port and registers itself as the handler for
|
||||
new connections. When a new connection comes in it will create a
|
||||
:class:`javax.net.ssl.SSLEngine` (details not shown here since they vary wildly
|
||||
for different setups, please refer to the JDK documentation) and wrap that in
|
||||
an :class:`SslTlsSupport` pipeline stage (which is included in ``akka-actor``).
|
||||
This single-stage pipeline will be driven by a :class:`TcpPipelineHandler`
|
||||
actor which is also included in ``akka-actor``. In order to capture the generic
|
||||
command and event types consumed and emitted by that actor we need to create a
|
||||
wrapper—the nested :class:`Init` class—which also provides the
|
||||
:meth:`makeContext` method for creating the pipeline context needed by the
|
||||
supplied pipeline. With those things bundled up all that remains is creating a
|
||||
:class:`TcpPipelineHandler` and registering that one as the recipient of
|
||||
inbound traffic from the TCP connection.
|
||||
|
||||
Since we instructed that handler actor to send any events which are emitted by
|
||||
the SSL pipeline to ourselves, we can then just wait for the reception of the
|
||||
decrypted payload messages, compute a response—just ``"world"`` in this
|
||||
case—and reply by sending back a ``Tcp.Write``. It should be noted that
|
||||
communication with the handler wraps commands and events in the inner types of
|
||||
the ``init`` object in order to keep things well separated. To ease handling of
|
||||
such path-dependent types there exist two helper methods, namely
|
||||
:class:`Init.command` for creating a command and :class:`Init.event` for
|
||||
unwrapping an event.
|
||||
|
||||
.. warning::
|
||||
|
||||
The :class:`TcpPipelineHandler` does currently not handle back-pressure from
|
||||
the TCP socket, i.e. it will just lose data when the kernel buffer
|
||||
overflows. This will be fixed before Akka 2.2 final.
|
||||
|
||||
Looking at the client side we see that not much needs to be changed:
|
||||
|
||||
.. includecode:: code/docs/io/japi/SslDocTest.java#client
|
||||
|
||||
Once the connection is established we again create a
|
||||
:class:`TcpPipelineHandler` wrapping an :class:`SslTlsSupport` (in client mode)
|
||||
and register that as the recipient of inbound traffic and ourselves as
|
||||
recipient for the decrypted payload data. The we send a greeting to the server
|
||||
and forward any replies to some ``listener`` actor.
|
||||
|
||||
Using UDP
|
||||
---------
|
||||
|
|
|
|||
|
|
@ -601,6 +601,40 @@ The helper functions are very similar to the ACK-based case:
|
|||
|
||||
.. includecode:: code/docs/io/EchoServer.scala#helpers
|
||||
|
||||
Usage Example: TcpPipelineHandler and SSL
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This example shows the different parts described above working together:
|
||||
|
||||
.. includecode:: ../../../akka-remote/src/test/scala/akka/io/ssl/SslTlsSupportSpec.scala#server
|
||||
|
||||
The actor above is meant to be registered as the inbound connection handler for
|
||||
a listen socket. When a new connection comes in it will create a
|
||||
:class:`javax.net.ssl.SSLEngine` (details not shown here since they vary wildly
|
||||
for different setups, please refer to the JDK documentation) and wrap that in
|
||||
an :class:`SslTlsSupport` pipeline stage (which is included in ``akka-actor``).
|
||||
This single-stage pipeline will be driven by a :class:`TcpPipelineHandler`
|
||||
actor which is also included in ``akka-actor``. In order to capture the generic
|
||||
command and event types consumed and emitted by that actor we need to create a
|
||||
wrapper—the nested :class:`Init` class—which also provides the
|
||||
:meth:`makeContext` method for creating the pipeline context needed by the
|
||||
supplied pipeline. With those things bundled up all that remains is creating a
|
||||
:class:`TcpPipelineHandler` and registering that one as the recipient of
|
||||
inbound traffic from the TCP connection.
|
||||
|
||||
Since we instructed that handler actor to send any events which are emitted by
|
||||
the SSL pipeline to ourselves, we can then just switch behavior to receive the
|
||||
decrypted payload message, compute a response and reply by sending back a
|
||||
``Tcp.Write``. It should be noted that communication with the handler wraps
|
||||
commands and events in the inner types of the ``init`` object in order to keep
|
||||
things well separated.
|
||||
|
||||
.. warning::
|
||||
|
||||
The :class:`TcpPipelineHandler` does currently not handle back-pressure from
|
||||
the TCP socket, i.e. it will just lose data when the kernel buffer
|
||||
overflows. This will be fixed before Akka 2.2 final.
|
||||
|
||||
Using UDP
|
||||
---------
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue