=doc Significantly extend HTTP documentation with new content and ports from spray docs
This commit is contained in:
parent
6149b328a0
commit
20759e1b34
238 changed files with 6541 additions and 1563 deletions
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 = ???
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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!!!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//#
|
||||
|
|
@ -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!!!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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._
|
||||
|
|
@ -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._
|
||||
|
|
@ -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 }
|
||||
|
|
@ -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
|
||||
|
|
@ -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'"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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'"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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._
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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!!!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue