├── .gitignore ├── README.md ├── akka-http-example └── src │ └── main │ ├── resources │ └── example.conf │ └── scala │ ├── HttpClient.scala │ ├── HttpServer.scala │ ├── Main.scala │ ├── PrintDataSubscriber.scala │ ├── RandomDataPublisher.scala │ └── StreamClientPublisher.scala ├── akka-stream-example └── src │ └── main │ ├── resources │ └── application.conf │ └── scala │ ├── DoublingProcessor.scala │ ├── FibanacciSubscriber.scala │ ├── FibonacciPublisher.scala │ └── Main.scala ├── client.conf ├── project ├── Build.scala └── plugins.sbt ├── server-client.conf └── server.conf /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | *.log 3 | 4 | # sbt specific 5 | .cache/ 6 | .history/ 7 | .lib/ 8 | dist/* 9 | target/ 10 | lib_managed/ 11 | src_managed/ 12 | project/boot/ 13 | project/plugins/project/ 14 | 15 | # Scala-IDE specific 16 | .scala_dependencies 17 | .worksheet 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | akka-http-stream-example 2 | ======================== 3 | 4 | An example program that uses the akka-http stream api 5 | 6 | Running 7 | ------- 8 | 9 | Stream Example 10 | 11 | $ sbt akka-stream-example/run 12 | 13 | Http Example 14 | 15 | $ sbt akka-http-example/run -------------------------------------------------------------------------------- /akka-http-example/src/main/resources/example.conf: -------------------------------------------------------------------------------- 1 | example.publisher = "random" # random | client 2 | example.subscriber = "server" # server | print 3 | example.server.port = 8080 4 | example.client.port = 8081 5 | example.delay = 100 -------------------------------------------------------------------------------- /akka-http-example/src/main/scala/HttpClient.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import scala.concurrent.Future 4 | import scala.concurrent.duration._ 5 | import scala.async.Async.{async, await} 6 | 7 | import akka.io.IO 8 | import akka.util.Timeout 9 | import akka.http.Http 10 | import akka.actor.{ActorSystem} 11 | import akka.http.model.{HttpMethods,HttpEntity,HttpRequest,HttpResponse,Uri} 12 | import akka.stream.{FlowMaterializer} 13 | import akka.stream.scaladsl.Flow 14 | import akka.pattern.ask 15 | 16 | import HttpEntity._ 17 | 18 | object HttpClient { 19 | implicit val askTimeout: Timeout = 1000.millis 20 | 21 | def makeRequest(port: Int, path: String)(implicit system: ActorSystem, materializer: FlowMaterializer): Future[HttpResponse] = { 22 | implicit val ec = system.dispatcher 23 | 24 | async { 25 | val connFuture = IO(Http).ask(Http.Connect("127.0.0.1", port)).mapTo[Http.OutgoingConnection] 26 | val connection = await(connFuture) 27 | val request = HttpRequest(HttpMethods.GET, Uri(path)) 28 | Flow(List(request -> 'NoContext)).produceTo(connection.processor) 29 | await(Flow(connection.processor).map(_._1).toFuture) 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /akka-http-example/src/main/scala/HttpServer.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import scala.concurrent.duration._ 4 | 5 | import akka.actor.{ActorSystem} 6 | import akka.util.{Timeout} 7 | import akka.pattern.ask 8 | import akka.io.IO 9 | import akka.http.Http 10 | import akka.http.model.{HttpRequest,HttpResponse} 11 | import akka.stream.scaladsl.Flow 12 | import akka.stream.{FlowMaterializer} 13 | 14 | object HttpServer { 15 | implicit val askTimeout: Timeout = 1000.millis 16 | 17 | def bindServer(port: Int)(handler: (HttpRequest) => HttpResponse)(implicit system: ActorSystem, materializer: FlowMaterializer) { 18 | implicit val ec = system.dispatcher 19 | val bindingFuture = IO(Http) ? Http.Bind(interface = "localhost", port = port) 20 | bindingFuture foreach { 21 | case Http.ServerBinding(localAddress, connectionStream) => 22 | Flow(connectionStream).foreach({ 23 | case Http.IncomingConnection(remoteAddress, requestProducer, responseConsumer) => 24 | println("Accepted new connection from " + remoteAddress) 25 | Flow(requestProducer).map(handler).produceTo(responseConsumer) 26 | }) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /akka-http-example/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import java.io.File 4 | import akka.util.ByteString 5 | import akka.actor.{ActorSystem,ActorRefFactory} 6 | import akka.stream.actor.{ActorPublisher} 7 | import akka.stream.{MaterializerSettings,FlowMaterializer} 8 | import akka.http.model._ 9 | import com.typesafe.config._ 10 | import org.reactivestreams.{Publisher,Subscriber} 11 | 12 | import HttpMethods._ 13 | import HttpEntity._ 14 | import MainFunctions._ 15 | 16 | object Main extends App { 17 | 18 | implicit val system = ActorSystem() 19 | implicit val materializer = FlowMaterializer(MaterializerSettings(system)) 20 | val config = ConfigFactory.parseFile(new File(args(0))) 21 | 22 | val publisher: Publisher[ChunkStreamPart] = config.getString("example.publisher") match { 23 | case "random" => RandomDataPublisher() 24 | case "client" => StreamClientPublisher(config.getInt("example.client.port")) 25 | } 26 | 27 | config.getString("example.subscriber") match { 28 | case "server" => startStreamServerWithPublisher(publisher, config.getInt("example.server.port")) 29 | case "print" => startPrintSubscriber(publisher, config.getLong("example.delay")) 30 | } 31 | } 32 | 33 | object MainFunctions { 34 | 35 | def startStreamServerWithPublisher(publisher: Publisher[ChunkStreamPart], port: Int)(implicit system: ActorSystem, materializer: FlowMaterializer) = { 36 | HttpServer.bindServer(port) { 37 | case HttpRequest(GET, Uri.Path("/"), _, _, _) => 38 | HttpResponse ( 39 | entity = new Chunked(MediaTypes.`text/plain`, publisher) 40 | ) 41 | case _: HttpRequest => HttpResponse(404, entity = "Unknown resource!") 42 | } 43 | } 44 | 45 | def startPrintSubscriber(publisher: Publisher[ChunkStreamPart], delay: Long)(implicit arf: ActorRefFactory) = { 46 | val subscriber = PrintDataSubscriber(delay) 47 | publisher.subscribe(subscriber) 48 | } 49 | } -------------------------------------------------------------------------------- /akka-http-example/src/main/scala/PrintDataSubscriber.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import akka.util.ByteString 4 | import akka.actor.{ActorLogging,ActorRefFactory,Props} 5 | import akka.stream.actor.{ActorSubscriber,ActorSubscriberMessage,WatermarkRequestStrategy} 6 | import akka.http.model.HttpEntity 7 | import org.reactivestreams.Subscriber 8 | 9 | import HttpEntity._ 10 | 11 | object PrintDataSubscriber { 12 | def apply(delay: Long)(implicit arf: ActorRefFactory): Subscriber[ChunkStreamPart] = { 13 | ActorSubscriber[ChunkStreamPart](arf.actorOf(Props(new PrintDataSubscriber(delay)))) 14 | } 15 | } 16 | 17 | import ActorSubscriberMessage._ 18 | 19 | class PrintDataSubscriber(delay: Long) extends ActorSubscriber with ActorLogging { 20 | log.info("Data Chunk Stream Subscription Started") 21 | val requestStrategy = WatermarkRequestStrategy(50) 22 | 23 | def receive = { 24 | case OnNext(chunk: ChunkStreamPart) => 25 | log.info("Received Bytes ({})", chunk.data.length) 26 | if(delay > 0) { Thread.sleep(delay) } 27 | case OnComplete => log.info("Data Chunk Stream Completed") 28 | case OnError(err) => log.error(err, "Data Chunk Stream Error") 29 | case _ => 30 | } 31 | } -------------------------------------------------------------------------------- /akka-http-example/src/main/scala/RandomDataPublisher.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import java.util.Random 4 | import scala.concurrent.duration._ 5 | 6 | import akka.actor.{ActorLogging,ActorRefFactory,Props} 7 | import akka.util.{ByteString} 8 | import akka.stream.actor.{ActorPublisher,ActorPublisherMessage} 9 | import akka.http.model.HttpEntity 10 | import org.reactivestreams.Publisher 11 | 12 | import ActorPublisherMessage._ 13 | import HttpEntity._ 14 | 15 | object RandomDataPublisher { 16 | def apply()(implicit arf: ActorRefFactory): Publisher[ChunkStreamPart] = { 17 | ActorPublisher[ChunkStreamPart](arf.actorOf(Props[RandomDataPublisher])) 18 | } 19 | } 20 | 21 | class RandomDataPublisher extends ActorPublisher[ChunkStreamPart] with ActorLogging { 22 | implicit val ec = context.dispatcher 23 | var count = 0 24 | val random = new Random() 25 | 26 | log.info("Starting Data Publisher") 27 | 28 | def receive = { 29 | case Request(cnt) => sendDataChunks(cnt) 30 | case Cancel => context.stop(self) 31 | case _ => 32 | } 33 | 34 | def sendDataChunks(cnt: Long) { 35 | for(i <- 1L to cnt) { 36 | if(isActive && totalDemand > 0) { 37 | count += 1 38 | println(s"Sending Data! ($count)") 39 | onNext(generateDataChunk()) 40 | } 41 | } 42 | } 43 | 44 | def generateDataChunk(): ChunkStreamPart = { 45 | val b = new Array[Byte](1024) 46 | random.nextBytes(b) 47 | ChunkStreamPart(ByteString(b)) 48 | } 49 | } -------------------------------------------------------------------------------- /akka-http-example/src/main/scala/StreamClientPublisher.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import scala.collection.mutable.{Queue => MQueue} 4 | import scala.concurrent.{Future,Await} 5 | import scala.concurrent.duration._ 6 | import scala.async.Async.{async, await} 7 | 8 | import akka.util.{ByteString} 9 | import akka.actor.{ActorSystem,ActorLogging,Props} 10 | import akka.stream.{FlowMaterializer,MaterializerSettings} 11 | import akka.stream.actor._ 12 | import akka.http.model.HttpEntity 13 | import org.reactivestreams.Publisher 14 | 15 | import HttpEntity._ 16 | 17 | object StreamClientPublisher { 18 | 19 | def apply(port: Int)(implicit system: ActorSystem, materializer: FlowMaterializer): Publisher[ChunkStreamPart] = { 20 | implicit val ec = system.dispatcher 21 | val publisherFuture = async { 22 | val response = await(HttpClient.makeRequest(port, "/")) 23 | val processor = system.actorOf(Props[DataChunkProcessor]) 24 | val processorSubscriber = ActorSubscriber[ByteString](processor) 25 | val processorPublisher = ActorPublisher[ChunkStreamPart](processor) 26 | response.entity.dataBytes(materializer).subscribe(processorSubscriber) 27 | processorPublisher 28 | } 29 | Await.result(publisherFuture, 1.seconds) 30 | } 31 | } 32 | 33 | import ActorSubscriberMessage._ 34 | import ActorPublisherMessage._ 35 | 36 | class DataChunkProcessor extends ActorSubscriber with ActorPublisher[ChunkStreamPart] with ActorLogging { 37 | log.info("Data Chunk Stream Subscription Started") 38 | val queue = MQueue[ByteString]() 39 | var requested = 0L 40 | 41 | def receive = { 42 | case Request(cnt) => requested += cnt; sendDataChunks() 43 | case Cancel => onComplete(); context.stop(self) 44 | case OnNext(bytes: ByteString) => queue.enqueue(bytes); sendDataChunks() 45 | case OnComplete => onComplete() 46 | case OnError(err) => onError(err) 47 | case _ => 48 | } 49 | 50 | def sendDataChunks() { 51 | while(requested > 0 && queue.nonEmpty && isActive && totalDemand > 0) { 52 | println("Sending Data Chunk -- DataChunkProcessor") 53 | onNext(ChunkStreamPart(queue.dequeue())) 54 | requested -= 1 55 | } 56 | } 57 | 58 | val requestStrategy = new MaxInFlightRequestStrategy(50) { 59 | def inFlightInternally(): Int = { println("In flight internally: " + queue.size); queue.size } 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /akka-stream-example/src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | loglevel = DEBUG 3 | } 4 | 5 | -------------------------------------------------------------------------------- /akka-stream-example/src/main/scala/DoublingProcessor.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import java.math.BigInteger 4 | import scala.collection.mutable.{Queue => MQueue} 5 | import akka.actor._ 6 | import akka.stream.actor._ 7 | 8 | import ActorPublisherMessage._ 9 | import ActorSubscriberMessage._ 10 | 11 | class DoublingProcessor extends ActorSubscriber with ActorPublisher[BigInteger] { 12 | val dos = BigInteger.valueOf(2L) 13 | val doubledQueue = MQueue[BigInteger]() 14 | 15 | def receive = { 16 | case OnNext(biggie: BigInteger) => 17 | doubledQueue.enqueue(biggie.multiply(dos)) 18 | sendDoubled() 19 | case OnError(err: Exception) => 20 | onError(err) 21 | context.stop(self) 22 | case OnComplete => 23 | onComplete() 24 | context.stop(self) 25 | case Request(cnt) => 26 | sendDoubled() 27 | case Cancel => 28 | cancel() 29 | context.stop(self) 30 | case _ => 31 | } 32 | 33 | def sendDoubled() { 34 | while(isActive && totalDemand > 0 && !doubledQueue.isEmpty) { 35 | onNext(doubledQueue.dequeue()) 36 | } 37 | } 38 | 39 | val requestStrategy = new MaxInFlightRequestStrategy(50) { 40 | def inFlightInternally(): Int = { doubledQueue.size } 41 | } 42 | } -------------------------------------------------------------------------------- /akka-stream-example/src/main/scala/FibanacciSubscriber.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import java.math.BigInteger 4 | import akka.actor._ 5 | import akka.stream.actor._ 6 | 7 | import ActorSubscriberMessage._ 8 | 9 | class FibonacciSubscriber(delay: Long) extends ActorSubscriber with ActorLogging { 10 | val requestStrategy = WatermarkRequestStrategy(50) 11 | 12 | def receive = { 13 | case OnNext(fib: BigInteger) => 14 | log.debug("[FibonacciSubscriber] Received Fibonacci Number: {}", fib) 15 | Thread.sleep(delay) 16 | case OnError(err: Exception) => 17 | log.error(err, "[FibonacciSubscriber] Receieved Exception in Fibonacci Stream") 18 | context.stop(self) 19 | case OnComplete => 20 | log.info("[FibonacciSubscriber] Fibonacci Stream Completed!") 21 | context.stop(self) 22 | case _ => 23 | } 24 | } -------------------------------------------------------------------------------- /akka-stream-example/src/main/scala/FibonacciPublisher.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import java.math.BigInteger 4 | import akka.actor._ 5 | import akka.stream.actor._ 6 | 7 | import ActorPublisherMessage._ 8 | 9 | class FibonacciPublisher extends ActorPublisher[BigInteger] with ActorLogging { 10 | var prev = BigInteger.ZERO 11 | var curr = BigInteger.ZERO 12 | 13 | def receive = { 14 | case Request(cnt) => 15 | log.debug("[FibonacciPublisher] Received Request ({}) from Subscriber", cnt) 16 | sendFibs() 17 | case Cancel => 18 | log.info("[FibonacciPublisher] Cancel Message Received -- Stopping") 19 | context.stop(self) 20 | case _ => 21 | } 22 | 23 | def sendFibs() { 24 | while(isActive && totalDemand > 0) { 25 | onNext(nextFib()) 26 | } 27 | } 28 | 29 | def nextFib(): BigInteger = { 30 | if(curr == BigInteger.ZERO) { 31 | curr = BigInteger.ONE 32 | } else { 33 | val tmp = prev.add(curr) 34 | prev = curr 35 | curr = tmp 36 | } 37 | curr 38 | } 39 | } -------------------------------------------------------------------------------- /akka-stream-example/src/main/scala/Main.scala: -------------------------------------------------------------------------------- 1 | package example 2 | 3 | import java.math.BigInteger 4 | import akka.actor._ 5 | import akka.stream.actor._ 6 | 7 | object Main extends App { 8 | val system = ActorSystem("example-stream-system") 9 | 10 | Examples.startSimplePubSubExample(system) 11 | Examples.startPubSubTransformerExample(system) 12 | } 13 | 14 | object Examples { 15 | 16 | def startSimplePubSubExample(system: ActorSystem) { 17 | system.log.info("Starting Publisher") 18 | val publisherActor = system.actorOf(Props[FibonacciPublisher]) 19 | val publisher = ActorPublisher[BigInteger](publisherActor) 20 | 21 | system.log.info("Starting Subscriber") 22 | val subscriberActor = system.actorOf(Props(new FibonacciSubscriber(500))) 23 | val subscriber = ActorSubscriber[BigInteger](subscriberActor) 24 | 25 | system.log.info("Subscribing to Publisher") 26 | publisher.subscribe(subscriber) 27 | } 28 | 29 | def startPubSubTransformerExample(system: ActorSystem) { 30 | system.log.info("Starting Publisher") 31 | val publisherActor = system.actorOf(Props[FibonacciPublisher]) 32 | val publisher = ActorPublisher[BigInteger](publisherActor) 33 | 34 | system.log.info("Starting Doubling Processor") 35 | val doubleProcessorActor = system.actorOf(Props[DoublingProcessor]) 36 | val doublePublisher = ActorPublisher[BigInteger](doubleProcessorActor) 37 | val doubleSubscriber = ActorSubscriber[BigInteger](doubleProcessorActor) 38 | 39 | system.log.info("Starting Subscriber") 40 | val subscriberActor = system.actorOf(Props(new FibonacciSubscriber(500))) 41 | val subscriber = ActorSubscriber[BigInteger](subscriberActor) 42 | 43 | system.log.info("Subscribing to Processor to Publisher") 44 | publisher.subscribe(doubleSubscriber) 45 | system.log.info("Subscribing Subscriber to Processor") 46 | doublePublisher.subscribe(subscriber) 47 | } 48 | } -------------------------------------------------------------------------------- /client.conf: -------------------------------------------------------------------------------- 1 | example.publisher = "client" # random | client 2 | example.subscriber = "print" # server | print 3 | # example.server.port = 8081 4 | example.client.port = 8081 5 | example.delay = 100 -------------------------------------------------------------------------------- /project/Build.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import Keys._ 3 | import com.github.retronym.SbtOneJar 4 | 5 | object BuildSettings { 6 | val buildOrganization = "bryan.codes" 7 | val buildVersion = "0.1" 8 | val buildScalaVersion = "2.11.2" 9 | 10 | val buildSettings = Defaults.defaultSettings ++ Seq ( 11 | organization := buildOrganization, 12 | version := buildVersion, 13 | scalaVersion := buildScalaVersion 14 | ) 15 | } 16 | 17 | object Resolvers { 18 | val typesafeRepo = "Typesafe Repo" at "http://repo.typesafe.com/typesafe/releases/" 19 | } 20 | 21 | object Dependencies { 22 | val akkaVersion = "2.3.9" 23 | 24 | val akkaActor = "com.typesafe.akka" %% "akka-actor" % akkaVersion 25 | val akkaSlf4j = "com.typesafe.akka" %% "akka-slf4j" % akkaVersion 26 | val akkaTestkit = "com.typesafe.akka" %% "akka-testkit" % akkaVersion 27 | 28 | val akkaHttp = "com.typesafe.akka" %% "akka-http-core-experimental" % "1.0-M2" 29 | val akkaStream = "com.typesafe.akka" %% "akka-stream-experimental" % "1.0-M2" 30 | 31 | val playJson = "com.typesafe.play" %% "play-json" % "2.3.7" 32 | val scalatest = "org.scalatest" %% "scalatest" % "2.2.0" % "test" 33 | val scalaAsync = "org.scala-lang.modules" %% "scala-async" % "0.9.2" 34 | 35 | val akkaDependencies = Seq(akkaActor, akkaSlf4j, akkaTestkit, akkaHttp, akkaStream) 36 | val miscDependencies = Seq(playJson, scalaAsync) 37 | val testDependencies = Seq(scalatest) 38 | val allDependencies = akkaDependencies ++ miscDependencies ++ testDependencies 39 | } 40 | 41 | object AkkaHttpStreamExample extends Build { 42 | import Resolvers._ 43 | import BuildSettings._ 44 | import Defaults._ 45 | 46 | lazy val akkaHttpStreamExample = 47 | Project ("akka-http-example", file("./akka-http-example")) 48 | .settings ( buildSettings : _* ) 49 | .settings ( SbtOneJar.oneJarSettings : _* ) 50 | .settings ( resolvers ++= Seq(typesafeRepo) ) 51 | .settings ( libraryDependencies ++= Dependencies.allDependencies ) 52 | .settings ( scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") ) 53 | 54 | lazy val akkaStreamExample = 55 | Project ("akka-stream-example", file("./akka-stream-example")) 56 | .settings ( buildSettings : _* ) 57 | .settings ( SbtOneJar.oneJarSettings : _* ) 58 | .settings ( resolvers ++= Seq(typesafeRepo) ) 59 | .settings ( libraryDependencies ++= Dependencies.allDependencies ) 60 | .settings ( scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature") ) 61 | } -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | 2 | resolvers += Classpaths.typesafeResolver 3 | 4 | addSbtPlugin("com.github.retronym" % "sbt-onejar" % "0.8") -------------------------------------------------------------------------------- /server-client.conf: -------------------------------------------------------------------------------- 1 | example.publisher = "client" # random | client 2 | example.subscriber = "server" # server | print 3 | example.server.port = 8081 4 | example.client.port = 8080 5 | # example.delay = 100 -------------------------------------------------------------------------------- /server.conf: -------------------------------------------------------------------------------- 1 | example.publisher = "random" # random | client 2 | example.subscriber = "server" # server | print 3 | example.server.port = 8080 4 | # example.client.port = 8081 5 | # example.delay = 100 --------------------------------------------------------------------------------