Remove Akka-HTTP sources from akka/akka, moving to akka/akka-http! (#21690)

This commit is contained in:
Konrad Malawski 2016-10-18 15:17:17 +02:00 committed by GitHub
parent 09a6d2ede1
commit a6a5556a8f
1155 changed files with 20 additions and 96517 deletions

View file

@ -1,219 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import akka.Done
import akka.actor.{ ActorLogging, ActorSystem }
import akka.http.scaladsl.model.HttpEntity.Strict
import akka.http.scaladsl.model.HttpMessage.DiscardedEntity
import akka.stream.{ IOResult, Materializer }
import akka.stream.scaladsl.{ Framing, Sink }
import akka.util.ByteString
import docs.CompileOnlySpec
import org.scalatest.{ Matchers, WordSpec }
import scala.concurrent.{ ExecutionContextExecutor, Future }
class HttpClientExampleSpec extends WordSpec with Matchers with CompileOnlySpec {
"manual-entity-consume-example-1" in compileOnlySpec {
//#manual-entity-consume-example-1
import java.io.File
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Framing
import akka.stream.scaladsl.FileIO
import akka.http.scaladsl.model._
implicit val system = ActorSystem()
implicit val dispatcher = system.dispatcher
implicit val materializer = ActorMaterializer()
val response: HttpResponse = ???
response.entity.dataBytes
.via(Framing.delimiter(ByteString("\n"), maximumFrameLength = 256))
.map(transformEachLine)
.runWith(FileIO.toPath(new File("/tmp/example.out").toPath))
def transformEachLine(line: ByteString): ByteString = ???
//#manual-entity-consume-example-1
}
"manual-entity-consume-example-2" in compileOnlySpec {
//#manual-entity-consume-example-2
import java.io.File
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.model._
import scala.concurrent.duration._
implicit val system = ActorSystem()
implicit val dispatcher = system.dispatcher
implicit val materializer = ActorMaterializer()
case class ExamplePerson(name: String)
def parse(line: ByteString): ExamplePerson = ???
val response: HttpResponse = ???
// toStrict to enforce all data be loaded into memory from the connection
val strictEntity: Future[HttpEntity.Strict] = response.entity.toStrict(3.seconds)
// while API remains the same to consume dataBytes, now they're in memory already:
val transformedData: Future[ExamplePerson] =
strictEntity flatMap { e =>
e.dataBytes
.runFold(ByteString.empty) { case (acc, b) => acc ++ b }
.map(parse)
}
//#manual-entity-consume-example-2
}
"manual-entity-discard-example-1" in compileOnlySpec {
//#manual-entity-discard-example-1
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.model._
implicit val system = ActorSystem()
implicit val dispatcher = system.dispatcher
implicit val materializer = ActorMaterializer()
val response1: HttpResponse = ??? // obtained from an HTTP call (see examples below)
val discarded: DiscardedEntity = response1.discardEntityBytes()
discarded.future.onComplete { case done => println("Entity discarded completely!") }
//#manual-entity-discard-example-1
}
"manual-entity-discard-example-2" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.model._
implicit val system = ActorSystem()
implicit val dispatcher = system.dispatcher
implicit val materializer = ActorMaterializer()
//#manual-entity-discard-example-2
val response1: HttpResponse = ??? // obtained from an HTTP call (see examples below)
val discardingComplete: Future[Done] = response1.entity.dataBytes.runWith(Sink.ignore)
discardingComplete.onComplete { case done => println("Entity discarded completely!") }
//#manual-entity-discard-example-2
}
"outgoing-connection-example" in compileOnlySpec {
//#outgoing-connection-example
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import scala.concurrent.Future
import scala.util.{ Failure, Success }
object WebClient {
def main(args: Array[String]): Unit = {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
Http().outgoingConnection("akka.io")
val responseFuture: Future[HttpResponse] =
Source.single(HttpRequest(uri = "/"))
.via(connectionFlow)
.runWith(Sink.head)
responseFuture.andThen {
case Success(_) => println("request succeded")
case Failure(_) => println("request failed")
}.andThen {
case _ => system.terminate()
}
}
}
//#outgoing-connection-example
}
"host-level-example" in compileOnlySpec {
//#host-level-example
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import scala.concurrent.Future
import scala.util.Try
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// construct a pool client flow with context type `Int`
val poolClientFlow = Http().cachedHostConnectionPool[Int]("akka.io")
val responseFuture: Future[(Try[HttpResponse], Int)] =
Source.single(HttpRequest(uri = "/") -> 42)
.via(poolClientFlow)
.runWith(Sink.head)
//#host-level-example
}
"single-request-example" in compileOnlySpec {
//#single-request-example
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import scala.concurrent.Future
import scala.util.{ Failure, Success }
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val responseFuture: Future[HttpResponse] =
Http().singleRequest(HttpRequest(uri = "http://akka.io"))
//#single-request-example
}
"single-request-in-actor-example" in compileOnlySpec {
//#single-request-in-actor-example
import akka.actor.Actor
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.ActorMaterializerSettings
class Myself extends Actor
with ActorLogging {
import akka.pattern.pipe
import context.dispatcher
final implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(context.system))
val http = Http(context.system)
override def preStart() = {
http.singleRequest(HttpRequest(uri = "http://akka.io"))
.pipeTo(self)
}
def receive = {
case HttpResponse(StatusCodes.OK, headers, entity, _) =>
log.info("Got response, body: " + entity.dataBytes.runFold(ByteString(""))(_ ++ _))
case resp @ HttpResponse(code, _, _, _) =>
log.info("Request failed, response code: " + code)
resp.discardEntityBytes()
}
}
//#single-request-in-actor-example
}
}

View file

@ -1,682 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import akka.event.LoggingAdapter
import akka.http.scaladsl.model.{ RequestEntity, StatusCodes }
import akka.stream.scaladsl.Sink
import akka.testkit.TestActors
import docs.CompileOnlySpec
import org.scalatest.{ Matchers, WordSpec }
import scala.language.postfixOps
import scala.concurrent.{ ExecutionContext, Future }
class HttpServerExampleSpec extends WordSpec with Matchers
with CompileOnlySpec {
// never actually called
val log: LoggingAdapter = null
"binding-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = 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.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.concurrent.Future
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future onFailure in the end
implicit val executionContext = 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 = {
import akka.stream.scaladsl.Sink
Sink.ignore.mapMaterializedValue(_ => Future.failed(new Exception("")))
}
"binding-failure-handling" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.Http.ServerBinding
import akka.stream.ActorMaterializer
import scala.concurrent.Future
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future onFailure in the end
implicit val executionContext = 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 {
import akka.actor.ActorSystem
import akka.actor.ActorRef
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Flow
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = 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]
.watchTermination()((_, termination) => termination.onFailure {
case cause => failureMonitor ! cause
})
serverSource
.via(reactToTopLevelFailures)
.to(handleConnections) // Sink[Http.IncomingConnection, _]
.run()
}
"connection-stream-failure-handling" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Flow
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
implicit val executionContext = system.dispatcher
val (host, port) = ("localhost", 8080)
val serverSource = Http().bind(host, port)
val reactToConnectionFailure = Flow[HttpRequest]
.recover[HttpRequest] {
case ex =>
// handle the failure somehow
throw ex
}
val httpEcho = Flow[HttpRequest]
.via(reactToConnectionFailure)
.map { request =>
// simple streaming (!) "echo" response:
HttpResponse(entity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, request.entity.dataBytes))
}
serverSource
.runForeach { con =>
con.handleWith(httpEcho)
}
}
"full-server-example" in compileOnlySpec {
import akka.actor.ActorSystem
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()
implicit val executionContext = system.dispatcher
val serverSource = Http().bind(interface = "localhost", port = 8080)
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(
ContentTypes.`text/html(UTF-8)`,
"<html><body>Hello world!</body></html>"))
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
HttpResponse(entity = "PONG!")
case HttpRequest(GET, Uri.Path("/crash"), _, _, _) =>
sys.error("BOOM!")
case r: HttpRequest =>
r.discardEntityBytes() // important to drain incoming HTTP Entity stream
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.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.stream.ActorMaterializer
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future map/flatmap in the end
implicit val executionContext = system.dispatcher
val requestHandler: HttpRequest => HttpResponse = {
case HttpRequest(GET, Uri.Path("/"), _, _, _) =>
HttpResponse(entity = HttpEntity(
ContentTypes.`text/html(UTF-8)`,
"<html><body>Hello world!</body></html>"))
case HttpRequest(GET, Uri.Path("/ping"), _, _, _) =>
HttpResponse(entity = "PONG!")
case HttpRequest(GET, Uri.Path("/crash"), _, _, _) =>
sys.error("BOOM!")
case r: HttpRequest =>
r.discardEntityBytes() // important to drain incoming HTTP Entity stream
HttpResponse(404, entity = "Unknown resource!")
}
val bindingFuture = Http().bindAndHandleSync(requestHandler, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
}
// format: OFF
"high-level-server-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{ContentTypes, HttpEntity}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
get {
pathSingleSlash {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
} ~
path("ping") {
complete("PONG!")
} ~
path("crash") {
sys.error("BOOM!")
}
}
// `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow`
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
}
"minimal-routing-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem("my-system")
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
path("hello") {
get {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
}
"long-routing-example" in compileOnlySpec {
//#long-routing-example
import akka.actor.{ActorRef, ActorSystem}
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._
import akka.http.scaladsl.unmarshalling.FromRequestUnmarshaller
import akka.pattern.ask
import akka.stream.ActorMaterializer
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" / Remaining) { pathRest =>
redirect("http://oldapi.example.com/" + pathRest, MovedPermanently)
}
}
}
"stream random numbers" in compileOnlySpec {
//#stream-random-numbers
import akka.actor.ActorSystem
import akka.stream.scaladsl._
import akka.util.ByteString
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.{HttpEntity, ContentTypes}
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.util.Random
import scala.io.StdIn
object WebServer {
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
// streams are re-usable so we can define it here
// and use it for every request
val numbers = Source.fromIterator(() =>
Iterator.continually(Random.nextInt()))
val route =
path("random") {
get {
complete(
HttpEntity(
ContentTypes.`text/plain(UTF-8)`,
// transform each number to a chunk of bytes
numbers.map(n => ByteString(s"$n\n"))
)
)
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
//#stream-random-numbers
}
object Auction {
import akka.actor.Props
def props: Props = ???
}
"interact with an actor" in compileOnlySpec {
//#actor-interaction
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.pattern.ask
import akka.stream.ActorMaterializer
import akka.util.Timeout
import spray.json.DefaultJsonProtocol._
import scala.concurrent.duration._
import scala.io.StdIn
object WebServer {
case class Bid(userId: String, bid: Int)
case object GetBids
case class Bids(bids: List[Bid])
// these are from spray-json
implicit val bidFormat = jsonFormat2(Bid)
implicit val bidsFormat = jsonFormat1(Bids)
def main(args: Array[String]) {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val auction = system.actorOf(Auction.props, "auction")
val route =
path("auction") {
put {
parameter("bid".as[Int], "user") { (bid, user) =>
// place a bid, fire-and-forget
auction ! Bid(user, bid)
complete((StatusCodes.Accepted, "bid placed"))
}
}
get {
implicit val timeout: Timeout = 5.seconds
// query the actor for the current auction state
val bids: Future[Bids] = (auction ? GetBids).mapTo[Bids]
complete(bids)
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}
//#actor-interaction
}
"consume entity using entity directive" in compileOnlySpec {
//#consume-entity-directive
import akka.actor.ActorSystem
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.stream.ActorMaterializer
import spray.json.DefaultJsonProtocol._
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
final case class Bid(userId: String, bid: Int)
// these are from spray-json
implicit val bidFormat = jsonFormat2(Bid)
val route =
path("bid") {
put {
entity(as[Bid]) { bid =>
// incoming entity is fully consumed and converted into a Bid
complete("The bid was: " + bid)
}
}
}
//#consume-entity-directive
}
"consume entity using raw dataBytes to file" in compileOnlySpec {
//#consume-raw-dataBytes
import akka.actor.ActorSystem
import akka.stream.scaladsl.FileIO
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import java.io.File
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
(put & path("lines")) {
withoutSizeLimit {
extractDataBytes { bytes =>
val finishedWriting = bytes.runWith(FileIO.toPath(new File("/tmp/example.out").toPath))
// we only want to respond once the incoming data has been handled:
onComplete(finishedWriting) { ioResult =>
complete("Finished writing data: " + ioResult)
}
}
}
}
//#consume-raw-dataBytes
}
"drain entity using request#discardEntityBytes" in compileOnlySpec {
//#discard-discardEntityBytes
import akka.actor.ActorSystem
import akka.stream.scaladsl.FileIO
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.http.scaladsl.model.HttpRequest
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
(put & path("lines")) {
withoutSizeLimit {
extractRequest { r: HttpRequest =>
val finishedWriting = r.discardEntityBytes().future
// we only want to respond once the incoming data has been handled:
onComplete(finishedWriting) { done =>
complete("Drained all data from connection... (" + done + ")")
}
}
}
}
//#discard-discardEntityBytes
}
"discard entity manually" in compileOnlySpec {
//#discard-close-connections
import akka.actor.ActorSystem
import akka.stream.scaladsl.Sink
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.headers.Connection
import akka.stream.ActorMaterializer
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.dispatcher
val route =
(put & path("lines")) {
withoutSizeLimit {
extractDataBytes { data =>
// Closing connections, method 1 (eager):
// we deem this request as illegal, and close the connection right away:
data.runWith(Sink.cancelled) // "brutally" closes the connection
// Closing connections, method 2 (graceful):
// consider draining connection and replying with `Connection: Close` header
// if you want the client to close after this request/reply cycle instead:
respondWithHeader(Connection("close"))
complete(StatusCodes.Forbidden -> "Not allowed!")
}
}
}
//#discard-close-connections
}
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import akka.actor.{ ActorLogging, ActorSystem }
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.util.ByteString
import com.typesafe.sslconfig.akka.AkkaSSLConfig
import docs.CompileOnlySpec
import org.scalatest.{ Matchers, WordSpec }
class HttpsExamplesSpec extends WordSpec with Matchers with CompileOnlySpec {
"disable SNI for connection" in compileOnlySpec {
val unsafeHost = "example.com"
//#disable-sni-connection
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
// WARNING: disabling SNI is a very bad idea, please don't unless you have a very good reason to.
val badSslConfig = AkkaSSLConfig().mapSettings(s => s.withLoose(s.loose.withDisableSNI(true)))
val badCtx = Http().createClientHttpsContext(badSslConfig)
Http().outgoingConnectionHttps(unsafeHost, connectionContext = badCtx)
//#disable-sni-connection
}
}

View file

@ -1,38 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import akka.testkit.AkkaSpec
class MarshalSpec extends AkkaSpec {
"use marshal" in {
import scala.concurrent.Await
import scala.concurrent.duration._
import akka.http.scaladsl.marshalling.Marshal
import akka.http.scaladsl.model._
import system.dispatcher // ExecutionContext
val string = "Yeah"
val entityFuture = Marshal(string).to[MessageEntity]
val entity = Await.result(entityFuture, 1.second) // don't block in non-test code!
entity.contentType shouldEqual ContentTypes.`text/plain(UTF-8)`
val errorMsg = "Easy, pal!"
val responseFuture = Marshal(420 -> errorMsg).to[HttpResponse]
val response = Await.result(responseFuture, 1.second) // don't block in non-test code!
response.status shouldEqual StatusCodes.EnhanceYourCalm
response.entity.contentType shouldEqual ContentTypes.`text/plain(UTF-8)`
val request = HttpRequest(headers = List(headers.Accept(MediaTypes.`application/json`)))
val responseText = "Plaintext"
val respFuture = Marshal(responseText).toResponseFor(request) // with content negotiation!
a[Marshal.UnacceptableResponseContentTypeException] should be thrownBy {
Await.result(respFuture, 1.second) // client requested JSON, we only have text/plain!
}
}
}

View file

@ -1,91 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
//#import-model
import akka.http.scaladsl.model._
//#import-model
import akka.testkit.AkkaSpec
import akka.util.ByteString
import akka.http.scaladsl.model.headers.BasicHttpCredentials
class ModelSpec extends AkkaSpec {
"construct request" in {
//#construct-request
import HttpMethods._
// construct a simple GET request to `homeUri`
val homeUri = Uri("/abc")
HttpRequest(GET, uri = homeUri)
// construct simple GET request to "/index" (implicit string to Uri conversion)
HttpRequest(GET, uri = "/index")
// construct simple POST request containing entity
val data = ByteString("abc")
HttpRequest(POST, uri = "/receive", entity = data)
// customize every detail of HTTP request
import HttpProtocols._
import MediaTypes._
import HttpCharsets._
val userData = ByteString("abc")
val authorization = headers.Authorization(BasicHttpCredentials("user", "pass"))
HttpRequest(
PUT,
uri = "/user",
entity = HttpEntity(`text/plain` withCharset `UTF-8`, userData),
headers = List(authorization),
protocol = `HTTP/1.0`)
//#construct-request
}
"construct response" in {
//#construct-response
import StatusCodes._
// simple OK response without data created using the integer status code
HttpResponse(200)
// 404 response created using the named StatusCode constant
HttpResponse(NotFound)
// 404 response with a body explaining the error
HttpResponse(404, entity = "Unfortunately, the resource couldn't be found.")
// A redirecting response containing an extra header
val locationHeader = headers.Location("http://example.com/other")
HttpResponse(Found, headers = List(locationHeader))
//#construct-response
}
"deal with headers" in {
//#headers
import akka.http.scaladsl.model.headers._
// create a ``Location`` header
val loc = Location("http://example.com/other")
// create an ``Authorization`` header with HTTP Basic authentication data
val auth = Authorization(BasicHttpCredentials("joe", "josepp"))
// custom type
case class User(name: String, pass: String)
// a method that extracts basic HTTP credentials from a request
def credentialsOfRequest(req: HttpRequest): Option[User] =
for {
Authorization(BasicHttpCredentials(user, pass)) <- req.header[Authorization]
} yield User(user, pass)
//#headers
credentialsOfRequest(HttpRequest(headers = List(auth))) should be(Some(User("joe", "josepp")))
credentialsOfRequest(HttpRequest()) should be(None)
credentialsOfRequest(HttpRequest(headers = List(Authorization(GenericHttpCredentials("Other", Map.empty[String, String]))))) should be(None)
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright (C) 2009-2016 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Directives
import org.scalatest.{ Matchers, WordSpec }
class SprayJsonCompactMarshalSpec extends WordSpec with Matchers {
"spray-json example" in {
//#example
import spray.json._
// domain model
final case class CompactPrintedItem(name: String, id: Long)
trait CompactJsonFormatSupport extends DefaultJsonProtocol with SprayJsonSupport {
implicit val printer = CompactPrinter
implicit val compactPrintedItemFormat = jsonFormat2(CompactPrintedItem)
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives with CompactJsonFormatSupport {
// format: OFF
val route =
get {
pathSingleSlash {
complete {
// should complete with spray.json.JsValue = {"name":"akka","id":42}
CompactPrintedItem("akka", 42) // will render as JSON
}
}
}
// format: ON
//#
}
}
}

View file

@ -1,123 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import org.scalatest.{ Matchers, WordSpec }
class SprayJsonExampleSpec extends WordSpec with Matchers {
def compileOnlySpec(body: => Unit) = ()
"spray-json example" in compileOnlySpec {
//#minimal-spray-json-example
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import spray.json._
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// collect your json format instances into a support trait:
trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol {
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat1(Order) // contains List[Item]
}
// use it wherever json (un)marshalling is needed
class MyJsonService extends Directives with JsonSupport {
// format: OFF
val route =
get {
pathSingleSlash {
complete(Item("thing", 42)) // will render as JSON
}
} ~
post {
entity(as[Order]) { order => // will unmarshal JSON to Order
val itemsCount = order.items.size
val itemNames = order.items.map(_.name).mkString(", ")
complete(s"Ordered $itemsCount items: $itemNames")
}
}
// format: ON
//#
}
}
"second-spray-json-example" in compileOnlySpec {
//#second-spray-json-example
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.Done
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import spray.json.DefaultJsonProtocol._
import scala.io.StdIn
import scala.concurrent.Future
object WebServer {
// domain model
final case class Item(name: String, id: Long)
final case class Order(items: List[Item])
// formats for unmarshalling and marshalling
implicit val itemFormat = jsonFormat2(Item)
implicit val orderFormat = jsonFormat1(Order)
// (fake) async database query api
def fetchItem(itemId: Long): Future[Option[Item]] = ???
def saveOrder(order: Order): Future[Done] = ???
def main(args: Array[String]) {
// needed to run the route
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
// needed for the future map/flatmap in the end
implicit val executionContext = system.dispatcher
val route: Route =
get {
pathPrefix("item" / LongNumber) { id =>
// there might be no item for a given id
val maybeItem: Future[Option[Item]] = fetchItem(id)
onSuccess(maybeItem) {
case Some(item) => complete(item)
case None => complete(StatusCodes.NotFound)
}
}
} ~
post {
path("create-order") {
entity(as[Order]) { order =>
val saved: Future[Done] = saveOrder(order)
onComplete(saved) { done =>
complete("order created")
}
}
}
}
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ system.terminate()) // and shutdown when done
}
}
//#second-spray-json-example
}
}

View file

@ -1,29 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import akka.stream.{ Materializer, ActorMaterializer }
import akka.testkit.AkkaSpec
class UnmarshalSpec extends AkkaSpec {
"use unmarshal" in {
import akka.http.scaladsl.unmarshalling.Unmarshal
import system.dispatcher // ExecutionContext
implicit val materializer: Materializer = ActorMaterializer()
import scala.concurrent.Await
import scala.concurrent.duration._
val intFuture = Unmarshal("42").to[Int]
val int = Await.result(intFuture, 1.second) // don't block in non-test code!
int shouldEqual 42
val boolFuture = Unmarshal("off").to[Boolean]
val bool = Await.result(boolFuture, 1.second) // don't block in non-test code!
bool shouldBe false
}
}

View file

@ -1,248 +0,0 @@
/**
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl
import docs.CompileOnlySpec
import org.scalatest.{ Matchers, WordSpec }
class WebSocketClientExampleSpec extends WordSpec with Matchers with CompileOnlySpec {
"singleWebSocket-request-example" in compileOnlySpec {
//#single-WebSocket-request
import akka.actor.ActorSystem
import akka.{ Done, NotUsed }
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.ws._
import scala.concurrent.Future
object SingleWebSocketRequest {
def main(args: Array[String]) = {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
// print each incoming strict text message
val printSink: Sink[Message, Future[Done]] =
Sink.foreach {
case message: TextMessage.Strict =>
println(message.text)
}
val helloSource: Source[Message, NotUsed] =
Source.single(TextMessage("hello world!"))
// the Future[Done] is the materialized value of Sink.foreach
// and it is completed when the stream completes
val flow: Flow[Message, Message, Future[Done]] =
Flow.fromSinkAndSourceMat(printSink, helloSource)(Keep.left)
// upgradeResponse is a Future[WebSocketUpgradeResponse] that
// completes or fails when the connection succeeds or fails
// and closed is a Future[Done] representing the stream completion from above
val (upgradeResponse, closed) =
Http().singleWebSocketRequest(WebSocketRequest("ws://echo.websocket.org"), flow)
val connected = upgradeResponse.map { upgrade =>
// just like a regular http request we can access response status which is available via upgrade.response.status
// status code 101 (Switching Protocols) indicates that server support WebSockets
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Done
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}
// in a real application you would not side effect here
// and handle errors more carefully
connected.onComplete(println)
closed.foreach(_ => println("closed"))
}
}
//#single-WebSocket-request
}
"half-closed-WebSocket-closing-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.{ Done, NotUsed }
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model.ws._
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
//#half-closed-WebSocket-closing-example
// we may expect to be able to to just tail
// the server websocket output like this
val flow: Flow[Message, Message, NotUsed] =
Flow.fromSinkAndSource(
Sink.foreach(println),
Source.empty)
Http().singleWebSocketRequest(
WebSocketRequest("ws://example.com:8080/some/path"),
flow)
//#half-closed-WebSocket-closing-example
}
"half-closed-WebSocket-working-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model.ws._
import scala.concurrent.Promise
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
//#half-closed-WebSocket-working-example
// using Source.maybe materializes into a promise
// which will allow us to complete the source later
val flow: Flow[Message, Message, Promise[Option[Message]]] =
Flow.fromSinkAndSourceMat(
Sink.foreach[Message](println),
Source.maybe[Message])(Keep.right)
val (upgradeResponse, promise) =
Http().singleWebSocketRequest(
WebSocketRequest("ws://example.com:8080/some/path"),
flow)
// at some later time we want to disconnect
promise.success(None)
//#half-closed-WebSocket-working-example
}
"half-closed-WebSocket-finite-working-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.{ Done, NotUsed }
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model.ws._
import scala.concurrent.Promise
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
//#half-closed-WebSocket-finite-working-example
// using emit "one" and "two" and then keep the connection open
val flow: Flow[Message, Message, Promise[Option[Message]]] =
Flow.fromSinkAndSourceMat(
Sink.foreach[Message](println),
Source(List(TextMessage("one"), TextMessage("two")))
.concatMat(Source.maybe[Message])(Keep.right))(Keep.right)
val (upgradeResponse, promise) =
Http().singleWebSocketRequest(
WebSocketRequest("ws://example.com:8080/some/path"),
flow)
// at some later time we want to disconnect
promise.success(None)
//#half-closed-WebSocket-finite-working-example
}
"authorized-singleWebSocket-request-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.NotUsed
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model.headers.{ Authorization, BasicHttpCredentials }
import akka.http.scaladsl.model.ws._
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import collection.immutable.Seq
val flow: Flow[Message, Message, NotUsed] = ???
//#authorized-single-WebSocket-request
val (upgradeResponse, _) =
Http().singleWebSocketRequest(
WebSocketRequest(
"ws://example.com:8080/some/path",
extraHeaders = Seq(Authorization(
BasicHttpCredentials("johan", "correcthorsebatterystaple")))),
flow)
//#authorized-single-WebSocket-request
}
"WebSocketClient-flow-example" in compileOnlySpec {
//#WebSocket-client-flow
import akka.actor.ActorSystem
import akka.Done
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import akka.stream.scaladsl._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.ws._
import scala.concurrent.Future
object WebSocketClientFlow {
def main(args: Array[String]) = {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import system.dispatcher
// Future[Done] is the materialized value of Sink.foreach,
// emitted when the stream completes
val incoming: Sink[Message, Future[Done]] =
Sink.foreach[Message] {
case message: TextMessage.Strict =>
println(message.text)
}
// send this as a message over the WebSocket
val outgoing = Source.single(TextMessage("hello world!"))
// flow to use (note: not re-usable!)
val webSocketFlow = Http().webSocketClientFlow(WebSocketRequest("ws://echo.websocket.org"))
// the materialized value is a tuple with
// upgradeResponse is a Future[WebSocketUpgradeResponse] that
// completes or fails when the connection succeeds or fails
// and closed is a Future[Done] with the stream completion from the incoming sink
val (upgradeResponse, closed) =
outgoing
.viaMat(webSocketFlow)(Keep.right) // keep the materialized Future[WebSocketUpgradeResponse]
.toMat(incoming)(Keep.both) // also keep the Future[Done]
.run()
// just like a regular http request we can access response status which is available via upgrade.response.status
// status code 101 (Switching Protocols) indicates that server support WebSockets
val connected = upgradeResponse.flatMap { upgrade =>
if (upgrade.response.status == StatusCodes.SwitchingProtocols) {
Future.successful(Done)
} else {
throw new RuntimeException(s"Connection failed: ${upgrade.response.status}")
}
}
// in a real application you would not side effect here
connected.onComplete(println)
closed.foreach(_ => println("closed"))
}
}
//#WebSocket-client-flow
}
}

View file

@ -1,53 +0,0 @@
/**
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
import akka.actor.ActorSystem
import akka.http.scaladsl.server.{ Directives, Route }
import docs.CompileOnlySpec
import org.scalatest.WordSpec
import scala.concurrent.Future
class BlockingInHttpExamplesSpec extends WordSpec with CompileOnlySpec
with Directives {
compileOnlySpec {
val system: ActorSystem = ???
//#blocking-example-in-default-dispatcher
// BAD (due to blocking in Future, on default dispatcher)
implicit val defaultDispatcher = system.dispatcher
val routes: Route = post {
complete {
Future { // uses defaultDispatcher
Thread.sleep(5000) // will block on default dispatcher,
System.currentTimeMillis().toString // Starving the routing infrastructure
}
}
}
//#
}
compileOnlySpec {
val system: ActorSystem = ???
//#blocking-example-in-dedicated-dispatcher
// GOOD (the blocking is now isolated onto a dedicated dispatcher):
implicit val blockingDispatcher = system.dispatchers.lookup("my-blocking-dispatcher")
val routes: Route = post {
complete {
Future { // uses the good "blocking dispatcher" that we configured,
// instead of the default dispatcher- the blocking is isolated.
Thread.sleep(5000)
System.currentTimeMillis().toString
}
}
}
//#
}
}

View file

@ -1,84 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
/*
import org.scalatest.Inside
import akka.http.scaladsl.server._
class CaseClassExtractionExamplesSpec extends RoutingSpec with Inside {
// FIXME: investigate why it doesn't work without this import
import akka.http.scaladsl.server.directives.ParameterDirectives.ParamMagnet
// format: OFF
"example-1" in {
case class Color(red: Int, green: Int, blue: Int)
val route =
path("color") {
parameters('red.as[Int], 'green.as[Int], 'blue.as[Int]) { (red, green, blue) =>
val color = Color(red, green, blue)
// ... route working with the `color` instance
null // hide
}
}
Get("/color?red=1&green=2&blue=3") ~> route ~> check { responseAs[String] shouldEqual "Color(1,2,3)" } // hide
}
"example-2" in {
case class Color(red: Int, green: Int, blue: Int)
val route =
path("color") {
parameters('red.as[Int], 'green.as[Int], 'blue.as[Int]).as(Color) { color =>
// ... route working with the `color` instance
null // hide
}
}
Get("/color?red=1&green=2&blue=3") ~> route ~> check { responseAs[String] shouldEqual "Color(1,2,3)" } // hide
}
"example-3" in {
case class Color(name: String, red: Int, green: Int, blue: Int)
val route =
(path("color" / Segment) & parameters('r.as[Int], 'g.as[Int], 'b.as[Int]))
.as(Color) { color =>
// ... route working with the `color` instance
null // hide
}
Get("/color/abc?r=1&g=2&b=3") ~> route ~> check { responseAs[String] shouldEqual "Color(abc,1,2,3)" } // hide
}
//# example-4
case class Color(name: String, red: Int, green: Int, blue: Int) {
require(!name.isEmpty, "color name must not be empty")
require(0 <= red && red <= 255, "red color component must be between 0 and 255")
require(0 <= green && green <= 255, "green color component must be between 0 and 255")
require(0 <= blue && blue <= 255, "blue color component must be between 0 and 255")
}
//#
"example 4 test" in {
val route =
(path("color" / Segment) &
parameters('r.as[Int], 'g.as[Int], 'b.as[Int])).as(Color) { color =>
doSomethingWith(color) // route working with the Color instance
}
Get("/color/abc?r=1&g=2&b=3") ~> route ~> check {
responseAs[String] shouldEqual "Color(abc,1,2,3)"
}
Get("/color/abc?r=1&g=2&b=345") ~> route ~> check {
inside(rejection) {
case ValidationRejection("requirement failed: blue color component must be between 0 and 255", _) =>
}
}
}
def doSomethingWith(x: Any) = complete(x.toString)
}*/

View file

@ -1,107 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
import akka.http.scaladsl.server._
import akka.http.scaladsl.testkit.ScalatestRouteTest
class DirectiveExamplesSpec extends RoutingSpec {
// format: OFF
"example-1" in {
val route: Route =
path("order" / IntNumber) { id =>
get {
complete {
"Received GET request for order " + id
}
} ~
put {
complete {
"Received PUT request for order " + id
}
}
}
verify(route) // hide
}
"example-2" in {
def innerRoute(id: Int): Route =
get {
complete {
"Received GET request for order " + id
}
} ~
put {
complete {
"Received PUT request for order " + id
}
}
val route: Route = path("order" / IntNumber) { id => innerRoute(id) }
verify(route) // hide
}
"example-3" in {
val route =
path("order" / IntNumber) { id =>
(get | put) { ctx =>
ctx.complete(s"Received ${ctx.request.method.name} request for order $id")
}
}
verify(route) // hide
}
"example-4" in {
val route =
path("order" / IntNumber) { id =>
(get | put) {
extractMethod { m =>
complete(s"Received ${m.name} request for order $id")
}
}
}
verify(route) // hide
}
"example-5" in {
val getOrPut = get | put
val route =
path("order" / IntNumber) { id =>
getOrPut {
extractMethod { m =>
complete(s"Received ${m.name} request for order $id")
}
}
}
verify(route) // hide
}
"example-6" in {
val getOrPut = get | put
val route =
(path("order" / IntNumber) & getOrPut & extractMethod) { (id, m) =>
complete(s"Received ${m.name} request for order $id")
}
verify(route) // hide
}
"example-7" in {
val orderGetOrPutWithMethod =
path("order" / IntNumber) & (get | put) & extractMethod
val route =
orderGetOrPutWithMethod { (id, m) =>
complete(s"Received ${m.name} request for order $id")
}
verify(route) // hide
}
def verify(route: Route) = {
Get("/order/42") ~> route ~> check { responseAs[String] shouldEqual "Received GET request for order 42" }
Put("/order/42") ~> route ~> check { responseAs[String] shouldEqual "Received PUT request for order 42" }
Get("/") ~> route ~> check { handled shouldEqual false }
}
}

View file

@ -1,95 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
// format: OFF
object MyExplicitExceptionHandler {
//#explicit-handler-example
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._
val myExceptionHandler = ExceptionHandler {
case _: ArithmeticException =>
extractUri { uri =>
println(s"Request to $uri could not be handled normally")
complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
}
}
object MyApp extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route: Route =
handleExceptions(myExceptionHandler) {
// ... some route structure
null // hide
}
Http().bindAndHandle(route, "localhost", 8080)
}
//#
}
object MyImplicitExceptionHandler {
//#implicit-handler-example
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._
implicit def myExceptionHandler: ExceptionHandler =
ExceptionHandler {
case _: ArithmeticException =>
extractUri { uri =>
println(s"Request to $uri could not be handled normally")
complete(HttpResponse(InternalServerError, entity = "Bad numbers, bad result!!!"))
}
}
object MyApp extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route: Route =
// ... some route structure
null // hide
Http().bindAndHandle(route, "localhost", 8080)
}
//#
}
class ExceptionHandlerExamplesSpec extends RoutingSpec {
"test explicit example" in {
// tests:
Get() ~> handleExceptions(MyExplicitExceptionHandler.myExceptionHandler) {
_.complete((1 / 0).toString)
} ~> check {
responseAs[String] === "Bad numbers, bad result!!!"
}
}
"test implicit example" in {
import akka.http.scaladsl.server._
import MyImplicitExceptionHandler.myExceptionHandler
// tests:
Get() ~> Route.seal(ctx => ctx.complete((1 / 0).toString)) ~> check {
responseAs[String] === "Bad numbers, bad result!!!"
}
}
}

View file

@ -1,97 +0,0 @@
/**
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
import java.io.File
import akka.Done
import akka.actor.ActorRef
import akka.http.scaladsl.model.Multipart.FormData.BodyPart
import akka.stream.scaladsl.Framing
import akka.stream.scaladsl._
import akka.http.scaladsl.model.Multipart
import akka.util.ByteString
import scala.concurrent.duration._
import scala.concurrent.Future
class FileUploadExamplesSpec extends RoutingSpec {
case class Video(file: File, title: String, author: String)
object db {
def create(video: Video): Future[Unit] = Future.successful(Unit)
}
"simple-upload" in {
val uploadVideo =
path("video") {
entity(as[Multipart.FormData]) { formData =>
// collect all parts of the multipart as it arrives into a map
val allPartsF: Future[Map[String, Any]] = formData.parts.mapAsync[(String, Any)](1) {
case b: BodyPart if b.name == "file" =>
// stream into a file as the chunks of it arrives and return a future
// file to where it got stored
val file = File.createTempFile("upload", "tmp")
b.entity.dataBytes.runWith(FileIO.toPath(file.toPath)).map(_ =>
(b.name -> file))
case b: BodyPart =>
// collect form field values
b.toStrict(2.seconds).map(strict =>
(b.name -> strict.entity.data.utf8String))
}.runFold(Map.empty[String, Any])((map, tuple) => map + tuple)
val done = allPartsF.map { allParts =>
// You would have some better validation/unmarshalling here
db.create(Video(
file = allParts("file").asInstanceOf[File],
title = allParts("title").asInstanceOf[String],
author = allParts("author").asInstanceOf[String]))
}
// when processing have finished create a response for the user
onSuccess(allPartsF) { allParts =>
complete {
"ok!"
}
}
}
}
}
object MetadataActor {
case class Entry(id: Long, values: Seq[String])
}
val metadataActor: ActorRef = system.deadLetters
"stream-csv-upload" in {
val splitLines = Framing.delimiter(ByteString("\n"), 256)
val csvUploads =
path("metadata" / LongNumber) { id =>
entity(as[Multipart.FormData]) { formData =>
val done: Future[Done] = formData.parts.mapAsync(1) {
case b: BodyPart if b.filename.exists(_.endsWith(".csv")) =>
b.entity.dataBytes
.via(splitLines)
.map(_.utf8String.split(",").toVector)
.runForeach(csv =>
metadataActor ! MetadataActor.Entry(id, csv))
case _ => Future.successful(Done)
}.runWith(Sink.ignore)
// when processing have finished create a response for the user
onSuccess(done) { _ =>
complete {
"ok!"
}
}
}
}
}
}

View file

@ -1,62 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
// format: OFF
//# source-quote
import org.scalatest.{ Matchers, WordSpec }
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.testkit.ScalatestRouteTest
import akka.http.scaladsl.server._
import Directives._
class FullTestKitExampleSpec extends WordSpec with Matchers with ScalatestRouteTest {
val smallRoute =
get {
pathSingleSlash {
complete {
"Captain on the bridge!"
}
} ~
path("ping") {
complete("PONG!")
}
}
"The service" should {
"return a greeting for GET requests to the root path" in {
// tests:
Get() ~> smallRoute ~> check {
responseAs[String] shouldEqual "Captain on the bridge!"
}
}
"return a 'PONG!' response for GET requests to /ping" in {
// tests:
Get("/ping") ~> smallRoute ~> check {
responseAs[String] shouldEqual "PONG!"
}
}
"leave GET requests to other paths unhandled" in {
// tests:
Get("/kermit") ~> smallRoute ~> check {
handled shouldBe false
}
}
"return a MethodNotAllowed error for PUT requests to the root path" in {
// tests:
Put() ~> Route.seal(smallRoute) ~> check {
status === StatusCodes.MethodNotAllowed
responseAs[String] shouldEqual "HTTP method not allowed, supported methods: GET"
}
}
}
}
//#

View file

@ -1,83 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
//#imports
import java.io.InputStream
import java.security.{ SecureRandom, KeyStore }
import javax.net.ssl.{ SSLContext, TrustManagerFactory, KeyManagerFactory }
import akka.actor.ActorSystem
import akka.http.scaladsl.server.{ RouteResult, Route, Directives }
import akka.http.scaladsl.{ ConnectionContext, HttpsConnectionContext, Http }
import akka.stream.ActorMaterializer
import com.typesafe.sslconfig.akka.AkkaSSLConfig
//#
import docs.CompileOnlySpec
import org.scalatest.{ Matchers, WordSpec }
abstract class HttpsServerExampleSpec extends WordSpec with Matchers
with Directives with CompileOnlySpec {
class HowToObtainSSLConfig {
//#akka-ssl-config
implicit val system = ActorSystem()
val sslConfig = AkkaSSLConfig()
//#
}
"low level api" in compileOnlySpec {
//#low-level-default
implicit val system = ActorSystem()
implicit val mat = ActorMaterializer()
implicit val dispatcher = system.dispatcher
// Manual HTTPS configuration
val password: Array[Char] = ??? // do not store passwords in code, read them from somewhere safe!
val ks: KeyStore = KeyStore.getInstance("PKCS12")
val keystore: InputStream = getClass.getClassLoader.getResourceAsStream("server.p12")
require(keystore != null, "Keystore required!")
ks.load(keystore, password)
val keyManagerFactory: KeyManagerFactory = KeyManagerFactory.getInstance("SunX509")
keyManagerFactory.init(ks, password)
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance("SunX509")
tmf.init(ks)
val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, tmf.getTrustManagers, new SecureRandom)
val https: HttpsConnectionContext = ConnectionContext.https(sslContext)
//#
//#both-https-and-http
// you can run both HTTP and HTTPS in the same application as follows:
val commonRoutes: Route = get { complete("Hello world!") }
Http().bindAndHandle(commonRoutes, "127.0.0.1", 443, connectionContext = https)
Http().bindAndHandle(commonRoutes, "127.0.0.1", 80)
//#
//#bind-low-level-context
Http().bind("127.0.0.1", connectionContext = https)
// or using the high level routing DSL:
val routes: Route = get { complete("Hello world!") }
Http().bindAndHandle(routes, "127.0.0.1", 8080, connectionContext = https)
//#
//#set-low-level-context-default
// sets default context to HTTPS all Http() bound servers for this ActorSystem will use HTTPS from now on
Http().setDefaultServerHttpContext(https)
Http().bindAndHandle(routes, "127.0.0.1", 9090, connectionContext = https)
//#
system.terminate()
}
}

View file

@ -1,79 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
// format: OFF
object MyRejectionHandler {
//#custom-handler-example
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import StatusCodes._
import Directives._
implicit def myRejectionHandler =
RejectionHandler.newBuilder()
.handle { case MissingCookieRejection(cookieName) =>
complete(HttpResponse(BadRequest, entity = "No cookies, no service!!!"))
}
.handle { case AuthorizationFailedRejection =>
complete((Forbidden, "You're out of your depth!"))
}
.handle { case ValidationRejection(msg, _) =>
complete((InternalServerError, "That wasn't valid! " + msg))
}
.handleAll[MethodRejection] { methodRejections =>
val names = methodRejections.map(_.supported.name)
complete((MethodNotAllowed, s"Can't do that! Supported: ${names mkString " or "}!"))
}
.handleNotFound { complete((NotFound, "Not here!")) }
.result()
object MyApp extends App {
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
val route: Route =
// ... some route structure
null // hide
Http().bindAndHandle(route, "localhost", 8080)
}
//#
}
class RejectionHandlerExamplesSpec extends RoutingSpec {
import MyRejectionHandler._
"example-1" in {
import akka.http.scaladsl.coding.Gzip
val route =
path("order") {
get {
complete("Received GET")
} ~
post {
decodeRequestWith(Gzip) {
complete("Received compressed POST")
}
}
}
}
"test custom handler example" in {
import akka.http.scaladsl.server._
val route = Route.seal(reject(MissingCookieRejection("abc")))
// tests:
Get() ~> route ~> check {
responseAs[String] === "No cookies, no service!!!"
}
}
}

View file

@ -1,14 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
import akka.http.scaladsl.server.Directives
import akka.http.scaladsl.testkit.ScalatestRouteTest
import docs.CompileOnlySpec
import org.scalatest.{ Matchers, WordSpec }
abstract class RoutingSpec extends WordSpec with Matchers
with Directives with ScalatestRouteTest
with CompileOnlySpec

View file

@ -1,111 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server
import akka.http.scaladsl.model.ws.BinaryMessage
import akka.stream.scaladsl.Sink
import docs.CompileOnlySpec
import org.scalatest.{ Matchers, WordSpec }
class WebSocketExampleSpec extends WordSpec with Matchers with CompileOnlySpec {
"core-example" in compileOnlySpec {
//#websocket-example-using-core
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ Source, Flow }
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.UpgradeToWebSocket
import akka.http.scaladsl.model.ws.{ TextMessage, Message }
import akka.http.scaladsl.model.{ HttpResponse, Uri, HttpRequest }
import akka.http.scaladsl.model.HttpMethods._
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
//#websocket-handler
// The Greeter WebSocket Service expects a "name" per message and
// returns a greeting message for that name
val greeterWebSocketService =
Flow[Message]
.mapConcat {
// we match but don't actually consume the text message here,
// rather we simply stream it back as the tail of the response
// this means we might start sending the response even before the
// end of the incoming message has been received
case tm: TextMessage => TextMessage(Source.single("Hello ") ++ tm.textStream) :: Nil
case bm: BinaryMessage =>
// ignore binary messages but drain content to avoid the stream being clogged
bm.dataStream.runWith(Sink.ignore)
Nil
}
//#websocket-handler
//#websocket-request-handling
val requestHandler: HttpRequest => HttpResponse = {
case req @ HttpRequest(GET, Uri.Path("/greeter"), _, _, _) =>
req.header[UpgradeToWebSocket] match {
case Some(upgrade) => upgrade.handleMessages(greeterWebSocketService)
case None => HttpResponse(400, entity = "Not a valid websocket request!")
}
case r: HttpRequest =>
r.discardEntityBytes() // important to drain incoming HTTP Entity stream
HttpResponse(404, entity = "Unknown resource!")
}
//#websocket-request-handling
val bindingFuture =
Http().bindAndHandleSync(requestHandler, interface = "localhost", port = 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine()
import system.dispatcher // for the future transformations
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
"routing-example" in compileOnlySpec {
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ Source, Flow }
import akka.http.scaladsl.Http
import akka.http.scaladsl.model.ws.{ TextMessage, Message }
import akka.http.scaladsl.server.Directives
implicit val system = ActorSystem()
implicit val materializer = ActorMaterializer()
import Directives._
// The Greeter WebSocket Service expects a "name" per message and
// returns a greeting message for that name
val greeterWebSocketService =
Flow[Message]
.collect {
case tm: TextMessage => TextMessage(Source.single("Hello ") ++ tm.textStream)
// ignore binary messages
// TODO #20096 in case a Streamed message comes in, we should runWith(Sink.ignore) its data
}
//#websocket-routing
val route =
path("greeter") {
get {
handleWebSocketMessages(greeterWebSocketService)
}
}
//#websocket-routing
val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)
println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
Console.readLine()
import system.dispatcher // for the future transformations
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
}

View file

@ -1,878 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import java.nio.file.Paths
import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.{ Server, RawHeader }
import akka.http.scaladsl.server.RouteResult.{ Complete, Rejected }
import akka.http.scaladsl.server._
import akka.http.scaladsl.settings.RoutingSettings
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.{ FileIO, Sink, Source }
import akka.util.ByteString
import docs.http.scaladsl.server.RoutingSpec
import scala.concurrent.Future
import scala.util.control.NonFatal
class BasicDirectivesExamplesSpec extends RoutingSpec {
"0extract" in {
//#0extract
val uriLength = extract(_.request.uri.toString.length)
val route =
uriLength { len =>
complete(s"The length of the request URI is $len")
}
// tests:
Get("/abcdef") ~> route ~> check {
responseAs[String] shouldEqual "The length of the request URI is 25"
}
//#
}
"0extractLog" in {
//#0extractLog
val route =
extractLog { log =>
log.debug("I'm logging things in much detail..!")
complete("It's amazing!")
}
// tests:
Get("/abcdef") ~> route ~> check {
responseAs[String] shouldEqual "It's amazing!"
}
//#
}
"withMaterializer-0" in {
//#withMaterializer-0
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
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${materializer.##}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${special.##}!"
}
//#
}
"extractMaterializer-0" in {
//#extractMaterializer-0
val route =
path("sample") {
extractMaterializer { materializer =>
complete {
// explicitly use the `materializer`:
Source.single(s"Materialized by ${materializer.##}!")
.runWith(Sink.head)(materializer)
}
}
} // default materializer will be used
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Materialized by ${materializer.##}!"
}
//#
}
"withExecutionContext-0" in compileOnlySpec {
//#withExecutionContext-0
val special = system.dispatchers.lookup("special")
def sample() =
path("sample") {
extractExecutionContext { implicit executor =>
complete {
Future(s"Run on ${executor.##}!") // uses the `executor` ExecutionContext
}
}
}
val route =
pathPrefix("special") {
withExecutionContext(special) {
sample() // `special` execution context will be used
}
} ~ sample() // default execution context will be used
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${system.dispatcher.##}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${special.##}!"
}
//#
}
"extractExecutionContext-0" in compileOnlySpec {
//#extractExecutionContext-0
def sample() =
path("sample") {
extractExecutionContext { implicit executor =>
complete {
Future(s"Run on ${executor.##}!") // uses the `executor` ExecutionContext
}
}
}
val route =
pathPrefix("special") {
sample() // default execution context will be used
}
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Run on ${system.dispatcher.##}!"
}
//#
}
"0withLog" in {
//#0withLog
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
// tests:
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Logging using ${system.log}!"
}
Get("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"Logging using $special!"
}
//#
}
"withSettings-0" in compileOnlySpec {
//#withSettings-0
val special = RoutingSettings(system).withFileIODispatcher("special-io-dispatcher")
def sample() =
path("sample") {
complete {
// internally uses the configured fileIODispatcher:
val source = FileIO.fromPath(Paths.get("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
}
// tests:
Post("/special/sample") ~> route ~> check {
responseAs[String] shouldEqual s"{}"
}
Get("/sample") ~> route ~> check {
responseAs[String] shouldEqual "{}"
}
//#
}
"textract" in {
//#textract
val pathAndQuery = textract { ctx =>
val uri = ctx.request.uri
(uri.path, uri.query())
}
val route =
pathAndQuery { (p, query) =>
complete(s"The path is $p and the query is $query")
}
// tests:
Get("/abcdef?ghi=12") ~> route ~> check {
responseAs[String] shouldEqual "The path is /abcdef and the query is ghi=12"
}
//#
}
"tprovide" in {
//#tprovide
def provideStringAndLength(value: String) = tprovide((value, value.length))
val route =
provideStringAndLength("test") { (value, len) =>
complete(s"Value is $value and its length is $len")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Value is test and its length is 4"
}
//#
}
"0mapResponse" in {
//#0mapResponse
def overwriteResultStatus(response: HttpResponse): HttpResponse =
response.copy(status = StatusCodes.BadGateway)
val route = mapResponse(overwriteResultStatus)(complete("abc"))
// tests:
Get("/abcdef?ghi=12") ~> route ~> check {
status shouldEqual StatusCodes.BadGateway
}
//#
}
"1mapResponse-advanced-json" in {
//#1mapResponse-advanced
trait ApiRoutes {
protected def system: ActorSystem
private val log = Logging(system, "ApiRoutes")
private val NullJsonEntity = HttpEntity(ContentTypes.`application/json`, "{}")
private def nonSuccessToEmptyJsonEntity(response: HttpResponse): HttpResponse =
response.status match {
case code if code.isSuccess => response
case code =>
log.warning("Dropping response entity since response status code was: {}", code)
response.copy(entity = NullJsonEntity)
}
/** Wrapper for all of our JSON API routes */
def apiRoute(innerRoutes: => Route): Route =
mapResponse(nonSuccessToEmptyJsonEntity)(innerRoutes)
}
//#
import StatusCodes._
val __system = system
val routes = new ApiRoutes {
override protected def system = __system
}
import routes.apiRoute
//#1mapResponse-advanced
val route: Route =
apiRoute {
get {
complete(InternalServerError)
}
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "{}"
}
//#
}
"mapRouteResult" in {
//#mapRouteResult
// this directive is a joke, don't do that :-)
val makeEverythingOk = mapRouteResult {
case Complete(response) =>
// "Everything is OK!"
Complete(response.copy(status = 200))
case r => r
}
val route =
makeEverythingOk {
// will actually render as 200 OK (!)
complete(StatusCodes.Accepted)
}
// tests:
Get("/") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
//#
}
"mapRouteResultFuture" in {
//#mapRouteResultFuture
val tryRecoverAddServer = mapRouteResultFuture { fr =>
fr recover {
case ex: IllegalArgumentException =>
Complete(HttpResponse(StatusCodes.InternalServerError))
} map {
case Complete(res) => Complete(res.addHeader(Server("MyServer 1.0")))
case rest => rest
}
}
val route =
tryRecoverAddServer {
complete("Hello world!")
}
// tests:
Get("/") ~> route ~> check {
status shouldEqual StatusCodes.OK
header[Server] shouldEqual Some(Server("MyServer 1.0"))
}
//#
}
"mapResponseEntity" in {
//#mapResponseEntity
def prefixEntity(entity: ResponseEntity): ResponseEntity = entity match {
case HttpEntity.Strict(contentType, data) =>
HttpEntity.Strict(contentType, ByteString("test") ++ data)
case _ => throw new IllegalStateException("Unexpected entity type")
}
val prefixWithTest: Directive0 = mapResponseEntity(prefixEntity)
val route = prefixWithTest(complete("abc"))
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "testabc"
}
//#
}
"mapResponseHeaders" in {
//#mapResponseHeaders
// adds all request headers to the response
val echoRequestHeaders = extract(_.request.headers).flatMap(respondWithHeaders)
val removeIdHeader = mapResponseHeaders(_.filterNot(_.lowercaseName == "id"))
val route =
removeIdHeader {
echoRequestHeaders {
complete("test")
}
}
// tests:
Get("/") ~> RawHeader("id", "12345") ~> RawHeader("id2", "67890") ~> route ~> check {
header("id") shouldEqual None
header("id2").get.value shouldEqual "67890"
}
//#
}
"mapInnerRoute" in {
//#mapInnerRoute
val completeWithInnerException =
mapInnerRoute { route => ctx =>
try {
route(ctx)
} catch {
case NonFatal(e) => ctx.complete(s"Got ${e.getClass.getSimpleName} '${e.getMessage}'")
}
}
val route =
completeWithInnerException {
complete(throw new IllegalArgumentException("BLIP! BLOP! Everything broke"))
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Got IllegalArgumentException 'BLIP! BLOP! Everything broke'"
}
//#
}
"mapRejections" in {
//#mapRejections
// ignore any rejections and replace them by AuthorizationFailedRejection
val replaceByAuthorizationFailed = mapRejections(_ => List(AuthorizationFailedRejection))
val route =
replaceByAuthorizationFailed {
path("abc")(complete("abc"))
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
Get("/abc") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
//#
}
"recoverRejections" in {
//#recoverRejections
val authRejectionsToNothingToSeeHere = recoverRejections { rejections =>
if (rejections.exists(_.isInstanceOf[AuthenticationFailedRejection]))
Complete(HttpResponse(entity = "Nothing to see here, move along."))
else if (rejections == Nil) // see "Empty Rejections" for more details
Complete(HttpResponse(StatusCodes.NotFound, entity = "Literally nothing to see here."))
else
Rejected(rejections)
}
val neverAuth: Authenticator[String] = creds => None
val alwaysAuth: Authenticator[String] = creds => Some("id")
val route =
authRejectionsToNothingToSeeHere {
pathPrefix("auth") {
path("never") {
authenticateBasic("my-realm", neverAuth) { user =>
complete("Welcome to the bat-cave!")
}
} ~
path("always") {
authenticateBasic("my-realm", alwaysAuth) { user =>
complete("Welcome to the secret place!")
}
}
}
}
// tests:
Get("/auth/never") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Nothing to see here, move along."
}
Get("/auth/always") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Welcome to the secret place!"
}
Get("/auth/does_not_exist") ~> route ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Literally nothing to see here."
}
//#
}
"recoverRejectionsWith" in {
//#recoverRejectionsWith
val authRejectionsToNothingToSeeHere = recoverRejectionsWith { rejections =>
Future {
// imagine checking rejections takes a longer time:
if (rejections.exists(_.isInstanceOf[AuthenticationFailedRejection]))
Complete(HttpResponse(entity = "Nothing to see here, move along."))
else
Rejected(rejections)
}
}
val neverAuth: Authenticator[String] = creds => None
val route =
authRejectionsToNothingToSeeHere {
pathPrefix("auth") {
path("never") {
authenticateBasic("my-realm", neverAuth) { user =>
complete("Welcome to the bat-cave!")
}
}
}
}
// tests:
Get("/auth/never") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Nothing to see here, move along."
}
//#
}
"0mapRequest" in {
//#0mapRequest
def transformToPostRequest(req: HttpRequest): HttpRequest = req.copy(method = HttpMethods.POST)
val route =
mapRequest(transformToPostRequest) {
extractRequest { req =>
complete(s"The request method was ${req.method.name}")
}
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The request method was POST"
}
//#
}
"mapRequestContext" in {
//#mapRequestContext
val replaceRequest =
mapRequestContext(_.withRequest(HttpRequest(HttpMethods.POST)))
val route =
replaceRequest {
extractRequest { req =>
complete(req.method.value)
}
}
// tests:
Get("/abc/def/ghi") ~> route ~> check {
responseAs[String] shouldEqual "POST"
}
//#
}
"0mapRouteResult" in {
//#0mapRouteResult
val rejectAll = // not particularly useful directive
mapRouteResult {
case _ => Rejected(List(AuthorizationFailedRejection))
}
val route =
rejectAll {
complete("abc")
}
// tests:
Get("/") ~> route ~> check {
rejections.nonEmpty shouldEqual true
}
//#
}
"mapRouteResultPF" in {
//#mapRouteResultPF
case object MyCustomRejection extends Rejection
val rejectRejections = // not particularly useful directive
mapRouteResultPF {
case Rejected(_) => Rejected(List(AuthorizationFailedRejection))
}
val route =
rejectRejections {
reject(MyCustomRejection)
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
//#
}
"mapRouteResultWithPF-0" in {
//#mapRouteResultWithPF-0
case object MyCustomRejection extends Rejection
val rejectRejections = // not particularly useful directive
mapRouteResultWithPF {
case Rejected(_) => Future(Rejected(List(AuthorizationFailedRejection)))
}
val route =
rejectRejections {
reject(MyCustomRejection)
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
//#
}
"mapRouteResultWith-0" in {
//#mapRouteResultWith-0
case object MyCustomRejection extends Rejection
val rejectRejections = // not particularly useful directive
mapRouteResultWith {
case Rejected(_) => Future(Rejected(List(AuthorizationFailedRejection)))
case res => Future(res)
}
val route =
rejectRejections {
reject(MyCustomRejection)
}
// tests:
Get("/") ~> route ~> check {
rejection shouldEqual AuthorizationFailedRejection
}
//#
}
"pass" in {
//#pass
val route = pass(complete("abc"))
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "abc"
}
//#
}
"0provide" in {
//#0provide
def providePrefixedString(value: String): Directive1[String] = provide("prefix:" + value)
val route =
providePrefixedString("test") { value =>
complete(value)
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "prefix:test"
}
//#
}
"cancelRejections-filter-example" in {
//#cancelRejections-filter-example
def isMethodRejection: Rejection => Boolean = {
case MethodRejection(_) => true
case _ => false
}
val route =
cancelRejections(isMethodRejection) {
post {
complete("Result")
}
}
// tests:
Get("/") ~> route ~> check {
rejections shouldEqual Nil
handled shouldEqual false
}
//#
}
"cancelRejection-example" in {
//#cancelRejection-example
val route =
cancelRejection(MethodRejection(HttpMethods.POST)) {
post {
complete("Result")
}
}
// tests:
Get("/") ~> route ~> check {
rejections shouldEqual Nil
handled shouldEqual false
}
//#
}
"extractRequest-example" in {
//#extractRequest-example
val route =
extractRequest { request =>
complete(s"Request method is ${request.method.name} and content-type is ${request.entity.contentType}")
}
// tests:
Post("/", "text") ~> route ~> check {
responseAs[String] shouldEqual "Request method is POST and content-type is text/plain; charset=UTF-8"
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Request method is GET and content-type is none/none"
}
//#
}
"extractSettings-examples" in {
//#extractSettings-examples
val route =
extractSettings { settings: RoutingSettings =>
complete(s"RoutingSettings.renderVanityFooter = ${settings.renderVanityFooter}")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "RoutingSettings.renderVanityFooter = true"
}
//#
}
"mapSettings-examples" in {
//#mapSettings-examples
val tunedSettings = mapSettings { settings =>
settings.withFileGetConditional(false)
}
val route =
tunedSettings {
extractSettings { settings: RoutingSettings =>
complete(s"RoutingSettings.fileGetConditional = ${settings.fileGetConditional}")
}
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual s"RoutingSettings.fileGetConditional = false"
}
//#
}
"extractRequestContext-example" in {
//#extractRequestContext-example
val route =
extractRequestContext { ctx =>
ctx.log.debug("Using access to additional context availablethings, like the logger.")
val request = ctx.request
complete(s"Request method is ${request.method.name} and content-type is ${request.entity.contentType}")
}
// tests:
Post("/", "text") ~> route ~> check {
responseAs[String] shouldEqual "Request method is POST and content-type is text/plain; charset=UTF-8"
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "Request method is GET and content-type is none/none"
}
//#
}
"extractUri-example" in {
//#extractUri-example
val route =
extractUri { uri =>
complete(s"Full URI: $uri")
}
// tests:
Get("/") ~> route ~> check {
// tests are executed with the host assumed to be "example.com"
responseAs[String] shouldEqual "Full URI: http://example.com/"
}
Get("/test") ~> route ~> check {
responseAs[String] shouldEqual "Full URI: http://example.com/test"
}
//#
}
"mapUnmatchedPath-example" in {
//#mapUnmatchedPath-example
def ignore456(path: Uri.Path) = path match {
case s @ Uri.Path.Segment(head, tail) if head.startsWith("456") =>
val newHead = head.drop(3)
if (newHead.isEmpty) tail
else s.copy(head = head.drop(3))
case _ => path
}
val ignoring456 = mapUnmatchedPath(ignore456)
val route =
pathPrefix("123") {
ignoring456 {
path("abc") {
complete("Content")
}
}
}
// tests:
Get("/123/abc") ~> route ~> check {
responseAs[String] shouldEqual "Content"
}
Get("/123456/abc") ~> route ~> check {
responseAs[String] shouldEqual "Content"
}
//#
}
"extractUnmatchedPath-example" in {
//#extractUnmatchedPath-example
val route =
pathPrefix("abc") {
extractUnmatchedPath { remaining =>
complete(s"Unmatched: '$remaining'")
}
}
// tests:
Get("/abc") ~> route ~> check {
responseAs[String] shouldEqual "Unmatched: ''"
}
Get("/abc/456") ~> route ~> check {
responseAs[String] shouldEqual "Unmatched: '/456'"
}
//#
}
"extractRequestEntity-example" in {
//#extractRequestEntity-example
val route =
extractRequestEntity { entity =>
complete(s"Request entity content-type is ${entity.contentType}")
}
// tests:
val httpEntity = HttpEntity(ContentTypes.`text/plain(UTF-8)`, "req")
Post("/abc", httpEntity) ~> route ~> check {
responseAs[String] shouldEqual "Request entity content-type is text/plain; charset=UTF-8"
}
//#
}
"extractDataBytes-example" in {
//#extractDataBytes-example
val route =
extractDataBytes { data
val sum = data.runFold(0) { (acc, i) acc + i.utf8String.toInt }
onSuccess(sum) { s
complete(HttpResponse(entity = HttpEntity(s.toString)))
}
}
// tests:
val dataBytes = Source.fromIterator(() Iterator.range(1, 10).map(x ByteString(x.toString)))
Post("/abc", HttpEntity(ContentTypes.`text/plain(UTF-8)`, data = dataBytes)) ~> route ~> check {
responseAs[String] shouldEqual "45"
}
//#
}
"extractStrictEntity-example" in {
//#extractStrictEntity-example
import scala.concurrent.duration._
val route = extractStrictEntity(3.seconds) { entity =>
complete(entity.data.utf8String)
}
// tests:
val dataBytes = Source.fromIterator(() Iterator.range(1, 10).map(x ByteString(x.toString)))
Post("/", HttpEntity(ContentTypes.`text/plain(UTF-8)`, data = dataBytes)) ~> route ~> check {
responseAs[String] shouldEqual "123456789"
}
//#
}
"toStrictEntity-example" in {
//#toStrictEntity-example
import scala.concurrent.duration._
val route = toStrictEntity(3.seconds) {
extractRequest { req =>
req.entity match {
case strict: HttpEntity.Strict =>
complete(s"Request entity is strict, data=${strict.data.utf8String}")
case _ =>
complete("Ooops, request entity is not strict!")
}
}
}
// tests:
val dataBytes = Source.fromIterator(() Iterator.range(1, 10).map(x ByteString(x.toString)))
Post("/", HttpEntity(ContentTypes.`text/plain(UTF-8)`, data = dataBytes)) ~> route ~> check {
responseAs[String] shouldEqual "Request entity is strict, data=123456789"
}
//#
}
"extractActorSystem-example" in {
//#extractActorSystem-example
val route = extractActorSystem { actorSystem =>
complete(s"Actor System extracted, hash=${actorSystem.hashCode()}")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual s"Actor System extracted, hash=${system.hashCode()}"
}
//#
}
}

View file

@ -1,126 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.coding._
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.{ HttpResponse, StatusCodes }
import akka.http.scaladsl.model.headers.{ HttpEncodings, HttpEncoding, `Accept-Encoding`, `Content-Encoding` }
import akka.http.scaladsl.model.headers.HttpEncodings._
import akka.http.scaladsl.server._
import akka.util.ByteString
import org.scalatest.matchers.Matcher
class CodingDirectivesExamplesSpec extends RoutingSpec {
"responseEncodingAccepted" in {
val route = responseEncodingAccepted(gzip) { complete("content") }
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "content"
}
Get("/") ~> `Accept-Encoding`(deflate) ~> route ~> check {
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
}
}
"encodeResponse" in {
val route = encodeResponse { complete("content") }
// tests:
Get("/") ~> route ~> check {
response should haveContentEncoding(identity)
}
Get("/") ~> `Accept-Encoding`(gzip, deflate) ~> route ~> check {
response should haveContentEncoding(gzip)
}
Get("/") ~> `Accept-Encoding`(deflate) ~> route ~> check {
response should haveContentEncoding(deflate)
}
Get("/") ~> `Accept-Encoding`(identity) ~> route ~> check {
response should haveContentEncoding(identity)
}
}
"encodeResponseWith" in {
val route = encodeResponseWith(Gzip) { complete("content") }
// tests:
Get("/") ~> route ~> check {
response should haveContentEncoding(gzip)
}
Get("/") ~> `Accept-Encoding`(gzip, deflate) ~> route ~> check {
response should haveContentEncoding(gzip)
}
Get("/") ~> `Accept-Encoding`(deflate) ~> route ~> check {
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
}
Get("/") ~> `Accept-Encoding`(identity) ~> route ~> check {
rejection shouldEqual UnacceptedResponseEncodingRejection(gzip)
}
}
val helloGzipped = compress("Hello", Gzip)
val helloDeflated = compress("Hello", Deflate)
"decodeRequest" in {
val route =
decodeRequest {
entity(as[String]) { content: String =>
complete(s"Request content: '$content'")
}
}
// tests:
Post("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", helloDeflated) ~> `Content-Encoding`(deflate) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", "hello uncompressed") ~> `Content-Encoding`(identity) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'hello uncompressed'"
}
}
"decodeRequestWith-0" in {
val route =
decodeRequestWith(Gzip) {
entity(as[String]) { content: String =>
complete(s"Request content: '$content'")
}
}
// tests:
Post("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", helloDeflated) ~> `Content-Encoding`(deflate) ~> route ~> check {
rejection shouldEqual UnsupportedRequestEncodingRejection(gzip)
}
Post("/", "hello") ~> `Content-Encoding`(identity) ~> route ~> check {
rejection shouldEqual UnsupportedRequestEncodingRejection(gzip)
}
}
"decodeRequestWith-1" in {
val route =
decodeRequestWith(Gzip, NoCoding) {
entity(as[String]) { content: String =>
complete(s"Request content: '$content'")
}
}
// tests:
Post("/", helloGzipped) ~> `Content-Encoding`(gzip) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'Hello'"
}
Post("/", helloDeflated) ~> `Content-Encoding`(deflate) ~> route ~> check {
rejections shouldEqual List(UnsupportedRequestEncodingRejection(gzip), UnsupportedRequestEncodingRejection(identity))
}
Post("/", "hello uncompressed") ~> `Content-Encoding`(identity) ~> route ~> check {
responseAs[String] shouldEqual "Request content: 'hello uncompressed'"
}
}
def haveContentEncoding(encoding: HttpEncoding): Matcher[HttpResponse] =
be(encoding) compose { (_: HttpResponse).header[`Content-Encoding`].map(_.encodings.head).getOrElse(HttpEncodings.identity) }
def compress(input: String, encoder: Encoder): ByteString = encoder.encode(ByteString(input))
}

View file

@ -1,70 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.server._
import akka.http.scaladsl.model.headers.{ HttpCookie, Cookie, `Set-Cookie` }
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.DateTime
class CookieDirectivesExamplesSpec extends RoutingSpec {
"cookie" in {
val route =
cookie("userName") { nameCookie =>
complete(s"The logged in user is '${nameCookie.value}'")
}
// tests:
Get("/") ~> Cookie("userName" -> "paul") ~> route ~> check {
responseAs[String] shouldEqual "The logged in user is 'paul'"
}
// missing cookie
Get("/") ~> route ~> check {
rejection shouldEqual MissingCookieRejection("userName")
}
Get("/") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "Request is missing required cookie 'userName'"
}
}
"optionalCookie" in {
val route =
optionalCookie("userName") {
case Some(nameCookie) => complete(s"The logged in user is '${nameCookie.value}'")
case None => complete("No user logged in")
}
// tests:
Get("/") ~> Cookie("userName" -> "paul") ~> route ~> check {
responseAs[String] shouldEqual "The logged in user is 'paul'"
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "No user logged in"
}
}
"deleteCookie" in {
val route =
deleteCookie("userName") {
complete("The user was logged out")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The user was logged out"
header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", value = "deleted", expires = Some(DateTime.MinValue))))
}
}
"setCookie" in {
val route =
setCookie(HttpCookie("userName", value = "paul")) {
complete("The user was logged in")
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "The user was logged in"
header[`Set-Cookie`] shouldEqual Some(`Set-Cookie`(HttpCookie("userName", value = "paul")))
}
}
}

View file

@ -1,72 +0,0 @@
/**
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.server.{ Directive1, Directive }
import docs.http.scaladsl.server.RoutingSpec
class CustomDirectivesExamplesSpec extends RoutingSpec {
"labeling" in {
val getOrPut = get | put
// tests:
val route = getOrPut { complete("ok") }
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "ok"
}
Put("/") ~> route ~> check {
responseAs[String] shouldEqual "ok"
}
}
"map-0" in {
val textParam: Directive1[String] =
parameter("text".as[String])
val lengthDirective: Directive1[Int] =
textParam.map(text => text.length)
// tests:
Get("/?text=abcdefg") ~> lengthDirective(x => complete(x.toString)) ~> check {
responseAs[String] === "7"
}
}
"tmap-1" in {
val twoIntParameters: Directive[(Int, Int)] =
parameters(("a".as[Int], "b".as[Int]))
val myDirective: Directive1[String] =
twoIntParameters.tmap {
case (a, b) => (a + b).toString
}
// tests:
Get("/?a=2&b=5") ~> myDirective(x => complete(x)) ~> check {
responseAs[String] === "7"
}
}
"flatMap-0" in {
val intParameter: Directive1[Int] = parameter("a".as[Int])
val myDirective: Directive1[Int] =
intParameter.flatMap {
case a if a > 0 => provide(2 * a)
case _ => reject
}
// tests:
Get("/?a=21") ~> myDirective(i => complete(i.toString)) ~> check {
responseAs[String] === "42"
}
Get("/?a=-18") ~> myDirective(i => complete(i.toString)) ~> check {
handled === false
}
}
}

View file

@ -1,56 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.{ Http, TestUtils }
import akka.http.scaladsl.client.RequestBuilding
import akka.http.scaladsl.model.HttpProtocols._
import akka.http.scaladsl.model.RequestEntityAcceptance.Expected
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives
import akka.stream.ActorMaterializer
import akka.testkit.AkkaSpec
import akka.util.ByteString
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.duration._
class CustomHttpMethodSpec extends AkkaSpec with ScalaFutures
with Directives with RequestBuilding {
implicit val mat = ActorMaterializer()
"Http" should {
"allow registering custom method" in {
import system.dispatcher
val (_, host, port) = TestUtils.temporaryServerHostnameAndPort()
//#application-custom
import akka.http.scaladsl.settings.{ ParserSettings, ServerSettings }
// define custom media type:
val BOLT = HttpMethod.custom("BOLT", safe = false,
idempotent = true, requestEntityAcceptance = Expected)
// add custom method to parser settings:
val parserSettings = ParserSettings(system).withCustomMethods(BOLT)
val serverSettings = ServerSettings(system).withParserSettings(parserSettings)
val routes = extractMethod { method
complete(s"This is a ${method.name} method request.")
}
val binding = Http().bindAndHandle(routes, host, port, settings = serverSettings)
//#application-custom
val request = HttpRequest(BOLT, s"http://$host:$port/", protocol = `HTTP/1.0`)
val response = Http().singleRequest(request).futureValue
response.status should ===(StatusCodes.OK)
val responseBody = response.toStrict(1.second).futureValue.entity.dataBytes.runFold(ByteString.empty)(_ ++ _).futureValue.utf8String
responseBody should ===("This is a BOLT method request.")
}
}
}

View file

@ -1,128 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.event.{ LoggingAdapter, Logging }
import akka.event.Logging.LogLevel
import akka.http.scaladsl.model.{ HttpRequest, HttpResponse }
import akka.http.scaladsl.server.RouteResult
import akka.http.scaladsl.server.RouteResult.{ Rejected, Complete }
import akka.http.scaladsl.server.directives.{ DebuggingDirectives, LogEntry, LoggingMagnet }
import docs.http.scaladsl.server.RoutingSpec
class DebuggingDirectivesExamplesSpec extends RoutingSpec {
"logRequest-0" in {
// different possibilities of using logRequest
// The first alternatives use an implicitly available LoggingContext for logging
// marks with "get-user", log with debug level, HttpRequest.toString
DebuggingDirectives.logRequest("get-user")
// marks with "get-user", log with info level, HttpRequest.toString
DebuggingDirectives.logRequest(("get-user", Logging.InfoLevel))
// logs just the request method at debug level
def requestMethod(req: HttpRequest): String = req.method.name
DebuggingDirectives.logRequest(requestMethod _)
// logs just the request method at info level
def requestMethodAsInfo(req: HttpRequest): LogEntry = LogEntry(req.method.name, Logging.InfoLevel)
DebuggingDirectives.logRequest(requestMethodAsInfo _)
// This one doesn't use the implicit LoggingContext but uses `println` for logging
def printRequestMethod(req: HttpRequest): Unit = println(req.method.name)
val logRequestPrintln = DebuggingDirectives.logRequest(LoggingMagnet(_ => printRequestMethod))
// tests:
Get("/") ~> logRequestPrintln(complete("logged")) ~> check {
responseAs[String] shouldEqual "logged"
}
}
"logRequestResult" in {
// different possibilities of using logRequestResponse
// The first alternatives use an implicitly available LoggingContext for logging
// marks with "get-user", log with debug level, HttpRequest.toString, HttpResponse.toString
DebuggingDirectives.logRequestResult("get-user")
// marks with "get-user", log with info level, HttpRequest.toString, HttpResponse.toString
DebuggingDirectives.logRequestResult(("get-user", Logging.InfoLevel))
// logs just the request method and response status at info level
def requestMethodAndResponseStatusAsInfo(req: HttpRequest): RouteResult => Option[LogEntry] = {
case RouteResult.Complete(res) => Some(LogEntry(req.method.name + ": " + res.status, Logging.InfoLevel))
case _ => None // no log entries for rejections
}
DebuggingDirectives.logRequestResult(requestMethodAndResponseStatusAsInfo _)
// This one doesn't use the implicit LoggingContext but uses `println` for logging
def printRequestMethodAndResponseStatus(req: HttpRequest)(res: RouteResult): Unit =
println(requestMethodAndResponseStatusAsInfo(req)(res).map(_.obj.toString).getOrElse(""))
val logRequestResultPrintln = DebuggingDirectives.logRequestResult(LoggingMagnet(_ => printRequestMethodAndResponseStatus))
// tests:
Get("/") ~> logRequestResultPrintln(complete("logged")) ~> check {
responseAs[String] shouldEqual "logged"
}
}
"logResult" in {
// different possibilities of using logResponse
// The first alternatives use an implicitly available LoggingContext for logging
// marks with "get-user", log with debug level, HttpResponse.toString
DebuggingDirectives.logResult("get-user")
// marks with "get-user", log with info level, HttpResponse.toString
DebuggingDirectives.logResult(("get-user", Logging.InfoLevel))
// logs just the response status at debug level
def responseStatus(res: RouteResult): String = res match {
case RouteResult.Complete(x) => x.status.toString
case RouteResult.Rejected(rejections) => "Rejected: " + rejections.mkString(", ")
}
DebuggingDirectives.logResult(responseStatus _)
// logs just the response status at info level
def responseStatusAsInfo(res: RouteResult): LogEntry = LogEntry(responseStatus(res), Logging.InfoLevel)
DebuggingDirectives.logResult(responseStatusAsInfo _)
// This one doesn't use the implicit LoggingContext but uses `println` for logging
def printResponseStatus(res: RouteResult): Unit = println(responseStatus(res))
val logResultPrintln = DebuggingDirectives.logResult(LoggingMagnet(_ => printResponseStatus))
// tests:
Get("/") ~> logResultPrintln(complete("logged")) ~> check {
responseAs[String] shouldEqual "logged"
}
}
"logRequestResultWithResponseTime" in {
def akkaResponseTimeLoggingFunction(
loggingAdapter: LoggingAdapter,
requestTimestamp: Long,
level: LogLevel = Logging.InfoLevel)(req: HttpRequest)(res: Any): Unit = {
val entry = res match {
case Complete(resp) =>
val responseTimestamp: Long = System.nanoTime
val elapsedTime: Long = (responseTimestamp - requestTimestamp) / 1000000
val loggingString = s"""Logged Request:${req.method}:${req.uri}:${resp.status}:${elapsedTime}"""
LogEntry(loggingString, level)
case Rejected(reason) =>
LogEntry(s"Rejected Reason: ${reason.mkString(",")}", level)
}
entry.logTo(loggingAdapter)
}
def printResponseTime(log: LoggingAdapter) = {
val requestTimestamp = System.nanoTime
akkaResponseTimeLoggingFunction(log, requestTimestamp)(_)
}
val logResponseTime = DebuggingDirectives.logRequestResult(LoggingMagnet(printResponseTime(_)))
Get("/") ~> logResponseTime(complete("logged")) ~> check {
responseAs[String] shouldEqual "logged"
}
}
}

View file

@ -1,62 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server._
import docs.http.scaladsl.server.RoutingSpec
class ExecutionDirectivesExamplesSpec extends RoutingSpec {
"handleExceptions" in {
val divByZeroHandler = ExceptionHandler {
case _: ArithmeticException => complete((StatusCodes.BadRequest, "You've got your arithmetic wrong, fool!"))
}
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
handleExceptions(divByZeroHandler) {
complete(s"The result is ${a / b}")
}
}
// tests:
Get("/divide/10/5") ~> route ~> check {
responseAs[String] shouldEqual "The result is 2"
}
Get("/divide/10/0") ~> route ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "You've got your arithmetic wrong, fool!"
}
}
"handleRejections" in {
val totallyMissingHandler = RejectionHandler.newBuilder()
.handleNotFound { complete((StatusCodes.NotFound, "Oh man, what you are looking for is long gone.")) }
.handle { case ValidationRejection(msg, _) => complete((StatusCodes.InternalServerError, msg)) }
.result()
val route =
pathPrefix("handled") {
handleRejections(totallyMissingHandler) {
path("existing")(complete("This path exists")) ~
path("boom")(reject(new ValidationRejection("This didn't work.")))
}
}
// tests:
Get("/handled/existing") ~> route ~> check {
responseAs[String] shouldEqual "This path exists"
}
Get("/missing") ~> Route.seal(route) /* applies default handler */ ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "The requested resource could not be found."
}
Get("/handled/missing") ~> route ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Oh man, what you are looking for is long gone."
}
Get("/handled/boom") ~> route ~> check {
status shouldEqual StatusCodes.InternalServerError
responseAs[String] shouldEqual "This didn't work."
}
}
}

View file

@ -1,106 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.marshalling.ToEntityMarshaller
import akka.http.scaladsl.model.StatusCodes
import akka.http.scaladsl.server.directives.DirectoryListing
import akka.http.scaladsl.server.directives.FileAndResourceDirectives.DirectoryRenderer
import docs.http.scaladsl.server.RoutingSpec
import docs.http.scaladsl.server.RoutingSpec
class FileAndResourceDirectivesExamplesSpec extends RoutingSpec {
"getFromFile-examples" in compileOnlySpec {
import akka.http.scaladsl.server.directives._
import ContentTypeResolver.Default
val route =
path("logs" / Segment) { name =>
getFromFile(s"$name.log") // uses implicit ContentTypeResolver
}
// tests:
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"getFromResource-examples" in compileOnlySpec {
import akka.http.scaladsl.server.directives._
import ContentTypeResolver.Default
val route =
path("logs" / Segment) { name =>
getFromResource(s"$name.log") // uses implicit ContentTypeResolver
}
// tests:
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"listDirectoryContents-examples" in compileOnlySpec {
val route =
path("tmp") {
listDirectoryContents("/tmp")
} ~
path("custom") {
// implement your custom renderer here
val renderer = new DirectoryRenderer {
override def marshaller(renderVanityFooter: Boolean): ToEntityMarshaller[DirectoryListing] = ???
}
listDirectoryContents("/tmp")(renderer)
}
// tests:
Get("/logs/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"getFromBrowseableDirectory-examples" in compileOnlySpec {
val route =
path("tmp") {
getFromBrowseableDirectory("/tmp")
}
// tests:
Get("/tmp") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
"getFromBrowseableDirectories-examples" in compileOnlySpec {
val route =
path("tmp") {
getFromBrowseableDirectories("/main", "/backups")
}
// tests:
Get("/tmp") ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
"getFromDirectory-examples" in compileOnlySpec {
val route =
pathPrefix("tmp") {
getFromDirectory("/tmp")
}
// tests:
Get("/tmp/example") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
"getFromResourceDirectory-examples" in compileOnlySpec {
val route =
pathPrefix("examples") {
getFromResourceDirectory("/examples")
}
// tests:
Get("/examples/example-1") ~> route ~> check {
responseAs[String] shouldEqual "example file contents"
}
}
}

View file

@ -1,83 +0,0 @@
/**
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.testkit.RouteTestTimeout
import akka.stream.scaladsl.Framing
import akka.util.ByteString
import docs.http.scaladsl.server.RoutingSpec
import akka.testkit.TestDuration
import scala.concurrent.Future
import scala.concurrent.duration._
class FileUploadDirectivesExamplesSpec extends RoutingSpec {
override def testConfigSource = "akka.actor.default-mailbox.mailbox-type = \"akka.dispatch.UnboundedMailbox\""
// test touches disk, so give it some time
implicit val routeTimeout = RouteTestTimeout(3.seconds.dilated)
"uploadedFile" in {
val route =
uploadedFile("csv") {
case (metadata, file) =>
// do something with the file and file metadata ...
file.delete()
complete(StatusCodes.OK)
}
// tests:
val multipartForm =
Multipart.FormData(
Multipart.FormData.BodyPart.Strict(
"csv",
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "1,5,7\n11,13,17"),
Map("filename" -> "data.csv")))
Post("/", multipartForm) ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
"fileUpload" in {
// adding integers as a service ;)
val route =
extractRequestContext { ctx =>
implicit val materializer = ctx.materializer
implicit val ec = ctx.executionContext
fileUpload("csv") {
case (metadata, byteSource) =>
val sumF: Future[Int] =
// sum the numbers as they arrive so that we can
// accept any size of file
byteSource.via(Framing.delimiter(ByteString("\n"), 1024))
.mapConcat(_.utf8String.split(",").toVector)
.map(_.toInt)
.runFold(0) { (acc, n) => acc + n }
onSuccess(sumF) { sum => complete(s"Sum: $sum") }
}
}
// tests:
val multipartForm =
Multipart.FormData(Multipart.FormData.BodyPart.Strict(
"csv",
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "2,3,5\n7,11,13,17,23\n29,31,37\n"),
Map("filename" -> "primes.csv")))
Post("/", multipartForm) ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Sum: 178"
}
}
}

View file

@ -1,95 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.model._
import docs.http.scaladsl.server.RoutingSpec
class FormFieldDirectivesExamplesSpec extends RoutingSpec {
"formFields" in {
val route =
formFields('color, 'age.as[Int]) { (color, age) =>
complete(s"The color is '$color' and the age ten years ago was ${age - 10}")
}
// tests:
Post("/", FormData("color" -> "blue", "age" -> "68")) ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the age ten years ago was 58"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "Request is missing required form field 'color'"
}
}
"formField" in {
val route =
formField('color) { color =>
complete(s"The color is '$color'")
} ~
formField('id.as[Int]) { id =>
complete(s"The id is '$id'")
}
// tests:
Post("/", FormData("color" -> "blue")) ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue'"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "Request is missing required form field 'color'"
}
}
"formFieldMap" in {
val route =
formFieldMap { fields =>
def formFieldString(formField: (String, String)): String =
s"""${formField._1} = '${formField._2}'"""
complete(s"The form fields are ${fields.map(formFieldString).mkString(", ")}")
}
// tests:
Post("/", FormData("color" -> "blue", "count" -> "42")) ~> route ~> check {
responseAs[String] shouldEqual "The form fields are color = 'blue', count = '42'"
}
Post("/", FormData("x" -> "1", "x" -> "5")) ~> route ~> check {
responseAs[String] shouldEqual "The form fields are x = '5'"
}
}
"formFieldMultiMap" in {
val route =
formFieldMultiMap { fields =>
complete("There are " +
s"form fields ${fields.map(x => x._1 + " -> " + x._2.size).mkString(", ")}")
}
// tests:
Post("/", FormData("color" -> "blue", "count" -> "42")) ~> route ~> check {
responseAs[String] shouldEqual "There are form fields color -> 1, count -> 1"
}
Post("/", FormData("x" -> "23", "x" -> "4", "x" -> "89")) ~> route ~> check {
responseAs[String] shouldEqual "There are form fields x -> 3"
}
}
"formFieldSeq" in {
val route =
formFieldSeq { fields =>
def formFieldString(formField: (String, String)): String =
s"""${formField._1} = '${formField._2}'"""
complete(s"The form fields are ${fields.map(formFieldString).mkString(", ")}")
}
// tests:
Post("/", FormData("color" -> "blue", "count" -> "42")) ~> route ~> check {
responseAs[String] shouldEqual "The form fields are color = 'blue', count = '42'"
}
Post("/", FormData("x" -> "23", "x" -> "4", "x" -> "89")) ~> route ~> check {
responseAs[String] shouldEqual "The form fields are x = '23', x = '4', x = '89'"
}
}
}

View file

@ -1,145 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import java.util.concurrent.TimeUnit
import docs.http.scaladsl.server.RoutingSpec
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.util.{ Failure, Success }
import akka.http.scaladsl.server.{ CircuitBreakerOpenRejection, ExceptionHandler, Route }
import akka.util.Timeout
import akka.http.scaladsl.model._
import StatusCodes._
import akka.pattern.CircuitBreaker
// format: OFF
class FutureDirectivesExamplesSpec extends RoutingSpec {
object TestException extends Throwable
implicit val myExceptionHandler =
ExceptionHandler {
case TestException => ctx =>
ctx.complete((InternalServerError, "Unsuccessful future!"))
}
implicit val responseTimeout = Timeout(2, TimeUnit.SECONDS)
"onComplete" in {
def divide(a: Int, b: Int): Future[Int] = Future {
a / b
}
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onComplete(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
// tests:
Get("/divide/10/2") ~> route ~> check {
responseAs[String] shouldEqual "The result was 5"
}
Get("/divide/10/0") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "An error occurred: / by zero"
}
}
"onCompleteWithBreaker" in {
def divide(a: Int, b: Int): Future[Int] = Future {
a / b
}
val resetTimeout = 1.second
val breaker = new CircuitBreaker(system.scheduler,
maxFailures = 1,
callTimeout = 5.seconds,
resetTimeout
)
val route =
path("divide" / IntNumber / IntNumber) { (a, b) =>
onCompleteWithBreaker(breaker)(divide(a, b)) {
case Success(value) => complete(s"The result was $value")
case Failure(ex) => complete((InternalServerError, s"An error occurred: ${ex.getMessage}"))
}
}
// tests:
Get("/divide/10/2") ~> route ~> check {
responseAs[String] shouldEqual "The result was 5"
}
Get("/divide/10/0") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "An error occurred: / by zero"
} // opens the circuit breaker
Get("/divide/10/2") ~> route ~> check {
rejection shouldBe a[CircuitBreakerOpenRejection]
}
Thread.sleep(resetTimeout.toMillis + 200)
Get("/divide/10/2") ~> route ~> check {
responseAs[String] shouldEqual "The result was 5"
}
}
"onSuccess" in {
val route =
path("success") {
onSuccess(Future { "Ok" }) { extraction =>
complete(extraction)
}
} ~
path("failure") {
onSuccess(Future.failed[String](TestException)) { extraction =>
complete(extraction)
}
}
// tests:
Get("/success") ~> route ~> check {
responseAs[String] shouldEqual "Ok"
}
Get("/failure") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "Unsuccessful future!"
}
}
"completeOrRecoverWith" in {
val route =
path("success") {
completeOrRecoverWith(Future { "Ok" }) { extraction =>
failWith(extraction) // not executed.
}
} ~
path("failure") {
completeOrRecoverWith(Future.failed[String](TestException)) { extraction =>
failWith(extraction)
}
}
// tests:
Get("/success") ~> route ~> check {
responseAs[String] shouldEqual "Ok"
}
Get("/failure") ~> Route.seal(route) ~> check {
status shouldEqual InternalServerError
responseAs[String] shouldEqual "Unsuccessful future!"
}
}
}

View file

@ -1,221 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.StatusCodes._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.{ InvalidOriginRejection, MissingHeaderRejection, Route }
import docs.http.scaladsl.server.RoutingSpec
import org.scalatest.Inside
class HeaderDirectivesExamplesSpec extends RoutingSpec with Inside {
"headerValueByName-0" in {
val route =
headerValueByName("X-User-Id") { userId =>
complete(s"The user is $userId")
}
// tests:
Get("/") ~> RawHeader("X-User-Id", "Joe42") ~> route ~> check {
responseAs[String] shouldEqual "The user is Joe42"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual BadRequest
responseAs[String] shouldEqual "Request is missing required HTTP header 'X-User-Id'"
}
}
"headerValue-0" in {
def extractHostPort: HttpHeader => Option[Int] = {
case h: `Host` => Some(h.port)
case x => None
}
val route =
headerValue(extractHostPort) { port =>
complete(s"The port was $port")
}
// tests:
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual NotFound
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"
}
}
}
// tests:
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"
}
}
}
// tests:
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
}
val route =
headerValuePF(extractHostPort) { port =>
complete(s"The port was $port")
}
// tests:
Get("/") ~> Host("example.com", 5043) ~> route ~> check {
responseAs[String] shouldEqual "The port was 5043"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual NotFound
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"
}
}
}
// tests:
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 =>
complete(s"The first origin was ${origin.origins.head}")
}
val originHeader = Origin(HttpOrigin("http://localhost:8080"))
// tests:
// extract a header if the type is matching
Get("abc") ~> originHeader ~> route ~> check {
responseAs[String] shouldEqual "The first origin was http://localhost:8080"
}
// reject a request if no header of the given type is present
Get("abc") ~> route ~> check {
inside(rejection) { case MissingHeaderRejection("Origin") => }
}
}
"optionalHeaderValueByType-0" in {
val route =
optionalHeaderValueByType[Origin]() {
case Some(origin) => complete(s"The first origin was ${origin.origins.head}")
case None => complete("No Origin header found.")
}
val originHeader = Origin(HttpOrigin("http://localhost:8080"))
// tests:
// extract Some(header) if the type is matching
Get("abc") ~> originHeader ~> route ~> check {
responseAs[String] shouldEqual "The first origin was http://localhost:8080"
}
// extract None if no header of the given type is present
Get("abc") ~> route ~> check {
responseAs[String] shouldEqual "No Origin header found."
}
}
"checkSameOrigin-0" in {
val correctOrigin = HttpOrigin("http://localhost:8080")
val route = checkSameOrigin(HttpOriginRange(correctOrigin)) {
complete("Result")
}
// tests:
// handle request with correct origin headers
Get("abc") ~> Origin(correctOrigin) ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Result"
}
// reject request with missed origin header
Get("abc") ~> route ~> check {
inside(rejection) {
case MissingHeaderRejection(headerName) headerName shouldEqual Origin.name
}
}
// rejects request with invalid origin headers
val invalidHttpOrigin = HttpOrigin("http://invalid.com")
val invalidOriginHeader = Origin(invalidHttpOrigin)
Get("abc") ~> invalidOriginHeader ~> route ~> check {
inside(rejection) {
case InvalidOriginRejection(allowedOrigins)
allowedOrigins shouldEqual Seq(correctOrigin)
}
}
Get("abc") ~> invalidOriginHeader ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.Forbidden
responseAs[String] should include(s"${correctOrigin.value}")
}
}
}

View file

@ -1,92 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import docs.http.scaladsl.server.RoutingSpec
import headers._
import StatusCodes._
class HostDirectivesExamplesSpec extends RoutingSpec {
"extractHost" in {
val route =
extractHost { hn =>
complete(s"Hostname: $hn")
}
// tests:
Get() ~> Host("company.com", 9090) ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Hostname: company.com"
}
}
"list-of-hosts" in {
val route =
host("api.company.com", "rest.company.com") {
complete("Ok")
}
// tests:
Get() ~> Host("rest.company.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Ok"
}
Get() ~> Host("notallowed.company.com") ~> route ~> check {
handled shouldBe false
}
}
"predicate" in {
val shortOnly: String => Boolean = (hostname) => hostname.length < 10
val route =
host(shortOnly) {
complete("Ok")
}
// tests:
Get() ~> Host("short.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Ok"
}
Get() ~> Host("verylonghostname.com") ~> route ~> check {
handled shouldBe false
}
}
"using-regex" in {
val route =
host("api|rest".r) { prefix =>
complete(s"Extracted prefix: $prefix")
} ~
host("public.(my|your)company.com".r) { captured =>
complete(s"You came through $captured company")
}
// tests:
Get() ~> Host("api.company.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "Extracted prefix: api"
}
Get() ~> Host("public.mycompany.com") ~> route ~> check {
status shouldEqual OK
responseAs[String] shouldEqual "You came through my company"
}
}
"failing-regex" in {
an[IllegalArgumentException] should be thrownBy {
host("server-([0-9]).company.(com|net|org)".r) { target =>
complete("Will never complete :'(")
}
}
}
}

View file

@ -1,226 +0,0 @@
/*
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.http.scaladsl.server.directives
import akka.NotUsed
import akka.http.scaladsl.common.{ EntityStreamingSupport, JsonEntityStreamingSupport }
import akka.http.scaladsl.marshalling._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.Accept
import akka.http.scaladsl.server.{ UnacceptedResponseContentTypeRejection, UnsupportedRequestContentTypeRejection }
import akka.stream.scaladsl.{ Flow, Source }
import akka.util.ByteString
import docs.http.scaladsl.server.RoutingSpec
import scala.concurrent.Future
class JsonStreamingExamplesSpec extends RoutingSpec {
//#models
case class Tweet(uid: Int, txt: String)
case class Measurement(id: String, value: Int)
//#
val tweets = List(
Tweet(1, "#Akka rocks!"),
Tweet(2, "Streaming is so hot right now!"),
Tweet(3, "You cannot enter the same river twice."))
def getTweets = Source(tweets)
//#formats
object MyJsonProtocol
extends akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
with spray.json.DefaultJsonProtocol {
implicit val tweetFormat = jsonFormat2(Tweet.apply)
implicit val measurementFormat = jsonFormat2(Measurement.apply)
}
//#
"spray-json-response-streaming" in {
// [1] import "my protocol", for marshalling Tweet objects:
import MyJsonProtocol._
// [2] pick a Source rendering support trait:
// Note that the default support renders the Source as JSON Array
implicit val jsonStreamingSupport: JsonEntityStreamingSupport = EntityStreamingSupport.json()
val route =
path("tweets") {
// [3] simply complete a request with a source of tweets:
val tweets: Source[Tweet, NotUsed] = getTweets
complete(tweets)
}
// tests ------------------------------------------------------------
val AcceptJson = Accept(MediaRange(MediaTypes.`application/json`))
val AcceptXml = Accept(MediaRange(MediaTypes.`text/xml`))
Get("/tweets").withHeaders(AcceptJson) ~> route ~> check {
responseAs[String] shouldEqual
"""[""" +
"""{"uid":1,"txt":"#Akka rocks!"},""" +
"""{"uid":2,"txt":"Streaming is so hot right now!"},""" +
"""{"uid":3,"txt":"You cannot enter the same river twice."}""" +
"""]"""
}
// endpoint can only marshal Json, so it will *reject* requests for application/xml:
Get("/tweets").withHeaders(AcceptXml) ~> route ~> check {
handled should ===(false)
rejection should ===(UnacceptedResponseContentTypeRejection(Set(ContentTypes.`application/json`)))
}
}
"line-by-line-json-response-streaming" in {
import MyJsonProtocol._
// Configure the EntityStreamingSupport to render the elements as:
// {"example":42}
// {"example":43}
// ...
// {"example":1000}
val start = ByteString.empty
val sep = ByteString("\n")
val end = ByteString.empty
implicit val jsonStreamingSupport = EntityStreamingSupport.json()
.withFramingRenderer(Flow[ByteString].intersperse(start, sep, end))
val route =
path("tweets") {
// [3] simply complete a request with a source of tweets:
val tweets: Source[Tweet, NotUsed] = getTweets
complete(tweets)
}
// tests ------------------------------------------------------------
val AcceptJson = Accept(MediaRange(MediaTypes.`application/json`))
Get("/tweets").withHeaders(AcceptJson) ~> route ~> check {
responseAs[String] shouldEqual
"""{"uid":1,"txt":"#Akka rocks!"}""" + "\n" +
"""{"uid":2,"txt":"Streaming is so hot right now!"}""" + "\n" +
"""{"uid":3,"txt":"You cannot enter the same river twice."}"""
}
}
"csv-example" in {
// [1] provide a marshaller to ByteString
implicit val tweetAsCsv = Marshaller.strict[Tweet, ByteString] { t =>
Marshalling.WithFixedContentType(ContentTypes.`text/csv(UTF-8)`, () => {
val txt = t.txt.replaceAll(",", ".")
val uid = t.uid
ByteString(List(uid, txt).mkString(","))
})
}
// [2] enable csv streaming:
implicit val csvStreaming = EntityStreamingSupport.csv()
val route =
path("tweets") {
val tweets: Source[Tweet, NotUsed] = getTweets
complete(tweets)
}
// tests ------------------------------------------------------------
val AcceptCsv = Accept(MediaRange(MediaTypes.`text/csv`))
Get("/tweets").withHeaders(AcceptCsv) ~> route ~> check {
responseAs[String] shouldEqual
"1,#Akka rocks!" + "\n" +
"2,Streaming is so hot right now!" + "\n" +
"3,You cannot enter the same river twice."
}
}
"response-streaming-modes" in {
{
//#async-rendering
import MyJsonProtocol._
implicit val jsonStreamingSupport: JsonEntityStreamingSupport =
EntityStreamingSupport.json()
.withParallelMarshalling(parallelism = 8, unordered = false)
path("tweets") {
val tweets: Source[Tweet, NotUsed] = getTweets
complete(tweets)
}
//#
}
{
//#async-unordered-rendering
import MyJsonProtocol._
implicit val jsonStreamingSupport: JsonEntityStreamingSupport =
EntityStreamingSupport.json()
.withParallelMarshalling(parallelism = 8, unordered = true)
path("tweets" / "unordered") {
val tweets: Source[Tweet, NotUsed] = getTweets
complete(tweets)
}
//#
}
}
"spray-json-request-streaming" in {
// [1] import "my protocol", for unmarshalling Measurement objects:
import MyJsonProtocol._
// [2] enable Json Streaming
implicit val jsonStreamingSupport = EntityStreamingSupport.json()
// prepare your persisting logic here
val persistMetrics = Flow[Measurement]
val route =
path("metrics") {
// [3] extract Source[Measurement, _]
entity(asSourceOf[Measurement]) { measurements =>
// alternative syntax:
// entity(as[Source[Measurement, NotUsed]]) { measurements =>
val measurementsSubmitted: Future[Int] =
measurements
.via(persistMetrics)
.runFold(0) { (cnt, _) => cnt + 1 }
complete {
measurementsSubmitted.map(n => Map("msg" -> s"""Total metrics received: $n"""))
}
}
}
// tests ------------------------------------------------------------
// uploading an array or newline separated values works out of the box
val data = HttpEntity(
ContentTypes.`application/json`,
"""
|{"id":"temp","value":32}
|{"id":"temp","value":31}
|
""".stripMargin)
Post("/metrics", entity = data) ~> route ~> check {
status should ===(StatusCodes.OK)
responseAs[String] should ===("""{"msg":"Total metrics received: 2"}""")
}
// the FramingWithContentType will reject any content type that it does not understand:
val xmlData = HttpEntity(
ContentTypes.`text/xml(UTF-8)`,
"""|<data id="temp" value="32"/>
|<data id="temp" value="31"/>""".stripMargin)
Post("/metrics", entity = xmlData) ~> route ~> check {
handled should ===(false)
rejection should ===(UnsupportedRequestContentTypeRejection(Set(ContentTypes.`application/json`)))
}
}
}

View file

@ -1,102 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.MediaTypes.`application/json`
import akka.http.scaladsl.model._
import spray.json.{ JsValue, DefaultJsonProtocol }
//# person-case-class
case class Person(name: String, favoriteNumber: Int)
//# person-json-support
object PersonJsonSupport extends DefaultJsonProtocol with SprayJsonSupport {
implicit val PortofolioFormats = jsonFormat2(Person)
}
//#
class MarshallingDirectivesExamplesSpec extends RoutingSpec {
"example-entity-with-json" in {
import PersonJsonSupport._
val route = post {
entity(as[Person]) { person =>
complete(s"Person: ${person.name} - favorite number: ${person.favoriteNumber}")
}
}
// tests:
Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""")) ~>
route ~> check {
responseAs[String] shouldEqual "Person: Jane - favorite number: 42"
}
}
"example-entity-with-raw-json" in {
import PersonJsonSupport._
val route = post {
entity(as[JsValue]) { json =>
complete(s"Person: ${json.asJsObject.fields("name")} - favorite number: ${json.asJsObject.fields("favoriteNumber")}")
}
}
// tests:
Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""")) ~>
route ~> check {
responseAs[String] shouldEqual """Person: "Jane" - favorite number: 42"""
}
}
"example-completeWith-with-json" in {
import PersonJsonSupport._
val findPerson = (f: Person => Unit) => {
//... some processing logic...
//complete the request
f(Person("Jane", 42))
}
val route = get {
completeWith(instanceOf[Person]) { completionFunction => findPerson(completionFunction) }
}
// tests:
Get("/") ~> route ~> check {
mediaType shouldEqual `application/json`
responseAs[String] should include(""""name":"Jane"""")
responseAs[String] should include(""""favoriteNumber":42""")
}
}
"example-handleWith-with-json" in {
import PersonJsonSupport._
val updatePerson = (person: Person) => {
//... some processing logic...
//return the person
person
}
val route = post {
handleWith(updatePerson)
}
// tests:
Post("/", HttpEntity(`application/json`, """{ "name": "Jane", "favoriteNumber" : 42 }""")) ~>
route ~> check {
mediaType shouldEqual `application/json`
responseAs[String] should include(""""name":"Jane"""")
responseAs[String] should include(""""favoriteNumber":42""")
}
}
}

View file

@ -1,136 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Route
import docs.http.scaladsl.server.RoutingSpec
class MethodDirectivesExamplesSpec extends RoutingSpec {
"delete-method" in {
val route = delete { complete("This is a DELETE request.") }
// tests:
Delete("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a DELETE request."
}
}
"get-method" in {
val route = get { complete("This is a GET request.") }
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a GET request."
}
}
"head-method" in {
val route = head { complete("This is a HEAD request.") }
// tests:
Head("/") ~> route ~> check {
responseAs[String] shouldEqual "This is a HEAD request."
}
}
"options-method" in {
val route = options { complete("This is an OPTIONS request.") }
// tests:
Options("/") ~> route ~> check {
responseAs[String] shouldEqual "This is an OPTIONS request."
}
}
"patch-method" in {
val route = patch { complete("This is a PATCH request.") }
// tests:
Patch("/", "patch content") ~> route ~> check {
responseAs[String] shouldEqual "This is a PATCH request."
}
}
"post-method" in {
val route = post { complete("This is a POST request.") }
// tests:
Post("/", "post content") ~> route ~> check {
responseAs[String] shouldEqual "This is a POST request."
}
}
"put-method" in {
val route = put { complete("This is a PUT request.") }
// tests:
Put("/", "put content") ~> route ~> check {
responseAs[String] shouldEqual "This is a PUT request."
}
}
"method-example" in {
val route = method(HttpMethods.PUT) { complete("This is a PUT request.") }
// tests:
Put("/", "put content") ~> route ~> check {
responseAs[String] shouldEqual "This is a PUT request."
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.MethodNotAllowed
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!")
}
// tests:
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!"
}
}
"overrideMethodWithParameter-0" in {
val route =
overrideMethodWithParameter("method") {
get {
complete("This looks like a GET request.")
} ~
post {
complete("This looks like a POST request.")
}
}
// tests:
Get("/?method=POST") ~> route ~> check {
responseAs[String] shouldEqual "This looks like a POST request."
}
Post("/?method=get") ~> route ~> check {
responseAs[String] shouldEqual "This looks like a GET request."
}
Get("/?method=hallo") ~> route ~> check {
status shouldEqual StatusCodes.NotImplemented
}
}
}

View file

@ -1,181 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server._
import headers._
import docs.http.scaladsl.server.RoutingSpec
import java.net.InetAddress
class MiscDirectivesExamplesSpec extends RoutingSpec {
"extractClientIP-example" in {
val route = extractClientIP { ip =>
complete("Client's ip is " + ip.toOption.map(_.getHostAddress).getOrElse("unknown"))
}
// tests:
Get("/").withHeaders(`Remote-Address`(RemoteAddress(InetAddress.getByName("192.168.3.12")))) ~> route ~> check {
responseAs[String] shouldEqual "Client's ip is 192.168.3.12"
}
}
"rejectEmptyResponse-example" in {
val route = rejectEmptyResponse {
path("even" / IntNumber) { i =>
complete {
// returns Some(evenNumberDescription) or None
Option(i).filter(_ % 2 == 0).map { num =>
s"Number $num is even."
}
}
}
}
// tests:
Get("/even/23") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
}
Get("/even/28") ~> route ~> check {
responseAs[String] shouldEqual "Number 28 is even."
}
}
"requestEntityEmptyPresent-example" in {
val route =
requestEntityEmpty {
complete("request entity empty")
} ~
requestEntityPresent {
complete("request entity present")
}
// tests:
Post("/", "text") ~> Route.seal(route) ~> check {
responseAs[String] shouldEqual "request entity present"
}
Post("/") ~> route ~> check {
responseAs[String] shouldEqual "request entity empty"
}
}
"selectPreferredLanguage-example" in {
val request = Get() ~> `Accept-Language`(
Language("en-US"),
Language("en") withQValue 0.7f,
LanguageRange.`*` withQValue 0.1f,
Language("de") withQValue 0.5f)
request ~> {
selectPreferredLanguage("en", "en-US") { lang =>
complete(lang.toString)
}
} ~> check { responseAs[String] shouldEqual "en-US" }
request ~> {
selectPreferredLanguage("de-DE", "hu") { lang =>
complete(lang.toString)
}
} ~> check { responseAs[String] shouldEqual "de-DE" }
}
"validate-example" in {
val route =
extractUri { uri =>
validate(uri.path.toString.size < 5, s"Path too long: '${uri.path.toString}'") {
complete(s"Full URI: $uri")
}
}
// tests:
Get("/234") ~> route ~> check {
responseAs[String] shouldEqual "Full URI: http://example.com/234"
}
Get("/abcdefghijkl") ~> route ~> check {
rejection shouldEqual ValidationRejection("Path too long: '/abcdefghijkl'", None)
}
}
"withSizeLimit-example" in {
val route = withSizeLimit(500) {
entity(as[String]) { _
complete(HttpResponse())
}
}
// tests:
def entityOfSize(size: Int) =
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size)
Post("/abc", entityOfSize(500)) ~> route ~> check {
status shouldEqual StatusCodes.OK
}
Post("/abc", entityOfSize(501)) ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
}
}
"withSizeLimit-execution-moment-example" in {
val route = withSizeLimit(500) {
complete(HttpResponse())
}
// tests:
def entityOfSize(size: Int) =
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size)
Post("/abc", entityOfSize(500)) ~> route ~> check {
status shouldEqual StatusCodes.OK
}
Post("/abc", entityOfSize(501)) ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
"withSizeLimit-nested-example" in {
val route =
withSizeLimit(500) {
withSizeLimit(800) {
entity(as[String]) { _
complete(HttpResponse())
}
}
}
// tests:
def entityOfSize(size: Int) =
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size)
Post("/abc", entityOfSize(800)) ~> route ~> check {
status shouldEqual StatusCodes.OK
}
Post("/abc", entityOfSize(801)) ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
}
}
"withoutSizeLimit-example" in {
val route =
withoutSizeLimit {
entity(as[String]) { _
complete(HttpResponse())
}
}
// tests:
def entityOfSize(size: Int) =
HttpEntity(ContentTypes.`text/plain(UTF-8)`, "0" * size)
// will work even if you have configured akka.http.parsing.max-content-length = 500
Post("/abc", entityOfSize(501)) ~> route ~> check {
status shouldEqual StatusCodes.OK
}
}
}

View file

@ -1,212 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.unmarshalling.PredefinedFromStringUnmarshallers
import docs.http.scaladsl.server.RoutingSpec
class ParameterDirectivesExamplesSpec extends RoutingSpec with PredefinedFromStringUnmarshallers {
"example-1" in {
val route =
parameter('color) { color =>
complete(s"The color is '$color'")
}
// tests:
Get("/?color=blue") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue'"
}
Get("/") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Request is missing required query parameter 'color'"
}
}
"required-1" in {
val route =
parameters('color, 'backgroundColor) { (color, backgroundColor) =>
complete(s"The color is '$color' and the background is '$backgroundColor'")
}
// tests:
Get("/?color=blue&backgroundColor=red") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'red'"
}
Get("/?color=blue") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "Request is missing required query parameter 'backgroundColor'"
}
}
"optional" in {
val route =
parameters('color, 'backgroundColor.?) { (color, backgroundColor) =>
val backgroundStr = backgroundColor.getOrElse("<undefined>")
complete(s"The color is '$color' and the background is '$backgroundStr'")
}
// tests:
Get("/?color=blue&backgroundColor=red") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'red'"
}
Get("/?color=blue") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is '<undefined>'"
}
}
"optional-with-default" in {
val route =
parameters('color, 'backgroundColor ? "white") { (color, backgroundColor) =>
complete(s"The color is '$color' and the background is '$backgroundColor'")
}
// tests:
Get("/?color=blue&backgroundColor=red") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'red'"
}
Get("/?color=blue") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and the background is 'white'"
}
}
"required-value" in {
val route =
parameters('color, 'action ! "true") { (color) =>
complete(s"The color is '$color'.")
}
// tests:
Get("/?color=blue&action=true") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue'."
}
Get("/?color=blue&action=false") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.NotFound
responseAs[String] shouldEqual "The requested resource could not be found."
}
}
"mapped-value" in {
val route =
parameters('color, 'count.as[Int]) { (color, count) =>
complete(s"The color is '$color' and you have $count of it.")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "The color is 'blue' and you have 42 of it."
}
Get("/?color=blue&count=blub") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.BadRequest
responseAs[String] shouldEqual "The query parameter 'count' was malformed:\n'blub' is not a valid 32-bit signed integer value"
}
}
"repeated" in {
val route =
parameters('color, 'city.*) { (color, cities) =>
cities.toList match {
case Nil => complete(s"The color is '$color' and there are no cities.")
case city :: Nil => complete(s"The color is '$color' and the city is $city.")
case multiple => complete(s"The color is '$color' and the cities are ${multiple.mkString(", ")}.")
}
}
// tests:
Get("/?color=blue") ~> route ~> check {
responseAs[String] === "The color is 'blue' and there are no cities."
}
Get("/?color=blue&city=Chicago") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the city is Chicago."
}
Get("/?color=blue&city=Chicago&city=Boston") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the cities are Chicago, Boston."
}
}
"mapped-repeated" in {
val route =
parameters('color, 'distance.as[Int].*) { (color, cities) =>
cities.toList match {
case Nil => complete(s"The color is '$color' and there are no distances.")
case distance :: Nil => complete(s"The color is '$color' and the distance is $distance.")
case multiple => complete(s"The color is '$color' and the distances are ${multiple.mkString(", ")}.")
}
}
// tests:
Get("/?color=blue") ~> route ~> check {
responseAs[String] === "The color is 'blue' and there are no distances."
}
Get("/?color=blue&distance=5") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the distance is 5."
}
Get("/?color=blue&distance=5&distance=14") ~> Route.seal(route) ~> check {
responseAs[String] === "The color is 'blue' and the distances are 5, 14."
}
}
"parameterMap" in {
val route =
parameterMap { params =>
def paramString(param: (String, String)): String = s"""${param._1} = '${param._2}'"""
complete(s"The parameters are ${params.map(paramString).mkString(", ")}")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are color = 'blue', count = '42'"
}
Get("/?x=1&x=2") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are x = '2'"
}
}
"parameterMultiMap" in {
val route =
parameterMultiMap { params =>
complete(s"There are parameters ${params.map(x => x._1 + " -> " + x._2.size).mkString(", ")}")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "There are parameters color -> 1, count -> 1"
}
Get("/?x=23&x=42") ~> route ~> check {
responseAs[String] shouldEqual "There are parameters x -> 2"
}
}
"parameterSeq" in {
val route =
parameterSeq { params =>
def paramString(param: (String, String)): String = s"""${param._1} = '${param._2}'"""
complete(s"The parameters are ${params.map(paramString).mkString(", ")}")
}
// tests:
Get("/?color=blue&count=42") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are color = 'blue', count = '42'"
}
Get("/?x=1&x=2") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are x = '1', x = '2'"
}
}
"csv" in {
val route =
parameter("names".as(CsvSeq[String])) { names =>
complete(s"The parameters are ${names.mkString(", ")}")
}
// tests:
Get("/?names=") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are "
}
Get("/?names=Caplin") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are Caplin"
}
Get("/?names=Caplin,John") ~> route ~> check {
responseAs[String] shouldEqual "The parameters are Caplin, John"
}
}
}

View file

@ -1,379 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.server._
import docs.http.scaladsl.server.RoutingSpec
class PathDirectivesExamplesSpec extends RoutingSpec {
//# path-matcher
val matcher: PathMatcher1[Option[Int]] =
"foo" / "bar" / "X" ~ IntNumber.? / ("edit" | "create")
//#
//# path-dsl
// matches /foo/
path("foo"./)
// matches e.g. /foo/123 and extracts "123" as a String
path("foo" / """\d+""".r)
// matches e.g. /foo/bar123 and extracts "123" as a String
path("foo" / """bar(\d+)""".r)
// similar to `path(Segments)`
path(Segment.repeat(10, separator = Slash))
// matches e.g. /i42 or /hCAFE and extracts an Int
path("i" ~ IntNumber | "h" ~ HexIntNumber)
// identical to path("foo" ~ (PathEnd | Slash))
path("foo" ~ Slash.?)
// matches /red or /green or /blue and extracts 1, 2 or 3 respectively
path(Map("red" -> 1, "green" -> 2, "blue" -> 3))
// matches anything starting with "/foo" except for /foobar
pathPrefix("foo" ~ !"bar")
//#
//# pathPrefixTest-, rawPathPrefix-, rawPathPrefixTest-, pathSuffix-, pathSuffixTest-
val completeWithUnmatchedPath =
extractUnmatchedPath { p =>
complete(p.toString)
}
//#
"path-example" in {
val route =
path("foo") {
complete("/foo")
} ~
path("foo" / "bar") {
complete("/foo/bar")
} ~
pathPrefix("ball") {
pathEnd {
complete("/ball")
} ~
path(IntNumber) { int =>
complete(if (int % 2 == 0) "even ball" else "odd ball")
}
}
// tests:
Get("/") ~> route ~> check {
handled shouldEqual false
}
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/bar") ~> route ~> check {
responseAs[String] shouldEqual "/foo/bar"
}
Get("/ball/1337") ~> route ~> check {
responseAs[String] shouldEqual "odd ball"
}
}
"pathEnd-" in {
val route =
pathPrefix("foo") {
pathEnd {
complete("/foo")
} ~
path("bar") {
complete("/foo/bar")
}
}
// tests:
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/") ~> route ~> check {
handled shouldEqual false
}
Get("/foo/bar") ~> route ~> check {
responseAs[String] shouldEqual "/foo/bar"
}
}
"pathEndOrSingleSlash-" in {
val route =
pathPrefix("foo") {
pathEndOrSingleSlash {
complete("/foo")
} ~
path("bar") {
complete("/foo/bar")
}
}
// tests:
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/") ~> route ~> check {
responseAs[String] shouldEqual "/foo"
}
Get("/foo/bar") ~> route ~> check {
responseAs[String] shouldEqual "/foo/bar"
}
}
"pathPrefix-" in {
val route =
pathPrefix("ball") {
pathEnd {
complete("/ball")
} ~
path(IntNumber) { int =>
complete(if (int % 2 == 0) "even ball" else "odd ball")
}
}
// tests:
Get("/") ~> route ~> check {
handled shouldEqual false
}
Get("/ball") ~> route ~> check {
responseAs[String] shouldEqual "/ball"
}
Get("/ball/1337") ~> route ~> check {
responseAs[String] shouldEqual "odd ball"
}
}
"pathPrefixTest-" in {
val route =
pathPrefixTest("foo" | "bar") {
pathPrefix("foo") { completeWithUnmatchedPath } ~
pathPrefix("bar") { completeWithUnmatchedPath }
}
// tests:
Get("/foo/doo") ~> route ~> check {
responseAs[String] shouldEqual "/doo"
}
Get("/bar/yes") ~> route ~> check {
responseAs[String] shouldEqual "/yes"
}
}
"pathSingleSlash-" in {
val route =
pathSingleSlash {
complete("root")
} ~
pathPrefix("ball") {
pathSingleSlash {
complete("/ball/")
} ~
path(IntNumber) { int =>
complete(if (int % 2 == 0) "even ball" else "odd ball")
}
}
// tests:
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "root"
}
Get("/ball") ~> route ~> check {
handled shouldEqual false
}
Get("/ball/") ~> route ~> check {
responseAs[String] shouldEqual "/ball/"
}
Get("/ball/1337") ~> route ~> check {
responseAs[String] shouldEqual "odd ball"
}
}
"pathSuffix-" in {
val route =
pathPrefix("start") {
pathSuffix("end") {
completeWithUnmatchedPath
} ~
pathSuffix("foo" / "bar" ~ "baz") {
completeWithUnmatchedPath
}
}
// tests:
Get("/start/middle/end") ~> route ~> check {
responseAs[String] shouldEqual "/middle/"
}
Get("/start/something/barbaz/foo") ~> route ~> check {
responseAs[String] shouldEqual "/something/"
}
}
"pathSuffixTest-" in {
val route =
pathSuffixTest(Slash) {
complete("slashed")
} ~
complete("unslashed")
// tests:
Get("/foo/") ~> route ~> check {
responseAs[String] shouldEqual "slashed"
}
Get("/foo") ~> route ~> check {
responseAs[String] shouldEqual "unslashed"
}
}
"rawPathPrefix-" in {
val route =
pathPrefix("foo") {
rawPathPrefix("bar") { completeWithUnmatchedPath } ~
rawPathPrefix("doo") { completeWithUnmatchedPath }
}
// tests:
Get("/foobar/baz") ~> route ~> check {
responseAs[String] shouldEqual "/baz"
}
Get("/foodoo/baz") ~> route ~> check {
responseAs[String] shouldEqual "/baz"
}
}
"rawPathPrefixTest-" in {
val route =
pathPrefix("foo") {
rawPathPrefixTest("bar") {
completeWithUnmatchedPath
}
}
// tests:
Get("/foobar") ~> route ~> check {
responseAs[String] shouldEqual "bar"
}
Get("/foobaz") ~> route ~> check {
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" /
???
}
}
// tests:
// Redirected:
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.MovedPermanently
// results in nice human readable message,
// in case the redirect can't be followed automatically:
responseAs[String] shouldEqual {
"This and all future requests should be directed to " +
"<a href=\"http://example.com/foo/\">this URI</a>."
}
}
// Properly handled:
Get("/foo/") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "OK"
}
// MISTAKE! will never match - reason explained in routes
Get("/bad-1/") ~> route ~> check {
handled shouldEqual false
}
// MISTAKE! will never match - reason explained in routes
Get("/bad-2/") ~> route ~> check {
handled shouldEqual false
}
}
"redirectToNoTrailingSlashIfPresent-0" in {
import akka.http.scaladsl.model.StatusCodes
val route =
redirectToNoTrailingSlashIfPresent(StatusCodes.MovedPermanently) {
path("foo") {
// We require the explicit trailing slash in the path
complete("OK")
} ~
path("bad"./) {
// MISTAKE!
// Since inside a `redirectToNoTrailingSlashIfPresent` directive
// the matched path here will never contain a trailing slash,
// thus this path will never match.
//
// It should be `path("bad")` instead.
???
}
}
// tests:
// Redirected:
Get("/foo/") ~> route ~> check {
status shouldEqual StatusCodes.MovedPermanently
// results in nice human readable message,
// in case the redirect can't be followed automatically:
responseAs[String] shouldEqual {
"This and all future requests should be directed to " +
"<a href=\"http://example.com/foo\">this URI</a>."
}
}
// Properly handled:
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "OK"
}
// MISTAKE! will never match - reason explained in routes
Get("/bad") ~> route ~> check {
handled shouldEqual false
}
}
}

View file

@ -1,58 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import com.typesafe.config.{ ConfigFactory, Config }
import akka.util.ByteString
import headers._
import scala.concurrent.Await
import scala.concurrent.duration._
import docs.http.scaladsl.server.RoutingSpec
class RangeDirectivesExamplesSpec extends RoutingSpec {
override def testConfig: Config =
ConfigFactory.parseString("akka.http.routing.range-coalescing-threshold=2").withFallback(super.testConfig)
"withRangeSupport" in {
val route =
withRangeSupport {
complete("ABCDEFGH")
}
Get() ~> addHeader(Range(ByteRange(3, 4))) ~> route ~> check {
headers should contain(`Content-Range`(ContentRange(3, 4, 8)))
status shouldEqual StatusCodes.PartialContent
responseAs[String] shouldEqual "DE"
}
// we set "akka.http.routing.range-coalescing-threshold = 2"
// above to make sure we get two BodyParts
Get() ~> addHeader(Range(ByteRange(0, 1), ByteRange(1, 2), ByteRange(6, 7))) ~> route ~> check {
headers.collectFirst { case `Content-Range`(_, _) => true } shouldBe None
val responseF = responseAs[Multipart.ByteRanges].parts
.runFold[List[Multipart.ByteRanges.BodyPart]](Nil)((acc, curr) => curr :: acc)
val response = Await.result(responseF, 3.seconds).reverse
response should have length 2
val part1 = response(0)
part1.contentRange === ContentRange(0, 2, 8)
part1.entity should matchPattern {
case HttpEntity.Strict(_, bytes) if bytes.utf8String == "ABC" =>
}
val part2 = response(1)
part2.contentRange === ContentRange(6, 7, 8)
part2.entity should matchPattern {
case HttpEntity.Strict(_, bytes) if bytes.utf8String == "GH" =>
}
}
}
}

View file

@ -1,83 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.headers._
import docs.http.scaladsl.server.RoutingSpec
class RespondWithDirectivesExamplesSpec extends RoutingSpec {
"respondWithHeader-0" in {
val route =
path("foo") {
respondWithHeader(RawHeader("Funky-Muppet", "gonzo")) {
complete("beep")
}
}
// tests:
Get("/foo") ~> route ~> check {
header("Funky-Muppet") shouldEqual Some(RawHeader("Funky-Muppet", "gonzo"))
responseAs[String] shouldEqual "beep"
}
}
"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
// tests:
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(HttpOrigin("http://akka.io"))) {
complete("beep")
}
}
// tests:
Get("/foo") ~> route ~> check {
header("Funky-Muppet") shouldEqual Some(RawHeader("Funky-Muppet", "gonzo"))
header[Origin] shouldEqual Some(Origin(HttpOrigin("http://akka.io")))
responseAs[String] shouldEqual "beep"
}
}
}

View file

@ -1,136 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.ContentTypes._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.`Content-Type`
import akka.http.scaladsl.server.{ Route, ValidationRejection }
import akka.testkit.EventFilter
import docs.http.scaladsl.server.RoutingSpec
class RouteDirectivesExamplesSpec extends RoutingSpec {
"complete-examples" in {
val route =
path("a") {
complete(HttpResponse(entity = "foo"))
} ~
path("b") {
complete(StatusCodes.OK)
} ~
path("c") {
complete(StatusCodes.Created -> "bar")
} ~
path("d") {
complete(201 -> "bar")
} ~
path("e") {
complete(StatusCodes.Created, List(`Content-Type`(`text/plain(UTF-8)`)), "bar")
} ~
path("f") {
complete(201, List(`Content-Type`(`text/plain(UTF-8)`)), "bar")
} ~
(path("g") & complete("baz")) // `&` also works with `complete` as the 2nd argument
// tests:
Get("/a") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "foo"
}
Get("/b") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "OK"
}
Get("/c") ~> route ~> check {
status shouldEqual StatusCodes.Created
responseAs[String] shouldEqual "bar"
}
Get("/d") ~> route ~> check {
status shouldEqual StatusCodes.Created
responseAs[String] shouldEqual "bar"
}
Get("/e") ~> route ~> check {
status shouldEqual StatusCodes.Created
header[`Content-Type`] shouldEqual Some(`Content-Type`(`text/plain(UTF-8)`))
responseAs[String] shouldEqual "bar"
}
Get("/f") ~> route ~> check {
status shouldEqual StatusCodes.Created
header[`Content-Type`] shouldEqual Some(`Content-Type`(`text/plain(UTF-8)`))
responseAs[String] shouldEqual "bar"
}
Get("/g") ~> route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "baz"
}
}
"reject-examples" in {
val route =
path("a") {
reject // don't handle here, continue on
} ~
path("a") {
complete("foo")
} ~
path("b") {
// trigger a ValidationRejection explicitly
// rather than through the `validate` directive
reject(ValidationRejection("Restricted!"))
}
// tests:
Get("/a") ~> route ~> check {
responseAs[String] shouldEqual "foo"
}
Get("/b") ~> route ~> check {
rejection shouldEqual ValidationRejection("Restricted!")
}
}
"redirect-examples" in {
val route =
pathPrefix("foo") {
pathSingleSlash {
complete("yes")
} ~
pathEnd {
redirect("/foo/", StatusCodes.PermanentRedirect)
}
}
// tests:
Get("/foo/") ~> route ~> check {
responseAs[String] shouldEqual "yes"
}
Get("/foo") ~> route ~> check {
status shouldEqual StatusCodes.PermanentRedirect
responseAs[String] shouldEqual """The request, and all future requests should be repeated using <a href="/foo/">this URI</a>."""
}
}
"failwith-examples" in EventFilter[RuntimeException](start = "Error during processing of request", occurrences = 1).intercept {
val route =
path("foo") {
failWith(new RuntimeException("Oops."))
}
// tests:
Get("/foo") ~> Route.seal(route) ~> check {
status shouldEqual StatusCodes.InternalServerError
responseAs[String] shouldEqual "There was an internal server error."
}
}
}

View file

@ -1,46 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import docs.http.scaladsl.server.RoutingSpec
class SchemeDirectivesExamplesSpec extends RoutingSpec {
"example-1" in {
val route =
extractScheme { scheme =>
complete(s"The scheme is '${scheme}'")
}
// tests:
Get("https://www.example.com/") ~> route ~> check {
responseAs[String] shouldEqual "The scheme is 'https'"
}
}
"example-2" in {
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers.Location
import StatusCodes.MovedPermanently
val route =
scheme("http") {
extract(_.request.uri) { uri =>
redirect(uri.copy(scheme = "https"), MovedPermanently)
}
} ~
scheme("https") {
complete(s"Safe and secure!")
}
// tests:
Get("http://www.example.com/hello") ~> route ~> check {
status shouldEqual MovedPermanently
header[Location] shouldEqual Some(Location(Uri("https://www.example.com/hello")))
}
Get("https://www.example.com/hello") ~> route ~> check {
responseAs[String] shouldEqual "Safe and secure!"
}
}
}

View file

@ -1,328 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Route
import akka.http.scaladsl.server.directives.Credentials
import scala.concurrent.Future
import docs.http.scaladsl.server.RoutingSpec
class SecurityDirectivesExamplesSpec extends RoutingSpec {
"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'")
}
}
}
// tests:
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", Some("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", Some("secure site"))
}
}
"authenticateBasicPF-0" in {
val myUserPassAuthenticator: AuthenticatorPF[String] = {
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd") => id
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd-special") => s"$id-admin"
}
val route =
Route.seal {
path("secured") {
authenticateBasicPF(realm = "secure site", myUserPassAuthenticator) { userName =>
complete(s"The user is '$userName'")
}
}
}
// tests:
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", Some("secure site"))
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John'"
}
val validAdminCredentials = BasicHttpCredentials("John", "p4ssw0rd-special")
Get("/secured") ~> addCredentials(validAdminCredentials) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "The user is 'John-admin'"
}
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", Some("secure site"))
}
}
"authenticateBasicPFAsync-0" in {
case class User(id: String)
def fetchUser(id: String): Future[User] = {
// some fancy logic to obtain a User
Future.successful(User(id))
}
val myUserPassAuthenticator: AsyncAuthenticatorPF[User] = {
case p @ Credentials.Provided(id) if p.verify("p4ssw0rd") =>
fetchUser(id)
}
val route =
Route.seal {
path("secured") {
authenticateBasicPFAsync(realm = "secure site", myUserPassAuthenticator) { user =>
complete(s"The user is '${user.id}'")
}
}
}
// tests:
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", Some("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", Some("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'")
}
}
}
// tests:
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", Some("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", Some("secure site"))
}
}
"authenticateOrRejectWithChallenge-0" in {
val challenge = HttpChallenge("MyAuth", Some("MyRealm"))
// your custom authentication logic:
def auth(creds: HttpCredentials): Boolean = true
def myUserPassAuthenticator(credentials: Option[HttpCredentials]): Future[AuthenticationResult[String]] =
Future {
credentials match {
case Some(creds) if auth(creds) => Right("some-user-name-from-creds")
case _ => Left(challenge)
}
}
val route =
Route.seal {
path("secured") {
authenticateOrRejectWithChallenge(myUserPassAuthenticator _) { userName =>
complete("Authenticated!")
}
}
}
// tests:
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("MyAuth", Some("MyRealm"))
}
val validCredentials = BasicHttpCredentials("John", "p4ssw0rd")
Get("/secured") ~> addCredentials(validCredentials) ~> // adds Authorization header
route ~> check {
status shouldEqual StatusCodes.OK
responseAs[String] shouldEqual "Authenticated!"
}
}
"0authorize-0" 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 {
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { user =>
path("peters-lair") {
authorize(hasAdminPermissions(user)) {
complete(s"'${user.name}' visited Peter's lair")
}
}
}
}
// tests:
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
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
route ~> check {
responseAs[String] shouldEqual "'Peter' visited Peter's lair"
}
}
"0authorizeAsync" 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,
// this could potentially be a long operation so it would return a Future
val admins = Set("Peter")
def hasAdminPermissions(user: User): Future[Boolean] =
Future.successful(admins.contains(user.name))
val route =
Route.seal {
authenticateBasic(realm = "secure site", myUserPassAuthenticator) { user =>
path("peters-lair") {
authorizeAsync(_ => hasAdminPermissions(user)) {
complete(s"'${user.name}' visited Peter's lair")
}
}
}
}
// tests:
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
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
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"
}
}
}
// tests:
val johnsCred = BasicHttpCredentials("John", "p4ssw0rd")
Get("/") ~> addCredentials(johnsCred) ~> // adds Authorization header
route ~> check {
responseAs[String] shouldEqual "Credentials: Basic Sm9objpwNHNzdzByZA=="
}
Get("/") ~> route ~> check {
responseAs[String] shouldEqual "No credentials"
}
}
}

View file

@ -1,169 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import akka.http.scaladsl.model.{ HttpResponse, StatusCodes }
import akka.http.scaladsl.server.Route
import docs.CompileOnlySpec
import akka.http.scaladsl.{ Http, TestUtils }
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.http.scaladsl.model.HttpEntity._
import akka.http.scaladsl.model._
import com.typesafe.config.{ Config, ConfigFactory }
import org.scalatest.concurrent.ScalaFutures
import scala.concurrent.duration._
import scala.concurrent.{ Future, Promise }
import akka.testkit.AkkaSpec
private[this] object TimeoutDirectivesInfiniteTimeoutTestConfig {
val testConf: Config = ConfigFactory.parseString("""
akka.loggers = ["akka.testkit.TestEventListener"]
akka.loglevel = ERROR
akka.stdout-loglevel = ERROR
windows-connection-abort-workaround-enabled = auto
akka.log-dead-letters = OFF
akka.http.server.request-timeout = infinite""")
}
class TimeoutDirectivesExamplesSpec extends AkkaSpec(TimeoutDirectivesInfiniteTimeoutTestConfig.testConf)
with ScalaFutures with CompileOnlySpec {
//#testSetup
import system.dispatcher
implicit val materializer = ActorMaterializer()
def slowFuture(): Future[String] = Promise[String].future // TODO: move to Future.never in Scala 2.12
def runRoute(route: Route, routePath: String): HttpResponse = {
val (_, hostname, port) = TestUtils.temporaryServerHostnameAndPort()
val binding = Http().bindAndHandle(route, hostname, port)
val response = Http().singleRequest(HttpRequest(uri = s"http://$hostname:$port/$routePath")).futureValue
binding.flatMap(_.unbind()).futureValue
response
}
//#
// demonstrates that timeout is correctly set despite infinite initial value of akka.http.server.request-timeout
"Request Timeout" should {
"be configurable in routing layer despite infinite initial value of request-timeout" in {
//#withRequestTimeout-plain
val route =
path("timeout") {
withRequestTimeout(1.seconds) { // modifies the global akka.http.server.request-timeout for this request
val response: Future[String] = slowFuture() // very slow
complete(response)
}
}
// check
runRoute(route, "timeout").status should ===(StatusCodes.ServiceUnavailable) // the timeout response
//#
}
"without timeout" in compileOnlySpec {
//#withoutRequestTimeout-1
val route =
path("timeout") {
withoutRequestTimeout {
val response: Future[String] = slowFuture() // very slow
complete(response)
}
}
// no check as there is no time-out, the future would time out failing the test
//#
}
"allow mapping the response while setting the timeout" in {
//#withRequestTimeout-with-handler
val timeoutResponse = HttpResponse(
StatusCodes.EnhanceYourCalm,
entity = "Unable to serve response within time limit, please enhance your calm.")
val route =
path("timeout") {
// updates timeout and handler at
withRequestTimeout(1.milli, request => timeoutResponse) {
val response: Future[String] = slowFuture() // very slow
complete(response)
}
}
// check
runRoute(route, "timeout").status should ===(StatusCodes.EnhanceYourCalm) // the timeout response
//#
}
// make it compile only to avoid flaking in slow builds
"allow mapping the response" in compileOnlySpec {
//#withRequestTimeoutResponse
val timeoutResponse = HttpResponse(
StatusCodes.EnhanceYourCalm,
entity = "Unable to serve response within time limit, please enhance your calm.")
val route =
path("timeout") {
withRequestTimeout(100.milli) { // racy! for a very short timeout like 1.milli you can still get 503
withRequestTimeoutResponse(request => timeoutResponse) {
val response: Future[String] = slowFuture() // very slow
complete(response)
}
}
}
// check
runRoute(route, "timeout").status should ===(StatusCodes.EnhanceYourCalm) // the timeout response
//#
}
}
}
private[this] object TimeoutDirectivesFiniteTimeoutTestConfig {
val testConf: Config = ConfigFactory.parseString("""
akka.loggers = ["akka.testkit.TestEventListener"]
akka.loglevel = ERROR
akka.stdout-loglevel = ERROR
windows-connection-abort-workaround-enabled = auto
akka.log-dead-letters = OFF
akka.http.server.request-timeout = 1000s""")
}
class TimeoutDirectivesFiniteTimeoutExamplesSpec extends AkkaSpec(TimeoutDirectivesFiniteTimeoutTestConfig.testConf)
with ScalaFutures with CompileOnlySpec {
import system.dispatcher
implicit val materializer = ActorMaterializer()
def slowFuture(): Future[String] = Promise[String].future // TODO: move to Future.never in Scala 2.12
def runRoute(route: Route, routePath: String): HttpResponse = {
val (_, hostname, port) = TestUtils.temporaryServerHostnameAndPort()
val binding = Http().bindAndHandle(route, hostname, port)
val response = Http().singleRequest(HttpRequest(uri = s"http://$hostname:$port/$routePath")).futureValue
binding.flatMap(_.unbind()).futureValue
response
}
// demonstrates that timeout is correctly modified for finite initial values of akka.http.server.request-timeout
"Request Timeout" should {
"be configurable in routing layer for finite initial value of request-timeout" in {
val route =
path("timeout") {
withRequestTimeout(1.seconds) { // modifies the global akka.http.server.request-timeout for this request
val response: Future[String] = slowFuture() // very slow
complete(response)
}
}
runRoute(route, "timeout").status should ===(StatusCodes.ServiceUnavailable) // the timeout response
}
}
}

View file

@ -1,105 +0,0 @@
/*
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
*/
package docs.http.scaladsl.server.directives
import scala.concurrent.duration._
import akka.util.ByteString
import akka.stream.OverflowStrategy
import akka.stream.scaladsl.{ Sink, Source, Flow }
import docs.http.scaladsl.server.RoutingSpec
import akka.http.scaladsl.model.ws.{ TextMessage, Message, BinaryMessage }
import akka.http.scaladsl.testkit.WSProbe
class WebSocketDirectivesExamplesSpec extends RoutingSpec {
"greeter-service" in {
def greeter: Flow[Message, Message, Any] =
Flow[Message].mapConcat {
case tm: TextMessage =>
TextMessage(Source.single("Hello ") ++ tm.textStream ++ Source.single("!")) :: Nil
case bm: BinaryMessage =>
// ignore binary messages but drain content to avoid the stream being clogged
bm.dataStream.runWith(Sink.ignore)
Nil
}
val websocketRoute =
path("greeter") {
handleWebSocketMessages(greeter)
}
// tests:
// create a testing probe representing the client-side
val wsClient = WSProbe()
// WS creates a WebSocket request for testing
WS("/greeter", wsClient.flow) ~> websocketRoute ~>
check {
// check response for WS Upgrade headers
isWebSocketUpgrade shouldEqual true
// manually run a WS conversation
wsClient.sendMessage("Peter")
wsClient.expectMessage("Hello Peter!")
wsClient.sendMessage(BinaryMessage(ByteString("abcdef")))
wsClient.expectNoMessage(100.millis)
wsClient.sendMessage("John")
wsClient.expectMessage("Hello John!")
wsClient.sendCompletion()
wsClient.expectCompletion()
}
}
"handle-multiple-protocols" in {
def greeterService: Flow[Message, Message, Any] =
Flow[Message].mapConcat {
case tm: TextMessage =>
TextMessage(Source.single("Hello ") ++ tm.textStream ++ Source.single("!")) :: Nil
case bm: BinaryMessage =>
// ignore binary messages but drain content to avoid the stream being clogged
bm.dataStream.runWith(Sink.ignore)
Nil
}
def echoService: Flow[Message, Message, Any] =
Flow[Message]
// needed because a noop flow hasn't any buffer that would start processing in tests
.buffer(1, OverflowStrategy.backpressure)
def websocketMultipleProtocolRoute =
path("services") {
handleWebSocketMessagesForProtocol(greeterService, "greeter") ~
handleWebSocketMessagesForProtocol(echoService, "echo")
}
// tests:
val wsClient = WSProbe()
// WS creates a WebSocket request for testing
WS("/services", wsClient.flow, List("other", "echo")) ~>
websocketMultipleProtocolRoute ~>
check {
expectWebSocketUpgradeWithProtocol { protocol =>
protocol shouldEqual "echo"
wsClient.sendMessage("Peter")
wsClient.expectMessage("Peter")
wsClient.sendMessage(BinaryMessage(ByteString("abcdef")))
wsClient.expectMessage(ByteString("abcdef"))
wsClient.sendMessage("John")
wsClient.expectMessage("John")
wsClient.sendCompletion()
wsClient.expectCompletion()
}
}
}
}