* !htt #18919 #19519 Align Java HTTP server DSL with Scala This commits replaces the Java HTTP server DSL with a Java-8 centric one which exposes all scala DSL concepts to be usable from Java, including custom directives, (un)marshallers, rejections, headers, and type safety for path and query parameters. * Add RequestContext and RouteResult to Java DSL fix websockets WIP bring java docs up to date. This applies some updates to the root-level documentation * [htp] Fix java documentation to correctly mention timeouts Timeouts are configured the same in Java and Scala. Hence, linking to the scala docs for timeouts from Java. * =htc fix optionalHeaderValueByType in Java * =htt #20200 fix java testkit always using NoLogging instead logger * +htt actually run new javadsl tests, allow overriding config * =htt improve javadsl test infra with more details when fails * =htt fix bug in wrong path matcher exposed * +htp add missing remaining path matcher * =htp Java DSL cookie tests fixed * =htt Java DSL ParameterDirectivesTest fixed Protect the tweets from scalariform Incorrect response expectations in cache condition directives spec fixed * =htt Path directives for Java DSL * +!htt PathMatchers rewritten, made uniform and tests passing * Bugfix in java reject and a little test-boyscouting * Revert "Incorrect response expectations in cache condition directives spec fixed" This reverts commit cd50e89d45db010309f8249b090ea654ebb11c7a. * +htc HttpAPIsTest is compile time only, not for running Also, moved from the client package since not strictly a client test. SecurityDirectives passing Two faulty tests and two actual bugs. Fix for cache condition spec not working * Not sending in Unit instad of the implicit magnet in the test * HeaderMagnet now works as expected * Java API added for - and + on DateTime PetStore example and test fixed * Annotations to make marshalling work without default constructor * Made model class immutable Incorrect tests fixed Some scaladoc boyscouting as bonus * =htt RequestValTest sprinkled out across multiple directive tests Client ip extraction test with incorrect header name fixed. * =htt Incorrect CodingDirectivesTest fixed. * =htt Bugfix for Java Unmarshaller.firstOf and fixes to JavaRouteTest * =htt MarshallerTest fixed * Missing seal signature added to JavaDSL * More consistent (with Scala) test kit setup for Java * missing Javadocs added * Thread.sleep in default exception handler removed * =htt copy directive docs, prepare for finishing it up * +htt SecurityDirectives.authorize variants and test coverage added * +htt Custom headers in Java DSL * =htt WIP on java docs * +htp add missing parameterOrDefault directive Fixed a lot of doc warnings * =htc intense progress on javadsl docs * =htc #20470 Link to issue about docs and fix compile error compile, migration guide don't mima check http-experimental * =htt Java DSL doc warnings fixed. Only `Could not lex literal_block` ones left now * =htc fix mima settings * =doc fix MethodDirectives doc test with custom method * =htc fix coding directives spec after bad merge * =htc fix concat being corresponding to route() in javadsl * =htt Disable consistency check for route/concat as it fails only on ci server * !htt Minor fixes to PathMatchers
544 lines
18 KiB
Scala
544 lines
18 KiB
Scala
/*
|
|
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
|
*/
|
|
|
|
package docs.http.scaladsl
|
|
|
|
import akka.event.LoggingAdapter
|
|
import akka.http.scaladsl.Http.ServerBinding
|
|
import akka.http.scaladsl.model._
|
|
import akka.stream.ActorMaterializer
|
|
import akka.stream.scaladsl.{ Flow, 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.server.Directives._
|
|
import akka.stream.ActorMaterializer
|
|
|
|
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:
|
|
import akka.http.scaladsl.Http
|
|
import akka.actor.ActorSystem
|
|
val handleConnections: Sink[Http.IncomingConnection, Future[Http.ServerBinding]] =
|
|
Sink.ignore.mapMaterializedValue(_ => Future.failed(new Exception("")))
|
|
|
|
"binding-failure-handling" in compileOnlySpec {
|
|
implicit val system = ActorSystem()
|
|
implicit val materializer = ActorMaterializer()
|
|
// 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.http.scaladsl.model.HttpEntity
|
|
import akka.http.scaladsl.model.ContentTypes
|
|
import akka.http.scaladsl.server.Directives._
|
|
import akka.stream.ActorMaterializer
|
|
import scala.io.StdIn
|
|
|
|
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.{ ContentTypes, HttpEntity }
|
|
import akka.stream.ActorMaterializer
|
|
|
|
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 text "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 _: HttpRequest =>
|
|
HttpResponse(404, entity = "Unknown resource!")
|
|
}
|
|
|
|
val bindingFuture: Future[Http.ServerBinding] =
|
|
serverSource.to(Sink.foreach { connection =>
|
|
println("Accepted new connection from " + connection.remoteAddress)
|
|
|
|
connection handleWithSyncHandler requestHandler
|
|
// this is equivalent to
|
|
// connection handleWith { Flow[HttpRequest] map requestHandler }
|
|
}).run()
|
|
}
|
|
|
|
"low-level-server-example" in compileOnlySpec {
|
|
import akka.http.scaladsl.Http
|
|
import akka.http.scaladsl.model.HttpMethods._
|
|
import akka.http.scaladsl.model._
|
|
import akka.stream.ActorMaterializer
|
|
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 _: HttpRequest =>
|
|
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.http.scaladsl.Http
|
|
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
|
|
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.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.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.actor.Props
|
|
import scala.concurrent.duration._
|
|
import akka.util.Timeout
|
|
import akka.pattern.ask
|
|
import akka.stream.ActorMaterializer
|
|
import akka.http.scaladsl.Http
|
|
import akka.http.scaladsl.server.Directives._
|
|
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
|
|
import spray.json.DefaultJsonProtocol._
|
|
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
|
|
}
|
|
|
|
|
|
}
|