+doc #19429 initial merge of docs-dev and docs
This commit is contained in:
parent
be0c8af4c0
commit
5a18d43435
501 changed files with 9876 additions and 3681 deletions
639
akka-docs/rst/java/code/docs/actor/ActorDocTest.java
Normal file
639
akka-docs/rst/java/code/docs/actor/ActorDocTest.java
Normal file
|
|
@ -0,0 +1,639 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.testkit.ErrorFilter;
|
||||
import akka.testkit.EventFilter;
|
||||
import akka.testkit.TestEvent;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import scala.PartialFunction;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import static docs.actor.Messages.Swap.Swap;
|
||||
import static docs.actor.Messages.*;
|
||||
import static akka.japi.Util.immutableSeq;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
//#import-props
|
||||
import akka.actor.Props;
|
||||
//#import-props
|
||||
//#import-actorRef
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
//#import-actorRef
|
||||
//#import-identify
|
||||
import akka.actor.ActorIdentity;
|
||||
import akka.actor.ActorSelection;
|
||||
import akka.actor.Identify;
|
||||
//#import-identify
|
||||
//#import-graceFulStop
|
||||
import akka.pattern.AskTimeoutException;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.Future;
|
||||
import static akka.pattern.Patterns.gracefulStop;
|
||||
//#import-graceFulStop
|
||||
|
||||
public class ActorDocTest {
|
||||
|
||||
public static Config config = ConfigFactory.parseString(
|
||||
"akka {\n" +
|
||||
" loggers = [\"akka.testkit.TestEventListener\"]\n" +
|
||||
" loglevel = \"WARNING\"\n" +
|
||||
" stdout-loglevel = \"WARNING\"\n" +
|
||||
"}\n"
|
||||
);
|
||||
|
||||
static ActorSystem system = null;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
system = ActorSystem.create("ActorDocTest", config);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
system.shutdown();
|
||||
system.awaitTermination(Duration.create("5 seconds"));
|
||||
}
|
||||
|
||||
static
|
||||
//#context-actorOf
|
||||
public class FirstActor extends AbstractActor {
|
||||
final ActorRef child = context().actorOf(Props.create(MyActor.class), "myChild");
|
||||
//#plus-some-behavior
|
||||
public FirstActor() {
|
||||
receive(ReceiveBuilder.
|
||||
matchAny(x -> {
|
||||
sender().tell(x, self());
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
//#plus-some-behavior
|
||||
}
|
||||
//#context-actorOf
|
||||
|
||||
static public abstract class SomeActor extends AbstractActor {
|
||||
//#receive-constructor
|
||||
public SomeActor() {
|
||||
receive(ReceiveBuilder.
|
||||
//#and-some-behavior
|
||||
match(String.class, s -> System.out.println(s.toLowerCase())).
|
||||
//#and-some-behavior
|
||||
build());
|
||||
}
|
||||
//#receive-constructor
|
||||
@Override
|
||||
//#receive
|
||||
public abstract PartialFunction<Object, BoxedUnit> receive();
|
||||
//#receive
|
||||
}
|
||||
|
||||
static public class ActorWithArgs extends AbstractActor {
|
||||
private final String args;
|
||||
|
||||
ActorWithArgs(String args) {
|
||||
this.args = args;
|
||||
receive(ReceiveBuilder.
|
||||
matchAny(x -> { }).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
//#props-factory
|
||||
public class DemoActor extends AbstractActor {
|
||||
/**
|
||||
* Create Props for an actor of this type.
|
||||
* @param magicNumber The magic number to be passed to this actor’s constructor.
|
||||
* @return a Props for creating this actor, which can then be further configured
|
||||
* (e.g. calling `.withDispatcher()` on it)
|
||||
*/
|
||||
static Props props(Integer magicNumber) {
|
||||
// You need to specify the actual type of the returned actor
|
||||
// since Java 8 lambdas have some runtime type information erased
|
||||
return Props.create(DemoActor.class, () -> new DemoActor(magicNumber));
|
||||
}
|
||||
|
||||
private final Integer magicNumber;
|
||||
|
||||
DemoActor(Integer magicNumber) {
|
||||
this.magicNumber = magicNumber;
|
||||
receive(ReceiveBuilder.
|
||||
match(Integer.class, i -> {
|
||||
sender().tell(i + magicNumber, self());
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//#props-factory
|
||||
static
|
||||
//#props-factory
|
||||
public class SomeOtherActor extends AbstractActor {
|
||||
// Props(new DemoActor(42)) would not be safe
|
||||
ActorRef demoActor = context().actorOf(DemoActor.props(42), "demo");
|
||||
// ...
|
||||
//#props-factory
|
||||
public SomeOtherActor() {
|
||||
receive(emptyBehavior());
|
||||
}
|
||||
//#props-factory
|
||||
}
|
||||
//#props-factory
|
||||
|
||||
public static class Hook extends AbstractActor {
|
||||
ActorRef target = null;
|
||||
public Hook() {
|
||||
receive(emptyBehavior());
|
||||
}
|
||||
//#preStart
|
||||
@Override
|
||||
public void preStart() {
|
||||
target = context().actorOf(Props.create(MyActor.class, "target"));
|
||||
}
|
||||
//#preStart
|
||||
//#postStop
|
||||
@Override
|
||||
public void postStop() {
|
||||
//#clean-up-some-resources
|
||||
final String message = "stopped";
|
||||
//#tell
|
||||
// don’t forget to think about who is the sender (2nd argument)
|
||||
target.tell(message, self());
|
||||
//#tell
|
||||
final Object result = "";
|
||||
//#forward
|
||||
target.forward(result, context());
|
||||
//#forward
|
||||
target = null;
|
||||
//#clean-up-some-resources
|
||||
}
|
||||
//#postStop
|
||||
|
||||
// compilation test only
|
||||
public void compileSelections() {
|
||||
//#selection-local
|
||||
// will look up this absolute path
|
||||
context().actorSelection("/user/serviceA/actor");
|
||||
// will look up sibling beneath same supervisor
|
||||
context().actorSelection("../joe");
|
||||
//#selection-local
|
||||
|
||||
//#selection-wildcard
|
||||
// will look all children to serviceB with names starting with worker
|
||||
context().actorSelection("/user/serviceB/worker*");
|
||||
// will look up all siblings beneath same supervisor
|
||||
context().actorSelection("../*");
|
||||
//#selection-wildcard
|
||||
|
||||
//#selection-remote
|
||||
context().actorSelection("akka.tcp://app@otherhost:1234/user/serviceB");
|
||||
//#selection-remote
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReplyException extends AbstractActor {
|
||||
public ReplyException() {
|
||||
receive(ReceiveBuilder.
|
||||
matchAny(x -> {
|
||||
//#reply-exception
|
||||
try {
|
||||
String result = operation();
|
||||
sender().tell(result, self());
|
||||
} catch (Exception e) {
|
||||
sender().tell(new akka.actor.Status.Failure(e), self());
|
||||
throw e;
|
||||
}
|
||||
//#reply-exception
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
|
||||
private String operation() {
|
||||
return "Hi";
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
//#gracefulStop-actor
|
||||
public class Manager extends AbstractActor {
|
||||
private static enum Shutdown {
|
||||
Shutdown
|
||||
}
|
||||
public static final Shutdown SHUTDOWN = Shutdown.Shutdown;
|
||||
|
||||
private ActorRef worker =
|
||||
context().watch(context().actorOf(Props.create(Cruncher.class), "worker"));
|
||||
|
||||
public Manager() {
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals("job", s -> {
|
||||
worker.tell("crunch", self());
|
||||
}).
|
||||
matchEquals(SHUTDOWN, x -> {
|
||||
worker.tell(PoisonPill.getInstance(), self());
|
||||
context().become(shuttingDown);
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
|
||||
public PartialFunction<Object, BoxedUnit> shuttingDown =
|
||||
ReceiveBuilder.
|
||||
matchEquals("job", s -> {
|
||||
sender().tell("service unavailable, shutting down", self());
|
||||
}).
|
||||
match(Terminated.class, t -> t.actor().equals(worker), t -> {
|
||||
context().stop(self());
|
||||
}).build();
|
||||
}
|
||||
//#gracefulStop-actor
|
||||
|
||||
@Test
|
||||
public void usePatternsGracefulStop() throws Exception {
|
||||
ActorRef actorRef = system.actorOf(Props.create(Manager.class));
|
||||
//#gracefulStop
|
||||
try {
|
||||
Future<Boolean> stopped =
|
||||
gracefulStop(actorRef, Duration.create(5, TimeUnit.SECONDS), Manager.SHUTDOWN);
|
||||
Await.result(stopped, Duration.create(6, TimeUnit.SECONDS));
|
||||
// the actor has been stopped
|
||||
} catch (AskTimeoutException e) {
|
||||
// the actor wasn't stopped within 5 seconds
|
||||
}
|
||||
//#gracefulStop
|
||||
}
|
||||
|
||||
|
||||
public static class Cruncher extends AbstractActor {
|
||||
public Cruncher() {
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals("crunch", s -> { }).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
//#swapper
|
||||
public class Swapper extends AbstractLoggingActor {
|
||||
public Swapper() {
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals(Swap, s -> {
|
||||
log().info("Hi");
|
||||
context().become(ReceiveBuilder.
|
||||
matchEquals(Swap, x -> {
|
||||
log().info("Ho");
|
||||
context().unbecome(); // resets the latest 'become' (just for fun)
|
||||
}).build(), false); // push on top instead of replace
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//#swapper
|
||||
static
|
||||
//#swapper
|
||||
public class SwapperApp {
|
||||
public static void main(String[] args) {
|
||||
ActorSystem system = ActorSystem.create("SwapperSystem");
|
||||
ActorRef swapper = system.actorOf(Props.create(Swapper.class), "swapper");
|
||||
swapper.tell(Swap, ActorRef.noSender()); // logs Hi
|
||||
swapper.tell(Swap, ActorRef.noSender()); // logs Ho
|
||||
swapper.tell(Swap, ActorRef.noSender()); // logs Hi
|
||||
swapper.tell(Swap, ActorRef.noSender()); // logs Ho
|
||||
swapper.tell(Swap, ActorRef.noSender()); // logs Hi
|
||||
swapper.tell(Swap, ActorRef.noSender()); // logs Ho
|
||||
system.shutdown();
|
||||
}
|
||||
}
|
||||
//#swapper
|
||||
|
||||
|
||||
@Test
|
||||
public void creatingActorWithSystemActorOf() {
|
||||
//#system-actorOf
|
||||
// ActorSystem is a heavy object: create only one per application
|
||||
final ActorSystem system = ActorSystem.create("MySystem", config);
|
||||
final ActorRef myActor = system.actorOf(Props.create(MyActor.class), "myactor");
|
||||
//#system-actorOf
|
||||
try {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
myActor.tell("hello", getRef());
|
||||
expectMsgEquals("hello");
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void creatingPropsConfig() {
|
||||
//#creating-props
|
||||
Props props1 = Props.create(MyActor.class);
|
||||
Props props2 = Props.create(ActorWithArgs.class,
|
||||
() -> new ActorWithArgs("arg")); // careful, see below
|
||||
Props props3 = Props.create(ActorWithArgs.class, "arg");
|
||||
//#creating-props
|
||||
|
||||
//#creating-props-deprecated
|
||||
// NOT RECOMMENDED within another actor:
|
||||
// encourages to close over enclosing class
|
||||
Props props7 = Props.create(ActorWithArgs.class,
|
||||
() -> new ActorWithArgs("arg"));
|
||||
//#creating-props-deprecated
|
||||
}
|
||||
|
||||
@Test(expected=IllegalArgumentException.class)
|
||||
public void creatingPropsIllegal() {
|
||||
//#creating-props-illegal
|
||||
// This will throw an IllegalArgumentException since some runtime
|
||||
// type information of the lambda is erased.
|
||||
// Use Props.create(actorClass, Creator) instead.
|
||||
Props props = Props.create(() -> new ActorWithArgs("arg"));
|
||||
//#creating-props-illegal
|
||||
}
|
||||
|
||||
static
|
||||
//#receive-timeout
|
||||
public class ReceiveTimeoutActor extends AbstractActor {
|
||||
//#receive-timeout
|
||||
ActorRef target = context().system().deadLetters();
|
||||
//#receive-timeout
|
||||
public ReceiveTimeoutActor() {
|
||||
// To set an initial delay
|
||||
context().setReceiveTimeout(Duration.create("10 seconds"));
|
||||
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals("Hello", s -> {
|
||||
// To set in a response to a message
|
||||
context().setReceiveTimeout(Duration.create("1 second"));
|
||||
//#receive-timeout
|
||||
target = sender();
|
||||
target.tell("Hello world", self());
|
||||
//#receive-timeout
|
||||
}).
|
||||
match(ReceiveTimeout.class, r -> {
|
||||
// To turn it off
|
||||
context().setReceiveTimeout(Duration.Undefined());
|
||||
//#receive-timeout
|
||||
target.tell("timeout", self());
|
||||
//#receive-timeout
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
//#receive-timeout
|
||||
|
||||
@Test
|
||||
public void using_receiveTimeout() {
|
||||
final ActorRef myActor = system.actorOf(Props.create(ReceiveTimeoutActor.class));
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
myActor.tell("Hello", getRef());
|
||||
expectMsgEquals("Hello world");
|
||||
expectMsgEquals("timeout");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static
|
||||
//#hot-swap-actor
|
||||
public class HotSwapActor extends AbstractActor {
|
||||
private PartialFunction<Object, BoxedUnit> angry;
|
||||
private PartialFunction<Object, BoxedUnit> happy;
|
||||
|
||||
public HotSwapActor() {
|
||||
angry =
|
||||
ReceiveBuilder.
|
||||
matchEquals("foo", s -> {
|
||||
sender().tell("I am already angry?", self());
|
||||
}).
|
||||
matchEquals("bar", s -> {
|
||||
context().become(happy);
|
||||
}).build();
|
||||
|
||||
happy = ReceiveBuilder.
|
||||
matchEquals("bar", s -> {
|
||||
sender().tell("I am already happy :-)", self());
|
||||
}).
|
||||
matchEquals("foo", s -> {
|
||||
context().become(angry);
|
||||
}).build();
|
||||
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals("foo", s -> {
|
||||
context().become(angry);
|
||||
}).
|
||||
matchEquals("bar", s -> {
|
||||
context().become(happy);
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
//#hot-swap-actor
|
||||
|
||||
@Test
|
||||
public void using_hot_swap() {
|
||||
final ActorRef actor = system.actorOf(Props.create(HotSwapActor.class), "hot");
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
actor.tell("foo", getRef());
|
||||
actor.tell("foo", getRef());
|
||||
expectMsgEquals("I am already angry?");
|
||||
actor.tell("bar", getRef());
|
||||
actor.tell("bar", getRef());
|
||||
expectMsgEquals("I am already happy :-)");
|
||||
actor.tell("foo", getRef());
|
||||
actor.tell("foo", getRef());
|
||||
expectMsgEquals("I am already angry?");
|
||||
expectNoMsg(Duration.create(1, TimeUnit.SECONDS));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
//#stash
|
||||
public class ActorWithProtocol extends AbstractActorWithStash {
|
||||
public ActorWithProtocol() {
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals("open", s -> {
|
||||
context().become(ReceiveBuilder.
|
||||
matchEquals("write", ws -> { /* do writing */ }).
|
||||
matchEquals("close", cs -> {
|
||||
unstashAll();
|
||||
context().unbecome();
|
||||
}).
|
||||
matchAny(msg -> stash()).build(), false);
|
||||
}).
|
||||
matchAny(msg -> stash()).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
//#stash
|
||||
|
||||
@Test
|
||||
public void using_Stash() {
|
||||
final ActorRef actor = system.actorOf(Props.create(ActorWithProtocol.class), "stash");
|
||||
}
|
||||
|
||||
static
|
||||
//#watch
|
||||
public class WatchActor extends AbstractActor {
|
||||
private final ActorRef child = context().actorOf(Props.empty(), "target");
|
||||
private ActorRef lastSender = system.deadLetters();
|
||||
|
||||
public WatchActor() {
|
||||
context().watch(child); // <-- this is the only call needed for registration
|
||||
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals("kill", s -> {
|
||||
context().stop(child);
|
||||
lastSender = sender();
|
||||
}).
|
||||
match(Terminated.class, t -> t.actor().equals(child), t -> {
|
||||
lastSender.tell("finished", self());
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
//#watch
|
||||
|
||||
@Test
|
||||
public void using_watch() {
|
||||
ActorRef actor = system.actorOf(Props.create(WatchActor.class));
|
||||
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
actor.tell("kill", getRef());
|
||||
expectMsgEquals("finished");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static
|
||||
//#identify
|
||||
public class Follower extends AbstractActor {
|
||||
final Integer identifyId = 1;
|
||||
|
||||
public Follower(){
|
||||
ActorSelection selection = context().actorSelection("/user/another");
|
||||
selection.tell(new Identify(identifyId), self());
|
||||
|
||||
receive(ReceiveBuilder.
|
||||
match(ActorIdentity.class, id -> id.getRef() != null, id -> {
|
||||
ActorRef ref = id.getRef();
|
||||
context().watch(ref);
|
||||
context().become(active(ref));
|
||||
}).
|
||||
match(ActorIdentity.class, id -> id.getRef() == null, id -> {
|
||||
context().stop(self());
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
|
||||
final PartialFunction<Object, BoxedUnit> active(final ActorRef another) {
|
||||
return ReceiveBuilder.
|
||||
match(Terminated.class, t -> t.actor().equals(another), t -> {
|
||||
context().stop(self());
|
||||
}).build();
|
||||
}
|
||||
}
|
||||
//#identify
|
||||
|
||||
@Test
|
||||
public void using_Identify() {
|
||||
ActorRef a = system.actorOf(Props.empty());
|
||||
ActorRef b = system.actorOf(Props.create(Follower.class));
|
||||
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
watch(b);
|
||||
system.stop(a);
|
||||
assertEquals(expectMsgClass(Duration.create(2, TimeUnit.SECONDS), Terminated.class).actor(), b);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static class NoReceiveActor extends AbstractActor {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noReceiveActor() {
|
||||
EventFilter ex1 = new ErrorFilter(ActorInitializationException.class);
|
||||
EventFilter[] ignoreExceptions = { ex1 };
|
||||
try {
|
||||
system.eventStream().publish(new TestEvent.Mute(immutableSeq(ignoreExceptions)));
|
||||
new JavaTestKit(system) {{
|
||||
final ActorRef victim = new EventFilter<ActorRef>(ActorInitializationException.class) {
|
||||
protected ActorRef run() {
|
||||
return system.actorOf(Props.create(NoReceiveActor.class), "victim");
|
||||
}
|
||||
}.message("Actor behavior has not been set with receive(...)").occurrences(1).exec();
|
||||
|
||||
assertEquals(true, victim.isTerminated());
|
||||
}};
|
||||
} finally {
|
||||
system.eventStream().publish(new TestEvent.UnMute(immutableSeq(ignoreExceptions)));
|
||||
}
|
||||
}
|
||||
|
||||
public static class MultipleReceiveActor extends AbstractActor {
|
||||
public MultipleReceiveActor() {
|
||||
receive(ReceiveBuilder.
|
||||
match(String.class, s1 -> s1.toLowerCase().equals("become"), s1 -> {
|
||||
sender().tell(s1.toUpperCase(), self());
|
||||
receive(ReceiveBuilder.
|
||||
match(String.class, s2 -> {
|
||||
sender().tell(s2.toLowerCase(), self());
|
||||
}).build()
|
||||
);
|
||||
}).
|
||||
match(String.class, s1 -> {
|
||||
sender().tell(s1.toUpperCase(), self());
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleReceiveActor() {
|
||||
EventFilter ex1 = new ErrorFilter(IllegalActorStateException.class);
|
||||
EventFilter[] ignoreExceptions = { ex1 };
|
||||
try {
|
||||
system.eventStream().publish(new TestEvent.Mute(immutableSeq(ignoreExceptions)));
|
||||
new JavaTestKit(system) {{
|
||||
new EventFilter<Boolean>(IllegalActorStateException.class) {
|
||||
protected Boolean run() {
|
||||
ActorRef victim = system.actorOf(Props.create(MultipleReceiveActor.class), "victim2");
|
||||
victim.tell("Foo", getRef());
|
||||
expectMsgEquals("FOO");
|
||||
victim.tell("bEcoMe", getRef());
|
||||
expectMsgEquals("BECOME");
|
||||
victim.tell("Foo", getRef());
|
||||
// if it's upper case, then the actor was restarted
|
||||
expectMsgEquals("FOO");
|
||||
return true;
|
||||
}
|
||||
}.message("Actor behavior has already been set with receive(...), " +
|
||||
"use context().become(...) to change it later").occurrences(1).exec();
|
||||
}};
|
||||
} finally {
|
||||
system.eventStream().publish(new TestEvent.UnMute(immutableSeq(ignoreExceptions)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
204
akka-docs/rst/java/code/docs/actor/FaultHandlingTestJava8.java
Normal file
204
akka-docs/rst/java/code/docs/actor/FaultHandlingTestJava8.java
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor;
|
||||
|
||||
//#testkit
|
||||
import akka.actor.*;
|
||||
|
||||
import static akka.actor.SupervisorStrategy.resume;
|
||||
import static akka.actor.SupervisorStrategy.restart;
|
||||
import static akka.actor.SupervisorStrategy.stop;
|
||||
import static akka.actor.SupervisorStrategy.escalate;
|
||||
import akka.japi.pf.DeciderBuilder;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import scala.PartialFunction;
|
||||
import scala.concurrent.Await;
|
||||
import static akka.pattern.Patterns.ask;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import akka.testkit.TestProbe;
|
||||
|
||||
//#testkit
|
||||
import akka.testkit.ErrorFilter;
|
||||
import akka.testkit.EventFilter;
|
||||
import akka.testkit.TestEvent;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static akka.japi.Util.immutableSeq;
|
||||
import scala.Option;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.AfterClass;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
//#testkit
|
||||
public class FaultHandlingTestJava8 {
|
||||
//#testkit
|
||||
|
||||
public static Config config = ConfigFactory.parseString(
|
||||
"akka {\n" +
|
||||
" loggers = [\"akka.testkit.TestEventListener\"]\n" +
|
||||
" loglevel = \"WARNING\"\n" +
|
||||
" stdout-loglevel = \"WARNING\"\n" +
|
||||
"}\n");
|
||||
|
||||
static
|
||||
//#supervisor
|
||||
public class Supervisor extends AbstractActor {
|
||||
|
||||
//#strategy
|
||||
private static SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(10, Duration.create("1 minute"), DeciderBuilder.
|
||||
match(ArithmeticException.class, e -> resume()).
|
||||
match(NullPointerException.class, e -> restart()).
|
||||
match(IllegalArgumentException.class, e -> stop()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
//#strategy
|
||||
|
||||
public Supervisor() {
|
||||
receive(ReceiveBuilder.
|
||||
match(Props.class, props -> {
|
||||
sender().tell(context().actorOf(props), self());
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//#supervisor
|
||||
|
||||
static
|
||||
//#supervisor2
|
||||
public class Supervisor2 extends AbstractActor {
|
||||
|
||||
//#strategy2
|
||||
private static SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(10, Duration.create("1 minute"), DeciderBuilder.
|
||||
match(ArithmeticException.class, e -> resume()).
|
||||
match(NullPointerException.class, e -> restart()).
|
||||
match(IllegalArgumentException.class, e -> stop()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
//#strategy2
|
||||
|
||||
public Supervisor2() {
|
||||
receive(ReceiveBuilder.
|
||||
match(Props.class, props -> {
|
||||
sender().tell(context().actorOf(props), self());
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preRestart(Throwable cause, Option<Object> msg) {
|
||||
// do not kill all children, which is the default here
|
||||
}
|
||||
}
|
||||
|
||||
//#supervisor2
|
||||
|
||||
static
|
||||
//#child
|
||||
public class Child extends AbstractActor {
|
||||
int state = 0;
|
||||
|
||||
public Child() {
|
||||
receive(ReceiveBuilder.
|
||||
match(Exception.class, exception -> { throw exception; }).
|
||||
match(Integer.class, i -> state = i).
|
||||
matchEquals("get", s -> sender().tell(state, self())).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//#child
|
||||
|
||||
//#testkit
|
||||
static ActorSystem system;
|
||||
Duration timeout = Duration.create(5, SECONDS);
|
||||
|
||||
@BeforeClass
|
||||
public static void start() {
|
||||
system = ActorSystem.create("FaultHandlingTest", config);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void cleanup() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mustEmploySupervisorStrategy() throws Exception {
|
||||
// code here
|
||||
//#testkit
|
||||
EventFilter ex1 = new ErrorFilter(ArithmeticException.class);
|
||||
EventFilter ex2 = new ErrorFilter(NullPointerException.class);
|
||||
EventFilter ex3 = new ErrorFilter(IllegalArgumentException.class);
|
||||
EventFilter ex4 = new ErrorFilter(Exception.class);
|
||||
EventFilter[] ignoreExceptions = { ex1, ex2, ex3, ex4 };
|
||||
system.eventStream().publish(new TestEvent.Mute(immutableSeq(ignoreExceptions)));
|
||||
|
||||
//#create
|
||||
Props superprops = Props.create(Supervisor.class);
|
||||
ActorRef supervisor = system.actorOf(superprops, "supervisor");
|
||||
ActorRef child = (ActorRef) Await.result(ask(supervisor,
|
||||
Props.create(Child.class), 5000), timeout);
|
||||
//#create
|
||||
|
||||
//#resume
|
||||
child.tell(42, ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(42);
|
||||
child.tell(new ArithmeticException(), ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(42);
|
||||
//#resume
|
||||
|
||||
//#restart
|
||||
child.tell(new NullPointerException(), ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(0);
|
||||
//#restart
|
||||
|
||||
//#stop
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
probe.watch(child);
|
||||
child.tell(new IllegalArgumentException(), ActorRef.noSender());
|
||||
probe.expectMsgClass(Terminated.class);
|
||||
//#stop
|
||||
|
||||
//#escalate-kill
|
||||
child = (ActorRef) Await.result(ask(supervisor,
|
||||
Props.create(Child.class), 5000), timeout);
|
||||
probe.watch(child);
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(0);
|
||||
child.tell(new Exception(), ActorRef.noSender());
|
||||
probe.expectMsgClass(Terminated.class);
|
||||
//#escalate-kill
|
||||
|
||||
//#escalate-restart
|
||||
superprops = Props.create(Supervisor2.class);
|
||||
supervisor = system.actorOf(superprops);
|
||||
child = (ActorRef) Await.result(ask(supervisor,
|
||||
Props.create(Child.class), 5000), timeout);
|
||||
child.tell(23, ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(23);
|
||||
child.tell(new Exception(), ActorRef.noSender());
|
||||
assert Await.result(ask(child, "get", 5000), timeout).equals(0);
|
||||
//#escalate-restart
|
||||
//#testkit
|
||||
}
|
||||
|
||||
}
|
||||
//#testkit
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.PartialFunction;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class InitializationDocTest {
|
||||
|
||||
static ActorSystem system = null;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
system = ActorSystem.create("InitializationDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() {
|
||||
system.shutdown();
|
||||
system.awaitTermination(Duration.create("5 seconds"));
|
||||
}
|
||||
|
||||
public static class MessageInitExample extends AbstractActor {
|
||||
private String initializeMe = null;
|
||||
|
||||
public MessageInitExample() {
|
||||
//#messageInit
|
||||
receive(ReceiveBuilder.
|
||||
matchEquals("init", m1 -> {
|
||||
initializeMe = "Up and running";
|
||||
context().become(ReceiveBuilder.
|
||||
matchEquals("U OK?", m2 -> {
|
||||
sender().tell(initializeMe, self());
|
||||
}).build());
|
||||
}).build()
|
||||
//#messageInit
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIt() {
|
||||
|
||||
new JavaTestKit(system) {{
|
||||
ActorRef testactor = system.actorOf(Props.create(MessageInitExample.class), "testactor");
|
||||
String msg = "U OK?";
|
||||
|
||||
testactor.tell(msg, getRef());
|
||||
expectNoMsg(Duration.create(1, TimeUnit.SECONDS));
|
||||
|
||||
testactor.tell("init", getRef());
|
||||
testactor.tell(msg, getRef());
|
||||
expectMsgEquals("Up and running");
|
||||
}};
|
||||
}
|
||||
}
|
||||
149
akka-docs/rst/java/code/docs/actor/Messages.java
Normal file
149
akka-docs/rst/java/code/docs/actor/Messages.java
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class Messages {
|
||||
static
|
||||
//#immutable-message
|
||||
public class ImmutableMessage {
|
||||
private final int sequenceNumber;
|
||||
private final List<String> values;
|
||||
|
||||
public ImmutableMessage(int sequenceNumber, List<String> values) {
|
||||
this.sequenceNumber = sequenceNumber;
|
||||
this.values = Collections.unmodifiableList(new ArrayList<String>(values));
|
||||
}
|
||||
|
||||
public int getSequenceNumber() {
|
||||
return sequenceNumber;
|
||||
}
|
||||
|
||||
public List<String> getValues() {
|
||||
return values;
|
||||
}
|
||||
}
|
||||
//#immutable-message
|
||||
|
||||
public static class DoIt {
|
||||
private final ImmutableMessage msg;
|
||||
|
||||
DoIt(ImmutableMessage msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public ImmutableMessage getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
DoIt doIt = (DoIt) o;
|
||||
|
||||
if (!msg.equals(doIt.msg)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return msg.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DoIt{" +
|
||||
"msg=" + msg +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static class Message {
|
||||
final String str;
|
||||
|
||||
Message(String str) {
|
||||
this.str = str;
|
||||
}
|
||||
|
||||
public String getStr() {
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Message message = (Message) o;
|
||||
|
||||
if (!str.equals(message.str)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return str.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Message{" +
|
||||
"str='" + str + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
public static enum Swap {
|
||||
Swap
|
||||
}
|
||||
|
||||
public static class Result {
|
||||
final String x;
|
||||
final String s;
|
||||
|
||||
public Result(String x, String s) {
|
||||
this.x = x;
|
||||
this.s = s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((s == null) ? 0 : s.hashCode());
|
||||
result = prime * result + ((x == null) ? 0 : x.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Result other = (Result) obj;
|
||||
if (s == null) {
|
||||
if (other.s != null)
|
||||
return false;
|
||||
} else if (!s.equals(other.s))
|
||||
return false;
|
||||
if (x == null) {
|
||||
if (other.x != null)
|
||||
return false;
|
||||
} else if (!x.equals(other.x))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
akka-docs/rst/java/code/docs/actor/MyActor.java
Normal file
33
akka-docs/rst/java/code/docs/actor/MyActor.java
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor;
|
||||
|
||||
//#imports
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
|
||||
//#imports
|
||||
|
||||
//#my-actor
|
||||
public class MyActor extends AbstractActor {
|
||||
private final LoggingAdapter log = Logging.getLogger(context().system(), this);
|
||||
|
||||
public MyActor() {
|
||||
receive(ReceiveBuilder.
|
||||
match(String.class, s -> {
|
||||
log.info("Received String message: {}", s);
|
||||
//#my-actor
|
||||
//#reply
|
||||
sender().tell(s, self());
|
||||
//#reply
|
||||
//#my-actor
|
||||
}).
|
||||
matchAny(o -> log.info("received unknown message")).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
//#my-actor
|
||||
36
akka-docs/rst/java/code/docs/actor/SampleActor.java
Normal file
36
akka-docs/rst/java/code/docs/actor/SampleActor.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor;
|
||||
|
||||
//#sample-actor
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import scala.PartialFunction;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
public class SampleActor extends AbstractActor {
|
||||
|
||||
private PartialFunction<Object, BoxedUnit> guarded = ReceiveBuilder.
|
||||
match(String.class, s -> s.contains("guard"), s -> {
|
||||
sender().tell("contains(guard): " + s, self());
|
||||
context().unbecome();
|
||||
}).build();
|
||||
|
||||
public SampleActor() {
|
||||
receive(ReceiveBuilder.
|
||||
match(Double.class, d -> {
|
||||
sender().tell(d.isNaN() ? 0 : d, self());
|
||||
}).
|
||||
match(Integer.class, i -> {
|
||||
sender().tell(i * 10, self());
|
||||
}).
|
||||
match(String.class, s -> s.startsWith("guard"), s -> {
|
||||
sender().tell("startsWith(guard): " + s.toUpperCase(), self());
|
||||
context().become(guarded, false);
|
||||
}).build()
|
||||
);
|
||||
}
|
||||
}
|
||||
//#sample-actor
|
||||
55
akka-docs/rst/java/code/docs/actor/SampleActorTest.java
Normal file
55
akka-docs/rst/java/code/docs/actor/SampleActorTest.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SampleActorTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("SampleActorTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSampleActor()
|
||||
{
|
||||
new JavaTestKit(system) {{
|
||||
final ActorRef subject = system.actorOf(Props.create(SampleActor.class), "sample-actor");
|
||||
final ActorRef probeRef = getRef();
|
||||
|
||||
subject.tell(47.11, probeRef);
|
||||
subject.tell("and no guard in the beginning", probeRef);
|
||||
subject.tell("guard is a good thing", probeRef);
|
||||
subject.tell(47.11, probeRef);
|
||||
subject.tell(4711, probeRef);
|
||||
subject.tell("and no guard in the beginning", probeRef);
|
||||
subject.tell(4711, probeRef);
|
||||
subject.tell("and an unmatched message", probeRef);
|
||||
|
||||
expectMsgEquals(47.11);
|
||||
assertTrue(expectMsgClass(String.class).startsWith("startsWith(guard):"));
|
||||
assertTrue(expectMsgClass(String.class).startsWith("contains(guard):"));
|
||||
expectMsgEquals(47110);
|
||||
expectNoMsg();
|
||||
}};
|
||||
}
|
||||
}
|
||||
136
akka-docs/rst/java/code/docs/actor/fsm/Buncher.java
Normal file
136
akka-docs/rst/java/code/docs/actor/fsm/Buncher.java
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor.fsm;
|
||||
|
||||
//#simple-imports
|
||||
import akka.actor.AbstractFSM;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.japi.pf.UnitMatch;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import scala.concurrent.duration.Duration;
|
||||
//#simple-imports
|
||||
|
||||
import static docs.actor.fsm.Buncher.Data;
|
||||
import static docs.actor.fsm.Buncher.State.*;
|
||||
import static docs.actor.fsm.Buncher.State;
|
||||
import static docs.actor.fsm.Buncher.Uninitialized.*;
|
||||
import static docs.actor.fsm.Events.*;
|
||||
|
||||
//#simple-fsm
|
||||
public class Buncher extends AbstractFSM<State, Data> {
|
||||
{
|
||||
//#fsm-body
|
||||
startWith(Idle, Uninitialized);
|
||||
|
||||
//#when-syntax
|
||||
when(Idle,
|
||||
matchEvent(SetTarget.class, Uninitialized.class,
|
||||
(setTarget, uninitialized) ->
|
||||
stay().using(new Todo(setTarget.getRef(), new LinkedList<>()))));
|
||||
//#when-syntax
|
||||
|
||||
//#transition-elided
|
||||
onTransition(
|
||||
matchState(Active, Idle, () -> {
|
||||
// reuse this matcher
|
||||
final UnitMatch<Data> m = UnitMatch.create(
|
||||
matchData(Todo.class,
|
||||
todo -> todo.getTarget().tell(new Batch(todo.getQueue()), self())));
|
||||
m.match(stateData());
|
||||
}).
|
||||
state(Idle, Active, () -> {/* Do something here */}));
|
||||
//#transition-elided
|
||||
|
||||
when(Active, Duration.create(1, "second"),
|
||||
matchEvent(Arrays.asList(Flush.class, StateTimeout()), Todo.class,
|
||||
(event, todo) -> goTo(Idle).using(todo.copy(new LinkedList<>()))));
|
||||
|
||||
//#unhandled-elided
|
||||
whenUnhandled(
|
||||
matchEvent(Queue.class, Todo.class,
|
||||
(queue, todo) -> goTo(Active).using(todo.addElement(queue.getObj()))).
|
||||
anyEvent((event, state) -> {
|
||||
log().warning("received unhandled request {} in state {}/{}",
|
||||
event, stateName(), state);
|
||||
return stay();
|
||||
}));
|
||||
//#unhandled-elided
|
||||
|
||||
initialize();
|
||||
//#fsm-body
|
||||
}
|
||||
//#simple-fsm
|
||||
|
||||
static
|
||||
//#simple-state
|
||||
// states
|
||||
enum State {
|
||||
Idle, Active
|
||||
}
|
||||
|
||||
//#simple-state
|
||||
static
|
||||
//#simple-state
|
||||
// state data
|
||||
interface Data {
|
||||
}
|
||||
|
||||
//#simple-state
|
||||
static
|
||||
//#simple-state
|
||||
enum Uninitialized implements Data {
|
||||
Uninitialized
|
||||
}
|
||||
|
||||
//#simple-state
|
||||
static
|
||||
//#simple-state
|
||||
final class Todo implements Data {
|
||||
private final ActorRef target;
|
||||
private final List<Object> queue;
|
||||
|
||||
public Todo(ActorRef target, List<Object> queue) {
|
||||
this.target = target;
|
||||
this.queue = queue;
|
||||
}
|
||||
|
||||
public ActorRef getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public List<Object> getQueue() {
|
||||
return queue;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Todo{" +
|
||||
"target=" + target +
|
||||
", queue=" + queue +
|
||||
'}';
|
||||
}
|
||||
|
||||
public Todo addElement(Object element) {
|
||||
List<Object> nQueue = new LinkedList<>(queue);
|
||||
nQueue.add(element);
|
||||
return new Todo(this.target, nQueue);
|
||||
}
|
||||
|
||||
public Todo copy(List<Object> queue) {
|
||||
return new Todo(this.target, queue);
|
||||
}
|
||||
|
||||
public Todo copy(ActorRef target) {
|
||||
return new Todo(target, this.queue);
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
//#simple-state
|
||||
//#simple-fsm
|
||||
}
|
||||
//#simple-fsm
|
||||
78
akka-docs/rst/java/code/docs/actor/fsm/BuncherTest.java
Normal file
78
akka-docs/rst/java/code/docs/actor/fsm/BuncherTest.java
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor.fsm;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import docs.actor.fsm.*;
|
||||
import static docs.actor.fsm.Events.Batch;
|
||||
import static docs.actor.fsm.Events.Queue;
|
||||
import static docs.actor.fsm.Events.SetTarget;
|
||||
import static docs.actor.fsm.Events.Flush.Flush;
|
||||
|
||||
//#test-code
|
||||
public class BuncherTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("BuncherTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuncherActorBatchesCorrectly() {
|
||||
new JavaTestKit(system) {{
|
||||
final ActorRef buncher =
|
||||
system.actorOf(Props.create(Buncher.class));
|
||||
final ActorRef probe = getRef();
|
||||
|
||||
buncher.tell(new SetTarget(probe), probe);
|
||||
buncher.tell(new Queue(42), probe);
|
||||
buncher.tell(new Queue(43), probe);
|
||||
LinkedList<Object> list1 = new LinkedList<>();
|
||||
list1.add(42);
|
||||
list1.add(43);
|
||||
expectMsgEquals(new Batch(list1));
|
||||
buncher.tell(new Queue(44), probe);
|
||||
buncher.tell(Flush, probe);
|
||||
buncher.tell(new Queue(45), probe);
|
||||
LinkedList<Object> list2 = new LinkedList<>();
|
||||
list2.add(44);
|
||||
expectMsgEquals(new Batch(list2));
|
||||
LinkedList<Object> list3 = new LinkedList<>();
|
||||
list3.add(45);
|
||||
expectMsgEquals(new Batch(list3));
|
||||
system.stop(buncher);
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBuncherActorDoesntBatchUninitialized() {
|
||||
new JavaTestKit(system) {{
|
||||
final ActorRef buncher =
|
||||
system.actorOf(Props.create(Buncher.class));
|
||||
final ActorRef probe = getRef();
|
||||
|
||||
buncher.tell(new Queue(42), probe);
|
||||
expectNoMsg();
|
||||
system.stop(buncher);
|
||||
}};
|
||||
}
|
||||
}
|
||||
//#test-code
|
||||
108
akka-docs/rst/java/code/docs/actor/fsm/Events.java
Normal file
108
akka-docs/rst/java/code/docs/actor/fsm/Events.java
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor.fsm;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import java.util.List;
|
||||
|
||||
public class Events {
|
||||
|
||||
static
|
||||
//#simple-events
|
||||
public final class SetTarget {
|
||||
private final ActorRef ref;
|
||||
|
||||
public SetTarget(ActorRef ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public ActorRef getRef() {
|
||||
return ref;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SetTarget{" +
|
||||
"ref=" + ref +
|
||||
'}';
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
|
||||
//#simple-events
|
||||
static
|
||||
//#simple-events
|
||||
public final class Queue {
|
||||
private final Object obj;
|
||||
|
||||
public Queue(Object obj) {
|
||||
this.obj = obj;
|
||||
}
|
||||
|
||||
public Object getObj() {
|
||||
return obj;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Queue{" +
|
||||
"obj=" + obj +
|
||||
'}';
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
|
||||
//#simple-events
|
||||
static
|
||||
//#simple-events
|
||||
public final class Batch {
|
||||
private final List<Object> list;
|
||||
|
||||
public Batch(List<Object> list) {
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public List<Object> getList() {
|
||||
return list;
|
||||
}
|
||||
//#boilerplate
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Batch batch = (Batch) o;
|
||||
|
||||
return list.equals(batch.list);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return list.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append( "Batch{list=");
|
||||
list.stream().forEachOrdered(e -> { builder.append(e); builder.append(","); });
|
||||
int len = builder.length();
|
||||
builder.replace(len, len, "}");
|
||||
return builder.toString();
|
||||
}
|
||||
//#boilerplate
|
||||
}
|
||||
|
||||
//#simple-events
|
||||
static
|
||||
//#simple-events
|
||||
public enum Flush {
|
||||
Flush
|
||||
}
|
||||
//#simple-events
|
||||
}
|
||||
179
akka-docs/rst/java/code/docs/actor/fsm/FSMDocTest.java
Normal file
179
akka-docs/rst/java/code/docs/actor/fsm/FSMDocTest.java
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.actor.fsm;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.hamcrest.CoreMatchers;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.duration.Duration;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import static docs.actor.fsm.FSMDocTest.StateType.*;
|
||||
import static docs.actor.fsm.FSMDocTest.Messages.*;
|
||||
import static java.util.concurrent.TimeUnit.*;
|
||||
|
||||
public class FSMDocTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FSMDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
public static enum StateType {
|
||||
SomeState,
|
||||
Processing,
|
||||
Idle,
|
||||
Active,
|
||||
Error
|
||||
}
|
||||
|
||||
public static enum Messages {
|
||||
WillDo,
|
||||
Tick
|
||||
}
|
||||
|
||||
public static enum Data {
|
||||
Foo,
|
||||
Bar
|
||||
};
|
||||
|
||||
public static interface X {};
|
||||
|
||||
public static class DummyFSM extends AbstractFSM<StateType, Integer> {
|
||||
Integer newData = 42;
|
||||
//#alt-transition-syntax
|
||||
public void handler(StateType from, StateType to) {
|
||||
// handle transition here
|
||||
}
|
||||
|
||||
//#alt-transition-syntax
|
||||
{
|
||||
//#modifier-syntax
|
||||
when(SomeState, matchAnyEvent((msg, data) -> {
|
||||
return goTo(Processing).using(newData).
|
||||
forMax(Duration.create(5, SECONDS)).replying(WillDo);
|
||||
}));
|
||||
//#modifier-syntax
|
||||
|
||||
//#NullFunction
|
||||
when(SomeState, AbstractFSM.NullFunction());
|
||||
//#NullFunction
|
||||
|
||||
//#transition-syntax
|
||||
onTransition(
|
||||
matchState(Active, Idle, () -> setTimer("timeout",
|
||||
Tick, Duration.create(1, SECONDS), true)).
|
||||
state(Active, null, () -> cancelTimer("timeout")).
|
||||
state(null, Idle, (f, t) -> log().info("entering Idle from " + f)));
|
||||
//#transition-syntax
|
||||
|
||||
//#alt-transition-syntax
|
||||
onTransition(this::handler);
|
||||
//#alt-transition-syntax
|
||||
|
||||
//#stop-syntax
|
||||
when(Error, matchEventEquals("stop", (event, data) -> {
|
||||
// do cleanup ...
|
||||
return stop();
|
||||
}));
|
||||
//#stop-syntax
|
||||
|
||||
//#termination-syntax
|
||||
onTermination(
|
||||
matchStop(Normal(),
|
||||
(state, data) -> {/* Do something here */}).
|
||||
stop(Shutdown(),
|
||||
(state, data) -> {/* Do something here */}).
|
||||
stop(Failure.class,
|
||||
(reason, state, data) -> {/* Do something here */}));
|
||||
//#termination-syntax
|
||||
|
||||
//#unhandled-syntax
|
||||
whenUnhandled(
|
||||
matchEvent(X.class, (x, data) -> {
|
||||
log().info("Received unhandled event: " + x);
|
||||
return stay();
|
||||
}).
|
||||
anyEvent((event, data) -> {
|
||||
log().warning("Received unknown event: " + event);
|
||||
return goTo(Error);
|
||||
}));
|
||||
}
|
||||
//#unhandled-syntax
|
||||
}
|
||||
|
||||
static
|
||||
//#logging-fsm
|
||||
public class MyFSM extends AbstractLoggingFSM<StateType, Data> {
|
||||
//#body-elided
|
||||
//#logging-fsm
|
||||
ActorRef target = null;
|
||||
//#logging-fsm
|
||||
@Override
|
||||
public int logDepth() { return 12; }
|
||||
{
|
||||
onTermination(
|
||||
matchStop(Failure.class, (reason, state, data) -> {
|
||||
String lastEvents = getLog().mkString("\n\t");
|
||||
log().warning("Failure in state " + state + " with data " + data + "\n" +
|
||||
"Events leading up to this point:\n\t" + lastEvents);
|
||||
//#logging-fsm
|
||||
target.tell(reason.cause(), self());
|
||||
target.tell(state, self());
|
||||
target.tell(data, self());
|
||||
target.tell(lastEvents, self());
|
||||
//#logging-fsm
|
||||
})
|
||||
);
|
||||
//...
|
||||
//#logging-fsm
|
||||
startWith(SomeState, Data.Foo);
|
||||
when(SomeState, matchEvent(ActorRef.class, Data.class, (ref, data) -> {
|
||||
target = ref;
|
||||
target.tell("going active", self());
|
||||
return goTo(Active);
|
||||
}));
|
||||
when(Active, matchEventEquals("stop", (event, data) -> {
|
||||
target.tell("stopping", self());
|
||||
return stop(new Failure("This is not the error you're looking for"));
|
||||
}));
|
||||
initialize();
|
||||
//#logging-fsm
|
||||
}
|
||||
//#body-elided
|
||||
}
|
||||
//#logging-fsm
|
||||
|
||||
@Test
|
||||
public void testLoggingFSM()
|
||||
{
|
||||
new JavaTestKit(system) {{
|
||||
final ActorRef logger =
|
||||
system.actorOf(Props.create(MyFSM.class));
|
||||
final ActorRef probe = getRef();
|
||||
|
||||
logger.tell(probe, probe);
|
||||
expectMsgEquals("going active");
|
||||
logger.tell("stop", probe);
|
||||
expectMsgEquals("stopping");
|
||||
expectMsgEquals("This is not the error you're looking for");
|
||||
expectMsgEquals(Active);
|
||||
expectMsgEquals(Data.Foo);
|
||||
String msg = expectMsgClass(String.class);
|
||||
assertThat(msg, CoreMatchers.startsWith("LogEntry(SomeState,Foo,Actor[akka://FSMDocTest/system/"));
|
||||
}};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,470 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.actor.japi;
|
||||
|
||||
//#all
|
||||
//#imports
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.dispatch.Mapper;
|
||||
import akka.event.LoggingReceive;
|
||||
import akka.japi.pf.DeciderBuilder;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.util.Timeout;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.PartialFunction;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import static akka.japi.Util.classTag;
|
||||
import static akka.actor.SupervisorStrategy.resume;
|
||||
import static akka.actor.SupervisorStrategy.restart;
|
||||
import static akka.actor.SupervisorStrategy.stop;
|
||||
import static akka.actor.SupervisorStrategy.escalate;
|
||||
|
||||
import static akka.pattern.Patterns.ask;
|
||||
import static akka.pattern.Patterns.pipe;
|
||||
|
||||
import static docs.actor.japi.FaultHandlingDocSample.WorkerApi.*;
|
||||
import static docs.actor.japi.FaultHandlingDocSample.CounterServiceApi.*;
|
||||
import static docs.actor.japi.FaultHandlingDocSample.CounterApi.*;
|
||||
import static docs.actor.japi.FaultHandlingDocSample.StorageApi.*;
|
||||
|
||||
//#imports
|
||||
|
||||
public class FaultHandlingDocSampleJava8 {
|
||||
|
||||
/**
|
||||
* Runs the sample
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
Config config = ConfigFactory.parseString(
|
||||
"akka.loglevel = \"DEBUG\"\n" +
|
||||
"akka.actor.debug {\n" +
|
||||
" receive = on\n" +
|
||||
" lifecycle = on\n" +
|
||||
"}\n");
|
||||
|
||||
ActorSystem system = ActorSystem.create("FaultToleranceSample", config);
|
||||
ActorRef worker = system.actorOf(Props.create(Worker.class), "worker");
|
||||
ActorRef listener = system.actorOf(Props.create(Listener.class), "listener");
|
||||
// start the work and listen on progress
|
||||
// note that the listener is used as sender of the tell,
|
||||
// i.e. it will receive replies from the worker
|
||||
worker.tell(Start, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens on progress from the worker and shuts down the system when enough
|
||||
* work has been done.
|
||||
*/
|
||||
public static class Listener extends AbstractLoggingActor {
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
// If we don't get any progress within 15 seconds then the service
|
||||
// is unavailable
|
||||
context().setReceiveTimeout(Duration.create("15 seconds"));
|
||||
}
|
||||
|
||||
public Listener() {
|
||||
receive(LoggingReceive.create(ReceiveBuilder.
|
||||
match(Progress.class, progress -> {
|
||||
log().info("Current progress: {} %", progress.percent);
|
||||
if (progress.percent >= 100.0) {
|
||||
log().info("That's all, shutting down");
|
||||
context().system().shutdown();
|
||||
}
|
||||
}).
|
||||
matchEquals(ReceiveTimeout.getInstance(), x -> {
|
||||
// No progress within 15 seconds, ServiceUnavailable
|
||||
log().error("Shutting down due to unavailable service");
|
||||
context().system().shutdown();
|
||||
}).build(), context()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface WorkerApi {
|
||||
public static final Object Start = "Start";
|
||||
public static final Object Do = "Do";
|
||||
|
||||
public static class Progress {
|
||||
public final double percent;
|
||||
|
||||
public Progress(double percent) {
|
||||
this.percent = percent;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), percent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Worker performs some work when it receives the Start message. It will
|
||||
* continuously notify the sender of the Start message of current Progress.
|
||||
* The Worker supervise the CounterService.
|
||||
*/
|
||||
public static class Worker extends AbstractLoggingActor {
|
||||
final Timeout askTimeout = new Timeout(Duration.create(5, "seconds"));
|
||||
|
||||
// The sender of the initial Start message will continuously be notified
|
||||
// about progress
|
||||
ActorRef progressListener;
|
||||
final ActorRef counterService = context().actorOf(
|
||||
Props.create(CounterService.class), "counter");
|
||||
final int totalCount = 51;
|
||||
|
||||
// Stop the CounterService child if it throws ServiceUnavailable
|
||||
private static final SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(DeciderBuilder.
|
||||
match(ServiceUnavailable.class, e -> stop()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
public Worker() {
|
||||
receive(LoggingReceive.create(ReceiveBuilder.
|
||||
matchEquals(Start, x -> progressListener == null, x -> {
|
||||
progressListener = sender();
|
||||
context().system().scheduler().schedule(
|
||||
Duration.Zero(), Duration.create(1, "second"), self(), Do,
|
||||
context().dispatcher(), null
|
||||
);
|
||||
}).
|
||||
matchEquals(Do, x -> {
|
||||
counterService.tell(new Increment(1), self());
|
||||
counterService.tell(new Increment(1), self());
|
||||
counterService.tell(new Increment(1), self());
|
||||
// Send current progress to the initial sender
|
||||
pipe(ask(counterService, GetCurrentCount, askTimeout)
|
||||
.mapTo(classTag(CurrentCount.class))
|
||||
.map(new Mapper<CurrentCount, Progress>() {
|
||||
public Progress apply(CurrentCount c) {
|
||||
return new Progress(100.0 * c.count / totalCount);
|
||||
}
|
||||
}, context().dispatcher()), context().dispatcher())
|
||||
.to(progressListener);
|
||||
}).build(), context())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface CounterServiceApi {
|
||||
|
||||
public static final Object GetCurrentCount = "GetCurrentCount";
|
||||
|
||||
public static class CurrentCount {
|
||||
public final String key;
|
||||
public final long count;
|
||||
|
||||
public CurrentCount(String key, long count) {
|
||||
this.key = key;
|
||||
this.count = count;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s, %s)", getClass().getSimpleName(), key, count);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Increment {
|
||||
public final long n;
|
||||
|
||||
public Increment(long n) {
|
||||
this.n = n;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), n);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ServiceUnavailable extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public ServiceUnavailable(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Adds the value received in Increment message to a persistent counter.
|
||||
* Replies with CurrentCount when it is asked for CurrentCount. CounterService
|
||||
* supervise Storage and Counter.
|
||||
*/
|
||||
public static class CounterService extends AbstractLoggingActor {
|
||||
|
||||
// Reconnect message
|
||||
static final Object Reconnect = "Reconnect";
|
||||
|
||||
private static class SenderMsgPair {
|
||||
final ActorRef sender;
|
||||
final Object msg;
|
||||
|
||||
SenderMsgPair(ActorRef sender, Object msg) {
|
||||
this.msg = msg;
|
||||
this.sender = sender;
|
||||
}
|
||||
}
|
||||
|
||||
final String key = self().path().name();
|
||||
ActorRef storage;
|
||||
ActorRef counter;
|
||||
final List<SenderMsgPair> backlog = new ArrayList<>();
|
||||
final int MAX_BACKLOG = 10000;
|
||||
|
||||
// Restart the storage child when StorageException is thrown.
|
||||
// After 3 restarts within 5 seconds it will be stopped.
|
||||
private static final SupervisorStrategy strategy =
|
||||
new OneForOneStrategy(3, Duration.create("5 seconds"), DeciderBuilder.
|
||||
match(StorageException.class, e -> restart()).
|
||||
matchAny(o -> escalate()).build());
|
||||
|
||||
@Override
|
||||
public SupervisorStrategy supervisorStrategy() {
|
||||
return strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
initStorage();
|
||||
}
|
||||
|
||||
/**
|
||||
* The child storage is restarted in case of failure, but after 3 restarts,
|
||||
* and still failing it will be stopped. Better to back-off than
|
||||
* continuously failing. When it has been stopped we will schedule a
|
||||
* Reconnect after a delay. Watch the child so we receive Terminated message
|
||||
* when it has been terminated.
|
||||
*/
|
||||
void initStorage() {
|
||||
storage = context().watch(context().actorOf(
|
||||
Props.create(Storage.class), "storage"));
|
||||
// Tell the counter, if any, to use the new storage
|
||||
if (counter != null)
|
||||
counter.tell(new UseStorage(storage), self());
|
||||
// We need the initial value to be able to operate
|
||||
storage.tell(new Get(key), self());
|
||||
}
|
||||
|
||||
public CounterService() {
|
||||
receive(LoggingReceive.create(ReceiveBuilder.
|
||||
match(Entry.class, entry -> entry.key.equals(key) && counter == null, entry -> {
|
||||
// Reply from Storage of the initial value, now we can create the Counter
|
||||
final long value = entry.value;
|
||||
counter = context().actorOf(Props.create(Counter.class, key, value));
|
||||
// Tell the counter to use current storage
|
||||
counter.tell(new UseStorage(storage), self());
|
||||
// and send the buffered backlog to the counter
|
||||
for (SenderMsgPair each : backlog) {
|
||||
counter.tell(each.msg, each.sender);
|
||||
}
|
||||
backlog.clear();
|
||||
}).
|
||||
match(Increment.class, increment -> {
|
||||
forwardOrPlaceInBacklog(increment);
|
||||
}).
|
||||
matchEquals(GetCurrentCount, gcc -> {
|
||||
forwardOrPlaceInBacklog(gcc);
|
||||
}).
|
||||
match(Terminated.class, o -> {
|
||||
// After 3 restarts the storage child is stopped.
|
||||
// We receive Terminated because we watch the child, see initStorage.
|
||||
storage = null;
|
||||
// Tell the counter that there is no storage for the moment
|
||||
counter.tell(new UseStorage(null), self());
|
||||
// Try to re-establish storage after while
|
||||
context().system().scheduler().scheduleOnce(
|
||||
Duration.create(10, "seconds"), self(), Reconnect,
|
||||
context().dispatcher(), null);
|
||||
}).
|
||||
matchEquals(Reconnect, o -> {
|
||||
// Re-establish storage after the scheduled delay
|
||||
initStorage();
|
||||
}).build(), context())
|
||||
);
|
||||
}
|
||||
|
||||
void forwardOrPlaceInBacklog(Object msg) {
|
||||
// We need the initial value from storage before we can start delegate to
|
||||
// the counter. Before that we place the messages in a backlog, to be sent
|
||||
// to the counter when it is initialized.
|
||||
if (counter == null) {
|
||||
if (backlog.size() >= MAX_BACKLOG)
|
||||
throw new ServiceUnavailable("CounterService not available," +
|
||||
" lack of initial value");
|
||||
backlog.add(new SenderMsgPair(sender(), msg));
|
||||
} else {
|
||||
counter.forward(msg, context());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface CounterApi {
|
||||
public static class UseStorage {
|
||||
public final ActorRef storage;
|
||||
|
||||
public UseStorage(ActorRef storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), storage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* The in memory count variable that will send current value to the Storage,
|
||||
* if there is any storage available at the moment.
|
||||
*/
|
||||
public static class Counter extends AbstractLoggingActor {
|
||||
final String key;
|
||||
long count;
|
||||
ActorRef storage;
|
||||
|
||||
public Counter(String key, long initialValue) {
|
||||
this.key = key;
|
||||
this.count = initialValue;
|
||||
|
||||
receive(LoggingReceive.create(ReceiveBuilder.
|
||||
match(UseStorage.class, useStorage -> {
|
||||
storage = useStorage.storage;
|
||||
storeCount();
|
||||
}).
|
||||
match(Increment.class, increment -> {
|
||||
count += increment.n;
|
||||
storeCount();
|
||||
}).
|
||||
matchEquals(GetCurrentCount, gcc -> {
|
||||
sender().tell(new CurrentCount(key, count), self());
|
||||
}).build(), context())
|
||||
);
|
||||
}
|
||||
|
||||
void storeCount() {
|
||||
// Delegate dangerous work, to protect our valuable state.
|
||||
// We can continue without storage.
|
||||
if (storage != null) {
|
||||
storage.tell(new Store(new Entry(key, count)), self());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
public interface StorageApi {
|
||||
|
||||
public static class Store {
|
||||
public final Entry entry;
|
||||
|
||||
public Store(Entry entry) {
|
||||
this.entry = entry;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), entry);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Entry {
|
||||
public final String key;
|
||||
public final long value;
|
||||
|
||||
public Entry(String key, long value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s, %s)", getClass().getSimpleName(), key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Get {
|
||||
public final String key;
|
||||
|
||||
public Get(String key) {
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return String.format("%s(%s)", getClass().getSimpleName(), key);
|
||||
}
|
||||
}
|
||||
|
||||
public static class StorageException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public StorageException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#messages
|
||||
|
||||
/**
|
||||
* Saves key/value pairs to persistent storage when receiving Store message.
|
||||
* Replies with current value when receiving Get message. Will throw
|
||||
* StorageException if the underlying data store is out of order.
|
||||
*/
|
||||
public static class Storage extends AbstractLoggingActor {
|
||||
|
||||
final DummyDB db = DummyDB.instance;
|
||||
|
||||
public Storage() {
|
||||
receive(LoggingReceive.create(ReceiveBuilder.
|
||||
match(Store.class, store -> {
|
||||
db.save(store.entry.key, store.entry.value);
|
||||
}).
|
||||
match(Get.class, get -> {
|
||||
Long value = db.load(get.key);
|
||||
sender().tell(new Entry(get.key, value == null ?
|
||||
Long.valueOf(0L) : value), self());
|
||||
}).build(), context())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//#dummydb
|
||||
public static class DummyDB {
|
||||
public static final DummyDB instance = new DummyDB();
|
||||
private final Map<String, Long> db = new HashMap<String, Long>();
|
||||
|
||||
private DummyDB() {
|
||||
}
|
||||
|
||||
public synchronized void save(String key, Long value) throws StorageException {
|
||||
if (11 <= value && value <= 14)
|
||||
throw new StorageException("Simulated store failure " + value);
|
||||
db.put(key, value);
|
||||
}
|
||||
|
||||
public synchronized Long load(String key) throws StorageException {
|
||||
return db.get(key);
|
||||
}
|
||||
}
|
||||
//#dummydb
|
||||
}
|
||||
//#all
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.HostConnectionPool;
|
||||
import akka.japi.Pair;
|
||||
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.stream.Materializer;
|
||||
import scala.concurrent.ExecutionContextExecutor;
|
||||
import scala.concurrent.Future;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.http.javadsl.OutgoingConnection;
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.Http;
|
||||
import scala.util.Try;
|
||||
|
||||
import static akka.pattern.Patterns.*;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HttpClientExampleDocTest {
|
||||
|
||||
// compile only test
|
||||
public void testConstructRequest() {
|
||||
//#outgoing-connection-example
|
||||
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Flow<HttpRequest, HttpResponse, Future<OutgoingConnection>> connectionFlow =
|
||||
Http.get(system).outgoingConnection("akka.io", 80);
|
||||
final Future<HttpResponse> responseFuture =
|
||||
Source.single(HttpRequest.create("/"))
|
||||
.via(connectionFlow)
|
||||
.runWith(Sink.<HttpResponse>head(), materializer);
|
||||
//#outgoing-connection-example
|
||||
}
|
||||
|
||||
// compile only test
|
||||
public void testHostLevelExample() {
|
||||
//#host-level-example
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
// construct a pool client flow with context type `Integer`
|
||||
final Flow<
|
||||
Pair<HttpRequest, Integer>,
|
||||
Pair<Try<HttpResponse>, Integer>,
|
||||
HostConnectionPool> poolClientFlow =
|
||||
Http.get(system).<Integer>cachedHostConnectionPool("akka.io", 80, materializer);
|
||||
|
||||
// construct a pool client flow with context type `Integer`
|
||||
|
||||
final Future<Pair<Try<HttpResponse>, Integer>> responseFuture =
|
||||
Source
|
||||
.single(Pair.create(HttpRequest.create("/"), 42))
|
||||
.via(poolClientFlow)
|
||||
.runWith(Sink.<Pair<Try<HttpResponse>, Integer>>head(), materializer);
|
||||
//#host-level-example
|
||||
}
|
||||
|
||||
// compile only test
|
||||
public void testSingleRequestExample() {
|
||||
//#single-request-example
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Future<HttpResponse> responseFuture =
|
||||
Http.get(system)
|
||||
.singleRequest(HttpRequest.create("http://akka.io"), materializer);
|
||||
//#single-request-example
|
||||
}
|
||||
|
||||
static
|
||||
//#single-request-in-actor-example
|
||||
class Myself extends AbstractActor {
|
||||
final Http http = Http.get(context().system());
|
||||
final ExecutionContextExecutor dispatcher = context().dispatcher();
|
||||
final Materializer materializer = ActorMaterializer.create(context());
|
||||
|
||||
public Myself() {
|
||||
receive(ReceiveBuilder
|
||||
.match(String.class, url -> {
|
||||
pipe(fetch (url), dispatcher).to(self());
|
||||
}).build());
|
||||
}
|
||||
|
||||
Future<HttpResponse> fetch(String url) {
|
||||
return http.singleRequest(HttpRequest.create(url), materializer);
|
||||
}
|
||||
}
|
||||
//#single-request-in-actor-example
|
||||
|
||||
}
|
||||
90
akka-docs/rst/java/code/docs/http/javadsl/ModelDocTest.java
Normal file
90
akka-docs/rst/java/code/docs/http/javadsl/ModelDocTest.java
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl;
|
||||
|
||||
import akka.japi.Option;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.Test;
|
||||
|
||||
//#import-model
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.model.headers.*;
|
||||
//#import-model
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ModelDocTest {
|
||||
@Test
|
||||
public void testConstructRequest() {
|
||||
//#construct-request
|
||||
// construct a simple GET request to `homeUri`
|
||||
Uri homeUri = Uri.create("/home");
|
||||
HttpRequest request1 = HttpRequest.create().withUri(homeUri);
|
||||
|
||||
// construct simple GET request to "/index" using helper methods
|
||||
HttpRequest request2 = HttpRequest.GET("/index");
|
||||
|
||||
// construct simple POST request containing entity
|
||||
ByteString data = ByteString.fromString("abc");
|
||||
HttpRequest postRequest1 = HttpRequest.POST("/receive").withEntity(data);
|
||||
|
||||
// customize every detail of HTTP request
|
||||
//import HttpProtocols._
|
||||
//import MediaTypes._
|
||||
Authorization authorization = Authorization.basic("user", "pass");
|
||||
HttpRequest complexRequest =
|
||||
HttpRequest.PUT("/user")
|
||||
.withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, "abc"))
|
||||
.addHeader(authorization)
|
||||
.withProtocol(HttpProtocols.HTTP_1_0);
|
||||
//#construct-request
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructResponse() {
|
||||
//#construct-response
|
||||
// simple OK response without data created using the integer status code
|
||||
HttpResponse ok = HttpResponse.create().withStatus(200);
|
||||
|
||||
// 404 response created using the named StatusCode constant
|
||||
HttpResponse notFound = HttpResponse.create().withStatus(StatusCodes.NOT_FOUND);
|
||||
|
||||
// 404 response with a body explaining the error
|
||||
HttpResponse notFoundCustom =
|
||||
HttpResponse.create()
|
||||
.withStatus(404)
|
||||
.withEntity("Unfortunately, the resource couldn't be found.");
|
||||
|
||||
// A redirecting response containing an extra header
|
||||
Location locationHeader = Location.create("http://example.com/other");
|
||||
HttpResponse redirectResponse =
|
||||
HttpResponse.create()
|
||||
.withStatus(StatusCodes.FOUND)
|
||||
.addHeader(locationHeader);
|
||||
//#construct-response
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDealWithHeaders() {
|
||||
//#headers
|
||||
// create a ``Location`` header
|
||||
Location locationHeader = Location.create("http://example.com/other");
|
||||
|
||||
// create an ``Authorization`` header with HTTP Basic authentication data
|
||||
Authorization authorization = Authorization.basic("user", "pass");
|
||||
//#headers
|
||||
}
|
||||
|
||||
//#headers
|
||||
|
||||
// a method that extracts basic HTTP credentials from a request
|
||||
private Option<BasicHttpCredentials> getCredentialsOfRequest(HttpRequest request) {
|
||||
Option<Authorization> auth = request.getHeader(Authorization.class);
|
||||
if (auth.isDefined() && auth.get().credentials() instanceof BasicHttpCredentials)
|
||||
return Option.some((BasicHttpCredentials) auth.get().credentials());
|
||||
else
|
||||
return Option.none();
|
||||
}
|
||||
//#headers
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.model.FormData;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.values.FormField;
|
||||
import akka.http.javadsl.server.values.FormFields;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.japi.Pair;
|
||||
import org.junit.Test;
|
||||
|
||||
public class FormFieldRequestValsExampleTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testFormFieldVals() {
|
||||
//#simple
|
||||
FormField<String> name = FormFields.stringValue("name");
|
||||
FormField<Integer> age = FormFields.intValue("age");
|
||||
|
||||
final Route route =
|
||||
route(
|
||||
handleWith2(name, age, (ctx, n, a) ->
|
||||
ctx.complete(String.format("Name: %s, age: %d", n, a))
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
final FormData formData = FormData.create(
|
||||
Pair.create("name", "Blippy"),
|
||||
Pair.create("age", "42"));
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.POST("/")
|
||||
.withEntity(formData.toEntity());
|
||||
testRoute(route).run(request).assertEntity("Name: Blippy, age: 42");
|
||||
|
||||
//#simple
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormFieldValsUnmarshaling() {
|
||||
//#custom-unmarshal
|
||||
FormField<SampleId> sampleId = FormFields.fromString("id", SampleId.class, s -> new SampleId(Integer.valueOf(s)));
|
||||
|
||||
final Route route =
|
||||
route(
|
||||
handleWith1(sampleId, (ctx, sid) ->
|
||||
ctx.complete(String.format("SampleId: %s", sid.id))
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
final FormData formData = FormData.create(Pair.create("id", "1337"));
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.POST("/")
|
||||
.withEntity(formData.toEntity());
|
||||
testRoute(route).run(request).assertEntity("SampleId: 1337");
|
||||
|
||||
//#custom-unmarshal
|
||||
}
|
||||
|
||||
static class SampleId {
|
||||
public final int id;
|
||||
|
||||
SampleId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.model.headers.RawHeader;
|
||||
import akka.http.javadsl.server.RequestVal;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.values.Headers;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Test;
|
||||
|
||||
public class HeaderRequestValsExampleTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testHeaderVals() {
|
||||
//#by-class
|
||||
// extract the entire header instance:
|
||||
RequestVal<Host> host = Headers.byClass(Host.class).instance();
|
||||
|
||||
final Route route =
|
||||
route(
|
||||
handleWith1(host, (ctx, h) ->
|
||||
ctx.complete(String.format("Host header was: %s", h.host()))
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"));
|
||||
testRoute(route).run(request).assertEntity("Host header was: akka.io");
|
||||
|
||||
//#by-class
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderByName() {
|
||||
//#by-name
|
||||
// extract the `value` of the header:
|
||||
final RequestVal<String> XFishName = Headers.byName("X-Fish-Name").value();
|
||||
|
||||
final Route route =
|
||||
route(
|
||||
handleWith1(XFishName, (ctx, xFishName) ->
|
||||
ctx.complete(String.format("The `X-Fish-Name` header's value was: %s", xFishName))
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.GET("/")
|
||||
.addHeader(RawHeader.create("X-Fish-Name", "Blippy"));
|
||||
testRoute(route).run(request).assertEntity("The `X-Fish-Name` header's value was: Blippy");
|
||||
|
||||
//#by-name
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
//#binding-failure-high-level-example
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.dispatch.OnFailure;
|
||||
import akka.http.javadsl.model.ContentTypes;
|
||||
import akka.http.javadsl.server.*;
|
||||
import akka.http.javadsl.server.values.Parameters;
|
||||
import akka.http.scaladsl.Http;
|
||||
import scala.concurrent.Future;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class HighLevelServerBindFailureExample {
|
||||
public static void main(String[] args) throws IOException {
|
||||
// boot up server using the route as defined below
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
|
||||
// HttpApp.bindRoute expects a route being provided by HttpApp.createRoute
|
||||
Future<Http.ServerBinding> bindingFuture =
|
||||
new HighLevelServerExample().bindRoute("localhost", 8080, system);
|
||||
|
||||
bindingFuture.onFailure(new OnFailure() {
|
||||
@Override
|
||||
public void onFailure(Throwable failure) throws Throwable {
|
||||
System.err.println("Something very bad happened! " + failure.getMessage());
|
||||
system.shutdown();
|
||||
}
|
||||
}, system.dispatcher());
|
||||
|
||||
system.shutdown();
|
||||
}
|
||||
}
|
||||
//#binding-failure-high-level-example
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
//#high-level-server-example
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.model.ContentTypes;
|
||||
import akka.http.javadsl.model.MediaTypes;
|
||||
import akka.http.javadsl.server.*;
|
||||
import akka.http.javadsl.server.values.Parameters;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class HighLevelServerExample extends HttpApp {
|
||||
public static void main(String[] args) throws IOException {
|
||||
// boot up server using the route as defined below
|
||||
ActorSystem system = ActorSystem.create();
|
||||
|
||||
// HttpApp.bindRoute expects a route being provided by HttpApp.createRoute
|
||||
new HighLevelServerExample().bindRoute("localhost", 8080, system);
|
||||
System.out.println("Type RETURN to exit");
|
||||
System.in.read();
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
// A RequestVal is a type-safe representation of some aspect of the request.
|
||||
// In this case it represents the `name` URI parameter of type String.
|
||||
private RequestVal<String> name = Parameters.stringValue("name").withDefault("Mister X");
|
||||
|
||||
@Override
|
||||
public Route createRoute() {
|
||||
// This handler generates responses to `/hello?name=XXX` requests
|
||||
Route helloRoute =
|
||||
handleWith1(name,
|
||||
// in Java 8 the following becomes simply
|
||||
// (ctx, name) -> ctx.complete("Hello " + name + "!")
|
||||
new Handler1<String>() {
|
||||
@Override
|
||||
public RouteResult apply(RequestContext ctx, String name) {
|
||||
return ctx.complete("Hello " + name + "!");
|
||||
}
|
||||
});
|
||||
|
||||
return
|
||||
// here the complete behavior for this server is defined
|
||||
route(
|
||||
// only handle GET requests
|
||||
get(
|
||||
// matches the empty path
|
||||
pathSingleSlash().route(
|
||||
// return a constant string with a certain content type
|
||||
complete(ContentTypes.TEXT_HTML_UTF8,
|
||||
"<html><body>Hello world!</body></html>")
|
||||
),
|
||||
path("ping").route(
|
||||
// return a simple `text/plain` response
|
||||
complete("PONG!")
|
||||
),
|
||||
path("hello").route(
|
||||
// uses the route defined above
|
||||
helloRoute
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
//#high-level-server-example
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.server.Handler1;
|
||||
import akka.http.javadsl.server.RequestContext;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.RouteResult;
|
||||
import akka.http.javadsl.server.values.BasicCredentials;
|
||||
import akka.http.javadsl.server.values.HttpBasicAuthenticator;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.scaladsl.model.headers.Authorization;
|
||||
import org.junit.Test;
|
||||
import scala.Option;
|
||||
import scala.concurrent.Future;
|
||||
|
||||
public class HttpBasicAuthenticatorExample extends JUnitRouteTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testBasicAuthenticator() {
|
||||
//#basic-authenticator-java
|
||||
final HttpBasicAuthenticator<String> authentication = new HttpBasicAuthenticator<String>("My realm") {
|
||||
|
||||
private final String hardcodedPassword = "correcthorsebatterystaple";
|
||||
|
||||
public Future<Option<String>> authenticate(BasicCredentials credentials) {
|
||||
// this is where your actual authentication logic would go
|
||||
if (credentials.available() && // no anonymous access
|
||||
credentials.verify(hardcodedPassword)) {
|
||||
return authenticateAs(credentials.identifier());
|
||||
} else {
|
||||
return refuseAccess();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Route route =
|
||||
authentication.route(
|
||||
handleWith1(
|
||||
authentication,
|
||||
new Handler1<String>() {
|
||||
public RouteResult apply(RequestContext ctx, String user) {
|
||||
return ctx.complete("Hello " + user + "!");
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// tests:
|
||||
final HttpRequest okRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.basic("randal", "correcthorsebatterystaple"));
|
||||
testRoute(route).run(okRequest).assertEntity("Hello randal!");
|
||||
|
||||
final HttpRequest badRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.basic("randal", "123abc"));
|
||||
testRoute(route).run(badRequest).assertStatusCode(401);
|
||||
|
||||
//#basic-authenticator-java
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.dispatch.OnFailure;
|
||||
import akka.http.impl.util.Util;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.IncomingConnection;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.model.ContentTypes;
|
||||
import akka.http.javadsl.model.HttpMethods;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.Uri;
|
||||
import akka.http.scaladsl.model.HttpEntity;
|
||||
import akka.japi.function.Function;
|
||||
import akka.japi.function.Procedure;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.stream.stage.Context;
|
||||
import akka.stream.stage.PushStage;
|
||||
import akka.stream.stage.SyncDirective;
|
||||
import akka.stream.stage.TerminationDirective;
|
||||
import akka.util.ByteString;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HttpServerExampleDocTest {
|
||||
|
||||
public static void bindingExample() throws Exception {
|
||||
//#binding-example
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, Future<ServerBinding>> serverSource =
|
||||
Http.get(system).bind("localhost", 8080, materializer);
|
||||
|
||||
Future<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(
|
||||
new Procedure<IncomingConnection>() {
|
||||
@Override
|
||||
public void apply(IncomingConnection connection) throws Exception {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
// ... and then actually handle the connection
|
||||
}
|
||||
})).run(materializer);
|
||||
//#binding-example
|
||||
Await.result(serverBindingFuture, new FiniteDuration(3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public static void bindingFailureExample() throws Exception {
|
||||
//#binding-failure-handling
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, Future<ServerBinding>> serverSource =
|
||||
Http.get(system).bind("localhost", 80, materializer);
|
||||
|
||||
Future<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(
|
||||
new Procedure<IncomingConnection>() {
|
||||
@Override
|
||||
public void apply(IncomingConnection connection) throws Exception {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
// ... and then actually handle the connection
|
||||
}
|
||||
})).run(materializer);
|
||||
|
||||
serverBindingFuture.onFailure(new OnFailure() {
|
||||
@Override
|
||||
public void onFailure(Throwable failure) throws Throwable {
|
||||
// possibly report the failure somewhere...
|
||||
}
|
||||
}, system.dispatcher());
|
||||
//#binding-failure-handling
|
||||
Await.result(serverBindingFuture, new FiniteDuration(3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public static void connectionSourceFailureExample() throws Exception {
|
||||
//#incoming-connections-source-failure-handling
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, Future<ServerBinding>> serverSource =
|
||||
Http.get(system).bind("localhost", 8080, materializer);
|
||||
|
||||
Flow<IncomingConnection, IncomingConnection, BoxedUnit> failureDetection =
|
||||
Flow.of(IncomingConnection.class).transform(() ->
|
||||
new PushStage<IncomingConnection, IncomingConnection>() {
|
||||
@Override
|
||||
public SyncDirective onPush(IncomingConnection elem, Context<IncomingConnection> ctx) {
|
||||
return ctx.push(elem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminationDirective onUpstreamFailure(Throwable cause, Context<IncomingConnection> ctx) {
|
||||
// signal the failure to external monitoring service!
|
||||
return super.onUpstreamFailure(cause, ctx);
|
||||
}
|
||||
});
|
||||
|
||||
Future<ServerBinding> serverBindingFuture =
|
||||
serverSource
|
||||
.via(failureDetection) // feed signals through our custom stage
|
||||
.to(Sink.foreach(
|
||||
new Procedure<IncomingConnection>() {
|
||||
@Override
|
||||
public void apply(IncomingConnection connection) throws Exception {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
// ... and then actually handle the connection
|
||||
}
|
||||
})).run(materializer);
|
||||
//#incoming-connections-source-failure-handling
|
||||
Await.result(serverBindingFuture, new FiniteDuration(3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public static void connectionStreamFailureExample() throws Exception {
|
||||
//#connection-stream-failure-handling
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, Future<ServerBinding>> serverSource =
|
||||
Http.get(system).bind("localhost", 8080, materializer);
|
||||
|
||||
Flow<HttpRequest, HttpRequest, BoxedUnit> failureDetection =
|
||||
Flow.of(HttpRequest.class).transform(() ->
|
||||
new PushStage<HttpRequest, HttpRequest>() {
|
||||
@Override
|
||||
public SyncDirective onPush(HttpRequest elem, Context<HttpRequest> ctx) {
|
||||
return ctx.push(elem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminationDirective onUpstreamFailure(Throwable cause, Context<HttpRequest> ctx) {
|
||||
// signal the failure to external monitoring service!
|
||||
return super.onUpstreamFailure(cause, ctx);
|
||||
}
|
||||
});
|
||||
|
||||
Flow<HttpRequest, HttpResponse, BoxedUnit> httpEcho =
|
||||
Flow.of(HttpRequest.class)
|
||||
.via(failureDetection)
|
||||
.map(request -> {
|
||||
Source<ByteString, Object> bytes = request.entity().getDataBytes();
|
||||
HttpEntity.Chunked entity = HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, bytes);
|
||||
|
||||
return HttpResponse.create()
|
||||
.withEntity(entity);
|
||||
});
|
||||
|
||||
Future<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(con -> {
|
||||
System.out.println("Accepted new connection from " + con.remoteAddress());
|
||||
con.handleWith(httpEcho, materializer);
|
||||
}
|
||||
)).run(materializer);
|
||||
//#connection-stream-failure-handling
|
||||
Await.result(serverBindingFuture, new FiniteDuration(3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
public static void fullServerExample() throws Exception {
|
||||
//#full-server-example
|
||||
ActorSystem system = ActorSystem.create();
|
||||
//#full-server-example
|
||||
try {
|
||||
//#full-server-example
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, Future<ServerBinding>> serverSource =
|
||||
Http.get(system).bind("localhost", 8080, materializer);
|
||||
|
||||
//#request-handler
|
||||
final Function<HttpRequest, HttpResponse> requestHandler =
|
||||
new Function<HttpRequest, HttpResponse>() {
|
||||
private final HttpResponse NOT_FOUND =
|
||||
HttpResponse.create()
|
||||
.withStatus(404)
|
||||
.withEntity("Unknown resource!");
|
||||
|
||||
|
||||
@Override
|
||||
public HttpResponse apply(HttpRequest request) throws Exception {
|
||||
Uri uri = request.getUri();
|
||||
if (request.method() == HttpMethods.GET) {
|
||||
if (uri.path().equals("/"))
|
||||
return
|
||||
HttpResponse.create()
|
||||
.withEntity(ContentTypes.TEXT_HTML_UTF8,
|
||||
"<html><body>Hello world!</body></html>");
|
||||
else if (uri.path().equals("/hello")) {
|
||||
String name = Util.getOrElse(uri.query().get("name"), "Mister X");
|
||||
|
||||
return
|
||||
HttpResponse.create()
|
||||
.withEntity("Hello " + name + "!");
|
||||
}
|
||||
else if (uri.path().equals("/ping"))
|
||||
return HttpResponse.create().withEntity("PONG!");
|
||||
else
|
||||
return NOT_FOUND;
|
||||
}
|
||||
else return NOT_FOUND;
|
||||
}
|
||||
};
|
||||
//#request-handler
|
||||
|
||||
Future<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(
|
||||
new Procedure<IncomingConnection>() {
|
||||
@Override
|
||||
public void apply(IncomingConnection connection) throws Exception {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
|
||||
connection.handleWithSyncHandler(requestHandler, materializer);
|
||||
// this is equivalent to
|
||||
//connection.handleWith(Flow.of(HttpRequest.class).map(requestHandler), materializer);
|
||||
}
|
||||
})).run(materializer);
|
||||
//#full-server-example
|
||||
|
||||
Await.result(serverBindingFuture, new FiniteDuration(1, TimeUnit.SECONDS)); // will throw if binding fails
|
||||
System.out.println("Press ENTER to stop.");
|
||||
new BufferedReader(new InputStreamReader(System.in)).readLine();
|
||||
} finally {
|
||||
system.shutdown();
|
||||
}
|
||||
}
|
||||
public static void main(String[] args) throws Exception {
|
||||
fullServerExample();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.model.headers.OAuth2BearerToken;
|
||||
import akka.http.javadsl.server.Handler1;
|
||||
import akka.http.javadsl.server.RequestContext;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.RouteResult;
|
||||
import akka.http.javadsl.server.values.BasicCredentials;
|
||||
import akka.http.javadsl.server.values.HttpBasicAuthenticator;
|
||||
import akka.http.javadsl.server.values.OAuth2Authenticator;
|
||||
import akka.http.javadsl.server.values.OAuth2Credentials;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.scaladsl.model.headers.Authorization;
|
||||
import org.junit.Test;
|
||||
import scala.Option;
|
||||
import scala.concurrent.Future;
|
||||
|
||||
public class OAuth2AuthenticatorExample extends JUnitRouteTest {
|
||||
|
||||
|
||||
@Test
|
||||
public void testOAuth2Authenticator() {
|
||||
//#oauth2-authenticator-java
|
||||
final OAuth2Authenticator<String> authentication = new OAuth2Authenticator<String>("My realm") {
|
||||
|
||||
private final String hardcodedToken = "token";
|
||||
|
||||
@Override
|
||||
public Future<Option<String>> authenticate(OAuth2Credentials credentials) {
|
||||
// this is where your actual authentication logic would go, looking up the user
|
||||
// based on the token or something in that direction
|
||||
if (credentials.available() && // no anonymous access
|
||||
credentials.verify(hardcodedToken)) {
|
||||
// not a secret + identity pair, so this is actually the token
|
||||
return authenticateAs(credentials.identifier());
|
||||
} else {
|
||||
return refuseAccess();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
final Route route =
|
||||
authentication.route(
|
||||
handleWith1(
|
||||
authentication,
|
||||
new Handler1<String>() {
|
||||
public RouteResult apply(RequestContext ctx, String token) {
|
||||
return ctx.complete("The secret token is: " + token);
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// tests:
|
||||
final HttpRequest okRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.oauth2("token"));
|
||||
testRoute(route).run(okRequest).assertEntity("The secret token is: token");
|
||||
|
||||
final HttpRequest badRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.oauth2("wrong"));
|
||||
testRoute(route).run(badRequest).assertStatusCode(401);
|
||||
|
||||
//#oauth2-authenticator-java
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.Handler1;
|
||||
import akka.http.javadsl.server.values.PathMatcher;
|
||||
import akka.http.javadsl.server.values.PathMatchers;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Test;
|
||||
|
||||
public class PathDirectiveExampleTest extends JUnitRouteTest {
|
||||
@Test
|
||||
public void testPathPrefix() {
|
||||
//#path-examples
|
||||
// matches "/test"
|
||||
path("test").route(
|
||||
completeWithStatus(StatusCodes.OK)
|
||||
);
|
||||
|
||||
// matches "/test", as well
|
||||
path(PathMatchers.segment("test")).route(
|
||||
completeWithStatus(StatusCodes.OK)
|
||||
);
|
||||
|
||||
// matches "/admin/user"
|
||||
path("admin", "user").route(
|
||||
completeWithStatus(StatusCodes.OK)
|
||||
);
|
||||
|
||||
// matches "/admin/user", as well
|
||||
pathPrefix("admin").route(
|
||||
path("user").route(
|
||||
completeWithStatus(StatusCodes.OK)
|
||||
)
|
||||
);
|
||||
|
||||
// matches "/admin/user/<user-id>"
|
||||
Handler1<Integer> completeWithUserId =
|
||||
(ctx, userId) -> ctx.complete("Hello user " + userId);
|
||||
PathMatcher<Integer> userId = PathMatchers.intValue();
|
||||
pathPrefix("admin", "user").route(
|
||||
path(userId).route(
|
||||
handleWith1(userId, completeWithUserId)
|
||||
)
|
||||
);
|
||||
|
||||
// matches "/admin/user/<user-id>", as well
|
||||
path("admin", "user", userId).route(
|
||||
handleWith1(userId, completeWithUserId)
|
||||
);
|
||||
|
||||
// never matches
|
||||
path("admin").route( // oops this only matches "/admin"
|
||||
path("user").route(
|
||||
completeWithStatus(StatusCodes.OK)
|
||||
)
|
||||
);
|
||||
|
||||
// matches "/user/" with the first subroute, "/user" (without a trailing slash)
|
||||
// with the second subroute, and "/user/<user-id>" with the last one.
|
||||
pathPrefix("user").route(
|
||||
pathSingleSlash().route(
|
||||
completeWithStatus(StatusCodes.OK)
|
||||
),
|
||||
pathEnd().route(
|
||||
completeWithStatus(StatusCodes.OK)
|
||||
),
|
||||
path(userId).route(
|
||||
handleWith1(userId, completeWithUserId)
|
||||
)
|
||||
);
|
||||
//#path-examples
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
//#websocket-example-using-core
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import akka.japi.Function;
|
||||
import akka.japi.JavaPartialFunction;
|
||||
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Source;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
import akka.http.javadsl.model.ws.Websocket;
|
||||
|
||||
public class WebsocketCoreExample {
|
||||
//#websocket-handling
|
||||
public static HttpResponse handleRequest(HttpRequest request) {
|
||||
System.out.println("Handling request to " + request.getUri());
|
||||
|
||||
if (request.getUri().path().equals("/greeter"))
|
||||
return Websocket.handleWebsocketRequestWith(request, greeter());
|
||||
else
|
||||
return HttpResponse.create().withStatus(404);
|
||||
}
|
||||
//#websocket-handling
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ActorSystem system = ActorSystem.create();
|
||||
|
||||
try {
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Future<ServerBinding> serverBindingFuture =
|
||||
Http.get(system).bindAndHandleSync(
|
||||
new Function<HttpRequest, HttpResponse>() {
|
||||
public HttpResponse apply(HttpRequest request) throws Exception {
|
||||
return handleRequest(request);
|
||||
}
|
||||
}, "localhost", 8080, materializer);
|
||||
|
||||
// will throw if binding fails
|
||||
Await.result(serverBindingFuture, new FiniteDuration(1, TimeUnit.SECONDS));
|
||||
System.out.println("Press ENTER to stop.");
|
||||
new BufferedReader(new InputStreamReader(System.in)).readLine();
|
||||
} finally {
|
||||
system.shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
//#websocket-handler
|
||||
/**
|
||||
* A handler that treats incoming messages as a name,
|
||||
* and responds with a greeting to that name
|
||||
*/
|
||||
public static Flow<Message, Message, BoxedUnit> greeter() {
|
||||
return
|
||||
Flow.<Message>create()
|
||||
.collect(new JavaPartialFunction<Message, Message>() {
|
||||
@Override
|
||||
public Message apply(Message msg, boolean isCheck) throws Exception {
|
||||
if (isCheck)
|
||||
if (msg.isText()) return null;
|
||||
else throw noMatch();
|
||||
else
|
||||
return handleTextMessage(msg.asTextMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
public static TextMessage handleTextMessage(TextMessage msg) {
|
||||
if (msg.isStrict()) // optimization that directly creates a simple response...
|
||||
return TextMessage.create("Hello "+msg.getStrictText());
|
||||
else // ... this would suffice to handle all text messages in a streaming fashion
|
||||
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
|
||||
}
|
||||
//#websocket-handler
|
||||
}
|
||||
//#websocket-example-using-core
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.server.Route;
|
||||
|
||||
import akka.japi.JavaPartialFunction;
|
||||
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Source;
|
||||
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
|
||||
import akka.http.javadsl.server.HttpApp;
|
||||
|
||||
public class WebsocketRoutingExample extends HttpApp {
|
||||
//#websocket-route
|
||||
@Override
|
||||
public Route createRoute() {
|
||||
return
|
||||
path("greeter").route(
|
||||
handleWebsocketMessages(greeter())
|
||||
);
|
||||
}
|
||||
//#websocket-route
|
||||
|
||||
/**
|
||||
* A handler that treats incoming messages as a name,
|
||||
* and responds with a greeting to that name
|
||||
*/
|
||||
public static Flow<Message, Message, ?> greeter() {
|
||||
return
|
||||
Flow.<Message>create()
|
||||
.collect(new JavaPartialFunction<Message, Message>() {
|
||||
@Override
|
||||
public Message apply(Message msg, boolean isCheck) throws Exception {
|
||||
if (isCheck)
|
||||
if (msg.isText()) return null;
|
||||
else throw noMatch();
|
||||
else
|
||||
return handleTextMessage(msg.asTextMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
public static TextMessage handleTextMessage(TextMessage msg) {
|
||||
if (msg.isStrict()) // optimization that directly creates a simple response...
|
||||
return TextMessage.create("Hello "+msg.getStrictText());
|
||||
else // ... this would suffice to handle all text messages in a streaming fashion
|
||||
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import akka.http.javadsl.model.HttpMethod;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.server.*;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class HostDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testListOfHost() {
|
||||
//#host1
|
||||
final Route matchListOfHosts = host(
|
||||
Arrays.asList("api.company.com", "rest.company.com"),
|
||||
completeWithStatus(StatusCodes.OK));
|
||||
|
||||
testRoute(matchListOfHosts).run(HttpRequest.GET("/").addHeader(Host.create("api.company.com")))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#host1
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostPredicate() {
|
||||
//#host2
|
||||
final Route shortOnly = host(hostname -> hostname.length() < 10,
|
||||
completeWithStatus(StatusCodes.OK));
|
||||
|
||||
testRoute(shortOnly).run(HttpRequest.GET("/").addHeader(Host.create("short.com")))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
|
||||
testRoute(shortOnly).run(HttpRequest.GET("/").addHeader(Host.create("verylonghostname.com")))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
//#host2
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHost() {
|
||||
//#extractHostname
|
||||
final RequestVal<String> host = RequestVals.host();
|
||||
|
||||
final Route route = handleWith1(host,
|
||||
(ctx, hn) -> ctx.complete("Hostname: " + hn));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("company.com", 9090)))
|
||||
.assertEntity("Hostname: company.com");
|
||||
//#extractHostname
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchAndExtractHost() {
|
||||
//#matchAndExtractHost
|
||||
final RequestVal<String> hostPrefix = RequestVals
|
||||
.matchAndExtractHost(Pattern.compile("api|rest"));
|
||||
|
||||
final Route hostPrefixRoute = handleWith1(hostPrefix,
|
||||
(ctx, prefix) -> ctx.complete("Extracted prefix: " + prefix));
|
||||
|
||||
final RequestVal<String> hostPart = RequestVals.matchAndExtractHost(Pattern
|
||||
.compile("public.(my|your)company.com"));
|
||||
|
||||
final Route hostPartRoute = handleWith1(
|
||||
hostPart,
|
||||
(ctx, captured) -> ctx.complete("You came through " + captured
|
||||
+ " company"));
|
||||
|
||||
final Route route = route(hostPrefixRoute, hostPartRoute);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("api.company.com")))
|
||||
.assertStatusCode(StatusCodes.OK).assertEntity("Extracted prefix: api");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("public.mycompany.com")))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("You came through my company");
|
||||
//#matchAndExtractHost
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testFailingMatchAndExtractHost() {
|
||||
//#failing-matchAndExtractHost
|
||||
// this will throw IllegalArgumentException
|
||||
final RequestVal<String> hostRegex = RequestVals
|
||||
.matchAndExtractHost(Pattern
|
||||
.compile("server-([0-9]).company.(com|net|org)"));
|
||||
//#failing-matchAndExtractHost
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpMethod;
|
||||
import akka.http.javadsl.model.HttpMethods;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.*;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class MethodDirectivesExamplesTest extends JUnitRouteTest {
|
||||
@Test
|
||||
public void testDelete() {
|
||||
//#delete
|
||||
final Route route = delete(complete("This is a DELETE request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.DELETE("/")).assertEntity(
|
||||
"This is a DELETE request.");
|
||||
//#delete
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
//#get
|
||||
final Route route = get(complete("This is a GET request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity(
|
||||
"This is a GET request.");
|
||||
//#get
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHead() {
|
||||
//#head
|
||||
final Route route = head(complete("This is a HEAD request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.HEAD("/")).assertEntity(
|
||||
"This is a HEAD request.");
|
||||
//#head
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptions() {
|
||||
//#options
|
||||
final Route route = options(complete("This is a OPTIONS request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.OPTIONS("/")).assertEntity(
|
||||
"This is a OPTIONS request.");
|
||||
//#options
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatch() {
|
||||
//#patch
|
||||
final Route route = patch(complete("This is a PATCH request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.PATCH("/").withEntity("patch content"))
|
||||
.assertEntity("This is a PATCH request.");
|
||||
//#patch
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPost() {
|
||||
//#post
|
||||
final Route route = post(complete("This is a POST request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity("post content"))
|
||||
.assertEntity("This is a POST request.");
|
||||
//#post
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() {
|
||||
//#put
|
||||
final Route route = put(complete("This is a PUT request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.PUT("/").withEntity("put content"))
|
||||
.assertEntity("This is a PUT request.");
|
||||
//#put
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodExample() {
|
||||
//#method-example
|
||||
final Route route = method(HttpMethods.PUT,
|
||||
complete("This is a PUT request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.PUT("/").withEntity("put content"))
|
||||
.assertEntity("This is a PUT request.");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertStatusCode(
|
||||
StatusCodes.METHOD_NOT_ALLOWED);
|
||||
//#method-example
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractMethodExample() {
|
||||
//#extractMethod
|
||||
final RequestVal<HttpMethod> requestMethod = RequestVals.requestMethod();
|
||||
|
||||
final Route otherMethod = handleWith1(
|
||||
requestMethod,
|
||||
(ctx, method) -> ctx.complete("This " + method.value()
|
||||
+ " request, clearly is not a GET!"));
|
||||
|
||||
final Route route = route(get(complete("This is a GET request.")),
|
||||
otherMethod);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity(
|
||||
"This is a GET request.");
|
||||
|
||||
testRoute(route).run(HttpRequest.PUT("/").withEntity("put content"))
|
||||
.assertEntity("This PUT request, clearly is not a GET!");
|
||||
|
||||
testRoute(route).run(HttpRequest.HEAD("/")).assertEntity(
|
||||
"This HEAD request, clearly is not a GET!");
|
||||
//#extractMethod
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.testkit;
|
||||
|
||||
//#simple-app
|
||||
import akka.http.javadsl.server.*;
|
||||
import akka.http.javadsl.server.values.Parameters;
|
||||
|
||||
public class MyAppService extends HttpApp {
|
||||
RequestVal<Double> x = Parameters.doubleValue("x");
|
||||
RequestVal<Double> y = Parameters.doubleValue("y");
|
||||
|
||||
public RouteResult add(RequestContext ctx, double x, double y) {
|
||||
return ctx.complete("x + y = " + (x + y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Route createRoute() {
|
||||
return
|
||||
route(
|
||||
get(
|
||||
pathPrefix("calculator").route(
|
||||
path("add").route(
|
||||
handleReflectively(this, "add", x, y)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
//#simple-app
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.testkit;
|
||||
|
||||
//#simple-app-testing
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.javadsl.testkit.TestRoute;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestkitExampleTest extends JUnitRouteTest {
|
||||
TestRoute appRoute = testRoute(new MyAppService().createRoute());
|
||||
|
||||
@Test
|
||||
public void testCalculatorAdd() {
|
||||
// test happy path
|
||||
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
|
||||
.assertStatusCode(200)
|
||||
.assertEntity("x + y = 6.5");
|
||||
|
||||
// test responses to potential errors
|
||||
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND) // 404
|
||||
.assertEntity("Request is missing required query parameter 'y'");
|
||||
|
||||
// test responses to potential errors
|
||||
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2&y=three"))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST)
|
||||
.assertEntity("The query parameter 'y' was malformed:\n" +
|
||||
"'three' is not a valid 64-bit floating point value");
|
||||
}
|
||||
}
|
||||
//#simple-app-testing
|
||||
150
akka-docs/rst/java/code/docs/stream/ActorPublisherDocTest.java
Normal file
150
akka-docs/rst/java/code/docs/stream/ActorPublisherDocTest.java
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.actor.AbstractActorPublisher;
|
||||
import akka.stream.actor.ActorPublisherMessage;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ActorPublisherDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("ActorPublisherDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
//#job-manager
|
||||
public static class JobManagerProtocol {
|
||||
final public static class Job {
|
||||
public final String payload;
|
||||
|
||||
public Job(String payload) {
|
||||
this.payload = payload;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class JobAcceptedMessage {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JobAccepted";
|
||||
}
|
||||
}
|
||||
public static final JobAcceptedMessage JobAccepted = new JobAcceptedMessage();
|
||||
|
||||
public static class JobDeniedMessage {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JobDenied";
|
||||
}
|
||||
}
|
||||
public static final JobDeniedMessage JobDenied = new JobDeniedMessage();
|
||||
}
|
||||
public static class JobManager extends AbstractActorPublisher<JobManagerProtocol.Job> {
|
||||
|
||||
public static Props props() { return Props.create(JobManager.class); }
|
||||
|
||||
private final int MAX_BUFFER_SIZE = 100;
|
||||
private final List<JobManagerProtocol.Job> buf = new ArrayList<>();
|
||||
|
||||
public JobManager() {
|
||||
receive(ReceiveBuilder.
|
||||
match(JobManagerProtocol.Job.class, job -> buf.size() == MAX_BUFFER_SIZE, job -> {
|
||||
sender().tell(JobManagerProtocol.JobDenied, self());
|
||||
}).
|
||||
match(JobManagerProtocol.Job.class, job -> {
|
||||
sender().tell(JobManagerProtocol.JobAccepted, self());
|
||||
|
||||
if (buf.isEmpty() && totalDemand() > 0)
|
||||
onNext(job);
|
||||
else {
|
||||
buf.add(job);
|
||||
deliverBuf();
|
||||
}
|
||||
}).
|
||||
match(ActorPublisherMessage.Request.class, request -> deliverBuf()).
|
||||
match(ActorPublisherMessage.Cancel.class, cancel -> context().stop(self())).
|
||||
build());
|
||||
}
|
||||
|
||||
void deliverBuf() {
|
||||
while (totalDemand() > 0) {
|
||||
/*
|
||||
* totalDemand is a Long and could be larger than
|
||||
* what buf.splitAt can accept
|
||||
*/
|
||||
if (totalDemand() <= Integer.MAX_VALUE) {
|
||||
final List<JobManagerProtocol.Job> took =
|
||||
buf.subList(0, Math.min(buf.size(), (int) totalDemand()));
|
||||
took.forEach(this::onNext);
|
||||
buf.removeAll(took);
|
||||
break;
|
||||
} else {
|
||||
final List<JobManagerProtocol.Job> took =
|
||||
buf.subList(0, Math.min(buf.size(), Integer.MAX_VALUE));
|
||||
took.forEach(this::onNext);
|
||||
buf.removeAll(took);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//#job-manager
|
||||
|
||||
@Test
|
||||
public void demonstrateActorPublisherUsage() {
|
||||
new JavaTestKit(system) {
|
||||
private final SilenceSystemOut.System System = SilenceSystemOut.get(getTestActor());
|
||||
|
||||
{
|
||||
//#actor-publisher-usage
|
||||
final Source<JobManagerProtocol.Job, ActorRef> jobManagerSource =
|
||||
Source.actorPublisher(JobManager.props());
|
||||
|
||||
final ActorRef ref = jobManagerSource
|
||||
.map(job -> job.payload.toUpperCase())
|
||||
.map(elem -> {
|
||||
System.out.println(elem);
|
||||
return elem;
|
||||
})
|
||||
.to(Sink.ignore())
|
||||
.run(mat);
|
||||
|
||||
ref.tell(new JobManagerProtocol.Job("a"), ActorRef.noSender());
|
||||
ref.tell(new JobManagerProtocol.Job("b"), ActorRef.noSender());
|
||||
ref.tell(new JobManagerProtocol.Job("c"), ActorRef.noSender());
|
||||
//#actor-publisher-usage
|
||||
|
||||
expectMsgEquals("A");
|
||||
expectMsgEquals("B");
|
||||
expectMsgEquals("C");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
233
akka-docs/rst/java/code/docs/stream/ActorSubscriberDocTest.java
Normal file
233
akka-docs/rst/java/code/docs/stream/ActorSubscriberDocTest.java
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.stream;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.routing.ActorRefRoutee;
|
||||
import akka.routing.RoundRobinRoutingLogic;
|
||||
import akka.routing.Routee;
|
||||
import akka.routing.Router;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.actor.AbstractActorSubscriber;
|
||||
import akka.stream.actor.ActorSubscriberMessage;
|
||||
import akka.stream.actor.MaxInFlightRequestStrategy;
|
||||
import akka.stream.actor.RequestStrategy;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class ActorSubscriberDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("ActorSubscriberDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
//#worker-pool
|
||||
public static class WorkerPoolProtocol {
|
||||
|
||||
public static class Msg {
|
||||
public final int id;
|
||||
public final ActorRef replyTo;
|
||||
|
||||
public Msg(int id, ActorRef replyTo) {
|
||||
this.id = id;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Msg(%s, %s)", id, replyTo);
|
||||
}
|
||||
}
|
||||
public static Msg msg(int id, ActorRef replyTo) {
|
||||
return new Msg(id, replyTo);
|
||||
}
|
||||
|
||||
|
||||
public static class Work {
|
||||
public final int id;
|
||||
public Work(int id) { this.id = id; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Work(%s)", id);
|
||||
}
|
||||
}
|
||||
public static Work work(int id) {
|
||||
return new Work(id);
|
||||
}
|
||||
|
||||
|
||||
public static class Reply {
|
||||
public final int id;
|
||||
public Reply(int id) { this.id = id; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Reply(%s)", id);
|
||||
}
|
||||
}
|
||||
public static Reply reply(int id) {
|
||||
return new Reply(id);
|
||||
}
|
||||
|
||||
|
||||
public static class Done {
|
||||
public final int id;
|
||||
public Done(int id) { this.id = id; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Done(%s)", id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Done done = (Done) o;
|
||||
|
||||
if (id != done.id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
public static Done done(int id) {
|
||||
return new Done(id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class WorkerPool extends AbstractActorSubscriber {
|
||||
|
||||
public static Props props() { return Props.create(WorkerPool.class); }
|
||||
|
||||
final int MAX_QUEUE_SIZE = 10;
|
||||
final Map<Integer, ActorRef> queue = new HashMap<>();
|
||||
|
||||
final Router router;
|
||||
|
||||
@Override
|
||||
public RequestStrategy requestStrategy() {
|
||||
return new MaxInFlightRequestStrategy(MAX_QUEUE_SIZE) {
|
||||
@Override
|
||||
public int inFlightInternally() {
|
||||
return queue.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public WorkerPool() {
|
||||
final List<Routee> routees = new ArrayList<>();
|
||||
for (int i = 0; i < 3; i++)
|
||||
routees.add(new ActorRefRoutee(context().actorOf(Props.create(Worker.class))));
|
||||
router = new Router(new RoundRobinRoutingLogic(), routees);
|
||||
|
||||
receive(ReceiveBuilder.
|
||||
match(ActorSubscriberMessage.OnNext.class, on -> on.element() instanceof WorkerPoolProtocol.Msg,
|
||||
onNext -> {
|
||||
WorkerPoolProtocol.Msg msg = (WorkerPoolProtocol.Msg) onNext.element();
|
||||
queue.put(msg.id, msg.replyTo);
|
||||
|
||||
if (queue.size() > MAX_QUEUE_SIZE)
|
||||
throw new RuntimeException("queued too many: " + queue.size());
|
||||
|
||||
router.route(WorkerPoolProtocol.work(msg.id), self());
|
||||
}).
|
||||
match(WorkerPoolProtocol.Reply.class, reply -> {
|
||||
int id = reply.id;
|
||||
queue.get(id).tell(WorkerPoolProtocol.done(id), self());
|
||||
queue.remove(id);
|
||||
}).
|
||||
build());
|
||||
}
|
||||
}
|
||||
|
||||
static class Worker extends AbstractActor {
|
||||
public Worker() {
|
||||
receive(ReceiveBuilder.
|
||||
match(WorkerPoolProtocol.Work.class, work -> {
|
||||
// ...
|
||||
sender().tell(WorkerPoolProtocol.reply(work.id), self());
|
||||
}).build());
|
||||
}
|
||||
}
|
||||
//#worker-pool
|
||||
|
||||
@Test
|
||||
public void demonstrateActorPublisherUsage() {
|
||||
new JavaTestKit(system) {
|
||||
|
||||
{
|
||||
final ActorRef replyTo = getTestActor();
|
||||
|
||||
//#actor-subscriber-usage
|
||||
final int N = 117;
|
||||
final List<Integer> data = new ArrayList<>(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
data.add(i);
|
||||
}
|
||||
|
||||
Source.from(data)
|
||||
.map(i -> WorkerPoolProtocol.msg(i, replyTo))
|
||||
.runWith(Sink.<WorkerPoolProtocol.Msg>actorSubscriber(WorkerPool.props()), mat);
|
||||
//#actor-subscriber-usage
|
||||
|
||||
List<Object> got = Arrays.asList(receiveN(N));
|
||||
Collections.sort(got, new Comparator<Object>() {
|
||||
@Override
|
||||
public int compare(Object o1, Object o2) {
|
||||
if (o1 instanceof WorkerPoolProtocol.Done && o2 instanceof WorkerPoolProtocol.Done) {
|
||||
return ((WorkerPoolProtocol.Done) o1).id - ((WorkerPoolProtocol.Done) o2).id;
|
||||
} else return 0;
|
||||
}
|
||||
});
|
||||
int i = 0;
|
||||
for (; i < N; i++) {
|
||||
assertEquals(String.format("Expected %d, but got %s", i, got.get(i)), WorkerPoolProtocol.done(i), got.get(i));
|
||||
}
|
||||
assertEquals(String.format("Expected 117 messages but got %d", i), i, 117);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
236
akka-docs/rst/java/code/docs/stream/BidiFlowDocTest.java
Normal file
236
akka-docs/rst/java/code/docs/stream/BidiFlowDocTest.java
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.stream.javadsl.GraphDSL;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.pf.PFBuilder;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.stage.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteIterator;
|
||||
import akka.util.ByteString;
|
||||
import akka.util.ByteStringBuilder;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
|
||||
public class BidiFlowDocTest {
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
//#codec
|
||||
static interface Message {}
|
||||
static class Ping implements Message {
|
||||
final int id;
|
||||
public Ping(int id) { this.id = id; }
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Ping) {
|
||||
return ((Ping) o).id == id;
|
||||
} else return false;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
static class Pong implements Message {
|
||||
final int id;
|
||||
public Pong(int id) { this.id = id; }
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof Pong) {
|
||||
return ((Pong) o).id == id;
|
||||
} else return false;
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
//#codec-impl
|
||||
public static ByteString toBytes(Message msg) {
|
||||
//#implementation-details-elided
|
||||
if (msg instanceof Ping) {
|
||||
final int id = ((Ping) msg).id;
|
||||
return new ByteStringBuilder().putByte((byte) 1)
|
||||
.putInt(id, ByteOrder.LITTLE_ENDIAN).result();
|
||||
} else {
|
||||
final int id = ((Pong) msg).id;
|
||||
return new ByteStringBuilder().putByte((byte) 2)
|
||||
.putInt(id, ByteOrder.LITTLE_ENDIAN).result();
|
||||
}
|
||||
//#implementation-details-elided
|
||||
}
|
||||
|
||||
public static Message fromBytes(ByteString bytes) {
|
||||
//#implementation-details-elided
|
||||
final ByteIterator it = bytes.iterator();
|
||||
switch(it.getByte()) {
|
||||
case 1:
|
||||
return new Ping(it.getInt(ByteOrder.LITTLE_ENDIAN));
|
||||
case 2:
|
||||
return new Pong(it.getInt(ByteOrder.LITTLE_ENDIAN));
|
||||
default:
|
||||
throw new RuntimeException("message format error");
|
||||
}
|
||||
//#implementation-details-elided
|
||||
}
|
||||
//#codec-impl
|
||||
|
||||
//#codec
|
||||
@SuppressWarnings("unused")
|
||||
//#codec
|
||||
public final BidiFlow<Message, ByteString, ByteString, Message, BoxedUnit> codecVerbose =
|
||||
BidiFlow.fromGraph(GraphDSL.create(b -> {
|
||||
final FlowShape<Message, ByteString> top =
|
||||
b.add(Flow.of(Message.class).map(BidiFlowDocTest::toBytes));
|
||||
final FlowShape<ByteString, Message> bottom =
|
||||
b.add(Flow.of(ByteString.class).map(BidiFlowDocTest::fromBytes));
|
||||
return BidiShape.fromFlows(top, bottom);
|
||||
}));
|
||||
|
||||
public final BidiFlow<Message, ByteString, ByteString, Message, BoxedUnit> codec =
|
||||
BidiFlow.fromFunctions(BidiFlowDocTest::toBytes, BidiFlowDocTest::fromBytes);
|
||||
//#codec
|
||||
|
||||
//#framing
|
||||
public static ByteString addLengthHeader(ByteString bytes) {
|
||||
final int len = bytes.size();
|
||||
return new ByteStringBuilder()
|
||||
.putInt(len, ByteOrder.LITTLE_ENDIAN)
|
||||
.append(bytes)
|
||||
.result();
|
||||
}
|
||||
|
||||
public static class FrameParser extends PushPullStage<ByteString, ByteString> {
|
||||
// this holds the received but not yet parsed bytes
|
||||
private ByteString stash = ByteString.empty();
|
||||
// this holds the current message length or -1 if at a boundary
|
||||
private int needed = -1;
|
||||
|
||||
@Override
|
||||
public SyncDirective onPull(Context<ByteString> ctx) {
|
||||
return run(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncDirective onPush(ByteString bytes, Context<ByteString> ctx) {
|
||||
stash = stash.concat(bytes);
|
||||
return run(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminationDirective onUpstreamFinish(Context<ByteString> ctx) {
|
||||
if (stash.isEmpty()) return ctx.finish();
|
||||
else return ctx.absorbTermination(); // we still have bytes to emit
|
||||
}
|
||||
|
||||
private SyncDirective run(Context<ByteString> ctx) {
|
||||
if (needed == -1) {
|
||||
// are we at a boundary? then figure out next length
|
||||
if (stash.size() < 4) return pullOrFinish(ctx);
|
||||
else {
|
||||
needed = stash.iterator().getInt(ByteOrder.LITTLE_ENDIAN);
|
||||
stash = stash.drop(4);
|
||||
return run(ctx); // cycle back to possibly already emit the next chunk
|
||||
}
|
||||
} else if (stash.size() < needed) {
|
||||
// we are in the middle of a message, need more bytes
|
||||
return pullOrFinish(ctx);
|
||||
} else {
|
||||
// we have enough to emit at least one message, so do it
|
||||
final ByteString emit = stash.take(needed);
|
||||
stash = stash.drop(needed);
|
||||
needed = -1;
|
||||
return ctx.push(emit);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* After having called absorbTermination() we cannot pull any more, so if we need
|
||||
* more data we will just have to give up.
|
||||
*/
|
||||
private SyncDirective pullOrFinish(Context<ByteString> ctx) {
|
||||
if (ctx.isFinishing()) return ctx.finish();
|
||||
else return ctx.pull();
|
||||
}
|
||||
}
|
||||
|
||||
public final BidiFlow<ByteString, ByteString, ByteString, ByteString, BoxedUnit> framing =
|
||||
BidiFlow.fromGraph(GraphDSL.create(b -> {
|
||||
final FlowShape<ByteString, ByteString> top =
|
||||
b.add(Flow.of(ByteString.class).map(BidiFlowDocTest::addLengthHeader));
|
||||
final FlowShape<ByteString, ByteString> bottom =
|
||||
b.add(Flow.of(ByteString.class).transform(() -> new FrameParser()));
|
||||
return BidiShape.fromFlows(top, bottom);
|
||||
}));
|
||||
//#framing
|
||||
|
||||
@Test
|
||||
public void mustCompose() throws Exception {
|
||||
//#compose
|
||||
/* construct protocol stack
|
||||
* +------------------------------------+
|
||||
* | stack |
|
||||
* | |
|
||||
* | +-------+ +---------+ |
|
||||
* ~> O~~o | ~> | o~~O ~>
|
||||
* Message | | codec | ByteString | framing | | ByteString
|
||||
* <~ O~~o | <~ | o~~O <~
|
||||
* | +-------+ +---------+ |
|
||||
* +------------------------------------+
|
||||
*/
|
||||
final BidiFlow<Message, ByteString, ByteString, Message, BoxedUnit> stack =
|
||||
codec.atop(framing);
|
||||
|
||||
// test it by plugging it into its own inverse and closing the right end
|
||||
final Flow<Message, Message, BoxedUnit> pingpong =
|
||||
Flow.of(Message.class).collect(new PFBuilder<Message, Message>()
|
||||
.match(Ping.class, p -> new Pong(p.id))
|
||||
.build()
|
||||
);
|
||||
final Flow<Message, Message, BoxedUnit> flow =
|
||||
stack.atop(stack.reversed()).join(pingpong);
|
||||
final Future<List<Message>> result = Source
|
||||
.from(Arrays.asList(0, 1, 2))
|
||||
.<Message> map(id -> new Ping(id))
|
||||
.via(flow)
|
||||
.grouped(10)
|
||||
.runWith(Sink.<List<Message>> head(), mat);
|
||||
final FiniteDuration oneSec = Duration.create(1, TimeUnit.SECONDS);
|
||||
assertArrayEquals(
|
||||
new Message[] { new Pong(0), new Pong(1), new Pong(2) },
|
||||
Await.result(result, oneSec).toArray(new Message[0]));
|
||||
//#compose
|
||||
}
|
||||
}
|
||||
308
akka-docs/rst/java/code/docs/stream/CompositionDocTest.java
Normal file
308
akka-docs/rst/java/code/docs/stream/CompositionDocTest.java
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import akka.stream.ClosedShape;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.dispatch.Mapper;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.javadsl.Tcp.OutgoingConnection;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteString;
|
||||
import scala.concurrent.*;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import scala.Option;
|
||||
|
||||
public class CompositionDocTest {
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void nonNestedFlow() throws Exception {
|
||||
//#non-nested-flow
|
||||
Source.single(0)
|
||||
.map(i -> i + 1)
|
||||
.filter(i -> i != 0)
|
||||
.map(i -> i - 2)
|
||||
.to(Sink.fold(0, (acc, i) -> acc + i));
|
||||
|
||||
// ... where is the nesting?
|
||||
//#non-nested-flow
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nestedFlow() throws Exception {
|
||||
//#nested-flow
|
||||
final Source<Integer, BoxedUnit> nestedSource =
|
||||
Source.single(0) // An atomic source
|
||||
.map(i -> i + 1) // an atomic processing stage
|
||||
.named("nestedSource"); // wraps up the current Source and gives it a name
|
||||
|
||||
final Flow<Integer, Integer, BoxedUnit> nestedFlow =
|
||||
Flow.of(Integer.class).filter(i -> i != 0) // an atomic processing stage
|
||||
.map(i -> i - 2) // another atomic processing stage
|
||||
.named("nestedFlow"); // wraps up the Flow, and gives it a name
|
||||
|
||||
final Sink<Integer, BoxedUnit> nestedSink =
|
||||
nestedFlow.to(Sink.fold(0, (acc, i) -> acc + i)) // wire an atomic sink to the nestedFlow
|
||||
.named("nestedSink"); // wrap it up
|
||||
|
||||
// Create a RunnableGraph
|
||||
final RunnableGraph<BoxedUnit> runnableGraph = nestedSource.to(nestedSink);
|
||||
//#nested-flow
|
||||
}
|
||||
|
||||
@Test
|
||||
public void reusingComponents() throws Exception {
|
||||
final Source<Integer, BoxedUnit> nestedSource =
|
||||
Source.single(0) // An atomic source
|
||||
.map(i -> i + 1) // an atomic processing stage
|
||||
.named("nestedSource"); // wraps up the current Source and gives it a name
|
||||
|
||||
final Flow<Integer, Integer, BoxedUnit> nestedFlow =
|
||||
Flow.of(Integer.class).filter(i -> i != 0) // an atomic processing stage
|
||||
.map(i -> i - 2) // another atomic processing stage
|
||||
.named("nestedFlow"); // wraps up the Flow, and gives it a name
|
||||
|
||||
final Sink<Integer, BoxedUnit> nestedSink =
|
||||
nestedFlow.to(Sink.fold(0, (acc, i) -> acc + i)) // wire an atomic sink to the nestedFlow
|
||||
.named("nestedSink"); // wrap it up
|
||||
|
||||
//#reuse
|
||||
// Create a RunnableGraph from our components
|
||||
final RunnableGraph<BoxedUnit> runnableGraph = nestedSource.to(nestedSink);
|
||||
|
||||
// Usage is uniform, no matter if modules are composite or atomic
|
||||
final RunnableGraph<BoxedUnit> runnableGraph2 =
|
||||
Source.single(0).to(Sink.fold(0, (acc, i) -> acc + i));
|
||||
//#reuse
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexGraph() throws Exception {
|
||||
//#complex-graph
|
||||
RunnableGraph.fromGraph(
|
||||
GraphDSL.create(builder -> {
|
||||
final Outlet<Integer> A = builder.add(Source.single(0)).out();
|
||||
final UniformFanOutShape<Integer, Integer> B = builder.add(Broadcast.create(2));
|
||||
final UniformFanInShape<Integer, Integer> C = builder.add(Merge.create(2));
|
||||
final FlowShape<Integer, Integer> D =
|
||||
builder.add(Flow.of(Integer.class).map(i -> i + 1));
|
||||
final UniformFanOutShape<Integer, Integer> E = builder.add(Balance.create(2));
|
||||
final UniformFanInShape<Integer, Integer> F = builder.add(Merge.create(2));
|
||||
final Inlet<Integer> G = builder.add(Sink.<Integer> foreach(System.out::println)).in();
|
||||
|
||||
builder.from(F).toFanIn(C);
|
||||
builder.from(A).viaFanOut(B).viaFanIn(C).toFanIn(F);
|
||||
builder.from(B).via(D).viaFanOut(E).toFanIn(F);
|
||||
builder.from(E).toInlet(G);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#complex-graph
|
||||
|
||||
//#complex-graph-alt
|
||||
RunnableGraph.fromGraph(
|
||||
GraphDSL.create(builder -> {
|
||||
final SourceShape<Integer> A = builder.add(Source.single(0));
|
||||
final UniformFanOutShape<Integer, Integer> B = builder.add(Broadcast.create(2));
|
||||
final UniformFanInShape<Integer, Integer> C = builder.add(Merge.create(2));
|
||||
final FlowShape<Integer, Integer> D =
|
||||
builder.add(Flow.of(Integer.class).map(i -> i + 1));
|
||||
final UniformFanOutShape<Integer, Integer> E = builder.add(Balance.create(2));
|
||||
final UniformFanInShape<Integer, Integer> F = builder.add(Merge.create(2));
|
||||
final SinkShape<Integer> G = builder.add(Sink.foreach(System.out::println));
|
||||
|
||||
builder.from(F.out()).toInlet(C.in(0));
|
||||
builder.from(A).toInlet(B.in());
|
||||
builder.from(B.out(0)).toInlet(C.in(1));
|
||||
builder.from(C.out()).toInlet(F.in(0));
|
||||
builder.from(B.out(1)).via(D).toInlet(E.in());
|
||||
builder.from(E.out(0)).toInlet(F.in(1));
|
||||
builder.from(E.out(1)).to(G);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#complex-graph-alt
|
||||
}
|
||||
|
||||
@Test
|
||||
public void partialGraph() throws Exception {
|
||||
//#partial-graph
|
||||
final Graph<FlowShape<Integer, Integer>, BoxedUnit> partial =
|
||||
GraphDSL.create(builder -> {
|
||||
final UniformFanOutShape<Integer, Integer> B = builder.add(Broadcast.create(2));
|
||||
final UniformFanInShape<Integer, Integer> C = builder.add(Merge.create(2));
|
||||
final UniformFanOutShape<Integer, Integer> E = builder.add(Balance.create(2));
|
||||
final UniformFanInShape<Integer, Integer> F = builder.add(Merge.create(2));
|
||||
|
||||
builder.from(F.out()).toInlet(C.in(0));
|
||||
builder.from(B).viaFanIn(C).toFanIn(F);
|
||||
builder.from(B).via(builder.add(Flow.of(Integer.class).map(i -> i + 1))).viaFanOut(E).toFanIn(F);
|
||||
|
||||
return new FlowShape<Integer, Integer>(B.in(), E.out(1));
|
||||
});
|
||||
|
||||
//#partial-graph
|
||||
|
||||
//#partial-use
|
||||
Source.single(0).via(partial).to(Sink.ignore());
|
||||
//#partial-use
|
||||
|
||||
//#partial-flow-dsl
|
||||
// Convert the partial graph of FlowShape to a Flow to get
|
||||
// access to the fluid DSL (for example to be able to call .filter())
|
||||
final Flow<Integer, Integer, BoxedUnit> flow = Flow.fromGraph(partial);
|
||||
|
||||
// Simple way to create a graph backed Source
|
||||
final Source<Integer, BoxedUnit> source = Source.fromGraph(
|
||||
GraphDSL.create(builder -> {
|
||||
final UniformFanInShape<Integer, Integer> merge = builder.add(Merge.create(2));
|
||||
builder.from(builder.add(Source.single(0))).toFanIn(merge);
|
||||
builder.from(builder.add(Source.from(Arrays.asList(2, 3, 4)))).toFanIn(merge);
|
||||
// Exposing exactly one output port
|
||||
return new SourceShape<Integer>(merge.out());
|
||||
})
|
||||
);
|
||||
|
||||
// Building a Sink with a nested Flow, using the fluid DSL
|
||||
final Sink<Integer, BoxedUnit> sink = Flow.of(Integer.class)
|
||||
.map(i -> i * 2)
|
||||
.drop(10)
|
||||
.named("nestedFlow")
|
||||
.to(Sink.head());
|
||||
|
||||
// Putting all together
|
||||
final RunnableGraph<BoxedUnit> closed = source.via(flow.filter(i -> i > 1)).to(sink);
|
||||
//#partial-flow-dsl
|
||||
}
|
||||
|
||||
@Test
|
||||
public void closedGraph() throws Exception {
|
||||
//#embed-closed
|
||||
final RunnableGraph<BoxedUnit> closed1 =
|
||||
Source.single(0).to(Sink.foreach(System.out::println));
|
||||
final RunnableGraph<BoxedUnit> closed2 =
|
||||
RunnableGraph.fromGraph(
|
||||
GraphDSL.create(builder -> {
|
||||
final ClosedShape embeddedClosed = builder.add(closed1);
|
||||
return embeddedClosed; // Could return ClosedShape.getInstance()
|
||||
}));
|
||||
//#embed-closed
|
||||
}
|
||||
|
||||
//#mat-combine-4a
|
||||
static class MyClass {
|
||||
private Promise<Option<Integer>> p;
|
||||
private OutgoingConnection conn;
|
||||
|
||||
public MyClass(Promise<Option<Integer>> p, OutgoingConnection conn) {
|
||||
this.p = p;
|
||||
this.conn = conn;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
p.success(Option.empty());
|
||||
}
|
||||
}
|
||||
|
||||
static class Combiner {
|
||||
static Future<MyClass> f(Promise<Option<Integer>> p,
|
||||
Pair<Future<OutgoingConnection>, Future<String>> rest) {
|
||||
return rest.first().map(new Mapper<OutgoingConnection, MyClass>() {
|
||||
public MyClass apply(OutgoingConnection c) {
|
||||
return new MyClass(p, c);
|
||||
}
|
||||
}, system.dispatcher());
|
||||
}
|
||||
}
|
||||
//#mat-combine-4a
|
||||
|
||||
@Test
|
||||
public void materializedValues() throws Exception {
|
||||
//#mat-combine-1
|
||||
// Materializes to Promise<BoxedUnit> (red)
|
||||
final Source<Integer, Promise<Option<Integer>>> source = Source.<Integer>maybe();
|
||||
|
||||
// Materializes to BoxedUnit (black)
|
||||
final Flow<Integer, Integer, BoxedUnit> flow1 = Flow.of(Integer.class).take(100);
|
||||
|
||||
// Materializes to Promise<Option<>> (red)
|
||||
final Source<Integer, Promise<Option<Integer>>> nestedSource =
|
||||
source.viaMat(flow1, Keep.left()).named("nestedSource");
|
||||
//#mat-combine-1
|
||||
|
||||
//#mat-combine-2
|
||||
// Materializes to BoxedUnit (orange)
|
||||
final Flow<Integer, ByteString, BoxedUnit> flow2 = Flow.of(Integer.class)
|
||||
.map(i -> ByteString.fromString(i.toString()));
|
||||
|
||||
// Materializes to Future<OutgoingConnection> (yellow)
|
||||
final Flow<ByteString, ByteString, Future<OutgoingConnection>> flow3 =
|
||||
Tcp.get(system).outgoingConnection("localhost", 8080);
|
||||
|
||||
// Materializes to Future<OutgoingConnection> (yellow)
|
||||
final Flow<Integer, ByteString, Future<OutgoingConnection>> nestedFlow =
|
||||
flow2.viaMat(flow3, Keep.right()).named("nestedFlow");
|
||||
//#mat-combine-2
|
||||
|
||||
//#mat-combine-3
|
||||
// Materializes to Future<String> (green)
|
||||
final Sink<ByteString, Future<String>> sink = Sink
|
||||
.fold("", (acc, i) -> acc + i.utf8String());
|
||||
|
||||
// Materializes to Pair<Future<OutgoingConnection>, Future<String>> (blue)
|
||||
final Sink<Integer, Pair<Future<OutgoingConnection>, Future<String>>> nestedSink =
|
||||
nestedFlow.toMat(sink, Keep.both());
|
||||
//#mat-combine-3
|
||||
|
||||
//#mat-combine-4b
|
||||
// Materializes to Future<MyClass> (purple)
|
||||
final RunnableGraph<Future<MyClass>> runnableGraph =
|
||||
nestedSource.toMat(nestedSink, Combiner::f);
|
||||
//#mat-combine-4b
|
||||
}
|
||||
|
||||
@Test
|
||||
public void attributes() throws Exception {
|
||||
//#attributes-inheritance
|
||||
final Source<Integer, BoxedUnit> nestedSource =
|
||||
Source.single(0)
|
||||
.map(i -> i + 1)
|
||||
.named("nestedSource"); // Wrap, no inputBuffer set
|
||||
|
||||
final Flow<Integer, Integer, BoxedUnit> nestedFlow =
|
||||
Flow.of(Integer.class).filter(i -> i != 0)
|
||||
.via(Flow.of(Integer.class)
|
||||
.map(i -> i - 2)
|
||||
.withAttributes(Attributes.inputBuffer(4, 4))) // override
|
||||
.named("nestedFlow"); // Wrap, no inputBuffer set
|
||||
|
||||
final Sink<Integer, BoxedUnit> nestedSink =
|
||||
nestedFlow.to(Sink.fold(0, (acc, i) -> acc + i)) // wire an atomic sink to the nestedFlow
|
||||
.withAttributes(Attributes.name("nestedSink")
|
||||
.and(Attributes.inputBuffer(3, 3))); // override
|
||||
//#attributes-inheritance
|
||||
}
|
||||
|
||||
}
|
||||
301
akka-docs/rst/java/code/docs/stream/FlowDocTest.java
Normal file
301
akka-docs/rst/java/code/docs/stream/FlowDocTest.java
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import akka.japi.Pair;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.Promise;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import scala.Option;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Cancellable;
|
||||
import akka.dispatch.Futures;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
public class FlowDocTest {
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void sourceIsImmutable() throws Exception {
|
||||
//#source-immutable
|
||||
final Source<Integer, BoxedUnit> source =
|
||||
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
|
||||
source.map(x -> 0); // has no effect on source, since it's immutable
|
||||
source.runWith(Sink.fold(0, (agg, next) -> agg + next), mat); // 55
|
||||
|
||||
// returns new Source<Integer>, with `map()` appended
|
||||
final Source<Integer, BoxedUnit> zeroes = source.map(x -> 0);
|
||||
final Sink<Integer, Future<Integer>> fold =
|
||||
Sink.fold(0, (agg, next) -> agg + next);
|
||||
zeroes.runWith(fold, mat); // 0
|
||||
//#source-immutable
|
||||
|
||||
int result = Await.result(
|
||||
zeroes.runWith(fold, mat),
|
||||
Duration.create(3, TimeUnit.SECONDS)
|
||||
);
|
||||
assertEquals(0, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void materializationInSteps() throws Exception {
|
||||
//#materialization-in-steps
|
||||
final Source<Integer, BoxedUnit> source =
|
||||
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
|
||||
// note that the Future is scala.concurrent.Future
|
||||
final Sink<Integer, Future<Integer>> sink =
|
||||
Sink.fold(0, (aggr, next) -> aggr + next);
|
||||
|
||||
// connect the Source to the Sink, obtaining a RunnableFlow
|
||||
final RunnableGraph<Future<Integer>> runnable =
|
||||
source.toMat(sink, Keep.right());
|
||||
|
||||
// materialize the flow
|
||||
final Future<Integer> sum = runnable.run(mat);
|
||||
//#materialization-in-steps
|
||||
|
||||
int result = Await.result(sum, Duration.create(3, TimeUnit.SECONDS));
|
||||
assertEquals(55, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void materializationRunWith() throws Exception {
|
||||
//#materialization-runWith
|
||||
final Source<Integer, BoxedUnit> source =
|
||||
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
|
||||
final Sink<Integer, Future<Integer>> sink =
|
||||
Sink.fold(0, (aggr, next) -> aggr + next);
|
||||
|
||||
// materialize the flow, getting the Sinks materialized value
|
||||
final Future<Integer> sum = source.runWith(sink, mat);
|
||||
//#materialization-runWith
|
||||
|
||||
int result = Await.result(sum, Duration.create(3, TimeUnit.SECONDS));
|
||||
assertEquals(55, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void materializedMapUnique() throws Exception {
|
||||
//#stream-reuse
|
||||
// connect the Source to the Sink, obtaining a RunnableGraph
|
||||
final Sink<Integer, Future<Integer>> sink =
|
||||
Sink.fold(0, (aggr, next) -> aggr + next);
|
||||
final RunnableGraph<Future<Integer>> runnable =
|
||||
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)).toMat(sink, Keep.right());
|
||||
|
||||
// get the materialized value of the FoldSink
|
||||
final Future<Integer> sum1 = runnable.run(mat);
|
||||
final Future<Integer> sum2 = runnable.run(mat);
|
||||
|
||||
// sum1 and sum2 are different Futures!
|
||||
//#stream-reuse
|
||||
|
||||
int result1 = Await.result(sum1, Duration.create(3, TimeUnit.SECONDS));
|
||||
assertEquals(55, result1);
|
||||
int result2 = Await.result(sum2, Duration.create(3, TimeUnit.SECONDS));
|
||||
assertEquals(55, result2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
public void compoundSourceCannotBeUsedAsKey() throws Exception {
|
||||
//#compound-source-is-not-keyed-runWith
|
||||
final Object tick = new Object();
|
||||
|
||||
final FiniteDuration oneSecond = Duration.create(1, TimeUnit.SECONDS);
|
||||
//akka.actor.Cancellable
|
||||
final Source<Object, Cancellable> timer =
|
||||
Source.tick(oneSecond, oneSecond, tick);
|
||||
|
||||
Sink.ignore().runWith(timer, mat);
|
||||
|
||||
final Source<String, Cancellable> timerMap = timer.map(t -> "tick");
|
||||
// WRONG: returned type is not the timers Cancellable!
|
||||
// Cancellable timerCancellable = Sink.ignore().runWith(timerMap, mat);
|
||||
//#compound-source-is-not-keyed-runWith
|
||||
|
||||
//#compound-source-is-not-keyed-run
|
||||
// retain the materialized map, in order to retrieve the timer's Cancellable
|
||||
final Cancellable timerCancellable = timer.to(Sink.ignore()).run(mat);
|
||||
timerCancellable.cancel();
|
||||
//#compound-source-is-not-keyed-run
|
||||
}
|
||||
|
||||
@Test
|
||||
public void creatingSourcesSinks() throws Exception {
|
||||
//#source-sink
|
||||
// Create a source from an Iterable
|
||||
List<Integer> list = new LinkedList<Integer>();
|
||||
list.add(1);
|
||||
list.add(2);
|
||||
list.add(3);
|
||||
Source.from(list);
|
||||
|
||||
// Create a source form a Future
|
||||
Source.fromFuture(Futures.successful("Hello Streams!"));
|
||||
|
||||
// Create a source from a single element
|
||||
Source.single("only one element");
|
||||
|
||||
// an empty source
|
||||
Source.empty();
|
||||
|
||||
// Sink that folds over the stream and returns a Future
|
||||
// of the final result in the MaterializedMap
|
||||
Sink.fold(0, (Integer aggr, Integer next) -> aggr + next);
|
||||
|
||||
// Sink that returns a Future in the MaterializedMap,
|
||||
// containing the first element of the stream
|
||||
Sink.head();
|
||||
|
||||
// A Sink that consumes a stream without doing anything with the elements
|
||||
Sink.ignore();
|
||||
|
||||
// A Sink that executes a side-effecting call for every element of the stream
|
||||
Sink.foreach(System.out::println);
|
||||
//#source-sink
|
||||
}
|
||||
|
||||
@Test
|
||||
public void variousWaysOfConnecting() throws Exception {
|
||||
//#flow-connecting
|
||||
// Explicitly creating and wiring up a Source, Sink and Flow
|
||||
Source.from(Arrays.asList(1, 2, 3, 4))
|
||||
.via(Flow.of(Integer.class).map(elem -> elem * 2))
|
||||
.to(Sink.foreach(System.out::println));
|
||||
|
||||
// Starting from a Source
|
||||
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(1, 2, 3, 4))
|
||||
.map(elem -> elem * 2);
|
||||
source.to(Sink.foreach(System.out::println));
|
||||
|
||||
// Starting from a Sink
|
||||
final Sink<Integer, BoxedUnit> sink = Flow.of(Integer.class)
|
||||
.map(elem -> elem * 2).to(Sink.foreach(System.out::println));
|
||||
Source.from(Arrays.asList(1, 2, 3, 4)).to(sink);
|
||||
//#flow-connecting
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformingMaterialized() throws Exception {
|
||||
|
||||
FiniteDuration oneSecond = FiniteDuration.apply(1, TimeUnit.SECONDS);
|
||||
Flow<Integer, Integer, Cancellable> throttler =
|
||||
Flow.fromGraph(GraphDSL.create(
|
||||
Source.tick(oneSecond, oneSecond, ""),
|
||||
(b, tickSource) -> {
|
||||
FanInShape2<String, Integer, Integer> zip = b.add(ZipWith.create(Keep.right()));
|
||||
b.from(tickSource).toInlet(zip.in0());
|
||||
return FlowShape.of(zip.in1(), zip.out());
|
||||
}));
|
||||
|
||||
//#flow-mat-combine
|
||||
|
||||
// An empty source that can be shut down explicitly from the outside
|
||||
Source<Integer, Promise<Option<Integer>>> source = Source.<Integer>maybe();
|
||||
|
||||
// A flow that internally throttles elements to 1/second, and returns a Cancellable
|
||||
// which can be used to shut down the stream
|
||||
Flow<Integer, Integer, Cancellable> flow = throttler;
|
||||
|
||||
// A sink that returns the first element of a stream in the returned Future
|
||||
Sink<Integer, Future<Integer>> sink = Sink.head();
|
||||
|
||||
|
||||
// By default, the materialized value of the leftmost stage is preserved
|
||||
RunnableGraph<Promise<Option<Integer>>> r1 = source.via(flow).to(sink);
|
||||
|
||||
// Simple selection of materialized values by using Keep.right
|
||||
RunnableGraph<Cancellable> r2 = source.viaMat(flow, Keep.right()).to(sink);
|
||||
RunnableGraph<Future<Integer>> r3 = source.via(flow).toMat(sink, Keep.right());
|
||||
|
||||
// Using runWith will always give the materialized values of the stages added
|
||||
// by runWith() itself
|
||||
Future<Integer> r4 = source.via(flow).runWith(sink, mat);
|
||||
Promise<Option<Integer>> r5 = flow.to(sink).runWith(source, mat);
|
||||
Pair<Promise<Option<Integer>>, Future<Integer>> r6 = flow.runWith(source, sink, mat);
|
||||
|
||||
// Using more complext combinations
|
||||
RunnableGraph<Pair<Promise<Option<Integer>>, Cancellable>> r7 =
|
||||
source.viaMat(flow, Keep.both()).to(sink);
|
||||
|
||||
RunnableGraph<Pair<Promise<Option<Integer>>, Future<Integer>>> r8 =
|
||||
source.via(flow).toMat(sink, Keep.both());
|
||||
|
||||
RunnableGraph<Pair<Pair<Promise<Option<Integer>>, Cancellable>, Future<Integer>>> r9 =
|
||||
source.viaMat(flow, Keep.both()).toMat(sink, Keep.both());
|
||||
|
||||
RunnableGraph<Pair<Cancellable, Future<Integer>>> r10 =
|
||||
source.viaMat(flow, Keep.right()).toMat(sink, Keep.both());
|
||||
|
||||
// It is also possible to map over the materialized values. In r9 we had a
|
||||
// doubly nested pair, but we want to flatten it out
|
||||
|
||||
|
||||
RunnableGraph<Cancellable> r11 =
|
||||
r9.mapMaterializedValue( (nestedTuple) -> {
|
||||
Promise<Option<Integer>> p = nestedTuple.first().first();
|
||||
Cancellable c = nestedTuple.first().second();
|
||||
Future<Integer> f = nestedTuple.second();
|
||||
|
||||
// Picking the Cancellable, but we could also construct a domain class here
|
||||
return c;
|
||||
});
|
||||
//#flow-mat-combine
|
||||
}
|
||||
|
||||
public void fusingAndAsync() {
|
||||
//#explicit-fusing
|
||||
Flow<Integer, Integer, BoxedUnit> flow =
|
||||
Flow.of(Integer.class).map(x -> x * 2).filter(x -> x > 500);
|
||||
Graph<FlowShape<Integer, Integer>, BoxedUnit> fused =
|
||||
akka.stream.Fusing.aggressive(flow);
|
||||
|
||||
Source.fromIterator(() -> Stream.iterate(0, x -> x + 1).iterator())
|
||||
.via(fused)
|
||||
.take(1000);
|
||||
//#explicit-fusing
|
||||
|
||||
//#flow-async
|
||||
Source.range(1, 3)
|
||||
.map(x -> x + 1)
|
||||
.withAttributes(Attributes.asyncBoundary())
|
||||
.map(x -> x * 2)
|
||||
.to(Sink.ignore());
|
||||
//#flow-async
|
||||
}
|
||||
|
||||
}
|
||||
140
akka-docs/rst/java/code/docs/stream/FlowErrorDocTest.java
Normal file
140
akka-docs/rst/java/code/docs/stream/FlowErrorDocTest.java
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.ActorMaterializerSettings;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.Supervision;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.ActorAttributes;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.japi.function.Function;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
public class FlowErrorDocTest {
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test(expected = ArithmeticException.class)
|
||||
public void demonstrateFailStream() throws Exception {
|
||||
//#stop
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(0, 1, 2, 3, 4, 5))
|
||||
.map(elem -> 100 / elem);
|
||||
final Sink<Integer, Future<Integer>> fold =
|
||||
Sink.fold(0, (acc, elem) -> acc + elem);
|
||||
final Future<Integer> result = source.runWith(fold, mat);
|
||||
// division by zero will fail the stream and the
|
||||
// result here will be a Future completed with Failure(ArithmeticException)
|
||||
//#stop
|
||||
|
||||
Await.result(result, Duration.create(3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateResumeStream() throws Exception {
|
||||
//#resume
|
||||
final Function<Throwable, Supervision.Directive> decider = exc -> {
|
||||
if (exc instanceof ArithmeticException)
|
||||
return Supervision.resume();
|
||||
else
|
||||
return Supervision.stop();
|
||||
};
|
||||
final Materializer mat = ActorMaterializer.create(
|
||||
ActorMaterializerSettings.create(system).withSupervisionStrategy(decider),
|
||||
system);
|
||||
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(0, 1, 2, 3, 4, 5))
|
||||
.map(elem -> 100 / elem);
|
||||
final Sink<Integer, Future<Integer>> fold =
|
||||
Sink.fold(0, (acc, elem) -> acc + elem);
|
||||
final Future<Integer> result = source.runWith(fold, mat);
|
||||
// the element causing division by zero will be dropped
|
||||
// result here will be a Future completed with Success(228)
|
||||
//#resume
|
||||
|
||||
assertEquals(Integer.valueOf(228), Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateResumeSectionStream() throws Exception {
|
||||
//#resume-section
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
final Function<Throwable, Supervision.Directive> decider = exc -> {
|
||||
if (exc instanceof ArithmeticException)
|
||||
return Supervision.resume();
|
||||
else
|
||||
return Supervision.stop();
|
||||
};
|
||||
final Flow<Integer, Integer, BoxedUnit> flow =
|
||||
Flow.of(Integer.class).filter(elem -> 100 / elem < 50).map(elem -> 100 / (5 - elem))
|
||||
.withAttributes(ActorAttributes.withSupervisionStrategy(decider));
|
||||
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(0, 1, 2, 3, 4, 5))
|
||||
.via(flow);
|
||||
final Sink<Integer, Future<Integer>> fold =
|
||||
Sink.fold(0, (acc, elem) -> acc + elem);
|
||||
final Future<Integer> result = source.runWith(fold, mat);
|
||||
// the elements causing division by zero will be dropped
|
||||
// result here will be a Future completed with Success(150)
|
||||
//#resume-section
|
||||
|
||||
assertEquals(Integer.valueOf(150), Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateRestartSectionStream() throws Exception {
|
||||
//#restart-section
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
final Function<Throwable, Supervision.Directive> decider = exc -> {
|
||||
if (exc instanceof IllegalArgumentException)
|
||||
return Supervision.restart();
|
||||
else
|
||||
return Supervision.stop();
|
||||
};
|
||||
final Flow<Integer, Integer, BoxedUnit> flow =
|
||||
Flow.of(Integer.class).scan(0, (acc, elem) -> {
|
||||
if (elem < 0) throw new IllegalArgumentException("negative not allowed");
|
||||
else return acc + elem;
|
||||
})
|
||||
.withAttributes(ActorAttributes.withSupervisionStrategy(decider));
|
||||
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(1, 3, -1, 5, 7))
|
||||
.via(flow);
|
||||
final Future<List<Integer>> result = source.grouped(1000)
|
||||
.runWith(Sink.<List<Integer>>head(), mat);
|
||||
// the negative element cause the scan stage to be restarted,
|
||||
// i.e. start from 0 again
|
||||
// result here will be a Future completed with Success(List(0, 1, 4, 0, 5, 12))
|
||||
//#restart-section
|
||||
|
||||
assertEquals(
|
||||
Arrays.asList(0, 1, 4, 0, 5, 12),
|
||||
Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
}
|
||||
172
akka-docs/rst/java/code/docs/stream/FlowGraphDocTest.java
Normal file
172
akka-docs/rst/java/code/docs/stream/FlowGraphDocTest.java
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.stream.ClosedShape;
|
||||
import akka.stream.SourceShape;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
public class FlowGraphDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowGraphDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void demonstrateBuildSimpleGraph() throws Exception {
|
||||
//#simple-flow-graph
|
||||
final Source<Integer, BoxedUnit> in = Source.from(Arrays.asList(1, 2, 3, 4, 5));
|
||||
final Sink<List<String>, Future<List<String>>> sink = Sink.head();
|
||||
final Sink<List<Integer>, Future<List<Integer>>> sink2 = Sink.head();
|
||||
final Flow<Integer, Integer, BoxedUnit> f1 = Flow.of(Integer.class).map(elem -> elem + 10);
|
||||
final Flow<Integer, Integer, BoxedUnit> f2 = Flow.of(Integer.class).map(elem -> elem + 20);
|
||||
final Flow<Integer, String, BoxedUnit> f3 = Flow.of(Integer.class).map(elem -> elem.toString());
|
||||
final Flow<Integer, Integer, BoxedUnit> f4 = Flow.of(Integer.class).map(elem -> elem + 30);
|
||||
|
||||
final RunnableGraph<Future<List<String>>> result =
|
||||
RunnableGraph.<Future<List<String>>>fromGraph(
|
||||
GraphDSL
|
||||
.create(
|
||||
sink,
|
||||
(builder, out) -> {
|
||||
final UniformFanOutShape<Integer, Integer> bcast = builder.add(Broadcast.create(2));
|
||||
final UniformFanInShape<Integer, Integer> merge = builder.add(Merge.create(2));
|
||||
|
||||
final Outlet<Integer> source = builder.add(in).out();
|
||||
builder.from(source).via(builder.add(f1))
|
||||
.viaFanOut(bcast).via(builder.add(f2)).viaFanIn(merge)
|
||||
.via(builder.add(f3.grouped(1000))).to(out);
|
||||
builder.from(bcast).via(builder.add(f4)).toFanIn(merge);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#simple-flow-graph
|
||||
final List<String> list = Await.result(result.run(mat), Duration.create(3, TimeUnit.SECONDS));
|
||||
final String[] res = list.toArray(new String[] {});
|
||||
Arrays.sort(res, null);
|
||||
assertArrayEquals(new String[] { "31", "32", "33", "34", "35", "41", "42", "43", "44", "45" }, res);
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
public void demonstrateConnectErrors() {
|
||||
try {
|
||||
//#simple-graph
|
||||
final RunnableGraph<BoxedUnit> g =
|
||||
RunnableGraph.<BoxedUnit>fromGraph(
|
||||
GraphDSL
|
||||
.create((b) -> {
|
||||
final SourceShape<Integer> source1 = b.add(Source.from(Arrays.asList(1, 2, 3, 4, 5)));
|
||||
final SourceShape<Integer> source2 = b.add(Source.from(Arrays.asList(1, 2, 3, 4, 5)));
|
||||
final FanInShape2<Integer, Integer, Pair<Integer, Integer>> zip = b.add(Zip.create());
|
||||
b.from(source1).toInlet(zip.in0());
|
||||
b.from(source2).toInlet(zip.in1());
|
||||
return ClosedShape.getInstance();
|
||||
}
|
||||
)
|
||||
);
|
||||
// unconnected zip.out (!) => "The inlets [] and outlets [] must correspond to the inlets [] and outlets [ZipWith2.out]"
|
||||
//#simple-graph
|
||||
fail("expected IllegalArgumentException");
|
||||
} catch (IllegalArgumentException e) {
|
||||
assertTrue(e != null && e.getMessage() != null && e.getMessage().contains("must correspond to"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateReusingFlowInGraph() throws Exception {
|
||||
//#flow-graph-reusing-a-flow
|
||||
final Sink<Integer, Future<Integer>> topHeadSink = Sink.head();
|
||||
final Sink<Integer, Future<Integer>> bottomHeadSink = Sink.head();
|
||||
final Flow<Integer, Integer, BoxedUnit> sharedDoubler = Flow.of(Integer.class).map(elem -> elem * 2);
|
||||
|
||||
final RunnableGraph<Pair<Future<Integer>, Future<Integer>>> g =
|
||||
RunnableGraph.<Pair<Future<Integer>, Future<Integer>>>fromGraph(
|
||||
GraphDSL.create(
|
||||
topHeadSink, // import this sink into the graph
|
||||
bottomHeadSink, // and this as well
|
||||
Keep.both(),
|
||||
(b, top, bottom) -> {
|
||||
final UniformFanOutShape<Integer, Integer> bcast =
|
||||
b.add(Broadcast.create(2));
|
||||
|
||||
b.from(b.add(Source.single(1))).viaFanOut(bcast)
|
||||
.via(b.add(sharedDoubler)).to(top);
|
||||
b.from(bcast).via(b.add(sharedDoubler)).to(bottom);
|
||||
return ClosedShape.getInstance();
|
||||
}
|
||||
)
|
||||
);
|
||||
//#flow-graph-reusing-a-flow
|
||||
final Pair<Future<Integer>, Future<Integer>> pair = g.run(mat);
|
||||
assertEquals(Integer.valueOf(2), Await.result(pair.first(), Duration.create(3, TimeUnit.SECONDS)));
|
||||
assertEquals(Integer.valueOf(2), Await.result(pair.second(), Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateMatValue() throws Exception {
|
||||
//#flow-graph-matvalue
|
||||
final Sink<Integer, Future<Integer>> foldSink = Sink.<Integer, Integer> fold(0, (a, b) -> {
|
||||
return a + b;
|
||||
});
|
||||
|
||||
final Flow<Future<Integer>, Integer, BoxedUnit> flatten = Flow.<Future<Integer>>create()
|
||||
.mapAsync(4, x -> {
|
||||
return x;
|
||||
});
|
||||
|
||||
final Flow<Integer, Integer, Future<Integer>> foldingFlow = Flow.fromGraph(
|
||||
GraphDSL.create(foldSink,
|
||||
(b, fold) -> {
|
||||
return FlowShape.of(
|
||||
fold.in(),
|
||||
b.from(b.materializedValue()).via(b.add(flatten)).out());
|
||||
}));
|
||||
//#flow-graph-matvalue
|
||||
|
||||
//#flow-graph-matvalue-cycle
|
||||
// This cannot produce any value:
|
||||
final Source<Integer, Future<Integer>> cyclicSource = Source.fromGraph(
|
||||
GraphDSL.create(foldSink,
|
||||
(b, fold) -> {
|
||||
// - Fold cannot complete until its upstream mapAsync completes
|
||||
// - mapAsync cannot complete until the materialized Future produced by
|
||||
// fold completes
|
||||
// As a result this Source will never emit anything, and its materialited
|
||||
// Future will never complete
|
||||
b.from(b.materializedValue()).via(b.add(flatten)).to(fold);
|
||||
return SourceShape.of(b.from(b.materializedValue()).via(b.add(flatten)).out());
|
||||
}));
|
||||
|
||||
//#flow-graph-matvalue-cycle
|
||||
}
|
||||
}
|
||||
146
akka-docs/rst/java/code/docs/stream/FlowParallelismDocTest.java
Normal file
146
akka-docs/rst/java/code/docs/stream/FlowParallelismDocTest.java
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
public class FlowParallelismDocTest {
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
static class ScoopOfBatter {}
|
||||
static class HalfCookedPancake {}
|
||||
static class Pancake {}
|
||||
|
||||
//#pipelining
|
||||
Flow<ScoopOfBatter, HalfCookedPancake, BoxedUnit> fryingPan1 =
|
||||
Flow.of(ScoopOfBatter.class).map(batter -> new HalfCookedPancake());
|
||||
|
||||
Flow<HalfCookedPancake, Pancake, BoxedUnit> fryingPan2 =
|
||||
Flow.of(HalfCookedPancake.class).map(halfCooked -> new Pancake());
|
||||
//#pipelining
|
||||
|
||||
@Test
|
||||
public void demonstratePipelining() {
|
||||
//#pipelining
|
||||
|
||||
// With the two frying pans we can fully cook pancakes
|
||||
Flow<ScoopOfBatter, Pancake, BoxedUnit> pancakeChef = fryingPan1.via(fryingPan2);
|
||||
//#pipelining
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateParallelism() {
|
||||
//#parallelism
|
||||
Flow<ScoopOfBatter, Pancake, BoxedUnit> fryingPan =
|
||||
Flow.of(ScoopOfBatter.class).map(batter -> new Pancake());
|
||||
|
||||
Flow<ScoopOfBatter, Pancake, BoxedUnit> pancakeChef =
|
||||
Flow.fromGraph(GraphDSL.create(b -> {
|
||||
final UniformFanInShape<Pancake, Pancake> mergePancakes =
|
||||
b.add(Merge.create(2));
|
||||
final UniformFanOutShape<ScoopOfBatter, ScoopOfBatter> dispatchBatter =
|
||||
b.add(Balance.create(2));
|
||||
|
||||
// Using two frying pans in parallel, both fully cooking a pancake from the batter.
|
||||
// We always put the next scoop of batter to the first frying pan that becomes available.
|
||||
b.from(dispatchBatter.out(0)).via(b.add(fryingPan)).toInlet(mergePancakes.in(0));
|
||||
// Notice that we used the "fryingPan" flow without importing it via builder.add().
|
||||
// Flows used this way are auto-imported, which in this case means that the two
|
||||
// uses of "fryingPan" mean actually different stages in the graph.
|
||||
b.from(dispatchBatter.out(1)).via(b.add(fryingPan)).toInlet(mergePancakes.in(1));
|
||||
|
||||
return FlowShape.of(dispatchBatter.in(), mergePancakes.out());
|
||||
}));
|
||||
//#parallelism
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parallelPipeline() {
|
||||
//#parallel-pipeline
|
||||
Flow<ScoopOfBatter, Pancake, BoxedUnit> pancakeChef =
|
||||
Flow.fromGraph(GraphDSL.create(b -> {
|
||||
final UniformFanInShape<Pancake, Pancake> mergePancakes =
|
||||
b.add(Merge.create(2));
|
||||
final UniformFanOutShape<ScoopOfBatter, ScoopOfBatter> dispatchBatter =
|
||||
b.add(Balance.create(2));
|
||||
|
||||
// Using two pipelines, having two frying pans each, in total using
|
||||
// four frying pans
|
||||
b.from(dispatchBatter.out(0))
|
||||
.via(b.add(fryingPan1))
|
||||
.via(b.add(fryingPan2))
|
||||
.toInlet(mergePancakes.in(0));
|
||||
|
||||
b.from(dispatchBatter.out(1))
|
||||
.via(b.add(fryingPan1))
|
||||
.via(b.add(fryingPan2))
|
||||
.toInlet(mergePancakes.in(1));
|
||||
|
||||
return FlowShape.of(dispatchBatter.in(), mergePancakes.out());
|
||||
}));
|
||||
//#parallel-pipeline
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pipelinedParallel() {
|
||||
//#pipelined-parallel
|
||||
Flow<ScoopOfBatter, HalfCookedPancake, BoxedUnit> pancakeChefs1 =
|
||||
Flow.fromGraph(GraphDSL.create(b -> {
|
||||
final UniformFanInShape<HalfCookedPancake, HalfCookedPancake> mergeHalfCooked =
|
||||
b.add(Merge.create(2));
|
||||
final UniformFanOutShape<ScoopOfBatter, ScoopOfBatter> dispatchBatter =
|
||||
b.add(Balance.create(2));
|
||||
|
||||
// Two chefs work with one frying pan for each, half-frying the pancakes then putting
|
||||
// them into a common pool
|
||||
b.from(dispatchBatter.out(0)).via(b.add(fryingPan1)).toInlet(mergeHalfCooked.in(0));
|
||||
b.from(dispatchBatter.out(1)).via(b.add(fryingPan1)).toInlet(mergeHalfCooked.in(1));
|
||||
|
||||
return FlowShape.of(dispatchBatter.in(), mergeHalfCooked.out());
|
||||
}));
|
||||
|
||||
Flow<HalfCookedPancake, Pancake, BoxedUnit> pancakeChefs2 =
|
||||
Flow.fromGraph(GraphDSL.create(b -> {
|
||||
final UniformFanInShape<Pancake, Pancake> mergePancakes =
|
||||
b.add(Merge.create(2));
|
||||
final UniformFanOutShape<HalfCookedPancake, HalfCookedPancake> dispatchHalfCooked =
|
||||
b.add(Balance.create(2));
|
||||
|
||||
// Two chefs work with one frying pan for each, finishing the pancakes then putting
|
||||
// them into a common pool
|
||||
b.from(dispatchHalfCooked.out(0)).via(b.add(fryingPan2)).toInlet(mergePancakes.in(0));
|
||||
b.from(dispatchHalfCooked.out(1)).via(b.add(fryingPan2)).toInlet(mergePancakes.in(1));
|
||||
|
||||
return FlowShape.of(dispatchHalfCooked.in(), mergePancakes.out());
|
||||
}));
|
||||
|
||||
Flow<ScoopOfBatter, Pancake, BoxedUnit> kitchen =
|
||||
pancakeChefs1.via(pancakeChefs2);
|
||||
//#pipelined-parallel
|
||||
}
|
||||
}
|
||||
256
akka-docs/rst/java/code/docs/stream/FlowStagesDocTest.java
Normal file
256
akka-docs/rst/java/code/docs/stream/FlowStagesDocTest.java
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.stage.*;
|
||||
import akka.stream.testkit.*;
|
||||
import akka.stream.testkit.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
public class FlowStagesDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowStagesDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
static //#one-to-one
|
||||
public class Map<A, B> extends PushPullStage<A, B> {
|
||||
private final Function<A, B> f;
|
||||
public Map(Function<A, B> f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
@Override public SyncDirective onPush(A elem, Context<B> ctx) {
|
||||
return ctx.push(f.apply(elem));
|
||||
}
|
||||
|
||||
@Override public SyncDirective onPull(Context<B> ctx) {
|
||||
return ctx.pull();
|
||||
}
|
||||
}
|
||||
//#one-to-one
|
||||
|
||||
static //#many-to-one
|
||||
public class Filter<A> extends PushPullStage<A, A> {
|
||||
private final Predicate<A> p;
|
||||
public Filter(Predicate<A> p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
|
||||
if (p.test(elem)) return ctx.push(elem);
|
||||
else return ctx.pull();
|
||||
}
|
||||
|
||||
@Override public SyncDirective onPull(Context<A> ctx) {
|
||||
return ctx.pull();
|
||||
}
|
||||
}
|
||||
//#many-to-one
|
||||
|
||||
//#one-to-many
|
||||
class Duplicator<A> extends PushPullStage<A, A> {
|
||||
private A lastElem = null;
|
||||
private boolean oneLeft = false;
|
||||
|
||||
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
|
||||
lastElem = elem;
|
||||
oneLeft = true;
|
||||
return ctx.push(elem);
|
||||
}
|
||||
|
||||
@Override public SyncDirective onPull(Context<A> ctx) {
|
||||
if (!ctx.isFinishing()) {
|
||||
// the main pulling logic is below as it is demonstrated on the illustration
|
||||
if (oneLeft) {
|
||||
oneLeft = false;
|
||||
return ctx.push(lastElem);
|
||||
} else
|
||||
return ctx.pull();
|
||||
} else {
|
||||
// If we need to emit a final element after the upstream
|
||||
// finished
|
||||
if (oneLeft) return ctx.pushAndFinish(lastElem);
|
||||
else return ctx.finish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override public TerminationDirective onUpstreamFinish(Context<A> ctx) {
|
||||
return ctx.absorbTermination();
|
||||
}
|
||||
|
||||
}
|
||||
//#one-to-many
|
||||
|
||||
static//#pushstage
|
||||
public class Map2<A, B> extends PushStage<A, B> {
|
||||
private final Function<A, B> f;
|
||||
public Map2(Function<A, B> f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
@Override public SyncDirective onPush(A elem, Context<B> ctx) {
|
||||
return ctx.push(f.apply(elem));
|
||||
}
|
||||
}
|
||||
|
||||
public class Filter2<A> extends PushStage<A, A> {
|
||||
private final Predicate<A> p;
|
||||
public Filter2(Predicate<A> p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
|
||||
if (p.test(elem)) return ctx.push(elem);
|
||||
else return ctx.pull();
|
||||
}
|
||||
}
|
||||
//#pushstage
|
||||
|
||||
static //#doubler-stateful
|
||||
public class Duplicator2<A> extends StatefulStage<A, A> {
|
||||
@Override public StageState<A, A> initial() {
|
||||
return new StageState<A, A>() {
|
||||
@Override public SyncDirective onPush(A elem, Context<A> ctx) {
|
||||
return emit(Arrays.asList(elem, elem).iterator(), ctx);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//#doubler-stateful
|
||||
|
||||
@Test
|
||||
public void demonstrateVariousPushPullStages() throws Exception {
|
||||
final Sink<Integer, Future<List<Integer>>> sink =
|
||||
Flow.of(Integer.class).grouped(10).toMat(Sink.head(), Keep.right());
|
||||
|
||||
//#stage-chain
|
||||
final RunnableGraph<Future<List<Integer>>> runnable =
|
||||
Source
|
||||
.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||
.transform(() -> new Filter<Integer>(elem -> elem % 2 == 0))
|
||||
.transform(() -> new Duplicator<Integer>())
|
||||
.transform(() -> new Map<Integer, Integer>(elem -> elem / 2))
|
||||
.toMat(sink, Keep.right());
|
||||
//#stage-chain
|
||||
|
||||
assertEquals(Arrays.asList(1, 1, 2, 2, 3, 3, 4, 4, 5, 5),
|
||||
Await.result(runnable.run(mat), FiniteDuration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
//#detached
|
||||
class Buffer2<T> extends DetachedStage<T, T> {
|
||||
final private Integer SIZE = 2;
|
||||
final private List<T> buf = new ArrayList<>(SIZE);
|
||||
private Integer capacity = SIZE;
|
||||
|
||||
private boolean isFull() {
|
||||
return capacity == 0;
|
||||
}
|
||||
|
||||
private boolean isEmpty() {
|
||||
return capacity == SIZE;
|
||||
}
|
||||
|
||||
private T dequeue() {
|
||||
capacity += 1;
|
||||
return buf.remove(0);
|
||||
}
|
||||
|
||||
private void enqueue(T elem) {
|
||||
capacity -= 1;
|
||||
buf.add(elem);
|
||||
}
|
||||
|
||||
public DownstreamDirective onPull(DetachedContext<T> ctx) {
|
||||
if (isEmpty()) {
|
||||
if (ctx.isFinishing()) return ctx.finish(); // No more elements will arrive
|
||||
else return ctx.holdDownstream(); // waiting until new elements
|
||||
} else {
|
||||
final T next = dequeue();
|
||||
if (ctx.isHoldingUpstream()) return ctx.pushAndPull(next); // release upstream
|
||||
else return ctx.push(next);
|
||||
}
|
||||
}
|
||||
|
||||
public UpstreamDirective onPush(T elem, DetachedContext<T> ctx) {
|
||||
enqueue(elem);
|
||||
if (isFull()) return ctx.holdUpstream(); // Queue is now full, wait until new empty slot
|
||||
else {
|
||||
if (ctx.isHoldingDownstream()) return ctx.pushAndPull(dequeue()); // Release downstream
|
||||
else return ctx.pull();
|
||||
}
|
||||
}
|
||||
|
||||
public TerminationDirective onUpstreamFinish(DetachedContext<T> ctx) {
|
||||
if (!isEmpty()) return ctx.absorbTermination(); // still need to flush from buffer
|
||||
else return ctx.finish(); // already empty, finishing
|
||||
}
|
||||
}
|
||||
//#detached
|
||||
|
||||
@Test
|
||||
public void demonstrateDetachedStage() throws Exception {
|
||||
final Pair<TestPublisher.Probe<Integer>,TestSubscriber.Probe<Integer>> pair =
|
||||
TestSource.<Integer>probe(system)
|
||||
.transform(() -> new Buffer2<Integer>())
|
||||
.toMat(TestSink.probe(system), Keep.both())
|
||||
.run(mat);
|
||||
|
||||
final TestPublisher.Probe<Integer> pub = pair.first();
|
||||
final TestSubscriber.Probe<Integer> sub = pair.second();
|
||||
|
||||
final FiniteDuration timeout = Duration.create(100, TimeUnit.MILLISECONDS);
|
||||
|
||||
sub.request(2);
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(1);
|
||||
pub.sendNext(2);
|
||||
sub.expectNext(1, 2);
|
||||
|
||||
pub.sendNext(3);
|
||||
pub.sendNext(4);
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
sub.request(2);
|
||||
sub.expectNext(3, 4);
|
||||
|
||||
pub.sendComplete();
|
||||
sub.expectComplete();
|
||||
}
|
||||
|
||||
}
|
||||
159
akka-docs/rst/java/code/docs/stream/GraphCyclesDocTest.java
Normal file
159
akka-docs/rst/java/code/docs/stream/GraphCyclesDocTest.java
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package docs.stream;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.runtime.BoxedUnit;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.scaladsl.MergePreferred.MergePreferredShape;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
|
||||
public class GraphCyclesDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("GraphCyclesDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
final static SilenceSystemOut.System System = SilenceSystemOut.get();
|
||||
|
||||
final Source<Integer, BoxedUnit> source = Source.from(Arrays.asList(1, 2, 3, 4, 5));
|
||||
|
||||
@Test
|
||||
public void demonstrateDeadlockedCycle() {
|
||||
//#deadlocked
|
||||
// WARNING! The graph below deadlocks!
|
||||
final Flow<Integer, Integer, BoxedUnit> printFlow =
|
||||
Flow.of(Integer.class).map(s -> {
|
||||
System.out.println(s);
|
||||
return s;
|
||||
});
|
||||
|
||||
RunnableGraph.fromGraph(GraphDSL.create(b -> {
|
||||
final UniformFanInShape<Integer, Integer> merge = b.add(Merge.create(2));
|
||||
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
|
||||
final Outlet<Integer> src = b.add(source).out();
|
||||
final FlowShape<Integer, Integer> printer = b.add(printFlow);
|
||||
final SinkShape<Integer> ignore = b.add(Sink.ignore());
|
||||
|
||||
b.from(src).viaFanIn(merge).via(printer).viaFanOut(bcast).to(ignore);
|
||||
b.to(merge) .fromFanOut(bcast);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#deadlocked
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateUnfairCycle() {
|
||||
final Flow<Integer, Integer, BoxedUnit> printFlow =
|
||||
Flow.of(Integer.class).map(s -> {
|
||||
System.out.println(s);
|
||||
return s;
|
||||
});
|
||||
//#unfair
|
||||
// WARNING! The graph below stops consuming from "source" after a few steps
|
||||
RunnableGraph.fromGraph(GraphDSL.create(b -> {
|
||||
final MergePreferredShape<Integer> merge = b.add(MergePreferred.create(1));
|
||||
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
|
||||
final Outlet<Integer> src = b.add(source).out();
|
||||
final FlowShape<Integer, Integer> printer = b.add(printFlow);
|
||||
final SinkShape<Integer> ignore = b.add(Sink.ignore());
|
||||
|
||||
b.from(src).viaFanIn(merge).via(printer).viaFanOut(bcast).to(ignore);
|
||||
b.to(merge.preferred()).fromFanOut(bcast);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#unfair
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateDroppingCycle() {
|
||||
final Flow<Integer, Integer, BoxedUnit> printFlow =
|
||||
Flow.of(Integer.class).map(s -> {
|
||||
System.out.println(s);
|
||||
return s;
|
||||
});
|
||||
//#dropping
|
||||
RunnableGraph.fromGraph(GraphDSL.create(b -> {
|
||||
final UniformFanInShape<Integer, Integer> merge = b.add(Merge.create(2));
|
||||
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
|
||||
final FlowShape<Integer, Integer> droppyFlow = b.add(
|
||||
Flow.of(Integer.class).buffer(10, OverflowStrategy.dropHead()));
|
||||
final Outlet<Integer> src = b.add(source).out();
|
||||
final FlowShape<Integer, Integer> printer = b.add(printFlow);
|
||||
final SinkShape<Integer> ignore = b.add(Sink.ignore());
|
||||
|
||||
b.from(src).viaFanIn(merge).via(printer).viaFanOut(bcast).to(ignore);
|
||||
b.to(merge).via(droppyFlow).fromFanOut(bcast);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#dropping
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateZippingCycle() {
|
||||
final Flow<Integer, Integer, BoxedUnit> printFlow =
|
||||
Flow.of(Integer.class).map(s -> {
|
||||
System.out.println(s);
|
||||
return s;
|
||||
});
|
||||
//#zipping-dead
|
||||
// WARNING! The graph below never processes any elements
|
||||
RunnableGraph.fromGraph(GraphDSL.create(b -> {
|
||||
final FanInShape2<Integer, Integer, Integer> zip =
|
||||
b.add(ZipWith.create((Integer left, Integer right) -> left));
|
||||
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
|
||||
final FlowShape<Integer, Integer> printer = b.add(printFlow);
|
||||
final SinkShape<Integer> ignore = b.add(Sink.ignore());
|
||||
|
||||
b.from(b.add(source)).toInlet(zip.in0());
|
||||
b.from(zip.out()).via(printer).viaFanOut(bcast).to(ignore);
|
||||
b.to(zip.in1()) .fromFanOut(bcast);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#zipping-dead
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateLiveZippingCycle() {
|
||||
final Flow<Integer, Integer, BoxedUnit> printFlow =
|
||||
Flow.of(Integer.class).map(s -> {
|
||||
System.out.println(s);
|
||||
return s;
|
||||
});
|
||||
//#zipping-live
|
||||
RunnableGraph.fromGraph(GraphDSL.create(b -> {
|
||||
final FanInShape2<Integer, Integer, Integer> zip =
|
||||
b.add(ZipWith.create((Integer left, Integer right) -> left));
|
||||
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
|
||||
final UniformFanInShape<Integer, Integer> concat = b.add(Concat.create());
|
||||
final FlowShape<Integer, Integer> printer = b.add(printFlow);
|
||||
final SinkShape<Integer> ignore = b.add(Sink.ignore());
|
||||
|
||||
b.from(b.add(source)).toInlet(zip.in0());
|
||||
b.from(zip.out()).via(printer).viaFanOut(bcast).to(ignore);
|
||||
b.to(zip.in1()).viaFanIn(concat).from(b.add(Source.single(1)));
|
||||
b.to(concat).fromFanOut(bcast);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#zipping-live
|
||||
}
|
||||
|
||||
}
|
||||
712
akka-docs/rst/java/code/docs/stream/GraphStageDocTest.java
Normal file
712
akka-docs/rst/java/code/docs/stream/GraphStageDocTest.java
Normal file
|
|
@ -0,0 +1,712 @@
|
|||
package docs.stream;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
//#imports
|
||||
import akka.dispatch.Futures;
|
||||
import akka.dispatch.Mapper;
|
||||
import akka.dispatch.OnSuccess;
|
||||
import akka.japi.Option;
|
||||
import akka.japi.Predicate;
|
||||
import akka.japi.function.Effect;
|
||||
import akka.japi.function.Procedure;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.stage.*;
|
||||
//#imports
|
||||
import akka.stream.testkit.TestPublisher;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.japi.Function;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Subscription;
|
||||
import scala.Tuple2;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.ExecutionContext;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.Promise;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class GraphStageDocTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("FlowGraphDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
|
||||
//#simple-source
|
||||
public class NumbersSource extends GraphStage<SourceShape<Integer>> {
|
||||
// Define the (sole) output port of this stage
|
||||
public final Outlet<Integer> out = Outlet.create("NumbersSource.out");
|
||||
|
||||
// Define the shape of this stage, which is SourceShape with the port we defined above
|
||||
private final SourceShape<Integer> shape = SourceShape.of(out);
|
||||
@Override
|
||||
public SourceShape<Integer> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
// This is where the actual (possibly stateful) logic is created
|
||||
@Override
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new GraphStageLogic(shape()) {
|
||||
// All state MUST be inside the GraphStageLogic,
|
||||
// never inside the enclosing GraphStage.
|
||||
// This state is safe to access and modify from all the
|
||||
// callbacks that are provided by GraphStageLogic and the
|
||||
// registered handlers.
|
||||
private int counter = 1;
|
||||
|
||||
{
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
push(out, counter);
|
||||
counter += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
//#simple-source
|
||||
|
||||
|
||||
@Test
|
||||
public void demonstrateCustomSourceUsage() throws Exception {
|
||||
//#simple-source-usage
|
||||
// A GraphStage is a proper Graph, just like what GraphDSL.create would return
|
||||
Graph<SourceShape<Integer>, BoxedUnit> sourceGraph = new NumbersSource();
|
||||
|
||||
// Create a Source from the Graph to access the DSL
|
||||
Source<Integer, BoxedUnit> mySource = Source.fromGraph(sourceGraph);
|
||||
|
||||
// Returns 55
|
||||
Future<Integer> result1 = mySource.take(10).runFold(0, (sum, next) -> sum + next, mat);
|
||||
|
||||
// The source is reusable. This returns 5050
|
||||
Future<Integer> result2 = mySource.take(100).runFold(0, (sum, next) -> sum + next, mat);
|
||||
//#simple-source-usage
|
||||
|
||||
assertEquals(Await.result(result1, Duration.create(3, "seconds")), (Integer) 55);
|
||||
assertEquals(Await.result(result2, Duration.create(3, "seconds")), (Integer) 5050);
|
||||
}
|
||||
|
||||
|
||||
//#one-to-one
|
||||
public class Map<A, B> extends GraphStage<FlowShape<A, B>> {
|
||||
|
||||
private final Function<A, B> f;
|
||||
|
||||
public Map(Function<A, B> f) {
|
||||
this.f = f;
|
||||
}
|
||||
|
||||
public final Inlet<A> in = Inlet.create("Map.in");
|
||||
public final Outlet<B> out = Outlet.create("Map.out");
|
||||
|
||||
private final FlowShape<A, B> shape = FlowShape.of(in, out);
|
||||
@Override
|
||||
public FlowShape<A,B> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new GraphStageLogic(shape) {
|
||||
|
||||
{
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
try {
|
||||
push(out, f.apply(grab(in)));
|
||||
} catch (Exception ex) {
|
||||
failStage(ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
pull(in);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
//#one-to-one
|
||||
|
||||
@Test
|
||||
public void demonstrateOneToOne() throws Exception {
|
||||
// tests:
|
||||
final Graph<FlowShape<String, Integer>, BoxedUnit> stringLength =
|
||||
Flow.fromGraph(new Map<String, Integer>(new Function<String, Integer>() {
|
||||
@Override
|
||||
public Integer apply(String str) {
|
||||
return str.length();
|
||||
}
|
||||
}));
|
||||
|
||||
Future<Integer> result =
|
||||
Source.from(Arrays.asList("one", "two", "three"))
|
||||
.via(stringLength)
|
||||
.runFold(0, (sum, n) -> sum + n, mat);
|
||||
|
||||
assertEquals(new Integer(11), Await.result(result, Duration.create(3, "seconds")));
|
||||
}
|
||||
|
||||
//#many-to-one
|
||||
public final class Filter<A> extends GraphStage<FlowShape<A, A>> {
|
||||
|
||||
private final Predicate<A> p;
|
||||
|
||||
public Filter(Predicate<A> p) {
|
||||
this.p = p;
|
||||
}
|
||||
|
||||
public final Inlet<A> in = Inlet.create("Filter.in");
|
||||
public final Outlet<A> out = Outlet.create("Filter.out");
|
||||
|
||||
private final FlowShape<A, A> shape = FlowShape.of(in, out);
|
||||
|
||||
@Override
|
||||
public FlowShape<A, A> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new GraphStageLogic(shape) {
|
||||
{
|
||||
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
A elem = grab(in);
|
||||
if (p.test(elem)) {
|
||||
push(out, elem);
|
||||
} else {
|
||||
pull(in);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
pull(in);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//#many-to-one
|
||||
|
||||
@Test
|
||||
public void demonstrateAManyToOneElementGraphStage() throws Exception {
|
||||
|
||||
// tests:
|
||||
Graph<FlowShape<Integer, Integer>, BoxedUnit> evenFilter =
|
||||
Flow.fromGraph(new Filter<Integer>(n -> n % 2 == 0));
|
||||
|
||||
Future<Integer> result =
|
||||
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6))
|
||||
.via(evenFilter)
|
||||
.runFold(0, (elem, sum) -> sum + elem, mat);
|
||||
|
||||
assertEquals(new Integer(12), Await.result(result, Duration.create(3, "seconds")));
|
||||
}
|
||||
|
||||
//#one-to-many
|
||||
public class Duplicator<A> extends GraphStage<FlowShape<A, A>> {
|
||||
|
||||
public final Inlet<A> in = Inlet.create("Duplicator.in");
|
||||
public final Outlet<A> out = Outlet.create("Duplicator.out");
|
||||
|
||||
private final FlowShape<A, A> shape = FlowShape.of(in, out);
|
||||
|
||||
@Override
|
||||
public FlowShape<A, A> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new GraphStageLogic(shape) {
|
||||
// Again: note that all mutable state
|
||||
// MUST be inside the GraphStageLogic
|
||||
Option<A> lastElem = Option.none();
|
||||
|
||||
{
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
A elem = grab(in);
|
||||
lastElem = Option.some(elem);
|
||||
push(out, elem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpstreamFinish() {
|
||||
if (lastElem.isDefined()) {
|
||||
emit(out, lastElem.get());
|
||||
}
|
||||
complete(out);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
if (lastElem.isDefined()) {
|
||||
push(out, lastElem.get());
|
||||
lastElem = Option.none();
|
||||
} else {
|
||||
pull(in);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//#one-to-many
|
||||
|
||||
@Test
|
||||
public void demonstrateAOneToManyElementGraphStage() throws Exception {
|
||||
// tests:
|
||||
Graph<FlowShape<Integer, Integer>, BoxedUnit> duplicator =
|
||||
Flow.fromGraph(new Duplicator<Integer>());
|
||||
|
||||
Future<Integer> result =
|
||||
Source.from(Arrays.asList(1, 2, 3))
|
||||
.via(duplicator)
|
||||
.runFold(0, (n, sum) -> n + sum, mat);
|
||||
|
||||
assertEquals(new Integer(12), Await.result(result, Duration.create(3, "seconds")));
|
||||
|
||||
}
|
||||
|
||||
//#simpler-one-to-many
|
||||
public class Duplicator2<A> extends GraphStage<FlowShape<A, A>> {
|
||||
|
||||
public final Inlet<A> in = Inlet.create("Duplicator.in");
|
||||
public final Outlet<A> out = Outlet.create("Duplicator.out");
|
||||
|
||||
private final FlowShape<A, A> shape = FlowShape.of(in, out);
|
||||
|
||||
@Override
|
||||
public FlowShape<A, A> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new GraphStageLogic(shape) {
|
||||
|
||||
{
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
A elem = grab(in);
|
||||
// this will temporarily suspend this handler until the two elems
|
||||
// are emitted and then reinstates it
|
||||
emitMultiple(out, Arrays.asList(elem, elem).iterator());
|
||||
}
|
||||
});
|
||||
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
pull(in);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//#simpler-one-to-many
|
||||
|
||||
|
||||
|
||||
@Test
|
||||
public void demonstrateASimplerOneToManyStage() throws Exception {
|
||||
// tests:
|
||||
Graph<FlowShape<Integer, Integer>, BoxedUnit> duplicator =
|
||||
Flow.fromGraph(new Duplicator2<Integer>());
|
||||
|
||||
Future<Integer> result =
|
||||
Source.from(Arrays.asList(1, 2, 3))
|
||||
.via(duplicator)
|
||||
.runFold(0, (n, sum) -> n + sum, mat);
|
||||
|
||||
assertEquals(new Integer(12), Await.result(result, Duration.create(3, "seconds")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateChainingOfGraphStages() throws Exception {
|
||||
Graph<SinkShape<Integer>, Future<String>> sink = Sink.fold("", (acc, n) -> acc + n.toString());
|
||||
|
||||
//#graph-stage-chain
|
||||
Future<String> resultFuture = Source.from(Arrays.asList(1,2,3,4,5))
|
||||
.via(new Filter<Integer>((n) -> n % 2 == 0))
|
||||
.via(new Duplicator<Integer>())
|
||||
.via(new Map<Integer, Integer>((n) -> n / 2))
|
||||
.runWith(sink, mat);
|
||||
|
||||
//#graph-stage-chain
|
||||
|
||||
assertEquals("1122", Await.result(resultFuture, Duration.create(3, "seconds")));
|
||||
}
|
||||
|
||||
|
||||
//#async-side-channel
|
||||
// will close upstream when the future completes
|
||||
public class KillSwitch<A> extends GraphStage<FlowShape<A, A>> {
|
||||
|
||||
private final Future<BoxedUnit> switchF;
|
||||
|
||||
public KillSwitch(Future<BoxedUnit> switchF) {
|
||||
this.switchF = switchF;
|
||||
}
|
||||
|
||||
public final Inlet<A> in = Inlet.create("KillSwitch.in");
|
||||
public final Outlet<A> out = Outlet.create("KillSwitch.out");
|
||||
|
||||
private final FlowShape<A, A> shape = FlowShape.of(in, out);
|
||||
@Override
|
||||
public FlowShape<A, A> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new GraphStageLogic(shape) {
|
||||
|
||||
{
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
push(out, grab(in));
|
||||
}
|
||||
});
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
pull(in);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
AsyncCallback<BoxedUnit> callback = createAsyncCallback(new Procedure<BoxedUnit>() {
|
||||
@Override
|
||||
public void apply(BoxedUnit param) throws Exception {
|
||||
completeStage();
|
||||
}
|
||||
});
|
||||
|
||||
ExecutionContext ec = system.dispatcher();
|
||||
switchF.onSuccess(new OnSuccess<BoxedUnit>() {
|
||||
@Override
|
||||
public void onSuccess(BoxedUnit result) throws Throwable {
|
||||
callback.invoke(BoxedUnit.UNIT);
|
||||
}
|
||||
}, ec);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//#async-side-channel
|
||||
|
||||
@Test
|
||||
public void demonstrateAnAsynchronousSideChannel() throws Exception{
|
||||
|
||||
// tests:
|
||||
Promise<BoxedUnit> switchF = Futures.promise();
|
||||
Graph<FlowShape<Integer, Integer>, BoxedUnit> killSwitch =
|
||||
Flow.fromGraph(new KillSwitch<>(switchF.future()));
|
||||
|
||||
ExecutionContext ec = system.dispatcher();
|
||||
|
||||
// TODO this is probably racey, is there a way to make sure it happens after?
|
||||
Future<Integer> valueAfterKill = switchF.future().flatMap(new Mapper<BoxedUnit, Future<Integer>>() {
|
||||
@Override
|
||||
public Future<Integer> apply(BoxedUnit parameter) {
|
||||
return Futures.successful(4);
|
||||
}
|
||||
}, ec);
|
||||
|
||||
|
||||
Future<Integer> result =
|
||||
Source.from(Arrays.asList(1, 2, 3)).concat(Source.fromFuture(valueAfterKill))
|
||||
.via(killSwitch)
|
||||
.runFold(0, (n, sum) -> n + sum, mat);
|
||||
|
||||
switchF.success(BoxedUnit.UNIT);
|
||||
|
||||
assertEquals(new Integer(6), Await.result(result, Duration.create(3, "seconds")));
|
||||
}
|
||||
|
||||
|
||||
//#timed
|
||||
// each time an event is pushed through it will trigger a period of silence
|
||||
public class TimedGate<A> extends GraphStage<FlowShape<A, A>> {
|
||||
|
||||
private final FiniteDuration silencePeriod;
|
||||
|
||||
public TimedGate(FiniteDuration silencePeriod) {
|
||||
this.silencePeriod = silencePeriod;
|
||||
}
|
||||
|
||||
public final Inlet<A> in = Inlet.create("TimedGate.in");
|
||||
public final Outlet<A> out = Outlet.create("TimedGate.out");
|
||||
|
||||
private final FlowShape<A, A> shape = FlowShape.of(in, out);
|
||||
@Override
|
||||
public FlowShape<A, A> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new TimerGraphStageLogic(shape) {
|
||||
|
||||
private boolean open = false;
|
||||
|
||||
{
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
A elem = grab(in);
|
||||
if (open) pull(in);
|
||||
else {
|
||||
push(out, elem);
|
||||
open = true;
|
||||
scheduleOnce("key", silencePeriod);
|
||||
}
|
||||
}
|
||||
});
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
pull(in);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimer(Object key) {
|
||||
if (key.equals("key")) {
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
//#timed
|
||||
|
||||
public void demonstrateAGraphStageWithATimer() throws Exception {
|
||||
// tests:
|
||||
Future<Integer> result =
|
||||
Source.from(Arrays.asList(1, 2, 3))
|
||||
.via(new TimedGate<>(Duration.create(2, "seconds")))
|
||||
.takeWithin(Duration.create(250, "millis"))
|
||||
.runFold(0, (n, sum) -> n + sum, mat);
|
||||
|
||||
assertEquals(new Integer(1), Await.result(result, Duration.create(3, "seconds")));
|
||||
}
|
||||
|
||||
|
||||
//#materialized
|
||||
public class FirstValue<A> extends GraphStageWithMaterializedValue<FlowShape<A, A>, Future<A>> {
|
||||
|
||||
public final Inlet<A> in = Inlet.create("FirstValue.in");
|
||||
public final Outlet<A> out = Outlet.create("FirstValue.out");
|
||||
|
||||
private final FlowShape<A, A> shape = FlowShape.of(in, out);
|
||||
@Override
|
||||
public FlowShape<A, A> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Tuple2<GraphStageLogic, Future<A>> createLogicAndMaterializedValue(Attributes inheritedAttributes) {
|
||||
Promise<A> promise = Futures.promise();
|
||||
|
||||
GraphStageLogic logic = new GraphStageLogic(shape) {
|
||||
{
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
A elem = grab(in);
|
||||
promise.success(elem);
|
||||
push(out, elem);
|
||||
|
||||
// replace handler with one just forwarding
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
push(out, grab(in));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
pull(in);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return new Tuple2(logic, promise.future());
|
||||
}
|
||||
}
|
||||
//#materialized
|
||||
|
||||
public void demonstrateACustomMaterializedValue() throws Exception {
|
||||
// tests:
|
||||
RunnableGraph<Future<Integer>> flow = Source.from(Arrays.asList(1, 2, 3))
|
||||
.viaMat(new FirstValue(), Keep.right())
|
||||
.to(Sink.ignore());
|
||||
|
||||
Future<Integer> result = flow.run(mat);
|
||||
|
||||
assertEquals(new Integer(1), Await.result(result, Duration.create(3, "seconds")));
|
||||
}
|
||||
|
||||
|
||||
//#detached
|
||||
public class TwoBuffer<A> extends GraphStage<FlowShape<A, A>> {
|
||||
|
||||
public final Inlet<A> in = Inlet.create("TwoBuffer.in");
|
||||
public final Outlet<A> out = Outlet.create("TwoBuffer.out");
|
||||
|
||||
private final FlowShape<A, A> shape = FlowShape.of(in, out);
|
||||
|
||||
@Override
|
||||
public FlowShape<A, A> shape() {
|
||||
return shape;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GraphStageLogic createLogic(Attributes inheritedAttributes) {
|
||||
return new GraphStageLogic(shape) {
|
||||
|
||||
private final int SIZE = 2;
|
||||
private Queue<A> buffer = new ArrayDeque<>(SIZE);
|
||||
private boolean downstreamWaiting = false;
|
||||
|
||||
private boolean isBufferFull() {
|
||||
return buffer.size() == SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
// a detached stage needs to start upstream demand
|
||||
// itself as it is not triggered by downstream demand
|
||||
pull(in);
|
||||
}
|
||||
|
||||
{
|
||||
setHandler(in, new AbstractInHandler() {
|
||||
@Override
|
||||
public void onPush() {
|
||||
A elem = grab(in);
|
||||
buffer.add(elem);
|
||||
if (downstreamWaiting) {
|
||||
downstreamWaiting = false;
|
||||
A bufferedElem = buffer.poll();
|
||||
push(out, bufferedElem);
|
||||
}
|
||||
if (!isBufferFull()) {
|
||||
pull(in);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpstreamFinish() {
|
||||
if (!buffer.isEmpty()) {
|
||||
// emit the rest if possible
|
||||
emitMultiple(out, buffer.iterator());
|
||||
}
|
||||
completeStage();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
setHandler(out, new AbstractOutHandler() {
|
||||
@Override
|
||||
public void onPull() {
|
||||
if (buffer.isEmpty()) {
|
||||
downstreamWaiting = true;
|
||||
} else {
|
||||
A elem = buffer.poll();
|
||||
push(out, elem);
|
||||
}
|
||||
if (!isBufferFull() && !hasBeenPulled(in)) {
|
||||
pull(in);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
//#detached
|
||||
|
||||
|
||||
public void demonstrateADetachedGraphStage() throws Exception {
|
||||
// tests:
|
||||
Future<Integer> result1 = Source.from(Arrays.asList(1, 2, 3))
|
||||
.via(new TwoBuffer<>())
|
||||
.runFold(0, (acc, n) -> acc + n, mat);
|
||||
|
||||
assertEquals(new Integer(6), Await.result(result1, Duration.create(3, "seconds")));
|
||||
|
||||
TestSubscriber.ManualProbe<Integer> subscriber = TestSubscriber.manualProbe(system);
|
||||
TestPublisher.Probe<Integer> publisher = TestPublisher.probe(0, system);
|
||||
RunnableGraph<BoxedUnit> flow2 =
|
||||
Source.fromPublisher(publisher)
|
||||
.via(new TwoBuffer<>())
|
||||
.to(Sink.fromSubscriber(subscriber));
|
||||
|
||||
flow2.run(mat);
|
||||
|
||||
Subscription sub = subscriber.expectSubscription();
|
||||
// this happens even though the subscriber has not signalled any demand
|
||||
publisher.sendNext(1);
|
||||
publisher.sendNext(2);
|
||||
|
||||
sub.cancel();
|
||||
}
|
||||
|
||||
}
|
||||
596
akka-docs/rst/java/code/docs/stream/IntegrationDocTest.java
Normal file
596
akka-docs/rst/java/code/docs/stream/IntegrationDocTest.java
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.stream;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.dispatch.Futures;
|
||||
import akka.dispatch.MessageDispatcher;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.testkit.TestProbe;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import docs.stream.TwitterStreamQuickstartDocTest.Model.Author;
|
||||
import docs.stream.TwitterStreamQuickstartDocTest.Model.Tweet;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.ExecutionContext;
|
||||
import scala.concurrent.Future;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import static akka.pattern.Patterns.ask;
|
||||
import static docs.stream.TwitterStreamQuickstartDocTest.Model.AKKA;
|
||||
import static docs.stream.TwitterStreamQuickstartDocTest.Model.tweets;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
public class IntegrationDocTest {
|
||||
|
||||
private static final SilenceSystemOut.System System = SilenceSystemOut.get();
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
final Config config = ConfigFactory.parseString("" +
|
||||
"blocking-dispatcher { \n" +
|
||||
" executor = thread-pool-executor \n" +
|
||||
" thread-pool-executor { \n" +
|
||||
" core-pool-size-min = 10 \n" +
|
||||
" core-pool-size-max = 10 \n" +
|
||||
" } \n" +
|
||||
"} \n" +
|
||||
"akka.actor.default-mailbox.mailbox-type = akka.dispatch.UnboundedMailbox\n");
|
||||
|
||||
system = ActorSystem.create("ActorPublisherDocTest", config);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
class AddressSystem {
|
||||
//#email-address-lookup
|
||||
public Future<Optional<String>> lookupEmail(String handle)
|
||||
//#email-address-lookup
|
||||
{
|
||||
return Futures.successful(Optional.of(handle + "@somewhere.com"));
|
||||
}
|
||||
|
||||
//#phone-lookup
|
||||
public Future<Optional<String>> lookupPhoneNumber(String handle)
|
||||
//#phone-lookup
|
||||
{
|
||||
return Futures.successful(Optional.of("" + handle.hashCode()));
|
||||
}
|
||||
}
|
||||
|
||||
class AddressSystem2 {
|
||||
//#email-address-lookup2
|
||||
public Future<String> lookupEmail(String handle)
|
||||
//#email-address-lookup2
|
||||
{
|
||||
return Futures.successful(handle + "@somewhere.com");
|
||||
}
|
||||
}
|
||||
|
||||
static class Email {
|
||||
public final String to;
|
||||
public final String title;
|
||||
public final String body;
|
||||
|
||||
public Email(String to, String title, String body) {
|
||||
this.to = to;
|
||||
this.title = title;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Email email = (Email) o;
|
||||
|
||||
if (body != null ? !body.equals(email.body) : email.body != null) {
|
||||
return false;
|
||||
}
|
||||
if (title != null ? !title.equals(email.title) : email.title != null) {
|
||||
return false;
|
||||
}
|
||||
if (to != null ? !to.equals(email.to) : email.to != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = to != null ? to.hashCode() : 0;
|
||||
result = 31 * result + (title != null ? title.hashCode() : 0);
|
||||
result = 31 * result + (body != null ? body.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static class TextMessage {
|
||||
public final String to;
|
||||
public final String body;
|
||||
|
||||
TextMessage(String to, String body) {
|
||||
this.to = to;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TextMessage that = (TextMessage) o;
|
||||
|
||||
if (body != null ? !body.equals(that.body) : that.body != null) {
|
||||
return false;
|
||||
}
|
||||
if (to != null ? !to.equals(that.to) : that.to != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = to != null ? to.hashCode() : 0;
|
||||
result = 31 * result + (body != null ? body.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
static class EmailServer {
|
||||
public final ActorRef probe;
|
||||
|
||||
public EmailServer(ActorRef probe) {
|
||||
this.probe = probe;
|
||||
}
|
||||
|
||||
//#email-server-send
|
||||
public Future<Email> send(Email email) {
|
||||
// ...
|
||||
//#email-server-send
|
||||
probe.tell(email.to, ActorRef.noSender());
|
||||
return Futures.successful(email);
|
||||
//#email-server-send
|
||||
}
|
||||
//#email-server-send
|
||||
}
|
||||
|
||||
|
||||
static class SmsServer {
|
||||
public final ActorRef probe;
|
||||
|
||||
public SmsServer(ActorRef probe) {
|
||||
this.probe = probe;
|
||||
}
|
||||
|
||||
//#sms-server-send
|
||||
public boolean send(TextMessage text) {
|
||||
// ...
|
||||
//#sms-server-send
|
||||
probe.tell(text.to, ActorRef.noSender());
|
||||
//#sms-server-send
|
||||
return true;
|
||||
}
|
||||
//#sms-server-send
|
||||
}
|
||||
|
||||
static class Save {
|
||||
public final Tweet tweet;
|
||||
|
||||
Save(Tweet tweet) {
|
||||
this.tweet = tweet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Save save = (Save) o;
|
||||
|
||||
if (tweet != null ? !tweet.equals(save.tweet) : save.tweet != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return tweet != null ? tweet.hashCode() : 0;
|
||||
}
|
||||
}
|
||||
static class SaveDone {
|
||||
public static SaveDone INSTANCE = new SaveDone();
|
||||
private SaveDone() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static class DatabaseService extends AbstractActor {
|
||||
public final ActorRef probe;
|
||||
|
||||
DatabaseService(ActorRef probe) {
|
||||
this.probe = probe;
|
||||
|
||||
receive(ReceiveBuilder.match(Save.class, s -> {
|
||||
probe.tell(s.tweet.author.handle, ActorRef.noSender());
|
||||
sender().tell(SaveDone.INSTANCE, self());
|
||||
}).build());
|
||||
}
|
||||
}
|
||||
|
||||
//#sometimes-slow-service
|
||||
static class SometimesSlowService {
|
||||
private final ExecutionContext ec;
|
||||
|
||||
public SometimesSlowService(ExecutionContext ec) {
|
||||
this.ec = ec;
|
||||
}
|
||||
|
||||
private final AtomicInteger runningCount = new AtomicInteger();
|
||||
|
||||
public Future<String> convert(String s) {
|
||||
System.out.println("running: " + s + "(" + runningCount.incrementAndGet() + ")");
|
||||
return Futures.future(() -> {
|
||||
if (!s.isEmpty() && Character.isLowerCase(s.charAt(0)))
|
||||
Thread.sleep(500);
|
||||
else
|
||||
Thread.sleep(20);
|
||||
System.out.println("completed: " + s + "(" + runningCount.decrementAndGet() + ")");
|
||||
return s.toUpperCase();
|
||||
}, ec);
|
||||
}
|
||||
}
|
||||
//#sometimes-slow-service
|
||||
|
||||
|
||||
@Test
|
||||
public void callingExternalServiceWithMapAsync() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
final AddressSystem addressSystem = new AddressSystem();
|
||||
final EmailServer emailServer = new EmailServer(probe.ref());
|
||||
|
||||
{
|
||||
//#tweet-authors
|
||||
final Source<Author, BoxedUnit> authors = tweets
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> t.author);
|
||||
|
||||
//#tweet-authors
|
||||
|
||||
//#email-addresses-mapAsync
|
||||
final Source<String, BoxedUnit> emailAddresses = authors
|
||||
.mapAsync(4, author -> addressSystem.lookupEmail(author.handle))
|
||||
.filter(o -> o.isPresent())
|
||||
.map(o -> o.get());
|
||||
|
||||
//#email-addresses-mapAsync
|
||||
|
||||
//#send-emails
|
||||
final RunnableGraph<BoxedUnit> sendEmails = emailAddresses
|
||||
.mapAsync(4, address ->
|
||||
emailServer.send(new Email(address, "Akka", "I like your tweet")))
|
||||
.to(Sink.ignore());
|
||||
|
||||
sendEmails.run(mat);
|
||||
//#send-emails
|
||||
|
||||
probe.expectMsg("rolandkuhn@somewhere.com");
|
||||
probe.expectMsg("patriknw@somewhere.com");
|
||||
probe.expectMsg("bantonsson@somewhere.com");
|
||||
probe.expectMsg("drewhk@somewhere.com");
|
||||
probe.expectMsg("ktosopl@somewhere.com");
|
||||
probe.expectMsg("mmartynas@somewhere.com");
|
||||
probe.expectMsg("akkateam@somewhere.com");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
public void callingExternalServiceWithMapAsyncAndSupervision() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final AddressSystem2 addressSystem = new AddressSystem2();
|
||||
|
||||
{
|
||||
final Source<Author, BoxedUnit> authors = tweets
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> t.author);
|
||||
|
||||
//#email-addresses-mapAsync-supervision
|
||||
final Attributes resumeAttrib =
|
||||
ActorAttributes.withSupervisionStrategy(Supervision.getResumingDecider());
|
||||
final Flow<Author, String, BoxedUnit> lookupEmail =
|
||||
Flow.of(Author.class)
|
||||
.mapAsync(4, author -> addressSystem.lookupEmail(author.handle))
|
||||
.withAttributes(resumeAttrib);
|
||||
final Source<String, BoxedUnit> emailAddresses = authors.via(lookupEmail);
|
||||
|
||||
//#email-addresses-mapAsync-supervision
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void callingExternalServiceWithMapAsyncUnordered() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
final AddressSystem addressSystem = new AddressSystem();
|
||||
final EmailServer emailServer = new EmailServer(probe.ref());
|
||||
|
||||
{
|
||||
//#external-service-mapAsyncUnordered
|
||||
final Source<Author, BoxedUnit> authors =
|
||||
tweets
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> t.author);
|
||||
|
||||
final Source<String, BoxedUnit> emailAddresses =
|
||||
authors
|
||||
.mapAsyncUnordered(4, author -> addressSystem.lookupEmail(author.handle))
|
||||
.filter(o -> o.isPresent())
|
||||
.map(o -> o.get());
|
||||
|
||||
final RunnableGraph<BoxedUnit> sendEmails =
|
||||
emailAddresses
|
||||
.mapAsyncUnordered(4, address ->
|
||||
emailServer.send(new Email(address, "Akka", "I like your tweet")))
|
||||
.to(Sink.ignore());
|
||||
|
||||
sendEmails.run(mat);
|
||||
//#external-service-mapAsyncUnordered
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carefulManagedBlockingWithMapAsync() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final AddressSystem addressSystem = new AddressSystem();
|
||||
final EmailServer emailServer = new EmailServer(getRef());
|
||||
final SmsServer smsServer = new SmsServer(getRef());
|
||||
|
||||
{
|
||||
final Source<Author, BoxedUnit> authors =
|
||||
tweets
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> t.author);
|
||||
|
||||
final Source<String, BoxedUnit> phoneNumbers = authors.mapAsync(4, author -> addressSystem.lookupPhoneNumber(author.handle))
|
||||
.filter(o -> o.isPresent())
|
||||
.map(o -> o.get());
|
||||
|
||||
//#blocking-mapAsync
|
||||
final MessageDispatcher blockingEc = system.dispatchers().lookup("blocking-dispatcher");
|
||||
|
||||
final RunnableGraph<BoxedUnit> sendTextMessages =
|
||||
phoneNumbers
|
||||
.mapAsync(4, phoneNo ->
|
||||
Futures.future(() ->
|
||||
smsServer.send(new TextMessage(phoneNo, "I like your tweet")),
|
||||
blockingEc)
|
||||
)
|
||||
.to(Sink.ignore());
|
||||
|
||||
sendTextMessages.run(mat);
|
||||
//#blocking-mapAsync
|
||||
|
||||
final Object[] got = receiveN(7);
|
||||
final Set<Object> set = new HashSet<>(Arrays.asList(got));
|
||||
|
||||
assertTrue(set.contains(String.valueOf("rolandkuhn".hashCode())));
|
||||
assertTrue(set.contains(String.valueOf("patriknw".hashCode())));
|
||||
assertTrue(set.contains(String.valueOf("bantonsson".hashCode())));
|
||||
assertTrue(set.contains(String.valueOf("drewhk".hashCode())));
|
||||
assertTrue(set.contains(String.valueOf("ktosopl".hashCode())));
|
||||
assertTrue(set.contains(String.valueOf("mmartynas".hashCode())));
|
||||
assertTrue(set.contains(String.valueOf("akkateam".hashCode())));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void carefulManagedBlockingWithMap() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
final AddressSystem addressSystem = new AddressSystem();
|
||||
final EmailServer emailServer = new EmailServer(probe.ref());
|
||||
final SmsServer smsServer = new SmsServer(probe.ref());
|
||||
|
||||
{
|
||||
final Source<Author, BoxedUnit> authors =
|
||||
tweets
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> t.author);
|
||||
|
||||
final Source<String, BoxedUnit> phoneNumbers = authors.mapAsync(4, author -> addressSystem.lookupPhoneNumber(author.handle))
|
||||
.filter(o -> o.isPresent())
|
||||
.map(o -> o.get());
|
||||
|
||||
//#blocking-map
|
||||
final Flow<String, Boolean, BoxedUnit> send =
|
||||
Flow.of(String.class)
|
||||
.map(phoneNo -> smsServer.send(new TextMessage(phoneNo, "I like your tweet")))
|
||||
.withAttributes(ActorAttributes.dispatcher("blocking-dispatcher"));
|
||||
final RunnableGraph<?> sendTextMessages =
|
||||
phoneNumbers.via(send).to(Sink.ignore());
|
||||
|
||||
sendTextMessages.run(mat);
|
||||
//#blocking-map
|
||||
|
||||
probe.expectMsg(String.valueOf("rolandkuhn".hashCode()));
|
||||
probe.expectMsg(String.valueOf("patriknw".hashCode()));
|
||||
probe.expectMsg(String.valueOf("bantonsson".hashCode()));
|
||||
probe.expectMsg(String.valueOf("drewhk".hashCode()));
|
||||
probe.expectMsg(String.valueOf("ktosopl".hashCode()));
|
||||
probe.expectMsg(String.valueOf("mmartynas".hashCode()));
|
||||
probe.expectMsg(String.valueOf("akkateam".hashCode()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void callingActorServiceWithMapAsync() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
final EmailServer emailServer = new EmailServer(probe.ref());
|
||||
|
||||
final ActorRef database = system.actorOf(Props.create(DatabaseService.class, probe.ref()), "db");
|
||||
|
||||
{
|
||||
//#save-tweets
|
||||
final Source<Tweet, BoxedUnit> akkaTweets = tweets.filter(t -> t.hashtags().contains(AKKA));
|
||||
|
||||
final RunnableGraph<BoxedUnit> saveTweets =
|
||||
akkaTweets
|
||||
.mapAsync(4, tweet -> ask(database, new Save(tweet), 300))
|
||||
.to(Sink.ignore());
|
||||
//#save-tweets
|
||||
|
||||
saveTweets.run(mat);
|
||||
|
||||
probe.expectMsg("rolandkuhn");
|
||||
probe.expectMsg("patriknw");
|
||||
probe.expectMsg("bantonsson");
|
||||
probe.expectMsg("drewhk");
|
||||
probe.expectMsg("ktosopl");
|
||||
probe.expectMsg("mmartynas");
|
||||
probe.expectMsg("akkateam");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void illustrateOrderingAndParallelismOfMapAsync() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
final EmailServer emailServer = new EmailServer(probe.ref());
|
||||
|
||||
class MockSystem {
|
||||
class Println {
|
||||
public <T> void println(T s) {
|
||||
if (s.toString().startsWith("after:"))
|
||||
probe.ref().tell(s, ActorRef.noSender());
|
||||
}
|
||||
}
|
||||
|
||||
public final Println out = new Println();
|
||||
}
|
||||
private final MockSystem System = new MockSystem();
|
||||
|
||||
{
|
||||
//#sometimes-slow-mapAsync
|
||||
final MessageDispatcher blockingEc = system.dispatchers().lookup("blocking-dispatcher");
|
||||
final SometimesSlowService service = new SometimesSlowService(blockingEc);
|
||||
|
||||
final ActorMaterializer mat = ActorMaterializer.create(
|
||||
ActorMaterializerSettings.create(system).withInputBuffer(4, 4), system);
|
||||
|
||||
Source.from(Arrays.asList("a", "B", "C", "D", "e", "F", "g", "H", "i", "J"))
|
||||
.map(elem -> { System.out.println("before: " + elem); return elem; })
|
||||
.mapAsync(4, service::convert)
|
||||
.runForeach(elem -> System.out.println("after: " + elem), mat);
|
||||
//#sometimes-slow-mapAsync
|
||||
|
||||
probe.expectMsg("after: A");
|
||||
probe.expectMsg("after: B");
|
||||
probe.expectMsg("after: C");
|
||||
probe.expectMsg("after: D");
|
||||
probe.expectMsg("after: E");
|
||||
probe.expectMsg("after: F");
|
||||
probe.expectMsg("after: G");
|
||||
probe.expectMsg("after: H");
|
||||
probe.expectMsg("after: I");
|
||||
probe.expectMsg("after: J");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void illustrateOrderingAndParallelismOfMapAsyncUnordered() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final EmailServer emailServer = new EmailServer(getRef());
|
||||
|
||||
class MockSystem {
|
||||
class Println {
|
||||
public <T> void println(T s) {
|
||||
if (s.toString().startsWith("after:"))
|
||||
getRef().tell(s, ActorRef.noSender());
|
||||
}
|
||||
}
|
||||
|
||||
public final Println out = new Println();
|
||||
}
|
||||
private final MockSystem System = new MockSystem();
|
||||
|
||||
{
|
||||
//#sometimes-slow-mapAsyncUnordered
|
||||
final MessageDispatcher blockingEc = system.dispatchers().lookup("blocking-dispatcher");
|
||||
final SometimesSlowService service = new SometimesSlowService(blockingEc);
|
||||
|
||||
final ActorMaterializer mat = ActorMaterializer.create(
|
||||
ActorMaterializerSettings.create(system).withInputBuffer(4, 4), system);
|
||||
|
||||
Source.from(Arrays.asList("a", "B", "C", "D", "e", "F", "g", "H", "i", "J"))
|
||||
.map(elem -> { System.out.println("before: " + elem); return elem; })
|
||||
.mapAsyncUnordered(4, service::convert)
|
||||
.runForeach(elem -> System.out.println("after: " + elem), mat);
|
||||
//#sometimes-slow-mapAsyncUnordered
|
||||
|
||||
final Object[] got = receiveN(10);
|
||||
final Set<Object> set = new HashSet<>(Arrays.asList(got));
|
||||
|
||||
assertTrue(set.contains("after: A"));
|
||||
assertTrue(set.contains("after: B"));
|
||||
assertTrue(set.contains("after: C"));
|
||||
assertTrue(set.contains("after: D"));
|
||||
assertTrue(set.contains("after: E"));
|
||||
assertTrue(set.contains("after: F"));
|
||||
assertTrue(set.contains("after: G"));
|
||||
assertTrue(set.contains("after: H"));
|
||||
assertTrue(set.contains("after: I"));
|
||||
assertTrue(set.contains("after: J"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
259
akka-docs/rst/java/code/docs/stream/MigrationsJava.java
Normal file
259
akka-docs/rst/java/code/docs/stream/MigrationsJava.java
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Cancellable;
|
||||
import akka.http.javadsl.model.Uri;
|
||||
import akka.dispatch.Futures;
|
||||
import akka.japi.function.Creator;
|
||||
import akka.japi.Pair;
|
||||
import akka.japi.function.Function;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.testkit.TestPublisher;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.util.ByteString;
|
||||
import scala.Option;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.concurrent.Promise;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
public class MigrationsJava {
|
||||
|
||||
// This is compile-only code, no need for actually running anything.
|
||||
public static ActorMaterializer mat = null;
|
||||
public static ActorSystem sys = null;
|
||||
|
||||
public static class SomeInputStream extends InputStream {
|
||||
public SomeInputStream() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SomeOutputStream extends OutputStream {
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
Outlet<Integer> outlet = null;
|
||||
|
||||
Outlet<Integer> outlet1 = null;
|
||||
Outlet<Integer> outlet2 = null;
|
||||
|
||||
Inlet<Integer> inlet = null;
|
||||
|
||||
Inlet<Integer> inlet1 = null;
|
||||
Inlet<Integer> inlet2 = null;
|
||||
|
||||
Flow<Integer, Integer, BoxedUnit> flow = Flow.of(Integer.class);
|
||||
Flow<Integer, Integer, BoxedUnit> flow1 = Flow.of(Integer.class);
|
||||
Flow<Integer, Integer, BoxedUnit> flow2 = Flow.of(Integer.class);
|
||||
|
||||
Promise<Option<Integer>> promise = null;
|
||||
|
||||
|
||||
{
|
||||
Graph<SourceShape<Integer>, BoxedUnit> graphSource = null;
|
||||
Graph<SinkShape<Integer>, BoxedUnit> graphSink = null;
|
||||
Graph<FlowShape<Integer, Integer>, BoxedUnit> graphFlow = null;
|
||||
|
||||
//#flow-wrap
|
||||
Source<Integer, BoxedUnit> source = Source.fromGraph(graphSource);
|
||||
Sink<Integer, BoxedUnit> sink = Sink.fromGraph(graphSink);
|
||||
Flow<Integer, Integer, BoxedUnit> aflow = Flow.fromGraph(graphFlow);
|
||||
Flow.fromSinkAndSource(Sink.<Integer>head(), Source.single(0));
|
||||
Flow.fromSinkAndSourceMat(Sink.<Integer>head(), Source.single(0), Keep.left());
|
||||
//#flow-wrap
|
||||
|
||||
Graph<BidiShape<Integer, Integer, Integer, Integer>, BoxedUnit> bidiGraph = null;
|
||||
|
||||
//#bidi-wrap
|
||||
BidiFlow<Integer, Integer, Integer, Integer, BoxedUnit> bidiFlow =
|
||||
BidiFlow.fromGraph(bidiGraph);
|
||||
BidiFlow.fromFlows(flow1, flow2);
|
||||
BidiFlow.fromFlowsMat(flow1, flow2, Keep.both());
|
||||
//#bidi-wrap
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
//#graph-create
|
||||
GraphDSL.create(builder -> {
|
||||
//...
|
||||
return ClosedShape.getInstance();
|
||||
});
|
||||
|
||||
GraphDSL.create(builder -> {
|
||||
//...
|
||||
return new FlowShape<>(inlet, outlet);
|
||||
});
|
||||
//#graph-create
|
||||
}
|
||||
|
||||
{
|
||||
//#graph-create-2
|
||||
GraphDSL.create(builder -> {
|
||||
//...
|
||||
return SourceShape.of(outlet);
|
||||
});
|
||||
|
||||
GraphDSL.create(builder -> {
|
||||
//...
|
||||
return SinkShape.of(inlet);
|
||||
});
|
||||
|
||||
GraphDSL.create(builder -> {
|
||||
//...
|
||||
return FlowShape.of(inlet, outlet);
|
||||
});
|
||||
|
||||
GraphDSL.create(builder -> {
|
||||
//...
|
||||
return BidiShape.of(inlet1, outlet1, inlet2, outlet2);
|
||||
});
|
||||
//#graph-create-2
|
||||
}
|
||||
|
||||
{
|
||||
//#graph-builder
|
||||
GraphDSL.create(builder -> {
|
||||
builder.from(outlet).toInlet(inlet);
|
||||
builder.from(outlet).via(builder.add(flow)).toInlet(inlet);
|
||||
builder.from(builder.add(Source.single(0))).to(builder.add(Sink.head()));
|
||||
//...
|
||||
return ClosedShape.getInstance();
|
||||
});
|
||||
//#graph-builder
|
||||
}
|
||||
|
||||
//#source-creators
|
||||
Source<Integer, Promise<Option<Integer>>> src = Source.<Integer>maybe();
|
||||
// Complete the promise with an empty option to emulate the old lazyEmpty
|
||||
promise.trySuccess(scala.Option.empty());
|
||||
|
||||
final Source<String, Cancellable> ticks = Source.tick(
|
||||
FiniteDuration.create(0, TimeUnit.MILLISECONDS),
|
||||
FiniteDuration.create(200, TimeUnit.MILLISECONDS),
|
||||
"tick");
|
||||
|
||||
final Source<Integer, BoxedUnit> pubSource =
|
||||
Source.fromPublisher(TestPublisher.<Integer>manualProbe(true, sys));
|
||||
|
||||
final Source<Integer, BoxedUnit> futSource =
|
||||
Source.fromFuture(Futures.successful(42));
|
||||
|
||||
final Source<Integer, Subscriber<Integer>> subSource =
|
||||
Source.<Integer>asSubscriber();
|
||||
//#source-creators
|
||||
|
||||
//#sink-creators
|
||||
final Sink<Integer, BoxedUnit> subSink =
|
||||
Sink.fromSubscriber(TestSubscriber.<Integer>manualProbe(sys));
|
||||
//#sink-creators
|
||||
|
||||
//#sink-as-publisher
|
||||
final Sink<Integer, Publisher<Integer>> pubSink =
|
||||
Sink.<Integer>asPublisher(false);
|
||||
|
||||
final Sink<Integer, Publisher<Integer>> pubSinkFanout =
|
||||
Sink.<Integer>asPublisher(true);
|
||||
//#sink-as-publisher
|
||||
|
||||
//#empty-flow
|
||||
Flow<Integer, Integer, BoxedUnit> emptyFlow = Flow.<Integer>create();
|
||||
// or
|
||||
Flow<Integer, Integer, BoxedUnit> emptyFlow2 = Flow.of(Integer.class);
|
||||
//#empty-flow
|
||||
|
||||
//#flatMapConcat
|
||||
Flow.<Source<Integer, BoxedUnit>>create().
|
||||
<Integer, BoxedUnit>flatMapConcat(i -> i);
|
||||
//#flatMapConcat
|
||||
|
||||
//#group-flatten
|
||||
Flow.of(Integer.class)
|
||||
.groupBy(2, in -> in % 2) // the first parameter sets max number of substreams
|
||||
.map(subIn -> + 3)
|
||||
.concatSubstreams();
|
||||
//#group-flatten
|
||||
|
||||
final int maxDistinctWords = 1000;
|
||||
//#group-fold
|
||||
Flow.of(String.class)
|
||||
.groupBy(maxDistinctWords, i -> i)
|
||||
.fold(Pair.create("", 0), (pair, word) -> Pair.create(word, pair.second() + 1))
|
||||
.mergeSubstreams();
|
||||
//#group-fold
|
||||
|
||||
Uri uri = null;
|
||||
//#raw-query
|
||||
final akka.japi.Option<String> theRawQueryString = uri.rawQueryString();
|
||||
//#raw-query
|
||||
|
||||
//#query-param
|
||||
final akka.japi.Option<String> aQueryParam = uri.query().get("a");
|
||||
//#query-param
|
||||
|
||||
//#file-source-sink
|
||||
final Source<ByteString, Future<Long>> fileSrc =
|
||||
FileIO.fromFile(new File("."));
|
||||
|
||||
final Source<ByteString, Future<Long>> otherFileSrc =
|
||||
FileIO.fromFile(new File("."), 1024);
|
||||
|
||||
final Sink<ByteString, Future<Long>> fileSink =
|
||||
FileIO.toFile(new File("."));
|
||||
//#file-source-sink
|
||||
|
||||
//#input-output-stream-source-sink
|
||||
final Source<ByteString, Future<java.lang.Long>> inputStreamSrc =
|
||||
StreamConverters.fromInputStream((Creator<InputStream>) () -> new SomeInputStream());
|
||||
|
||||
final Source<ByteString, Future<java.lang.Long>> otherInputStreamSrc =
|
||||
StreamConverters.fromInputStream((Creator<InputStream>) () -> new SomeInputStream(), 1024);
|
||||
|
||||
final Sink<ByteString, Future<java.lang.Long>> outputStreamSink =
|
||||
StreamConverters.fromOutputStream((Creator<OutputStream>) () -> new SomeOutputStream());
|
||||
//#input-output-stream-source-sink
|
||||
|
||||
|
||||
//#output-input-stream-source-sink
|
||||
final FiniteDuration timeout = FiniteDuration.Zero();
|
||||
|
||||
final Source<ByteString, OutputStream> outputStreamSrc =
|
||||
StreamConverters.asOutputStream();
|
||||
|
||||
final Source<ByteString, OutputStream> otherOutputStreamSrc =
|
||||
StreamConverters.asOutputStream(timeout);
|
||||
|
||||
final Sink<ByteString, InputStream> someInputStreamSink =
|
||||
StreamConverters.asInputStream();
|
||||
|
||||
final Sink<ByteString, InputStream> someOtherInputStreamSink =
|
||||
StreamConverters.asInputStream(timeout);
|
||||
//#output-input-stream-source-sink
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.stream;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.DoubleStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.japi.tuple.Tuple3;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.testkit.TestPublisher;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.stream.testkit.javadsl.TestSink;
|
||||
import akka.stream.testkit.javadsl.TestSource;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.testkit.TestLatch;
|
||||
import scala.collection.Iterator;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import scala.util.Random;
|
||||
|
||||
public class RateTransformationDocTest {
|
||||
|
||||
private static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RateTransformationDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
final Random r = new Random();
|
||||
|
||||
@Test
|
||||
public void conflateShouldSummarize() throws Exception {
|
||||
//#conflate-summarize
|
||||
final Flow<Double, Tuple3<Double, Double, Integer>, BoxedUnit> statsFlow =
|
||||
Flow.of(Double.class)
|
||||
.conflate(elem -> Collections.singletonList(elem), (acc, elem) -> {
|
||||
return Stream
|
||||
.concat(acc.stream(), Collections.singletonList(elem).stream())
|
||||
.collect(Collectors.toList());
|
||||
})
|
||||
.map(s -> {
|
||||
final Double mean = s.stream().mapToDouble(d -> d).sum() / s.size();
|
||||
final DoubleStream se = s.stream().mapToDouble(x -> Math.pow(x - mean, 2));
|
||||
final Double stdDev = Math.sqrt(se.sum() / s.size());
|
||||
return new Tuple3<>(stdDev, mean, s.size());
|
||||
});
|
||||
//#conflate-summarize
|
||||
|
||||
final Future<List<Tuple3<Double, Double, Integer>>> fut = Source.repeat(0).map(i -> r.nextGaussian())
|
||||
.via(statsFlow)
|
||||
.grouped(10)
|
||||
.runWith(Sink.head(), mat);
|
||||
|
||||
final Duration timeout = Duration.create(100, TimeUnit.MILLISECONDS);
|
||||
Await.result(fut, timeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conflateShouldSample() throws Exception {
|
||||
//#conflate-sample
|
||||
final Double p = 0.01;
|
||||
final Flow<Double, Double, BoxedUnit> sampleFlow = Flow.of(Double.class)
|
||||
.conflate(elem -> Collections.singletonList(elem), (acc, elem) -> {
|
||||
if (r.nextDouble() < p) {
|
||||
return Stream
|
||||
.concat(acc.stream(), Collections.singletonList(elem).stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
return acc;
|
||||
})
|
||||
.mapConcat(d -> d);
|
||||
//#conflate-sample
|
||||
|
||||
final Future<Double> fut = Source.from(new ArrayList<Double>(Collections.nCopies(1000, 1.0)))
|
||||
.via(sampleFlow)
|
||||
.runWith(Sink.fold(0.0, (agg, next) -> agg + next), mat);
|
||||
|
||||
final Duration timeout = Duration.create(1, TimeUnit.SECONDS);
|
||||
final Double count = Await.result(fut, timeout);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandShouldRepeatLast() throws Exception {
|
||||
//#expand-last
|
||||
final Flow<Double, Double, BoxedUnit> lastFlow = Flow.of(Double.class)
|
||||
.expand(d -> d, s -> new Pair<>(s, s));
|
||||
//#expand-last
|
||||
|
||||
final Pair<TestPublisher.Probe<Double>, Future<List<Double>>> probeFut = TestSource.<Double> probe(system)
|
||||
.via(lastFlow)
|
||||
.grouped(10)
|
||||
.toMat(Sink.head(), Keep.both())
|
||||
.run(mat);
|
||||
|
||||
final TestPublisher.Probe<Double> probe = probeFut.first();
|
||||
final Future<List<Double>> fut = probeFut.second();
|
||||
probe.sendNext(1.0);
|
||||
final Duration timeout = Duration.create(1, TimeUnit.SECONDS);
|
||||
final List<Double> expanded = Await.result(fut, timeout);
|
||||
assertEquals(expanded.size(), 10);
|
||||
assertEquals(expanded.stream().mapToDouble(d -> d).sum(), 10, 0.1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void expandShouldTrackDrift() throws Exception {
|
||||
@SuppressWarnings("unused")
|
||||
//#expand-drift
|
||||
final Flow<Double, Pair<Double, Integer>, BoxedUnit> driftFlow = Flow.of(Double.class)
|
||||
.expand(d -> new Pair<Double, Integer>(d, 0), t -> {
|
||||
return new Pair<>(t, new Pair<>(t.first(), t.second() + 1));
|
||||
});
|
||||
//#expand-drift
|
||||
final TestLatch latch = new TestLatch(2, system);
|
||||
final Flow<Double, Pair<Double, Integer>, BoxedUnit> realDriftFlow = Flow.of(Double.class)
|
||||
.expand(d -> { latch.countDown(); return new Pair<Double, Integer>(d, 0); }, t -> {
|
||||
return new Pair<>(t, new Pair<>(t.first(), t.second() + 1));
|
||||
});
|
||||
|
||||
final Pair<TestPublisher.Probe<Double>, TestSubscriber.Probe<Pair<Double, Integer>>> pubSub = TestSource.<Double> probe(system)
|
||||
.via(realDriftFlow)
|
||||
.toMat(TestSink.<Pair<Double, Integer>> probe(system), Keep.both())
|
||||
.run(mat);
|
||||
|
||||
final TestPublisher.Probe<Double> pub = pubSub.first();
|
||||
final TestSubscriber.Probe<Pair<Double, Integer>> sub = pubSub.second();
|
||||
|
||||
sub.request(1);
|
||||
pub.sendNext(1.0);
|
||||
sub.expectNext(new Pair<>(1.0, 0));
|
||||
|
||||
sub.requestNext(new Pair<>(1.0, 1));
|
||||
sub.requestNext(new Pair<>(1.0, 2));
|
||||
|
||||
pub.sendNext(2.0);
|
||||
Await.ready(latch, Duration.create(1, TimeUnit.SECONDS));
|
||||
sub.requestNext(new Pair<>(2.0, 0));
|
||||
}
|
||||
|
||||
}
|
||||
264
akka-docs/rst/java/code/docs/stream/ReactiveStreamsDocTest.java
Normal file
264
akka-docs/rst/java/code/docs/stream/ReactiveStreamsDocTest.java
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.stream;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.japi.function.Creator;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.testkit.TestProbe;
|
||||
import docs.stream.TwitterStreamQuickstartDocTest.Model.Author;
|
||||
import docs.stream.TwitterStreamQuickstartDocTest.Model.Tweet;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
//#imports
|
||||
import org.reactivestreams.Publisher;
|
||||
import org.reactivestreams.Subscriber;
|
||||
import org.reactivestreams.Processor;
|
||||
//#imports
|
||||
import org.reactivestreams.Subscription;
|
||||
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.lang.Exception;
|
||||
|
||||
import static docs.stream.ReactiveStreamsDocTest.Fixture.Data.authors;
|
||||
import static docs.stream.TwitterStreamQuickstartDocTest.Model.AKKA;
|
||||
|
||||
public class ReactiveStreamsDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("ReactiveStreamsDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
|
||||
static class Fixture {
|
||||
// below class additionally helps with aligning code includes nicely
|
||||
static class Data {
|
||||
|
||||
static //#authors
|
||||
final Flow<Tweet, Author, BoxedUnit> authors = Flow.of(Tweet.class)
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> t.author);
|
||||
|
||||
//#authors
|
||||
}
|
||||
|
||||
static interface RS {
|
||||
//#tweets-publisher
|
||||
Publisher<Tweet> tweets();
|
||||
//#tweets-publisher
|
||||
|
||||
//#author-storage-subscriber
|
||||
Subscriber<Author> storage();
|
||||
//#author-storage-subscriber
|
||||
|
||||
//#author-alert-subscriber
|
||||
Subscriber<Author> alert();
|
||||
//#author-alert-subscriber
|
||||
}
|
||||
}
|
||||
|
||||
final TestProbe storageProbe = TestProbe.apply(system);
|
||||
final TestProbe alertProbe = TestProbe.apply(system);
|
||||
|
||||
final Fixture.RS rs = new Fixture.RS() {
|
||||
@Override
|
||||
public Publisher<Tweet> tweets() {
|
||||
return TwitterStreamQuickstartDocTest.Model.tweets.runWith(Sink.asPublisher(false), mat);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a minimal version of SubscriberProbe,
|
||||
* which lives in akka-stream-testkit (test scope) and for
|
||||
* now wanted to avoid setting up (test -> compile) dependency for maven).
|
||||
*
|
||||
* TODO: Once SubscriberProbe is easily used here replace this MPS with it.
|
||||
*/
|
||||
class MinimalProbeSubscriber<T> implements Subscriber<T> {
|
||||
|
||||
private final ActorRef ref;
|
||||
|
||||
public MinimalProbeSubscriber(ActorRef ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSubscribe(Subscription s) {
|
||||
s.request(Long.MAX_VALUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(T t) {
|
||||
ref.tell(t, ActorRef.noSender());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
ref.tell(t, ActorRef.noSender());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
ref.tell("complete", ActorRef.noSender());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscriber<Author> storage() {
|
||||
return new MinimalProbeSubscriber<>(storageProbe.ref());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscriber<Author> alert() {
|
||||
return new MinimalProbeSubscriber<>(alertProbe.ref());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@Test
|
||||
public void reactiveStreamsPublisherViaFlowToSubscriber() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
|
||||
{
|
||||
//#connect-all
|
||||
Source.fromPublisher(rs.tweets())
|
||||
.via(authors)
|
||||
.to(Sink.fromSubscriber(rs.storage()));
|
||||
//#connect-all
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flowAsPublisherAndSubscriber() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
|
||||
{
|
||||
//#flow-publisher-subscriber
|
||||
final Processor<Tweet, Author> processor =
|
||||
authors.toProcessor().run(mat);
|
||||
|
||||
|
||||
rs.tweets().subscribe(processor);
|
||||
processor.subscribe(rs.storage());
|
||||
//#flow-publisher-subscriber
|
||||
|
||||
assertStorageResult();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceAsPublisher() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
|
||||
{
|
||||
//#source-publisher
|
||||
final Publisher<Author> authorPublisher =
|
||||
Source.fromPublisher(rs.tweets()).via(authors).runWith(Sink.asPublisher(false), mat);
|
||||
|
||||
authorPublisher.subscribe(rs.storage());
|
||||
//#source-publisher
|
||||
|
||||
assertStorageResult();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceAsFanoutPublisher() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
|
||||
{
|
||||
//#source-fanoutPublisher
|
||||
final Publisher<Author> authorPublisher =
|
||||
Source.fromPublisher(rs.tweets())
|
||||
.via(authors)
|
||||
.runWith(Sink.asPublisher(true), mat);
|
||||
|
||||
authorPublisher.subscribe(rs.storage());
|
||||
authorPublisher.subscribe(rs.alert());
|
||||
//#source-fanoutPublisher
|
||||
|
||||
assertStorageResult();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sinkAsSubscriber() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
|
||||
{
|
||||
//#sink-subscriber
|
||||
final Subscriber<Author> storage = rs.storage();
|
||||
|
||||
final Subscriber<Tweet> tweetSubscriber =
|
||||
authors
|
||||
.to(Sink.fromSubscriber(storage))
|
||||
.runWith(Source.asSubscriber(), mat);
|
||||
|
||||
rs.tweets().subscribe(tweetSubscriber);
|
||||
//#sink-subscriber
|
||||
|
||||
assertStorageResult();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void useProcessor() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
//#use-processor
|
||||
// An example Processor factory
|
||||
final Creator<Processor<Integer, Integer>> factory =
|
||||
new Creator<Processor<Integer, Integer>>() {
|
||||
public Processor<Integer, Integer> create() {
|
||||
return Flow.of(Integer.class).toProcessor().run(mat);
|
||||
}
|
||||
};
|
||||
|
||||
final Flow<Integer, Integer, BoxedUnit> flow = Flow.fromProcessor(factory);
|
||||
|
||||
//#use-processor
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void assertStorageResult() {
|
||||
storageProbe.expectMsg(new Author("rolandkuhn"));
|
||||
storageProbe.expectMsg(new Author("patriknw"));
|
||||
storageProbe.expectMsg(new Author("bantonsson"));
|
||||
storageProbe.expectMsg(new Author("drewhk"));
|
||||
storageProbe.expectMsg(new Author("ktosopl"));
|
||||
storageProbe.expectMsg(new Author("mmartynas"));
|
||||
storageProbe.expectMsg(new Author("akkateam"));
|
||||
storageProbe.expectMsg("complete");
|
||||
}
|
||||
|
||||
}
|
||||
64
akka-docs/rst/java/code/docs/stream/SilenceSystemOut.java
Normal file
64
akka-docs/rst/java/code/docs/stream/SilenceSystemOut.java
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package docs.stream;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Acts as if `System.out.println()` yet swallows all messages. Useful for putting printlines in examples yet without poluting the build with them.
|
||||
*/
|
||||
public class SilenceSystemOut {
|
||||
|
||||
private SilenceSystemOut() {
|
||||
}
|
||||
|
||||
public static System get() {
|
||||
return new System(new System.Println() {
|
||||
@Override
|
||||
public void println(String s) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static System get(ActorRef probe) {
|
||||
return new System(new System.Println() {
|
||||
@Override
|
||||
public void println(String s) {
|
||||
probe.tell(s, ActorRef.noSender());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static System get(Predicate<String> filter, ActorRef probe) {
|
||||
return new System(new System.Println() {
|
||||
@Override
|
||||
public void println(String s) {
|
||||
if (filter.test(s))
|
||||
probe.tell(s, ActorRef.noSender());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class System {
|
||||
public final Println out;
|
||||
|
||||
public System(Println out) {
|
||||
this.out = out;
|
||||
}
|
||||
|
||||
public static abstract class Println {
|
||||
public abstract void println(String s);
|
||||
|
||||
public void println(Object s) {
|
||||
println(s.toString());
|
||||
}
|
||||
|
||||
public void printf(String format, Object... args) {
|
||||
println(String.format(format, args));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* Copyright (C) 2014 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Cancellable;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
public class StreamBuffersRateDocTest {
|
||||
|
||||
static class Job {}
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("StreamBufferRateDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
final SilenceSystemOut.System System = SilenceSystemOut.get();
|
||||
|
||||
@Test
|
||||
public void demonstratePipelining() {
|
||||
//#pipelining
|
||||
Source.from(Arrays.asList(1, 2, 3))
|
||||
.map(i -> {System.out.println("A: " + i); return i;})
|
||||
.map(i -> {System.out.println("B: " + i); return i;})
|
||||
.map(i -> {System.out.println("C: " + i); return i;})
|
||||
.runWith(Sink.ignore(), mat);
|
||||
//#pipelining
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unused")
|
||||
public void demonstrateBufferSizes() {
|
||||
//#materializer-buffer
|
||||
final Materializer materializer = ActorMaterializer.create(
|
||||
ActorMaterializerSettings.create(system)
|
||||
.withInputBuffer(64, 64), system);
|
||||
//#materializer-buffer
|
||||
|
||||
//#section-buffer
|
||||
final Flow<Integer, Integer, BoxedUnit> flow1 =
|
||||
Flow.of(Integer.class)
|
||||
.map(elem -> elem * 2) // the buffer size of this map is 1
|
||||
.withAttributes(Attributes.inputBuffer(1, 1));
|
||||
final Flow<Integer, Integer, BoxedUnit> flow2 =
|
||||
flow1.via(
|
||||
Flow.of(Integer.class)
|
||||
.map(elem -> elem / 2)); // the buffer size of this map is the default
|
||||
//#section-buffer
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateBufferAbstractionLeak() {
|
||||
//#buffering-abstraction-leak
|
||||
final FiniteDuration oneSecond =
|
||||
FiniteDuration.create(1, TimeUnit.SECONDS);
|
||||
final Source<String, Cancellable> msgSource =
|
||||
Source.tick(oneSecond, oneSecond, "message!");
|
||||
final Source<String, Cancellable> tickSource =
|
||||
Source.tick(oneSecond.mul(3), oneSecond.mul(3), "tick");
|
||||
final Flow<String, Integer, BoxedUnit> conflate =
|
||||
Flow.of(String.class).conflate(
|
||||
first -> 1, (count, elem) -> count + 1);
|
||||
|
||||
RunnableGraph.fromGraph(GraphDSL.create(b -> {
|
||||
final FanInShape2<String, Integer, Integer> zipper =
|
||||
b.add(ZipWith.create((String tick, Integer count) -> count));
|
||||
b.from(b.add(msgSource)).via(b.add(conflate)).toInlet(zipper.in1());
|
||||
b.from(b.add(tickSource)).toInlet(zipper.in0());
|
||||
b.from(zipper.out()).to(b.add(Sink.foreach(elem -> System.out.println(elem))));
|
||||
return ClosedShape.getInstance();
|
||||
})).run(mat);
|
||||
//#buffering-abstraction-leak
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateExplicitBuffers() {
|
||||
final Source<Job, BoxedUnit> inboundJobsConnector = Source.empty();
|
||||
//#explicit-buffers-backpressure
|
||||
// Getting a stream of jobs from an imaginary external system as a Source
|
||||
final Source<Job, BoxedUnit> jobs = inboundJobsConnector;
|
||||
jobs.buffer(1000, OverflowStrategy.backpressure());
|
||||
//#explicit-buffers-backpressure
|
||||
|
||||
//#explicit-buffers-droptail
|
||||
jobs.buffer(1000, OverflowStrategy.dropTail());
|
||||
//#explicit-buffers-droptail
|
||||
|
||||
//#explicit-buffers-dropnew
|
||||
jobs.buffer(1000, OverflowStrategy.dropNew());
|
||||
//#explicit-buffers-dropnew
|
||||
|
||||
//#explicit-buffers-drophead
|
||||
jobs.buffer(1000, OverflowStrategy.dropHead());
|
||||
//#explicit-buffers-drophead
|
||||
|
||||
//#explicit-buffers-dropbuffer
|
||||
jobs.buffer(1000, OverflowStrategy.dropBuffer());
|
||||
//#explicit-buffers-dropbuffer
|
||||
|
||||
//#explicit-buffers-fail
|
||||
jobs.buffer(1000, OverflowStrategy.fail());
|
||||
//#explicit-buffers-fail
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
import akka.actor.*;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
public class StreamPartialFlowGraphDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("StreamPartialFlowGraphDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void demonstrateBuildWithOpenPorts() throws Exception {
|
||||
//#simple-partial-flow-graph
|
||||
final Graph<FanInShape2<Integer, Integer, Integer>, BoxedUnit> zip =
|
||||
ZipWith.create((Integer left, Integer right) -> Math.max(left, right));
|
||||
|
||||
final Graph<UniformFanInShape<Integer, Integer>, BoxedUnit> pickMaxOfThree =
|
||||
GraphDSL.create(builder -> {
|
||||
final FanInShape2<Integer, Integer, Integer> zip1 = builder.add(zip);
|
||||
final FanInShape2<Integer, Integer, Integer> zip2 = builder.add(zip);
|
||||
|
||||
builder.from(zip1.out()).toInlet(zip2.in0());
|
||||
// return the shape, which has three inputs and one output
|
||||
return new UniformFanInShape<Integer, Integer>(zip2.out(),
|
||||
new Inlet[] {zip1.in0(), zip1.in1(), zip2.in1()});
|
||||
});
|
||||
|
||||
final Sink<Integer, Future<Integer>> resultSink = Sink.<Integer>head();
|
||||
|
||||
final RunnableGraph<Future<Integer>> g =
|
||||
RunnableGraph.<Future<Integer>>fromGraph(
|
||||
GraphDSL.create(resultSink, (builder, sink) -> {
|
||||
// import the partial flow graph explicitly
|
||||
final UniformFanInShape<Integer, Integer> pm = builder.add(pickMaxOfThree);
|
||||
|
||||
builder.from(builder.add(Source.single(1))).toInlet(pm.in(0));
|
||||
builder.from(builder.add(Source.single(2))).toInlet(pm.in(1));
|
||||
builder.from(builder.add(Source.single(3))).toInlet(pm.in(2));
|
||||
builder.from(pm.out()).to(sink);
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
|
||||
final Future<Integer> max = g.run(mat);
|
||||
//#simple-partial-flow-graph
|
||||
assertEquals(Integer.valueOf(3), Await.result(max, Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
//#source-from-partial-flow-graph
|
||||
// first create an indefinite source of integer numbers
|
||||
class Ints implements Iterator<Integer> {
|
||||
private int next = 0;
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public Integer next() {
|
||||
return next++;
|
||||
}
|
||||
}
|
||||
//#source-from-partial-flow-graph
|
||||
|
||||
@Test
|
||||
public void demonstrateBuildSourceFromPartialFlowGraphCreate() throws Exception {
|
||||
//#source-from-partial-flow-graph
|
||||
final Source<Integer, BoxedUnit> ints = Source.fromIterator(() -> new Ints());
|
||||
|
||||
final Source<Pair<Integer, Integer>, BoxedUnit> pairs = Source.fromGraph(
|
||||
GraphDSL.create(
|
||||
builder -> {
|
||||
final FanInShape2<Integer, Integer, Pair<Integer, Integer>> zip =
|
||||
builder.add(Zip.create());
|
||||
|
||||
builder.from(builder.add(ints.filter(i -> i % 2 == 0))).toInlet(zip.in0());
|
||||
builder.from(builder.add(ints.filter(i -> i % 2 == 1))).toInlet(zip.in1());
|
||||
|
||||
return SourceShape.of(zip.out());
|
||||
}));
|
||||
|
||||
final Future<Pair<Integer, Integer>> firstPair =
|
||||
pairs.runWith(Sink.<Pair<Integer, Integer>>head(), mat);
|
||||
//#source-from-partial-flow-graph
|
||||
assertEquals(new Pair<>(0, 1), Await.result(firstPair, Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateBuildFlowFromPartialFlowGraphCreate() throws Exception {
|
||||
//#flow-from-partial-flow-graph
|
||||
final Flow<Integer, Pair<Integer, String>, BoxedUnit> pairs = Flow.fromGraph(GraphDSL.create(
|
||||
b -> {
|
||||
final UniformFanOutShape<Integer, Integer> bcast = b.add(Broadcast.create(2));
|
||||
final FanInShape2<Integer, String, Pair<Integer, String>> zip =
|
||||
b.add(Zip.create());
|
||||
|
||||
b.from(bcast).toInlet(zip.in0());
|
||||
b.from(bcast).via(b.add(Flow.of(Integer.class).map(i -> i.toString()))).toInlet(zip.in1());
|
||||
|
||||
return FlowShape.of(bcast.in(), zip.out());
|
||||
}));
|
||||
|
||||
//#flow-from-partial-flow-graph
|
||||
final Future<Pair<Integer, String>> matSink =
|
||||
//#flow-from-partial-flow-graph
|
||||
Source.single(1).via(pairs).runWith(Sink.<Pair<Integer, String>>head(), mat);
|
||||
//#flow-from-partial-flow-graph
|
||||
|
||||
assertEquals(new Pair<>(1, "1"), Await.result(matSink, Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void demonstrateBuildSourceWithCombine() throws Exception {
|
||||
//#source-combine
|
||||
Source<Integer, BoxedUnit> source1 = Source.single(1);
|
||||
Source<Integer, BoxedUnit> source2 = Source.single(2);
|
||||
|
||||
final Source<Integer, BoxedUnit> sources = Source.combine(source1, source2, new ArrayList<>(),
|
||||
i -> Merge.<Integer>create(i));
|
||||
//#source-combine
|
||||
final Future<Integer> result=
|
||||
//#source-combine
|
||||
sources.runWith(Sink.<Integer, Integer>fold(0, (a,b) -> a + b), mat);
|
||||
//#source-combine
|
||||
|
||||
assertEquals(Integer.valueOf(3), Await.result(result, Duration.create(3, TimeUnit.SECONDS)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateBuildSinkWithCombine() throws Exception {
|
||||
final JavaTestKit probe = new JavaTestKit(system);
|
||||
ActorRef actorRef = probe.getRef();
|
||||
|
||||
//#sink-combine
|
||||
Sink<Integer, BoxedUnit> sendRmotely = Sink.actorRef(actorRef, "Done");
|
||||
Sink<Integer, Future<BoxedUnit>> localProcessing = Sink.<Integer>foreach(a -> { /*do something useful*/ } );
|
||||
Sink<Integer, BoxedUnit> sinks = Sink.combine(sendRmotely,localProcessing, new ArrayList<>(), a -> Broadcast.create(a));
|
||||
|
||||
Source.<Integer>from(Arrays.asList(new Integer[]{0, 1, 2})).runWith(sinks, mat);
|
||||
//#sink-combine
|
||||
probe.expectMsgEquals(0);
|
||||
probe.expectMsgEquals(1);
|
||||
probe.expectMsgEquals(2);
|
||||
}
|
||||
}
|
||||
229
akka-docs/rst/java/code/docs/stream/StreamTestKitDocTest.java
Normal file
229
akka-docs/rst/java/code/docs/stream/StreamTestKitDocTest.java
Normal file
|
|
@ -0,0 +1,229 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.dispatch.Futures;
|
||||
import akka.testkit.*;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.testkit.*;
|
||||
import akka.stream.testkit.javadsl.*;
|
||||
import akka.testkit.TestProbe;
|
||||
import scala.util.*;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
public class StreamTestKitDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("StreamTestKitDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void strictCollection() throws Exception {
|
||||
//#strict-collection
|
||||
final Sink<Integer, Future<Integer>> sinkUnderTest = Flow.of(Integer.class)
|
||||
.map(i -> i * 2)
|
||||
.toMat(Sink.fold(0, (agg, next) -> agg + next), Keep.right());
|
||||
|
||||
final Future<Integer> future = Source.from(Arrays.asList(1, 2, 3, 4))
|
||||
.runWith(sinkUnderTest, mat);
|
||||
final Integer result = Await.result(future, Duration.create(1, TimeUnit.SECONDS));
|
||||
assert(result == 20);
|
||||
//#strict-collection
|
||||
}
|
||||
|
||||
@Test
|
||||
public void groupedPartOfInfiniteStream() throws Exception {
|
||||
//#grouped-infinite
|
||||
final Source<Integer, BoxedUnit> sourceUnderTest = Source.repeat(1)
|
||||
.map(i -> i * 2);
|
||||
|
||||
final Future<List<Integer>> future = sourceUnderTest
|
||||
.grouped(10)
|
||||
.runWith(Sink.head(), mat);
|
||||
final List<Integer> result =
|
||||
Await.result(future, Duration.create(1, TimeUnit.SECONDS));
|
||||
assertEquals(result, Collections.nCopies(10, 2));
|
||||
//#grouped-infinite
|
||||
}
|
||||
|
||||
@Test
|
||||
public void foldedStream() throws Exception {
|
||||
//#folded-stream
|
||||
final Flow<Integer, Integer, BoxedUnit> flowUnderTest = Flow.of(Integer.class)
|
||||
.takeWhile(i -> i < 5);
|
||||
|
||||
final Future<Integer> future = Source.from(Arrays.asList(1, 2, 3, 4, 5, 6))
|
||||
.via(flowUnderTest).runWith(Sink.fold(0, (agg, next) -> agg + next), mat);
|
||||
final Integer result = Await.result(future, Duration.create(1, TimeUnit.SECONDS));
|
||||
assert(result == 10);
|
||||
//#folded-stream
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pipeToTestProbe() throws Exception {
|
||||
//#pipeto-testprobe
|
||||
final Source<List<Integer>, BoxedUnit> sourceUnderTest = Source
|
||||
.from(Arrays.asList(1, 2, 3, 4))
|
||||
.grouped(2);
|
||||
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
final Future<List<List<Integer>>> future = sourceUnderTest
|
||||
.grouped(2)
|
||||
.runWith(Sink.head(), mat);
|
||||
akka.pattern.Patterns.pipe(future, system.dispatcher()).to(probe.ref());
|
||||
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS),
|
||||
Arrays.asList(Arrays.asList(1, 2), Arrays.asList(3, 4))
|
||||
);
|
||||
//#pipeto-testprobe
|
||||
}
|
||||
|
||||
public enum Tick { TOCK, COMPLETED };
|
||||
|
||||
@Test
|
||||
public void sinkActorRef() throws Exception {
|
||||
//#sink-actorref
|
||||
final Source<Tick, Cancellable> sourceUnderTest = Source.tick(
|
||||
FiniteDuration.create(0, TimeUnit.MILLISECONDS),
|
||||
FiniteDuration.create(200, TimeUnit.MILLISECONDS),
|
||||
Tick.TOCK);
|
||||
|
||||
final TestProbe probe = new TestProbe(system);
|
||||
final Cancellable cancellable = sourceUnderTest
|
||||
.to(Sink.actorRef(probe.ref(), Tick.COMPLETED)).run(mat);
|
||||
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS), Tick.TOCK);
|
||||
probe.expectNoMsg(Duration.create(100, TimeUnit.MILLISECONDS));
|
||||
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS), Tick.TOCK);
|
||||
cancellable.cancel();
|
||||
probe.expectMsg(Duration.create(1, TimeUnit.SECONDS), Tick.COMPLETED);
|
||||
//#sink-actorref
|
||||
}
|
||||
|
||||
@Test
|
||||
public void sourceActorRef() throws Exception {
|
||||
//#source-actorref
|
||||
final Sink<Integer, Future<String>> sinkUnderTest = Flow.of(Integer.class)
|
||||
.map(i -> i.toString())
|
||||
.toMat(Sink.fold("", (agg, next) -> agg + next), Keep.right());
|
||||
|
||||
final Pair<ActorRef, Future<String>> refAndFuture =
|
||||
Source.<Integer>actorRef(8, OverflowStrategy.fail())
|
||||
.toMat(sinkUnderTest, Keep.both())
|
||||
.run(mat);
|
||||
final ActorRef ref = refAndFuture.first();
|
||||
final Future<String> future = refAndFuture.second();
|
||||
|
||||
ref.tell(1, ActorRef.noSender());
|
||||
ref.tell(2, ActorRef.noSender());
|
||||
ref.tell(3, ActorRef.noSender());
|
||||
ref.tell(new akka.actor.Status.Success("done"), ActorRef.noSender());
|
||||
|
||||
final String result = Await.result(future, Duration.create(1, TimeUnit.SECONDS));
|
||||
assertEquals(result, "123");
|
||||
//#source-actorref
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSinkProbe() {
|
||||
//#test-sink-probe
|
||||
final Source<Integer, BoxedUnit> sourceUnderTest = Source.from(Arrays.asList(1, 2, 3, 4))
|
||||
.filter(elem -> elem % 2 == 0)
|
||||
.map(elem -> elem * 2);
|
||||
|
||||
sourceUnderTest
|
||||
.runWith(TestSink.probe(system), mat)
|
||||
.request(2)
|
||||
.expectNext(4, 8)
|
||||
.expectComplete();
|
||||
//#test-sink-probe
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSourceProbe() {
|
||||
//#test-source-probe
|
||||
final Sink<Integer, BoxedUnit> sinkUnderTest = Sink.cancelled();
|
||||
|
||||
TestSource.<Integer>probe(system)
|
||||
.toMat(sinkUnderTest, Keep.left())
|
||||
.run(mat)
|
||||
.expectCancellation();
|
||||
//#test-source-probe
|
||||
}
|
||||
|
||||
@Test
|
||||
public void injectingFailure() throws Exception {
|
||||
//#injecting-failure
|
||||
final Sink<Integer, Future<Integer>> sinkUnderTest = Sink.head();
|
||||
|
||||
final Pair<TestPublisher.Probe<Integer>, Future<Integer>> probeAndFuture =
|
||||
TestSource.<Integer>probe(system)
|
||||
.toMat(sinkUnderTest, Keep.both())
|
||||
.run(mat);
|
||||
final TestPublisher.Probe<Integer> probe = probeAndFuture.first();
|
||||
final Future<Integer> future = probeAndFuture.second();
|
||||
probe.sendError(new Exception("boom"));
|
||||
|
||||
Await.ready(future, Duration.create(1, TimeUnit.SECONDS));
|
||||
final Throwable exception = ((Failure)future.value().get()).exception();
|
||||
assertEquals(exception.getMessage(), "boom");
|
||||
//#injecting-failure
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSourceAndTestSink() throws Exception {
|
||||
//#test-source-and-sink
|
||||
final Flow<Integer, Integer, BoxedUnit> flowUnderTest = Flow.of(Integer.class)
|
||||
.mapAsyncUnordered(2, sleep -> akka.pattern.Patterns.after(
|
||||
Duration.create(10, TimeUnit.MILLISECONDS),
|
||||
system.scheduler(),
|
||||
system.dispatcher(),
|
||||
Futures.successful(sleep)
|
||||
));
|
||||
|
||||
final Pair<TestPublisher.Probe<Integer>, TestSubscriber.Probe<Integer>> pubAndSub =
|
||||
TestSource.<Integer>probe(system)
|
||||
.via(flowUnderTest)
|
||||
.toMat(TestSink.<Integer>probe(system), Keep.both())
|
||||
.run(mat);
|
||||
final TestPublisher.Probe<Integer> pub = pubAndSub.first();
|
||||
final TestSubscriber.Probe<Integer> sub = pubAndSub.second();
|
||||
|
||||
sub.request(3);
|
||||
pub.sendNext(3);
|
||||
pub.sendNext(2);
|
||||
pub.sendNext(1);
|
||||
sub.expectNextUnordered(1, 2, 3);
|
||||
|
||||
pub.sendError(new Exception("Power surge in the linear subroutine C-47!"));
|
||||
final Throwable ex = sub.expectError();
|
||||
assert(ex.getMessage().contains("C-47"));
|
||||
//#test-source-and-sink
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.dispatch.Foreach;
|
||||
import akka.japi.JavaPartialFunction;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import docs.stream.TwitterStreamQuickstartDocTest.Model.Author;
|
||||
import docs.stream.TwitterStreamQuickstartDocTest.Model.Hashtag;
|
||||
import docs.stream.TwitterStreamQuickstartDocTest.Model.Tweet;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static docs.stream.TwitterStreamQuickstartDocTest.Model.AKKA;
|
||||
import static docs.stream.TwitterStreamQuickstartDocTest.Model.tweets;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class TwitterStreamQuickstartDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("SampleActorTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
static abstract class Model {
|
||||
//#model
|
||||
public static class Author {
|
||||
public final String handle;
|
||||
|
||||
public Author(String handle) {
|
||||
this.handle = handle;
|
||||
}
|
||||
|
||||
// ...
|
||||
|
||||
//#model
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Author(" + handle + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Author author = (Author) o;
|
||||
|
||||
if (handle != null ? !handle.equals(author.handle) : author.handle != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return handle != null ? handle.hashCode() : 0;
|
||||
}
|
||||
//#model
|
||||
}
|
||||
//#model
|
||||
|
||||
//#model
|
||||
|
||||
public static class Hashtag {
|
||||
public final String name;
|
||||
|
||||
public Hashtag(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
// ...
|
||||
//#model
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Hashtag other = (Hashtag) obj;
|
||||
return name.equals(other.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Hashtag(" + name + ")";
|
||||
}
|
||||
//#model
|
||||
}
|
||||
//#model
|
||||
|
||||
//#model
|
||||
|
||||
public static class Tweet {
|
||||
public final Author author;
|
||||
public final long timestamp;
|
||||
public final String body;
|
||||
|
||||
public Tweet(Author author, long timestamp, String body) {
|
||||
this.author = author;
|
||||
this.timestamp = timestamp;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Set<Hashtag> hashtags() {
|
||||
return Arrays.asList(body.split(" ")).stream()
|
||||
.filter(a -> a.startsWith("#"))
|
||||
.map(a -> new Hashtag(a))
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
// ...
|
||||
//#model
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Tweet(" + author + "," + timestamp + "," + body + ")";
|
||||
}
|
||||
|
||||
//#model
|
||||
}
|
||||
//#model
|
||||
|
||||
//#model
|
||||
|
||||
public static final Hashtag AKKA = new Hashtag("#akka");
|
||||
//#model
|
||||
|
||||
public static final Source<Tweet, BoxedUnit> tweets = Source.from(
|
||||
Arrays.asList(new Tweet[] {
|
||||
new Tweet(new Author("rolandkuhn"), System.currentTimeMillis(), "#akka rocks!"),
|
||||
new Tweet(new Author("patriknw"), System.currentTimeMillis(), "#akka !"),
|
||||
new Tweet(new Author("bantonsson"), System.currentTimeMillis(), "#akka !"),
|
||||
new Tweet(new Author("drewhk"), System.currentTimeMillis(), "#akka !"),
|
||||
new Tweet(new Author("ktosopl"), System.currentTimeMillis(), "#akka on the rocks!"),
|
||||
new Tweet(new Author("mmartynas"), System.currentTimeMillis(), "wow #akka !"),
|
||||
new Tweet(new Author("akkateam"), System.currentTimeMillis(), "#akka rocks!"),
|
||||
new Tweet(new Author("bananaman"), System.currentTimeMillis(), "#bananas rock!"),
|
||||
new Tweet(new Author("appleman"), System.currentTimeMillis(), "#apples rock!"),
|
||||
new Tweet(new Author("drama"), System.currentTimeMillis(), "we compared #apples to #oranges!")
|
||||
}));
|
||||
}
|
||||
|
||||
static abstract class Example0 {
|
||||
//#tweet-source
|
||||
Source<Tweet, BoxedUnit> tweets;
|
||||
//#tweet-source
|
||||
}
|
||||
|
||||
static abstract class Example1 {
|
||||
//#first-sample
|
||||
//#materializer-setup
|
||||
final ActorSystem system = ActorSystem.create("reactive-tweets");
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
//#first-sample
|
||||
//#materializer-setup
|
||||
}
|
||||
|
||||
static class Example2 {
|
||||
public void run(final Materializer mat) throws TimeoutException, InterruptedException {
|
||||
//#backpressure-by-readline
|
||||
final Future<?> completion =
|
||||
Source.from(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))
|
||||
.map(i -> { System.out.println("map => " + i); return i; })
|
||||
.runForeach(i -> System.console().readLine("Element = %s continue reading? [press enter]\n", i), mat);
|
||||
|
||||
Await.ready(completion, FiniteDuration.create(1, TimeUnit.MINUTES));
|
||||
//#backpressure-by-readline
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void demonstrateFilterAndMap() {
|
||||
final SilenceSystemOut.System System = SilenceSystemOut.get();
|
||||
|
||||
//#first-sample
|
||||
|
||||
//#authors-filter-map
|
||||
final Source<Author, BoxedUnit> authors =
|
||||
tweets
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> t.author);
|
||||
//#first-sample
|
||||
//#authors-filter-map
|
||||
|
||||
new Object() {
|
||||
//#authors-collect
|
||||
JavaPartialFunction<Tweet, Author> collectFunction =
|
||||
new JavaPartialFunction<Tweet, Author>() {
|
||||
public Author apply(Tweet t, boolean isCheck) {
|
||||
if (t.hashtags().contains(AKKA)) {
|
||||
if (isCheck) return null; // to spare the expensive or side-effecting code
|
||||
return t.author;
|
||||
} else {
|
||||
throw noMatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Source<Author, BoxedUnit> authors =
|
||||
tweets.collect(collectFunction);
|
||||
//#authors-collect
|
||||
};
|
||||
|
||||
//#first-sample
|
||||
|
||||
//#authors-foreachsink-println
|
||||
authors.runWith(Sink.foreach(a -> System.out.println(a)), mat);
|
||||
//#first-sample
|
||||
//#authors-foreachsink-println
|
||||
|
||||
//#authors-foreach-println
|
||||
authors.runForeach(a -> System.out.println(a), mat);
|
||||
//#authors-foreach-println
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateMapConcat() {
|
||||
//#hashtags-mapConcat
|
||||
final Source<Hashtag, BoxedUnit> hashtags =
|
||||
tweets.mapConcat(t -> new ArrayList<Hashtag>(t.hashtags()));
|
||||
//#hashtags-mapConcat
|
||||
}
|
||||
|
||||
static abstract class HiddenDefinitions {
|
||||
//#flow-graph-broadcast
|
||||
Sink<Author, BoxedUnit> writeAuthors;
|
||||
Sink<Hashtag, BoxedUnit> writeHashtags;
|
||||
//#flow-graph-broadcast
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateBroadcast() {
|
||||
final Sink<Author, Future<BoxedUnit>> writeAuthors = Sink.ignore();
|
||||
final Sink<Hashtag, Future<BoxedUnit>> writeHashtags = Sink.ignore();
|
||||
|
||||
//#flow-graph-broadcast
|
||||
RunnableGraph.fromGraph(GraphDSL.create(b -> {
|
||||
final UniformFanOutShape<Tweet, Tweet> bcast = b.add(Broadcast.create(2));
|
||||
final FlowShape<Tweet, Author> toAuthor =
|
||||
b.add(Flow.of(Tweet.class).map(t -> t.author));
|
||||
final FlowShape<Tweet, Hashtag> toTags =
|
||||
b.add(Flow.of(Tweet.class).mapConcat(t -> new ArrayList<Hashtag>(t.hashtags())));
|
||||
final SinkShape<Author> authors = b.add(writeAuthors);
|
||||
final SinkShape<Hashtag> hashtags = b.add(writeHashtags);
|
||||
|
||||
b.from(b.add(tweets)).viaFanOut(bcast).via(toAuthor).to(authors);
|
||||
b.from(bcast).via(toTags).to(hashtags);
|
||||
return ClosedShape.getInstance();
|
||||
})).run(mat);
|
||||
//#flow-graph-broadcast
|
||||
}
|
||||
|
||||
long slowComputation(Tweet t) {
|
||||
try {
|
||||
// act as if performing some heavy computation
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {}
|
||||
return 42;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateSlowProcessing() {
|
||||
//#tweets-slow-consumption-dropHead
|
||||
tweets
|
||||
.buffer(10, OverflowStrategy.dropHead())
|
||||
.map(t -> slowComputation(t))
|
||||
.runWith(Sink.ignore(), mat);
|
||||
//#tweets-slow-consumption-dropHead
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateCountOnFiniteStream() {
|
||||
//#tweets-fold-count
|
||||
final Sink<Integer, Future<Integer>> sumSink =
|
||||
Sink.<Integer, Integer>fold(0, (acc, elem) -> acc + elem);
|
||||
|
||||
final RunnableGraph<Future<Integer>> counter =
|
||||
tweets.map(t -> 1).toMat(sumSink, Keep.right());
|
||||
|
||||
final Future<Integer> sum = counter.run(mat);
|
||||
|
||||
sum.foreach(new Foreach<Integer>() {
|
||||
public void each(Integer c) {
|
||||
System.out.println("Total tweets processed: " + c);
|
||||
}
|
||||
}, system.dispatcher());
|
||||
//#tweets-fold-count
|
||||
|
||||
new Object() {
|
||||
//#tweets-fold-count-oneline
|
||||
final Future<Integer> sum = tweets.map(t -> 1).runWith(sumSink, mat);
|
||||
//#tweets-fold-count-oneline
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateMaterializeMultipleTimes() {
|
||||
final Source<Tweet, BoxedUnit> tweetsInMinuteFromNow = tweets; // not really in second, just acting as if
|
||||
|
||||
//#tweets-runnable-flow-materialized-twice
|
||||
final Sink<Integer, Future<Integer>> sumSink =
|
||||
Sink.<Integer, Integer>fold(0, (acc, elem) -> acc + elem);
|
||||
final RunnableGraph<Future<Integer>> counterRunnableGraph =
|
||||
tweetsInMinuteFromNow
|
||||
.filter(t -> t.hashtags().contains(AKKA))
|
||||
.map(t -> 1)
|
||||
.toMat(sumSink, Keep.right());
|
||||
|
||||
// materialize the stream once in the morning
|
||||
final Future<Integer> morningTweetsCount = counterRunnableGraph.run(mat);
|
||||
// and once in the evening, reusing the blueprint
|
||||
final Future<Integer> eveningTweetsCount = counterRunnableGraph.run(mat);
|
||||
//#tweets-runnable-flow-materialized-twice
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream.io;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorAttributes;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.FileIO;
|
||||
import docs.stream.SilenceSystemOut;
|
||||
import docs.stream.cookbook.RecipeParseLines;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Future;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import akka.stream.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteString;
|
||||
|
||||
public class StreamFileDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("StreamFileDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
final SilenceSystemOut.System System = SilenceSystemOut.get();
|
||||
|
||||
{
|
||||
//#file-source
|
||||
final File file = new File("example.csv");
|
||||
//#file-source
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateMaterializingBytesWritten() throws IOException {
|
||||
final File file = File.createTempFile(getClass().getName(), ".tmp");
|
||||
|
||||
try {
|
||||
//#file-source
|
||||
Sink<ByteString, Future<BoxedUnit>> printlnSink =
|
||||
Sink.foreach(chunk -> System.out.println(chunk.utf8String()));
|
||||
|
||||
Future<Long> bytesWritten =
|
||||
FileIO.fromFile(file)
|
||||
.to(printlnSink)
|
||||
.run(mat);
|
||||
//#file-source
|
||||
} finally {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateSettingDispatchersInCode() throws IOException {
|
||||
final File file = File.createTempFile(getClass().getName(), ".tmp");
|
||||
|
||||
try {
|
||||
Sink<ByteString, Future<Long>> byteStringFutureSink =
|
||||
//#custom-dispatcher-code
|
||||
FileIO.toFile(file)
|
||||
.withAttributes(ActorAttributes.dispatcher("custom-blocking-io-dispatcher"));
|
||||
//#custom-dispatcher-code
|
||||
} finally {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
184
akka-docs/rst/java/code/docs/stream/io/StreamTcpDocTest.java
Normal file
184
akka-docs/rst/java/code/docs/stream/io/StreamTcpDocTest.java
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.stream.io;
|
||||
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
import akka.stream.io.Framing;
|
||||
import docs.stream.SilenceSystemOut;
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import docs.util.SocketUtils;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Future;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.javadsl.Tcp.*;
|
||||
import akka.stream.stage.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.testkit.TestProbe;
|
||||
import akka.util.ByteString;
|
||||
|
||||
public class StreamTcpDocTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("StreamTcpDocTest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
final SilenceSystemOut.System System = SilenceSystemOut.get();
|
||||
|
||||
private final ConcurrentLinkedQueue<String> input = new ConcurrentLinkedQueue<String>();
|
||||
{
|
||||
input.add("Hello world");
|
||||
input.add("What a lovely day");
|
||||
}
|
||||
|
||||
private String readLine(String prompt) {
|
||||
String s = input.poll();
|
||||
return (s == null ? "q": s);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateSimpleServerConnection() {
|
||||
{
|
||||
//#echo-server-simple-bind
|
||||
// IncomingConnection and ServerBinding imported from Tcp
|
||||
final Source<IncomingConnection, Future<ServerBinding>> connections =
|
||||
Tcp.get(system).bind("127.0.0.1", 8889);
|
||||
//#echo-server-simple-bind
|
||||
}
|
||||
{
|
||||
|
||||
final InetSocketAddress localhost = SocketUtils.temporaryServerAddress();
|
||||
final Source<IncomingConnection, Future<ServerBinding>> connections =
|
||||
Tcp.get(system).bind(localhost.getHostName(), localhost.getPort()); // TODO getHostString in Java7
|
||||
|
||||
//#echo-server-simple-handle
|
||||
connections.runForeach(connection -> {
|
||||
System.out.println("New connection from: " + connection.remoteAddress());
|
||||
|
||||
final Flow<ByteString, ByteString, BoxedUnit> echo = Flow.of(ByteString.class)
|
||||
.via(Framing.delimiter(ByteString.fromString("\n"), 256, false))
|
||||
.map(bytes -> bytes.utf8String())
|
||||
.map(s -> s + "!!!\n")
|
||||
.map(s -> ByteString.fromString(s));
|
||||
|
||||
connection.handleWith(echo, mat);
|
||||
}, mat);
|
||||
//#echo-server-simple-handle
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void actuallyWorkingClientServerApp() {
|
||||
|
||||
final InetSocketAddress localhost = SocketUtils.temporaryServerAddress();
|
||||
|
||||
final TestProbe serverProbe = new TestProbe(system);
|
||||
|
||||
final Source<IncomingConnection,Future<ServerBinding>> connections =
|
||||
Tcp.get(system).bind(localhost.getHostName(), localhost.getPort()); // TODO getHostString in Java7
|
||||
//#welcome-banner-chat-server
|
||||
connections.runForeach(connection -> {
|
||||
// server logic, parses incoming commands
|
||||
final PushStage<String, String> commandParser = new PushStage<String, String>() {
|
||||
@Override public SyncDirective onPush(String elem, Context<String> ctx) {
|
||||
if (elem.equals("BYE"))
|
||||
return ctx.finish();
|
||||
else
|
||||
return ctx.push(elem + "!");
|
||||
}
|
||||
};
|
||||
|
||||
final String welcomeMsg = "Welcome to: " + connection.localAddress() +
|
||||
" you are: " + connection.remoteAddress() + "!\n";
|
||||
|
||||
final Source<ByteString, BoxedUnit> welcome =
|
||||
Source.single(ByteString.fromString(welcomeMsg));
|
||||
final Flow<ByteString, ByteString, BoxedUnit> echoFlow =
|
||||
Flow.of(ByteString.class)
|
||||
.via(Framing.delimiter(ByteString.fromString("\n"), 256, false))
|
||||
.map(bytes -> bytes.utf8String())
|
||||
//#welcome-banner-chat-server
|
||||
.map(command -> {
|
||||
serverProbe.ref().tell(command, null);
|
||||
return command;
|
||||
})
|
||||
//#welcome-banner-chat-server
|
||||
.transform(() -> commandParser)
|
||||
.map(s -> s + "\n")
|
||||
.map(s -> ByteString.fromString(s));
|
||||
|
||||
final Flow<ByteString, ByteString, BoxedUnit> serverLogic =
|
||||
Flow.fromGraph(GraphDSL.create(builder -> {
|
||||
final UniformFanInShape<ByteString, ByteString> concat =
|
||||
builder.add(Concat.create());
|
||||
final FlowShape<ByteString, ByteString> echo = builder.add(echoFlow);
|
||||
|
||||
builder
|
||||
.from(builder.add(welcome)).toFanIn(concat)
|
||||
.from(echo).toFanIn(concat);
|
||||
|
||||
return FlowShape.of(echo.in(), concat.out());
|
||||
}));
|
||||
|
||||
connection.handleWith(serverLogic, mat);
|
||||
}, mat);
|
||||
|
||||
//#welcome-banner-chat-server
|
||||
|
||||
{
|
||||
//#repl-client
|
||||
final Flow<ByteString, ByteString, Future<OutgoingConnection>> connection =
|
||||
Tcp.get(system).outgoingConnection("127.0.0.1", 8889);
|
||||
//#repl-client
|
||||
}
|
||||
|
||||
{
|
||||
final Flow<ByteString, ByteString, Future<OutgoingConnection>> connection =
|
||||
Tcp.get(system).outgoingConnection(localhost.getHostName(), localhost.getPort()); // TODO getHostString in Java7
|
||||
//#repl-client
|
||||
|
||||
final PushStage<String, ByteString> replParser = new PushStage<String, ByteString>() {
|
||||
@Override public SyncDirective onPush(String elem, Context<ByteString> ctx) {
|
||||
if (elem.equals("q"))
|
||||
return ctx.pushAndFinish(ByteString.fromString("BYE\n"));
|
||||
else
|
||||
return ctx.push(ByteString.fromString(elem + "\n"));
|
||||
}
|
||||
};
|
||||
|
||||
final Flow<ByteString, ByteString, BoxedUnit> repl = Flow.of(ByteString.class)
|
||||
.via(Framing.delimiter(ByteString.fromString("\n"), 256, false))
|
||||
.map(bytes -> bytes.utf8String())
|
||||
.map(text -> {System.out.println("Server: " + text); return "next";})
|
||||
.map(elem -> readLine("> "))
|
||||
.transform(() -> replParser);
|
||||
|
||||
connection.join(repl).run(mat);
|
||||
//#repl-client
|
||||
}
|
||||
|
||||
serverProbe.expectMsg("Hello world");
|
||||
serverProbe.expectMsg("What a lovely day");
|
||||
serverProbe.expectMsg("BYE");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,207 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.stream.stage.Context;
|
||||
import akka.stream.stage.PushPullStage;
|
||||
import akka.stream.stage.PushStage;
|
||||
import akka.stream.stage.SyncDirective;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.Tuple2;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RecipeByteStrings extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeByteStrings");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
final Source<ByteString, BoxedUnit> rawBytes = Source.from(Arrays.asList(
|
||||
ByteString.fromArray(new byte[] { 1, 2 }),
|
||||
ByteString.fromArray(new byte[] { 3 }),
|
||||
ByteString.fromArray(new byte[] { 4, 5, 6 }),
|
||||
ByteString.fromArray(new byte[] { 7, 8, 9 })));
|
||||
|
||||
@Test
|
||||
public void chunker() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final int CHUNK_LIMIT = 2;
|
||||
|
||||
//#bytestring-chunker
|
||||
class Chunker extends PushPullStage<ByteString, ByteString> {
|
||||
private final int chunkSize;
|
||||
private ByteString buffer = ByteString.empty();
|
||||
|
||||
public Chunker(int chunkSize) {
|
||||
this.chunkSize = chunkSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncDirective onPush(ByteString elem, Context<ByteString> ctx) {
|
||||
buffer = buffer.concat(elem);
|
||||
return emitChunkOrPull(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncDirective onPull(Context<ByteString> ctx) {
|
||||
return emitChunkOrPull(ctx);
|
||||
}
|
||||
|
||||
public SyncDirective emitChunkOrPull(Context<ByteString> ctx) {
|
||||
if (buffer.isEmpty()) {
|
||||
return ctx.pull();
|
||||
} else {
|
||||
Tuple2<ByteString, ByteString> split = buffer.splitAt(chunkSize);
|
||||
ByteString emit = split._1();
|
||||
buffer = split._2();
|
||||
return ctx.push(emit);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#bytestring-chunker
|
||||
|
||||
{
|
||||
//#bytestring-chunker2
|
||||
Source<ByteString, BoxedUnit> chunksStream =
|
||||
rawBytes.transform(() -> new Chunker(CHUNK_LIMIT));
|
||||
//#bytestring-chunker2
|
||||
|
||||
Future<List<ByteString>> chunksFuture = chunksStream.grouped(10).runWith(Sink.head(), mat);
|
||||
|
||||
List<ByteString> chunks = Await.result(chunksFuture, FiniteDuration.create(3, TimeUnit.SECONDS));
|
||||
|
||||
for (ByteString chunk : chunks) {
|
||||
assertTrue(chunk.size() <= 2);
|
||||
}
|
||||
|
||||
ByteString sum = ByteString.empty();
|
||||
for (ByteString chunk : chunks) {
|
||||
sum = sum.concat(chunk);
|
||||
}
|
||||
assertEquals(sum, ByteString.fromArray(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }));
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void limiterShouldWork() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final int SIZE_LIMIT = 9;
|
||||
|
||||
//#bytes-limiter
|
||||
class ByteLimiter extends PushStage<ByteString, ByteString> {
|
||||
final long maximumBytes;
|
||||
private int count = 0;
|
||||
|
||||
public ByteLimiter(long maximumBytes) {
|
||||
this.maximumBytes = maximumBytes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncDirective onPush(ByteString chunk, Context<ByteString> ctx) {
|
||||
count += chunk.size();
|
||||
if (count > maximumBytes) {
|
||||
return ctx.fail(new IllegalStateException("Too much bytes"));
|
||||
} else {
|
||||
return ctx.push(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#bytes-limiter
|
||||
|
||||
{
|
||||
//#bytes-limiter2
|
||||
Flow<ByteString, ByteString, BoxedUnit> limiter =
|
||||
Flow.of(ByteString.class).transform(() -> new ByteLimiter(SIZE_LIMIT));
|
||||
//#bytes-limiter2
|
||||
|
||||
final Source<ByteString, BoxedUnit> bytes1 = Source.from(Arrays.asList(
|
||||
ByteString.fromArray(new byte[] { 1, 2 }),
|
||||
ByteString.fromArray(new byte[] { 3 }),
|
||||
ByteString.fromArray(new byte[] { 4, 5, 6 }),
|
||||
ByteString.fromArray(new byte[] { 7, 8, 9 })));
|
||||
|
||||
final Source<ByteString, BoxedUnit> bytes2 = Source.from(Arrays.asList(
|
||||
ByteString.fromArray(new byte[] { 1, 2 }),
|
||||
ByteString.fromArray(new byte[] { 3 }),
|
||||
ByteString.fromArray(new byte[] { 4, 5, 6 }),
|
||||
ByteString.fromArray(new byte[] { 7, 8, 9, 10 })));
|
||||
|
||||
FiniteDuration threeSeconds = FiniteDuration.create(3, TimeUnit.SECONDS);
|
||||
|
||||
List<ByteString> got = Await.result(bytes1.via(limiter).grouped(10).runWith(Sink.head(), mat), threeSeconds);
|
||||
ByteString acc = ByteString.empty();
|
||||
for (ByteString b : got) {
|
||||
acc = acc.concat(b);
|
||||
}
|
||||
assertEquals(acc, ByteString.fromArray(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }));
|
||||
|
||||
boolean thrown = false;
|
||||
try {
|
||||
Await.result(bytes2.via(limiter).grouped(10).runWith(Sink.head(), mat), threeSeconds);
|
||||
} catch (IllegalStateException ex) {
|
||||
thrown = true;
|
||||
}
|
||||
|
||||
assertTrue("Expected IllegalStateException to be thrown", thrown);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void compacting() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<ByteString, BoxedUnit> rawBytes = Source.from(Arrays.asList(
|
||||
ByteString.fromArray(new byte[] { 1, 2 }),
|
||||
ByteString.fromArray(new byte[] { 3 }),
|
||||
ByteString.fromArray(new byte[] { 4, 5, 6 }),
|
||||
ByteString.fromArray(new byte[] { 7, 8, 9 })));
|
||||
|
||||
//#compacting-bytestrings
|
||||
Source<ByteString, BoxedUnit> compacted = rawBytes.map(bs -> bs.compact());
|
||||
//#compacting-bytestrings
|
||||
|
||||
FiniteDuration timeout = FiniteDuration.create(3, TimeUnit.SECONDS);
|
||||
List<ByteString> got = Await.result(compacted.grouped(10).runWith(Sink.head(), mat), timeout);
|
||||
|
||||
for (ByteString byteString : got) {
|
||||
assertTrue(byteString.isCompact());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.stream.stage.Context;
|
||||
import akka.stream.stage.PushPullStage;
|
||||
import akka.stream.stage.SyncDirective;
|
||||
import akka.stream.stage.TerminationDirective;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class RecipeDigest extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeDigest");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void work() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
//#calculating-digest
|
||||
public PushPullStage<ByteString, ByteString> digestCalculator(String algorithm)
|
||||
throws NoSuchAlgorithmException {
|
||||
return new PushPullStage<ByteString, ByteString>() {
|
||||
final MessageDigest digest = MessageDigest.getInstance(algorithm);
|
||||
|
||||
@Override
|
||||
public SyncDirective onPush(ByteString chunk, Context<ByteString> ctx) {
|
||||
digest.update(chunk.toArray());
|
||||
return ctx.pull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncDirective onPull(Context<ByteString> ctx) {
|
||||
if (ctx.isFinishing()) {
|
||||
return ctx.pushAndFinish(ByteString.fromArray(digest.digest()));
|
||||
} else {
|
||||
return ctx.pull();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TerminationDirective onUpstreamFinish(Context<ByteString> ctx) {
|
||||
// If the stream is finished, we need to emit the last element in the onPull block.
|
||||
// It is not allowed to directly emit elements from a termination block
|
||||
// (onUpstreamFinish or onUpstreamFailure)
|
||||
return ctx.absorbTermination();
|
||||
}
|
||||
};
|
||||
}
|
||||
//#calculating-digest
|
||||
|
||||
{
|
||||
Source<ByteString, BoxedUnit> data = Source.from(Arrays.asList(
|
||||
ByteString.fromString("abcdbcdecdef"),
|
||||
ByteString.fromString("defgefghfghighijhijkijkljklmklmnlmnomnopnopq")));
|
||||
|
||||
//#calculating-digest2
|
||||
final Source<ByteString, BoxedUnit> digest = data
|
||||
.transform(() -> digestCalculator("SHA-256"));
|
||||
//#calculating-digest2
|
||||
|
||||
ByteString got = Await.result(digest.runWith(Sink.head(), mat), Duration.create(3, TimeUnit.SECONDS));
|
||||
assertEquals(ByteString.fromInts(
|
||||
0x24, 0x8d, 0x6a, 0x61,
|
||||
0xd2, 0x06, 0x38, 0xb8,
|
||||
0xe5, 0xc0, 0x26, 0x93,
|
||||
0x0c, 0x3e, 0x60, 0x39,
|
||||
0xa3, 0x3c, 0xe4, 0x59,
|
||||
0x64, 0xff, 0x21, 0x67,
|
||||
0xf6, 0xec, 0xed, 0xd4,
|
||||
0x19, 0xdb, 0x06, 0xc1), got);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Future;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class RecipeDroppyBroadcast extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeLoggingElements");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void work() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
//#droppy-bcast
|
||||
// Makes a sink drop elements if too slow
|
||||
public <T> Sink<T, Future<BoxedUnit>> droppySink(Sink<T, Future<BoxedUnit>> sink, int size) {
|
||||
return Flow.<T> create()
|
||||
.buffer(size, OverflowStrategy.dropHead())
|
||||
.toMat(sink, Keep.right());
|
||||
}
|
||||
//#droppy-bcast
|
||||
|
||||
{
|
||||
final List<Integer> nums = new ArrayList<>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
nums.add(i + 1);
|
||||
}
|
||||
|
||||
final Sink<Integer, Future<BoxedUnit>> mySink1 = Sink.ignore();
|
||||
final Sink<Integer, Future<BoxedUnit>> mySink2 = Sink.ignore();
|
||||
final Sink<Integer, Future<BoxedUnit>> mySink3 = Sink.ignore();
|
||||
|
||||
final Source<Integer, BoxedUnit> myData = Source.from(nums);
|
||||
|
||||
//#droppy-bcast2
|
||||
RunnableGraph.fromGraph(GraphDSL.create(builder -> {
|
||||
final int outputCount = 3;
|
||||
final UniformFanOutShape<Integer, Integer> bcast =
|
||||
builder.add(Broadcast.create(outputCount));
|
||||
builder.from(builder.add(myData)).toFanOut(bcast);
|
||||
builder.from(bcast).to(builder.add(droppySink(mySink1, 10)));
|
||||
builder.from(bcast).to(builder.add(droppySink(mySink2, 10)));
|
||||
builder.from(bcast).to(builder.add(droppySink(mySink3, 10)));
|
||||
return ClosedShape.getInstance();
|
||||
}));
|
||||
//#droppy-bcast2
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class RecipeFlattenList extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeFlattenList");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void workWithMapConcat() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
Source<List<Message>, BoxedUnit> someDataSource = Source
|
||||
.from(Arrays.asList(Arrays.asList(new Message("1")), Arrays.asList(new Message("2"), new Message("3"))));
|
||||
|
||||
//#flattening-lists
|
||||
Source<List<Message>, BoxedUnit> myData = someDataSource;
|
||||
Source<Message, BoxedUnit> flattened = myData.mapConcat(i -> i);
|
||||
//#flattening-lists
|
||||
|
||||
List<Message> got = Await.result(flattened.grouped(10).runWith(Sink.head(), mat),
|
||||
new FiniteDuration(1, TimeUnit.SECONDS));
|
||||
assertEquals(got.get(0), new Message("1"));
|
||||
assertEquals(got.get(1), new Message("2"));
|
||||
assertEquals(got.get(2), new Message("3"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,236 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.*;
|
||||
import akka.dispatch.Mapper;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.pattern.Patterns;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.stream.testkit.javadsl.TestSink;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.Timeout;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.PartialFunction;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
public class RecipeGlobalRateLimit extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeGlobalRateLimit");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
static
|
||||
//#global-limiter-actor
|
||||
public class Limiter extends AbstractActor {
|
||||
|
||||
public static class WantToPass {}
|
||||
public static final WantToPass WANT_TO_PASS = new WantToPass();
|
||||
|
||||
public static class MayPass {}
|
||||
public static final MayPass MAY_PASS = new MayPass();
|
||||
|
||||
public static class ReplenishTokens {}
|
||||
public static final ReplenishTokens REPLENISH_TOKENS = new ReplenishTokens();
|
||||
|
||||
private final int maxAvailableTokens;
|
||||
private final FiniteDuration tokenRefreshPeriod;
|
||||
private final int tokenRefreshAmount;
|
||||
|
||||
private final List<ActorRef> waitQueue = new ArrayList<>();
|
||||
private final Cancellable replenishTimer;
|
||||
|
||||
private int permitTokens;
|
||||
|
||||
public static Props props(int maxAvailableTokens, FiniteDuration tokenRefreshPeriod,
|
||||
int tokenRefreshAmount) {
|
||||
return Props.create(Limiter.class, maxAvailableTokens, tokenRefreshPeriod,
|
||||
tokenRefreshAmount);
|
||||
}
|
||||
|
||||
private Limiter(int maxAvailableTokens, FiniteDuration tokenRefreshPeriod,
|
||||
int tokenRefreshAmount) {
|
||||
this.maxAvailableTokens = maxAvailableTokens;
|
||||
this.tokenRefreshPeriod = tokenRefreshPeriod;
|
||||
this.tokenRefreshAmount = tokenRefreshAmount;
|
||||
this.permitTokens = maxAvailableTokens;
|
||||
|
||||
this.replenishTimer = system.scheduler().schedule(
|
||||
this.tokenRefreshPeriod,
|
||||
this.tokenRefreshPeriod,
|
||||
self(),
|
||||
REPLENISH_TOKENS,
|
||||
context().system().dispatcher(),
|
||||
self());
|
||||
|
||||
receive(open());
|
||||
}
|
||||
|
||||
PartialFunction<Object, BoxedUnit> open() {
|
||||
return ReceiveBuilder
|
||||
.match(ReplenishTokens.class, rt -> {
|
||||
permitTokens = Math.min(permitTokens + tokenRefreshAmount, maxAvailableTokens);
|
||||
})
|
||||
.match(WantToPass.class, wtp -> {
|
||||
permitTokens -= 1;
|
||||
sender().tell(MAY_PASS, self());
|
||||
if (permitTokens == 0) {
|
||||
context().become(closed());
|
||||
}
|
||||
}).build();
|
||||
}
|
||||
|
||||
PartialFunction<Object, BoxedUnit> closed() {
|
||||
return ReceiveBuilder
|
||||
.match(ReplenishTokens.class, rt -> {
|
||||
permitTokens = Math.min(permitTokens + tokenRefreshAmount, maxAvailableTokens);
|
||||
releaseWaiting();
|
||||
})
|
||||
.match(WantToPass.class, wtp -> {
|
||||
waitQueue.add(sender());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
private void releaseWaiting() {
|
||||
final List<ActorRef> toBeReleased = new ArrayList<>(permitTokens);
|
||||
for (int i = 0; i < permitTokens && i < waitQueue.size(); i++) {
|
||||
toBeReleased.add(waitQueue.remove(i));
|
||||
}
|
||||
|
||||
permitTokens -= toBeReleased.size();
|
||||
toBeReleased.stream().forEach(ref -> ref.tell(MAY_PASS, self()));
|
||||
if (permitTokens > 0) {
|
||||
context().become(open());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void postStop() {
|
||||
replenishTimer.cancel();
|
||||
waitQueue.stream().forEach(ref -> {
|
||||
ref.tell(new Status.Failure(new IllegalStateException("limiter stopped")), self());
|
||||
});
|
||||
}
|
||||
}
|
||||
//#global-limiter-actor
|
||||
|
||||
@Test
|
||||
public void work() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
//#global-limiter-flow
|
||||
public <T> Flow<T, T, BoxedUnit> limitGlobal(ActorRef limiter, FiniteDuration maxAllowedWait) {
|
||||
final int parallelism = 4;
|
||||
final Flow<T, T, BoxedUnit> f = Flow.create();
|
||||
|
||||
return f.mapAsync(parallelism, element -> {
|
||||
final Timeout triggerTimeout = new Timeout(maxAllowedWait);
|
||||
final Future<Object> limiterTriggerFuture =
|
||||
Patterns.ask(limiter, Limiter.WANT_TO_PASS, triggerTimeout);
|
||||
return limiterTriggerFuture.map(new Mapper<Object, T>() {
|
||||
@Override
|
||||
public T apply(Object parameter) {
|
||||
return element;
|
||||
}
|
||||
}, system.dispatcher());
|
||||
});
|
||||
}
|
||||
//#global-limiter-flow
|
||||
|
||||
{
|
||||
// Use a large period and emulate the timer by hand instead
|
||||
ActorRef limiter = system.actorOf(Limiter.props(2, new FiniteDuration(100, TimeUnit.DAYS), 1), "limiter");
|
||||
|
||||
final Iterator<String> e1 = new Iterator<String>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return "E1";
|
||||
}
|
||||
};
|
||||
final Iterator<String> e2 = new Iterator<String>() {
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String next() {
|
||||
return "E2";
|
||||
}
|
||||
};
|
||||
|
||||
final FiniteDuration twoSeconds = Duration.create(2, TimeUnit.SECONDS);
|
||||
|
||||
final Sink<String, TestSubscriber.Probe<String>> sink = TestSink.probe(system);
|
||||
final TestSubscriber.Probe<String> probe =
|
||||
RunnableGraph.<TestSubscriber.Probe<String>>fromGraph(
|
||||
GraphDSL.create(sink, (builder, s) -> {
|
||||
final int inputPorts = 2;
|
||||
final UniformFanInShape<String, String> merge = builder.add(Merge.create(inputPorts));
|
||||
|
||||
final SourceShape<String> source1 =
|
||||
builder.add(Source.<String>fromIterator(() -> e1).via(limitGlobal(limiter, twoSeconds)));
|
||||
final SourceShape<String> source2 =
|
||||
builder.add(Source.<String>fromIterator(() -> e2).via(limitGlobal(limiter, twoSeconds)));
|
||||
|
||||
builder.from(source1).toFanIn(merge);
|
||||
builder.from(source2).toFanIn(merge);
|
||||
builder.from(merge).to(s);
|
||||
return ClosedShape.getInstance();
|
||||
})
|
||||
).run(mat);
|
||||
|
||||
probe.expectSubscription().request(1000);
|
||||
|
||||
FiniteDuration fiveHundredMillis = FiniteDuration.create(500, TimeUnit.MILLISECONDS);
|
||||
|
||||
assertTrue(probe.expectNext().startsWith("E"));
|
||||
assertTrue(probe.expectNext().startsWith("E"));
|
||||
probe.expectNoMsg(fiveHundredMillis);
|
||||
|
||||
limiter.tell(Limiter.REPLENISH_TOKENS, getTestActor());
|
||||
assertTrue(probe.expectNext().startsWith("E"));
|
||||
probe.expectNoMsg(fiveHundredMillis);
|
||||
|
||||
final Set<String> resultSet = new HashSet<>();
|
||||
for (int i = 0; i < 100; i++) {
|
||||
limiter.tell(Limiter.REPLENISH_TOKENS, getTestActor());
|
||||
resultSet.add(probe.expectNext());
|
||||
}
|
||||
|
||||
assertTrue(resultSet.contains("E1"));
|
||||
assertTrue(resultSet.contains("E2"));
|
||||
|
||||
probe.expectError();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Keep;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.stream.stage.DetachedContext;
|
||||
import akka.stream.stage.DetachedStage;
|
||||
import akka.stream.stage.DownstreamDirective;
|
||||
import akka.stream.stage.UpstreamDirective;
|
||||
import akka.stream.testkit.TestPublisher;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.stream.testkit.javadsl.TestSink;
|
||||
import akka.stream.testkit.javadsl.TestSource;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecipeHold extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeMultiGroupBy");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
//#hold-version-1
|
||||
class HoldWithInitial<T> extends DetachedStage<T, T> {
|
||||
private T currentValue;
|
||||
|
||||
public HoldWithInitial(T initial) {
|
||||
currentValue = initial;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpstreamDirective onPush(T elem, DetachedContext<T> ctx) {
|
||||
currentValue = elem;
|
||||
return ctx.pull();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownstreamDirective onPull(DetachedContext<T> ctx) {
|
||||
return ctx.push(currentValue);
|
||||
}
|
||||
}
|
||||
//#hold-version-1
|
||||
|
||||
//#hold-version-2
|
||||
class HoldWithWait<T> extends DetachedStage<T, T> {
|
||||
private T currentValue = null;
|
||||
private boolean waitingFirstValue = true;
|
||||
|
||||
@Override
|
||||
public UpstreamDirective onPush(T elem, DetachedContext<T> ctx) {
|
||||
currentValue = elem;
|
||||
waitingFirstValue = false;
|
||||
if (ctx.isHoldingDownstream()) {
|
||||
return ctx.pushAndPull(currentValue);
|
||||
} else {
|
||||
return ctx.pull();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DownstreamDirective onPull(DetachedContext<T> ctx) {
|
||||
if (waitingFirstValue) {
|
||||
return ctx.holdDownstream();
|
||||
} else {
|
||||
return ctx.push(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
//#hold-version-2
|
||||
|
||||
@Test
|
||||
public void workForVersion1() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<Integer, TestPublisher.Probe<Integer>> source = TestSource.probe(system);
|
||||
final Sink<Integer, TestSubscriber.Probe<Integer>> sink = TestSink.probe(system);
|
||||
|
||||
Pair<TestPublisher.Probe<Integer>, TestSubscriber.Probe<Integer>> pubSub =
|
||||
source.transform(() -> new HoldWithInitial<>(0)).toMat(sink, Keep.both()).run(mat);
|
||||
TestPublisher.Probe<Integer> pub = pubSub.first();
|
||||
TestSubscriber.Probe<Integer> sub = pubSub.second();
|
||||
|
||||
sub.requestNext(0);
|
||||
sub.requestNext(0);
|
||||
|
||||
pub.sendNext(1);
|
||||
pub.sendNext(2);
|
||||
|
||||
sub.request(2);
|
||||
sub.expectNext(2, 2);
|
||||
|
||||
pub.sendComplete();
|
||||
sub.request(1);
|
||||
sub.expectComplete();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void workForVersion2() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<Integer, TestPublisher.Probe<Integer>> source = TestSource.probe(system);
|
||||
final Sink<Integer, TestSubscriber.Probe<Integer>> sink = TestSink.probe(system);
|
||||
|
||||
Pair<TestPublisher.Probe<Integer>, TestSubscriber.Probe<Integer>> pubSub =
|
||||
source.transform(() -> new HoldWithWait<>()).toMat(sink, Keep.both()).run(mat);
|
||||
TestPublisher.Probe<Integer> pub = pubSub.first();
|
||||
TestSubscriber.Probe<Integer> sub = pubSub.second();
|
||||
|
||||
FiniteDuration timeout = FiniteDuration.create(200, TimeUnit.MILLISECONDS);
|
||||
|
||||
sub.request(1);
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(1);
|
||||
sub.expectNext(1);
|
||||
|
||||
pub.sendNext(2);
|
||||
pub.sendNext(3);
|
||||
|
||||
sub.request(2);
|
||||
sub.expectNext(3, 3);
|
||||
|
||||
pub.sendComplete();
|
||||
sub.request(1);
|
||||
sub.expectComplete();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecipeKeepAlive extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeKeepAlive");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
class Tick {}
|
||||
public final Tick TICK = new Tick();
|
||||
|
||||
@Test
|
||||
public void workForVersion1() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final ByteString keepAliveMessage = ByteString.fromArray(new byte[]{11});
|
||||
|
||||
//@formatter:off
|
||||
//#inject-keepalive
|
||||
Flow<ByteString, ByteString, BoxedUnit> keepAliveInject =
|
||||
Flow.of(ByteString.class).keepAlive(
|
||||
scala.concurrent.duration.Duration.create(1, TimeUnit.SECONDS),
|
||||
() -> keepAliveMessage);
|
||||
//#inject-keepalive
|
||||
//@formatter:on
|
||||
|
||||
// Enough to compile, tested elsewhere as a built-in stage
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Attributes;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.testkit.DebugFilter;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import docs.stream.SilenceSystemOut;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.runtime.AbstractFunction0;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class RecipeLoggingElements extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeLoggingElements", ConfigFactory.parseString("akka.loglevel=DEBUG\nakka.loggers = [akka.testkit.TestEventListener]"));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void workWithPrintln() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final SilenceSystemOut.System System = SilenceSystemOut.get(getTestActor());
|
||||
|
||||
{
|
||||
final Source<String, BoxedUnit> mySource = Source.from(Arrays.asList("1", "2", "3"));
|
||||
|
||||
//#println-debug
|
||||
mySource.map(elem -> {
|
||||
System.out.println(elem);
|
||||
return elem;
|
||||
});
|
||||
//#println-debug
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void workWithLog() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
private <T> T analyse(T i) {
|
||||
return i;
|
||||
}
|
||||
|
||||
{
|
||||
final Source<String, BoxedUnit> mySource = Source.from(Arrays.asList("1", "2", "3"));
|
||||
|
||||
final int onElement = Logging.WarningLevel();
|
||||
final int onFinish = Logging.ErrorLevel();
|
||||
final int onFailure = Logging.ErrorLevel();
|
||||
|
||||
//#log-custom
|
||||
// customise log levels
|
||||
mySource.log("before-map")
|
||||
.withAttributes(Attributes.createLogLevels(onElement, onFinish, onFailure))
|
||||
.map(i -> analyse(i));
|
||||
|
||||
// or provide custom logging adapter
|
||||
final LoggingAdapter adapter = Logging.getLogger(system, "customLogger");
|
||||
mySource.log("custom", adapter);
|
||||
//#log-custom
|
||||
|
||||
new DebugFilter("customLogger", "[custom] Element: ", false, false, 3).intercept(new AbstractFunction0 () {
|
||||
public Void apply() {
|
||||
mySource.log("custom", adapter).runWith(Sink.ignore(), mat);
|
||||
return null;
|
||||
}
|
||||
}, system);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.stream.testkit.TestPublisher;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.stream.testkit.javadsl.TestSink;
|
||||
import akka.stream.testkit.javadsl.TestSource;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecipeManualTrigger extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeKeepAlive");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
class Trigger {
|
||||
}
|
||||
|
||||
public final Trigger TRIGGER = new Trigger();
|
||||
|
||||
@Test
|
||||
public void zipped() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<Trigger, TestPublisher.Probe<Trigger>> triggerSource = TestSource.probe(system);
|
||||
final Sink<Message, TestSubscriber.Probe<Message>> messageSink = TestSink.probe(system);
|
||||
|
||||
//#manually-triggered-stream
|
||||
final RunnableGraph<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>> g =
|
||||
RunnableGraph.<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>>fromGraph(
|
||||
GraphDSL.create(
|
||||
triggerSource,
|
||||
messageSink,
|
||||
(p, s) -> new Pair<>(p, s),
|
||||
(builder, source, sink) -> {
|
||||
SourceShape<Message> elements =
|
||||
builder.add(Source.from(Arrays.asList("1", "2", "3", "4")).map(t -> new Message(t)));
|
||||
FlowShape<Pair<Message, Trigger>, Message> takeMessage =
|
||||
builder.add(Flow.<Pair<Message, Trigger>>create().map(p -> p.first()));
|
||||
final FanInShape2<Message, Trigger, Pair<Message, Trigger>> zip =
|
||||
builder.add(Zip.create());
|
||||
builder.from(elements).toInlet(zip.in0());
|
||||
builder.from(source).toInlet(zip.in1());
|
||||
builder.from(zip.out()).via(takeMessage).to(sink);
|
||||
return ClosedShape.getInstance();
|
||||
}
|
||||
)
|
||||
);
|
||||
//#manually-triggered-stream
|
||||
|
||||
Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>> pubSub = g.run(mat);
|
||||
TestPublisher.Probe<Trigger> pub = pubSub.first();
|
||||
TestSubscriber.Probe<Message> sub = pubSub.second();
|
||||
|
||||
FiniteDuration timeout = FiniteDuration.create(100, TimeUnit.MILLISECONDS);
|
||||
sub.expectSubscription().request(1000);
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(TRIGGER);
|
||||
sub.expectNext(new Message("1"));
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(TRIGGER);
|
||||
pub.sendNext(TRIGGER);
|
||||
sub.expectNext(new Message("2"));
|
||||
sub.expectNext(new Message("3"));
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(TRIGGER);
|
||||
sub.expectNext(new Message("4"));
|
||||
sub.expectComplete();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void zipWith() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<Trigger, TestPublisher.Probe<Trigger>> triggerSource = TestSource.probe(system);
|
||||
final Sink<Message, TestSubscriber.Probe<Message>> messageSink = TestSink.probe(system);
|
||||
|
||||
//#manually-triggered-stream-zipwith
|
||||
final RunnableGraph<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>> g =
|
||||
RunnableGraph.<Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>>>fromGraph(
|
||||
GraphDSL.create(
|
||||
triggerSource,
|
||||
messageSink,
|
||||
(p, s) -> new Pair<>(p, s),
|
||||
(builder, source, sink) -> {
|
||||
final SourceShape<Message> elements =
|
||||
builder.add(Source.from(Arrays.asList("1", "2", "3", "4")).map(t -> new Message(t)));
|
||||
final FanInShape2<Message, Trigger, Message> zipWith =
|
||||
builder.add(ZipWith.create((msg, trigger) -> msg));
|
||||
builder.from(elements).toInlet(zipWith.in0());
|
||||
builder.from(source).toInlet(zipWith.in1());
|
||||
builder.from(zipWith.out()).to(sink);
|
||||
return ClosedShape.getInstance();
|
||||
}
|
||||
)
|
||||
);
|
||||
//#manually-triggered-stream-zipwith
|
||||
|
||||
Pair<TestPublisher.Probe<Trigger>, TestSubscriber.Probe<Message>> pubSub = g.run(mat);
|
||||
TestPublisher.Probe<Trigger> pub = pubSub.first();
|
||||
TestSubscriber.Probe<Message> sub = pubSub.second();
|
||||
|
||||
FiniteDuration timeout = FiniteDuration.create(100, TimeUnit.MILLISECONDS);
|
||||
sub.expectSubscription().request(1000);
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(TRIGGER);
|
||||
sub.expectNext(new Message("1"));
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(TRIGGER);
|
||||
pub.sendNext(TRIGGER);
|
||||
sub.expectNext(new Message("2"));
|
||||
sub.expectNext(new Message("3"));
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(TRIGGER);
|
||||
sub.expectNext(new Message("4"));
|
||||
sub.expectComplete();
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Keep;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.stream.testkit.TestPublisher;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.stream.testkit.javadsl.TestSink;
|
||||
import akka.stream.testkit.javadsl.TestSource;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.testkit.TestLatch;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecipeMissedTicks extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeMultiGroupBy");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void work() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
class Tick {
|
||||
}
|
||||
|
||||
final Tick Tick = new Tick();
|
||||
|
||||
{
|
||||
final Source<Tick, TestPublisher.Probe<Tick>> tickStream = TestSource.probe(system);
|
||||
final Sink<Integer, TestSubscriber.Probe<Integer>> sink = TestSink.probe(system);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
//#missed-ticks
|
||||
final Flow<Tick, Integer, BoxedUnit> missedTicks =
|
||||
Flow.of(Tick.class).conflate(tick -> 0, (missed, tick) -> missed + 1);
|
||||
//#missed-ticks
|
||||
final TestLatch latch = new TestLatch(3, system);
|
||||
final Flow<Tick, Integer, BoxedUnit> realMissedTicks =
|
||||
Flow.of(Tick.class).conflate(tick -> 0, (missed, tick) -> { latch.countDown(); return missed + 1; });
|
||||
|
||||
Pair<TestPublisher.Probe<Tick>, TestSubscriber.Probe<Integer>> pubSub =
|
||||
tickStream.via(realMissedTicks).toMat(sink, Keep.both()).run(mat);
|
||||
TestPublisher.Probe<Tick> pub = pubSub.first();
|
||||
TestSubscriber.Probe<Integer> sub = pubSub.second();
|
||||
|
||||
pub.sendNext(Tick);
|
||||
pub.sendNext(Tick);
|
||||
pub.sendNext(Tick);
|
||||
pub.sendNext(Tick);
|
||||
|
||||
FiniteDuration timeout = FiniteDuration.create(200, TimeUnit.MILLISECONDS);
|
||||
|
||||
Await.ready(latch, Duration.create(1, TimeUnit.SECONDS));
|
||||
|
||||
sub.request(1);
|
||||
sub.expectNext(3);
|
||||
sub.request(1);
|
||||
sub.expectNoMsg(timeout);
|
||||
|
||||
pub.sendNext(Tick);
|
||||
sub.expectNext(0);
|
||||
|
||||
pub.sendComplete();
|
||||
sub.request(1);
|
||||
sub.expectComplete();
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Function;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.stream.javadsl.SubSource;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
|
||||
public class RecipeMultiGroupByTest extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeMultiGroupBy");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
static class Topic {
|
||||
private final String name;
|
||||
|
||||
public Topic(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Topic topic = (Topic) o;
|
||||
|
||||
if (name != null ? !name.equals(topic.name) : topic.name != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name != null ? name.hashCode() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void work() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
final List<Topic> extractTopics(Message m) {
|
||||
final List<Topic> topics = new ArrayList<>(2);
|
||||
|
||||
if (m.msg.startsWith("1")) {
|
||||
topics.add(new Topic("1"));
|
||||
} else {
|
||||
topics.add(new Topic("1"));
|
||||
topics.add(new Topic("2"));
|
||||
}
|
||||
|
||||
return topics;
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
final Source<Message, BoxedUnit> elems = Source
|
||||
.from(Arrays.asList("1: a", "1: b", "all: c", "all: d", "1: e"))
|
||||
.map(s -> new Message(s));
|
||||
|
||||
//#multi-groupby
|
||||
final Function<Message, List<Topic>> topicMapper = m -> extractTopics(m);
|
||||
|
||||
final Source<Pair<Message, Topic>, BoxedUnit> messageAndTopic = elems
|
||||
.mapConcat((Message msg) -> {
|
||||
List<Topic> topicsForMessage = topicMapper.apply(msg);
|
||||
// Create a (Msg, Topic) pair for each of the topics
|
||||
|
||||
// the message belongs to
|
||||
return topicsForMessage
|
||||
.stream()
|
||||
.map(topic -> new Pair<Message, Topic>(msg, topic))
|
||||
.collect(toList());
|
||||
});
|
||||
|
||||
SubSource<Pair<Message, Topic>, BoxedUnit> multiGroups = messageAndTopic
|
||||
.groupBy(2, pair -> pair.second())
|
||||
.map(pair -> {
|
||||
Message message = pair.first();
|
||||
Topic topic = pair.second();
|
||||
|
||||
// do what needs to be done
|
||||
//#multi-groupby
|
||||
return pair;
|
||||
//#multi-groupby
|
||||
});
|
||||
//#multi-groupby
|
||||
|
||||
Future<List<String>> result = multiGroups
|
||||
.grouped(10)
|
||||
.mergeSubstreams()
|
||||
.map(pair -> {
|
||||
Topic topic = pair.get(0).second();
|
||||
return topic.name + mkString(pair.stream().map(p -> p.first().msg).collect(toList()), "[", ", ", "]");
|
||||
})
|
||||
.grouped(10)
|
||||
.runWith(Sink.head(), mat);
|
||||
|
||||
List<String> got = Await.result(result, FiniteDuration.create(3, TimeUnit.SECONDS));
|
||||
assertTrue(got.contains("1[1: a, 1: b, all: c, all: d, 1: e]"));
|
||||
assertTrue(got.contains("2[all: c, all: d]"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static final String mkString(List<String> l, String start, String separate, String end) {
|
||||
StringBuilder sb = new StringBuilder(start);
|
||||
for (String s : l) {
|
||||
sb.append(s).append(separate);
|
||||
}
|
||||
return sb
|
||||
.delete(sb.length() - separate.length(), sb.length())
|
||||
.append(end).toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.io.Framing;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecipeParseLines extends RecipeTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeLoggingElements");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void parseLines() throws Exception {
|
||||
final Source<ByteString, BoxedUnit> rawData = Source.from(Arrays.asList(
|
||||
ByteString.fromString("Hello World"),
|
||||
ByteString.fromString("\r"),
|
||||
ByteString.fromString("!\r"),
|
||||
ByteString.fromString("\nHello Akka!\r\nHello Streams!"),
|
||||
ByteString.fromString("\r\n\r\n")));
|
||||
|
||||
//#parse-lines
|
||||
final Source<String, BoxedUnit> lines = rawData
|
||||
.via(Framing.delimiter(ByteString.fromString("\r\n"), 100, true))
|
||||
.map(b -> b.utf8String());
|
||||
//#parse-lines
|
||||
|
||||
Await.result(lines.grouped(10).runWith(Sink.head(), mat), new FiniteDuration(1, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.japi.function.Function;
|
||||
import akka.japi.function.Function2;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class RecipeReduceByKeyTest extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeLoggingElements");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void work() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<String, BoxedUnit> words = Source.from(Arrays.asList("hello", "world", "and", "hello", "akka"));
|
||||
|
||||
//#word-count
|
||||
final int MAXIMUM_DISTINCT_WORDS = 1000;
|
||||
|
||||
final Source<Pair<String, Integer>, BoxedUnit> counts = words
|
||||
// split the words into separate streams first
|
||||
.groupBy(MAXIMUM_DISTINCT_WORDS, i -> i)
|
||||
// add counting logic to the streams
|
||||
.fold(new Pair<>("", 0), (pair, elem) -> new Pair<>(elem, pair.second() + 1))
|
||||
// get a stream of word counts
|
||||
.mergeSubstreams();
|
||||
//#word-count
|
||||
|
||||
final Future<List<Pair<String, Integer>>> f = counts.grouped(10).runWith(Sink.head(), mat);
|
||||
final Set<Pair<String, Integer>> result = Await.result(f, getRemainingTime()).stream().collect(Collectors.toSet());
|
||||
final Set<Pair<String, Integer>> expected = new HashSet<>();
|
||||
expected.add(new Pair<>("hello", 2));
|
||||
expected.add(new Pair<>("world", 1));
|
||||
expected.add(new Pair<>("and", 1));
|
||||
expected.add(new Pair<>("akka", 1));
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//#reduce-by-key-general
|
||||
static public <In, K, Out> Flow<In, Pair<K, Out>, BoxedUnit> reduceByKey(
|
||||
int maximumGroupSize,
|
||||
Function<In, K> groupKey,
|
||||
Function<K, Out> foldZero,
|
||||
Function2<Out, In, Out> fold,
|
||||
Materializer mat) {
|
||||
|
||||
return Flow.<In> create()
|
||||
.groupBy(maximumGroupSize, i -> i)
|
||||
.fold((Pair<K, Out>) null, (pair, elem) -> {
|
||||
final K key = groupKey.apply(elem);
|
||||
if (pair == null) return new Pair<>(key, fold.apply(foldZero.apply(key), elem));
|
||||
else return new Pair<>(key, fold.apply(pair.second(), elem));
|
||||
})
|
||||
.mergeSubstreams();
|
||||
}
|
||||
//#reduce-by-key-general
|
||||
|
||||
@Test
|
||||
public void workGeneralised() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<String, BoxedUnit> words = Source.from(Arrays.asList("hello", "world", "and", "hello", "akka"));
|
||||
|
||||
//#reduce-by-key-general2
|
||||
final int MAXIMUM_DISTINCT_WORDS = 1000;
|
||||
|
||||
Source<Pair<String, Integer>, BoxedUnit> counts = words.via(reduceByKey(
|
||||
MAXIMUM_DISTINCT_WORDS,
|
||||
word -> word,
|
||||
key -> 0,
|
||||
(count, elem) -> count + 1,
|
||||
mat));
|
||||
|
||||
//#reduce-by-key-general2
|
||||
final Future<List<Pair<String, Integer>>> f = counts.grouped(10).runWith(Sink.head(), mat);
|
||||
final Set<Pair<String, Integer>> result = Await.result(f, getRemainingTime()).stream().collect(Collectors.toSet());
|
||||
final Set<Pair<String, Integer>> expected = new HashSet<>();
|
||||
expected.add(new Pair<>("hello", 2));
|
||||
expected.add(new Pair<>("world", 1));
|
||||
expected.add(new Pair<>("and", 1));
|
||||
expected.add(new Pair<>("akka", 1));
|
||||
Assert.assertEquals(expected, result);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.testkit.TestPublisher;
|
||||
import akka.stream.testkit.TestSubscriber;
|
||||
import akka.stream.testkit.javadsl.TestSink;
|
||||
import akka.stream.testkit.javadsl.TestSource;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import akka.testkit.TestLatch;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecipeSimpleDrop extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeSimpleDrop");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void work() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
@SuppressWarnings("unused")
|
||||
//#simple-drop
|
||||
final Flow<Message, Message, BoxedUnit> droppyStream =
|
||||
Flow.of(Message.class).conflate(i -> i, (lastMessage, newMessage) -> newMessage);
|
||||
//#simple-drop
|
||||
final TestLatch latch = new TestLatch(2, system);
|
||||
final Flow<Message, Message, BoxedUnit> realDroppyStream =
|
||||
Flow.of(Message.class).conflate(i -> i, (lastMessage, newMessage) -> { latch.countDown(); return newMessage; });
|
||||
|
||||
final Pair<TestPublisher.Probe<Message>, TestSubscriber.Probe<Message>> pubSub = TestSource
|
||||
.<Message> probe(system)
|
||||
.via(realDroppyStream)
|
||||
.toMat(TestSink.probe(system),
|
||||
(pub, sub) -> new Pair<>(pub, sub))
|
||||
.run(mat);
|
||||
final TestPublisher.Probe<Message> pub = pubSub.first();
|
||||
final TestSubscriber.Probe<Message> sub = pubSub.second();
|
||||
|
||||
pub.sendNext(new Message("1"));
|
||||
pub.sendNext(new Message("2"));
|
||||
pub.sendNext(new Message("3"));
|
||||
|
||||
Await.ready(latch, Duration.create(1, TimeUnit.SECONDS));
|
||||
|
||||
sub.requestNext(new Message("3"));
|
||||
|
||||
pub.sendComplete();
|
||||
sub.request(1);
|
||||
sub.expectComplete();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
public class RecipeTest {
|
||||
final class Message {
|
||||
public final String msg;
|
||||
|
||||
public Message(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Message message = (Message) o;
|
||||
|
||||
if (msg != null ? !msg.equals(message.msg) : message.msg != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return msg != null ? msg.hashCode() : 0;
|
||||
}
|
||||
}
|
||||
|
||||
final class Trigger {
|
||||
}
|
||||
|
||||
final class Job {
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RecipeToStrict extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeLoggingElements");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
@Test
|
||||
public void workWithPrintln() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
final Source<String, BoxedUnit> myData = Source.from(Arrays.asList("1", "2", "3"));
|
||||
final int MAX_ALLOWED_SIZE = 100;
|
||||
|
||||
//#draining-to-list
|
||||
final Future<List<String>> strings = myData
|
||||
.grouped(MAX_ALLOWED_SIZE).runWith(Sink.head(), mat);
|
||||
//#draining-to-list
|
||||
|
||||
Await.result(strings, new FiniteDuration(1, TimeUnit.SECONDS));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
package docs.stream.javadsl.cookbook;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.stream.*;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.Future;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RecipeWorkerPool extends RecipeTest {
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("RecipeWorkerPool");
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
final Materializer mat = ActorMaterializer.create(system);
|
||||
|
||||
//#worker-pool
|
||||
public static <In, Out> Flow<In, Out, BoxedUnit> balancer(
|
||||
Flow<In, Out, BoxedUnit> worker, int workerCount) {
|
||||
return Flow.fromGraph(GraphDSL.create(b -> {
|
||||
boolean waitForAllDownstreams = true;
|
||||
final UniformFanOutShape<In, In> balance =
|
||||
b.add(Balance.<In>create(workerCount, waitForAllDownstreams));
|
||||
final UniformFanInShape<Out, Out> merge =
|
||||
b.add(Merge.<Out>create(workerCount));
|
||||
|
||||
for (int i = 0; i < workerCount; i++) {
|
||||
b.from(balance.out(i)).via(b.add(worker)).toInlet(merge.in(i));
|
||||
}
|
||||
|
||||
return FlowShape.of(balance.in(), merge.out());
|
||||
}));
|
||||
}
|
||||
//#worker-pool
|
||||
|
||||
@Test
|
||||
public void workForVersion1() throws Exception {
|
||||
new JavaTestKit(system) {
|
||||
{
|
||||
Source<Message, BoxedUnit> data =
|
||||
Source
|
||||
.from(Arrays.asList("1", "2", "3", "4", "5"))
|
||||
.map(t -> new Message(t));
|
||||
|
||||
Flow<Message, Message, BoxedUnit> worker = Flow.of(Message.class).map(m -> new Message(m.msg + " done"));
|
||||
|
||||
//#worker-pool2
|
||||
Flow<Message, Message, BoxedUnit> balancer = balancer(worker, 3);
|
||||
Source<Message, BoxedUnit> processedJobs = data.via(balancer);
|
||||
//#worker-pool2
|
||||
|
||||
FiniteDuration timeout = FiniteDuration.create(200, TimeUnit.MILLISECONDS);
|
||||
Future<List<String>> future = processedJobs.map(m -> m.msg).grouped(10).runWith(Sink.head(), mat);
|
||||
List<String> got = Await.result(future, timeout);
|
||||
assertTrue(got.contains("1 done"));
|
||||
assertTrue(got.contains("2 done"));
|
||||
assertTrue(got.contains("3 done"));
|
||||
assertTrue(got.contains("4 done"));
|
||||
assertTrue(got.contains("5 done"));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
31
akka-docs/rst/java/code/docs/util/SocketUtils.java
Normal file
31
akka-docs/rst/java/code/docs/util/SocketUtils.java
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.util;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.nio.channels.ServerSocketChannel;
|
||||
|
||||
public class SocketUtils {
|
||||
|
||||
public static InetSocketAddress temporaryServerAddress(String hostname) {
|
||||
try {
|
||||
ServerSocket socket = ServerSocketChannel.open().socket();
|
||||
socket.bind(new InetSocketAddress(hostname, 0));
|
||||
InetSocketAddress address = new InetSocketAddress(hostname, socket.getLocalPort());
|
||||
socket.close();
|
||||
return address;
|
||||
}
|
||||
catch (IOException io) {
|
||||
throw new RuntimeException(io);
|
||||
}
|
||||
}
|
||||
|
||||
public static InetSocketAddress temporaryServerAddress() {
|
||||
return temporaryServerAddress("127.0.0.1");
|
||||
}
|
||||
|
||||
}
|
||||
80
akka-docs/rst/java/http/client-side/connection-level.rst
Normal file
80
akka-docs/rst/java/http/client-side/connection-level.rst
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
.. _connection-level-api-java:
|
||||
|
||||
Connection-Level Client-Side API
|
||||
================================
|
||||
|
||||
The connection-level API is the lowest-level client-side API Akka HTTP provides. It gives you full control over when
|
||||
HTTP connections are opened and closed and how requests are to be send across which connection. As such it offers the
|
||||
highest flexibility at the cost of providing the least convenience.
|
||||
|
||||
|
||||
Opening HTTP Connections
|
||||
------------------------
|
||||
With the connection-level API you open a new HTTP connection to a target endpoint by materializing a ``Flow``
|
||||
returned by the ``Http.get(system).outgoingConnection(...)`` method. Here is an example:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#outgoing-connection-example
|
||||
|
||||
Apart from the host name and port the ``Http.get(system).outgoingConnection(...)`` method also allows you to specify socket options
|
||||
and a number of configuration settings for the connection.
|
||||
|
||||
Note that no connection is attempted until the returned flow is actually materialized! If the flow is materialized
|
||||
several times then several independent connections will be opened (one per materialization).
|
||||
If the connection attempt fails, for whatever reason, the materialized flow will be immediately terminated with a
|
||||
respective exception.
|
||||
|
||||
|
||||
Request-Response Cycle
|
||||
----------------------
|
||||
|
||||
Once the connection flow has been materialized it is ready to consume ``HttpRequest`` instances from the source it is
|
||||
attached to. Each request is sent across the connection and incoming responses dispatched to the downstream pipeline.
|
||||
Of course and as always, back-pressure is adequately maintained across all parts of the
|
||||
connection. This means that, if the downstream pipeline consuming the HTTP responses is slow, the request source will
|
||||
eventually be slowed down in sending requests.
|
||||
|
||||
Any errors occurring on the underlying connection are surfaced as exceptions terminating the response stream (and
|
||||
canceling the request source).
|
||||
|
||||
Note that, if the source produces subsequent requests before the prior responses have arrived, these requests will be
|
||||
pipelined__ across the connection, which is something that is not supported by all HTTP servers.
|
||||
Also, if the server closes the connection before responses to all requests have been received this will result in the
|
||||
response stream being terminated with a truncation error.
|
||||
|
||||
__ http://en.wikipedia.org/wiki/HTTP_pipelining
|
||||
|
||||
|
||||
Closing Connections
|
||||
-------------------
|
||||
|
||||
Akka HTTP actively closes an established connection upon reception of a response containing ``Connection: close`` header.
|
||||
The connection can also be closed by the server.
|
||||
|
||||
An application can actively trigger the closing of the connection by completing the request stream. In this case the
|
||||
underlying TCP connection will be closed when the last pending response has been received.
|
||||
|
||||
|
||||
Timeouts
|
||||
--------
|
||||
|
||||
Currently Akka HTTP doesn't implement client-side request timeout checking itself as this functionality can be regarded
|
||||
as a more general purpose streaming infrastructure feature.
|
||||
However, akka-stream should soon provide such a feature.
|
||||
|
||||
|
||||
.. _http-client-layer-java:
|
||||
|
||||
Stand-Alone HTTP Layer Usage
|
||||
----------------------------
|
||||
|
||||
Due to its Reactive-Streams-based nature the Akka HTTP layer is fully detachable from the underlying TCP
|
||||
interface. While in most applications this "feature" will not be crucial it can be useful in certain cases to be able
|
||||
to "run" the HTTP layer (and, potentially, higher-layers) against data that do not come from the network but rather
|
||||
some other source. Potential scenarios where this might be useful include tests, debugging or low-level event-sourcing
|
||||
(e.g by replaying network traffic).
|
||||
|
||||
On the client-side the stand-alone HTTP layer forms a ``BidiFlow<HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, BoxedUnit>``,
|
||||
that is a stage that "upgrades" a potentially encrypted raw connection to the HTTP level.
|
||||
|
||||
You create an instance of the layer by calling one of the two overloads of the ``Http.get(system).clientLayer`` method,
|
||||
which also allows for varying degrees of configuration.
|
||||
151
akka-docs/rst/java/http/client-side/host-level.rst
Normal file
151
akka-docs/rst/java/http/client-side/host-level.rst
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
.. _host-level-api-java:
|
||||
|
||||
Host-Level Client-Side API
|
||||
==========================
|
||||
|
||||
As opposed to the :ref:`connection-level-api` the host-level API relieves you from manually managing individual HTTP
|
||||
connections. It autonomously manages a configurable pool of connections to *one particular target endpoint* (i.e.
|
||||
host/port combination).
|
||||
|
||||
|
||||
Requesting a Host Connection Pool
|
||||
---------------------------------
|
||||
|
||||
The best way to get a hold of a connection pool to a given target endpoint is the ``Http.get(system).cachedHostConnectionPool(...)``
|
||||
method, which returns a ``Flow`` that can be "baked" into an application-level stream setup. This flow is also called
|
||||
a "pool client flow".
|
||||
|
||||
The connection pool underlying a pool client flow is cached. For every ``ActorSystem``, target endpoint and pool
|
||||
configuration there will never be more than a single pool live at any time.
|
||||
|
||||
Also, the HTTP layer transparently manages idle shutdown and restarting of connection pools as configured.
|
||||
The client flow instances therefore remain valid throughout the lifetime of the application, i.e. they can be
|
||||
materialized as often as required and the time between individual materialization is of no importance.
|
||||
|
||||
When you request a pool client flow with ``Http.get(system).cachedHostConnectionPool(...)`` Akka HTTP will immediately start
|
||||
the pool, even before the first client flow materialization. However, this running pool will not actually open the
|
||||
first connection to the target endpoint until the first request has arrived.
|
||||
|
||||
|
||||
Configuring a Host Connection Pool
|
||||
----------------------------------
|
||||
|
||||
Apart from the connection-level config settings and socket options there are a number of settings that allow you to
|
||||
influence the behavior of the connection pool logic itself.
|
||||
Check out the ``akka.http.client.host-connection-pool`` section of the Akka HTTP :ref:`akka-http-configuration-java` for
|
||||
more information about which settings are available and what they mean.
|
||||
|
||||
Note that, if you request pools with different configurations for the same target host you will get *independent* pools.
|
||||
This means that, in total, your application might open more concurrent HTTP connections to the target endpoint than any
|
||||
of the individual pool's ``max-connections`` settings allow!
|
||||
|
||||
There is one setting that likely deserves a bit deeper explanation: ``max-open-requests``.
|
||||
This setting limits the maximum number of requests that can be in-flight at any time for a single connection pool.
|
||||
If an application calls ``Http.get(system).cachedHostConnectionPool(...)`` 3 times (with the same endpoint and settings) it will get
|
||||
back ``3`` different client flow instances for the same pool. If each of these client flows is then materialized ``4`` times
|
||||
(concurrently) the application will have 12 concurrently running client flow materializations.
|
||||
All of these share the resources of the single pool.
|
||||
|
||||
This means that, if the pool's ``pipelining-limit`` is left at ``1`` (effecitvely disabeling pipelining), no more than 12 requests can be open at any time.
|
||||
With a ``pipelining-limit`` of ``8`` and 12 concurrent client flow materializations the theoretical open requests
|
||||
maximum is ``96``.
|
||||
|
||||
The ``max-open-requests`` config setting allows for applying a hard limit which serves mainly as a protection against
|
||||
erroneous connection pool use, e.g. because the application is materializing too many client flows that all compete for
|
||||
the same pooled connections.
|
||||
|
||||
.. _using-a-host-connection-pool-java:
|
||||
|
||||
Using a Host Connection Pool
|
||||
----------------------------
|
||||
|
||||
The "pool client flow" returned by ``Http.get(system).cachedHostConnectionPool(...)`` has the following type::
|
||||
|
||||
// TODO Tuple2 will be changed to be `akka.japi.Pair`
|
||||
Flow[Tuple2[HttpRequest, T], Tuple2[Try[HttpResponse], T], HostConnectionPool]
|
||||
|
||||
This means it consumes tuples of type ``(HttpRequest, T)`` and produces tuples of type ``(Try[HttpResponse], T)``
|
||||
which might appear more complicated than necessary on first sight.
|
||||
The reason why the pool API includes objects of custom type ``T`` on both ends lies in the fact that the underlying
|
||||
transport usually comprises more than a single connection and as such the pool client flow often generates responses in
|
||||
an order that doesn't directly match the consumed requests.
|
||||
We could have built the pool logic in a way that reorders responses according to their requests before dispatching them
|
||||
to the application, but this would have meant that a single slow response could block the delivery of potentially many
|
||||
responses that would otherwise be ready for consumption by the application.
|
||||
|
||||
In order to prevent unnecessary head-of-line blocking the pool client-flow is allowed to dispatch responses as soon as
|
||||
they arrive, independently of the request order. Of course this means that there needs to be another way to associate a
|
||||
response with its respective request. The way that this is done is by allowing the application to pass along a custom
|
||||
"context" object with the request, which is then passed back to the application with the respective response.
|
||||
This context object of type ``T`` is completely opaque to Akka HTTP, i.e. you can pick whatever works best for your
|
||||
particular application scenario.
|
||||
|
||||
.. note::
|
||||
A consequence of using a pool is that long-running requests block a connection while running and may starve other
|
||||
requests. Make sure not to use a connection pool for long-running requests like long-polling GET requests.
|
||||
Use the :ref:`connection-level-api-java` instead.
|
||||
|
||||
Connection Allocation Logic
|
||||
---------------------------
|
||||
|
||||
This is how Akka HTTP allocates incoming requests to the available connection "slots":
|
||||
|
||||
1. If there is a connection alive and currently idle then schedule the request across this connection.
|
||||
2. If no connection is idle and there is still an unconnected slot then establish a new connection.
|
||||
3. If all connections are already established and "loaded" with other requests then pick the connection with the least
|
||||
open requests (< the configured ``pipelining-limit``) that only has requests with idempotent methods scheduled to it,
|
||||
if there is one.
|
||||
4. Otherwise apply back-pressure to the request source, i.e. stop accepting new requests.
|
||||
|
||||
For more information about scheduling more than one request at a time across a single connection see
|
||||
`this wikipedia entry on HTTP pipelining`__.
|
||||
|
||||
__ http://en.wikipedia.org/wiki/HTTP_pipelining
|
||||
|
||||
|
||||
|
||||
Retrying a Request
|
||||
------------------
|
||||
|
||||
If the ``max-retries`` pool config setting is greater than zero the pool retries idempotent requests for which
|
||||
a response could not be successfully retrieved. Idempotent requests are those whose HTTP method is defined to be
|
||||
idempotent by the HTTP spec, which are all the ones currently modelled by Akka HTTP except for the ``POST``, ``PATCH``
|
||||
and ``CONNECT`` methods.
|
||||
|
||||
When a response could not be received for a certain request there are essentially three possible error scenarios:
|
||||
|
||||
1. The request got lost on the way to the server.
|
||||
2. The server experiences a problem while processing the request.
|
||||
3. The response from the server got lost on the way back.
|
||||
|
||||
Since the host connector cannot know which one of these possible reasons caused the problem and therefore ``PATCH`` and
|
||||
``POST`` requests could have already triggered a non-idempotent action on the server these requests cannot be retried.
|
||||
|
||||
In these cases, as well as when all retries have not yielded a proper response, the pool produces a failed ``Try``
|
||||
(i.e. a ``scala.util.Failure``) together with the custom request context.
|
||||
|
||||
|
||||
Pool Shutdown
|
||||
-------------
|
||||
|
||||
Completing a pool client flow will simply detach the flow from the pool. The connection pool itself will continue to run
|
||||
as it may be serving other client flows concurrently or in the future. Only after the configured ``idle-timeout`` for
|
||||
the pool has expired will Akka HTTP automatically terminate the pool and free all its resources.
|
||||
|
||||
If a new client flow is requested with ``Http.get(system).cachedHostConnectionPool(...)`` or if an already existing client flow is
|
||||
re-materialized the respective pool is automatically and transparently restarted.
|
||||
|
||||
In addition to the automatic shutdown via the configured idle timeouts it's also possible to trigger the immediate
|
||||
shutdown of a specific pool by calling ``shutdown()`` on the :class:`HostConnectionPool` instance that the pool client
|
||||
flow materializes into. This ``shutdown()`` call produces a ``Future[Unit]`` which is fulfilled when the pool
|
||||
termination has been completed.
|
||||
|
||||
It's also possible to trigger the immediate termination of *all* connection pools in the ``ActorSystem`` at the same
|
||||
time by calling ``Http.get(system).shutdownAllConnectionPools()``. This call too produces a ``Future[Unit]`` which is fulfilled when
|
||||
all pools have terminated.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#host-level-example
|
||||
65
akka-docs/rst/java/http/client-side/https-support.rst
Normal file
65
akka-docs/rst/java/http/client-side/https-support.rst
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
.. _clientSideHTTPS-java:
|
||||
|
||||
Client-Side HTTPS Support
|
||||
=========================
|
||||
|
||||
Akka HTTP supports TLS encryption on the client-side as well as on the :ref:`server-side <serverSideHTTPS-java>`.
|
||||
|
||||
.. warning:
|
||||
|
||||
Akka HTTP 1.0 does not completely validate certificates when using HTTPS. Please do not treat HTTPS connections
|
||||
made with this version as secure. Requests are vulnerable to a Man-In-The-Middle attack via certificate substitution.
|
||||
|
||||
The central vehicle for configuring encryption is the ``HttpsContext``, which can be created using
|
||||
the static method ``HttpsContext.create`` which is defined like this:
|
||||
|
||||
.. includecode:: /../../akka-http-core/src/main/java/akka/http/javadsl/HttpsContext.java
|
||||
:include: http-context-creation
|
||||
|
||||
In addition to the ``outgoingConnection``, ``newHostConnectionPool`` and ``cachedHostConnectionPool`` methods the
|
||||
`akka.http.javadsl.Http`_ extension also defines ``outgoingConnectionTls``, ``newHostConnectionPoolTls`` and
|
||||
``cachedHostConnectionPoolTls``. These methods work identically to their counterparts without the ``-Tls`` suffix,
|
||||
with the exception that all connections will always be encrypted.
|
||||
|
||||
The ``singleRequest`` and ``superPool`` methods determine the encryption state via the scheme of the incoming request,
|
||||
i.e. requests to an "https" URI will be encrypted, while requests to an "http" URI won't.
|
||||
|
||||
The encryption configuration for all HTTPS connections, i.e. the ``HttpsContext`` is determined according to the
|
||||
following logic:
|
||||
|
||||
1. If the optional ``httpsContext`` method parameter is defined it contains the configuration to be used (and thus
|
||||
takes precedence over any potentially set default client-side ``HttpsContext``).
|
||||
|
||||
2. If the optional ``httpsContext`` method parameter is undefined (which is the default) the default client-side
|
||||
``HttpsContext`` is used, which can be set via the ``setDefaultClientHttpsContext`` on the ``Http`` extension.
|
||||
|
||||
3. If no default client-side ``HttpsContext`` has been set via the ``setDefaultClientHttpsContext`` on the ``Http``
|
||||
extension the default system configuration is used.
|
||||
|
||||
Usually the process is, if the default system TLS configuration is not good enough for your application's needs,
|
||||
that you configure a custom ``HttpsContext`` instance and set it via ``Http.get(system).setDefaultClientHttpsContext``.
|
||||
Afterwards you simply use ``outgoingConnectionTls``, ``newHostConnectionPoolTls``, ``cachedHostConnectionPoolTls``,
|
||||
``superPool`` or ``singleRequest`` without a specific ``httpsContext`` argument, which causes encrypted connections
|
||||
to rely on the configured default client-side ``HttpsContext``.
|
||||
|
||||
If no custom ``HttpsContext`` is defined the default context uses Java's default TLS settings. Customizing the
|
||||
``HttpsContext`` can make the Https client less secure. Understand what you are doing!
|
||||
|
||||
Hostname verification
|
||||
---------------------
|
||||
|
||||
Hostname verification proves that the Akka HTTP client is actually communicating with the server it intended to
|
||||
communicate with. Without this check a man-in-the-middle attack is possible. In the attack scenario, an alternative
|
||||
certificate would be presented which was issued for another host name. Checking the host name in the certificate
|
||||
against the host name the connection was opened against is therefore vital.
|
||||
|
||||
The default ``HttpsContext`` enables hostname verification. Akka HTTP relies on the `Typesafe SSL-Config`_ library
|
||||
to implement this and security options for SSL/TLS. Hostname verification is provided by the JDK
|
||||
and used by Akka HTTP since Java 7, and on Java 6 the verification is implemented by ssl-config manually.
|
||||
|
||||
.. note::
|
||||
We highly recommend updating your Java runtime to the latest available release,
|
||||
preferably JDK 8, as it includes this and many more security features related to TLS.
|
||||
|
||||
.. _Typesafe SSL-Config: https://github.com/typesafehub/ssl-config
|
||||
.. _akka.http.javadsl.Http: @github@/akka-http-core/src/main/scala/akka/http/javadsl/Http.scala
|
||||
30
akka-docs/rst/java/http/client-side/index.rst
Normal file
30
akka-docs/rst/java/http/client-side/index.rst
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
.. _http-client-side-java:
|
||||
|
||||
Consuming HTTP-based Services (Client-Side)
|
||||
===========================================
|
||||
|
||||
All client-side functionality of Akka HTTP, for consuming HTTP-based services offered by other endpoints, is currently
|
||||
provided by the ``akka-http-core`` module.
|
||||
|
||||
Depending on your application's specific needs you can choose from three different API levels:
|
||||
|
||||
:ref:`connection-level-api-java`
|
||||
for full-control over when HTTP connections are opened/closed and how requests are scheduled across them
|
||||
|
||||
:ref:`host-level-api-java`
|
||||
for letting Akka HTTP manage a connection-pool to *one specific* host/port endpoint
|
||||
|
||||
:ref:`request-level-api-java`
|
||||
for letting Akka HTTP perform all connection management
|
||||
|
||||
You can interact with different API levels at the same time and, independently of which API level you choose,
|
||||
Akka HTTP will happily handle many thousand concurrent connections to a single or many different hosts.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
connection-level
|
||||
host-level
|
||||
request-level
|
||||
https-support
|
||||
websocket-support
|
||||
73
akka-docs/rst/java/http/client-side/request-level.rst
Normal file
73
akka-docs/rst/java/http/client-side/request-level.rst
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
.. _request-level-api-java:
|
||||
|
||||
Request-Level Client-Side API
|
||||
=============================
|
||||
|
||||
The request-level API is the most convenient way of using Akka HTTP's client-side functionality. It internally builds upon the
|
||||
:ref:`host-level-api-java` to provide you with a simple and easy-to-use way of retrieving HTTP responses from remote servers.
|
||||
Depending on your preference you can pick the flow-based or the future-based variant.
|
||||
|
||||
.. note::
|
||||
The request-level API is implemented on top of a connection pool that is shared inside the ActorSystem. A consequence of
|
||||
using a pool is that long-running requests block a connection while running and starve other requests. Make sure not to use
|
||||
the request-level API for long-running requests like long-polling GET requests. Use the :ref:`connection-level-api-java` instead.
|
||||
|
||||
Flow-Based Variant
|
||||
------------------
|
||||
|
||||
The flow-based variant of the request-level client-side API is presented by the ``Http().superPool(...)`` method.
|
||||
It creates a new "super connection pool flow", which routes incoming requests to a (cached) host connection pool
|
||||
depending on their respective effective URIs.
|
||||
|
||||
The ``Flow`` returned by ``Http().superPool(...)`` is very similar to the one from the :ref:`host-level-api-java`, so the
|
||||
:ref:`using-a-host-connection-pool-java` section also applies here.
|
||||
|
||||
However, there is one notable difference between a "host connection pool client flow" for the host-level API and a
|
||||
"super-pool flow":
|
||||
Since in the former case the flow has an implicit target host context the requests it takes don't need to have absolute
|
||||
URIs or a valid ``Host`` header. The host connection pool will automatically add a ``Host`` header if required.
|
||||
|
||||
For a super-pool flow this is not the case. All requests to a super-pool must either have an absolute URI or a valid
|
||||
``Host`` header, because otherwise it'd be impossible to find out which target endpoint to direct the request to.
|
||||
|
||||
|
||||
Future-Based Variant
|
||||
--------------------
|
||||
|
||||
Sometimes your HTTP client needs are very basic. You simply need the HTTP response for a certain request and don't
|
||||
want to bother with setting up a full-blown streaming infrastructure.
|
||||
|
||||
For these cases Akka HTTP offers the ``Http().singleRequest(...)`` method, which simply turns an ``HttpRequest`` instance
|
||||
into ``Future<HttpResponse>``. Internally the request is dispatched across the (cached) host connection pool for the
|
||||
request's effective URI.
|
||||
|
||||
Just like in the case of the super-pool flow described above the request must have either an absolute URI or a valid
|
||||
``Host`` header, otherwise the returned future will be completed with an error.
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#single-request-example
|
||||
|
||||
Using the Future-Based API in Actors
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
When using the ``Future`` based API from inside an ``Actor``, all the usual caveats apply to how one should deal
|
||||
with the futures completion. For example you should not access the Actors state from within the Future's callbacks
|
||||
(such as ``map``, ``onComplete``, ...) and instead you should use the ``pipe`` pattern to pipe the result back
|
||||
to the Actor as a message:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#single-request-in-actor-example
|
||||
|
||||
|
||||
.. warning::
|
||||
Be sure to consume the response entities ``dataBytes:Source[ByteString,Unit]`` by for example connecting it
|
||||
to a ``Sink`` (for example ``response.entity.dataBytes.runWith(Sink.ignore)`` if you don't care about the
|
||||
response entity), since otherwise Akka HTTP (and the underlying Streams infrastructure) will understand the
|
||||
lack of entity consumption as a back-pressure signal and stop reading from the underlying TCP connection!
|
||||
|
||||
This is a feature of Akka HTTP that allows consuming entities (and pulling them through the network) in
|
||||
a streaming fashion, and only *on demand* when the client is ready to consume the bytes -
|
||||
it may be a bit suprising at first though.
|
||||
|
||||
There are tickets open about automatically dropping entities if not consumed (`#18716`_ and `#18540`_),
|
||||
so these may be implemented in the near future.
|
||||
|
||||
.. _#18540: https://github.com/akka/akka/issues/18540
|
||||
.. _#18716: https://github.com/akka/akka/issues/18716
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
.. _client-side-websocket-support-java:
|
||||
|
||||
Client-Side WebSocket Support
|
||||
=============================
|
||||
|
||||
Not yet implemented see 17275_.
|
||||
|
||||
.. _17275: https://github.com/akka/akka/issues/17275
|
||||
28
akka-docs/rst/java/http/configuration.rst
Normal file
28
akka-docs/rst/java/http/configuration.rst
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
.. _akka-http-configuration-java:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Just like any other Akka module Akka HTTP is configured via `Typesafe Config`_.
|
||||
Usually this means that you provide an ``application.conf`` which contains all the application-specific settings that
|
||||
differ from the default ones provided by the reference configuration files from the individual Akka modules.
|
||||
|
||||
These are the relevant default configuration values for the Akka HTTP modules.
|
||||
|
||||
akka-http-core
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. literalinclude:: ../../../../akka-http-core/src/main/resources/reference.conf
|
||||
:language: none
|
||||
|
||||
|
||||
akka-http
|
||||
~~~~~~~~~
|
||||
|
||||
.. literalinclude:: ../../../../akka-http/src/main/resources/reference.conf
|
||||
:language: none
|
||||
|
||||
|
||||
The other Akka HTTP modules do not offer any configuration via `Typesafe Config`_.
|
||||
|
||||
.. _Typesafe Config: https://github.com/typesafehub/config
|
||||
270
akka-docs/rst/java/http/http-model.rst
Normal file
270
akka-docs/rst/java/http/http-model.rst
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
.. _http-model-java:
|
||||
|
||||
HTTP Model
|
||||
==========
|
||||
|
||||
Akka HTTP model contains a deeply structured, fully immutable, case-class based model of all the major HTTP data
|
||||
structures, like HTTP requests, responses and common headers.
|
||||
It lives in the *akka-http-core* module and forms the basis for most of Akka HTTP's APIs.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Since akka-http-core provides the central HTTP data structures you will find the following import in quite a
|
||||
few places around the code base (and probably your own code as well):
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: import-model
|
||||
|
||||
This brings all of the most relevant types in scope, mainly:
|
||||
|
||||
- ``HttpRequest`` and ``HttpResponse``, the central message model
|
||||
- ``headers``, the package containing all the predefined HTTP header models and supporting types
|
||||
- Supporting types like ``Uri``, ``HttpMethods``, ``MediaTypes``, ``StatusCodes``, etc.
|
||||
|
||||
A common pattern is that the model of a certain entity is represented by an immutable type (class or trait),
|
||||
while the actual instances of the entity defined by the HTTP spec live in an accompanying object carrying the name of
|
||||
the type plus a trailing plural 's'.
|
||||
|
||||
For example:
|
||||
|
||||
- Defined ``HttpMethod`` instances are defined as static fields of the ``HttpMethods`` class.
|
||||
- Defined ``HttpCharset`` instances are defined as static fields of the ``HttpCharsets`` class.
|
||||
- Defined ``HttpEncoding`` instances are defined as static fields of the ``HttpEncodings`` class.
|
||||
- Defined ``HttpProtocol`` instances are defined as static fields of the ``HttpProtocols`` class.
|
||||
- Defined ``MediaType`` instances are defined as static fields of the ``MediaTypes`` class.
|
||||
- Defined ``StatusCode`` instances are defined as static fields of the ``StatusCodes`` class.
|
||||
|
||||
HttpRequest
|
||||
-----------
|
||||
|
||||
``HttpRequest`` and ``HttpResponse`` are the basic immutable classes representing HTTP messages.
|
||||
|
||||
An ``HttpRequest`` consists of
|
||||
|
||||
- a method (GET, POST, etc.)
|
||||
- a URI
|
||||
- a seq of headers
|
||||
- an entity (body data)
|
||||
- a protocol
|
||||
|
||||
Here are some examples how to construct an ``HttpRequest``:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: construct-request
|
||||
|
||||
In its basic form ``HttpRequest.create`` creates an empty default GET request without headers which can then be
|
||||
transformed using one of the ``withX`` methods, ``addHeader``, or ``addHeaders``. Each of those will create a
|
||||
new immutable instance, so instances can be shared freely. There exist some overloads for ``HttpRequest.create`` that
|
||||
simplify creating requests for common cases. Also, to aid readability, there are predefined alternatives for ``create``
|
||||
named after HTTP methods to create a request with a given method and uri directly.
|
||||
|
||||
HttpResponse
|
||||
------------
|
||||
|
||||
An ``HttpResponse`` consists of
|
||||
|
||||
- a status code
|
||||
- a list of headers
|
||||
- an entity (body data)
|
||||
- a protocol
|
||||
|
||||
Here are some examples how to construct an ``HttpResponse``:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: construct-response
|
||||
|
||||
In addition to the simple ``HttpEntities.create`` methods which create an entity from a fixed ``String`` or ``ByteString``
|
||||
as shown here the Akka HTTP model defines a number of subclasses of ``HttpEntity`` which allow body data to be specified as a
|
||||
stream of bytes. All of these types can be created using the method on ``HttpEntites``.
|
||||
|
||||
|
||||
.. _HttpEntity-java:
|
||||
|
||||
HttpEntity
|
||||
----------
|
||||
|
||||
An ``HttpEntity`` carries the data bytes of a message together with its Content-Type and, if known, its Content-Length.
|
||||
In Akka HTTP there are five different kinds of entities which model the various ways that message content can be
|
||||
received or sent:
|
||||
|
||||
HttpEntityStrict
|
||||
The simplest entity, which is used when all the entity are already available in memory.
|
||||
It wraps a plain ``ByteString`` and represents a standard, unchunked entity with a known ``Content-Length``.
|
||||
|
||||
|
||||
HttpEntityDefault
|
||||
The general, unchunked HTTP/1.1 message entity.
|
||||
It has a known length and presents its data as a ``Source[ByteString]`` which can be only materialized once.
|
||||
It is an error if the provided source doesn't produce exactly as many bytes as specified.
|
||||
The distinction of ``HttpEntityStrict`` and ``HttpEntityDefault`` is an API-only one. One the wire,
|
||||
both kinds of entities look the same.
|
||||
|
||||
|
||||
HttpEntityChunked
|
||||
The model for HTTP/1.1 `chunked content`__ (i.e. sent with ``Transfer-Encoding: chunked``).
|
||||
The content length is unknown and the individual chunks are presented as a ``Source[ChunkStreamPart]``.
|
||||
A ``ChunkStreamPart`` is either a non-empty chunk or the empty last chunk containing optional trailer headers.
|
||||
The stream consists of zero or more non-empty chunks parts and can be terminated by an optional last chunk.
|
||||
|
||||
|
||||
HttpEntityCloseDelimited
|
||||
An unchunked entity of unknown length that is implicitly delimited by closing the connection (``Connection: close``).
|
||||
Content data is presented as a ``Source[ByteString]``.
|
||||
Since the connection must be closed after sending an entity of this type it can only be used on the server-side for
|
||||
sending a response.
|
||||
Also, the main purpose of ``CloseDelimited`` entities is compatibility with HTTP/1.0 peers, which do not support
|
||||
chunked transfer encoding. If you are building a new application and are not constrained by legacy requirements you
|
||||
shouldn't rely on ``CloseDelimited`` entities, since implicit terminate-by-connection-close is not a robust way of
|
||||
signaling response end, especially in the presence of proxies. Additionally this type of entity prevents connection
|
||||
reuse which can seriously degrade performance. Use ``HttpEntityChunked`` instead!
|
||||
|
||||
|
||||
HttpEntityIndefiniteLength
|
||||
A streaming entity of unspecified length for use in a ``Multipart.BodyPart``.
|
||||
|
||||
__ http://tools.ietf.org/html/rfc7230#section-4.1
|
||||
|
||||
Entity types ``HttpEntityStrict``, ``HttpEntityDefault``, and ``HttpEntityChunked`` are a subtype of ``RequestEntity``
|
||||
which allows to use them for requests and responses. In contrast, ``HttpEntityCloseDelimited`` can only be used for responses.
|
||||
|
||||
Streaming entity types (i.e. all but ``HttpEntityStrict``) cannot be shared or serialized. To create a strict, sharable copy of an
|
||||
entity or message use ``HttpEntity.toStrict`` or ``HttpMessage.toStrict`` which returns a ``Future`` of the object with
|
||||
the body data collected into a ``ByteString``.
|
||||
|
||||
The class ``HttpEntities`` contains static methods to create entities from common types easily.
|
||||
|
||||
You can use the ``isX` methods of ``HttpEntity`` to find out of which subclass an entity is if you want to provide
|
||||
special handling for each of the subtypes. However, in many cases a recipient of an ``HttpEntity`` doesn't care about
|
||||
of which subtype an entity is (and how data is transported exactly on the HTTP layer). Therefore, the general method
|
||||
``HttpEntity.getDataBytes()`` is provided which returns a ``Source<ByteString, ?>`` that allows access to the data of an
|
||||
entity regardless of its concrete subtype.
|
||||
|
||||
.. note::
|
||||
|
||||
When to use which subtype?
|
||||
- Use ``HttpEntityStrict`` if the amount of data is "small" and already available in memory (e.g. as a ``String`` or ``ByteString``)
|
||||
- Use ``HttpEntityDefault`` if the data is generated by a streaming data source and the size of the data is known
|
||||
- Use ``HttpEntityChunked`` for an entity of unknown length
|
||||
- Use ``HttpEntityCloseDelimited`` for a response as a legacy alternative to ``HttpEntityChunked`` if the client
|
||||
doesn't support chunked transfer encoding. Otherwise use ``HttpEntityChunked``!
|
||||
- In a ``Multipart.Bodypart`` use ``HttpEntityIndefiniteLength`` for content of unknown length.
|
||||
|
||||
.. caution::
|
||||
|
||||
When you receive a non-strict message from a connection then additional data is only read from the network when you
|
||||
request it by consuming the entity data stream. This means that, if you *don't* consume the entity stream then the
|
||||
connection will effectively be stalled. In particular, no subsequent message (request or response) will be read from
|
||||
the connection as the entity of the current message "blocks" the stream.
|
||||
Therefore you must make sure that you always consume the entity data, even in the case that you are not actually
|
||||
interested in it!
|
||||
|
||||
Special processing for HEAD requests
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
`RFC 7230`_ defines very clear rules for the entity length of HTTP messages.
|
||||
|
||||
Especially this rule requires special treatment in Akka HTTP:
|
||||
|
||||
Any response to a HEAD request and any response with a 1xx
|
||||
(Informational), 204 (No Content), or 304 (Not Modified) status
|
||||
code is always terminated by the first empty line after the
|
||||
header fields, regardless of the header fields present in the
|
||||
message, and thus cannot contain a message body.
|
||||
|
||||
Responses to HEAD requests introduce the complexity that `Content-Length` or `Transfer-Encoding` headers
|
||||
can be present but the entity is empty. This is modeled by allowing `HttpEntityDefault` and `HttpEntityChunked`
|
||||
to be used for HEAD responses with an empty data stream.
|
||||
|
||||
Also, when a HEAD response has an `HttpEntityCloseDelimited` entity the Akka HTTP implementation will *not* close the
|
||||
connection after the response has been sent. This allows the sending of HEAD responses without `Content-Length`
|
||||
header across persistent HTTP connections.
|
||||
|
||||
.. _RFC 7230: http://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||
|
||||
|
||||
Header Model
|
||||
------------
|
||||
|
||||
Akka HTTP contains a rich model of the most common HTTP headers. Parsing and rendering is done automatically so that
|
||||
applications don't need to care for the actual syntax of headers. Headers not modelled explicitly are represented
|
||||
as a ``RawHeader`` (which is essentially a String/String name/value pair).
|
||||
|
||||
See these examples of how to deal with headers:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: headers
|
||||
|
||||
|
||||
HTTP Headers
|
||||
------------
|
||||
|
||||
When the Akka HTTP server receives an HTTP request it tries to parse all its headers into their respective
|
||||
model classes. Independently of whether this succeeds or not, the HTTP layer will
|
||||
always pass on all received headers to the application. Unknown headers as well as ones with invalid syntax (according
|
||||
to the header parser) will be made available as ``RawHeader`` instances. For the ones exhibiting parsing errors a
|
||||
warning message is logged depending on the value of the ``illegal-header-warnings`` config setting.
|
||||
|
||||
Some headers have special status in HTTP and are therefore treated differently from "regular" headers:
|
||||
|
||||
Content-Type
|
||||
The Content-Type of an HTTP message is modeled as the ``contentType`` field of the ``HttpEntity``.
|
||||
The ``Content-Type`` header therefore doesn't appear in the ``headers`` sequence of a message.
|
||||
Also, a ``Content-Type`` header instance that is explicitly added to the ``headers`` of a request or response will
|
||||
not be rendered onto the wire and trigger a warning being logged instead!
|
||||
|
||||
Transfer-Encoding
|
||||
Messages with ``Transfer-Encoding: chunked`` are represented as a ``HttpEntityChunked`` entity.
|
||||
As such chunked messages that do not have another deeper nested transfer encoding will not have a ``Transfer-Encoding``
|
||||
header in their ``headers`` list.
|
||||
Similarly, a ``Transfer-Encoding`` header instance that is explicitly added to the ``headers`` of a request or
|
||||
response will not be rendered onto the wire and trigger a warning being logged instead!
|
||||
|
||||
Content-Length
|
||||
The content length of a message is modelled via its :ref:`HttpEntity-java`. As such no ``Content-Length`` header will ever
|
||||
be part of a message's ``header`` sequence.
|
||||
Similarly, a ``Content-Length`` header instance that is explicitly added to the ``headers`` of a request or
|
||||
response will not be rendered onto the wire and trigger a warning being logged instead!
|
||||
|
||||
Server
|
||||
A ``Server`` header is usually added automatically to any response and its value can be configured via the
|
||||
``akka.http.server.server-header`` setting. Additionally an application can override the configured header with a
|
||||
custom one by adding it to the response's ``header`` sequence.
|
||||
|
||||
User-Agent
|
||||
A ``User-Agent`` header is usually added automatically to any request and its value can be configured via the
|
||||
``akka.http.client.user-agent-header`` setting. Additionally an application can override the configured header with a
|
||||
custom one by adding it to the request's ``header`` sequence.
|
||||
|
||||
Date
|
||||
The ``Date`` response header is added automatically but can be overridden by supplying it manually.
|
||||
|
||||
Connection
|
||||
On the server-side Akka HTTP watches for explicitly added ``Connection: close`` response headers and as such honors
|
||||
the potential wish of the application to close the connection after the respective response has been sent out.
|
||||
The actual logic for determining whether to close the connection is quite involved. It takes into account the
|
||||
request's method, protocol and potential ``Connection`` header as well as the response's protocol, entity and
|
||||
potential ``Connection`` header. See `this test`__ for a full table of what happens when.
|
||||
|
||||
__ @github@/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/ResponseRendererSpec.scala#L422
|
||||
|
||||
|
||||
Parsing / Rendering
|
||||
-------------------
|
||||
|
||||
Parsing and rendering of HTTP data structures is heavily optimized and for most types there's currently no public API
|
||||
provided to parse (or render to) Strings or byte arrays.
|
||||
|
||||
.. note::
|
||||
Various parsing and rendering settings are available to tweak in the configuration under ``akka.http.client[.parsing]``,
|
||||
``akka.http.server[.parsing]`` and ``akka.http.host-connection-pool[.client.parsing]``, with defaults for all of these
|
||||
being defined in the ``akka.http.parsing`` configuration section.
|
||||
|
||||
For example, if you want to change a parsing setting for all components, you can set the ``akka.http.parsing.illegal-header-warnings = off``
|
||||
value. However this setting can be stil overriden by the more specific sections, like for example ``akka.http.server.parsing.illegal-header-warnings = on``.
|
||||
In this case both ``client`` and ``host-connection-pool`` APIs will see the setting ``off``, however the server will see ``on``.
|
||||
|
||||
In the case of ``akka.http.host-connection-pool.client`` settings, they default to settings set in ``akka.http.client``,
|
||||
and can override them if needed. This is useful, since both ``client`` and ``host-connection-pool`` APIs,
|
||||
such as the Client API ``Http.get(sys).outgoingConnection`` or the Host Connection Pool APIs ``Http.get(sys).singleRequest``
|
||||
or ``Http.get(sys).superPool``, usually need the same settings, however the ``server`` most likely has a very different set of settings.
|
||||
41
akka-docs/rst/java/http/index.rst
Normal file
41
akka-docs/rst/java/http/index.rst
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
.. _http-java:
|
||||
|
||||
Akka HTTP
|
||||
=========
|
||||
|
||||
The Akka HTTP modules implement a full server- and client-side HTTP stack on top of *akka-actor* and *akka-stream*. It's
|
||||
not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. While interaction
|
||||
with a browser is of course also in scope it is not the primary focus of Akka HTTP.
|
||||
|
||||
Akka HTTP follows a rather open design and many times offers several different API levels for "doing the same thing".
|
||||
You get to pick the API level of abstraction that is most suitable for your application.
|
||||
This means that, if you have trouble achieving something using a high-level API, there's a good chance that you can get
|
||||
it done with a low-level API, which offers more flexibility but might require you to write more application code.
|
||||
|
||||
Akka HTTP is structured into several modules:
|
||||
|
||||
akka-http-core
|
||||
A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets).
|
||||
Includes a model of all things HTTP.
|
||||
|
||||
akka-http
|
||||
Higher-level functionality, like (un)marshalling, (de)compression as well as a powerful DSL
|
||||
for defining HTTP-based APIs on the server-side
|
||||
|
||||
akka-http-testkit
|
||||
A test harness and set of utilities for verifying server-side service implementations
|
||||
|
||||
akka-http-jackson
|
||||
Predefined glue-code for (de)serializing custom types from/to JSON with jackson_
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
http-model
|
||||
server-side/low-level-server-side-api
|
||||
server-side/websocket-support
|
||||
routing-dsl/index
|
||||
client-side/index
|
||||
configuration
|
||||
|
||||
.. _jackson: https://github.com/FasterXML/jackson
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
.. _-extractHost-java-:
|
||||
|
||||
RequestVals.host
|
||||
================
|
||||
|
||||
Extract the hostname part of the ``Host`` request header and expose it as a ``String`` extraction
|
||||
to its inner route.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java#extractHostname
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
.. _-host-java-:
|
||||
|
||||
host
|
||||
====
|
||||
|
||||
Filter requests matching conditions against the hostname part of the Host header value
|
||||
in the request.
|
||||
|
||||
There are a few variants:
|
||||
|
||||
* reject all requests with a hostname different from the given ones
|
||||
* reject all requests for which the hostname does not satisfy the given predicate
|
||||
* reject all requests for which the hostname does not satisfy the given regular expression
|
||||
|
||||
The regular expression matching works a little bit different: it rejects all requests with a hostname
|
||||
that doesn't have a prefix matching the given regular expression and also extracts a ``String`` to its
|
||||
inner route following this rules:
|
||||
|
||||
* For all matching requests the prefix string matching the regex is extracted and passed to the inner route.
|
||||
* If the regex contains a capturing group only the string matched by this group is extracted.
|
||||
* If the regex contains more than one capturing group an ``IllegalArgumentException`` is thrown.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Matching a list of hosts:
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java#host1
|
||||
|
||||
Making sure the host satisfies the given predicate
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java#host2
|
||||
|
||||
Using a regular expressions:
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java#matchAndExtractHost
|
||||
|
||||
Beware that in the case of introducing multiple capturing groups in the regex such as in the case bellow, the
|
||||
directive will fail at runtime, at the moment the route tree is evaluated for the first time. This might cause
|
||||
your http handler actor to enter in a fail/restart loop depending on your supervision strategy.
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/HostDirectivesExamplesTest.java#failing-matchAndExtractHost
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
.. _HostDirectives-java:
|
||||
|
||||
HostDirectives
|
||||
==============
|
||||
|
||||
HostDirectives allow you to filter requests based on the hostname part of the ``Host`` header
|
||||
contained in incoming requests as well as extracting its value for usage in inner routes.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
host
|
||||
extractHost
|
||||
|
||||
|
||||
70
akka-docs/rst/java/http/routing-dsl/directives/index.rst
Normal file
70
akka-docs/rst/java/http/routing-dsl/directives/index.rst
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
.. _directives-java:
|
||||
|
||||
Directives
|
||||
==========
|
||||
|
||||
A directive is a wrapper for a route or a list of alternative routes that adds one or more of the following
|
||||
functionality to its nested route(s):
|
||||
|
||||
* it filters the request and lets only matching requests pass (e.g. the `get` directive lets only GET-requests pass)
|
||||
* it modifies the request or the ``RequestContext`` (e.g. the `path` directives filters on the unmatched path and then
|
||||
passes an updated ``RequestContext`` unmatched path)
|
||||
* it modifies the response coming out of the nested route
|
||||
|
||||
akka-http provides a set of predefined directives for various tasks. You can access them by either extending from
|
||||
``akka.http.javadsl.server.AllDirectives`` or by importing them statically with
|
||||
``import static akka.http.javadsl.server.Directives.*;``.
|
||||
|
||||
These classes of directives are currently defined:
|
||||
|
||||
BasicDirectives
|
||||
Contains methods to create routes that complete with a static values or allow specifying :ref:`handlers-java` to
|
||||
process a request.
|
||||
|
||||
CacheConditionDirectives
|
||||
Contains a single directive ``conditional`` that wraps its inner route with support for Conditional Requests as defined
|
||||
by `RFC 7234`_.
|
||||
|
||||
CodingDirectives
|
||||
Contains directives to decode compressed requests and encode responses.
|
||||
|
||||
CookieDirectives
|
||||
Contains a single directive ``setCookie`` to aid adding a cookie to a response.
|
||||
|
||||
ExecutionDirectives
|
||||
Contains directives to deal with exceptions that occurred during routing.
|
||||
|
||||
FileAndResourceDirectives
|
||||
Contains directives to serve resources from files on the file system or from the classpath.
|
||||
|
||||
HostDirectives
|
||||
Contains directives to filter on the ``Host`` header of the incoming request.
|
||||
|
||||
MethodDirectives
|
||||
Contains directives to filter on the HTTP method of the incoming request.
|
||||
|
||||
MiscDirectives
|
||||
Contains directives that validate a request by user-defined logic.
|
||||
|
||||
:ref:`path-directives-java`
|
||||
Contains directives to match and filter on the URI path of the incoming request.
|
||||
|
||||
RangeDirectives
|
||||
Contains a single directive ``withRangeSupport`` that adds support for retrieving partial responses.
|
||||
|
||||
SchemeDirectives
|
||||
Contains a single directive ``scheme`` to filter requests based on the URI scheme (http vs. https).
|
||||
|
||||
WebsocketDirectives
|
||||
Contains directives to support answering Websocket requests.
|
||||
|
||||
TODO this page should be rewritten as the corresponding Scala page
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
path-directives
|
||||
method-directives/index
|
||||
host-directives/index
|
||||
|
||||
.. _`RFC 7234`: http://tools.ietf.org/html/rfc7234
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
.. _-delete-java-:
|
||||
|
||||
delete
|
||||
======
|
||||
|
||||
Matches requests with HTTP method ``DELETE``.
|
||||
|
||||
This directive filters an incoming request by its HTTP method. Only requests with
|
||||
method ``DELETE`` are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#delete
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
.. _-extractMethod-java-:
|
||||
|
||||
extractMethod
|
||||
=============
|
||||
|
||||
Extracts the :class:`HttpMethod` from the request context and provides it for use for other directives explicitly.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
In the below example our route first matches all ``GET`` requests, and if an incoming request wasn't a ``GET``,
|
||||
the matching continues and the extractMethod route will be applied which we can use to programatically
|
||||
print what type of request it was - independent of what actual HttpMethod it was:
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#extractMethod
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
.. _-get-java-:
|
||||
|
||||
get
|
||||
===
|
||||
|
||||
Matches requests with HTTP method ``GET``.
|
||||
|
||||
This directive filters the incoming request by its HTTP method. Only requests with
|
||||
method ``GET`` are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#get
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
.. _-head-java-:
|
||||
|
||||
head
|
||||
====
|
||||
|
||||
Matches requests with HTTP method ``HEAD``.
|
||||
|
||||
This directive filters the incoming request by its HTTP method. Only requests with
|
||||
method ``HEAD`` are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
.. note:: By default, akka-http handles HEAD-requests transparently by dispatching a GET-request to the handler and
|
||||
stripping of the result body. See the ``akka.http.server.transparent-head-requests`` setting for how to disable
|
||||
this behavior.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#head
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
.. _method-directives-java:
|
||||
|
||||
MethodDirectives
|
||||
================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
delete
|
||||
extractMethod
|
||||
get
|
||||
head
|
||||
method
|
||||
options
|
||||
overrideMethodWithParameter
|
||||
patch
|
||||
post
|
||||
put
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
.. _-method-java-:
|
||||
|
||||
method
|
||||
======
|
||||
|
||||
Matches HTTP requests based on their method.
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
This directive filters the incoming request by its HTTP method. Only requests with
|
||||
the specified method are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#method-example
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
.. _-options-java-:
|
||||
|
||||
options
|
||||
=======
|
||||
|
||||
Matches requests with HTTP method ``OPTIONS``.
|
||||
|
||||
This directive filters the incoming request by its HTTP method. Only requests with
|
||||
method ``OPTIONS`` are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#options
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
.. _-overrideMethodWithParameter-java-:
|
||||
|
||||
overrideMethodWithParameter
|
||||
===========================
|
||||
|
||||
TODO ...
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
TODO sample is missing, also in Scala documentation
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
.. _-patch-java-:
|
||||
|
||||
patch
|
||||
=====
|
||||
|
||||
Matches requests with HTTP method ``PATCH``.
|
||||
|
||||
This directive filters the incoming request by its HTTP method. Only requests with
|
||||
method ``PATCH`` are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#patch
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
.. _-post-java-:
|
||||
|
||||
post
|
||||
====
|
||||
|
||||
Matches requests with HTTP method ``POST``.
|
||||
|
||||
This directive filters the incoming request by its HTTP method. Only requests with
|
||||
method ``POST`` are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#post
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
.. _-put-java-:
|
||||
|
||||
put
|
||||
===
|
||||
|
||||
Matches requests with HTTP method ``PUT``.
|
||||
|
||||
This directive filters the incoming request by its HTTP method. Only requests with
|
||||
method ``PUT`` are passed on to the inner route. All others are rejected with a
|
||||
``MethodRejection``, which is translated into a ``405 Method Not Allowed`` response
|
||||
by the default ``RejectionHandler``.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/MethodDirectivesExamplesTest.java#put
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
.. _path-directives-java:
|
||||
|
||||
PathDirectives
|
||||
==============
|
||||
|
||||
Path directives are the most basic building blocks for routing requests depending on the URI path.
|
||||
|
||||
When a request (or rather the respective ``RequestContext`` instance) enters the route structure it has an
|
||||
"unmatched path" that is identical to the ``request.uri.path``. As it descends the routing tree and passes through one
|
||||
or more ``pathPrefix`` or ``path`` directives the "unmatched path" progressively gets "eaten into" from the
|
||||
left until, in most cases, it eventually has been consumed completely.
|
||||
|
||||
The two main directives are ``path`` and ``pathPrefix``. The ``path`` directive tries to match the complete remaining
|
||||
unmatched path against the specified "path matchers", the ``pathPrefix`` directive only matches a prefix and passes the
|
||||
remaining unmatched path to nested directives. Both directives automatically match a slash from the beginning, so
|
||||
that matching slashes in a hierarchy of nested ``pathPrefix`` and ``path`` directives is usually not needed.
|
||||
|
||||
Path directives take a variable amount of arguments. Each argument must be a ``PathMatcher`` or a string (which is
|
||||
automatically converted to a path matcher using ``PathMatchers.segment``). In the case of ``path`` and ``pathPrefix``,
|
||||
if multiple arguments are supplied, a slash is assumed between any of the supplied path matchers. The ``rawPathX``
|
||||
variants of those directives on the other side do no such preprocessing, so that slashes must be matched manually.
|
||||
|
||||
Path Matchers
|
||||
-------------
|
||||
|
||||
A path matcher is a description of a part of a path to match. The simplest path matcher is ``PathMatcher.segment`` which
|
||||
matches exactly one path segment against the supplied constant string.
|
||||
|
||||
Other path matchers defined in ``PathMatchers`` match the end of the path (``PathMatchers.END``), a single slash
|
||||
(``PathMatchers.SLASH``), or nothing at all (``PathMatchers.NEUTRAL``).
|
||||
|
||||
Many path matchers are hybrids that can both match (by using them with one of the PathDirectives) and extract values,
|
||||
i.e. they are :ref:`request-vals-java`. Extracting a path matcher value (i.e. using it with ``handleWithX``) is only
|
||||
allowed if it nested inside a path directive that uses that path matcher and so specifies at which position the value
|
||||
should be extracted from the path.
|
||||
|
||||
Predefined path matchers allow extraction of various types of values:
|
||||
|
||||
``PathMatchers.segment(String)``
|
||||
Strings simply match themselves and extract no value.
|
||||
Note that strings are interpreted as the decoded representation of the path, so if they include a '/' character
|
||||
this character will match "%2F" in the encoded raw URI!
|
||||
|
||||
``PathMatchers.regex``
|
||||
You can use a regular expression instance as a path matcher, which matches whatever the regex matches and extracts
|
||||
one ``String`` value. A ``PathMatcher`` created from a regular expression extracts either the complete match (if the
|
||||
regex doesn't contain a capture group) or the capture group (if the regex contains exactly one capture group).
|
||||
If the regex contains more than one capture group an ``IllegalArgumentException`` will be thrown.
|
||||
|
||||
``PathMatchers.SLASH``
|
||||
Matches exactly one path-separating slash (``/``) character.
|
||||
|
||||
``PathMatchers.END``
|
||||
Matches the very end of the path, similar to ``$`` in regular expressions.
|
||||
|
||||
``PathMatchers.Segment``
|
||||
Matches if the unmatched path starts with a path segment (i.e. not a slash).
|
||||
If so the path segment is extracted as a ``String`` instance.
|
||||
|
||||
``PathMatchers.Rest``
|
||||
Matches and extracts the complete remaining unmatched part of the request's URI path as an (encoded!) String.
|
||||
If you need access to the remaining *decoded* elements of the path use ``RestPath`` instead.
|
||||
|
||||
``PathMatchers.intValue``
|
||||
Efficiently matches a number of decimal digits (unsigned) and extracts their (non-negative) ``Int`` value. The matcher
|
||||
will not match zero digits or a sequence of digits that would represent an ``Int`` value larger than ``Integer.MAX_VALUE``.
|
||||
|
||||
``PathMatchers.longValue``
|
||||
Efficiently matches a number of decimal digits (unsigned) and extracts their (non-negative) ``Long`` value. The matcher
|
||||
will not match zero digits or a sequence of digits that would represent an ``Long`` value larger than ``Long.MAX_VALUE``.
|
||||
|
||||
``PathMatchers.hexIntValue``
|
||||
Efficiently matches a number of hex digits and extracts their (non-negative) ``Int`` value. The matcher will not match
|
||||
zero digits or a sequence of digits that would represent an ``Int`` value larger than ``Integer.MAX_VALUE``.
|
||||
|
||||
``PathMatchers.hexLongValue``
|
||||
Efficiently matches a number of hex digits and extracts their (non-negative) ``Long`` value. The matcher will not
|
||||
match zero digits or a sequence of digits that would represent an ``Long`` value larger than ``Long.MAX_VALUE``.
|
||||
|
||||
``PathMatchers.uuid``
|
||||
Matches and extracts a ``java.util.UUID`` instance.
|
||||
|
||||
``PathMatchers.NEUTRAL``
|
||||
A matcher that always matches, doesn't consume anything and extracts nothing.
|
||||
Serves mainly as a neutral element in ``PathMatcher`` composition.
|
||||
|
||||
``PathMatchers.segments``
|
||||
Matches all remaining segments as a list of strings. Note that this can also be "no segments" resulting in the empty
|
||||
list. If the path has a trailing slash this slash will *not* be matched, i.e. remain unmatched and to be consumed by
|
||||
potentially nested directives.
|
||||
|
||||
Here's a collection of path matching examples:
|
||||
|
||||
.. includecode:: ../../../code/docs/http/javadsl/server/PathDirectiveExampleTest.java
|
||||
:include: path-examples
|
||||
142
akka-docs/rst/java/http/routing-dsl/handlers.rst
Normal file
142
akka-docs/rst/java/http/routing-dsl/handlers.rst
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
.. _handlers-java:
|
||||
|
||||
Handlers
|
||||
========
|
||||
|
||||
Handlers implement the actual application-defined logic for a certain trace in the routing tree. Most of the leaves of
|
||||
the routing tree will be routes created from handlers. Creating a ``Route`` from a handler is achieved using the
|
||||
``BasicDirectives.handleWith`` overloads. They come in several forms:
|
||||
|
||||
* with a single ``Handler`` argument and a variable number of ``RequestVal<?>`` (may be 0)
|
||||
* with a number ``n`` of ``RequestVal<T1>`` arguments and a ``HandlerN<T1, .., TN>`` argument
|
||||
* with a ``Class<?>`` and/or instance and a method name String argument and a variable number of ``RequestVal<?>`` (may be 0)
|
||||
arguments
|
||||
|
||||
Simple Handler
|
||||
--------------
|
||||
|
||||
In its simplest form a ``Handler`` is a SAM class that defines application behavior
|
||||
by inspecting the ``RequestContext`` and returning a ``RouteResult``:
|
||||
|
||||
.. includecode:: /../../akka-http/src/main/scala/akka/http/javadsl/server/Handler.scala
|
||||
:include: handler
|
||||
|
||||
Such a handler inspects the ``RequestContext`` it receives and uses the ``RequestContext``'s methods to
|
||||
create a response:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: simple-handler
|
||||
|
||||
The handler can include any kind of logic but must return a ``RouteResult`` in the end which can only
|
||||
be created by using one of the ``RequestContext`` methods.
|
||||
|
||||
A handler instance can be used once or several times as shown in the full example:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: simple-handler-example-full
|
||||
|
||||
Handlers and Request Values
|
||||
---------------------------
|
||||
|
||||
In many cases, instead of manually inspecting the request, a handler will make use of :ref:`request-vals-java`
|
||||
to extract details from the request. This is possible using one of the other ``handleWith`` overloads that bind
|
||||
the values of one or more request values with a ``HandlerN`` instance to produce a ``Route``:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: handler2
|
||||
|
||||
The handler here implements multiplication of two integers. However, it doesn't need to specify where these
|
||||
parameters come from. In ``handleWith``, as many request values of the matching type have to be specified as the
|
||||
handler needs. This can be seen in the full example:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: handler2-example-full
|
||||
|
||||
Here, the handler is again being reused. First, in creating a route that expects URI parameters ``x`` and ``y``. This
|
||||
route is then used in the route structure. And second, the handler is used with another set of ``RequestVal`` in the
|
||||
route structure, this time representing segments from the URI path.
|
||||
|
||||
Handlers in Java 8
|
||||
------------------
|
||||
|
||||
Handlers are in fact simply classes which extend ``akka.japi.function.FunctionN`` in order to make reasoning
|
||||
about the number of handled arguments easier. For example, a :class:`Handler1[String]` is simply a
|
||||
``Function2[RequestContext, String, RouteResult]``. You can think of handlers as hot-dogs, where each ``T``
|
||||
type represents a sausage, put between the "buns" which are ``RequestContext`` and ``RouteResult``.
|
||||
|
||||
In Java 8 handlers can be provided as function literals or method references. The previous example can then be written
|
||||
like this:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: handler2-java8-example-full
|
||||
|
||||
|
||||
.. note::
|
||||
The reason the ``handleWith##`` methods include the number of handled values is because otherwise (if overloading would
|
||||
be used, for all 22 methods) error messages generated by ``javac`` end up being very long and not readable, i.e.
|
||||
if one type of a handler does not match the given values, *all* possible candidates would be printed in the error message
|
||||
(22 of them), instead of just the one arity-matching method, pointing out that the type does not match.
|
||||
|
||||
We opted for better error messages as we feel this is more helpful when developing applications,
|
||||
instead of having one overloaded method which looks nice when everything works, but procudes hard to read error
|
||||
messages if something does not match up.
|
||||
|
||||
Providing Handlers by Reflection
|
||||
--------------------------------
|
||||
|
||||
Using Java before Java 8, writing out handlers as (anonymous) classes can be unwieldy. Therefore, ``handleReflectively``
|
||||
overloads are provided that allow writing handler as simple methods and specifying them by name:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: reflective
|
||||
|
||||
The complete calculator example can then be written like this:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: reflective-example-full
|
||||
|
||||
There are alternative overloads for ``handleReflectively`` that take a ``Class`` instead of an object instance to refer to
|
||||
static methods. The referenced method must be publicly accessible.
|
||||
|
||||
Deferring Result Creation
|
||||
-------------------------
|
||||
|
||||
Sometimes a handler cannot directly complete the request but needs to do some processing asynchronously. In this case
|
||||
the completion of a request needs to be deferred until the result has been generated. This is supported by the routing
|
||||
DSL in two ways: either you can use one of the ``handleWithAsyncN`` methods passing an ``AsyncHandlerN`` which
|
||||
returns a ``Future<RouteResult>``, i.e. an eventual ``RouteResult``, or you can also use a regular handler as shown
|
||||
above and use ``RequestContext.completeWith`` for completion which takes an ``Future<RouteResult>`` as an argument.
|
||||
|
||||
This is demonstrated in the following example. Consider a asynchronous service defined like this
|
||||
(making use of Java 8 lambdas):
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: async-service-definition
|
||||
|
||||
Here the calculator runs the actual calculation in the background and only eventually returns the result. The HTTP
|
||||
service should provide a front-end to that service without having to block while waiting for the results. As explained
|
||||
above this can be done in two ways.
|
||||
|
||||
First, you can use ``handleWithAsyncN`` to be able to return a ``Future<RouteResult>``:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: async-handler-1
|
||||
|
||||
The handler invokes the service and then maps the calculation result to a ``RouteResult`` using ``Future.map`` and
|
||||
returns the resulting ``Future<RouteResult>``.
|
||||
|
||||
Otherwise, you can also still use ``handleWithN`` and use ``RequestContext.completeWith`` to "convert" a
|
||||
``Future<RouteResult>`` into a ``RouteResult`` as shown here:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: async-handler-2
|
||||
|
||||
Using this style, you can decide in your handler if you want to return a direct synchronous result or if you need
|
||||
to defer completion.
|
||||
|
||||
Both alternatives will not block and show the same runtime behavior.
|
||||
|
||||
Here's the complete example:
|
||||
|
||||
.. includecode:: /../../akka-http-tests/src/test/java/docs/http/javadsl/server/HandlerExampleDocTest.java
|
||||
:include: async-example-full
|
||||
18
akka-docs/rst/java/http/routing-dsl/index.rst
Normal file
18
akka-docs/rst/java/http/routing-dsl/index.rst
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
.. _http-high-level-server-side-api-java:
|
||||
|
||||
High-level Server-Side API
|
||||
==========================
|
||||
|
||||
To use the high-level API you need to add a dependency to the ``akka-http-experimental`` module.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
overview
|
||||
routes
|
||||
directives/index
|
||||
request-vals/index
|
||||
handlers
|
||||
marshalling
|
||||
testkit
|
||||
json-support
|
||||
31
akka-docs/rst/java/http/routing-dsl/json-support.rst
Normal file
31
akka-docs/rst/java/http/routing-dsl/json-support.rst
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
.. _json-support-java:
|
||||
|
||||
Json Support
|
||||
============
|
||||
|
||||
akka-http provides support to convert application-domain objects from and to JSON using jackson_ in an
|
||||
extra artifact.
|
||||
|
||||
Integration with other JSON libraries may be supported by the community.
|
||||
See `the list of current community extensions for Akka HTTP`_.
|
||||
|
||||
.. _`the list of current community extensions for Akka HTTP`: http://akka.io/community/#extensions-to-akka-http
|
||||
|
||||
.. _json-jackson-support-java:
|
||||
|
||||
Json Support via Jackson
|
||||
------------------------
|
||||
|
||||
To make use of the support module, you need to add a dependency on `akka-http-jackson-experimental`.
|
||||
|
||||
Use ``akka.http.javadsl.marshallers.jackson.Jackson.jsonAs[T]`` to create a ``RequestVal<T>`` which expects the request
|
||||
body to be of type ``application/json`` and converts it to ``T`` using Jackson.
|
||||
|
||||
See `this example`__ in the sources for an example.
|
||||
|
||||
Use ``akka.http.javadsl.marshallers.jackson.Jackson.json[T]`` to create a ``Marshaller<T>`` which can be used with
|
||||
``RequestContext.completeAs`` to convert a POJO to an HttpResponse.
|
||||
|
||||
|
||||
.. _jackson: https://github.com/FasterXML/jackson
|
||||
__ @github@/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreExample.java
|
||||
43
akka-docs/rst/java/http/routing-dsl/marshalling.rst
Normal file
43
akka-docs/rst/java/http/routing-dsl/marshalling.rst
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
.. _marshalling-java:
|
||||
|
||||
Marshalling & Unmarshalling
|
||||
===========================
|
||||
|
||||
"Marshalling" is the process of converting a higher-level (object) structure into some kind of lower-level
|
||||
representation (and vice versa), often a binary wire format. Other popular names for it are "Serialization" or
|
||||
"Pickling".
|
||||
|
||||
In akka-http "Marshalling" means the conversion of an object of type T into an HttpEntity, which forms the entity body
|
||||
of an HTTP request or response (depending on whether used on the client or server side).
|
||||
|
||||
Marshalling
|
||||
-----------
|
||||
|
||||
On the server-side marshalling is used to convert a application-domain object to a response (entity). Requests can
|
||||
contain an ``Accept`` header that lists acceptable content types for the client. A marshaller contains the logic to
|
||||
negotiate the result content types based on the ``Accept`` and the ``AcceptCharset`` headers.
|
||||
|
||||
Marshallers can be specified when completing a request with ``RequestContext.completeAs`` or by using the ``BasicDirectives.completeAs``
|
||||
directives.
|
||||
|
||||
These marshallers are provided by akka-http:
|
||||
|
||||
* Use :ref:`json-jackson-support-java` to create an marshaller that can convert a POJO to an ``application/json``
|
||||
response using jackson_.
|
||||
* Use ``Marshallers.toEntityString``, ``Marshallers.toEntityBytes``, ``Marshallers.toEntityByteString``,
|
||||
``Marshallers.toEntity``, and ``Marshallers.toResponse`` to create custom marshallers.
|
||||
|
||||
Unmarshalling
|
||||
-------------
|
||||
|
||||
On the server-side unmarshalling is used to convert a request (entity) to a application-domain object. This means
|
||||
unmarshalling to a certain type is represented by a ``RequestVal``. Currently, several options are provided to create
|
||||
an unmarshalling ``RequestVal``:
|
||||
|
||||
* Use :ref:`json-jackson-support-java` to create an unmarshaller that can convert an ``application/json`` request
|
||||
to a POJO using jackson_.
|
||||
* Use the predefined ``Unmarshallers.String``, ``Unmarshallers.ByteString``, ``Unmarshallers.ByteArray``,
|
||||
``Unmarshallers.CharArray`` to convert to those basic types.
|
||||
* Use ``Unmarshallers.fromMessage`` or ``Unmarshaller.fromEntity`` to create a custom unmarshaller.
|
||||
|
||||
.. _jackson: https://github.com/FasterXML/jackson
|
||||
100
akka-docs/rst/java/http/routing-dsl/overview.rst
Normal file
100
akka-docs/rst/java/http/routing-dsl/overview.rst
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
.. _http-routing-java:
|
||||
|
||||
Routing DSL Overview
|
||||
====================
|
||||
|
||||
The Akka HTTP :ref:`http-low-level-server-side-api-java` provides a ``Flow``- or ``Function``-level interface that allows
|
||||
an application to respond to incoming HTTP requests by simply mapping requests to responses
|
||||
(excerpt from :ref:`Low-level server side example <http-low-level-server-side-example-java>`):
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/server/HttpServerExampleDocTest.java
|
||||
:include: request-handler
|
||||
|
||||
While it'd be perfectly possible to define a complete REST API service purely by inspecting the incoming
|
||||
``HttpRequest`` this approach becomes somewhat unwieldy for larger services due to the amount of syntax "ceremony"
|
||||
required. Also, it doesn't help in keeping your service definition as DRY_ as you might like.
|
||||
|
||||
As an alternative Akka HTTP provides a flexible DSL for expressing your service behavior as a structure of
|
||||
composable elements (called :ref:`directives-java`) in a concise and readable way. Directives are assembled into a so called
|
||||
*route structure* which, at its top-level, can be used to create a handler ``Flow`` (or, alternatively, an
|
||||
async handler function) that can be directly supplied to a ``bind`` call.
|
||||
|
||||
Here's the complete example rewritten using the composable high-level API:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/server/HighLevelServerExample.java
|
||||
:include: high-level-server-example
|
||||
|
||||
Heart of the high-level architecture is the route tree. It is a big expression of type ``Route``
|
||||
that is evaluated only once during startup time of your service. It completely describes how your service
|
||||
should react to any request.
|
||||
|
||||
The type ``Route`` is the basic building block of the route tree. It defines if and a how a request should
|
||||
be handled. Routes are composed to form the route tree in the following two ways.
|
||||
|
||||
A route can be wrapped by a "Directive" which adds some behavioral aspect to its wrapped "inner route". ``path("ping")`` is such
|
||||
a directive that implements a path filter, i.e. it only passes control to its inner route when the unmatched path
|
||||
matches ``"ping"``. Directives can be more versatile than this: A directive can also transform the request before
|
||||
passing it into its inner route or transform a response that comes out of its inner route. It's a general and powerful
|
||||
abstraction that allows to package any kind of HTTP processing into well-defined blocks that can be freely combined.
|
||||
akka-http defines a library of predefined directives and routes for all the various aspects of dealing with
|
||||
HTTP requests and responses.
|
||||
|
||||
Read more about :ref:`directives-java`.
|
||||
|
||||
The other way of composition is defining a list of ``Route`` alternatives. Alternative routes are tried one after
|
||||
the other until one route "accepts" the request and provides a response. Otherwise, a route can also "reject" a request,
|
||||
in which case further alternatives are explored. Alternatives are specified by passing a list of routes either
|
||||
to ``Directive.route()`` as in ``pathSingleSlash().route()`` or to directives that directly take a variable number
|
||||
of inner routes as argument like ``get()`` here.
|
||||
|
||||
Read more about :ref:`routes-java`.
|
||||
|
||||
Another important building block is a ``RequestVal<T>``. It represents a value that can be extracted from a
|
||||
request (like the URI parameter ``Parameters.stringValue("name")`` in the example) and which is then interpreted
|
||||
as a value of type ``T``. Examples of HTTP aspects represented by a ``RequestVal`` are URI parameters, HTTP form
|
||||
fields, details of the request like headers, URI, the entity, or authentication data.
|
||||
|
||||
Read more about :ref:`request-vals-java`.
|
||||
|
||||
The actual application-defined processing of a request is defined with a ``Handler`` instance or by specifying
|
||||
a handling method with reflection. A handler can receive the value of any request values and is converted into
|
||||
a ``Route`` by using one of the ``BasicDirectives.handleWith`` directives.
|
||||
|
||||
Read more about :ref:`handlers-java`.
|
||||
|
||||
Requests or responses often contain data that needs to be interpreted or rendered in some way.
|
||||
Akka-http provides the abstraction of ``Marshaller`` and ``Unmarshaller`` that define how domain model objects map
|
||||
to HTTP entities.
|
||||
|
||||
Read more about :ref:`marshalling-java`.
|
||||
|
||||
akka-http contains a testkit that simplifies testing routes. It allows to run test-requests against (sub-)routes
|
||||
quickly without running them over the network and helps with writing assertions on HTTP response properties.
|
||||
|
||||
Read more about :ref:`http-testkit-java`.
|
||||
|
||||
.. _DRY: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself
|
||||
|
||||
.. _handling-http-server-failures-high-level-java:
|
||||
|
||||
Handling HTTP Server failures in the High-Level API
|
||||
---------------------------------------------------
|
||||
There are various situations when failure may occur while initialising or running an Akka HTTP server.
|
||||
Akka by default will log all these failures, however sometimes one may want to react to failures in addition
|
||||
to them just being logged, for example by shutting down the actor system, or notifying some external monitoring
|
||||
end-point explicitly.
|
||||
|
||||
Bind failures
|
||||
^^^^^^^^^^^^^
|
||||
For example the server might be unable to bind to the given port. For example when the port
|
||||
is already taken by another application, or if the port is privileged (i.e. only usable by ``root``).
|
||||
In this case the "binding future" will fail immediatly, and we can react to if by listening on the Future's completion:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/server/HighLevelServerBindFailureExample.java
|
||||
:include: binding-failure-high-level-example
|
||||
|
||||
|
||||
.. note::
|
||||
For a more low-level overview of the kinds of failures that can happen and also more fine-grained control over them
|
||||
refer to the :ref:`handling-http-server-failures-low-level-java` documentation.
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
.. _form-field-request-vals-java:
|
||||
|
||||
Request Values: FormFields
|
||||
==========================
|
||||
|
||||
A collection of pre-defined :ref:`request-vals-java` that can be used to extract header values from incoming requests.
|
||||
|
||||
Description
|
||||
-----------
|
||||
``FormField`` request values allow extracting fields submitted as ``application/x-www-form-urlencoded`` values or concrete instances from HTTP requests.
|
||||
|
||||
The ``FormField`` request value builder is made up of 2 steps, initially you need to pick which what type of value you
|
||||
want to extract from the field (for example ``intValue``, which would reject the route if the value is not an ``int``),
|
||||
and then **optionally** you may specify if the value is optional (by calling ``optional()`` on the ``RequestVal``)
|
||||
or has a default value (by calling ``withDefault()`` on the ``RequestVal``).
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Extracting form fields of a certain primitive type from a request:
|
||||
|
||||
.. includecode:: ../../../code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java#simple
|
||||
|
||||
Extracting values of custom type from a request by providing a conversion function:
|
||||
|
||||
.. includecode:: ../../../code/docs/http/javadsl/server/FormFieldRequestValsExampleTest.java#custom-unmarshal
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
.. _header-request-vals-java:
|
||||
|
||||
Request Values: Headers
|
||||
=======================
|
||||
|
||||
A collection of pre-defined :ref:`request-vals-java` that can be used to extract header values from incoming requests.
|
||||
|
||||
Description
|
||||
-----------
|
||||
Header request values allow extracting ``HttpHeader`` values or concrete instances from HTTP requests.
|
||||
|
||||
The ``RequestVal`` builder is made up of 2 steps, initially you need to pick which Header to extract (``byName`` or
|
||||
``byClass``) and then you need to pick if the header is optionally available or required (i.e. the route should not
|
||||
match if the header is not present in the request). This is done using one of the below depicted methods::
|
||||
|
||||
RequestVal<T> instance()
|
||||
RequestVal<<Option<T>> optionalInstance()
|
||||
|
||||
RequestVal<String> value()
|
||||
RequestVal<Option<String>> optionalValue()
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Extracting a header by using a specific ``Header`` class (which are pre-defined in ``akka.http.javadsl.model.headers.*``):
|
||||
|
||||
.. includecode:: ../../../code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java
|
||||
:include: by-class
|
||||
|
||||
Extracting arbitrary headers by their name, for example custom headers (usually starting with ``X-...``):
|
||||
|
||||
.. includecode:: ../../../code/docs/http/javadsl/server/HeaderRequestValsExampleTest.java
|
||||
:include: by-name
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue