Merge branch 'master' into wip-sync-artery-dev-2.4.9-patriknw
This commit is contained in:
commit
8ab02738b7
483 changed files with 9535 additions and 2177 deletions
|
|
@ -152,12 +152,13 @@ class LoggingDocSpec extends AkkaSpec {
|
|||
|
||||
"demonstrate superclass subscriptions on eventStream" in {
|
||||
def println(s: String) = ()
|
||||
//#superclass-subscription-eventstream
|
||||
abstract class AllKindsOfMusic { def artist: String }
|
||||
case class Jazz(artist: String) extends AllKindsOfMusic
|
||||
case class Electronic(artist: String) extends AllKindsOfMusic
|
||||
|
||||
new AnyRef {
|
||||
//#superclass-subscription-eventstream
|
||||
abstract class AllKindsOfMusic { def artist: String }
|
||||
case class Jazz(artist: String) extends AllKindsOfMusic
|
||||
case class Electronic(artist: String) extends AllKindsOfMusic
|
||||
|
||||
class Listener extends Actor {
|
||||
def receive = {
|
||||
case m: Jazz => println(s"${self.path.name} is listening to: ${m.artist}")
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@
|
|||
|
||||
package docs.http.scaladsl
|
||||
|
||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
||||
import akka.http.scaladsl.server.Directives
|
||||
import org.scalatest.{ Matchers, WordSpec }
|
||||
|
||||
class SprayJsonExampleSpec extends WordSpec with Matchers {
|
||||
|
|
@ -13,7 +11,9 @@ class SprayJsonExampleSpec extends WordSpec with Matchers {
|
|||
def compileOnlySpec(body: => Unit) = ()
|
||||
|
||||
"spray-json example" in compileOnlySpec {
|
||||
//#example
|
||||
//#minimal-spray-json-example
|
||||
import akka.http.scaladsl.server.Directives
|
||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
||||
import spray.json._
|
||||
|
||||
// domain model
|
||||
|
|
@ -120,4 +120,4 @@ class SprayJsonExampleSpec extends WordSpec with Matchers {
|
|||
}
|
||||
//#second-spray-json-example
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class FileUploadExamplesSpec extends RoutingSpec {
|
|||
// stream into a file as the chunks of it arrives and return a future
|
||||
// file to where it got stored
|
||||
val file = File.createTempFile("upload", "tmp")
|
||||
b.entity.dataBytes.runWith(FileIO.toFile(file)).map(_ =>
|
||||
b.entity.dataBytes.runWith(FileIO.toPath(file.toPath)).map(_ =>
|
||||
(b.name -> file))
|
||||
|
||||
case b: BodyPart =>
|
||||
|
|
|
|||
|
|
@ -54,10 +54,13 @@ abstract class HttpsServerExampleSpec extends WordSpec with Matchers
|
|||
val sslContext: SSLContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
|
||||
val https: HttpsConnectionContext = ConnectionContext.https(sslContext)
|
||||
//#
|
||||
|
||||
// sets default context to HTTPS – all Http() bound servers for this ActorSystem will use HTTPS from now on
|
||||
Http().setDefaultServerHttpContext(https)
|
||||
|
||||
//#both-https-and-http
|
||||
// you can run both HTTP and HTTPS in the same application as follows:
|
||||
val commonRoutes: Route = get { complete("Hello world!") }
|
||||
Http().bindAndHandle(commonRoutes, "127.0.0.1", 443, connectionContext = https)
|
||||
Http().bindAndHandle(commonRoutes, "127.0.0.1", 80)
|
||||
//#
|
||||
|
||||
//#bind-low-level-context
|
||||
|
|
@ -67,6 +70,14 @@ abstract class HttpsServerExampleSpec extends WordSpec with Matchers
|
|||
val routes: Route = get { complete("Hello world!") }
|
||||
Http().bindAndHandle(routes, "127.0.0.1", 8080, connectionContext = https)
|
||||
//#
|
||||
|
||||
//#set-low-level-context-default
|
||||
// sets default context to HTTPS – all Http() bound servers for this ActorSystem will use HTTPS from now on
|
||||
Http().setDefaultServerHttpContext(https)
|
||||
Http().bindAndHandle(routes, "127.0.0.1", 9090, connectionContext = https)
|
||||
//#
|
||||
|
||||
system.terminate()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -805,7 +805,7 @@ class BasicDirectivesExamplesSpec extends RoutingSpec {
|
|||
// tests:
|
||||
val httpEntity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "req")
|
||||
Post("/abc", httpEntity) ~> route ~> check {
|
||||
responseAs[String] shouldEqual s"Request entity content-type is text/plain; charset=UTF-8"
|
||||
responseAs[String] shouldEqual "Request entity content-type is text/plain; charset=UTF-8"
|
||||
}
|
||||
//#
|
||||
}
|
||||
|
|
@ -826,5 +826,53 @@ class BasicDirectivesExamplesSpec extends RoutingSpec {
|
|||
}
|
||||
//#
|
||||
}
|
||||
"extractStrictEntity-example" in {
|
||||
//#extractStrictEntity-example
|
||||
import scala.concurrent.duration._
|
||||
val route = extractStrictEntity(3.seconds) { entity =>
|
||||
complete(entity.data.utf8String)
|
||||
}
|
||||
|
||||
// tests:
|
||||
val dataBytes = Source.fromIterator(() ⇒ Iterator.range(1, 10).map(x ⇒ ByteString(x.toString)))
|
||||
Post("/", HttpEntity(ContentTypes.`text/plain(UTF-8)`, data = dataBytes)) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "123456789"
|
||||
}
|
||||
//#
|
||||
}
|
||||
"toStrictEntity-example" in {
|
||||
//#toStrictEntity-example
|
||||
import scala.concurrent.duration._
|
||||
val route = toStrictEntity(3.seconds) {
|
||||
extractRequest { req =>
|
||||
req.entity match {
|
||||
case strict: HttpEntity.Strict =>
|
||||
complete(s"Request entity is strict, data=${strict.data.utf8String}")
|
||||
case _ =>
|
||||
complete("Ooops, request entity is not strict!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tests:
|
||||
val dataBytes = Source.fromIterator(() ⇒ Iterator.range(1, 10).map(x ⇒ ByteString(x.toString)))
|
||||
Post("/", HttpEntity(ContentTypes.`text/plain(UTF-8)`, data = dataBytes)) ~> route ~> check {
|
||||
responseAs[String] shouldEqual "Request entity is strict, data=123456789"
|
||||
}
|
||||
//#
|
||||
}
|
||||
|
||||
"extractActorSystem-example" in {
|
||||
//#extractActorSystem-example
|
||||
val route = extractActorSystem { actorSystem =>
|
||||
complete(s"Actor System extracted, hash=${actorSystem.hashCode()}")
|
||||
}
|
||||
|
||||
// tests:
|
||||
Get("/") ~> route ~> check {
|
||||
responseAs[String] shouldEqual s"Actor System extracted, hash=${system.hashCode()}"
|
||||
}
|
||||
//#
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.scaladsl.server.directives
|
||||
|
||||
import akka.http.scaladsl.{ Http, TestUtils }
|
||||
import akka.http.scaladsl.client.RequestBuilding
|
||||
import akka.http.scaladsl.model.HttpProtocols._
|
||||
import akka.http.scaladsl.model.RequestEntityAcceptance.Expected
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.server.Directives
|
||||
import akka.stream.ActorMaterializer
|
||||
import akka.testkit.AkkaSpec
|
||||
import akka.util.ByteString
|
||||
import org.scalatest.concurrent.ScalaFutures
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class CustomHttpMethodSpec extends AkkaSpec with ScalaFutures
|
||||
with Directives with RequestBuilding {
|
||||
|
||||
implicit val mat = ActorMaterializer()
|
||||
|
||||
"Http" should {
|
||||
"allow registering custom method" in {
|
||||
import system.dispatcher
|
||||
val (_, host, port) = TestUtils.temporaryServerHostnameAndPort()
|
||||
|
||||
//#application-custom
|
||||
import akka.http.scaladsl.settings.{ ParserSettings, ServerSettings }
|
||||
|
||||
// define custom media type:
|
||||
val BOLT = HttpMethod.custom("BOLT", safe = false,
|
||||
idempotent = true, requestEntityAcceptance = Expected)
|
||||
|
||||
// add custom method to parser settings:
|
||||
val parserSettings = ParserSettings(system).withCustomMethods(BOLT)
|
||||
val serverSettings = ServerSettings(system).withParserSettings(parserSettings)
|
||||
|
||||
val routes = extractMethod { method ⇒
|
||||
complete(s"This is a ${method.name} method request.")
|
||||
}
|
||||
val binding = Http().bindAndHandle(routes, host, port, settings = serverSettings)
|
||||
//#application-custom
|
||||
|
||||
val request = HttpRequest(BOLT, s"http://$host:$port/", protocol = `HTTP/1.0`)
|
||||
val response = Http().singleRequest(request).futureValue
|
||||
|
||||
response.status should ===(StatusCodes.OK)
|
||||
val responseBody = response.toStrict(1.second).futureValue.entity.dataBytes.runFold(ByteString.empty)(_ ++ _).futureValue.utf8String
|
||||
responseBody should ===("This is a BOLT method request.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4,8 +4,11 @@
|
|||
|
||||
package docs.http.scaladsl.server.directives
|
||||
|
||||
import akka.event.Logging
|
||||
import akka.event.{ LoggingAdapter, Logging }
|
||||
import akka.event.Logging.LogLevel
|
||||
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse }
|
||||
import akka.http.scaladsl.server.RouteResult
|
||||
import akka.http.scaladsl.server.RouteResult.{ Rejected, Complete }
|
||||
import akka.http.scaladsl.server.directives.{ DebuggingDirectives, LogEntry, LoggingMagnet }
|
||||
import docs.http.scaladsl.server.RoutingSpec
|
||||
|
||||
|
|
@ -21,15 +24,15 @@ class DebuggingDirectivesExamplesSpec extends RoutingSpec {
|
|||
DebuggingDirectives.logRequest(("get-user", Logging.InfoLevel))
|
||||
|
||||
// logs just the request method at debug level
|
||||
def requestMethod(req: HttpRequest): String = req.method.toString
|
||||
def requestMethod(req: HttpRequest): String = req.method.name
|
||||
DebuggingDirectives.logRequest(requestMethod _)
|
||||
|
||||
// logs just the request method at info level
|
||||
def requestMethodAsInfo(req: HttpRequest): LogEntry = LogEntry(req.method.toString, Logging.InfoLevel)
|
||||
def requestMethodAsInfo(req: HttpRequest): LogEntry = LogEntry(req.method.name, Logging.InfoLevel)
|
||||
DebuggingDirectives.logRequest(requestMethodAsInfo _)
|
||||
|
||||
// This one doesn't use the implicit LoggingContext but uses `println` for logging
|
||||
def printRequestMethod(req: HttpRequest): Unit = println(req.method)
|
||||
def printRequestMethod(req: HttpRequest): Unit = println(req.method.name)
|
||||
val logRequestPrintln = DebuggingDirectives.logRequest(LoggingMagnet(_ => printRequestMethod))
|
||||
|
||||
// tests:
|
||||
|
|
@ -48,14 +51,14 @@ class DebuggingDirectivesExamplesSpec extends RoutingSpec {
|
|||
DebuggingDirectives.logRequestResult(("get-user", Logging.InfoLevel))
|
||||
|
||||
// logs just the request method and response status at info level
|
||||
def requestMethodAndResponseStatusAsInfo(req: HttpRequest): Any => Option[LogEntry] = {
|
||||
case res: HttpResponse => Some(LogEntry(req.method + ":" + res.status, Logging.InfoLevel))
|
||||
case _ => None // other kind of responses
|
||||
def requestMethodAndResponseStatusAsInfo(req: HttpRequest): RouteResult => Option[LogEntry] = {
|
||||
case RouteResult.Complete(res) => Some(LogEntry(req.method.name + ": " + res.status, Logging.InfoLevel))
|
||||
case _ => None // no log entries for rejections
|
||||
}
|
||||
DebuggingDirectives.logRequestResult(requestMethodAndResponseStatusAsInfo _)
|
||||
|
||||
// This one doesn't use the implicit LoggingContext but uses `println` for logging
|
||||
def printRequestMethodAndResponseStatus(req: HttpRequest)(res: Any): Unit =
|
||||
def printRequestMethodAndResponseStatus(req: HttpRequest)(res: RouteResult): Unit =
|
||||
println(requestMethodAndResponseStatusAsInfo(req)(res).map(_.obj.toString).getOrElse(""))
|
||||
val logRequestResultPrintln = DebuggingDirectives.logRequestResult(LoggingMagnet(_ => printRequestMethodAndResponseStatus))
|
||||
|
||||
|
|
@ -75,18 +78,18 @@ class DebuggingDirectivesExamplesSpec extends RoutingSpec {
|
|||
DebuggingDirectives.logResult(("get-user", Logging.InfoLevel))
|
||||
|
||||
// logs just the response status at debug level
|
||||
def responseStatus(res: Any): String = res match {
|
||||
case x: HttpResponse => x.status.toString
|
||||
case _ => "unknown response part"
|
||||
def responseStatus(res: RouteResult): String = res match {
|
||||
case RouteResult.Complete(x) => x.status.toString
|
||||
case RouteResult.Rejected(rejections) => "Rejected: " + rejections.mkString(", ")
|
||||
}
|
||||
DebuggingDirectives.logResult(responseStatus _)
|
||||
|
||||
// logs just the response status at info level
|
||||
def responseStatusAsInfo(res: Any): LogEntry = LogEntry(responseStatus(res), Logging.InfoLevel)
|
||||
def responseStatusAsInfo(res: RouteResult): LogEntry = LogEntry(responseStatus(res), Logging.InfoLevel)
|
||||
DebuggingDirectives.logResult(responseStatusAsInfo _)
|
||||
|
||||
// This one doesn't use the implicit LoggingContext but uses `println` for logging
|
||||
def printResponseStatus(res: Any): Unit = println(responseStatus(res))
|
||||
def printResponseStatus(res: RouteResult): Unit = println(responseStatus(res))
|
||||
val logResultPrintln = DebuggingDirectives.logResult(LoggingMagnet(_ => printResponseStatus))
|
||||
|
||||
// tests:
|
||||
|
|
@ -94,4 +97,32 @@ class DebuggingDirectivesExamplesSpec extends RoutingSpec {
|
|||
responseAs[String] shouldEqual "logged"
|
||||
}
|
||||
}
|
||||
"logRequestResultWithResponseTime" in {
|
||||
|
||||
def akkaResponseTimeLoggingFunction(
|
||||
loggingAdapter: LoggingAdapter,
|
||||
requestTimestamp: Long,
|
||||
level: LogLevel = Logging.InfoLevel)(req: HttpRequest)(res: Any): Unit = {
|
||||
val entry = res match {
|
||||
case Complete(resp) =>
|
||||
val responseTimestamp: Long = System.nanoTime
|
||||
val elapsedTime: Long = (responseTimestamp - requestTimestamp) / 1000000
|
||||
val loggingString = s"""Logged Request:${req.method}:${req.uri}:${resp.status}:${elapsedTime}"""
|
||||
LogEntry(loggingString, level)
|
||||
case Rejected(reason) =>
|
||||
LogEntry(s"Rejected Reason: ${reason.mkString(",")}", level)
|
||||
}
|
||||
entry.logTo(loggingAdapter)
|
||||
}
|
||||
def printResponseTime(log: LoggingAdapter) = {
|
||||
val requestTimestamp = System.nanoTime
|
||||
akkaResponseTimeLoggingFunction(log, requestTimestamp)(_)
|
||||
}
|
||||
|
||||
val logResponseTime = DebuggingDirectives.logRequestResult(LoggingMagnet(printResponseTime(_)))
|
||||
|
||||
Get("/") ~> logResponseTime(complete("logged")) ~> check {
|
||||
responseAs[String] shouldEqual "logged"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,15 +4,21 @@
|
|||
package docs.http.scaladsl.server.directives
|
||||
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.testkit.RouteTestTimeout
|
||||
import akka.stream.scaladsl.Framing
|
||||
import akka.util.ByteString
|
||||
import docs.http.scaladsl.server.RoutingSpec
|
||||
import akka.testkit.TestDuration
|
||||
import scala.concurrent.Future
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class FileUploadDirectivesExamplesSpec extends RoutingSpec {
|
||||
|
||||
override def testConfigSource = "akka.actor.default-mailbox.mailbox-type = \"akka.dispatch.UnboundedMailbox\""
|
||||
|
||||
// test touches disk, so give it some time
|
||||
implicit val routeTimeout = RouteTestTimeout(3.seconds.dilated)
|
||||
|
||||
"uploadedFile" in {
|
||||
|
||||
val route =
|
||||
|
|
|
|||
|
|
@ -209,13 +209,13 @@ class HeaderDirectivesExamplesSpec extends RoutingSpec with Inside {
|
|||
val invalidOriginHeader = Origin(invalidHttpOrigin)
|
||||
Get("abc") ~> invalidOriginHeader ~> route ~> check {
|
||||
inside(rejection) {
|
||||
case InvalidOriginRejection(invalidOrigins) ⇒
|
||||
invalidOrigins shouldEqual Seq(invalidHttpOrigin)
|
||||
case InvalidOriginRejection(allowedOrigins) ⇒
|
||||
allowedOrigins shouldEqual Seq(correctOrigin)
|
||||
}
|
||||
}
|
||||
Get("abc") ~> invalidOriginHeader ~> Route.seal(route) ~> check {
|
||||
status shouldEqual StatusCodes.Forbidden
|
||||
responseAs[String] should include(s"${invalidHttpOrigin.value}")
|
||||
responseAs[String] should include(s"${correctOrigin.value}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package docs.http.scaladsl.server.directives
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.http.scaladsl.common.{ EntityStreamingSupport, JsonEntityStreamingSupport }
|
||||
import akka.http.scaladsl.marshalling._
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.model.headers.Accept
|
||||
import akka.http.scaladsl.server.{ UnacceptedResponseContentTypeRejection, UnsupportedRequestContentTypeRejection }
|
||||
import akka.stream.scaladsl.{ Flow, Source }
|
||||
import akka.util.ByteString
|
||||
import docs.http.scaladsl.server.RoutingSpec
|
||||
|
||||
import scala.concurrent.Future
|
||||
|
||||
class JsonStreamingExamplesSpec extends RoutingSpec {
|
||||
|
||||
//#models
|
||||
case class Tweet(uid: Int, txt: String)
|
||||
case class Measurement(id: String, value: Int)
|
||||
//#
|
||||
|
||||
val tweets = List(
|
||||
Tweet(1, "#Akka rocks!"),
|
||||
Tweet(2, "Streaming is so hot right now!"),
|
||||
Tweet(3, "You cannot enter the same river twice."))
|
||||
def getTweets = Source(tweets)
|
||||
|
||||
//#formats
|
||||
object MyJsonProtocol
|
||||
extends akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
||||
with spray.json.DefaultJsonProtocol {
|
||||
|
||||
implicit val tweetFormat = jsonFormat2(Tweet.apply)
|
||||
implicit val measurementFormat = jsonFormat2(Measurement.apply)
|
||||
}
|
||||
//#
|
||||
|
||||
"spray-json-response-streaming" in {
|
||||
// [1] import "my protocol", for marshalling Tweet objects:
|
||||
import MyJsonProtocol._
|
||||
|
||||
// [2] pick a Source rendering support trait:
|
||||
// Note that the default support renders the Source as JSON Array
|
||||
implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json()
|
||||
|
||||
val route =
|
||||
path("tweets") {
|
||||
// [3] simply complete a request with a source of tweets:
|
||||
val tweets: Source[Tweet, NotUsed] = getTweets
|
||||
complete(tweets)
|
||||
}
|
||||
|
||||
// tests ------------------------------------------------------------
|
||||
val AcceptJson = Accept(MediaRange(MediaTypes.`application/json`))
|
||||
val AcceptXml = Accept(MediaRange(MediaTypes.`text/xml`))
|
||||
|
||||
Get("/tweets").withHeaders(AcceptJson) ~> route ~> check {
|
||||
responseAs[String] shouldEqual
|
||||
"""[""" +
|
||||
"""{"uid":1,"txt":"#Akka rocks!"},""" +
|
||||
"""{"uid":2,"txt":"Streaming is so hot right now!"},""" +
|
||||
"""{"uid":3,"txt":"You cannot enter the same river twice."}""" +
|
||||
"""]"""
|
||||
}
|
||||
|
||||
// endpoint can only marshal Json, so it will *reject* requests for application/xml:
|
||||
Get("/tweets").withHeaders(AcceptXml) ~> route ~> check {
|
||||
handled should ===(false)
|
||||
rejection should ===(UnacceptedResponseContentTypeRejection(Set(ContentTypes.`application/json`)))
|
||||
}
|
||||
}
|
||||
|
||||
"line-by-line-json-response-streaming" in {
|
||||
import MyJsonProtocol._
|
||||
|
||||
// Configure the EntityStreamingSupport to render the elements as:
|
||||
// {"example":42}
|
||||
// {"example":43}
|
||||
// ...
|
||||
// {"example":1000}
|
||||
val start = ByteString.empty
|
||||
val sep = ByteString("\n")
|
||||
val end = ByteString.empty
|
||||
|
||||
implicit val jsonStreamingSupport = EntityStreamingSupport.json()
|
||||
.withFramingRenderer(Flow[ByteString].intersperse(start, sep, end))
|
||||
|
||||
val route =
|
||||
path("tweets") {
|
||||
// [3] simply complete a request with a source of tweets:
|
||||
val tweets: Source[Tweet, NotUsed] = getTweets
|
||||
complete(tweets)
|
||||
}
|
||||
|
||||
// tests ------------------------------------------------------------
|
||||
val AcceptJson = Accept(MediaRange(MediaTypes.`application/json`))
|
||||
|
||||
Get("/tweets").withHeaders(AcceptJson) ~> route ~> check {
|
||||
responseAs[String] shouldEqual
|
||||
"""{"uid":1,"txt":"#Akka rocks!"}""" + "\n" +
|
||||
"""{"uid":2,"txt":"Streaming is so hot right now!"}""" + "\n" +
|
||||
"""{"uid":3,"txt":"You cannot enter the same river twice."}"""
|
||||
}
|
||||
}
|
||||
|
||||
"csv-example" in {
|
||||
// [1] provide a marshaller to ByteString
|
||||
implicit val tweetAsCsv = Marshaller.strict[Tweet, ByteString] { t =>
|
||||
Marshalling.WithFixedContentType(ContentTypes.`text/csv(UTF-8)`, () => {
|
||||
val txt = t.txt.replaceAll(",", ".")
|
||||
val uid = t.uid
|
||||
ByteString(List(uid, txt).mkString(","))
|
||||
})
|
||||
}
|
||||
|
||||
// [2] enable csv streaming:
|
||||
implicit val csvStreaming = EntityStreamingSupport.csv()
|
||||
|
||||
val route =
|
||||
path("tweets") {
|
||||
val tweets: Source[Tweet, NotUsed] = getTweets
|
||||
complete(tweets)
|
||||
}
|
||||
|
||||
// tests ------------------------------------------------------------
|
||||
val AcceptCsv = Accept(MediaRange(MediaTypes.`text/csv`))
|
||||
|
||||
Get("/tweets").withHeaders(AcceptCsv) ~> route ~> check {
|
||||
responseAs[String] shouldEqual
|
||||
"1,#Akka rocks!" + "\n" +
|
||||
"2,Streaming is so hot right now!" + "\n" +
|
||||
"3,You cannot enter the same river twice."
|
||||
}
|
||||
}
|
||||
|
||||
"response-streaming-modes" in {
|
||||
|
||||
{
|
||||
//#async-rendering
|
||||
import MyJsonProtocol._
|
||||
implicit val jsonStreamingSupport: JsonEntityStreamingSupport =
|
||||
EntityStreamingSupport.json()
|
||||
.withParallelMarshalling(parallelism = 8, unordered = false)
|
||||
|
||||
path("tweets") {
|
||||
val tweets: Source[Tweet, NotUsed] = getTweets
|
||||
complete(tweets)
|
||||
}
|
||||
//#
|
||||
}
|
||||
|
||||
{
|
||||
|
||||
//#async-unordered-rendering
|
||||
import MyJsonProtocol._
|
||||
implicit val jsonStreamingSupport: JsonEntityStreamingSupport =
|
||||
EntityStreamingSupport.json()
|
||||
.withParallelMarshalling(parallelism = 8, unordered = true)
|
||||
|
||||
path("tweets" / "unordered") {
|
||||
val tweets: Source[Tweet, NotUsed] = getTweets
|
||||
complete(tweets)
|
||||
}
|
||||
//#
|
||||
}
|
||||
}
|
||||
|
||||
"spray-json-request-streaming" in {
|
||||
// [1] import "my protocol", for unmarshalling Measurement objects:
|
||||
import MyJsonProtocol._
|
||||
|
||||
// [2] enable Json Streaming
|
||||
implicit val jsonStreamingSupport = EntityStreamingSupport.json()
|
||||
|
||||
// prepare your persisting logic here
|
||||
val persistMetrics = Flow[Measurement]
|
||||
|
||||
val route =
|
||||
path("metrics") {
|
||||
// [3] extract Source[Measurement, _]
|
||||
entity(asSourceOf[Measurement]) { measurements =>
|
||||
// alternative syntax:
|
||||
// entity(as[Source[Measurement, NotUsed]]) { measurements =>
|
||||
val measurementsSubmitted: Future[Int] =
|
||||
measurements
|
||||
.via(persistMetrics)
|
||||
.runFold(0) { (cnt, _) => cnt + 1 }
|
||||
|
||||
complete {
|
||||
measurementsSubmitted.map(n => Map("msg" -> s"""Total metrics received: $n"""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tests ------------------------------------------------------------
|
||||
// uploading an array or newline separated values works out of the box
|
||||
val data = HttpEntity(
|
||||
ContentTypes.`application/json`,
|
||||
"""
|
||||
|{"id":"temp","value":32}
|
||||
|{"id":"temp","value":31}
|
||||
|
|
||||
""".stripMargin)
|
||||
|
||||
Post("/metrics", entity = data) ~> route ~> check {
|
||||
status should ===(StatusCodes.OK)
|
||||
responseAs[String] should ===("""{"msg":"Total metrics received: 2"}""")
|
||||
}
|
||||
|
||||
// the FramingWithContentType will reject any content type that it does not understand:
|
||||
val xmlData = HttpEntity(
|
||||
ContentTypes.`text/xml(UTF-8)`,
|
||||
"""|<data id="temp" value="32"/>
|
||||
|<data id="temp" value="31"/>""".stripMargin)
|
||||
|
||||
Post("/metrics", entity = xmlData) ~> route ~> check {
|
||||
handled should ===(false)
|
||||
rejection should ===(UnsupportedRequestContentTypeRejection(Set(ContentTypes.`application/json`)))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -71,8 +71,8 @@ class MarshallingDirectivesExamplesSpec extends RoutingSpec {
|
|||
// tests:
|
||||
Get("/") ~> route ~> check {
|
||||
mediaType shouldEqual `application/json`
|
||||
responseAs[String] should include(""""name": "Jane"""")
|
||||
responseAs[String] should include(""""favoriteNumber": 42""")
|
||||
responseAs[String] should include(""""name":"Jane"""")
|
||||
responseAs[String] should include(""""favoriteNumber":42""")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -95,8 +95,8 @@ class MarshallingDirectivesExamplesSpec extends RoutingSpec {
|
|||
Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""")) ~>
|
||||
route ~> check {
|
||||
mediaType shouldEqual `application/json`
|
||||
responseAs[String] should include(""""name": "Jane"""")
|
||||
responseAs[String] should include(""""favoriteNumber": 42""")
|
||||
responseAs[String] should include(""""name":"Jane"""")
|
||||
responseAs[String] should include(""""favoriteNumber":42""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
Get("/secured") ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
|
||||
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
|
||||
|
|
@ -49,7 +49,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The supplied authentication is invalid"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
}
|
||||
"authenticateBasicPF-0" in {
|
||||
|
|
@ -71,7 +71,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
Get("/secured") ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
|
||||
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
|
||||
|
|
@ -92,7 +92,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The supplied authentication is invalid"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
}
|
||||
"authenticateBasicPFAsync-0" in {
|
||||
|
|
@ -120,7 +120,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
Get("/secured") ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
|
||||
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
|
||||
|
|
@ -135,7 +135,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The supplied authentication is invalid"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
}
|
||||
"authenticateBasicAsync-0" in {
|
||||
|
|
@ -163,7 +163,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
Get("/secured") ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
|
||||
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
|
||||
|
|
@ -178,11 +178,11 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The supplied authentication is invalid"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", Some("secure site"))
|
||||
}
|
||||
}
|
||||
"authenticateOrRejectWithChallenge-0" in {
|
||||
val challenge = HttpChallenge("MyAuth", "MyRealm")
|
||||
val challenge = HttpChallenge("MyAuth", Some("MyRealm"))
|
||||
|
||||
// your custom authentication logic:
|
||||
def auth(creds: HttpCredentials): Boolean = true
|
||||
|
|
@ -208,7 +208,7 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
|
|||
Get("/secured") ~> route ~> check {
|
||||
status shouldEqual StatusCodes.Unauthorized
|
||||
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("MyAuth", "MyRealm")
|
||||
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("MyAuth", Some("MyRealm"))
|
||||
}
|
||||
|
||||
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
|
||||
|
|
|
|||
|
|
@ -101,6 +101,18 @@ akka.actor.deployment {
|
|||
}
|
||||
}
|
||||
#//#config-balancing-pool3
|
||||
|
||||
#//#config-balancing-pool4
|
||||
akka.actor.deployment {
|
||||
/parent/router10c {
|
||||
router = balancing-pool
|
||||
nr-of-instances = 5
|
||||
pool-dispatcher {
|
||||
mailbox = myapp.myprioritymailbox
|
||||
}
|
||||
}
|
||||
}
|
||||
#//#config-balancing-pool4
|
||||
|
||||
#//#config-smallest-mailbox-pool
|
||||
akka.actor.deployment {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,14 @@
|
|||
package docs.stream
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.stream.scaladsl.{ Keep, Sink, Flow, Source }
|
||||
import akka.stream.scaladsl.{ Flow, Keep, Sink, Source }
|
||||
import akka.stream.stage._
|
||||
import akka.stream._
|
||||
|
||||
import akka.stream.testkit.{ TestPublisher, TestSubscriber }
|
||||
import akka.testkit.{ AkkaSpec, TestLatch }
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.{ Promise, Await, Future }
|
||||
import scala.concurrent.{ Await, Future, Promise }
|
||||
import scala.concurrent.duration._
|
||||
import scala.collection.immutable.Iterable
|
||||
|
||||
|
|
@ -86,6 +85,35 @@ class GraphStageDocSpec extends AkkaSpec {
|
|||
Await.result(result2, 3.seconds) should ===(5050)
|
||||
}
|
||||
|
||||
"Demonstrate creation of GraphStage Sink" in {
|
||||
//#custom-sink-example
|
||||
import akka.stream.SinkShape
|
||||
import akka.stream.stage.GraphStage
|
||||
import akka.stream.stage.InHandler
|
||||
|
||||
class StdoutSink extends GraphStage[SinkShape[Int]] {
|
||||
val in: Inlet[Int] = Inlet("StdoutSink")
|
||||
override val shape: SinkShape[Int] = SinkShape(in)
|
||||
|
||||
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
|
||||
new GraphStageLogic(shape) {
|
||||
|
||||
// This requests one element at the Sink startup.
|
||||
override def preStart(): Unit = pull(in)
|
||||
|
||||
setHandler(in, new InHandler {
|
||||
override def onPush(): Unit = {
|
||||
println(grab(in))
|
||||
pull(in)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
//#custom-sink-example
|
||||
|
||||
Source(List(0, 1, 2)).runWith(Sink.fromGraph(new StdoutSink))
|
||||
}
|
||||
|
||||
//#one-to-one
|
||||
class Map[A, B](f: A => B) extends GraphStage[FlowShape[A, B]] {
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class StreamBuffersRateSpec extends AkkaSpec {
|
|||
|
||||
//#section-buffer
|
||||
val section = Flow[Int].map(_ * 2).async
|
||||
.withAttributes(Attributes.inputBuffer(initial = 1, max = 1)) // the buffer size of this map is 1
|
||||
.addAttributes(Attributes.inputBuffer(initial = 1, max = 1)) // the buffer size of this map is 1
|
||||
val flow = section.via(Flow[Int].map(_ / 2)).async // the buffer size of this map is the default
|
||||
//#section-buffer
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,8 +42,15 @@ class RecipeByteStrings extends RecipeSpec {
|
|||
|
||||
override def onUpstreamFinish(): Unit = {
|
||||
if (buffer.isEmpty) completeStage()
|
||||
// elements left in buffer, keep accepting downstream pulls
|
||||
// and push from buffer until buffer is emitted
|
||||
else {
|
||||
// There are elements left in buffer, so
|
||||
// we keep accepting downstream pulls and push from buffer until emptied.
|
||||
//
|
||||
// It might be though, that the upstream finished while it was pulled, in which
|
||||
// case we will not get an onPull from the downstream, because we already had one.
|
||||
// In that case we need to emit from the buffer.
|
||||
if (isAvailable(out)) emitChunk()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue