From 7f883a8b16e0af0af5aa6309c09b4d37bc26015a Mon Sep 17 00:00:00 2001 From: Konrad Malawski Date: Thu, 1 Oct 2015 13:25:41 +0200 Subject: [PATCH] +htp #18496 add missing directive documentation (scala) --- .../_sphinx/themes/akka/static/docs.css | 2 +- .../rst/java/http/routing-dsl/overview.rst | 2 +- .../scaladsl/HttpServerExampleSpec.scala.orig | 407 ++++++++++++++++++ .../BasicDirectivesExamplesSpec.scala | 132 ++++++ ...ileAndResourceDirectivesExamplesSpec.scala | 71 +++ .../HeaderDirectivesExamplesSpec.scala | 75 ++++ .../MethodDirectivesExamplesSpec.scala | 21 + .../PathDirectivesExamplesSpec.scala | 99 ++++- .../RespondWithDirectivesExamplesSpec.scala | 173 +++++--- .../SecurityDirectivesExamplesSpec.scala | 214 ++++++--- .../basic-directives/extractLog.rst | 7 +- .../extractRequestContext.rst | 6 +- .../basic-directives/extractSettings.rst | 7 +- .../basic-directives/withExecutionContext.rst | 8 +- .../directives/basic-directives/withLog.rst | 8 +- .../basic-directives/withMaterializer.rst | 8 +- .../basic-directives/withSettings.rst | 7 +- .../getFromBrowseableDirectory.rst | 8 + .../getFromDirectory.rst | 5 + .../getFromFile.rst | 7 +- .../getFromResource.rst | 9 +- .../getFromResourceDirectory.rst | 7 +- .../listDirectoryContents.rst | 7 +- .../header-directives/optionalHeaderValue.rst | 6 + .../optionalHeaderValueByName.rst | 6 + .../optionalHeaderValuePF.rst | 6 + .../method-directives/extractMethod.rst | 10 +- .../directives/misc-directives/validate.rst | 8 +- .../redirectToNoTrailingSlashIfPresent.rst | 21 +- .../redirectToTrailingSlashIfMissing.rst | 17 +- .../respondWithDefaultHeader.rst | 6 +- .../respondWithDefaultHeaders.rst | 13 + .../respondWithHeader.rst | 2 +- .../respondWithHeaders.rst | 2 +- .../security-directives/authenticateBasic.rst | 26 +- .../authenticateBasicAsync.rst | 26 +- .../authenticateBasicPF.rst | 18 +- .../authenticateBasicPFAsync.rst | 20 +- .../authenticateOAuth2.rst | 25 ++ .../authenticateOAuth2Async.rst | 25 ++ .../authenticateOAuth2AsyncPF.rst | 25 ++ .../authenticateOAuth2PF.rst | 25 ++ .../authenticateOrRejectWithChallenge.rst | 14 +- .../security-directives/authorize.rst | 19 +- .../extractCredentials.rst | 5 +- .../directives/security-directives/index.rst | 6 +- .../http/scaladsl/model/HttpMessageSpec.scala | 4 +- .../directives/PathDirectivesSpec.scala | 5 + .../directives/SecurityDirectives.scala | 18 + 49 files changed, 1453 insertions(+), 195 deletions(-) create mode 100644 akka-docs-dev/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala.orig create mode 100644 akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala create mode 100644 akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2.rst create mode 100644 akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2Async.rst create mode 100644 akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2AsyncPF.rst create mode 100644 akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2PF.rst diff --git a/akka-docs-dev/_sphinx/themes/akka/static/docs.css b/akka-docs-dev/_sphinx/themes/akka/static/docs.css index 0cd5619c44..d9afa16d5d 100644 --- a/akka-docs-dev/_sphinx/themes/akka/static/docs.css +++ b/akka-docs-dev/_sphinx/themes/akka/static/docs.css @@ -170,7 +170,7 @@ strong {color: #0B5567; } box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25); } -.pre { color: #0B5567; } +.pre { padding: 1px 2px; color: #008FA9; background-color: #EFF2F5; border: 1px solid #DDDEDF; font-family: Menlo, Monaco, "Courier New", monospace; font-size: 12px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } .footer h5 { text-transform: none; } diff --git a/akka-docs-dev/rst/java/http/routing-dsl/overview.rst b/akka-docs-dev/rst/java/http/routing-dsl/overview.rst index 590af57ae6..94f95b496e 100644 --- a/akka-docs-dev/rst/java/http/routing-dsl/overview.rst +++ b/akka-docs-dev/rst/java/http/routing-dsl/overview.rst @@ -75,7 +75,7 @@ Read more about :ref:`http-testkit-java`. .. _DRY: http://en.wikipedia.org/wiki/Don%27t_repeat_yourself -.. _handling-http-server-failures-high-level-scala: +.. _handling-http-server-failures-high-level-java: Handling HTTP Server failures in the High-Level API --------------------------------------------------- diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala.orig b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala.orig new file mode 100644 index 0000000000..cab7a27ca1 --- /dev/null +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala.orig @@ -0,0 +1,407 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +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`, + "Hello world!")) + + 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`, + "Hello world!")) + + 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 { + + Hello world! + + } + } ~ + 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 { +

Say hello to akka-http

+ } + } + } + + 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) + } + } + } +} diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala index 151a302168..dc10148857 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/BasicDirectivesExamplesSpec.scala @@ -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 } diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala new file mode 100644 index 0000000000..a6d740aedf --- /dev/null +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/FileAndResourceDirectivesExamplesSpec.scala @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009-2014 Typesafe Inc. + */ + +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 +} diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/HeaderDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/HeaderDirectivesExamplesSpec.scala index 0445ead7d3..fa23f85cb6 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/HeaderDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/HeaderDirectivesExamplesSpec.scala @@ -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 ⇒ diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/MethodDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/MethodDirectivesExamplesSpec.scala index 3c8a81afe2..ce5cb64db8 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/MethodDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/MethodDirectivesExamplesSpec.scala @@ -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!" + } + } } diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/PathDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/PathDirectivesExamplesSpec.scala index 79bb8bdc7b..ed107ed3a1 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/PathDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/PathDirectivesExamplesSpec.scala @@ -1,10 +1,11 @@ /* - * Copyright (C) 2009-2014 Typesafe Inc. + * Copyright (C) 2009-2015 Typesafe Inc. */ 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 " + + "this URI." + } + } + + // 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 " + + "this URI." + } + } + + // 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 + } + } } diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala index 868443ea36..56c1dba636 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala @@ -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" + // } + // } } -*/ \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala index 61b33fab0e..1f5e78ca6f 100644 --- a/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala +++ b/akka-docs-dev/rst/scala/code/docs/http/scaladsl/server/directives/SecurityDirectivesExamplesSpec.scala @@ -1,28 +1,117 @@ /* - * Copyright (C) 2009-2014 Typesafe Inc. + * Copyright (C) 2009-2015 Typesafe Inc. */ 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" + } + } } -*/ \ No newline at end of file + diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractLog.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractLog.rst index 8d2de13d0c..6ed95af9af 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractLog.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractLog.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractRequestContext.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractRequestContext.rst index eb543c17b4..360324d6e9 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractRequestContext.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractRequestContext.rst @@ -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 --------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractSettings.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractSettings.rst index a79869fd1f..50c72f67b6 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractSettings.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/extractSettings.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withExecutionContext.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withExecutionContext.rst index f2b5331f95..4837a7ce1e 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withExecutionContext.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withExecutionContext.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withLog.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withLog.rst index 52c4b67a37..654d28875e 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withLog.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withLog.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withMaterializer.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withMaterializer.rst index 01ee507201..1dac5a48a9 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withMaterializer.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withMaterializer.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withSettings.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withSettings.rst index a794774f46..b804edfb7e 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withSettings.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/basic-directives/withSettings.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectory.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectory.rst index 215b9b4fa6..33b86daf7a 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectory.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromBrowseableDirectory.rst @@ -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 \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromDirectory.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromDirectory.rst index 940ec4143d..c9dd753035 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromDirectory.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromDirectory.rst @@ -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 +------- + +... \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromFile.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromFile.rst index dc505b8ed3..9616eb00da 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromFile.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromFile.rst @@ -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. \ No newline at end of file +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +Example +------- + +... \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResource.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResource.rst index 745c045141..4c18e8bbe7 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResource.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResource.rst @@ -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. \ No newline at end of file +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +Example +------- + +... \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResourceDirectory.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResourceDirectory.rst index af77d3c030..4b699ca06d 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResourceDirectory.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/getFromResourceDirectory.rst @@ -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. \ No newline at end of file +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +Example +------- + +... \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/listDirectoryContents.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/listDirectoryContents.rst index 51fbbc2dcb..f4169c1a5d 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/listDirectoryContents.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/file-and-resource-directives/listDirectoryContents.rst @@ -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. \ No newline at end of file +Note that it's not required to wrap this directive with ``get`` as this directive will only respond to ``GET`` requests. + +Example +------- + +... \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValue.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValue.rst index 94e2c7bfec..0b52870506 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValue.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValue.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValueByName.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValueByName.rst index e029b127c6..fe894520e7 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValueByName.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValueByName.rst @@ -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 \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValuePF.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValuePF.rst index 302439c4af..c81985f72f 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValuePF.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/header-directives/optionalHeaderValuePF.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst index 7ad86f4cbb..506ea253b7 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/method-directives/extractMethod.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/misc-directives/validate.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/misc-directives/validate.rst index 755121f591..ad671bb9e1 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/misc-directives/validate.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/misc-directives/validate.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToNoTrailingSlashIfPresent.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToNoTrailingSlashIfPresent.rst index 2721983ee9..cbbab223f2 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToNoTrailingSlashIfPresent.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToNoTrailingSlashIfPresent.rst @@ -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. \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToTrailingSlashIfMissing.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToTrailingSlashIfMissing.rst index 9b6a8149bb..2cfb5a4d1b 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToTrailingSlashIfMissing.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/path-directives/redirectToTrailingSlashIfMissing.rst @@ -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. \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeader.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeader.rst index 0e7c44c59b..84e5ea9fca 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeader.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeader.rst @@ -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 \ No newline at end of file + :snippet: respondWithDefaultHeader-0 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeaders.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeaders.rst index a661dc1947..93934a72b7 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeaders.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithDefaultHeaders.rst @@ -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. \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeader.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeader.rst index 29d6ee3bf4..dbbf4d7d86 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeader.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeader.rst @@ -25,4 +25,4 @@ Example ------- .. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala - :snippet: respondWithHeader-examples \ No newline at end of file + :snippet: respondWithHeader-0 \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeaders.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeaders.rst index 9c47c5ae53..ce7e7268ad 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeaders.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/respond-with-directives/respondWithHeaders.rst @@ -25,4 +25,4 @@ Example ------- .. includecode2:: ../../../../code/docs/http/scaladsl/server/directives/RespondWithDirectivesExamplesSpec.scala - :snippet: respondWithHeaders-examples \ No newline at end of file + :snippet: respondWithHeaders-0 \ No newline at end of file diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasic.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasic.rst index efb80c035f..5aeaedf57b 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasic.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasic.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst index 4c8e1403e4..01c2663772 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicAsync.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst index fcf20efb71..5253fcf302 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPF.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst index cbb3900234..e4d5f0f5a3 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateBasicPFAsync.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2.rst new file mode 100644 index 0000000000..1fa1327d2b --- /dev/null +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2Async.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2Async.rst new file mode 100644 index 0000000000..4a50d3f180 --- /dev/null +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2Async.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2AsyncPF.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2AsyncPF.rst new file mode 100644 index 0000000000..60b25dac7a --- /dev/null +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2AsyncPF.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2PF.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2PF.rst new file mode 100644 index 0000000000..9d7f7001bb --- /dev/null +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOAuth2PF.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst index 8687317de1..417f27cf74 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authenticateOrRejectWithChallenge.rst @@ -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 diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authorize.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authorize.rst index b6b76222fb..0a34a7e46b 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authorize.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/authorize.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/extractCredentials.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/extractCredentials.rst index f5a269ce57..ecf687d456 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/extractCredentials.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/extractCredentials.rst @@ -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 ------- diff --git a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/index.rst b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/index.rst index 75ffef7869..1fc1f62049 100644 --- a/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/index.rst +++ b/akka-docs-dev/rst/scala/http/routing-dsl/directives/security-directives/index.rst @@ -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 diff --git a/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpMessageSpec.scala b/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpMessageSpec.scala index 17779f42f1..58b27e2a9d 100644 --- a/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpMessageSpec.scala +++ b/akka-http-core/src/test/scala/akka/http/scaladsl/model/HttpMessageSpec.scala @@ -6,10 +6,10 @@ import org.scalatest.{ Matchers, WordSpec } class HttpMessageSpec extends WordSpec with Matchers { def test(uri: String, hostHeader: Host, effectiveUri: String) = - HttpRequest.effectiveUri(Uri(uri), List(hostHeader), securedConnection = false, null) shouldEqual Uri(effectiveUri) + HttpRequest.effectiveUri(Uri(uri), List(hostHeader), securedConnection = false, null) shouldEqual Uri(effectiveUri) def fail(uri: String, hostHeader: Host) = - an [IllegalUriException] should be thrownBy + an[IllegalUriException] should be thrownBy HttpRequest.effectiveUri(Uri(uri), List(hostHeader), securedConnection = false, null) "HttpRequset" should { diff --git a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala index e12a694146..5b2dfe11ae 100644 --- a/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala +++ b/akka-http-tests/src/test/scala/akka/http/scaladsl/server/directives/PathDirectivesSpec.scala @@ -4,6 +4,7 @@ package akka.http.scaladsl.server.directives +import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server._ import org.scalatest.Inside @@ -323,6 +324,10 @@ class PathDirectivesSpec extends RoutingSpec with Inside { Get("/foo/bar") ~> redirectToTrailingSlashIfMissing(MovedPermanently) { completeOk } ~> check { status shouldEqual MovedPermanently } + + Get("/foo/bar/") ~> + redirectToTrailingSlashIfMissing(MovedPermanently) { completeOk } ~> + check { status shouldEqual StatusCodes.OK } } } diff --git a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala index 561522ebd5..be0fce5f3f 100644 --- a/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala +++ b/akka-http/src/main/scala/akka/http/scaladsl/server/directives/SecurityDirectives.scala @@ -16,6 +16,8 @@ import akka.http.scaladsl.server.AuthenticationFailedRejection.{ CredentialsReje /** * Provides directives for securing an inner route using the standard Http authentication headers [[`WWW-Authenticate`]] * and [[Authorization]]. Most prominently, HTTP Basic authentication as defined in RFC 2617. + * + * See: RFC 2617. */ trait SecurityDirectives { import BasicDirectives._ @@ -23,16 +25,26 @@ trait SecurityDirectives { import FutureDirectives._ import RouteDirectives._ + //#authentication-result /** * The result of an HTTP authentication attempt is either the user object or * an HttpChallenge to present to the browser. */ type AuthenticationResult[+T] = Either[HttpChallenge, T] + //#authentication-result + //#authenticator type Authenticator[T] = Credentials ⇒ Option[T] + //#authenticator + //#async-authenticator type AsyncAuthenticator[T] = Credentials ⇒ Future[Option[T]] + //#async-authenticator + //#authenticator-pf type AuthenticatorPF[T] = PartialFunction[Credentials, T] + //#authenticator-pf + //#async-authenticator-pf type AsyncAuthenticatorPF[T] = PartialFunction[Credentials, Future[T]] + //#async-authenticator-pf /** * Extracts the potentially present [[HttpCredentials]] provided with the request's [[Authorization]] header. @@ -184,6 +196,12 @@ sealed trait Credentials object Credentials { case object Missing extends Credentials abstract case class Provided(identifier: String) extends Credentials { + /** + * Safely compares the passed in `secret` with the received secret part of the Credentials. + * Use of this method instead of manual String equality testing is recommended in order to guard against timing attacks. + * + * See also [[EnhancedString#secure_==]], for more information. + */ def verify(secret: String): Boolean }