Merge pull request #18627 from ktoso/wip-directives-bonanza-ktoso
Akka HTTP Directives Documentation Bonanza
This commit is contained in:
commit
de9262ab8a
48 changed files with 1451 additions and 193 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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 ⇒
|
||||
|
|
|
|||
|
|
@ -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!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
// }
|
||||
// }
|
||||
}
|
||||
*/
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
---------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
-------
|
||||
|
||||
...
|
||||
|
|
@ -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
|
||||
-------
|
||||
|
||||
...
|
||||
|
|
@ -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
|
||||
-------
|
||||
|
||||
...
|
||||
|
|
@ -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
|
||||
-------
|
||||
|
||||
...
|
||||
|
|
@ -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
|
||||
-------
|
||||
|
||||
...
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -25,4 +25,4 @@ Example
|
|||
-------
|
||||
|
||||
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala
|
||||
:snippet: respondWithHeader-examples
|
||||
:snippet: respondWithHeader-0
|
||||
|
|
@ -25,4 +25,4 @@ Example
|
|||
-------
|
||||
|
||||
.. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala
|
||||
:snippet: respondWithHeaders-examples
|
||||
:snippet: respondWithHeaders-0
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue