Merge pull request #18627 from ktoso/wip-directives-bonanza-ktoso

Akka HTTP Directives Documentation Bonanza
This commit is contained in:
Konrad Malawski 2015-10-07 10:47:15 +02:00
commit de9262ab8a
48 changed files with 1451 additions and 193 deletions

View file

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

View file

@ -5,6 +5,15 @@
package docs.http.scaladsl.server
package directives
import java.io.File
import akka.event.Logging
import akka.http.scaladsl.model.HttpEntity.Chunked
import akka.stream.ActorMaterializer
import akka.stream.io.SynchronousFileSource
import akka.stream.scaladsl.{ Sink, Source }
import scala.concurrent.Future
import scala.util.control.NonFatal
import akka.util.ByteString
import akka.http.scaladsl.model.headers.RawHeader
@ -24,6 +33,127 @@ class BasicDirectivesExamplesSpec extends RoutingSpec {
responseAs[String] shouldEqual "The length of the request URI is 25"
}
}
"0extractLog" in {
val route =
extractLog { log =>
log.debug("I'm logging things in much detail..!")
complete("It's amazing!")
}
Get("/abcdef") ~> route ~> check {
responseAs[String] shouldEqual "It's amazing!"
}
}
"0withMaterializer" in {
val special = ActorMaterializer(namePrefix = Some("special"))
def sample() =
path("sample") {
extractMaterializer { mat =>
complete {
// explicitly use the materializer:
Source.single(s"Materialized by ${mat.##}!")
.runWith(Sink.head)(mat)
}
}
}
val route =
pathPrefix("special") {
withMaterializer(special) {
sample() // `special` materializer will be used
}
} ~ sample() // default materializer will be used
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${materializer.##}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${special.##}!"
}
}
"0withExecutionContext" in compileOnlySpec {
val special = system.dispatchers.lookup("special")
def sample() =
path("sample") {
extractExecutionContext { implicit ec =>
complete {
Future(s"Run on ${ec.##}!") // uses the `ec` ExecutionContext
}
}
}
val route =
pathPrefix("special") {
withExecutionContext(special) {
sample() // `special` execution context will be used
}
} ~ sample() // default execution context will be used
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${system.dispatcher.##}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${special.##}!"
}
}
"0withLog" in {
val special = Logging(system, "SpecialRoutes")
def sample() =
path("sample") {
extractLog { implicit log =>
complete {
val msg = s"Logging using $log!"
log.debug(msg)
msg
}
}
}
val route =
pathPrefix("special") {
withLog(special) {
sample() // `special` logging adapter will be used
}
} ~ sample() // default logging adapter will be used
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Logging using ${system.log}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Logging using $special!"
}
}
"0withSettings" in compileOnlySpec {
val special = RoutingSettings(system).copy(fileIODispatcher = "special-io-dispatcher")
def sample() =
path("sample") {
complete {
// internally uses the configured fileIODispatcher:
val source = SynchronousFileSource(new File("example.json"))
HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, source))
}
}
val route =
get {
pathPrefix("special") {
withSettings(special) {
sample() // `special` file-io-dispatcher will be used to read the file
}
} ~ sample() // default file-io-dispatcher will be used to read the file
}
Post("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"{}"
}
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual "{}"
}
}
"textract" in {
val pathAndQuery = textract { ctx =>
val uri = ctx.request.uri
@ -291,4 +421,6 @@ class BasicDirectivesExamplesSpec extends RoutingSpec {
responseAs[String] shouldEqual "Unmatched: '/456'"
}
}
private def compileOnlySpec(block: => Unit) = pending
}

View file

@ -0,0 +1,71 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import java.io.File
import akka.event.Logging
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.RawHeader
import akka.http.scaladsl.server.RouteResult.Rejected
import akka.http.scaladsl.server._
import akka.http.scaladsl.server.directives.DirectoryListing
import akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer
import akka.stream.ActorMaterializer
import akka.stream.io.SynchronousFileSource
import akka.stream.scaladsl.{ Sink, Source }
import akka.util.ByteString
import docs.http.scaladsl.server.RoutingSpec
import scala.concurrent.Future
import scala.util.control.NonFatal
class FileAndResourceDirectivesExamplesSpec extends RoutingSpec {
"0getFromFile" in compileOnlySpec {
import akka.http.scaladsl.server.directives._
import ContentTypeResolver.Default
val route =
path("logs" / Segment) { name =>
getFromFile(".log") // uses implicit ContentTypeResolver
}
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "The length of the request URI is 25"
}
}
"0getFromResource" in compileOnlySpec {
import akka.http.scaladsl.server.directives._
import ContentTypeResolver.Default
val route =
path("logs" / Segment) { name =>
getFromResource(".log") // uses implicit ContentTypeResolver
}
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "The length of the request URI is 25"
}
}
"0listDirectoryContents" in compileOnlySpec {
val route =
path("tmp") {
listDirectoryContents("/tmp")
} ~
path("custom") {
val renderer = new DirectoryRenderer {
override def marshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing] = ???
}
listDirectoryContents("/tmp")(renderer)
}
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "The length of the request URI is 25"
}
}
private def compileOnlySpec(block: => Unit) = pending
}

View file

@ -47,6 +47,55 @@ class HeaderDirectivesExamplesSpec extends RoutingSpec with Inside {
responseAs[String] shouldEqual "The requested resource could not be found."
}
}
"optionalHeaderValue-0" in {
def extractHostPort: HttpHeader => Option[Int] = {
case h: `Host` => Some(h.port)
case x => None
}
val route =
optionalHeaderValue(extractHostPort) {
case Some(port) => complete(s"The port was $port")
case None => complete(s"The port was not provided explicitly")
} ~ // can also be written as:
optionalHeaderValue(extractHostPort) { port =>
complete {
port match {
case Some(p) => s"The port was $p"
case _ => "The port was not provided explicitly"
}
}
}
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "The port was not provided explicitly"
}
}
"optionalHeaderValueByName-0" in {
val route =
optionalHeaderValueByName("X-User-Id") {
case Some(userId) => complete(s"The user is $userId")
case None => complete(s"No user was provided")
} ~ // can also be written as:
optionalHeaderValueByName("port") { port =>
complete {
port match {
case Some(p) => s"The user is $p"
case _ => "No user was provided"
}
}
}
Get("/") ~> RawHeader("X-User-Id", "Joe42") ~> route ~> check {
responseAs[String] shouldEqual "The user is Joe42"
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "No user was provided"
}
}
"headerValuePF-0" in {
def extractHostPort: PartialFunction[HttpHeader, Int] = {
case h: `Host` => h.port
@ -65,6 +114,32 @@ class HeaderDirectivesExamplesSpec extends RoutingSpec with Inside {
responseAs[String] shouldEqual "The requested resource could not be found."
}
}
"optionalHeaderValuePF-0" in {
def extractHostPort: PartialFunction[HttpHeader, Int] = {
case h: `Host` => h.port
}
val route =
optionalHeaderValuePF(extractHostPort) {
case Some(port) => complete(s"The port was $port")
case None => complete(s"The port was not provided explicitly")
} ~ // can also be written as:
optionalHeaderValuePF(extractHostPort) { port =>
complete {
port match {
case Some(p) => s"The port was $p"
case _ => "The port was not provided explicitly"
}
}
}
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "The port was not provided explicitly"
}
}
"headerValueByType-0" in {
val route =
headerValueByType[Origin]() { origin

View file

@ -78,4 +78,25 @@ class MethodDirectivesExamplesSpec extends RoutingSpec {
responseAs[String] shouldEqual "HTTP method not allowed, supported methods: PUT"
}
}
"extractMethod-example" in {
val route =
get {
complete("This is a GET request.")
} ~
extractMethod { method =>
complete(s"This ${method.name} request, clearly is not a GET!")
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a GET request."
}
Put("/") ~> route ~> check {
responseAs[String] shouldEqual "This PUT request, clearly is not a GET!"
}
Head("/") ~> route ~> check {
responseAs[String] shouldEqual "This HEAD request, clearly is not a GET!"
}
}
}

View file

@ -1,10 +1,11 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.server._
class PathDirectivesExamplesSpec extends RoutingSpec {
@ -268,4 +269,100 @@ class PathDirectivesExamplesSpec extends RoutingSpec {
handled shouldEqual false
}
}
"redirectToTrailingSlashIfMissing-0" in {
import akka.http.scaladsl.model.StatusCodes
val route =
redirectToTrailingSlashIfMissing(StatusCodes.MovedPermanently) {
path("foo"./) {
// We require the explicit trailing slash in the path
complete("OK")
} ~
path("bad-1") {
// MISTAKE!
// Missing `/` in path, causes this path to never match,
// because it is inside a `redirectToTrailingSlashIfMissing`
???
} ~
path("bad-2/") {
// MISTAKE!
// / should be explicit as path element separator and not *in* the path element
// So it should be: "bad-1" /
???
}
}
// Redirected:
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.MovedPermanently
// results in nice human readable message,
// in case the redirect can't be followed automatically:
responseAs[String] shouldEqual {
"This and all future requests should be directed to " +
"<a href=\"http://example.com/foo/\">this URI</a>."
}
}
// Properly handled:
Get("/foo/") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "OK"
}
// MISTAKE! will never match - reason explained in routes
Get("/bad-1/") ~> route ~> check {
handled shouldEqual false
}
// MISTAKE! will never match - reason explained in routes
Get("/bad-2/") ~> route ~> check {
handled shouldEqual false
}
}
"redirectToNoTrailingSlashIfPresent-0" in {
import akka.http.scaladsl.model.StatusCodes
val route =
redirectToNoTrailingSlashIfPresent(StatusCodes.MovedPermanently) {
path("foo") {
// We require the explicit trailing slash in the path
complete("OK")
} ~
path("bad"./) {
// MISTAKE!
// Since inside a `redirectToNoTrailingSlashIfPresent` directive
// the matched path here will never contain a trailing slash,
// thus this path will never match.
//
// It should be `path("bad")` instead.
???
}
}
// Redirected:
Get("/foo/") ~> route ~> check {
status shouldEqual StatusCodes.MovedPermanently
// results in nice human readable message,
// in case the redirect can't be followed automatically:
responseAs[String] shouldEqual {
"This and all future requests should be directed to " +
"<a href=\"http://example.com/foo\">this URI</a>."
}
}
// Properly handled:
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "OK"
}
// MISTAKE! will never match - reason explained in routes
Get("/bad") ~> route ~> check {
handled shouldEqual false
}
}
}

View file

@ -4,14 +4,15 @@
package docs.http.scaladsl.server
package directives
/*
import akka.http.scaladsl.server.UnacceptedResponseContentTypeRejection
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import headers._
class RespondWithDirectivesExamplesSpec extends RoutingSpec {
"respondWithHeader-examples" in {
"respondWithHeader-0" in {
val route =
path("foo") {
respondWithHeader(RawHeader("Funky-Muppet", "gonzo")) {
@ -25,82 +26,122 @@ class RespondWithDirectivesExamplesSpec extends RoutingSpec {
}
}
"respondWithHeaders-examples" in {
"respondWithDefaultHeader-0" in {
// custom headers
val blippy = RawHeader("X-Fish-Name", "Blippy")
val elTonno = RawHeader("X-Fish-Name", "El Tonno")
// format: OFF
// by default always include the Blippy header,
// unless a more specific X-Fish-Name is given by the inner route
val route =
respondWithDefaultHeader(blippy) { // blippy
respondWithHeader(elTonno) { // / el tonno
path("el-tonno") { // | /
complete("¡Ay blippy!") // | |- el tonno
} ~ // | |
path("los-tonnos") { // | |
complete("¡Ay ay blippy!") // | |- el tonno
} // | |
} ~ // | x
complete("Blip!") // |- blippy
} // x
// format: ON
Get("/") ~> route ~> check {
header("X-Fish-Name") shouldEqual Some(RawHeader("X-Fish-Name", "Blippy"))
responseAs[String] shouldEqual "Blip!"
}
Get("/el-tonno") ~> route ~> check {
header("X-Fish-Name") shouldEqual Some(RawHeader("X-Fish-Name", "El Tonno"))
responseAs[String] shouldEqual "¡Ay blippy!"
}
Get("/los-tonnos") ~> route ~> check {
header("X-Fish-Name") shouldEqual Some(RawHeader("X-Fish-Name", "El Tonno"))
responseAs[String] shouldEqual "¡Ay ay blippy!"
}
}
// format: ON
"respondWithHeaders-0" in {
val route =
path("foo") {
respondWithHeaders(RawHeader("Funky-Muppet", "gonzo"), Origin(Seq(HttpOrigin("http://akka.io")))) {
respondWithHeaders(RawHeader("Funky-Muppet", "gonzo"), Origin(HttpOrigin("http://akka.io"))) {
complete("beep")
}
}
Get("/foo") ~> route ~> check {
header("Funky-Muppet") shouldEqual Some(RawHeader("Funky-Muppet", "gonzo"))
header[Origin] shouldEqual Some(Origin(Seq(HttpOrigin("http://akka.io"))))
header[Origin] shouldEqual Some(Origin(HttpOrigin("http://akka.io")))
responseAs[String] shouldEqual "beep"
}
}
"respondWithMediaType-examples" in {
import MediaTypes._
// FIXME awaiting resolution of https://github.com/akka/akka/issues/18625
// "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)
// }
// }
val route =
path("foo") {
respondWithMediaType(`application/json`) {
complete("[]") // marshalled to `text/plain` here
}
}
// "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"
// }
// }
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"
}
}
// FIXME https://github.com/akka/akka/issues/18626
// "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

@ -1,28 +1,117 @@
/*
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server
package directives
/*
import com.typesafe.config.ConfigFactory
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.directives.Credentials
import akka.stream.scaladsl.{ FlowGraph, Flow, FlattenStrategy }
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]] =
"authenticateBasic-0" in {
def myUserPassAuthenticator(credentials: Credentials): Option[String] =
credentials match {
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd") => Some(id)
case _ => None
}
val route =
Route.seal {
path("secured") {
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
}
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The supplied authentication is invalid"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
}
}
"authenticateBasicAsync-0" in {
def myUserPassAuthenticator(credentials: Credentials): Future[Option[String]] =
credentials match {
case p @ Credentials.Provided(id) =>
Future {
// potentially
if (p.verify("p4ssw0rd")) Some(id)
else None
}
case _ => Future.successful(None)
}
val route =
Route.seal {
path("secured") {
authenticateBasicAsync(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
}
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John'"
}
val invalidCredentials = BasicHttpCredentials("Peter", "pan")
Get("/secured") ~>
addCredentials(invalidCredentials) ~> // adds Authorization header
route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The supplied authentication is invalid"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
}
}
"authenticateOrRejectWithChallenge-0" in {
val challenge = HttpChallenge("MyAuth", "MyRealm")
// your custom authentication logic:
def auth(creds: HttpCredentials): Boolean = true
def myUserPassAuthenticator(credentials: Option[HttpCredentials]): Future[AuthenticationResult[String]] =
Future {
if (userPass.exists(up => up.user == "John" && up.pass == "p4ssw0rd")) Some("John")
else None
credentials match {
case Some(creds) if auth(creds) => Right("some-user-name-from-creds")
case _ => Left(challenge)
}
}
val route =
Route.seal {
path("secured") {
authenticateBasic(BasicAuth(myUserPassAuthenticator _, realm = "secure site")) { userName =>
complete(s"The user is '$userName'")
authenticateOrRejectWithChallenge(myUserPassAuthenticator _) { userName =>
complete("Authenticated!")
}
}
}
@ -30,92 +119,77 @@ class SecurityDirectivesExamplesSpec extends RoutingSpec {
Get("/secured") ~> route ~> check {
status shouldEqual StatusCodes.Unauthorized
responseAs[String] shouldEqual "The resource requires authentication, which was not supplied with the request"
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("Basic", "secure site")
header[`WWW-Authenticate`].get.challenges.head shouldEqual HttpChallenge("MyAuth", "MyRealm")
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~>
addCredentials(validCredentials) ~> // adds Authorization header
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")
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Authenticated!"
}
}
"authenticate-from-config" in {
def extractUser(userPass: UserPass): String = userPass.user
val config = ConfigFactory.parseString("John = p4ssw0rd")
"0authorize" in {
case class User(name: String)
// authenticate the user:
def myUserPassAuthenticator(credentials: Credentials): Option[User] =
credentials match {
case Credentials.Provided(id) => Some(User(id))
case _ => None
}
// check if user is authorized to perform admin actions:
val admins = Set("Peter")
def hasAdminPermissions(user: User): Boolean =
admins.contains(user.name)
val route =
Route.seal {
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 =>
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { user =>
path("peters-lair") {
authorize(hasPermissionToPetersLair(userName)) {
complete(s"'$userName' visited Peter's lair")
authorize(hasAdminPermissions(user)) {
complete(s"'${user.name}' visited Peter's lair")
}
}
}
}
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
Get("/peters-lair") ~>
addCredentials(johnsCred) ~> // adds Authorization header
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
Get("/peters-lair") ~> addCredentials(petersCred) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "'Peter' visited Peter's lair"
}
}
"0extractCredentials" in {
val route =
extractCredentials { creds =>
complete {
creds match {
case Some(c) => "Credentials: " + c
case _ => "No credentials"
}
}
}
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
Get("/") ~> addCredentials(johnsCred) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "Credentials: Basic Sm9objpwNHNzdzByZA=="
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "No credentials"
}
}
}
*/