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"
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue