+doc #19429 initial merge of docs-dev and docs

This commit is contained in:
Konrad Malawski 2016-01-13 16:25:24 +01:00
parent be0c8af4c0
commit 5a18d43435
501 changed files with 9876 additions and 3681 deletions

View 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 actors 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
// dont 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)));
}
}
}

View 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

View file

@ -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");
}};
}
}

View 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;
}
}
}

View 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

View 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

View 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();
}};
}
}

View 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

View 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

View 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
}

View 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/"));
}};
}
}

View file

@ -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

View file

@ -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
}

View 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
}

View file

@ -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;
}
}
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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

View file

@ -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
}
}

View file

@ -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();
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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()));
}
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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

View file

@ -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

View 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");
}
};
}
}

View 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);
}
};
}
}

View 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
}
}

View 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
}
}

View 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
}
}

View 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)));
}
}

View 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
}
}

View 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
}
}

View 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();
}
}

View 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
}
}

View 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();
}
}

View 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"));
}
};
}
}

View 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
}
}

View file

@ -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));
}
}

View 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");
}
}

View 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));
}
}
}
}

View file

@ -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
}
}

View file

@ -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);
}
}

View 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
}
}

View file

@ -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
}
}

View file

@ -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();
}
}
}

View 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");
}
}

View file

@ -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());
}
}
};
}
}

View file

@ -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);
}
};
}
}

View file

@ -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
}
};
}
}

View file

@ -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"));
}
};
}
}

View file

@ -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();
}
};
}
}

View file

@ -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();
}
};
}
}

View file

@ -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
}
};
}
}

View file

@ -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);
}
};
}
}

View file

@ -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();
}
};
}
}

View file

@ -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();
}
};
}
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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);
}
};
}
}

View file

@ -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();
}
};
}
}

View file

@ -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 {
}
}

View file

@ -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));
}
};
}
}

View file

@ -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"));
}
};
}
}

View 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");
}
}

View 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.

View 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

View 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

View 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

View 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

View file

@ -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

View 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

View 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.

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,18 @@
.. _method-directives-java:
MethodDirectives
================
.. toctree::
:maxdepth: 1
delete
extractMethod
get
head
method
options
overrideMethodWithParameter
patch
post
put

View file

@ -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

View file

@ -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

View file

@ -0,0 +1,11 @@
.. _-overrideMethodWithParameter-java-:
overrideMethodWithParameter
===========================
TODO ...
Example
-------
TODO sample is missing, also in Scala documentation

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View 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

View 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

View 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

View 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.

View file

@ -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

View file

@ -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