+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,108 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
import akka.actor.{ ActorLogging, ActorSystem }
import akka.util.ByteString
import org.scalatest.{ Matchers, WordSpec }
class HttpClientExampleSpec extends WordSpec with Matchers {
"outgoing-connection-example" in {
pending // compile-time only test
//#outgoing-connection-example
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import scala.concurrent.Future
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
Http().outgoingConnection("akka.io")
val responseFuture: Future[HttpResponse] =
Source.single(HttpRequest(uri = "/"))
.via(connectionFlow)
.runWith(Sink.head)
//#outgoing-connection-example
}
"host-level-example" in {
pending // compile-time only test
//#host-level-example
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import scala.concurrent.Future
import scala.util.Try
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// construct a pool client flow with context type `Int`
val poolClientFlow = Http().cachedHostConnectionPool[Int]("akka.io")
val responseFuture: Future[(Try[HttpResponse], Int)] =
Source.single(HttpRequest(uri = "/") -> 42)
.via(poolClientFlow)
.runWith(Sink.head)
//#host-level-example
}
"single-request-example" in {
pending // compile-time only test
//#single-request-example
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import scala.concurrent.Future
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val responseFuture: Future[HttpResponse] =
Http().singleRequest(HttpRequest(uri = "http://akka.io"))
//#single-request-example
}
"single-request-in-actor-example" in {
pending // compile-time only test
//#single-request-in-actor-example
import akka.actor.Actor
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.scaladsl.ImplicitMaterializer
class Myself extends Actor
with ImplicitMaterializer
with ActorLogging {
import akka.pattern.pipe
import context.dispatcher
val http = Http(context.system)
override def preStart() = {
http.singleRequest(HttpRequest(uri = "http://akka.io"))
.pipeTo(self)
}
def receive = {
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
log.info("Got response, body: " + entity.dataBytes.runFold(ByteString(""))(_ ++ _))
case HttpResponse(code, _, _, _) =>
log.info("Request failed, response code: " + code)
}
}
//#single-request-in-actor-example
}
}

View file

@ -0,0 +1,398 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
import akka.actor.{ ActorRef, ActorSystem }
import akka.event.LoggingAdapter
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ Flow, Sink }
import akka.stream.stage.{ Context, PushStage }
import akka.testkit.TestActors
import org.scalatest.{ Matchers, WordSpec }
import scala.language.postfixOps
import scala.concurrent.{ ExecutionContext, Future }
class HttpServerExampleSpec extends WordSpec with Matchers {
// never actually called
val log: LoggingAdapter = null
def compileOnlySpec(body: => Unit) = ()
"binding-example" in compileOnlySpec {
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] =
Http().bind(interface = "localhost", port = 8080)
val bindingFuture: Future[Http.ServerBinding] =
serverSource.to(Sink.foreach { connection => // foreach materializes the source
println("Accepted new connection from " + connection.remoteAddress)
// ... and then actually handle the connection
}).run()
}
"binding-failure-high-level-example" in compileOnlySpec {
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
val handler = get {
complete("Hello world!")
}
// let's say the OS won't allow us to bind to 80.
val (host, port) = ("localhost", 80)
val bindingFuture: Future[ServerBinding] =
Http().bindAndHandle(handler, host, port)
bindingFuture onFailure {
case ex: Exception =>
log.error(ex, "Failed to bind to {}:{}!", host, port)
}
}
// mock values:
val handleConnections: Sink[Http.IncomingConnection, Future[Http.ServerBinding]] =
Sink.ignore.mapMaterializedValue(_ => Future.failed(new Exception("")))
"binding-failure-handling" in compileOnlySpec {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
// let's say the OS won't allow us to bind to 80.
val (host, port) = ("localhost", 80)
val serverSource = Http().bind(host, port)
val bindingFuture: Future[ServerBinding] = serverSource
.to(handleConnections) // Sink[Http.IncomingConnection, _]
.run()
bindingFuture onFailure {
case ex: Exception =>
log.error(ex, "Failed to bind to {}:{}!", host, port)
}
}
object MyExampleMonitoringActor {
def props = TestActors.echoActorProps
}
"incoming-connections-source-failure-handling" in compileOnlySpec {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
import Http._
val (host, port) = ("localhost", 8080)
val serverSource = Http().bind(host, port)
val failureMonitor: ActorRef = system.actorOf(MyExampleMonitoringActor.props)
val reactToTopLevelFailures = Flow[IncomingConnection]
.transform { () =>
new PushStage[IncomingConnection, IncomingConnection] {
override def onPush(elem: IncomingConnection, ctx: Context[IncomingConnection]) =
ctx.push(elem)
override def onUpstreamFailure(cause: Throwable, ctx: Context[IncomingConnection]) = {
failureMonitor ! cause
super.onUpstreamFailure(cause, ctx)
}
}
}
serverSource
.via(reactToTopLevelFailures)
.to(handleConnections) // Sink[Http.IncomingConnection, _]
.run()
}
"connection-stream-failure-handling" in compileOnlySpec {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
val (host, port) = ("localhost", 8080)
val serverSource = Http().bind(host, port)
val reactToConnectionFailure = Flow[HttpRequest]
.transform { () =>
new PushStage[HttpRequest, HttpRequest] {
override def onPush(elem: HttpRequest, ctx: Context[HttpRequest]) =
ctx.push(elem)
override def onUpstreamFailure(cause: Throwable, ctx: Context[HttpRequest]) = {
// handle the failure somehow
super.onUpstreamFailure(cause, ctx)
}
}
}
val httpEcho = Flow[HttpRequest]
.via(reactToConnectionFailure)
.map { request =>
// simple text "echo" response:
HttpResponse(entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, request.entity.dataBytes))
}
serverSource
.runForeach { con =>
con.handleWith(httpEcho)
}
}
"full-server-example" in compileOnlySpec {
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Sink
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val serverSource = Http().bind(interface = "localhost", port = 8080)
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
"<html><body>Hello world!</body></html>"))
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
HttpResponse(entity = "PONG!")
case HttpRequest(GET, Uri.Path("/crash"), _, _, _) =>
sys.error("BOOM!")
case _: HttpRequest =>
HttpResponse(404, entity = "Unknown resource!")
}
val bindingFuture: Future[Http.ServerBinding] =
serverSource.to(Sink.foreach { connection =>
println("Accepted new connection from " + connection.remoteAddress)
connection handleWithSyncHandler requestHandler
// this is equivalent to
// connection handleWith { Flow[HttpRequest] map requestHandler }
}).run()
}
"low-level-server-example" in compileOnlySpec {
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`,
"<html><body>Hello world!</body></html>"))
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
HttpResponse(entity = "PONG!")
case HttpRequest(GET, Uri.Path("/crash"), _, _, _) =>
sys.error("BOOM!")
case _: HttpRequest =>
HttpResponse(404, entity = "Unknown resource!")
}
Http().bindAndHandleSync(requestHandler, "localhost", 8080)
}
// format: OFF
"high-level-server-example" in compileOnlySpec {
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route =
get {
pathSingleSlash {
complete {
<html>
<body>Hello world!</body>
</html>
}
} ~
path("ping") {
complete("PONG!")
} ~
path("crash") {
sys.error("BOOM!")
}
}
// `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow`
Http().bindAndHandle(route, "localhost", 8080)
}
"minimal-routing-example" in compileOnlySpec {
import akka.http.scaladsl.Http
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
object Main extends App {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
implicit val ec = system.dispatcher
val route =
path("hello") {
get {
complete {
<h1>Say hello to akka-http</h1>
}
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine() // for the future transformations
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.shutdown()) // and shutdown when done
}
}
"long-routing-example" in compileOnlySpec {
//#long-routing-example
import akka.actor.ActorRef
import akka.http.scaladsl.coding.Deflate
import akka.http.scaladsl.marshalling.ToResponseMarshaller
import akka.http.scaladsl.model.StatusCodes.MovedPermanently
import akka.http.scaladsl.server.Directives._
// TODO: these explicit imports are only needed in complex cases, like below; Also, not needed on Scala 2.11
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet
import akka.http.scaladsl.server.directives.FormFieldDirectives.FieldMagnet
import akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller
import akka.pattern.ask
import akka.util.Timeout
// types used by the API routes
type Money = Double // only for demo purposes, don't try this at home!
type TransactionResult = String
case class User(name: String)
case class Order(email: String, amount: Money)
case class Update(order: Order)
case class OrderItem(i: Int, os: Option[String], s: String)
// marshalling would usually be derived automatically using libraries
implicit val orderUM: FromRequestUnmarshaller[Order] = ???
implicit val orderM: ToResponseMarshaller[Order] = ???
implicit val orderSeqM: ToResponseMarshaller[Seq[Order]] = ???
implicit val timeout: Timeout = ??? // for actor asks
implicit val ec: ExecutionContext = ???
implicit val mat: ActorMaterializer = ???
implicit val sys: ActorSystem = ???
// backend entry points
def myAuthenticator: Authenticator[User] = ???
def retrieveOrdersFromDB: Seq[Order] = ???
def myDbActor: ActorRef = ???
def processOrderRequest(id: Int, complete: Order => Unit): Unit = ???
val route = {
path("orders") {
authenticateBasic(realm = "admin area", myAuthenticator) { user =>
get {
encodeResponseWith(Deflate) {
complete {
// marshal custom object with in-scope marshaller
retrieveOrdersFromDB
}
}
} ~
post {
// decompress gzipped or deflated requests if required
decodeRequest {
// unmarshal with in-scope unmarshaller
entity(as[Order]) { order =>
complete {
// ... write order to DB
"Order received"
}
}
}
}
}
} ~
// extract URI path element as Int
pathPrefix("order" / IntNumber) { orderId =>
pathEnd {
(put | parameter('method ! "put")) {
// form extraction from multipart or www-url-encoded forms
formFields(('email, 'total.as[Money])).as(Order) { order =>
complete {
// complete with serialized Future result
(myDbActor ? Update(order)).mapTo[TransactionResult]
}
}
} ~
get {
// debugging helper
logRequest("GET-ORDER") {
// use in-scope marshaller to create completer function
completeWith(instanceOf[Order]) { completer =>
// custom
processOrderRequest(orderId, completer)
}
}
}
} ~
path("items") {
get {
// parameters to case class extraction
parameters(('size.as[Int], 'color ?, 'dangerous ? "no"))
.as(OrderItem) { orderItem =>
// ... route using case class instance created from
// required and optional query parameters
complete("") // hide
}
}
}
} ~
pathPrefix("documentation") {
// optionally compresses the response with Gzip or Deflate
// if the client accepts compressed responses
encodeResponse {
// serve up static content from a JAR resource
getFromResourceDirectory("docs")
}
} ~
path("oldApi" / Rest) { pathRest =>
redirect("http://oldapi.example.com/" + pathRest, MovedPermanently)
}
}
}
}

View file

@ -0,0 +1,38 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
import akka.stream.testkit.AkkaSpec
class MarshalSpec extends AkkaSpec {
"use marshal" in {
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import system.dispatcher // ExecutionContext
val string = "Yeah"
val entityFuture = Marshal(string).to[MessageEntity]
val entity = Await.result(entityFuture, 1.second) // don't block in non-test code!
entity.contentType shouldEqual ContentTypes.`text/plain(UTF-8)`
val errorMsg = "Easy, pal!"
val responseFuture = Marshal(420 -> errorMsg).to[HttpResponse]
val response = Await.result(responseFuture, 1.second) // don't block in non-test code!
response.status shouldEqual StatusCodes.EnhanceYourCalm
response.entity.contentType shouldEqual ContentTypes.`text/plain(UTF-8)`
val request = HttpRequest(headers = List(headers.Accept(MediaTypes.`application/json`)))
val responseText = "Plaintext"
val respFuture = Marshal(responseText).toResponseFor(request) // with content negotiation!
a[Marshal.UnacceptableResponseContentTypeException] should be thrownBy {
Await.result(respFuture, 1.second) // client requested JSON, we only have text/plain!
}
}
}

View file

@ -0,0 +1,91 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
//#import-model
import akka.http.scaladsl.model._
//#import-model
import akka.stream.testkit.AkkaSpec
import akka.util.ByteString
import akka.http.scaladsl.model.headers.BasicHttpCredentials
class ModelSpec extends AkkaSpec {
"construct request" in {
//#construct-request
import HttpMethods._
// construct a simple GET request to `homeUri`
val homeUri = Uri("/abc")
HttpRequest(GET, uri = homeUri)
// construct simple GET request to "/index" (implicit string to Uri conversion)
HttpRequest(GET, uri = "/index")
// construct simple POST request containing entity
val data = ByteString("abc")
HttpRequest(POST, uri = "/receive", entity = data)
// customize every detail of HTTP request
import HttpProtocols._
import MediaTypes._
import HttpCharsets._
val userData = ByteString("abc")
val authorization = headers.Authorization(BasicHttpCredentials("user", "pass"))
HttpRequest(
PUT,
uri = "/user",
entity = HttpEntity(`text/plain` withCharset `UTF-8`, userData),
headers = List(authorization),
protocol = `HTTP/1.0`)
//#construct-request
}
"construct response" in {
//#construct-response
import StatusCodes._
// simple OK response without data created using the integer status code
HttpResponse(200)
// 404 response created using the named StatusCode constant
HttpResponse(NotFound)
// 404 response with a body explaining the error
HttpResponse(404, entity = "Unfortunately, the resource couldn't be found.")
// A redirecting response containing an extra header
val locationHeader = headers.Location("http://example.com/other")
HttpResponse(Found, headers = List(locationHeader))
//#construct-response
}
"deal with headers" in {
//#headers
import akka.http.scaladsl.model.headers._
// create a ``Location`` header
val loc = Location("http://example.com/other")
// create an ``Authorization`` header with HTTP Basic authentication data
val auth = Authorization(BasicHttpCredentials("joe", "josepp"))
// custom type
case class User(name: String, pass: String)
// a method that extracts basic HTTP credentials from a request
def credentialsOfRequest(req: HttpRequest): Option[User] =
for {
Authorization(BasicHttpCredentials(user, pass)) <- req.header[Authorization]
} yield User(user, pass)
//#headers
credentialsOfRequest(HttpRequest(headers = List(auth))) should be(Some(User("joe", "josepp")))
credentialsOfRequest(HttpRequest()) should be(None)
credentialsOfRequest(HttpRequest(headers = List(Authorization(GenericHttpCredentials("Other", Map.empty[String, String]))))) should be(None)
}
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
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 {
"spray-json example" in {
//#example
import spray.json._
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// collect your json format instances into a support trait:
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat1(Order) // contains List[Item]
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives with JsonSupport {
// format: OFF
val route =
get {
pathSingleSlash {
complete {
Item("thing", 42) // will render as JSON
}
}
} ~
post {
entity(as[Order]) { order => // will unmarshal JSON to Order
val itemsCount = order.items.size
val itemNames = order.items.map(_.name).mkString(", ")
complete(s"Ordered $itemsCount items: $itemNames")
}
}
// format: ON
//#
}
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
import akka.stream.{ Materializer, ActorMaterializer }
import akka.stream.testkit.AkkaSpec
class UnmarshalSpec extends AkkaSpec {
"use unmarshal" in {
import akka.http.scaladsl.unmarshalling.Unmarshal
import system.dispatcher // ExecutionContext
implicit val materializer: Materializer = ActorMaterializer()
import scala.concurrent.Await
import scala.concurrent.duration._
val intFuture = Unmarshal("42").to[Int]
val int = Await.result(intFuture, 1.second) // don't block in non-test code!
int shouldEqual 42
val boolFuture = Unmarshal("off").to[Boolean]
val bool = Await.result(boolFuture, 1.second) // don't block in non-test code!
bool shouldBe false
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
/*
import org.scalatest.Inside
import akka.http.scaladsl.server._
class CaseClassExtractionExamplesSpec extends RoutingSpec with Inside {
// FIXME: investigate why it doesn't work without this import
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet
// format: OFF
"example-1" in {
case class Color(red: Int, green: Int, blue: Int)
val route =
path("color") {
parameters('red.as[Int], 'green.as[Int], 'blue.as[Int]) { (red, green, blue) =>
val color = Color(red, green, blue)
// ... route working with the `color` instance
null // hide
}
}
Get("/color?red=1&green=2&blue=3") ~> route ~> check { responseAs[String] shouldEqual "Color(1,2,3)" } // hide
}
"example-2" in {
case class Color(red: Int, green: Int, blue: Int)
val route =
path("color") {
parameters('red.as[Int], 'green.as[Int], 'blue.as[Int]).as(Color) { color =>
// ... route working with the `color` instance
null // hide
}
}
Get("/color?red=1&green=2&blue=3") ~> route ~> check { responseAs[String] shouldEqual "Color(1,2,3)" } // hide
}
"example-3" in {
case class Color(name: String, red: Int, green: Int, blue: Int)
val route =
(path("color" / Segment) & parameters('r.as[Int], 'g.as[Int], 'b.as[Int]))
.as(Color) { color =>
// ... route working with the `color` instance
null // hide
}
Get("/color/abc?r=1&g=2&b=3") ~> route ~> check { responseAs[String] shouldEqual "Color(abc,1,2,3)" } // hide
}
//# example-4
case class Color(name: String, red: Int, green: Int, blue: Int) {
require(!name.isEmpty, "color name must not be empty")
require(0 <= red && red <= 255, "red color component must be between 0 and 255")
require(0 <= green && green <= 255, "green color component must be between 0 and 255")
require(0 <= blue && blue <= 255, "blue color component must be between 0 and 255")
}
//#
"example 4 test" in {
val route =
(path("color" / Segment) &
parameters('r.as[Int], 'g.as[Int], 'b.as[Int])).as(Color) { color =>
doSomethingWith(color) // route working with the Color instance
}
Get("/color/abc?r=1&g=2&b=3") ~> route ~> check {
responseAs[String] shouldEqual "Color(abc,1,2,3)"
}
Get("/color/abc?r=1&g=2&b=345") ~> route ~> check {
inside(rejection) {
case ValidationRejection("requirement failed: blue color component must be between 0 and 255", _) =>
}
}
}
def doSomethingWith(x: Any) = complete(x.toString)
}*/

View file

@ -0,0 +1,109 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
import akka.http.scaladsl.server._
import Directives._
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.scalatest._
class DirectiveExamplesSpec extends RoutingSpec {
// format: OFF
"example-1" in {
val route: Route =
path("order" / IntNumber) { id =>
get {
complete {
"Received GET request for order " + id
}
} ~
put {
complete {
"Received PUT request for order " + id
}
}
}
verify(route) // hide
}
"example-2" in {
def innerRoute(id: Int): Route =
get {
complete {
"Received GET request for order " + id
}
} ~
put {
complete {
"Received PUT request for order " + id
}
}
val route: Route = path("order" / IntNumber) { id => innerRoute(id) }
verify(route) // hide
}
"example-3" in {
val route =
path("order" / IntNumber) { id =>
(get | put) { ctx =>
ctx.complete(s"Received ${ctx.request.method.name} request for order $id")
}
}
verify(route) // hide
}
"example-4" in {
val route =
path("order" / IntNumber) { id =>
(get | put) {
extractMethod { m =>
complete(s"Received ${m.name} request for order $id")
}
}
}
verify(route) // hide
}
"example-5" in {
val getOrPut = get | put
val route =
path("order" / IntNumber) { id =>
getOrPut {
extractMethod { m =>
complete(s"Received ${m.name} request for order $id")
}
}
}
verify(route) // hide
}
"example-6" in {
val getOrPut = get | put
val route =
(path("order" / IntNumber) & getOrPut & extractMethod) { (id, m) =>
complete(s"Received ${m.name} request for order $id")
}
verify(route) // hide
}
"example-7" in {
val orderGetOrPutWithMethod =
path("order" / IntNumber) & (get | put) & extractMethod
val route =
orderGetOrPutWithMethod { (id, m) =>
complete(s"Received ${m.name} request for order $id")
}
verify(route) // hide
}
def verify(route: Route) = {
Get("/order/42") ~> route ~> check { responseAs[String] shouldEqual "Received GET request for order 42" }
Put("/order/42") ~> route ~> check { responseAs[String] shouldEqual "Received PUT request for order 42" }
Get("/") ~> route ~> check { handled shouldEqual false }
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
// format: OFF
object MyExplicitExceptionHandler {
//#explicit-handler-example
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._
val myExceptionHandler = ExceptionHandler {
case _: ArithmeticException =>
extractUri { uri =>
println(s"Request to $uri could not be handled normally")
complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
}
}
object MyApp extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route: Route =
handleExceptions(myExceptionHandler) {
// ... some route structure
null // hide
}
Http().bindAndHandle(route, "localhost", 8080)
}
//#
}
object MyImplicitExceptionHandler {
//#implicit-handler-example
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._
implicit def myExceptionHandler: ExceptionHandler =
ExceptionHandler {
case _: ArithmeticException =>
extractUri { uri =>
println(s"Request to $uri could not be handled normally")
complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
}
}
object MyApp extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route: Route =
// ... some route structure
null // hide
Http().bindAndHandle(route, "localhost", 8080)
}
//#
}
class ExceptionHandlerExamplesSpec extends RoutingSpec {
"test explicit example" in {
// tests:
Get() ~> handleExceptions(MyExplicitExceptionHandler.myExceptionHandler) {
_.complete((1 / 0).toString)
} ~> check {
responseAs[String] === "Bad numbers, bad result!!!"
}
}
"test implicit example" in {
import akka.http.scaladsl.server._
import MyImplicitExceptionHandler.myExceptionHandler
// tests:
Get() ~> Route.seal(ctx => ctx.complete((1 / 0).toString)) ~> check {
responseAs[String] === "Bad numbers, bad result!!!"
}
}
}

View file

@ -0,0 +1,96 @@
/**
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
import java.io.File
import akka.actor.ActorRef
import akka.http.scaladsl.model.Multipart.FormData.BodyPart
import akka.stream.io.{ Framing }
import akka.stream.scaladsl._
import akka.http.scaladsl.model.Multipart
import akka.util.ByteString
import scala.concurrent.duration._
import scala.concurrent.Future
class FileUploadExamplesSpec extends RoutingSpec {
case class Video(file: File, title: String, author: String)
object db {
def create(video: Video): Future[Unit] = Future.successful(Unit)
}
"simple-upload" in {
val uploadVideo =
path("video") {
entity(as[Multipart.FormData]) { formData =>
// collect all parts of the multipart as it arrives into a map
val allPartsF: Future[Map[String, Any]] = formData.parts.mapAsync[(String, Any)](1) {
case b: BodyPart if b.name == "file" =>
// 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.name -> file))
case b: BodyPart =>
// collect form field values
b.toStrict(2.seconds).map(strict =>
(b.name -> strict.entity.data.utf8String))
}.runFold(Map.empty[String, Any])((map, tuple) => map + tuple)
val done = allPartsF.map { allParts =>
// You would have some better validation/unmarshalling here
db.create(Video(
file = allParts("file").asInstanceOf[File],
title = allParts("title").asInstanceOf[String],
author = allParts("author").asInstanceOf[String]))
}
// when processing have finished create a response for the user
onSuccess(allPartsF) { allParts =>
complete {
"ok!"
}
}
}
}
}
object MetadataActor {
case class Entry(id: Long, values: Seq[String])
}
val metadataActor: ActorRef = system.deadLetters
"stream-csv-upload" in {
val splitLines = Framing.delimiter(ByteString("\n"), 256)
val csvUploads =
path("metadata" / LongNumber) { id =>
entity(as[Multipart.FormData]) { formData =>
val done = formData.parts.mapAsync(1) {
case b: BodyPart if b.filename.exists(_.endsWith(".csv")) =>
b.entity.dataBytes
.via(splitLines)
.map(_.utf8String.split(",").toVector)
.runForeach(csv =>
metadataActor ! MetadataActor.Entry(id, csv))
case _ => Future.successful(Unit)
}.runWith(Sink.ignore)
// when processing have finished create a response for the user
onSuccess(done) {
complete {
"ok!"
}
}
}
}
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
// format: OFF
//# source-quote
import org.scalatest.{ Matchers, WordSpec }
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.http.scaladsl.server._
import Directives._
class FullTestKitExampleSpec extends WordSpec with Matchers with ScalatestRouteTest {
val smallRoute =
get {
pathSingleSlash {
complete {
"Captain on the bridge!"
}
} ~
path("ping") {
complete("PONG!")
}
}
"The service" should {
"return a greeting for GET requests to the root path" in {
// tests:
Get() ~> smallRoute ~> check {
responseAs[String] shouldEqual "Captain on the bridge!"
}
}
"return a 'PONG!' response for GET requests to /ping" in {
// tests:
Get("/ping") ~> smallRoute ~> check {
responseAs[String] shouldEqual "PONG!"
}
}
"leave GET requests to other paths unhandled" in {
// tests:
Get("/kermit") ~> smallRoute ~> check {
handled shouldBe false
}
}
"return a MethodNotAllowed error for PUT requests to the root path" in {
// tests:
Put() ~> Route.seal(smallRoute) ~> check {
status === StatusCodes.MethodNotAllowed
responseAs[String] shouldEqual "HTTP method not allowed, supported methods: GET"
}
}
}
}
//#

View file

@ -0,0 +1,79 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
// format: OFF
object MyRejectionHandler {
//#custom-handler-example
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._
implicit def myRejectionHandler =
RejectionHandler.newBuilder()
.handle { case MissingCookieRejection(cookieName) =>
complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
}
.handle { case AuthorizationFailedRejection
complete((Forbidden, "You're out of your depth!"))
}
.handle { case ValidationRejection(msg, _)
complete((InternalServerError, "That wasn't valid! " + msg))
}
.handleAll[MethodRejection] { methodRejections
val names = methodRejections.map(_.supported.name)
complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
}
.handleNotFound { complete((NotFound, "Not here!")) }
.result()
object MyApp extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route: Route =
// ... some route structure
null // hide
Http().bindAndHandle(route, "localhost", 8080)
}
//#
}
class RejectionHandlerExamplesSpec extends RoutingSpec {
import MyRejectionHandler._
"example-1" in {
import akka.http.scaladsl.coding.Gzip
val route =
path("order") {
get {
complete("Received GET")
} ~
post {
decodeRequestWith(Gzip) {
complete("Received compressed POST")
}
}
}
}
"test custom handler example" in {
import akka.http.scaladsl.server._
val route = Route.seal(reject(MissingCookieRejection("abc")))
// tests:
Get() ~> route ~> check {
responseAs[String] === "No cookies, no service!!!"
}
}
}

View file

@ -0,0 +1,11 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.testkit.ScalatestRouteTest
import org.scalatest.{ Matchers, WordSpec }
abstract class RoutingSpec extends WordSpec with Matchers with Directives with ScalatestRouteTest

View file

@ -0,0 +1,109 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
import akka.http.scaladsl.model.ws.BinaryMessage
import akka.stream.scaladsl.Sink
import org.scalatest.{ Matchers, WordSpec }
class WebsocketExampleSpec extends WordSpec with Matchers {
"core-example" in {
pending // compile-time only test
//#websocket-example-using-core
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ Source, Flow }
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.UpgradeToWebsocket
import akka.http.scaladsl.model.ws.{ TextMessage, Message }
import akka.http.scaladsl.model.{ HttpResponse, Uri, HttpRequest }
import akka.http.scaladsl.model.HttpMethods._
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
//#websocket-handler
// The Greeter WebSocket Service expects a "name" per message and
// returns a greeting message for that name
val greeterWebsocketService =
Flow[Message]
.mapConcat {
// we match but don't actually consume the text message here,
// rather we simply stream it back as the tail of the response
// this means we might start sending the response even before the
// end of the incoming message has been received
case tm: TextMessage TextMessage(Source.single("Hello ") ++ tm.textStream) :: Nil
case bm: BinaryMessage =>
// ignore binary messages but drain content to avoid the stream being clogged
bm.dataStream.runWith(Sink.ignore)
Nil
}
//#websocket-handler
//#websocket-request-handling
val requestHandler: HttpRequest HttpResponse = {
case req @ HttpRequest(GET, Uri.Path("/greeter"), _, _, _)
req.header[UpgradeToWebsocket] match {
case Some(upgrade) upgrade.handleMessages(greeterWebsocketService)
case None HttpResponse(400, entity = "Not a valid websocket request!")
}
case _: HttpRequest HttpResponse(404, entity = "Unknown resource!")
}
//#websocket-request-handling
val bindingFuture =
Http().bindAndHandleSync(requestHandler, interface = "localhost", port = 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine()
import system.dispatcher // for the future transformations
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.shutdown()) // and shutdown when done
}
"routing-example" in {
pending // compile-time only test
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ Source, Flow }
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{ TextMessage, Message }
import akka.http.scaladsl.server.Directives
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import Directives._
// The Greeter WebSocket Service expects a "name" per message and
// returns a greeting message for that name
val greeterWebsocketService =
Flow[Message]
.collect {
case tm: TextMessage TextMessage(Source.single("Hello ") ++ tm.textStream)
// ignore binary messages
}
//#websocket-routing
val route =
path("greeter") {
get {
handleWebsocketMessages(greeterWebsocketService)
}
}
//#websocket-routing
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine()
import system.dispatcher // for the future transformations
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.shutdown()) // and shutdown when done
}
}

View file

@ -0,0 +1,803 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import java.io.File
import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.{ Server, RawHeader }
import akka.http.scaladsl.server.RouteResult.{ Complete, Rejected }
import akka.http.scaladsl.server._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ FileIO, Sink, Source }
import akka.util.ByteString
import docs.http.scaladsl.server.RoutingSpec
import scala.concurrent.Future
import scala.util.control.NonFatal
class BasicDirectivesExamplesSpec extends RoutingSpec {
"0extract" in {
//#0extract
val uriLength = extract(_.request.uri.toString.length)
val route =
uriLength { len =>
complete(s"The length of the request URI is $len")
}
// tests:
Get("/abcdef") ~> route ~> check {
responseAs[String] shouldEqual "The length of the request URI is 25"
}
//#
}
"0extractLog" in {
//#0extractLog
val route =
extractLog { log =>
log.debug("I'm logging things in much detail..!")
complete("It's amazing!")
}
// tests:
Get("/abcdef") ~> route ~> check {
responseAs[String] shouldEqual "It's amazing!"
}
//#
}
"withMaterializer-0" in {
//#withMaterializer-0
val special = ActorMaterializer(namePrefix = Some("special"))
def sample() =
path("sample") {
extractMaterializer { mat =>
complete {
// explicitly use the materializer:
Source.single(s"Materialized by ${mat.##}!")
.runWith(Sink.head)(mat)
}
}
}
val route =
pathPrefix("special") {
withMaterializer(special) {
sample() // `special` materializer will be used
}
} ~ sample() // default materializer will be used
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${materializer.##}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${special.##}!"
}
//#
}
"extractMaterializer-0" in {
//#extractMaterializer-0
val route =
path("sample") {
extractMaterializer { materializer =>
complete {
// explicitly use the `materializer`:
Source.single(s"Materialized by ${materializer.##}!")
.runWith(Sink.head)(materializer)
}
}
} // default materializer will be used
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${materializer.##}!"
}
//#
}
"withExecutionContext-0" in compileOnlySpec {
//#withExecutionContext-0
val special = system.dispatchers.lookup("special")
def sample() =
path("sample") {
extractExecutionContext { implicit ec =>
complete {
Future(s"Run on ${ec.##}!") // uses the `ec` ExecutionContext
}
}
}
val route =
pathPrefix("special") {
withExecutionContext(special) {
sample() // `special` execution context will be used
}
} ~ sample() // default execution context will be used
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${system.dispatcher.##}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${special.##}!"
}
//#
}
"extractExecutionContext-0" in compileOnlySpec {
//#extractExecutionContext-0
def sample() =
path("sample") {
extractExecutionContext { implicit ec =>
complete {
Future(s"Run on ${ec.##}!") // uses the `ec` ExecutionContext
}
}
}
val route =
pathPrefix("special") {
sample() // default execution context will be used
}
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${system.dispatcher.##}!"
}
//#
}
"0withLog" in {
//#0withLog
val special = Logging(system, "SpecialRoutes")
def sample() =
path("sample") {
extractLog { implicit log =>
complete {
val msg = s"Logging using $log!"
log.debug(msg)
msg
}
}
}
val route =
pathPrefix("special") {
withLog(special) {
sample() // `special` logging adapter will be used
}
} ~ sample() // default logging adapter will be used
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Logging using ${system.log}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Logging using $special!"
}
//#
}
"withSettings-0" in compileOnlySpec {
//#withSettings-0
val special = RoutingSettings(system).copy(fileIODispatcher = "special-io-dispatcher")
def sample() =
path("sample") {
complete {
// internally uses the configured fileIODispatcher:
val source = FileIO.fromFile(new File("example.json"))
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, source))
}
}
val route =
get {
pathPrefix("special") {
withSettings(special) {
sample() // `special` file-io-dispatcher will be used to read the file
}
} ~ sample() // default file-io-dispatcher will be used to read the file
}
// tests:
Post("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"{}"
}
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual "{}"
}
//#
}
"textract" in {
//#textract
val pathAndQuery = textract { ctx =>
val uri = ctx.request.uri
(uri.path, uri.query())
}
val route =
pathAndQuery { (p, query) =>
complete(s"The path is $p and the query is $query")
}
// tests:
Get("/abcdef?ghi=12") ~> route ~> check {
responseAs[String] shouldEqual "The path is /abcdef and the query is ghi=12"
}
//#
}
"tprovide" in {
//#tprovide
def provideStringAndLength(value: String) = tprovide((value, value.length))
val route =
provideStringAndLength("test") { (value, len) =>
complete(s"Value is $value and its length is $len")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Value is test and its length is 4"
}
//#
}
"0mapResponse" in {
//#0mapResponse
def overwriteResultStatus(response: HttpResponse): HttpResponse =
response.copy(status = StatusCodes.BadGateway)
val route = mapResponse(overwriteResultStatus)(complete("abc"))
// tests:
Get("/abcdef?ghi=12") ~> route ~> check {
status shouldEqual StatusCodes.BadGateway
}
//#
}
"1mapResponse-advanced-json" in {
//#1mapResponse-advanced
trait ApiRoutes {
protected def system: ActorSystem
private val log = Logging(system, "ApiRoutes")
private val NullJsonEntity = HttpEntity(ContentTypes.`application/json`, "{}")
private def nonSuccessToEmptyJsonEntity(response: HttpResponse): HttpResponse =
response.status match {
case code if code.isSuccess response
case code
log.warning("Dropping response entity since response status code was: {}", code)
response.copy(entity = NullJsonEntity)
}
/** Wrapper for all of our JSON API routes */
def apiRoute(innerRoutes: Route): Route =
mapResponse(nonSuccessToEmptyJsonEntity)(innerRoutes)
}
//#
import StatusCodes._
val __system = system
val routes = new ApiRoutes {
override protected def system = __system
}
import routes.apiRoute
//#1mapResponse-advanced
val route: Route =
apiRoute {
get {
complete(InternalServerError)
}
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "{}"
}
//#
}
"mapRouteResult" in {
//#mapRouteResult
// this directive is a joke, don't do that :-)
val makeEverythingOk = mapRouteResult { r =>
r match {
case Complete(response) =>
// "Everything is OK!"
Complete(response.copy(status = 200))
case _ => r
}
}
val route =
makeEverythingOk {
// will actually render as 200 OK (!)
complete(StatusCodes.Accepted)
}
// tests:
Get("/") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
//#
}
"mapRouteResultFuture" in {
//#mapRouteResultFuture
val tryRecoverAddServer = mapRouteResultFuture { fr =>
fr recover {
case ex: IllegalArgumentException =>
Complete(HttpResponse(StatusCodes.InternalServerError))
} map {
case Complete(res) => Complete(res.addHeader(Server("MyServer 1.0")))
case rest => rest
}
}
val route =
tryRecoverAddServer {
complete("Hello world!")
}
// tests:
Get("/") ~> route ~> check {
status shouldEqual StatusCodes.OK
header[Server] shouldEqual Some(Server("MyServer 1.0"))
}
//#
}
"mapResponseEntity" in {
//#mapResponseEntity
def prefixEntity(entity: ResponseEntity): ResponseEntity = entity match {
case HttpEntity.Strict(contentType, data) =>
HttpEntity.Strict(contentType, ByteString("test") ++ data)
case _ => throw new IllegalStateException("Unexpected entity type")
}
val prefixWithTest: Directive0 = mapResponseEntity(prefixEntity)
val route = prefixWithTest(complete("abc"))
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "testabc"
}
//#
}
"mapResponseHeaders" in {
//#mapResponseHeaders
// adds all request headers to the response
val echoRequestHeaders = extract(_.request.headers).flatMap(respondWithHeaders)
val removeIdHeader = mapResponseHeaders(_.filterNot(_.lowercaseName == "id"))
val route =
removeIdHeader {
echoRequestHeaders {
complete("test")
}
}
// tests:
Get("/") ~> RawHeader("id", "12345") ~> RawHeader("id2", "67890") ~> route ~> check {
header("id") shouldEqual None
header("id2").get.value shouldEqual "67890"
}
//#
}
"mapInnerRoute" in {
//#mapInnerRoute
val completeWithInnerException =
mapInnerRoute { route =>
ctx =>
try {
route(ctx)
} catch {
case NonFatal(e) => ctx.complete(s"Got ${e.getClass.getSimpleName} '${e.getMessage}'")
}
}
val route =
completeWithInnerException {
complete(throw new IllegalArgumentException("BLIP! BLOP! Everything broke"))
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Got IllegalArgumentException 'BLIP! BLOP! Everything broke'"
}
//#
}
"mapRejections" in {
//#mapRejections
// ignore any rejections and replace them by AuthorizationFailedRejection
val replaceByAuthorizationFailed = mapRejections(_ => List(AuthorizationFailedRejection))
val route =
replaceByAuthorizationFailed {
path("abc")(complete("abc"))
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
Get("/abc") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
//#
}
"recoverRejections" in {
//#recoverRejections
val authRejectionsToNothingToSeeHere = recoverRejections { rejections =>
if (rejections.exists(_.isInstanceOf[AuthenticationFailedRejection]))
Complete(HttpResponse(entity = "Nothing to see here, move along."))
else if (rejections == Nil) // see "Empty Rejections" for more details
Complete(HttpResponse(StatusCodes.NotFound, entity = "Literally nothing to see here."))
else
Rejected(rejections)
}
val neverAuth: Authenticator[String] = creds => None
val alwaysAuth: Authenticator[String] = creds => Some("id")
val route =
authRejectionsToNothingToSeeHere {
pathPrefix("auth") {
path("never") {
authenticateBasic("my-realm", neverAuth) { user =>
complete("Welcome to the bat-cave!")
}
} ~
path("always") {
authenticateBasic("my-realm", alwaysAuth) { user =>
complete("Welcome to the secret place!")
}
}
}
}
// tests:
Get("/auth/never") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Nothing to see here, move along."
}
Get("/auth/always") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Welcome to the secret place!"
}
Get("/auth/does_not_exist") ~> route ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Literally nothing to see here."
}
//#
}
"recoverRejectionsWith" in {
//#recoverRejectionsWith
val authRejectionsToNothingToSeeHere = recoverRejectionsWith { rejections =>
Future {
// imagine checking rejections takes a longer time:
if (rejections.exists(_.isInstanceOf[AuthenticationFailedRejection]))
Complete(HttpResponse(entity = "Nothing to see here, move along."))
else
Rejected(rejections)
}
}
val neverAuth: Authenticator[String] = creds => None
val route =
authRejectionsToNothingToSeeHere {
pathPrefix("auth") {
path("never") {
authenticateBasic("my-realm", neverAuth) { user =>
complete("Welcome to the bat-cave!")
}
}
}
}
// tests:
Get("/auth/never") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Nothing to see here, move along."
}
//#
}
"0mapRequest" in {
//#0mapRequest
def transformToPostRequest(req: HttpRequest): HttpRequest = req.copy(method = HttpMethods.POST)
val route =
mapRequest(transformToPostRequest) {
extractRequest { req =>
complete(s"The request method was ${req.method.name}")
}
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The request method was POST"
}
//#
}
"mapRequestContext" in {
//#mapRequestContext
val replaceRequest =
mapRequestContext(_.withRequest(HttpRequest(HttpMethods.POST)))
val route =
replaceRequest {
extractRequest { req =>
complete(req.method.value)
}
}
// tests:
Get("/abc/def/ghi") ~> route ~> check {
responseAs[String] shouldEqual "POST"
}
//#
}
"0mapRouteResult" in {
//#0mapRouteResult
val rejectAll = // not particularly useful directive
mapRouteResult {
case _ => Rejected(List(AuthorizationFailedRejection))
}
val route =
rejectAll {
complete("abc")
}
// tests:
Get("/") ~> route ~> check {
rejections.nonEmpty shouldEqual true
}
//#
}
"mapRouteResultPF" in {
//#mapRouteResultPF
case object MyCustomRejection extends Rejection
val rejectRejections = // not particularly useful directive
mapRouteResultPF {
case Rejected(_) => Rejected(List(AuthorizationFailedRejection))
}
val route =
rejectRejections {
reject(MyCustomRejection)
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
//#
}
"mapRouteResultWithPF-0" in {
//#mapRouteResultWithPF-0
case object MyCustomRejection extends Rejection
val rejectRejections = // not particularly useful directive
mapRouteResultWithPF {
case Rejected(_) => Future(Rejected(List(AuthorizationFailedRejection)))
}
val route =
rejectRejections {
reject(MyCustomRejection)
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
//#
}
"mapRouteResultWith-0" in {
//#mapRouteResultWith-0
case object MyCustomRejection extends Rejection
val rejectRejections = // not particularly useful directive
mapRouteResultWith { res =>
res match {
case Rejected(_) => Future(Rejected(List(AuthorizationFailedRejection)))
case _ => Future(res)
}
}
val route =
rejectRejections {
reject(MyCustomRejection)
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
//#
}
"pass" in {
//#pass
val route = pass(complete("abc"))
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "abc"
}
//#
}
"0provide" in {
//#0provide
def providePrefixedString(value: String): Directive1[String] = provide("prefix:" + value)
val route =
providePrefixedString("test") { value =>
complete(value)
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "prefix:test"
}
//#
}
"cancelRejections-filter-example" in {
//#cancelRejections-filter-example
def isMethodRejection: Rejection => Boolean = {
case MethodRejection(_) => true
case _ => false
}
val route =
cancelRejections(isMethodRejection) {
post {
complete("Result")
}
}
// tests:
Get("/") ~> route ~> check {
rejections shouldEqual Nil
handled shouldEqual false
}
//#
}
"cancelRejection-example" in {
//#cancelRejection-example
val route =
cancelRejection(MethodRejection(HttpMethods.POST)) {
post {
complete("Result")
}
}
// tests:
Get("/") ~> route ~> check {
rejections shouldEqual Nil
handled shouldEqual false
}
//#
}
"extractRequest-example" in {
//#extractRequest-example
val route =
extractRequest { request =>
complete(s"Request method is ${request.method.name} and content-type is ${request.entity.contentType}")
}
// tests:
Post("/", "text") ~> route ~> check {
responseAs[String] shouldEqual "Request method is POST and content-type is text/plain; charset=UTF-8"
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Request method is GET and content-type is none/none"
}
//#
}
"extractSettings-examples" in {
//#extractSettings-examples
val route =
extractSettings { settings: RoutingSettings =>
complete(s"RoutingSettings.renderVanityFooter = ${settings.renderVanityFooter}")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual s"RoutingSettings.renderVanityFooter = true"
}
//#
}
"mapSettings-examples" in {
//#mapSettings-examples
val tunedSettings = mapSettings { settings =>
settings.copy(fileGetConditional = false)
}
val route =
tunedSettings {
extractSettings { settings: RoutingSettings =>
complete(s"RoutingSettings.fileGetConditional = ${settings.fileGetConditional}")
}
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual s"RoutingSettings.fileGetConditional = false"
}
//#
}
"extractRequestContext-example" in {
//#extractRequestContext-example
val route =
extractRequestContext { ctx =>
ctx.log.debug("Using access to additional context availablethings, like the logger.")
val request = ctx.request
complete(s"Request method is ${request.method.name} and content-type is ${request.entity.contentType}")
}
// tests:
Post("/", "text") ~> route ~> check {
responseAs[String] shouldEqual "Request method is POST and content-type is text/plain; charset=UTF-8"
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Request method is GET and content-type is none/none"
}
//#
}
"extractUri-example" in {
//#extractUri-example
val route =
extractUri { uri =>
complete(s"Full URI: $uri")
}
// tests:
Get("/") ~> route ~> check {
// tests are executed with the host assumed to be "example.com"
responseAs[String] shouldEqual "Full URI: http://example.com/"
}
Get("/test") ~> route ~> check {
responseAs[String] shouldEqual "Full URI: http://example.com/test"
}
//#
}
"mapUnmatchedPath-example" in {
//#mapUnmatchedPath-example
def ignore456(path: Uri.Path) = path match {
case s @ Uri.Path.Segment(head, tail) if head.startsWith("456") =>
val newHead = head.drop(3)
if (newHead.isEmpty) tail
else s.copy(head = head.drop(3))
case _ => path
}
val ignoring456 = mapUnmatchedPath(ignore456)
val route =
pathPrefix("123") {
ignoring456 {
path("abc") {
complete(s"Content")
}
}
}
// tests:
Get("/123/abc") ~> route ~> check {
responseAs[String] shouldEqual "Content"
}
Get("/123456/abc") ~> route ~> check {
responseAs[String] shouldEqual "Content"
}
//#
}
"extractUnmatchedPath-example" in {
//#extractUnmatchedPath-example
val route =
pathPrefix("abc") {
extractUnmatchedPath { remaining =>
complete(s"Unmatched: '$remaining'")
}
}
// tests:
Get("/abc") ~> route ~> check {
responseAs[String] shouldEqual "Unmatched: ''"
}
Get("/abc/456") ~> route ~> check {
responseAs[String] shouldEqual "Unmatched: '/456'"
}
//#
}
private def compileOnlySpec(block: => Unit) = pending
}

View file

@ -0,0 +1,126 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.coding._
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.{ HttpResponse, StatusCodes }
import akka.http.scaladsl.model.headers.{ HttpEncodings, HttpEncoding, `Accept-Encoding`, `Content-Encoding` }
import akka.http.scaladsl.model.headers.HttpEncodings._
import akka.http.scaladsl.server._
import akka.util.ByteString
import org.scalatest.matchers.Matcher
class CodingDirectivesExamplesSpec extends RoutingSpec {
"responseEncodingAccepted" in {
val route = responseEncodingAccepted(gzip) { complete("content") }
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "content"
}
Get("/") ~> `Accept-Encoding`(deflate) ~> route ~> check {
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
}
}
"encodeResponse" in {
val route = encodeResponse { complete("content") }
// tests:
Get("/") ~> route ~> check {
response should haveContentEncoding(identity)
}
Get("/") ~> `Accept-Encoding`(gzip, deflate) ~> route ~> check {
response should haveContentEncoding(gzip)
}
Get("/") ~> `Accept-Encoding`(deflate) ~> route ~> check {
response should haveContentEncoding(deflate)
}
Get("/") ~> `Accept-Encoding`(identity) ~> route ~> check {
response should haveContentEncoding(identity)
}
}
"encodeResponseWith" in {
val route = encodeResponseWith(Gzip) { complete("content") }
// tests:
Get("/") ~> route ~> check {
response should haveContentEncoding(gzip)
}
Get("/") ~> `Accept-Encoding`(gzip, deflate) ~> route ~> check {
response should haveContentEncoding(gzip)
}
Get("/") ~> `Accept-Encoding`(deflate) ~> route ~> check {
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
}
Get("/") ~> `Accept-Encoding`(identity) ~> route ~> check {
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
}
}
val helloGzipped = compress("Hello", Gzip)
val helloDeflated = compress("Hello", Deflate)
"decodeRequest" in {
val route =
decodeRequest {
entity(as[String]) { content: String =>
complete(s"Request content: '$content'")
}
}
// tests:
Post("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", helloDeflated) ~> `Content-Encoding`(deflate) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", "hello uncompressed") ~> `Content-Encoding`(identity) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'hello uncompressed'"
}
}
"decodeRequestWith-0" in {
val route =
decodeRequestWith(Gzip) {
entity(as[String]) { content: String =>
complete(s"Request content: '$content'")
}
}
// tests:
Post("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", helloDeflated) ~> `Content-Encoding`(deflate) ~> route ~> check {
rejection shouldEqual UnsupportedRequestEncodingRejection(gzip)
}
Post("/", "hello") ~> `Content-Encoding`(identity) ~> route ~> check {
rejection shouldEqual UnsupportedRequestEncodingRejection(gzip)
}
}
"decodeRequestWith-1" in {
val route =
decodeRequestWith(Gzip, NoCoding) {
entity(as[String]) { content: String =>
complete(s"Request content: '$content'")
}
}
// tests:
Post("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", helloDeflated) ~> `Content-Encoding`(deflate) ~> route ~> check {
rejections shouldEqual List(UnsupportedRequestEncodingRejection(gzip), UnsupportedRequestEncodingRejection(identity))
}
Post("/", "hello uncompressed") ~> `Content-Encoding`(identity) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'hello uncompressed'"
}
}
def haveContentEncoding(encoding: HttpEncoding): Matcher[HttpResponse] =
be(encoding) compose { (_: HttpResponse).header[`Content-Encoding`].map(_.encodings.head).getOrElse(HttpEncodings.identity) }
def compress(input: String, encoder: Encoder): ByteString = encoder.encode(ByteString(input))
}

View file

@ -0,0 +1,70 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.server._
import akka.http.scaladsl.model.headers.{ HttpCookie, Cookie, `Set-Cookie` }
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.DateTime
class CookieDirectivesExamplesSpec extends RoutingSpec {
"cookie" in {
val route =
cookie("userName") { nameCookie =>
complete(s"The logged in user is '${nameCookie.value}'")
}
// tests:
Get("/") ~> Cookie("userName" -> "paul") ~> route ~> check {
responseAs[String] shouldEqual "The logged in user is 'paul'"
}
// missing cookie
Get("/") ~> route ~> check {
rejection shouldEqual MissingCookieRejection("userName")
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "Request is missing required cookie 'userName'"
}
}
"optionalCookie" in {
val route =
optionalCookie("userName") {
case Some(nameCookie) => complete(s"The logged in user is '${nameCookie.value}'")
case None => complete("No user logged in")
}
// tests:
Get("/") ~> Cookie("userName" -> "paul") ~> route ~> check {
responseAs[String] shouldEqual "The logged in user is 'paul'"
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "No user logged in"
}
}
"deleteCookie" in {
val route =
deleteCookie("userName") {
complete("The user was logged out")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The user was logged out"
header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", value = "deleted", expires = Some(DateTime.MinValue))))
}
}
"setCookie" in {
val route =
setCookie(HttpCookie("userName", value = "paul")) {
complete("The user was logged in")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The user was logged in"
header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", value = "paul")))
}
}
}

View file

@ -0,0 +1,72 @@
/**
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.server.{ Directive1, Directive }
import docs.http.scaladsl.server.RoutingSpec
class CustomDirectivesExamplesSpec extends RoutingSpec {
"labeling" in {
val getOrPut = get | put
// tests:
val route = getOrPut { complete("ok") }
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "ok"
}
Put("/") ~> route ~> check {
responseAs[String] shouldEqual "ok"
}
}
"map-0" in {
val textParam: Directive1[String] =
parameter("text".as[String])
val lengthDirective: Directive1[Int] =
textParam.map(text => text.length)
// tests:
Get("/?text=abcdefg") ~> lengthDirective(x => complete(x.toString)) ~> check {
responseAs[String] === "7"
}
}
"tmap-1" in {
val twoIntParameters: Directive[(Int, Int)] =
parameters(("a".as[Int], "b".as[Int]))
val myDirective: Directive1[String] =
twoIntParameters.tmap {
case (a, b) => (a + b).toString
}
// tests:
Get("/?a=2&b=5") ~> myDirective(x => complete(x)) ~> check {
responseAs[String] === "7"
}
}
"flatMap-0" in {
val intParameter: Directive1[Int] = parameter("a".as[Int])
val myDirective: Directive1[Int] =
intParameter.flatMap {
case a if a > 0 => provide(2 * a)
case _ => reject
}
// tests:
Get("/?a=21") ~> myDirective(i => complete(i.toString)) ~> check {
responseAs[String] === "42"
}
Get("/?a=-18") ~> myDirective(i => complete(i.toString)) ~> check {
handled === false
}
}
}

View file

@ -0,0 +1,97 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.event.Logging
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse }
import akka.http.scaladsl.server.directives.{ DebuggingDirectives, LogEntry, LoggingMagnet }
import docs.http.scaladsl.server.RoutingSpec
class DebuggingDirectivesExamplesSpec extends RoutingSpec {
"logRequest-0" in {
// different possibilities of using logRequest
// The first alternatives use an implicitly available LoggingContext for logging
// marks with "get-user", log with debug level, HttpRequest.toString
DebuggingDirectives.logRequest("get-user")
// marks with "get-user", log with info level, HttpRequest.toString
DebuggingDirectives.logRequest(("get-user", Logging.InfoLevel))
// logs just the request method at debug level
def requestMethod(req: HttpRequest): String = req.method.toString
DebuggingDirectives.logRequest(requestMethod _)
// logs just the request method at info level
def requestMethodAsInfo(req: HttpRequest): LogEntry = LogEntry(req.method.toString, 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)
val logRequestPrintln = DebuggingDirectives.logRequest(LoggingMagnet(_ => printRequestMethod))
// tests:
Get("/") ~> logRequestPrintln(complete("logged")) ~> check {
responseAs[String] shouldEqual "logged"
}
}
"logRequestResult" in {
// different possibilities of using logRequestResponse
// The first alternatives use an implicitly available LoggingContext for logging
// marks with "get-user", log with debug level, HttpRequest.toString, HttpResponse.toString
DebuggingDirectives.logRequestResult("get-user")
// marks with "get-user", log with info level, HttpRequest.toString, HttpResponse.toString
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
}
DebuggingDirectives.logRequestResult(requestMethodAndResponseStatusAsInfo _)
// This one doesn't use the implicit LoggingContext but uses `println` for logging
def printRequestMethodAndResponseStatus(req: HttpRequest)(res: Any): Unit =
println(requestMethodAndResponseStatusAsInfo(req)(res).map(_.obj.toString).getOrElse(""))
val logRequestResultPrintln = DebuggingDirectives.logRequestResult(LoggingMagnet(_ => printRequestMethodAndResponseStatus))
// tests:
Get("/") ~> logRequestResultPrintln(complete("logged")) ~> check {
responseAs[String] shouldEqual "logged"
}
}
"logResult" in {
// different possibilities of using logResponse
// The first alternatives use an implicitly available LoggingContext for logging
// marks with "get-user", log with debug level, HttpResponse.toString
DebuggingDirectives.logResult("get-user")
// marks with "get-user", log with info level, HttpResponse.toString
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"
}
DebuggingDirectives.logResult(responseStatus _)
// logs just the response status at info level
def responseStatusAsInfo(res: Any): 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))
val logResultPrintln = DebuggingDirectives.logResult(LoggingMagnet(_ => printResponseStatus))
// tests:
Get("/") ~> logResultPrintln(complete("logged")) ~> check {
responseAs[String] shouldEqual "logged"
}
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server._
import docs.http.scaladsl.server.RoutingSpec
class ExecutionDirectivesExamplesSpec extends RoutingSpec {
"handleExceptions" in {
val divByZeroHandler = ExceptionHandler {
case _: ArithmeticException => complete((StatusCodes.BadRequest, "You've got your arithmetic wrong, fool!"))
}
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
handleExceptions(divByZeroHandler) {
complete(s"The result is ${a / b}")
}
}
// tests:
Get("/divide/10/5") ~> route ~> check {
responseAs[String] shouldEqual "The result is 2"
}
Get("/divide/10/0") ~> route ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "You've got your arithmetic wrong, fool!"
}
}
"handleRejections" in {
val totallyMissingHandler = RejectionHandler.newBuilder()
.handleNotFound { complete((StatusCodes.NotFound, "Oh man, what you are looking for is long gone.")) }
.handle { case ValidationRejection(msg, _) => complete((StatusCodes.InternalServerError, msg)) }
.result()
val route =
pathPrefix("handled") {
handleRejections(totallyMissingHandler) {
path("existing")(complete("This path exists")) ~
path("boom")(reject(new ValidationRejection("This didn't work.")))
}
}
// tests:
Get("/handled/existing") ~> route ~> check {
responseAs[String] shouldEqual "This path exists"
}
Get("/missing") ~> Route.seal(route) /* applies default handler */ ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "The requested resource could not be found."
}
Get("/handled/missing") ~> route ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Oh man, what you are looking for is long gone."
}
Get("/handled/boom") ~> route ~> check {
status shouldEqual StatusCodes.InternalServerError
responseAs[String] shouldEqual "This didn't work."
}
}
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.directives.DirectoryListing
import akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer
import docs.http.scaladsl.server.RoutingSpec
import docs.http.scaladsl.server.RoutingSpec
class FileAndResourceDirectivesExamplesSpec extends RoutingSpec {
"getFromFile-examples" in compileOnlySpec {
import akka.http.scaladsl.server.directives._
import ContentTypeResolver.Default
val route =
path("logs" / Segment) { name =>
getFromFile(".log") // uses implicit ContentTypeResolver
}
// tests:
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"getFromResource-examples" in compileOnlySpec {
import akka.http.scaladsl.server.directives._
import ContentTypeResolver.Default
val route =
path("logs" / Segment) { name =>
getFromResource(".log") // uses implicit ContentTypeResolver
}
// tests:
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"listDirectoryContents-examples" in compileOnlySpec {
val route =
path("tmp") {
listDirectoryContents("/tmp")
} ~
path("custom") {
val renderer = new DirectoryRenderer {
override def marshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing] = ???
}
listDirectoryContents("/tmp")(renderer)
}
// tests:
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"getFromBrowseableDirectory-examples" in compileOnlySpec {
val route =
path("tmp") {
getFromBrowseableDirectory("/tmp")
}
// tests:
Get("/tmp") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
"getFromBrowseableDirectories-examples" in compileOnlySpec {
val route =
path("tmp") {
getFromBrowseableDirectories("/main", "/backups")
}
// tests:
Get("/tmp") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
"getFromDirectory-examples" in compileOnlySpec {
val route =
path("tmp") {
getFromDirectory("/tmp")
}
// tests:
Get("/tmp/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"getFromResourceDirectory-examples" in compileOnlySpec {
val route =
path("examples") {
getFromResourceDirectory("/examples")
}
// tests:
Get("/examples/example-1") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
private def compileOnlySpec(block: => Unit) = pending
}

View file

@ -0,0 +1,77 @@
/**
* Copyright (C) 2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.stream.io.Framing
import akka.util.ByteString
import docs.http.scaladsl.server.RoutingSpec
import scala.concurrent.Future
class FileUploadDirectivesExamplesSpec extends RoutingSpec {
override def testConfigSource = "akka.actor.default-mailbox.mailbox-type = \"akka.dispatch.UnboundedMailbox\""
"uploadedFile" in {
val route =
uploadedFile("csv") {
case (metadata, file) =>
// do something with the file and file metadata ...
file.delete()
complete(StatusCodes.OK)
}
// tests:
val multipartForm =
Multipart.FormData(
Multipart.FormData.BodyPart.Strict(
"csv",
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "1,5,7\n11,13,17"),
Map("filename" -> "data.csv")))
Post("/", multipartForm) ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
"fileUpload" in {
// adding integers as a service ;)
val route =
extractRequestContext { ctx =>
implicit val materializer = ctx.materializer
implicit val ec = ctx.executionContext
fileUpload("csv") {
case (metadata, byteSource) =>
val sumF: Future[Int] =
// sum the numbers as they arrive so that we can
// accept any size of file
byteSource.via(Framing.delimiter(ByteString("\n"), 1024))
.mapConcat(_.utf8String.split(",").toVector)
.map(_.toInt)
.runFold(0) { (acc, n) => acc + n }
onSuccess(sumF) { sum => complete(s"Sum: $sum") }
}
}
// tests:
val multipartForm =
Multipart.FormData(Multipart.FormData.BodyPart.Strict(
"csv",
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "2,3,5\n7,11,13,17,23\n29,31,37\n"),
Map("filename" -> "primes.csv")))
Post("/", multipartForm) ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Sum: 178"
}
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.model._
import docs.http.scaladsl.server.RoutingSpec
class FormFieldDirectivesExamplesSpec extends RoutingSpec {
"formFields" in {
val route =
formFields('color, 'age.as[Int]) { (color, age) =>
complete(s"The color is '$color' and the age ten years ago was ${age - 10}")
}
// tests:
Post("/", FormData("color" -> "blue", "age" -> "68")) ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the age ten years ago was 58"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "Request is missing required form field 'color'"
}
}
"formField" in {
val route =
formField('color) { color =>
complete(s"The color is '$color'")
} ~
formField('id.as[Int]) { id =>
complete(s"The id is '$id'")
}
// tests:
Post("/", FormData("color" -> "blue")) ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue'"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "Request is missing required form field 'color'"
}
}
}

View file

@ -0,0 +1,103 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import java.util.concurrent.TimeUnit
import docs.http.scaladsl.server.RoutingSpec
import scala.concurrent.Future
import scala.util.{ Success, Failure }
import akka.http.scaladsl.server.ExceptionHandler
import akka.actor.{ Actor, Props }
import akka.util.Timeout
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Route
import StatusCodes._
// format: OFF
class FutureDirectivesExamplesSpec extends RoutingSpec {
object TestException extends Throwable
implicit val myExceptionHandler =
ExceptionHandler {
case TestException => ctx =>
ctx.complete((InternalServerError, "Unsuccessful future!"))
}
implicit val responseTimeout = Timeout(2, TimeUnit.SECONDS)
"onComplete" in {
def divide(a: Int, b: Int): Future[Int] = Future {
a / b
}
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
// tests:
Get("/divide/10/2") ~> route ~> check {
responseAs[String] shouldEqual "The result was 5"
}
Get("/divide/10/0") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "An error occurred: / by zero"
}
}
"onSuccess" in {
val route =
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
} ~
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
// tests:
Get("/success") ~> route ~> check {
responseAs[String] shouldEqual "Ok"
}
Get("/failure") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "Unsuccessful future!"
}
}
"completeOrRecoverWith" in {
val route =
path("success") {
completeOrRecoverWith(Future { "Ok" }) { extraction =>
failWith(extraction) // not executed.
}
} ~
path("failure") {
completeOrRecoverWith(Future.failed[String](TestException)) { extraction =>
failWith(extraction)
}
}
// tests:
Get("/success") ~> route ~> check {
responseAs[String] shouldEqual "Ok"
}
Get("/failure") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "Unsuccessful future!"
}
}
}

View file

@ -0,0 +1,188 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.MissingHeaderRejection
import akka.http.scaladsl.server.Route
import docs.http.scaladsl.server.RoutingSpec
import headers._
import StatusCodes._
import org.scalatest.Inside
class HeaderDirectivesExamplesSpec extends RoutingSpec with Inside {
"headerValueByName-0" in {
val route =
headerValueByName("X-User-Id") { userId =>
complete(s"The user is $userId")
}
// tests:
Get("/") ~> RawHeader("X-User-Id", "Joe42") ~> route ~> check {
responseAs[String] shouldEqual "The user is Joe42"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual BadRequest
responseAs[String] shouldEqual "Request is missing required HTTP header 'X-User-Id'"
}
}
"headerValue-0" in {
def extractHostPort: HttpHeader => Option[Int] = {
case h: `Host` => Some(h.port)
case x => None
}
val route =
headerValue(extractHostPort) { port =>
complete(s"The port was $port")
}
// tests:
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual NotFound
responseAs[String] shouldEqual "The requested resource could not be found."
}
}
"optionalHeaderValue-0" in {
def extractHostPort: HttpHeader => Option[Int] = {
case h: `Host` => Some(h.port)
case x => None
}
val route =
optionalHeaderValue(extractHostPort) {
case Some(port) => complete(s"The port was $port")
case None => complete(s"The port was not provided explicitly")
} ~ // can also be written as:
optionalHeaderValue(extractHostPort) { port =>
complete {
port match {
case Some(p) => s"The port was $p"
case _ => "The port was not provided explicitly"
}
}
}
// tests:
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "The port was not provided explicitly"
}
}
"optionalHeaderValueByName-0" in {
val route =
optionalHeaderValueByName("X-User-Id") {
case Some(userId) => complete(s"The user is $userId")
case None => complete(s"No user was provided")
} ~ // can also be written as:
optionalHeaderValueByName("port") { port =>
complete {
port match {
case Some(p) => s"The user is $p"
case _ => "No user was provided"
}
}
}
// tests:
Get("/") ~> RawHeader("X-User-Id", "Joe42") ~> route ~> check {
responseAs[String] shouldEqual "The user is Joe42"
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "No user was provided"
}
}
"headerValuePF-0" in {
def extractHostPort: PartialFunction[HttpHeader, Int] = {
case h: `Host` => h.port
}
val route =
headerValuePF(extractHostPort) { port =>
complete(s"The port was $port")
}
// tests:
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual NotFound
responseAs[String] shouldEqual "The requested resource could not be found."
}
}
"optionalHeaderValuePF-0" in {
def extractHostPort: PartialFunction[HttpHeader, Int] = {
case h: `Host` => h.port
}
val route =
optionalHeaderValuePF(extractHostPort) {
case Some(port) => complete(s"The port was $port")
case None => complete(s"The port was not provided explicitly")
} ~ // can also be written as:
optionalHeaderValuePF(extractHostPort) { port =>
complete {
port match {
case Some(p) => s"The port was $p"
case _ => "The port was not provided explicitly"
}
}
}
// tests:
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "The port was not provided explicitly"
}
}
"headerValueByType-0" in {
val route =
headerValueByType[Origin]() { origin
complete(s"The first origin was ${origin.origins.head}")
}
val originHeader = Origin(HttpOrigin("http://localhost:8080"))
// tests:
// extract a header if the type is matching
Get("abc") ~> originHeader ~> route ~> check {
responseAs[String] shouldEqual "The first origin was http://localhost:8080"
}
// reject a request if no header of the given type is present
Get("abc") ~> route ~> check {
inside(rejection) { case MissingHeaderRejection("Origin") }
}
}
"optionalHeaderValueByType-0" in {
val route =
optionalHeaderValueByType[Origin]() {
case Some(origin) complete(s"The first origin was ${origin.origins.head}")
case None complete("No Origin header found.")
}
val originHeader = Origin(HttpOrigin("http://localhost:8080"))
// tests:
// extract Some(header) if the type is matching
Get("abc") ~> originHeader ~> route ~> check {
responseAs[String] shouldEqual "The first origin was http://localhost:8080"
}
// extract None if no header of the given type is present
Get("abc") ~> route ~> check {
responseAs[String] shouldEqual "No Origin header found."
}
}
}

View file

@ -0,0 +1,92 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import docs.http.scaladsl.server.RoutingSpec
import headers._
import StatusCodes._
class HostDirectivesExamplesSpec extends RoutingSpec {
"extractHost" in {
val route =
extractHost { hn =>
complete(s"Hostname: $hn")
}
// tests:
Get() ~> Host("company.com", 9090) ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Hostname: company.com"
}
}
"list-of-hosts" in {
val route =
host("api.company.com", "rest.company.com") {
complete("Ok")
}
// tests:
Get() ~> Host("rest.company.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Ok"
}
Get() ~> Host("notallowed.company.com") ~> route ~> check {
handled shouldBe false
}
}
"predicate" in {
val shortOnly: String => Boolean = (hostname) => hostname.length < 10
val route =
host(shortOnly) {
complete("Ok")
}
// tests:
Get() ~> Host("short.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Ok"
}
Get() ~> Host("verylonghostname.com") ~> route ~> check {
handled shouldBe false
}
}
"using-regex" in {
val route =
host("api|rest".r) { prefix =>
complete(s"Extracted prefix: $prefix")
} ~
host("public.(my|your)company.com".r) { captured =>
complete(s"You came through $captured company")
}
// tests:
Get() ~> Host("api.company.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Extracted prefix: api"
}
Get() ~> Host("public.mycompany.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "You came through my company"
}
}
"failing-regex" in {
an[IllegalArgumentException] should be thrownBy {
host("server-([0-9]).company.(com|net|org)".r) { target =>
complete("Will never complete :'(")
}
}
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.MediaTypes.`application/json`
import akka.http.scaladsl.model._
import spray.json.DefaultJsonProtocol
//# person-case-class
case class Person(name: String, favoriteNumber: Int)
//# person-json-support
object PersonJsonSupport extends DefaultJsonProtocol with SprayJsonSupport {
implicit val PortofolioFormats = jsonFormat2(Person)
}
//#
class MarshallingDirectivesExamplesSpec extends RoutingSpec {
"example-entity-with-json" in {
import PersonJsonSupport._
val route = post {
entity(as[Person]) { person =>
complete(s"Person: ${person.name} - favorite number: ${person.favoriteNumber}")
}
}
// tests:
Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""")) ~>
route ~> check {
responseAs[String] shouldEqual "Person: Jane - favorite number: 42"
}
}
"example-completeWith-with-json" in {
import PersonJsonSupport._
val findPerson = (f: Person => Unit) => {
//... some processing logic...
//complete the request
f(Person("Jane", 42))
}
val route = get {
completeWith(instanceOf[Person]) { completionFunction => findPerson(completionFunction) }
}
// tests:
Get("/") ~> route ~> check {
mediaType shouldEqual `application/json`
responseAs[String] should include(""""name": "Jane"""")
responseAs[String] should include(""""favoriteNumber": 42""")
}
}
"example-handleWith-with-json" in {
import PersonJsonSupport._
val updatePerson = (person: Person) => {
//... some processing logic...
//return the person
person
}
val route = post {
handleWith(updatePerson)
}
// tests:
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""")
}
}
}

View file

@ -0,0 +1,136 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Route
import docs.http.scaladsl.server.RoutingSpec
class MethodDirectivesExamplesSpec extends RoutingSpec {
"delete-method" in {
val route = delete { complete("This is a DELETE request.") }
// tests:
Delete("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a DELETE request."
}
}
"get-method" in {
val route = get { complete("This is a GET request.") }
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a GET request."
}
}
"head-method" in {
val route = head { complete("This is a HEAD request.") }
// tests:
Head("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a HEAD request."
}
}
"options-method" in {
val route = options { complete("This is an OPTIONS request.") }
// tests:
Options("/") ~> route ~> check {
responseAs[String] shouldEqual "This is an OPTIONS request."
}
}
"patch-method" in {
val route = patch { complete("This is a PATCH request.") }
// tests:
Patch("/", "patch content") ~> route ~> check {
responseAs[String] shouldEqual "This is a PATCH request."
}
}
"post-method" in {
val route = post { complete("This is a POST request.") }
// tests:
Post("/", "post content") ~> route ~> check {
responseAs[String] shouldEqual "This is a POST request."
}
}
"put-method" in {
val route = put { complete("This is a PUT request.") }
// tests:
Put("/", "put content") ~> route ~> check {
responseAs[String] shouldEqual "This is a PUT request."
}
}
"method-example" in {
val route = method(HttpMethods.PUT) { complete("This is a PUT request.") }
// tests:
Put("/", "put content") ~> route ~> check {
responseAs[String] shouldEqual "This is a PUT request."
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.MethodNotAllowed
responseAs[String] shouldEqual "HTTP method not allowed, supported methods: PUT"
}
}
"extractMethod-example" in {
val route =
get {
complete("This is a GET request.")
} ~
extractMethod { method =>
complete(s"This ${method.name} request, clearly is not a GET!")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a GET request."
}
Put("/") ~> route ~> check {
responseAs[String] shouldEqual "This PUT request, clearly is not a GET!"
}
Head("/") ~> route ~> check {
responseAs[String] shouldEqual "This HEAD request, clearly is not a GET!"
}
}
"overrideMethodWithParameter-0" in {
val route =
overrideMethodWithParameter("method") {
get {
complete("This looks like a GET request.")
} ~
post {
complete("This looks like a POST request.")
}
}
// tests:
Get("/?method=POST") ~> route ~> check {
responseAs[String] shouldEqual "This looks like a POST request."
}
Post("/?method=get") ~> route ~> check {
responseAs[String] shouldEqual "This looks like a GET request."
}
Get("/?method=hallo") ~> route ~> check {
status shouldEqual StatusCodes.NotImplemented
}
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import headers._
import docs.http.scaladsl.server.RoutingSpec
class MiscDirectivesExamplesSpec extends RoutingSpec {
"extractClientIP-example" in {
val route = extractClientIP { ip =>
complete("Client's ip is " + ip.toOption.map(_.getHostAddress).getOrElse("unknown"))
}
// tests:
Get("/").withHeaders(`Remote-Address`(RemoteAddress("192.168.3.12"))) ~> route ~> check {
responseAs[String] shouldEqual "Client's ip is 192.168.3.12"
}
}
"rejectEmptyResponse-example" in {
val route = rejectEmptyResponse {
path("even" / IntNumber) { i =>
complete {
// returns Some(evenNumberDescription) or None
Option(i).filter(_ % 2 == 0).map { num =>
s"Number $num is even."
}
}
}
}
// tests:
Get("/even/23") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
}
Get("/even/28") ~> route ~> check {
responseAs[String] shouldEqual "Number 28 is even."
}
}
"requestEntityEmptyPresent-example" in {
val route =
requestEntityEmpty {
complete("request entity empty")
} ~
requestEntityPresent {
complete("request entity present")
}
// tests:
Post("/", "text") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "request entity present"
}
Post("/") ~> route ~> check {
responseAs[String] shouldEqual "request entity empty"
}
}
"selectPreferredLanguage-example" in {
val request = Get() ~> `Accept-Language`(
Language("en-US"),
Language("en") withQValue 0.7f,
LanguageRange.`*` withQValue 0.1f,
Language("de") withQValue 0.5f)
request ~> {
selectPreferredLanguage("en", "en-US") { lang
complete(lang.toString)
}
} ~> check { responseAs[String] shouldEqual "en-US" }
request ~> {
selectPreferredLanguage("de-DE", "hu") { lang
complete(lang.toString)
}
} ~> check { responseAs[String] shouldEqual "de-DE" }
}
"validate-example" in {
val route =
extractUri { uri =>
validate(uri.path.toString.size < 5, s"Path too long: '${uri.path.toString}'") {
complete(s"Full URI: $uri")
}
}
// tests:
Get("/234") ~> route ~> check {
responseAs[String] shouldEqual "Full URI: http://example.com/234"
}
Get("/abcdefghijkl") ~> route ~> check {
rejection shouldEqual ValidationRejection("Path too long: '/abcdefghijkl'", None)
}
}
}

View file

@ -0,0 +1,212 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers
import docs.http.scaladsl.server.RoutingSpec
class ParameterDirectivesExamplesSpec extends RoutingSpec with PredefinedFromStringUnmarshallers {
"example-1" in {
val route =
parameter('color) { color =>
complete(s"The color is '$color'")
}
// tests:
Get("/?color=blue") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue'"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Request is missing required query parameter 'color'"
}
}
"required-1" in {
val route =
parameters('color, 'backgroundColor) { (color, backgroundColor) =>
complete(s"The color is '$color' and the background is '$backgroundColor'")
}
// tests:
Get("/?color=blue&backgroundColor=red") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'red'"
}
Get("/?color=blue") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Request is missing required query parameter 'backgroundColor'"
}
}
"optional" in {
val route =
parameters('color, 'backgroundColor.?) { (color, backgroundColor) =>
val backgroundStr = backgroundColor.getOrElse("<undefined>")
complete(s"The color is '$color' and the background is '$backgroundStr'")
}
// tests:
Get("/?color=blue&backgroundColor=red") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'red'"
}
Get("/?color=blue") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is '<undefined>'"
}
}
"optional-with-default" in {
val route =
parameters('color, 'backgroundColor ? "white") { (color, backgroundColor) =>
complete(s"The color is '$color' and the background is '$backgroundColor'")
}
// tests:
Get("/?color=blue&backgroundColor=red") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'red'"
}
Get("/?color=blue") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'white'"
}
}
"required-value" in {
val route =
parameters('color, 'action ! "true") { (color) =>
complete(s"The color is '$color'.")
}
// tests:
Get("/?color=blue&action=true") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue'."
}
Get("/?color=blue&action=false") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "The requested resource could not be found."
}
}
"mapped-value" in {
val route =
parameters('color, 'count.as[Int]) { (color, count) =>
complete(s"The color is '$color' and you have $count of it.")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and you have 42 of it."
}
Get("/?color=blue&count=blub") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "The query parameter 'count' was malformed:\n'blub' is not a valid 32-bit signed integer value"
}
}
"repeated" in {
val route =
parameters('color, 'city.*) { (color, cities) =>
cities.toList match {
case Nil => complete(s"The color is '$color' and there are no cities.")
case city :: Nil => complete(s"The color is '$color' and the city is $city.")
case multiple => complete(s"The color is '$color' and the cities are ${multiple.mkString(", ")}.")
}
}
// tests:
Get("/?color=blue") ~> route ~> check {
responseAs[String] === "The color is 'blue' and there are no cities."
}
Get("/?color=blue&city=Chicago") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the city is Chicago."
}
Get("/?color=blue&city=Chicago&city=Boston") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the cities are Chicago, Boston."
}
}
"mapped-repeated" in {
val route =
parameters('color, 'distance.as[Int].*) { (color, cities) =>
cities.toList match {
case Nil => complete(s"The color is '$color' and there are no distances.")
case distance :: Nil => complete(s"The color is '$color' and the distance is $distance.")
case multiple => complete(s"The color is '$color' and the distances are ${multiple.mkString(", ")}.")
}
}
// tests:
Get("/?color=blue") ~> route ~> check {
responseAs[String] === "The color is 'blue' and there are no distances."
}
Get("/?color=blue&distance=5") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the distance is 5."
}
Get("/?color=blue&distance=5&distance=14") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the distances are 5, 14."
}
}
"parameterMap" in {
val route =
parameterMap { params =>
def paramString(param: (String, String)): String = s"""${param._1} = '${param._2}'"""
complete(s"The parameters are ${params.map(paramString).mkString(", ")}")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are color = 'blue', count = '42'"
}
Get("/?x=1&x=2") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are x = '2'"
}
}
"parameterMultiMap" in {
val route =
parameterMultiMap { params =>
complete(s"There are parameters ${params.map(x => x._1 + " -> " + x._2.size).mkString(", ")}")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "There are parameters color -> 1, count -> 1"
}
Get("/?x=23&x=42") ~> route ~> check {
responseAs[String] shouldEqual "There are parameters x -> 2"
}
}
"parameterSeq" in {
val route =
parameterSeq { params =>
def paramString(param: (String, String)): String = s"""${param._1} = '${param._2}'"""
complete(s"The parameters are ${params.map(paramString).mkString(", ")}")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are color = 'blue', count = '42'"
}
Get("/?x=1&x=2") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are x = '1', x = '2'"
}
}
"csv" in {
val route =
parameter("names".as(CsvSeq[String])) { names =>
complete(s"The parameters are ${names.mkString(", ")}")
}
// tests:
Get("/?names=") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are "
}
Get("/?names=Caplin") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are Caplin"
}
Get("/?names=Caplin,John") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are Caplin, John"
}
}
}

View file

@ -0,0 +1,380 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.server._
import docs.http.scaladsl.server.RoutingSpec
class PathDirectivesExamplesSpec extends RoutingSpec {
//# path-matcher
val matcher: PathMatcher1[Option[Int]] =
"foo" / "bar" / "X" ~ IntNumber.? / ("edit" | "create")
//#
//# path-dsl
// matches /foo/
path("foo"./)
// matches e.g. /foo/123 and extracts "123" as a String
path("foo" / """\d+""".r)
// matches e.g. /foo/bar123 and extracts "123" as a String
path("foo" / """bar(\d+)""".r)
// similar to `path(Segments)`
path(Segment.repeat(10, separator = Slash))
// matches e.g. /i42 or /hCAFE and extracts an Int
path("i" ~ IntNumber | "h" ~ HexIntNumber)
// identical to path("foo" ~ (PathEnd | Slash))
path("foo" ~ Slash.?)
// matches /red or /green or /blue and extracts 1, 2 or 3 respectively
path(Map("red" -> 1, "green" -> 2, "blue" -> 3))
// matches anything starting with "/foo" except for /foobar
pathPrefix("foo" ~ !"bar")
//#
//# pathPrefixTest-, rawPathPrefix-, rawPathPrefixTest-, pathSuffix-, pathSuffixTest-
val completeWithUnmatchedPath =
extractUnmatchedPath { p =>
complete(p.toString)
}
//#
"path-example" in {
val route =
path("foo") {
complete("/foo")
} ~
path("foo" / "bar") {
complete("/foo/bar")
} ~
pathPrefix("ball") {
pathEnd {
complete("/ball")
} ~
path(IntNumber) { int =>
complete(if (int % 2 == 0) "even ball" else "odd ball")
}
}
// tests:
Get("/") ~> route ~> check {
handled shouldEqual false
}
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/bar") ~> route ~> check {
responseAs[String] shouldEqual "/foo/bar"
}
Get("/ball/1337") ~> route ~> check {
responseAs[String] shouldEqual "odd ball"
}
}
"pathEnd-" in {
val route =
pathPrefix("foo") {
pathEnd {
complete("/foo")
} ~
path("bar") {
complete("/foo/bar")
}
}
// tests:
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/") ~> route ~> check {
handled shouldEqual false
}
Get("/foo/bar") ~> route ~> check {
responseAs[String] shouldEqual "/foo/bar"
}
}
"pathEndOrSingleSlash-" in {
val route =
pathPrefix("foo") {
pathEndOrSingleSlash {
complete("/foo")
} ~
path("bar") {
complete("/foo/bar")
}
}
// tests:
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/bar") ~> route ~> check {
responseAs[String] shouldEqual "/foo/bar"
}
}
"pathPrefix-" in {
val route =
pathPrefix("ball") {
pathEnd {
complete("/ball")
} ~
path(IntNumber) { int =>
complete(if (int % 2 == 0) "even ball" else "odd ball")
}
}
// tests:
Get("/") ~> route ~> check {
handled shouldEqual false
}
Get("/ball") ~> route ~> check {
responseAs[String] shouldEqual "/ball"
}
Get("/ball/1337") ~> route ~> check {
responseAs[String] shouldEqual "odd ball"
}
}
"pathPrefixTest-" in {
val route =
pathPrefixTest("foo" | "bar") {
pathPrefix("foo") { completeWithUnmatchedPath } ~
pathPrefix("bar") { completeWithUnmatchedPath }
}
// tests:
Get("/foo/doo") ~> route ~> check {
responseAs[String] shouldEqual "/doo"
}
Get("/bar/yes") ~> route ~> check {
responseAs[String] shouldEqual "/yes"
}
}
"pathSingleSlash-" in {
val route =
pathSingleSlash {
complete("root")
} ~
pathPrefix("ball") {
pathSingleSlash {
complete("/ball/")
} ~
path(IntNumber) { int =>
complete(if (int % 2 == 0) "even ball" else "odd ball")
}
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "root"
}
Get("/ball") ~> route ~> check {
handled shouldEqual false
}
Get("/ball/") ~> route ~> check {
responseAs[String] shouldEqual "/ball/"
}
Get("/ball/1337") ~> route ~> check {
responseAs[String] shouldEqual "odd ball"
}
}
"pathSuffix-" in {
val route =
pathPrefix("start") {
pathSuffix("end") {
completeWithUnmatchedPath
} ~
pathSuffix("foo" / "bar" ~ "baz") {
completeWithUnmatchedPath
}
}
// tests:
Get("/start/middle/end") ~> route ~> check {
responseAs[String] shouldEqual "/middle/"
}
Get("/start/something/barbaz/foo") ~> route ~> check {
responseAs[String] shouldEqual "/something/"
}
}
"pathSuffixTest-" in {
val route =
pathSuffixTest(Slash) {
complete("slashed")
} ~
complete("unslashed")
// tests:
Get("/foo/") ~> route ~> check {
responseAs[String] shouldEqual "slashed"
}
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "unslashed"
}
}
"rawPathPrefix-" in {
val route =
pathPrefix("foo") {
rawPathPrefix("bar") { completeWithUnmatchedPath } ~
rawPathPrefix("doo") { completeWithUnmatchedPath }
}
// tests:
Get("/foobar/baz") ~> route ~> check {
responseAs[String] shouldEqual "/baz"
}
Get("/foodoo/baz") ~> route ~> check {
responseAs[String] shouldEqual "/baz"
}
}
"rawPathPrefixTest-" in {
val route =
pathPrefix("foo") {
rawPathPrefixTest("bar") {
completeWithUnmatchedPath
}
}
// tests:
Get("/foobar") ~> route ~> check {
responseAs[String] shouldEqual "bar"
}
Get("/foobaz") ~> route ~> check {
handled shouldEqual false
}
}
"redirectToTrailingSlashIfMissing-0" in {
import akka.http.scaladsl.model.StatusCodes
val route =
redirectToTrailingSlashIfMissing(StatusCodes.MovedPermanently) {
path("foo"./) {
// We require the explicit trailing slash in the path
complete("OK")
} ~
path("bad-1") {
// MISTAKE!
// Missing `/` in path, causes this path to never match,
// because it is inside a `redirectToTrailingSlashIfMissing`
???
} ~
path("bad-2/") {
// MISTAKE!
// / should be explicit as path element separator and not *in* the path element
// So it should be: "bad-1" /
???
}
}
// tests:
// Redirected:
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.MovedPermanently
// results in nice human readable message,
// in case the redirect can't be followed automatically:
responseAs[String] shouldEqual {
"This and all future requests should be directed to " +
"<a href=\"http://example.com/foo/\">this URI</a>."
}
}
// Properly handled:
Get("/foo/") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "OK"
}
// MISTAKE! will never match - reason explained in routes
Get("/bad-1/") ~> route ~> check {
handled shouldEqual false
}
// MISTAKE! will never match - reason explained in routes
Get("/bad-2/") ~> route ~> check {
handled shouldEqual false
}
}
"redirectToNoTrailingSlashIfPresent-0" in {
import akka.http.scaladsl.model.StatusCodes
val route =
redirectToNoTrailingSlashIfPresent(StatusCodes.MovedPermanently) {
path("foo") {
// We require the explicit trailing slash in the path
complete("OK")
} ~
path("bad"./) {
// MISTAKE!
// Since inside a `redirectToNoTrailingSlashIfPresent` directive
// the matched path here will never contain a trailing slash,
// thus this path will never match.
//
// It should be `path("bad")` instead.
???
}
}
// tests:
// Redirected:
Get("/foo/") ~> route ~> check {
status shouldEqual StatusCodes.MovedPermanently
// results in nice human readable message,
// in case the redirect can't be followed automatically:
responseAs[String] shouldEqual {
"This and all future requests should be directed to " +
"<a href=\"http://example.com/foo\">this URI</a>."
}
}
// Properly handled:
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "OK"
}
// MISTAKE! will never match - reason explained in routes
Get("/bad") ~> route ~> check {
handled shouldEqual false
}
}
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import com.typesafe.config.{ ConfigFactory, Config }
import akka.util.ByteString
import headers._
import scala.concurrent.Await
import scala.concurrent.duration._
import docs.http.scaladsl.server.RoutingSpec
class RangeDirectivesExamplesSpec extends RoutingSpec {
override def testConfig: Config =
ConfigFactory.parseString("akka.http.routing.range-coalescing-threshold=2").withFallback(super.testConfig)
"withRangeSupport" in {
val route =
withRangeSupport {
complete("ABCDEFGH")
}
Get() ~> addHeader(Range(ByteRange(3, 4))) ~> route ~> check {
headers should contain(`Content-Range`(ContentRange(3, 4, 8)))
status shouldEqual StatusCodes.PartialContent
responseAs[String] shouldEqual "DE"
}
// we set "akka.http.routing.range-coalescing-threshold = 2"
// above to make sure we get two BodyParts
Get() ~> addHeader(Range(ByteRange(0, 1), ByteRange(1, 2), ByteRange(6, 7))) ~> route ~> check {
headers.collectFirst { case `Content-Range`(_, _) => true } shouldBe None
val responseF = responseAs[Multipart.ByteRanges].parts
.runFold[List[Multipart.ByteRanges.BodyPart]](Nil)((acc, curr) => curr :: acc)
val response = Await.result(responseF, 3.seconds).reverse
response should have length 2
val part1 = response(0)
part1.contentRange === ContentRange(0, 2, 8)
part1.entity should matchPattern {
case HttpEntity.Strict(_, bytes) if bytes.utf8String == "ABC" =>
}
val part2 = response(1)
part2.contentRange === ContentRange(6, 7, 8)
part2.entity should matchPattern {
case HttpEntity.Strict(_, bytes) if bytes.utf8String == "GH" =>
}
}
}
}

View file

@ -0,0 +1,83 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.headers._
import docs.http.scaladsl.server.RoutingSpec
class RespondWithDirectivesExamplesSpec extends RoutingSpec {
"respondWithHeader-0" in {
val route =
path("foo") {
respondWithHeader(RawHeader("Funky-Muppet", "gonzo")) {
complete("beep")
}
}
// tests:
Get("/foo") ~> route ~> check {
header("Funky-Muppet") shouldEqual Some(RawHeader("Funky-Muppet", "gonzo"))
responseAs[String] shouldEqual "beep"
}
}
"respondWithDefaultHeader-0" in {
// custom headers
val blippy = RawHeader("X-Fish-Name", "Blippy")
val elTonno = RawHeader("X-Fish-Name", "El Tonno")
// format: OFF
// by default always include the Blippy header,
// unless a more specific X-Fish-Name is given by the inner route
val route =
respondWithDefaultHeader(blippy) { // blippy
respondWithHeader(elTonno) { // / el tonno
path("el-tonno") { // | /
complete("¡Ay blippy!") // | |- el tonno
} ~ // | |
path("los-tonnos") { // | |
complete("¡Ay ay blippy!") // | |- el tonno
} // | |
} ~ // | x
complete("Blip!") // |- blippy
} // x
// format: ON
// tests:
Get("/") ~> route ~> check {
header("X-Fish-Name") shouldEqual Some(RawHeader("X-Fish-Name", "Blippy"))
responseAs[String] shouldEqual "Blip!"
}
Get("/el-tonno") ~> route ~> check {
header("X-Fish-Name") shouldEqual Some(RawHeader("X-Fish-Name", "El Tonno"))
responseAs[String] shouldEqual "¡Ay blippy!"
}
Get("/los-tonnos") ~> route ~> check {
header("X-Fish-Name") shouldEqual Some(RawHeader("X-Fish-Name", "El Tonno"))
responseAs[String] shouldEqual "¡Ay ay blippy!"
}
}
// format: ON
"respondWithHeaders-0" in {
val route =
path("foo") {
respondWithHeaders(RawHeader("Funky-Muppet", "gonzo"), Origin(HttpOrigin("http://akka.io"))) {
complete("beep")
}
}
// tests:
Get("/foo") ~> route ~> check {
header("Funky-Muppet") shouldEqual Some(RawHeader("Funky-Muppet", "gonzo"))
header[Origin] shouldEqual Some(Origin(HttpOrigin("http://akka.io")))
responseAs[String] shouldEqual "beep"
}
}
}

View file

@ -0,0 +1,100 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.{ Route, ValidationRejection }
import akka.testkit.EventFilter
import docs.http.scaladsl.server.RoutingSpec
class RouteDirectivesExamplesSpec extends RoutingSpec {
"complete-examples" in {
val route =
path("a") {
complete(HttpResponse(entity = "foo"))
} ~
path("b") {
complete((StatusCodes.Created, "bar"))
} ~
(path("c") & complete("baz")) // `&` also works with `complete` as the 2nd argument
// tests:
Get("/a") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "foo"
}
Get("/b") ~> route ~> check {
status shouldEqual StatusCodes.Created
responseAs[String] shouldEqual "bar"
}
Get("/c") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "baz"
}
}
"reject-examples" in {
val route =
path("a") {
reject // don't handle here, continue on
} ~
path("a") {
complete("foo")
} ~
path("b") {
// trigger a ValidationRejection explicitly
// rather than through the `validate` directive
reject(ValidationRejection("Restricted!"))
}
// tests:
Get("/a") ~> route ~> check {
responseAs[String] shouldEqual "foo"
}
Get("/b") ~> route ~> check {
rejection shouldEqual ValidationRejection("Restricted!")
}
}
"redirect-examples" in {
val route =
pathPrefix("foo") {
pathSingleSlash {
complete("yes")
} ~
pathEnd {
redirect("/foo/", StatusCodes.PermanentRedirect)
}
}
// tests:
Get("/foo/") ~> route ~> check {
responseAs[String] shouldEqual "yes"
}
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.PermanentRedirect
responseAs[String] shouldEqual """The request, and all future requests should be repeated using <a href="/foo/">this URI</a>."""
}
}
"failwith-examples" in EventFilter[RuntimeException](start = "Error during processing of request", occurrences = 1).intercept {
val route =
path("foo") {
failWith(new RuntimeException("Oops."))
}
// tests:
Get("/foo") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.InternalServerError
responseAs[String] shouldEqual "There was an internal server error."
}
}
}

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import docs.http.scaladsl.server.RoutingSpec
class SchemeDirectivesExamplesSpec extends RoutingSpec {
"example-1" in {
val route =
extractScheme { scheme =>
complete(s"The scheme is '${scheme}'")
}
// tests:
Get("https://www.example.com/") ~> route ~> check {
responseAs[String] shouldEqual "The scheme is 'https'"
}
}
"example-2" in {
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.Location
import StatusCodes.MovedPermanently
val route =
scheme("http") {
extract(_.request.uri) { uri
redirect(uri.copy(scheme = "https"), MovedPermanently)
}
} ~
scheme("https") {
complete(s"Safe and secure!")
}
// tests:
Get("http://www.example.com/hello") ~> route ~> check {
status shouldEqual MovedPermanently
header[Location] shouldEqual Some(Location(Uri("https://www.example.com/hello")))
}
Get("https://www.example.com/hello") ~> route ~> check {
responseAs[String] shouldEqual "Safe and secure!"
}
}
}

View file

@ -0,0 +1,286 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.directives.Credentials
import scala.concurrent.Future
import docs.http.scaladsl.server.RoutingSpec
class SecurityDirectivesExamplesSpec extends RoutingSpec {
"authenticateBasic-0" in {
def myUserPassAuthenticator(credentials: Credentials): Option[String] =
credentials match {
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd") => Some(id)
case _ => None
}
val route =
Route.seal {
path("secured") {
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
}
// tests:
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")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
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")
}
}
"authenticateBasicPF-0" in {
val myUserPassAuthenticator: AuthenticatorPF[String] = {
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd") => id
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd-special") => s"$id-admin"
}
val route =
Route.seal {
path("secured") {
authenticateBasicPF(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
}
// tests:
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")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John'"
}
val validAdminCredentials = BasicHttpCredentials("John", "p4ssw0rd-special")
Get("/secured") ~> addCredentials(validAdminCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John-admin'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
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")
}
}
"authenticateBasicPFAsync-0" in {
case class User(id: String)
def fetchUser(id: String): Future[User] = {
// some fancy logic to obtain a User
Future.successful(User(id))
}
val myUserPassAuthenticator: AsyncAuthenticatorPF[User] = {
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd") =>
fetchUser(id)
}
val route =
Route.seal {
path("secured") {
authenticateBasicPFAsync(realm = "secure site", myUserPassAuthenticator) { user =>
complete(s"The user is '${user.id}'")
}
}
}
// tests:
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")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
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")
}
}
"authenticateBasicAsync-0" in {
def myUserPassAuthenticator(credentials: Credentials): Future[Option[String]] =
credentials match {
case p @ Credentials.Provided(id) =>
Future {
// potentially
if (p.verify("p4ssw0rd")) Some(id)
else None
}
case _ => Future.successful(None)
}
val route =
Route.seal {
path("secured") {
authenticateBasicAsync(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
}
// tests:
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")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
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")
}
}
"authenticateOrRejectWithChallenge-0" in {
val challenge = HttpChallenge("MyAuth", "MyRealm")
// your custom authentication logic:
def auth(creds: HttpCredentials): Boolean = true
def myUserPassAuthenticator(credentials: Option[HttpCredentials]): Future[AuthenticationResult[String]] =
Future {
credentials match {
case Some(creds) if auth(creds) => Right("some-user-name-from-creds")
case _ => Left(challenge)
}
}
val route =
Route.seal {
path("secured") {
authenticateOrRejectWithChallenge(myUserPassAuthenticator _) { userName =>
complete("Authenticated!")
}
}
}
// tests:
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")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Authenticated!"
}
}
"0authorize" in {
case class User(name: String)
// authenticate the user:
def myUserPassAuthenticator(credentials: Credentials): Option[User] =
credentials match {
case Credentials.Provided(id) => Some(User(id))
case _ => None
}
// check if user is authorized to perform admin actions:
val admins = Set("Peter")
def hasAdminPermissions(user: User): Boolean =
admins.contains(user.name)
val route =
Route.seal {
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { user =>
path("peters-lair") {
authorize(hasAdminPermissions(user)) {
complete(s"'${user.name}' visited Peter's lair")
}
}
}
}
// tests:
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
Get("/peters-lair") ~> addCredentials(johnsCred) ~> // adds Authorization header
route ~> check {
status shouldEqual StatusCodes.Forbidden
responseAs[String] shouldEqual "The supplied authentication is not authorized to access this resource"
}
val petersCred = BasicHttpCredentials("Peter", "pan")
Get("/peters-lair") ~> addCredentials(petersCred) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "'Peter' visited Peter's lair"
}
}
"0extractCredentials" in {
val route =
extractCredentials { creds =>
complete {
creds match {
case Some(c) => "Credentials: " + c
case _ => "No credentials"
}
}
}
// tests:
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
Get("/") ~> addCredentials(johnsCred) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "Credentials: Basic Sm9objpwNHNzdzByZA=="
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "No credentials"
}
}
}

View file

@ -0,0 +1,105 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import scala.concurrent.duration._
import akka.util.ByteString
import akka.stream.OverflowStrategy
import akka.stream.scaladsl.{ Sink, Source, Flow }
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.ws.{ TextMessage, Message, BinaryMessage }
import akka.http.scaladsl.testkit.WSProbe
class WebsocketDirectivesExamplesSpec extends RoutingSpec {
"greeter-service" in {
def greeter: Flow[Message, Message, Any] =
Flow[Message].mapConcat {
case tm: TextMessage
TextMessage(Source.single("Hello ") ++ tm.textStream ++ Source.single("!")) :: Nil
case bm: BinaryMessage
// ignore binary messages but drain content to avoid the stream being clogged
bm.dataStream.runWith(Sink.ignore)
Nil
}
val websocketRoute =
path("greeter") {
handleWebsocketMessages(greeter)
}
// tests:
// create a testing probe representing the client-side
val wsClient = WSProbe()
// WS creates a Websocket request for testing
WS("/greeter", wsClient.flow) ~> websocketRoute ~>
check {
// check response for WS Upgrade headers
isWebsocketUpgrade shouldEqual true
// manually run a WS conversation
wsClient.sendMessage("Peter")
wsClient.expectMessage("Hello Peter!")
wsClient.sendMessage(BinaryMessage(ByteString("abcdef")))
wsClient.expectNoMessage(100.millis)
wsClient.sendMessage("John")
wsClient.expectMessage("Hello John!")
wsClient.sendCompletion()
wsClient.expectCompletion()
}
}
"handle-multiple-protocols" in {
def greeterService: Flow[Message, Message, Any] =
Flow[Message].mapConcat {
case tm: TextMessage
TextMessage(Source.single("Hello ") ++ tm.textStream ++ Source.single("!")) :: Nil
case bm: BinaryMessage
// ignore binary messages but drain content to avoid the stream being clogged
bm.dataStream.runWith(Sink.ignore)
Nil
}
def echoService: Flow[Message, Message, Any] =
Flow[Message]
// needed because a noop flow hasn't any buffer that would start processing in tests
.buffer(1, OverflowStrategy.backpressure)
def websocketMultipleProtocolRoute =
path("services") {
handleWebsocketMessagesForProtocol(greeterService, "greeter") ~
handleWebsocketMessagesForProtocol(echoService, "echo")
}
// tests:
val wsClient = WSProbe()
// WS creates a Websocket request for testing
WS("/services", wsClient.flow, List("other", "echo")) ~>
websocketMultipleProtocolRoute ~>
check {
expectWebsocketUpgradeWithProtocol { protocol
protocol shouldEqual "echo"
wsClient.sendMessage("Peter")
wsClient.expectMessage("Peter")
wsClient.sendMessage(BinaryMessage(ByteString("abcdef")))
wsClient.expectMessage(ByteString("abcdef"))
wsClient.sendMessage("John")
wsClient.expectMessage("John")
wsClient.sendCompletion()
wsClient.expectCompletion()
}
}
}
}