Merge branch 'master' into wip-sync-artery-dev-2.4.9-patriknw

This commit is contained in:
Patrik Nordwall 2016-08-23 20:14:15 +02:00
commit 8ab02738b7
483 changed files with 9535 additions and 2177 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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()}"
}
//#
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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