=doc Significantly extend HTTP documentation with new content and ports from spray docs

This commit is contained in:
Mathias 2015-05-11 23:05:18 +02:00 committed by Johannes Rudolph
parent 6149b328a0
commit 20759e1b34
238 changed files with 6541 additions and 1563 deletions

View file

@ -1,73 +0,0 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http
import akka.actor.ActorSystem
import akka.http.scaladsl.model._
import akka.stream.scaladsl._
import akka.stream.testkit.AkkaSpec
import scala.concurrent.Future
class HttpServerExampleSpec
extends AkkaSpec("akka.actor.default-mailbox.mailbox-type = akka.dispatch.UnboundedMailbox") {
def ActorSystem(): ActorSystem = system
"binding example" in {
//#bind-example
import akka.http.scaladsl.Http
import akka.stream.ActorFlowMaterializer
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] =
Http(system).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()
//#bind-example
}
"full-server-example" in {
import akka.http.scaladsl.Http
import akka.stream.ActorFlowMaterializer
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
val serverSource = Http(system).bind(interface = "localhost", port = 8080)
//#full-server-example
import akka.http.scaladsl.model.HttpMethods._
import akka.stream.scaladsl.{ Flow, Sink }
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(
entity = HttpEntity(MediaTypes.`text/html`,
"<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()
//#full-server-example
}
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
import akka.actor.ActorSystem
import org.scalatest.{ Matchers, WordSpec }
class HttpClientExampleSpec extends WordSpec with Matchers {
"outgoing-connection-example" in {
pending // compile-time only test
//#outgoing-connection-example
import scala.concurrent.Future
import akka.stream.ActorFlowMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model._
import akka.http.scaladsl.Http
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
Http().outgoingConnection("http://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 scala.concurrent.Future
import scala.util.Try
import akka.stream.ActorFlowMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model._
import akka.http.scaladsl.Http
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
// construct a pool client flow with context type `Int`
val poolClientFlow = Http().cachedHostConnectionPool[Int]("http://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 scala.concurrent.Future
import akka.stream.ActorFlowMaterializer
import akka.http.scaladsl.model._
import akka.http.scaladsl.Http
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
val responseFuture: Future[HttpResponse] =
Http().singleRequest(HttpRequest(uri = "http://akka.io"))
//#single-request-example
}
}

View file

@ -0,0 +1,256 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
import scala.concurrent.Future
import org.scalatest.{ WordSpec, Matchers }
import akka.actor.ActorSystem
class HttpServerExampleSpec extends WordSpec with Matchers {
"binding-example" in {
import akka.stream.ActorFlowMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.Http
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
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()
}
"full-server-example" in {
import akka.stream.ActorFlowMaterializer
import akka.stream.scaladsl.Sink
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
val serverSource = Http().bind(interface = "localhost", port = 8080)
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(MediaTypes.`text/html`,
"<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 {
import akka.stream.ActorFlowMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(MediaTypes.`text/html`,
"<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 {
import akka.stream.ActorFlowMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
implicit val system = ActorSystem()
implicit val materializer = ActorFlowMaterializer()
val route =
get {
pathSingleSlash {
complete {
<html>
<body>Hello world!</body>
</html>
}
} ~
path("ping") {
complete("PONG!")
} ~
path("crash") {
sys.error("BOOM!")
}
}
Http().bindAndHandle(route, "localhost", 8080)
}
"minimal-routing-example" in {
import akka.stream.ActorFlowMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._
object Main extends App {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorFlowMaterializer()
val route =
path("hello") {
get {
complete {
<h1>Say hello to spray</h1>
}
}
}
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
}
}
"long-routing-example" in {
import akka.actor.ActorRef
import akka.util.Timeout
import akka.pattern.ask
import akka.http.scaladsl.marshalling.ToResponseMarshaller
import akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller
import akka.http.scaladsl.model.StatusCodes.MovedPermanently
import akka.http.scaladsl.coding.Deflate
import akka.http.scaladsl.server.Directives._
// 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)
implicit val orderUM: FromRequestUnmarshaller[Order] = ???
implicit val orderM: ToResponseMarshaller[Seq[Order]] = ???
implicit val timeout: Timeout = ??? // for actor asks
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)
}
}
// backend entry points
def myAuthenticator: Authenticator[User] = ???
def retrieveOrdersFromDB: Seq[Order] = ???
def myDbActor: ActorRef = ???
def processOrderRequest(id: Int, complete: Order => Unit): Unit = ???
}
}

View file

@ -1,4 +1,8 @@
package docs.http
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
//#import-model
import akka.http.scaladsl.model._
@ -7,19 +11,18 @@ import akka.http.scaladsl.model._
import akka.stream.testkit.AkkaSpec
import akka.util.ByteString
import akka.http.scaladsl.model.headers.{ GenericHttpCredentials, BasicHttpCredentials }
import org.scalatest.MustMatchers
import akka.http.scaladsl.model.headers.BasicHttpCredentials
class ModelSpec extends AkkaSpec {
"construct request" in {
//#construct-request
import HttpMethods._
// construct simple GET request to `homeUri`
// construct a simple GET request to `homeUri`
val homeUri = Uri("/abc")
HttpRequest(GET, uri = homeUri)
// construct simple GET request to "/index" which is converted to Uri automatically
// construct simple GET request to "/index" (implicit string to Uri conversion)
HttpRequest(GET, uri = "/index")
// construct simple POST request containing entity
@ -70,11 +73,13 @@ class ModelSpec extends AkkaSpec {
// create an ``Authorization`` header with HTTP Basic authentication data
val auth = Authorization(BasicHttpCredentials("joe", "josepp"))
// a method that extracts basic HTTP credentials from a request
// 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[headers.Authorization]
Authorization(BasicHttpCredentials(user, pass)) <- req.header[Authorization]
} yield User(user, pass)
//#headers

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
import akka.http.scaladsl.server._
class CaseClassExtractionExamplesSpec extends RoutingSpec {
// 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 {
rejection must beLike {
case ValidationRejection("requirement failed: blue color component must be between 0 and 255", _) => ok
}
}
}
}

View file

@ -2,7 +2,7 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
import akka.http.scaladsl.server._
import Directives._
@ -11,7 +11,9 @@ import org.scalatest._
class DirectiveExamplesSpec extends RoutingSpec {
"example-1, example-2" in {
// format: OFF
"example-1" in {
val route: Route =
path("order" / IntNumber) { id =>
get {
@ -19,37 +21,49 @@ class DirectiveExamplesSpec extends RoutingSpec {
"Received GET request for order " + id
}
} ~
put {
complete {
"Received PUT request for order " + id
}
put {
complete {
"Received PUT request for order " + id
}
}
}
verify(route) // hide
}
"example-3" in {
"example-2" in {
def innerRoute(id: Int): Route =
get {
complete {
"Received GET request for order " + id
}
} ~
put {
complete {
"Received PUT 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) { ctx =>
ctx.complete("Received " + ctx.request.method.name + " request for order " + id)
(get | put) {
extractMethod { m =>
complete(s"Received ${m.name} request for order $id")
}
}
}
verify(route) // hide
@ -59,8 +73,10 @@ class DirectiveExamplesSpec extends RoutingSpec {
val getOrPut = get | put
val route =
path("order" / IntNumber) { id =>
getOrPut { ctx =>
ctx.complete("Received " + ctx.request.method.name + " request for order " + id)
getOrPut {
extractMethod { m =>
complete(s"Received ${m.name} request for order $id")
}
}
}
verify(route) // hide
@ -69,51 +85,18 @@ class DirectiveExamplesSpec extends RoutingSpec {
"example-6" in {
val getOrPut = get | put
val route =
(path("order" / IntNumber) & getOrPut) { id =>
ctx =>
ctx.complete("Received " + ctx.request.method.name + " request for order " + id)
(path("order" / IntNumber) & getOrPut & extractMethod) { (id, m) =>
complete(s"Received ${m.name} request for order $id")
}
verify(route) // hide
}
"example-7" in {
val orderGetOrPut = path("order" / IntNumber) & (get | put)
val orderGetOrPutWithMethod =
path("order" / IntNumber) & (get | put) & extractMethod
val route =
orderGetOrPut { id =>
ctx =>
ctx.complete("Received " + ctx.request.method.name + " request for order " + id)
}
verify(route) // hide
}
"example-8" in {
val orderGetOrPut = path("order" / IntNumber) & (get | put)
val requestMethod = extract(_.request.method)
val route =
orderGetOrPut { id =>
requestMethod { m =>
complete("Received " + m.name + " request for order " + id)
}
}
verify(route) // hide
}
"example-9" in {
val orderGetOrPut = path("order" / IntNumber) & (get | put)
val requestMethod = extract(_.request.method)
val route =
(orderGetOrPut & requestMethod) { (id, m) =>
complete("Received " + m.name + " request for order " + id)
}
verify(route) // hide
}
"example-A" in {
val orderGetOrPutMethod =
path("order" / IntNumber) & (get | put) & extract(_.request.method)
val route =
orderGetOrPutMethod { (id, m) =>
complete("Received " + m.name + " request for order " + id)
orderGetOrPutWithMethod { (id, m) =>
complete(s"Received ${m.name} request for order $id")
}
verify(route) // hide
}

View file

@ -0,0 +1,93 @@
/*
* 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.ActorFlowMaterializer
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 = ActorFlowMaterializer()
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.ActorFlowMaterializer
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 = ActorFlowMaterializer()
val route: Route =
// ... some route structure
null // hide
Http().bindAndHandle(route, "localhost", 8080)
}
//#
}
class ExceptionHandlerExamplesSpec extends RoutingSpec {
"test explicit example" in {
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
Get() ~> Route.seal(ctx => ctx.complete((1 / 0).toString)) ~> check {
responseAs[String] === "Bad numbers, bad result!!!"
}
}
}

View file

@ -0,0 +1,58 @@
/*
* 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 {
Get() ~> smallRoute ~> check {
responseAs[String] should contain("Captain on the bridge")
}
}
"return a 'PONG!' response for GET requests to /ping" in {
Get("/ping") ~> smallRoute ~> check {
responseAs[String] shouldEqual "PONG!"
}
}
"leave GET requests to other paths unhandled" in {
Get("/kermit") ~> smallRoute ~> check {
handled shouldBe false
}
}
"return a MethodNotAllowed error for PUT requests to the root path" in {
Put() ~> Route.seal(smallRoute) ~> check {
status === StatusCodes.MethodNotAllowed
responseAs[String] shouldEqual "HTTP method not allowed, supported methods: GET"
}
}
}
}
//#

View file

@ -0,0 +1,73 @@
/*
* 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.ActorFlowMaterializer
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!")
}
.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 = ActorFlowMaterializer()
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._
Get() ~> Route.seal(reject(MissingCookieRejection("abc"))) ~> check {
responseAs[String] === "No cookies, no service!!!"
}
}
}

View file

@ -2,7 +2,7 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.testkit.ScalatestRouteTest

View file

@ -2,16 +2,14 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
package directives
import scala.util.control.NonFatal
import akka.util.ByteString
import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.server.RouteResult.Rejected
import akka.http.scaladsl.server._
import akka.util.ByteString
import scala.util.control.NonFatal
import akka.http.scaladsl.model._
class BasicDirectivesExamplesSpec extends RoutingSpec {
@ -164,7 +162,7 @@ class BasicDirectivesExamplesSpec extends RoutingSpec {
rejections.nonEmpty shouldEqual true
}
}
"mapRouteResponsePF" in {
"mapRouteResultPF" in {
case object MyCustomRejection extends Rejection
val rejectRejections = // not particularly useful directive
mapRouteResultPF {

View file

@ -2,7 +2,7 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.coding._

View file

@ -2,7 +2,7 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.server._

View file

@ -2,7 +2,7 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model.{ HttpResponse, HttpRequest }

View file

@ -2,7 +2,7 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model.StatusCodes

View file

@ -0,0 +1,29 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.model._
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}")
}
Post("/", FormData(Seq("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'"
}
}
}

View file

@ -0,0 +1,102 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import java.util.concurrent.TimeUnit
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!")
}
val resourceActor = system.actorOf(Props(new Actor {
def receive = { case _ => sender ! "resource" }
}))
implicit val responseTimeout = Timeout(2, TimeUnit.SECONDS)
"example-1" 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}")
}
}
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"
}
}
"example-2" in {
val route =
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
} ~
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
Get("/success") ~> route ~> check {
responseAs[String] shouldEqual "Ok"
}
Get("/failure") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "Unsuccessful future!"
}
}
"example-3" in {
val route =
path("success") {
onFailure(Future { "Ok" }) { extraction =>
failWith(extraction) // not executed.
}
} ~
path("failure") {
onFailure(Future.failed[String](TestException)) { extraction =>
failWith(extraction)
}
}
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,103 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.MissingHeaderRejection
import akka.http.scaladsl.server.Route
import headers._
import StatusCodes._
class HeaderDirectivesExamplesSpec extends RoutingSpec {
"headerValueByName-0" in {
val route =
headerValueByName("X-User-Id") { userId =>
complete(s"The user is $userId")
}
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")
}
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."
}
}
"headerValuePF-0" in {
def extractHostPort: PartialFunction[HttpHeader, Int] = {
case h: `Host` => h.port
}
val route =
headerValuePF(extractHostPort) { port =>
complete(s"The port was $port")
}
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."
}
}
"headerValueByType-0" in {
val route =
headerValueByType[Origin]() { origin
complete(s"The first origin was ${origin.originList.head}")
}
val originHeader = Origin(Seq(HttpOrigin("http://localhost:8080")))
// 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 {
rejection must beLike { case MissingHeaderRejection("Origin") ok }
}
}
"optionalHeaderValueByType-0" in {
val route =
optionalHeaderValueByType[Origin]() {
case Some(origin) complete(s"The first origin was ${origin.originList.head}")
case None complete("No Origin header found.")
}
val originHeader = Origin(Seq(HttpOrigin("http://localhost:8080")))
// 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,88 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import headers._
import StatusCodes._
class HostDirectivesExamplesSpec extends RoutingSpec {
"extract-hostname" in {
val route =
hostName { hn =>
complete(s"Hostname: $hn")
}
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")
}
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")
}
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")
}
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 {
{
host("server-([0-9]).company.(com|net|org)".r) { target =>
complete("Will never complete :'(")
}
} must throwAn[IllegalArgumentException]
}
}

View file

@ -0,0 +1,84 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.model._
import spray.json.DefaultJsonProtocol
import headers._
import StatusCodes._
//# 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}")
}
}
Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""")) ~>
route ~> check {
responseAs[String] shouldEqual "Person: Jane - favorite number: 42"
}
}
"example-produce-with-json" in {
import PersonJsonSupport._
val findPerson = (f: Person => Unit) => {
//... some processing logic...
//complete the request
f(Person("Jane", 42))
}
val route = get {
produce(instanceOf[Person]) { completionFunction => ctx => findPerson(completionFunction) }
}
Get("/") ~> route ~> check {
mediaType shouldEqual `application/json`
responseAs[String] must contain(""""name": "Jane"""")
responseAs[String] must contain(""""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)
}
Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""")) ~>
route ~> check {
mediaType shouldEqual `application/json`
responseAs[String] must contain(""""name": "Jane"""")
responseAs[String] must contain(""""favoriteNumber": 42""")
}
}
}

View file

@ -0,0 +1,80 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
class MethodDirectivesExamplesSpec extends RoutingSpec {
"delete-method" in {
val route = delete { complete("This is a DELETE request.") }
Delete("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a DELETE request."
}
}
"get-method" in {
val route = get { complete("This is a GET request.") }
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a GET request."
}
}
"head-method" in {
val route = head { complete("This is a HEAD request.") }
Head("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a HEAD request."
}
}
"options-method" in {
val route = options { complete("This is an OPTIONS request.") }
Options("/") ~> route ~> check {
responseAs[String] shouldEqual "This is an OPTIONS request."
}
}
"patch-method" in {
val route = patch { complete("This is a PATCH request.") }
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.") }
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.") }
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.") }
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"
}
}
}

View file

@ -0,0 +1,199 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import headers._
class MiscDirectivesExamplesSpec extends RoutingSpec {
"cancelAllRejections-example" in {
def isMethodRejection: Rejection => Boolean = {
case MethodRejection(_) => true
case _ => false
}
val route =
cancelAllRejections(isMethodRejection) {
post {
complete("Result")
}
}
Get("/") ~> route ~> check {
rejections shouldEqual Nil
handled shouldEqual false
}
}
"cancelRejection-example" in {
val route =
cancelRejection(MethodRejection(HttpMethods.POST)) {
post {
complete("Result")
}
}
Get("/") ~> route ~> check {
rejections shouldEqual Nil
handled shouldEqual false
}
}
"clientIP-example" in {
val route = clientIP { ip =>
complete("Client's ip is " + ip.toOption.map(_.getHostAddress).getOrElse("unknown"))
}
Get("/").withHeaders(`Remote-Address`("192.168.3.12")) ~> route ~> check {
responseAs[String] shouldEqual "Client's ip is 192.168.3.12"
}
}
"jsonpWithParameter-example" in {
case class Test(abc: Int)
object TestProtocol {
import spray.json.DefaultJsonProtocol._
implicit val testFormat = jsonFormat(Test, "abc")
}
val route =
jsonpWithParameter("jsonp") {
import TestProtocol._
import spray.httpx.SprayJsonSupport._
complete(Test(456))
}
Get("/?jsonp=result") ~> route ~> check {
responseAs[String] shouldEqual
"""result({
| "abc": 456
|})""".stripMarginWithNewline("\n")
contentType shouldEqual MediaTypes.`application/javascript`.withCharset(HttpCharsets.`UTF-8`)
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual
"""{
| "abc": 456
|}""".stripMarginWithNewline("\n")
contentType shouldEqual ContentTypes.`application/json`
}
}
"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."
}
}
}
}
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")
}
Post("/", "text") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "request entity present"
}
Post("/") ~> route ~> check {
responseAs[String] shouldEqual "request entity empty"
}
}
"requestInstance-example" in {
val route =
requestInstance { request =>
complete(s"Request method is ${request.method} and length is ${request.entity.data.length}")
}
Post("/", "text") ~> route ~> check {
responseAs[String] shouldEqual "Request method is POST and length is 4"
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Request method is GET and length is 0"
}
}
"requestUri-example" in {
val route =
requestUri { uri =>
complete(s"Full URI: $uri")
}
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"
}
}
"rewriteUnmatchedPath-example" in {
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 = rewriteUnmatchedPath(ignore456)
val route =
pathPrefix("123") {
ignoring456 {
path("abc") {
complete(s"Content")
}
}
}
Get("/123/abc") ~> route ~> check {
responseAs[String] shouldEqual "Content"
}
Get("/123456/abc") ~> route ~> check {
responseAs[String] shouldEqual "Content"
}
}
"unmatchedPath-example" in {
val route =
pathPrefix("abc") {
unmatchedPath { remaining =>
complete(s"Unmatched: '$remaining'")
}
}
Get("/abc") ~> route ~> check {
responseAs[String] shouldEqual "Unmatched: ''"
}
Get("/abc/456") ~> route ~> check {
responseAs[String] shouldEqual "Unmatched: '/456'"
}
}
"validate-example" in {
val route =
requestUri { uri =>
validate(uri.path.toString.size < 5, s"Path too long: '${uri.path.toString}'") {
complete(s"Full URI: $uri")
}
}
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,138 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import spray.http.StatusCodes
class ParameterDirectivesExamplesSpec extends RoutingSpec {
"example-1" in {
val route =
parameter('color) { color =>
complete(s"The color is '$color'")
}
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'")
}
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'")
}
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'")
}
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'.")
}
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.")
}
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 integer value"
}
}
"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(", ")}")
}
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(", ")}")
}
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(", ")}")
}
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'"
}
}
}

View file

@ -2,7 +2,7 @@
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.server._

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
import headers._
class RangeDirectivesExamplesSpec extends RoutingSpec {
"withRangeSupport" in {
val route =
withRangeSupport(4, 2L) {
complete("ABCDEFGH")
}
Get() ~> addHeader(Range(ByteRange(3, 4))) ~> route ~> check {
headers must contain(`Content-Range`(ContentRange(3, 4, 8)))
status shouldEqual StatusCodes.PartialContent
responseAs[String] shouldEqual "DE"
}
Get() ~> addHeader(Range(ByteRange(0, 1), ByteRange(1, 2), ByteRange(6, 7))) ~> route ~> check {
headers must not(contain(like[HttpHeader] { case `Content-Range`(_, _) ok }))
responseAs[MultipartByteRanges] must beLike {
case MultipartByteRanges(
BodyPart(entity1, `Content-Range`(RangeUnit.Bytes, range1) +: _) +:
BodyPart(entity2, `Content-Range`(RangeUnit.Bytes, range2) +: _) +: Seq()
) entity1.asString shouldEqual "ABC" and range1 shouldEqual ContentRange(0, 2, 8) and
entity2.asString shouldEqual "GH" and range2 shouldEqual ContentRange(6, 7, 8)
}
}
}
}

View file

@ -0,0 +1,105 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.server.UnacceptedResponseContentTypeRejection
import akka.http.scaladsl.model._
import headers._
class RespondWithDirectivesExamplesSpec extends RoutingSpec {
"respondWithHeader-examples" in {
val route =
path("foo") {
respondWithHeader(RawHeader("Funky-Muppet", "gonzo")) {
complete("beep")
}
}
Get("/foo") ~> route ~> check {
header("Funky-Muppet") shouldEqual Some(RawHeader("Funky-Muppet", "gonzo"))
responseAs[String] shouldEqual "beep"
}
}
"respondWithHeaders-examples" in {
val route =
path("foo") {
respondWithHeaders(RawHeader("Funky-Muppet", "gonzo"), Origin(Seq(HttpOrigin("http://spray.io")))) {
complete("beep")
}
}
Get("/foo") ~> route ~> check {
header("Funky-Muppet") shouldEqual Some(RawHeader("Funky-Muppet", "gonzo"))
header[Origin] shouldEqual Some(Origin(Seq(HttpOrigin("http://spray.io"))))
responseAs[String] shouldEqual "beep"
}
}
"respondWithMediaType-examples" in {
import MediaTypes._
val route =
path("foo") {
respondWithMediaType(`application/json`) {
complete("[]") // marshalled to `text/plain` here
}
}
Get("/foo") ~> route ~> check {
mediaType shouldEqual `application/json`
responseAs[String] shouldEqual "[]"
}
Get("/foo") ~> Accept(MediaRanges.`text/*`) ~> route ~> check {
rejection shouldEqual UnacceptedResponseContentTypeRejection(ContentType(`application/json`) :: Nil)
}
}
"respondWithSingletonHeader-examples" in {
val respondWithMuppetHeader =
respondWithSingletonHeader(RawHeader("Funky-Muppet", "gonzo"))
val route =
path("foo") {
respondWithMuppetHeader {
complete("beep")
}
} ~
path("bar") {
respondWithMuppetHeader {
respondWithHeader(RawHeader("Funky-Muppet", "kermit")) {
complete("beep")
}
}
}
Get("/foo") ~> route ~> check {
headers.filter(_.is("funky-muppet")) shouldEqual List(RawHeader("Funky-Muppet", "gonzo"))
responseAs[String] shouldEqual "beep"
}
Get("/bar") ~> route ~> check {
headers.filter(_.is("funky-muppet")) shouldEqual List(RawHeader("Funky-Muppet", "kermit"))
responseAs[String] shouldEqual "beep"
}
}
"respondWithStatus-examples" in {
val route =
path("foo") {
respondWithStatus(201) {
complete("beep")
}
}
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.Created
responseAs[String] shouldEqual "beep"
}
}
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import spray.http.{ RequestProcessingException, HttpResponse, StatusCodes }
import spray.routing.ValidationRejection
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
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!"))
}
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)
}
}
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 {
val route =
path("foo") {
failWith(new RequestProcessingException(StatusCodes.BandwidthLimitExceeded))
}
Get("/foo") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BandwidthLimitExceeded
responseAs[String] shouldEqual "Bandwidth limit has been exceeded."
}
}
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model._
class SchemeDirectivesExamplesSpec extends RoutingSpec {
"example-1" in {
val route =
schemeName { scheme =>
complete(s"The scheme is '${scheme}'")
}
Get("https://www.example.com/") ~> route ~> check {
responseAs[String] shouldEqual "The scheme is 'https'"
}
}
"example-2" in {
val route =
scheme("http") {
extract(_.request.uri) { uri
redirect(uri.copy(scheme = "https"), MovedPermanently)
}
} ~
scheme("https") {
complete(s"Safe and secure!")
}
Get("http://www.example.com/hello") ~> route ~> check {
status shouldEqual MovedPermanently
header[headers.Location] shouldEqual Some(headers.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,120 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import com.typesafe.config.ConfigFactory
import scala.concurrent.Future
import akka.http.scaladsl.model._
import headers._
class SecurityDirectivesExamplesSpec extends RoutingSpec {
"authenticate-custom-user-pass-authenticator" in {
def myUserPassAuthenticator(userPass: Option[UserPass]): Future[Option[String]] =
Future {
if (userPass.exists(up => up.user == "John" && up.pass == "p4ssw0rd")) Some("John")
else None
}
val route =
Route.seal {
path("secured") {
authenticateBasic(BasicAuth(myUserPassAuthenticator _, realm = "secure site")) { userName =>
complete(s"The user is '$userName'")
}
}
}
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")
}
}
"authenticate-from-config" in {
def extractUser(userPass: UserPass): String = userPass.user
val config = ConfigFactory.parseString("John = p4ssw0rd")
val route =
Route.seal {
path("secured") {
authenticateBasic(BasicAuth(realm = "secure site", config = config, createUser = extractUser _)) { userName =>
complete(s"The user is '$userName'")
}
}
}
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")
}
}
"authorize-1" in {
def extractUser(userPass: UserPass): String = userPass.user
val config = ConfigFactory.parseString("John = p4ssw0rd\nPeter = pan")
def hasPermissionToPetersLair(userName: String) = userName == "Peter"
val route =
Route.seal {
authenticateBasic(BasicAuth(realm = "secure site", config = config, createUser = extractUser _)) { userName =>
path("peters-lair") {
authorize(hasPermissionToPetersLair(userName)) {
complete(s"'$userName' visited Peter's lair")
}
}
}
}
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"
}
}
}

View file

@ -1,48 +0,0 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
import akka.actor.ActorSystem
import akka.http.scaladsl.server.Route
import akka.stream.ActorFlowMaterializer
object MyHandler {
//# example-1
import akka.http.scaladsl.model.HttpResponse
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.server._
import Directives._
implicit def myExceptionHandler =
ExceptionHandler {
case e: ArithmeticException =>
extractUri { uri =>
logWarning(s"Request to $uri could not be handled normally")
complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
}
}
object MyApp {
implicit val system = ActorSystem()
import system.dispatcher
implicit val materializer = ActorFlowMaterializer()
def handler = Route.handlerFlow(`<my-route-definition>`)
}
//#
def `<my-route-definition>`: Route = null
def logWarning(str: String): Unit = {}
}
class ExceptionHandlerExamplesSpec extends RoutingSpec {
import MyHandler._
"example" in {
Get() ~> Route.seal(ctx => ctx.complete((1 / 0).toString)) ~> check {
responseAs[String] === "Bad numbers, bad result!!!"
}
}
}

View file

@ -1,64 +0,0 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.server
import akka.actor.ActorSystem
import akka.stream.ActorFlowMaterializer
import akka.http.scaladsl.server.{ Route, MissingCookieRejection }
import scala.concurrent.ExecutionContext
object MyRejectionHandler {
//# example-1
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._
implicit val myRejectionHandler = RejectionHandler.newBuilder()
.handle {
case MissingCookieRejection(cookieName) =>
complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
}
.result()
object MyApp {
implicit val system = ActorSystem()
import system.dispatcher
implicit val materializer = ActorFlowMaterializer()
def handler = Route.handlerFlow(`<my-route-definition>`)
}
//#
def `<my-route-definition>`: Route = null
}
class RejectionHandlerExamplesSpec extends RoutingSpec {
import MyRejectionHandler._
"example" in {
Get() ~> Route.seal(reject(MissingCookieRejection("abc"))) ~> check {
responseAs[String] === "No cookies, no service!!!"
}
}
"example-2" in {
import akka.http.scaladsl.coding.Gzip
val route =
path("order") {
get {
complete("Received GET")
} ~
post {
decodeRequestWith(Gzip) {
complete("Received POST")
}
}
}
}
}