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"
}
}
}
*/

View file

@ -3,7 +3,7 @@
extractLog
==========
...
Extracts a :class:`LoggingAdapter` from the request context which can be used for logging inside the route.
Signature
---------
@ -14,7 +14,10 @@ Signature
Description
-----------
...
The ``extractLog`` directive is used for providing logging to routes, such that they don't have to depend on
closing over a logger provided in the class body.
See :ref:`-extract-` and :ref:`ProvideDirectives` for an overview of similar directives.
Example
-------

View file

@ -3,7 +3,11 @@
extractRequestContext
=====================
...
Extracts the request's underlying :class:`RequestContext`.
This directive is used as a building block for most of the other directives,
which extract the context and by inspecting some of it's values can decide
what to do with the request - for example provide a value, or reject the request.
Signature
---------

View file

@ -3,7 +3,7 @@
extractSettings
===============
...
Extracts the ``RoutingSettings`` from the :class:`RequestContext`.
Signature
---------
@ -14,7 +14,10 @@ Signature
Description
-----------
...
Extracts the ``RoutingSettings`` from the :class:`RequestContext`.
By default the settings of the ``Http()`` extension running the route will be returned.
It is possible to override the settings for specific sub-routes by using the :ref:`-withSettings-` directive.
Example
-------

View file

@ -3,7 +3,7 @@
withExecutionContext
====================
...
Runs its inner route with the given alternative :class:`ExecutionContext`.
Signature
---------
@ -14,7 +14,11 @@ Signature
Description
-----------
...
Allows running an inner route using an alternative ``ExecutionContext`` in place of the default one.
The execution context can be extracted in an inner route using :ref:`-extractExecutionContext-` directly,
or used by directives which internally extract the materializer without sufracing this fact in the API.
Example
-------

View file

@ -3,7 +3,7 @@
withLog
=======
...
Runs its inner route with the given alternative :class:`LoggingAdapter`.
Signature
---------
@ -14,7 +14,11 @@ Signature
Description
-----------
...
Allows running an inner route using an alternative :class:`LoggingAdapter` in place of the default one.
The logging adapter can be extracted in an inner route using :ref:`-extractLog-` directly,
or used by directives which internally extract the materializer without sufracing this fact in the API.
Example
-------

View file

@ -3,7 +3,7 @@
withMaterializer
================
...
Runs its inner route with the given alternative ``Materializer``.
Signature
---------
@ -14,7 +14,11 @@ Signature
Description
-----------
...
Allows running an inner route using an alternative ``Materializer`` in place of the default one.
The materializer can be extracted in an inner route using :ref:`-extractMaterializer-` directly,
or used by directives which internally extract the materializer without sufracing this fact in the API
(e.g. responding with a Chunked entity).
Example
-------

View file

@ -3,7 +3,7 @@
withSettings
============
...
Runs its inner route with the given alternative :class:`RoutingSettings`.
Signature
---------
@ -14,7 +14,10 @@ Signature
Description
-----------
...
Allows running an inner route using an alternative :class:`RoutingSettings` in place of the default one.
The execution context can be extracted in an inner route using :ref:`-extractSettings-` directly,
or used by directives which internally extract the materializer without sufracing this fact in the API.
Example
-------

View file

@ -11,3 +11,11 @@ Signature
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/FileAndResourceDirectives.scala
:snippet: getFromBrowseableDirectory
Description
-----------
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala
:snippet: 0extractSettings

View file

@ -24,3 +24,8 @@ To serve a single file use ``getFromFile``. To serve browsable directory listing
To serve files from a classpath directory use ``getFromResourceDirectory`` instead.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Example
-------
...

View file

@ -20,4 +20,9 @@ some other thread !). If the file cannot be found or read the request is rejecte
To serve files from a directory use ``getFromDirectory``, instead. To serve a file from a classpath resource
use ``getFromResource`` instead.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Example
-------
...

View file

@ -14,10 +14,15 @@ Signature
Description
-----------
The actual I/O operation is running detached in a `Future`, so it doesn't block the current thread (but potentially
The actual I/O operation is running detached in a ``Future``, so it doesn't block the current thread (but potentially
some other thread !). If the file cannot be found or read the request is rejected.
To serve files from a classpath directory use ``getFromResourceDirectory`` instead. To serve files from a filesystem
directory use ``getFromDirectory``, instead.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Example
-------
...

View file

@ -20,4 +20,9 @@ some other thread !). If the file cannot be found or read the request is rejecte
To serve a single resource use ``getFromResource``, instead. To server files from a filesystem directory use
``getFromDirectory`` instead.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Example
-------
...

View file

@ -21,4 +21,9 @@ instead.
The rendering can be overridden by providing a custom ``Marshaller[DirectoryListing]``.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests.
Example
-------
...

View file

@ -17,3 +17,9 @@ Description
The ``optionalHeaderValue`` directive is similar to the ``headerValue`` directive but always extracts an ``Option``
value instead of rejecting the request if no matching header could be found.
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/HeaderDirectivesExamplesSpec.scala
:snippet: optionalHeaderValue-0

View file

@ -16,3 +16,9 @@ Description
The ``optionalHeaderValueByName`` directive is similar to the ``headerValueByName`` directive but always extracts
an ``Option`` value instead of rejecting the request if no matching header could be found.
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/HeaderDirectivesExamplesSpec.scala
:snippet: optionalHeaderValueByName-0

View file

@ -17,3 +17,9 @@ Description
The ``optionalHeaderValuePF`` directive is similar to the ``headerValuePF`` directive but always extracts an ``Option``
value instead of rejecting the request if no matching header could be found.
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/HeaderDirectivesExamplesSpec.scala
:snippet: optionalHeaderValuePF-0

View file

@ -3,7 +3,7 @@
extractMethod
=============
...
Extracts the :class:`HttpMethod` from the request context which can be used programatically in a route.
Signature
---------
@ -14,10 +14,14 @@ Signature
Description
-----------
...
Extracts the :class:`HttpMethod` from the request context and provides it for use for other directives explicitly.
Example
-------
In the below example our route first matches all ``GET`` requests, and if an incoming request wasn't a ``GET``,
the matching continues and the extractMethod route will be applied which we can use to programatically
print what type of request it was - independent of what actual HttpMethod it was:
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/MethodDirectivesExamplesSpec.scala
:snippet: 0extractMethod
:snippet: extractMethod-example

View file

@ -3,8 +3,7 @@
validate
========
Checks an arbitrary condition and passes control to the inner route if it returns ``true``. Otherwise, rejects the
request with a ``ValidationRejection`` containing the given error message.
Allows validating a precondition before handling a route.
Signature
---------
@ -12,6 +11,11 @@ Signature
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/MiscDirectives.scala
:snippet: validate
Description
-----------
Checks an arbitrary condition and passes control to the inner route if it returns ``true``.
Otherwise, rejects the request with a ``ValidationRejection`` containing the given error message.
Example
-------

View file

@ -3,7 +3,10 @@
redirectToNoTrailingSlashIfPresent
==================================
...
If the requested path does end with a trailing ``/`` character,
redirects to the same path without that trailing slash..
Opposite of :ref:`-redirectToTrailingSlashIfMissing-`.
Signature
---------
@ -14,10 +17,22 @@ Signature
Description
-----------
...
Redirects the HTTP Client to the same resource yet without the trailing ``/``, in case the request contained it.
When redirecting an HttpResponse with the given redirect response code (i.e. ``MovedPermanently`` or ``TemporaryRedirect``
etc.) as well as a simple HTML page containing a "*click me to follow redirect*" link to be used in case the client can not,
or refuses to for security reasons, automatically follow redirects.
Please note that the inner paths **MUST NOT** end with an explicit trailing slash (e.g. ``"things"./``)
for the re-directed-to route to match.
A good read on the subject of how to deal with trailing slashes is available on `Google Webmaster Central - To Slash or not to Slash`_.
.. _Google Webmaster Central - To Slash or not to Slash: http://googlewebmastercentral.blogspot.de/2010/04/to-slash-or-not-to-slash.html
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/PathDirectivesExamplesSpec.scala
:snippet: 0redirectToNoTrailingSlashIfPresent
:snippet: redirectToNoTrailingSlashIfPresent-0
See also :ref:`-redirectToTrailingSlashIfMissing-` which achieves the opposite - redirecting paths in case they do *not* have a trailing slash.

View file

@ -3,7 +3,10 @@
redirectToTrailingSlashIfMissing
================================
...
If the requested path does not end with a trailing ``/`` character,
redirects to the same path followed by such trailing slash.
Opposite of :ref:`-redirectToNoTrailingSlashIfPresent-`.
Signature
---------
@ -14,10 +17,18 @@ Signature
Description
-----------
...
Redirects the HTTP Client to the same resource yet followed by a trailing ``/``, in case the request did not contain it.
When redirecting an HttpResponse with the given redirect response code (i.e. ``MovedPermanently`` or ``TemporaryRedirect``
etc.) as well as a simple HTML page containing a "*click me to follow redirect*" link to be used in case the client can not,
or refuses to for security reasons, automatically follow redirects.
Please note that the inner paths **MUST** end with an explicit trailing slash (e.g. ``"things"./``) for the
re-directed-to route to match.
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/PathDirectivesExamplesSpec.scala
:snippet: 0redirectToTrailingSlashIfMissing
:snippet: redirectToTrailingSlashIfMissing-0
See also :ref:`-redirectToNoTrailingSlashIfPresent-` which achieves the opposite - redirecting paths in case they do have a trailing slash.

View file

@ -20,11 +20,13 @@ Description
This directive transforms ``HttpResponse`` and ``ChunkedResponseStart`` messages coming back from its inner route by
potentially adding the given ``HttpHeader`` instance to the headers list.
The header is only added if there is no header instance with the same name (case insensitively) already present in the
response. If you'd like to add more than one header you can use the :ref:`-respondWithDefaultHeaders-` directive instead.
response.
If you'd like to add more than one header you can use the :ref:`-respondWithDefaultHeaders-` directive instead.
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala
:snippet: respondWithDefaultHeader-examples
:snippet: respondWithDefaultHeader-0

View file

@ -26,4 +26,17 @@ response. If you'd like to add only a single header you can use the :ref:`-respo
Example
-------
The ``respondWithDefaultHeaders`` directive is equivalent to the ``respondWithDefaultHeader`` directive which
is shown in the example below, however it allows including multiple default headers at once in the directive, like so::
respondWithDefaultHeaders(
Origin(HttpOrigin("http://akka.io"),
RawHeader("X-Fish-Name", "Blippy"))) { /*...*/ }
The semantics remain the same however, as explained by the following example:
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala
:snippet: respondWithDefaultHeader-0
See the :ref:`-respondWithDefaultHeader-` directive for an example with only one header.

View file

@ -25,4 +25,4 @@ Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala
:snippet: respondWithHeader-examples
:snippet: respondWithHeader-0

View file

@ -25,4 +25,4 @@ Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala
:snippet: respondWithHeaders-examples
:snippet: respondWithHeaders-0

View file

@ -3,21 +3,41 @@
authenticateBasic
=================
...
Wraps the inner route with Http Basic authentication support using a given ``Authenticator[T]``.
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authenticator
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateBasic
Description
-----------
...
Provides support for handling `HTTP Basic Authentication`_.
Given a function returning ``Some[T]`` upon successful authentication and ``None`` otherwise,
respectively applies the inner route or rejects the request with a :class:`AuthenticationFailedRejection` rejection,
which by default is mapped to an ``401 Unauthorized`` response.
Longer-running authentication tasks (like looking up credentials in a database) should use the :ref:`-authenticateBasicAsync-`
variant of this directive which allows it to run without blocking routing layer of Akka HTTP, freeing it for other requests.
The ``authenticate*`` directives themselfs are not tied to any HTTP-specific
details so that various authentication schemes can be implemented on top of authenticate.
Standard HTTP-based authentication which uses the ``WWW-Authenticate`` header containing challenge data and
``Authorization`` header for receiving credentials is implemented in subclasses of ``HttpAuthenticator``.
.. warning::
Make sure to use basic authentication only over SSL because credentials are transferred in plaintext.
.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
:snippet: 0authenticateBasic
:snippet: authenticateBasic-0

View file

@ -1,23 +1,41 @@
.. _-authenticateBasicAsync-:
authenticateBasicAsync
=======================
======================
...
Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticator[T]``.
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#async-authenticator
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateBasicAsync
Description
-----------
...
This variant of the :ref:`authenticateBasic` directive returns a ``Future[Option[T]]`` which allows freeing up the routing
layer of Akka HTTP, freeing it for other requests. It should be used whenever an authentication is expected to take
a longer amount of time (e.g. looking up the user in a database).
In case the returned option is ``None`` the request is rejected with a :class:`AuthenticationFailedRejection`,
which by default is mapped to an ``401 Unauthorized`` response.
The ``authenticate*`` directives themselfs are not tied to any HTTP-specific
details so that various authentication schemes can be implemented on top of authenticate.
Standard HTTP-based authentication which uses the ``WWW-Authenticate`` header containing challenge data and
``Authorization`` header for receiving credentials is implemented in subclasses of ``HttpAuthenticator``.
.. warning::
Make sure to use basic authentication only over SSL because credentials are transferred in plaintext.
.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
:snippet: 0authenticateBasicAsync
:snippet: authenticateBasicAsync-0

View file

@ -3,18 +3,32 @@
authenticateBasicPF
===================
...
Wraps the inner route with Http Basic authentication support using a given ``AuthenticatorPF[T]``.
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authenticator-pf
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateBasicPF
Description
-----------
...
Provides support for handling `HTTP Basic Authentication`_.
Refer to :ref:`-authenticateBasic-` for a detailed description of this dictive.
It's semantics are equivalent to ``authenticateBasicPF`` 's, where not handling a case in the Partial Function (PF)
leaves the request to be rejected with a :class:`AuthenticationFailedRejection` rejection.
Longer-running authentication tasks (like looking up credentials in a database) should use :ref:`authenticateBasicAsync`
or :ref:`-authenticateBasicPFAsync-` if you prefer to use the ``PartialFunction`` syntax.
.. warning::
Make sure to use basic authentication only over SSL because credentials are transferred in plaintext.
.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth
Example
-------

View file

@ -1,20 +1,34 @@
.. _-authenticateBasicPFAsync-:
authenticateBasicPFAsync
=========================
========================
...
Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticatorPF[T]``.
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#async-authenticator-pf
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateBasicPFAsync
Description
-----------
...
Provides support for handling `HTTP Basic Authentication`_.
Refer to :ref:`-authenticateBasic-` for a detailed description of this dictive.
It's semantics are equivalent to ``authenticateBasicPF`` 's, where not handling a case in the Partial Function (PF)
leaves the request to be rejected with a :class:`AuthenticationFailedRejection` rejection.
Longer-running authentication tasks (like looking up credentials in a database) should use :ref:`authenticateBasicAsync`
or :ref:`-authenticateBasicPFAsync-` if you prefer to use the ``PartialFunction`` syntax.
.. warning::
Make sure to use basic authentication only over SSL because credentials are transferred in plaintext.
.. _HTTP Basic Authentication: https://en.wikipedia.org/wiki/Basic_auth
Example
-------

View file

@ -0,0 +1,25 @@
.. _-authenticateOAuth2-:
authenticateOAuth2
==================
...
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authenticator
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateOAuth2
Description
-----------
...
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
:snippet: authenticateOAuth2-0

View file

@ -0,0 +1,25 @@
.. _-authenticateOAuth2Async-:
authenticateOAuth2Async
=======================
...
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authenticator
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateOAuth2Async
Description
-----------
...
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
:snippet: authenticateOAuth2Async-0

View file

@ -0,0 +1,25 @@
.. _-authenticateOAuth2AsyncPF-:
authenticateOAuth2AsyncPF
=========================
...
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authenticator
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateOAuth2AsyncPF
Description
-----------
...
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
:snippet: authenticateOAuth2AsyncPF-0

View file

@ -0,0 +1,25 @@
.. _-authenticateOAuth2PF-:
authenticateOAuth2PF
====================
...
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authenticator
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateOAuth2PF
Description
-----------
...
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
:snippet: authenticateOAuth2PF-0

View file

@ -3,21 +3,29 @@
authenticateOrRejectWithChallenge
=================================
...
Lifts an authenticator function into a directive.
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authentication-result
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authenticateOrRejectWithChallenge
Description
-----------
...
This directive allows implementing the low level challange-response type of authentication that some services may require.
More details about challange-response authentication are available in the `RFC 2617`_, `RFC 7616`_ and `RFC 7617`_.
.. _RFC 2617: http://tools.ietf.org/html/rfc2617
.. _RFC 7616: http://tools.ietf.org/html/rfc7616
.. _RFC 7617: http://tools.ietf.org/html/rfc7617
Example
-------
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala
:snippet: 0authenticateOrRejectWithChallenge
:snippet: authenticateOrRejectWithChallenge-0

View file

@ -3,18 +3,33 @@
authorize
=========
...
Applies the given authorization check to the request.
Signature
---------
.. includecode:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala#authorize
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala
:snippet: authorize
Description
-----------
...
The user-defined authorization check can either be supplied as a ``=> Boolean`` value which is calculated
just from information out of the lexical scope, or as a function ``RequestContext => Boolean`` which can also
take information from the request itself into account.
If the check returns ``true`` the request is passed on to the inner route unchanged, otherwise an
``AuthorizationFailedRejection`` is created, triggering a ``403 Forbidden`` response by default
(the same as in the case of an ``AuthenticationFailedRejection``).
In a common use-case you would check if a user (e.g. supplied by any of the ``authenticate*`` family of directives,
e.g. :ref:`-authenticateBasic-`) is allowed to access the inner routes, e.g. by checking if the user has the needed permissions.
.. note::
See also :ref:`authentication-vs-authorization-scala` to understand the differences between those.
Example
-------

View file

@ -3,7 +3,7 @@
extractCredentials
==================
...
Extracts the potentially present ``HttpCredentials`` provided with the request's ``Authorization`` header.
Signature
---------
@ -14,7 +14,8 @@ Signature
Description
-----------
...
Extracts the potentially present ``HttpCredentials`` provided with the request's ``Authorization`` header,
which can be then used to implement some custom authentication or authorization logic.
Example
-------

View file

@ -15,14 +15,16 @@ SecurityDirectives
extractCredentials
.. _authentication-vs-authorization-scala:
Authentication vs. Authorization
--------------------------------
*Authentication* is the process of establishing a known identity for the user, whereby 'identity' is defined in the
**Authentication** is the process of establishing a known identity for the user, whereby 'identity' is defined in the
context of the application. This may be done with a username/password combination, a cookie, a pre-defined IP or some
other mechanism. After authentication the system believes that it knows who the user is.
*Authorization* is the process of determining, whether a given user is allowed access to a given resource or not. In
**Authorization** is the process of determining, whether a given user is allowed access to a given resource or not. In
most cases, in order to be able to authorize a user (i.e. allow access to some part of the system) the users identity
must already have been established, i.e. he/she must have been authenticated. Without prior authentication the
authorization would have to be very crude, e.g. "allow access for *all* users" or "allow access for *noone*". Only after