Remove Akka-HTTP sources from akka/akka, moving to akka/akka-http! (#21690)
This commit is contained in:
parent
09a6d2ede1
commit
a6a5556a8f
1155 changed files with 20 additions and 96517 deletions
|
|
@ -1,76 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.http
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import akka.stream.ActorMaterializer
|
||||
import akka.stream.scaladsl._
|
||||
import akka.http.scaladsl.Http
|
||||
import akka.http.scaladsl.Http.ServerBinding
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.unmarshalling._
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.openjdk.jmh.annotations._
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Try
|
||||
import com.typesafe.config.ConfigFactory
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||
@BenchmarkMode(Array(Mode.Throughput))
|
||||
class HttpBenchmark {
|
||||
|
||||
val config = ConfigFactory.parseString(
|
||||
"""
|
||||
akka {
|
||||
loglevel = "ERROR"
|
||||
}""".stripMargin
|
||||
).withFallback(ConfigFactory.load())
|
||||
|
||||
implicit val system = ActorSystem("HttpBenchmark", config)
|
||||
implicit val materializer = ActorMaterializer()
|
||||
|
||||
var binding: ServerBinding = _
|
||||
var request: HttpRequest = _
|
||||
var pool: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), _] = _
|
||||
|
||||
@Setup
|
||||
def setup(): Unit = {
|
||||
val route = {
|
||||
path("test") {
|
||||
get {
|
||||
complete("ok")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binding = Await.result(Http().bindAndHandle(route, "127.0.0.1", 0), 1.second)
|
||||
request = HttpRequest(uri = s"http://${binding.localAddress.getHostString}:${binding.localAddress.getPort}/test")
|
||||
pool = Http().cachedHostConnectionPool[Int](binding.localAddress.getHostString, binding.localAddress.getPort)
|
||||
}
|
||||
|
||||
@TearDown
|
||||
def shutdown(): Unit = {
|
||||
Await.ready(Http().shutdownAllConnectionPools(), 1.second)
|
||||
binding.unbind()
|
||||
Await.result(system.terminate(), 5.seconds)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
def single_request(): Unit = {
|
||||
import system.dispatcher
|
||||
val response = Await.result(Http().singleRequest(request), 1.second)
|
||||
Await.result(Unmarshal(response.entity).to[String], 1.second)
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
def single_request_pool(): Unit = {
|
||||
import system.dispatcher
|
||||
val (response, id) = Await.result(Source.single(HttpRequest(uri = "/test") -> 42).via(pool).runWith(Sink.head), 1.second)
|
||||
Await.result(Unmarshal(response.get.entity).to[String], 1.second)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.http
|
||||
|
||||
import java.util.concurrent.{ CountDownLatch, TimeUnit }
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.actor.ActorSystem
|
||||
import akka.http.impl.util.ByteStringRendering
|
||||
import akka.http.scaladsl.{ Http, HttpExt }
|
||||
import akka.http.scaladsl.Http.ServerBinding
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import akka.http.scaladsl.unmarshalling._
|
||||
import akka.stream._
|
||||
import akka.stream.TLSProtocol.{ SslTlsInbound, SslTlsOutbound }
|
||||
import akka.stream.scaladsl._
|
||||
import akka.stream.stage.{ GraphStage, GraphStageLogic }
|
||||
import akka.util.ByteString
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.openjdk.jmh.annotations._
|
||||
import org.openjdk.jmh.infra.Blackhole
|
||||
|
||||
import scala.concurrent.{ Await, Future }
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Try
|
||||
|
||||
/*
|
||||
Baseline:
|
||||
|
||||
[info] Benchmark Mode Cnt Score Error Units
|
||||
[info] HttpBlueprintBenchmark.run_10000_reqs thrpt 20 197972.659 ± 14512.694 ops/s
|
||||
*/
|
||||
@State(Scope.Benchmark)
|
||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||
@BenchmarkMode(Array(Mode.Throughput))
|
||||
class HttpBlueprintBenchmark {
|
||||
|
||||
val config = ConfigFactory.parseString(
|
||||
"""
|
||||
akka {
|
||||
loglevel = "WARNING"
|
||||
|
||||
stream.materializer {
|
||||
|
||||
# default: sync-processing-limit = 1000
|
||||
sync-processing-limit = 1000
|
||||
|
||||
# default: output-burst-limit = 10000
|
||||
output-burst-limit = 1000
|
||||
|
||||
# default: initial-input-buffer-size = 4
|
||||
initial-input-buffer-size = 4
|
||||
|
||||
# default: max-input-buffer-size = 16
|
||||
max-input-buffer-size = 16
|
||||
|
||||
}
|
||||
|
||||
http {
|
||||
# default: request-timeout = 20s
|
||||
request-timeout = infinite # disabled
|
||||
# request-timeout = 20s
|
||||
}
|
||||
}""".stripMargin
|
||||
).withFallback(ConfigFactory.load())
|
||||
|
||||
implicit val system: ActorSystem = ActorSystem("HttpBenchmark", config)
|
||||
|
||||
val materializer: ActorMaterializer = ActorMaterializer()
|
||||
val notFusingMaterializer = ActorMaterializer(materializer.settings.withAutoFusing(false))
|
||||
|
||||
val request: HttpRequest = HttpRequest()
|
||||
val requestRendered = ByteString(
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Accept: */*\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"Host: example.com\r\n" +
|
||||
"User-Agent: HTTPie/0.9.3\r\n" +
|
||||
"\r\n"
|
||||
)
|
||||
|
||||
val response: HttpResponse = HttpResponse()
|
||||
val responseRendered: ByteString = ByteString(
|
||||
s"HTTP/1.1 200 OK\r\n" +
|
||||
s"Content-Length: 0\r\n" +
|
||||
s"\r\n"
|
||||
)
|
||||
|
||||
def TCPPlacebo(requests: Int): Flow[ByteString, ByteString, NotUsed] =
|
||||
Flow.fromSinkAndSource(
|
||||
Flow[ByteString].takeWhile(it => !(it.utf8String contains "Connection: close")) to Sink.ignore,
|
||||
Source.repeat(requestRendered).take(requests)
|
||||
)
|
||||
|
||||
def layer: BidiFlow[HttpResponse, SslTlsOutbound, SslTlsInbound, HttpRequest, NotUsed] = Http().serverLayer()(materializer)
|
||||
def server(requests: Int): Flow[HttpResponse, HttpRequest, _] = layer atop TLSPlacebo() join TCPPlacebo(requests)
|
||||
|
||||
val reply = Flow[HttpRequest].map { _ => response }
|
||||
|
||||
@TearDown
|
||||
def shutdown(): Unit = {
|
||||
Await.result(system.terminate(), 5.seconds)
|
||||
}
|
||||
|
||||
val nothingHere: Flow[HttpRequest, HttpResponse, NotUsed] =
|
||||
Flow.fromSinkAndSource(Sink.cancelled, Source.empty)
|
||||
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(100000)
|
||||
def run_10000_reqs() = {
|
||||
val n = 100000
|
||||
val latch = new CountDownLatch(n)
|
||||
|
||||
val replyCountdown = reply map { x =>
|
||||
latch.countDown()
|
||||
x
|
||||
}
|
||||
server(n).joinMat(replyCountdown)(Keep.right).run()(materializer)
|
||||
|
||||
latch.await()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.http
|
||||
|
||||
import akka.http.scaladsl.model._
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.openjdk.jmh.annotations._
|
||||
|
||||
/**
|
||||
* Benchmark used to verify move to name-based extraction does not hurt preformance.
|
||||
* It does not allocate an Option thus it should be more optimal actually.
|
||||
*/
|
||||
@State(Scope.Benchmark)
|
||||
@OutputTimeUnit(TimeUnit.MICROSECONDS)
|
||||
@BenchmarkMode(Array(Mode.Throughput))
|
||||
class HttpMessageMatchingBenchmark {
|
||||
|
||||
val req = HttpRequest()
|
||||
val res = HttpResponse()
|
||||
|
||||
@Benchmark
|
||||
def res_matching: HttpResponse = {
|
||||
res match {
|
||||
case r @ HttpResponse(status, headers, entity, protocol) => r
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
def req_matching: HttpRequest = {
|
||||
req match {
|
||||
case r @ HttpRequest(method, uri, headers, entity, protocol) => r
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package akka.http
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
|
||||
import akka.Done
|
||||
import akka.actor.ActorSystem
|
||||
import akka.event.NoLogging
|
||||
import akka.http.impl.engine.parsing.{ HttpHeaderParser, HttpRequestParser }
|
||||
import akka.http.scaladsl.settings.ParserSettings
|
||||
import akka.event.NoLogging
|
||||
import akka.stream.ActorMaterializer
|
||||
import akka.stream.TLSProtocol.SessionBytes
|
||||
import akka.stream.scaladsl._
|
||||
import akka.util.ByteString
|
||||
import org.openjdk.jmh.annotations.{ OperationsPerInvocation, _ }
|
||||
import org.openjdk.jmh.infra.Blackhole
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ Await, Future }
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||
@BenchmarkMode(Array(Mode.Throughput))
|
||||
class HttpRequestParsingBenchmark {
|
||||
|
||||
implicit val system: ActorSystem = ActorSystem("HttpRequestParsingBenchmark")
|
||||
implicit val materializer = ActorMaterializer()(system)
|
||||
val parserSettings = ParserSettings(system)
|
||||
val parser = new HttpRequestParser(parserSettings, false, HttpHeaderParser(parserSettings, NoLogging)())
|
||||
val dummySession = SSLContext.getDefault.createSSLEngine.getSession
|
||||
|
||||
@Param(Array("small", "large"))
|
||||
var req: String = ""
|
||||
|
||||
def request = req match {
|
||||
case "small" => requestBytesSmall
|
||||
case "large" => requestBytesLarge
|
||||
}
|
||||
|
||||
val requestBytesSmall: SessionBytes = SessionBytes(
|
||||
dummySession,
|
||||
ByteString(
|
||||
"""|GET / HTTP/1.1
|
||||
|Accept: */*
|
||||
|Accept-Encoding: gzip, deflate
|
||||
|Connection: keep-alive
|
||||
|Host: example.com
|
||||
|User-Agent: HTTPie/0.9.3
|
||||
|
|
||||
|""".stripMargin.replaceAll("\n", "\r\n")
|
||||
)
|
||||
)
|
||||
|
||||
val requestBytesLarge: SessionBytes = SessionBytes(
|
||||
dummySession,
|
||||
ByteString(
|
||||
"""|GET /json HTTP/1.1
|
||||
|Host: server
|
||||
|User-Agent: Mozilla/5.0 (X11; Linux x86_64) Gecko/20130501 Firefox/30.0 AppleWebKit/600.00 Chrome/30.0.0000.0 Trident/10.0 Safari/600.00
|
||||
|Cookie: uid=12345678901234567890; __utma=1.1234567890.1234567890.1234567890.1234567890.12; wd=2560x1600
|
||||
|Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
|
||||
|Accept-Language: en-US,en;q=0.5
|
||||
|Connection: keep-alive
|
||||
|
|
||||
|""".stripMargin.replaceAll("\n", "\r\n")
|
||||
)
|
||||
)
|
||||
|
||||
/*
|
||||
// before:
|
||||
[info] Benchmark (req) Mode Cnt Score Error Units
|
||||
[info] HttpRequestParsingBenchmark.parse_10000_requests small thrpt 20 358 982.157 ± 93745.863 ops/s
|
||||
[info] HttpRequestParsingBenchmark.parse_10000_requests large thrpt 20 388 335.666 ± 16990.715 ops/s
|
||||
|
||||
// after:
|
||||
[info] HttpRequestParsingBenchmark.parse_10000_requests_val small thrpt 20 623 975.879 ± 6191.897 ops/s
|
||||
[info] HttpRequestParsingBenchmark.parse_10000_requests_val large thrpt 20 507 460.283 ± 4735.843 ops/s
|
||||
*/
|
||||
|
||||
val httpMessageParser = Flow.fromGraph(parser)
|
||||
|
||||
def flow(bytes: SessionBytes, n: Int): RunnableGraph[Future[Done]] =
|
||||
Source.repeat(request).take(n)
|
||||
.via(httpMessageParser)
|
||||
.toMat(Sink.ignore)(Keep.right)
|
||||
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(10000)
|
||||
def parse_10000_requests_val(blackhole: Blackhole): Unit = {
|
||||
val done = flow(requestBytesSmall, 10000).run()
|
||||
Await.ready(done, 32.days)
|
||||
}
|
||||
|
||||
@TearDown
|
||||
def shutdown(): Unit = {
|
||||
Await.result(system.terminate(), 5.seconds)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,250 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package akka.http
|
||||
|
||||
import java.util.concurrent.{ CountDownLatch, TimeUnit }
|
||||
|
||||
import akka.NotUsed
|
||||
import akka.actor.ActorSystem
|
||||
import akka.event.NoLogging
|
||||
import akka.http.impl.engine.rendering.ResponseRenderingOutput.HttpData
|
||||
import akka.http.impl.engine.rendering.{ HttpResponseRendererFactory, ResponseRenderingContext, ResponseRenderingOutput }
|
||||
import akka.http.scaladsl.Http
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.model.headers.Server
|
||||
import akka.http.scaladsl.unmarshalling.Unmarshal
|
||||
import akka.stream._
|
||||
import akka.stream.scaladsl._
|
||||
import akka.stream.stage.{ GraphStageLogic, GraphStageWithMaterializedValue, InHandler }
|
||||
import akka.util.ByteString
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.openjdk.jmh.annotations._
|
||||
import org.openjdk.jmh.infra.Blackhole
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ Await, Future }
|
||||
import scala.util.Try
|
||||
|
||||
@State(Scope.Benchmark)
|
||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
||||
@BenchmarkMode(Array(Mode.Throughput))
|
||||
class HttpResponseRenderingBenchmark extends HttpResponseRendererFactory(
|
||||
serverHeader = Some(Server("Akka HTTP 2.4.x")),
|
||||
responseHeaderSizeHint = 64,
|
||||
log = NoLogging
|
||||
) {
|
||||
|
||||
val config = ConfigFactory.parseString(
|
||||
"""
|
||||
akka {
|
||||
loglevel = "ERROR"
|
||||
}""".stripMargin
|
||||
).withFallback(ConfigFactory.load())
|
||||
|
||||
implicit val system = ActorSystem("HttpResponseRenderingBenchmark", config)
|
||||
implicit val materializer = ActorMaterializer()
|
||||
|
||||
import system.dispatcher
|
||||
|
||||
val requestRendered = ByteString(
|
||||
"GET / HTTP/1.1\r\n" +
|
||||
"Accept: */*\r\n" +
|
||||
"Accept-Encoding: gzip, deflate\r\n" +
|
||||
"Connection: keep-alive\r\n" +
|
||||
"Host: example.com\r\n" +
|
||||
"User-Agent: HTTPie/0.9.3\r\n" +
|
||||
"\r\n"
|
||||
)
|
||||
|
||||
def TCPPlacebo(requests: Int): Flow[ByteString, ByteString, NotUsed] =
|
||||
Flow.fromSinkAndSource(
|
||||
Flow[ByteString].takeWhile(it => !(it.utf8String contains "Connection: close")) to Sink.ignore,
|
||||
Source.repeat(requestRendered).take(requests)
|
||||
)
|
||||
|
||||
def TlsPlacebo = TLSPlacebo()
|
||||
|
||||
val requestRendering: Flow[HttpRequest, String, NotUsed] =
|
||||
Http()
|
||||
.clientLayer(headers.Host("blah.com"))
|
||||
.atop(TlsPlacebo)
|
||||
.join {
|
||||
Flow[ByteString].map { x ⇒
|
||||
val response = s"HTTP/1.1 200 OK\r\nContent-Length: ${x.size}\r\n\r\n"
|
||||
ByteString(response) ++ x
|
||||
}
|
||||
}
|
||||
.mapAsync(1)(response => Unmarshal(response).to[String])
|
||||
|
||||
def renderResponse: Future[String] = Source.single(HttpRequest(uri = "/foo"))
|
||||
.via(requestRendering)
|
||||
.runWith(Sink.head)
|
||||
|
||||
var request: HttpRequest = _
|
||||
var pool: Flow[(HttpRequest, Int), (Try[HttpResponse], Int), _] = _
|
||||
|
||||
@TearDown
|
||||
def shutdown(): Unit = {
|
||||
Await.ready(Http().shutdownAllConnectionPools(), 1.second)
|
||||
Await.result(system.terminate(), 5.seconds)
|
||||
}
|
||||
|
||||
/*
|
||||
[info] Benchmark Mode Cnt Score Error Units
|
||||
[info] HttpResponseRenderingBenchmark.header_date_val thrpt 20 2 704 169 260 029.906 ± 234456086114.237 ops/s
|
||||
|
||||
// def, normal time
|
||||
[info] HttpResponseRenderingBenchmark.header_date_def thrpt 20 178 297 625 609.638 ± 7429280865.659 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.response_ok_simple_val thrpt 20 1 258 119.673 ± 58399.454 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.response_ok_simple_def thrpt 20 687 576.928 ± 94813.618 ops/s
|
||||
|
||||
// clock nanos
|
||||
[info] HttpResponseRenderingBenchmark.response_ok_simple_clock thrpt 20 1 676 438.649 ± 33976.590 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.response_ok_simple_clock thrpt 40 1 199 462.263 ± 222226.304 ops/s
|
||||
|
||||
// ------
|
||||
|
||||
// before optimisig collectFirst
|
||||
[info] HttpResponseRenderingBenchmark.json_response thrpt 20 1 782 572.845 ± 16572.625 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.simple_response thrpt 20 1 611 802.216 ± 19557.151 ops/s
|
||||
|
||||
// after removing collectFirst and Option from renderHeaders
|
||||
// not much of a difference, but hey, less Option allocs
|
||||
[info] HttpResponseRenderingBenchmark.json_response thrpt 20 1 785 152.896 ± 15210.299 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.simple_response thrpt 20 1 783 800.184 ± 14938.415 ops/s
|
||||
|
||||
// -----
|
||||
|
||||
// baseline for this optimisation is the above results (after collectFirst).
|
||||
|
||||
// after introducing pre-rendered ContentType headers:
|
||||
|
||||
normal clock
|
||||
[info] HttpResponseRenderingBenchmark.json_long_raw_response thrpt 20 1738558.895 ± 159612.661 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.json_response thrpt 20 1714176.824 ± 100011.642 ops/s
|
||||
|
||||
"fast clock"
|
||||
[info] HttpResponseRenderingBenchmark.json_long_raw_response thrpt 20 1 528 632.480 ± 44934.827 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.json_response thrpt 20 1 517 383.792 ± 28256.716 ops/s
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTTP/1.1 200 OK
|
||||
* Server: Akka HTTP 2.4.x
|
||||
* Date: Tue, 26 Jul 2016 15:26:53 GMT
|
||||
* Content-Type: text/plain; charset=UTF-8
|
||||
* Content-Length: 6
|
||||
*
|
||||
* ENTITY
|
||||
*/
|
||||
val simpleResponse =
|
||||
ResponseRenderingContext(
|
||||
response = HttpResponse(
|
||||
200,
|
||||
headers = Nil,
|
||||
entity = HttpEntity("ENTITY")
|
||||
),
|
||||
requestMethod = HttpMethods.GET
|
||||
)
|
||||
|
||||
/**
|
||||
* HTTP/1.1 200 OK
|
||||
* Server: Akka HTTP 2.4.x
|
||||
* Date: Tue, 26 Jul 2016 15:26:53 GMT
|
||||
* Content-Type: application/json
|
||||
* Content-Length: 27
|
||||
*
|
||||
* {"message":"Hello, World!"}
|
||||
*/
|
||||
val jsonResponse =
|
||||
ResponseRenderingContext(
|
||||
response = HttpResponse(
|
||||
200,
|
||||
headers = Nil,
|
||||
entity = HttpEntity(ContentTypes.`application/json`, """{"message":"Hello, World!"}""")
|
||||
),
|
||||
requestMethod = HttpMethods.GET
|
||||
)
|
||||
|
||||
/**
|
||||
* HTTP/1.1 200 OK
|
||||
* Server: Akka HTTP 2.4.x
|
||||
* Date: Tue, 26 Jul 2016 15:26:53 GMT
|
||||
* Content-Type: application/json
|
||||
* Content-Length: 315
|
||||
*
|
||||
* [{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]
|
||||
*/
|
||||
val jsonLongRawResponse =
|
||||
ResponseRenderingContext(
|
||||
response = HttpResponse(
|
||||
200,
|
||||
headers = Nil,
|
||||
entity = HttpEntity(ContentTypes.`application/json`, """[{"id":4174,"randomNumber":331},{"id":51,"randomNumber":6544},{"id":4462,"randomNumber":952},{"id":2221,"randomNumber":532},{"id":9276,"randomNumber":3097},{"id":3056,"randomNumber":7293},{"id":6964,"randomNumber":620},{"id":675,"randomNumber":6601},{"id":8414,"randomNumber":6569},{"id":2753,"randomNumber":4065}]""")
|
||||
),
|
||||
requestMethod = HttpMethods.GET
|
||||
)
|
||||
|
||||
@Benchmark
|
||||
@Threads(8)
|
||||
@OperationsPerInvocation(100 * 1000)
|
||||
def simple_response(blackhole: Blackhole): Unit =
|
||||
renderToImpl(simpleResponse, blackhole, n = 100 * 1000).await()
|
||||
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(100 * 1000)
|
||||
def json_response(blackhole: Blackhole): Unit =
|
||||
renderToImpl(jsonResponse, blackhole, n = 100 * 1000).await()
|
||||
|
||||
/*
|
||||
Difference between 27 and 315 bytes long JSON is:
|
||||
|
||||
[info] Benchmark Mode Cnt Score Error Units
|
||||
[info] HttpResponseRenderingBenchmark.json_long_raw_response thrpt 20 1 932 331.049 ± 64125.621 ops/s
|
||||
[info] HttpResponseRenderingBenchmark.json_response thrpt 20 1 973 232.941 ± 18568.314 ops/s
|
||||
*/
|
||||
@Benchmark
|
||||
@OperationsPerInvocation(100 * 1000)
|
||||
def json_long_raw_response(blackhole: Blackhole): Unit =
|
||||
renderToImpl(jsonLongRawResponse, blackhole, n = 100 * 1000).await()
|
||||
|
||||
class JitSafeLatch[A](blackhole: Blackhole, n: Int) extends GraphStageWithMaterializedValue[SinkShape[A], CountDownLatch] {
|
||||
val in = Inlet[A]("JitSafeLatch.in")
|
||||
override val shape = SinkShape(in)
|
||||
|
||||
override def createLogicAndMaterializedValue(inheritedAttributes: Attributes): (GraphStageLogic, CountDownLatch) = {
|
||||
val latch = new CountDownLatch(n)
|
||||
val logic = new GraphStageLogic(shape) with InHandler {
|
||||
|
||||
override def preStart(): Unit = pull(in)
|
||||
override def onPush(): Unit = {
|
||||
if (blackhole ne null) blackhole.consume(grab(in))
|
||||
latch.countDown()
|
||||
pull(in)
|
||||
}
|
||||
|
||||
setHandler(in, this)
|
||||
}
|
||||
|
||||
(logic, latch)
|
||||
}
|
||||
}
|
||||
|
||||
def renderToImpl(ctx: ResponseRenderingContext, blackhole: Blackhole, n: Int)(implicit mat: Materializer): CountDownLatch = {
|
||||
val latch =
|
||||
(Source.repeat(ctx).take(n) ++ Source.maybe[ResponseRenderingContext]) // never send upstream completion
|
||||
.via(renderer.named("renderer"))
|
||||
.runWith(new JitSafeLatch[ResponseRenderingOutput](blackhole, n))
|
||||
|
||||
latch
|
||||
}
|
||||
|
||||
// TODO benchmark with stable override
|
||||
override def currentTimeMillis(): Long = System.currentTimeMillis()
|
||||
// override def currentTimeMillis(): Long = System.currentTimeMillis() // DateTime(2011, 8, 25, 9, 10, 29).clicks // provide a stable date for testing
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,209 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl;
|
||||
|
||||
import akka.Done;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.http.javadsl.HostConnectionPool;
|
||||
import akka.japi.Pair;
|
||||
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.stream.Materializer;
|
||||
import akka.util.ByteString;
|
||||
import scala.compat.java8.FutureConverters;
|
||||
import scala.concurrent.ExecutionContextExecutor;
|
||||
import scala.concurrent.Future;
|
||||
import akka.stream.javadsl.*;
|
||||
import akka.http.javadsl.OutgoingConnection;
|
||||
import akka.http.javadsl.Http;
|
||||
|
||||
import static akka.http.javadsl.ConnectHttp.toHost;
|
||||
import static akka.pattern.PatternsCS.*;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
//#manual-entity-consume-example-1
|
||||
import java.io.File;
|
||||
import akka.actor.ActorSystem;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.javadsl.Framing;
|
||||
import akka.http.javadsl.model.*;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import scala.util.Try;
|
||||
//#manual-entity-consume-example-1
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HttpClientExampleDocTest {
|
||||
|
||||
HttpResponse responseFromSomewhere() {
|
||||
return null;
|
||||
}
|
||||
|
||||
void manualEntityComsumeExample() {
|
||||
//#manual-entity-consume-example-1
|
||||
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final HttpResponse response = responseFromSomewhere();
|
||||
|
||||
final Function<ByteString, ByteString> transformEachLine = line -> line /* some transformation here */;
|
||||
|
||||
final int maximumFrameLength = 256;
|
||||
|
||||
response.entity().getDataBytes()
|
||||
.via(Framing.delimiter(ByteString.fromString("\n"), maximumFrameLength, FramingTruncation.ALLOW))
|
||||
.map(transformEachLine::apply)
|
||||
.runWith(FileIO.toPath(new File("/tmp/example.out").toPath()), materializer);
|
||||
//#manual-entity-consume-example-1
|
||||
}
|
||||
|
||||
private
|
||||
//#manual-entity-consume-example-2
|
||||
final class ExamplePerson {
|
||||
final String name;
|
||||
public ExamplePerson(String name) { this.name = name; }
|
||||
}
|
||||
|
||||
public ExamplePerson parse(ByteString line) {
|
||||
return new ExamplePerson(line.utf8String());
|
||||
}
|
||||
//#manual-entity-consume-example-2
|
||||
|
||||
void manualEntityConsumeExample2() {
|
||||
//#manual-entity-consume-example-2
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final HttpResponse response = responseFromSomewhere();
|
||||
|
||||
// toStrict to enforce all data be loaded into memory from the connection
|
||||
final CompletionStage<HttpEntity.Strict> strictEntity = response.entity()
|
||||
.toStrict(FiniteDuration.create(3, TimeUnit.SECONDS).toMillis(), materializer);
|
||||
|
||||
// while API remains the same to consume dataBytes, now they're in memory already:
|
||||
|
||||
final CompletionStage<ExamplePerson> person =
|
||||
strictEntity
|
||||
.thenCompose(strict ->
|
||||
strict.getDataBytes()
|
||||
.runFold(ByteString.empty(), (acc, b) -> acc.concat(b), materializer)
|
||||
.thenApply(this::parse)
|
||||
);
|
||||
|
||||
//#manual-entity-consume-example-2
|
||||
}
|
||||
|
||||
void manualEntityDiscardExample1() {
|
||||
//#manual-entity-discard-example-1
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final HttpResponse response = responseFromSomewhere();
|
||||
|
||||
final HttpMessage.DiscardedEntity discarded = response.discardEntityBytes(materializer);
|
||||
|
||||
discarded.completionStage().whenComplete((done, ex) -> {
|
||||
System.out.println("Entity discarded completely!");
|
||||
});
|
||||
//#manual-entity-discard-example-1
|
||||
}
|
||||
|
||||
void manualEntityDiscardExample2() {
|
||||
//#manual-entity-discard-example-2
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final HttpResponse response = responseFromSomewhere();
|
||||
|
||||
final CompletionStage<Done> discardingComplete = response.entity().getDataBytes().runWith(Sink.ignore(), materializer);
|
||||
|
||||
discardingComplete.whenComplete((done, ex) -> {
|
||||
System.out.println("Entity discarded completely!");
|
||||
});
|
||||
//#manual-entity-discard-example-2
|
||||
}
|
||||
|
||||
|
||||
// compile only test
|
||||
public void testConstructRequest() {
|
||||
//#outgoing-connection-example
|
||||
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Flow<HttpRequest, HttpResponse, CompletionStage<OutgoingConnection>> connectionFlow =
|
||||
Http.get(system).outgoingConnection(toHost("akka.io", 80));
|
||||
final CompletionStage<HttpResponse> responseFuture =
|
||||
Source.single(HttpRequest.create("/"))
|
||||
.via(connectionFlow)
|
||||
.runWith(Sink.<HttpResponse>head(), materializer);
|
||||
//#outgoing-connection-example
|
||||
}
|
||||
|
||||
// compile only test
|
||||
public void testHostLevelExample() {
|
||||
//#host-level-example
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
// construct a pool client flow with context type `Integer`
|
||||
final Flow<
|
||||
Pair<HttpRequest, Integer>,
|
||||
Pair<Try<HttpResponse>, Integer>,
|
||||
HostConnectionPool> poolClientFlow =
|
||||
Http.get(system).<Integer>cachedHostConnectionPool(toHost("akka.io", 80), materializer);
|
||||
|
||||
// construct a pool client flow with context type `Integer`
|
||||
|
||||
final CompletionStage<Pair<Try<HttpResponse>, Integer>> responseFuture =
|
||||
Source
|
||||
.single(Pair.create(HttpRequest.create("/"), 42))
|
||||
.via(poolClientFlow)
|
||||
.runWith(Sink.<Pair<Try<HttpResponse>, Integer>>head(), materializer);
|
||||
//#host-level-example
|
||||
}
|
||||
|
||||
// compile only test
|
||||
public void testSingleRequestExample() {
|
||||
//#single-request-example
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final CompletionStage<HttpResponse> responseFuture =
|
||||
Http.get(system)
|
||||
.singleRequest(HttpRequest.create("http://akka.io"), materializer);
|
||||
//#single-request-example
|
||||
}
|
||||
|
||||
static
|
||||
//#single-request-in-actor-example
|
||||
class Myself extends AbstractActor {
|
||||
final Http http = Http.get(context().system());
|
||||
final ExecutionContextExecutor dispatcher = context().dispatcher();
|
||||
final Materializer materializer = ActorMaterializer.create(context());
|
||||
|
||||
public Myself() {
|
||||
receive(ReceiveBuilder
|
||||
.match(String.class, url -> {
|
||||
pipe(fetch (url), dispatcher).to(self());
|
||||
}).build());
|
||||
}
|
||||
|
||||
CompletionStage<HttpResponse> fetch(String url) {
|
||||
return http.singleRequest(HttpRequest.create(url), materializer);
|
||||
}
|
||||
}
|
||||
//#single-request-in-actor-example
|
||||
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.*;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.japi.Pair;
|
||||
import akka.japi.pf.ReceiveBuilder;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import com.typesafe.sslconfig.akka.AkkaSSLConfig;
|
||||
import scala.concurrent.ExecutionContextExecutor;
|
||||
import scala.util.Try;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import static akka.http.javadsl.ConnectHttp.toHost;
|
||||
import static akka.pattern.PatternsCS.pipe;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HttpsExamplesDocTest {
|
||||
|
||||
// compile only test
|
||||
public void testConstructRequest() {
|
||||
String unsafeHost = "example.com";
|
||||
//#disable-sni-connection
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer mat = ActorMaterializer.create(system);
|
||||
final Http http = Http.get(system);
|
||||
|
||||
// WARNING: disabling SNI is a very bad idea, please don't unless you have a very good reason to.
|
||||
final AkkaSSLConfig defaultSSLConfig = AkkaSSLConfig.get(system);
|
||||
final AkkaSSLConfig badSslConfig = defaultSSLConfig
|
||||
.convertSettings(s -> s.withLoose(s.loose().withDisableSNI(true)));
|
||||
final HttpsConnectionContext badCtx = http.createClientHttpsContext(badSslConfig);
|
||||
|
||||
http.outgoingConnection(ConnectHttp.toHostHttps(unsafeHost).withCustomHttpsContext(badCtx));
|
||||
//#disable-sni-connection
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,91 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl;
|
||||
|
||||
import akka.util.ByteString;
|
||||
import org.junit.Test;
|
||||
|
||||
//#import-model
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.model.headers.*;
|
||||
|
||||
import java.util.Optional;
|
||||
//#import-model
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ModelDocTest {
|
||||
@Test
|
||||
public void testConstructRequest() {
|
||||
//#construct-request
|
||||
// construct a simple GET request to `homeUri`
|
||||
Uri homeUri = Uri.create("/home");
|
||||
HttpRequest request1 = HttpRequest.create().withUri(homeUri);
|
||||
|
||||
// construct simple GET request to "/index" using helper methods
|
||||
HttpRequest request2 = HttpRequest.GET("/index");
|
||||
|
||||
// construct simple POST request containing entity
|
||||
ByteString data = ByteString.fromString("abc");
|
||||
HttpRequest postRequest1 = HttpRequest.POST("/receive").withEntity(data);
|
||||
|
||||
// customize every detail of HTTP request
|
||||
//import HttpProtocols._
|
||||
//import MediaTypes._
|
||||
Authorization authorization = Authorization.basic("user", "pass");
|
||||
HttpRequest complexRequest =
|
||||
HttpRequest.PUT("/user")
|
||||
.withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, "abc"))
|
||||
.addHeader(authorization)
|
||||
.withProtocol(HttpProtocols.HTTP_1_0);
|
||||
//#construct-request
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConstructResponse() {
|
||||
//#construct-response
|
||||
// simple OK response without data created using the integer status code
|
||||
HttpResponse ok = HttpResponse.create().withStatus(200);
|
||||
|
||||
// 404 response created using the named StatusCode constant
|
||||
HttpResponse notFound = HttpResponse.create().withStatus(StatusCodes.NOT_FOUND);
|
||||
|
||||
// 404 response with a body explaining the error
|
||||
HttpResponse notFoundCustom =
|
||||
HttpResponse.create()
|
||||
.withStatus(404)
|
||||
.withEntity("Unfortunately, the resource couldn't be found.");
|
||||
|
||||
// A redirecting response containing an extra header
|
||||
Location locationHeader = Location.create("http://example.com/other");
|
||||
HttpResponse redirectResponse =
|
||||
HttpResponse.create()
|
||||
.withStatus(StatusCodes.FOUND)
|
||||
.addHeader(locationHeader);
|
||||
//#construct-response
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDealWithHeaders() {
|
||||
//#headers
|
||||
// create a ``Location`` header
|
||||
Location locationHeader = Location.create("http://example.com/other");
|
||||
|
||||
// create an ``Authorization`` header with HTTP Basic authentication data
|
||||
Authorization authorization = Authorization.basic("user", "pass");
|
||||
//#headers
|
||||
}
|
||||
|
||||
//#headers
|
||||
|
||||
// a method that extracts basic HTTP credentials from a request
|
||||
private Optional<BasicHttpCredentials> getCredentialsOfRequest(HttpRequest request) {
|
||||
Optional<Authorization> auth = request.getHeader(Authorization.class);
|
||||
if (auth.isPresent() && auth.get().credentials() instanceof BasicHttpCredentials)
|
||||
return Optional.of((BasicHttpCredentials) auth.get().credentials());
|
||||
else
|
||||
return Optional.empty();
|
||||
}
|
||||
//#headers
|
||||
}
|
||||
|
|
@ -1,241 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl;
|
||||
|
||||
import akka.Done;
|
||||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.Authorization;
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
import akka.http.javadsl.model.ws.WebSocketRequest;
|
||||
import akka.http.javadsl.model.ws.WebSocketUpgradeResponse;
|
||||
import akka.japi.Pair;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Keep;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class WebSocketClientExampleTest {
|
||||
|
||||
// compile only test
|
||||
public void testSingleWebSocketRequest() {
|
||||
//#single-WebSocket-request
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
Http http = Http.get(system);
|
||||
|
||||
// print each incoming text message
|
||||
// would throw exception on non strict or binary message
|
||||
final Sink<Message, CompletionStage<Done>> printSink =
|
||||
Sink.foreach((message) ->
|
||||
System.out.println("Got message: " + message.asTextMessage().getStrictText())
|
||||
);
|
||||
|
||||
// send this as a message over the WebSocket
|
||||
final Source<Message, NotUsed> helloSource =
|
||||
Source.single(TextMessage.create("hello world"));
|
||||
|
||||
// the CompletionStage<Done> is the materialized value of Sink.foreach
|
||||
// and it is completed when the stream completes
|
||||
final Flow<Message, Message, CompletionStage<Done>> flow =
|
||||
Flow.fromSinkAndSourceMat(printSink, helloSource, Keep.left());
|
||||
|
||||
final Pair<CompletionStage<WebSocketUpgradeResponse>, CompletionStage<Done>> pair =
|
||||
http.singleWebSocketRequest(
|
||||
WebSocketRequest.create("ws://echo.websocket.org"),
|
||||
flow,
|
||||
materializer
|
||||
);
|
||||
|
||||
// The first value in the pair is a CompletionStage<WebSocketUpgradeResponse> that
|
||||
// completes when the WebSocket request has connected successfully (or failed)
|
||||
final CompletionStage<Done> connected = pair.first().thenApply(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().equals(StatusCodes.SWITCHING_PROTOCOLS)) {
|
||||
return Done.getInstance();
|
||||
} else {
|
||||
throw new RuntimeException("Connection failed: " + upgrade.response().status());
|
||||
}
|
||||
});
|
||||
|
||||
// the second value is the completion of the sink from above
|
||||
// in other words, it completes when the WebSocket disconnects
|
||||
final CompletionStage<Done> closed = pair.second();
|
||||
|
||||
// in a real application you would not side effect here
|
||||
// and handle errors more carefully
|
||||
connected.thenAccept(done -> System.out.println("Connected"));
|
||||
closed.thenAccept(done -> System.out.println("Connection closed"));
|
||||
|
||||
//#single-WebSocket-request
|
||||
}
|
||||
|
||||
// compile only test
|
||||
public void halfClosedWebSocketClosingExample() {
|
||||
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
final Http http = Http.get(system);
|
||||
|
||||
//#half-closed-WebSocket-closing
|
||||
|
||||
// we may expect to be able to to just tail
|
||||
// the server websocket output like this
|
||||
final Flow<Message, Message, NotUsed> flow =
|
||||
Flow.fromSinkAndSource(
|
||||
Sink.foreach(System.out::println),
|
||||
Source.empty());
|
||||
|
||||
http.singleWebSocketRequest(
|
||||
WebSocketRequest.create("ws://example.com:8080/some/path"),
|
||||
flow,
|
||||
materializer);
|
||||
|
||||
//#half-closed-WebSocket-closing
|
||||
}
|
||||
|
||||
public void halfClosedWebSocketWorkingExample() {
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
final Http http = Http.get(system);
|
||||
|
||||
//#half-closed-WebSocket-working
|
||||
|
||||
// using Source.maybe materializes into a completable future
|
||||
// which will allow us to complete the source later
|
||||
final Flow<Message, Message, CompletableFuture<Optional<Message>>> flow =
|
||||
Flow.fromSinkAndSourceMat(
|
||||
Sink.foreach(System.out::println),
|
||||
Source.maybe(),
|
||||
Keep.right());
|
||||
|
||||
final Pair<CompletionStage<WebSocketUpgradeResponse>, CompletableFuture<Optional<Message>>> pair =
|
||||
http.singleWebSocketRequest(
|
||||
WebSocketRequest.create("ws://example.com:8080/some/path"),
|
||||
flow,
|
||||
materializer);
|
||||
|
||||
// at some later time we want to disconnect
|
||||
pair.second().complete(Optional.empty());
|
||||
//#half-closed-WebSocket-working
|
||||
}
|
||||
|
||||
public void halfClosedWebSocketFiniteWorkingExample() {
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
final Http http = Http.get(system);
|
||||
|
||||
//#half-closed-WebSocket-finite
|
||||
|
||||
// emit "one" and then "two" and then keep the source from completing
|
||||
final Source<Message, CompletableFuture<Optional<Message>>> source =
|
||||
Source.from(Arrays.<Message>asList(TextMessage.create("one"), TextMessage.create("two")))
|
||||
.concatMat(Source.maybe(), Keep.right());
|
||||
|
||||
final Flow<Message, Message, CompletableFuture<Optional<Message>>> flow =
|
||||
Flow.fromSinkAndSourceMat(
|
||||
Sink.foreach(System.out::println),
|
||||
source,
|
||||
Keep.right());
|
||||
|
||||
final Pair<CompletionStage<WebSocketUpgradeResponse>, CompletableFuture<Optional<Message>>> pair =
|
||||
http.singleWebSocketRequest(
|
||||
WebSocketRequest.create("ws://example.com:8080/some/path"),
|
||||
flow,
|
||||
materializer);
|
||||
|
||||
// at some later time we want to disconnect
|
||||
pair.second().complete(Optional.empty());
|
||||
//#half-closed-WebSocket-finite
|
||||
}
|
||||
|
||||
|
||||
|
||||
// compile time only test
|
||||
public void testAuthorizedSingleWebSocketRequest() {
|
||||
Materializer materializer = null;
|
||||
Http http = null;
|
||||
|
||||
Flow<Message, Message, NotUsed> flow = null;
|
||||
|
||||
//#authorized-single-WebSocket-request
|
||||
http.singleWebSocketRequest(
|
||||
WebSocketRequest.create("ws://example.com:8080/some/path")
|
||||
.addHeader(Authorization.basic("johan", "correcthorsebatterystaple")),
|
||||
flow,
|
||||
materializer);
|
||||
//#authorized-single-WebSocket-request
|
||||
}
|
||||
|
||||
// compile time only test
|
||||
public void testWebSocketClientFlow() {
|
||||
//#WebSocket-client-flow
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
Http http = Http.get(system);
|
||||
|
||||
// print each incoming text message
|
||||
// would throw exception on non strict or binary message
|
||||
Sink<Message, CompletionStage<Done>> printSink =
|
||||
Sink.foreach((message) ->
|
||||
System.out.println("Got message: " + message.asTextMessage().getStrictText())
|
||||
);
|
||||
|
||||
// send this as a message over the WebSocket
|
||||
Source<Message, NotUsed> helloSource =
|
||||
Source.single(TextMessage.create("hello world"));
|
||||
|
||||
|
||||
Flow<Message, Message, CompletionStage<WebSocketUpgradeResponse>> webSocketFlow =
|
||||
http.webSocketClientFlow(WebSocketRequest.create("ws://echo.websocket.org"));
|
||||
|
||||
|
||||
Pair<CompletionStage<WebSocketUpgradeResponse>, CompletionStage<Done>> pair =
|
||||
helloSource.viaMat(webSocketFlow, Keep.right())
|
||||
.toMat(printSink, Keep.both())
|
||||
.run(materializer);
|
||||
|
||||
|
||||
// The first value in the pair is a CompletionStage<WebSocketUpgradeResponse> that
|
||||
// completes when the WebSocket request has connected successfully (or failed)
|
||||
CompletionStage<WebSocketUpgradeResponse> upgradeCompletion = pair.first();
|
||||
|
||||
// the second value is the completion of the sink from above
|
||||
// in other words, it completes when the WebSocket disconnects
|
||||
CompletionStage<Done> closed = pair.second();
|
||||
|
||||
CompletionStage<Done> connected = upgradeCompletion.thenApply(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().equals(StatusCodes.SWITCHING_PROTOCOLS)) {
|
||||
return Done.getInstance();
|
||||
} else {
|
||||
throw new RuntimeException(("Connection failed: " + upgrade.response().status()));
|
||||
}
|
||||
});
|
||||
|
||||
// in a real application you would not side effect here
|
||||
// and handle errors more carefully
|
||||
connected.thenAccept(done -> System.out.println("Connected"));
|
||||
closed.thenAccept(done -> System.out.println("Connection closed"));
|
||||
|
||||
//#WebSocket-client-flow
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.FormData;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.unmarshalling.StringUnmarshallers;
|
||||
import akka.http.javadsl.unmarshalling.StringUnmarshaller;
|
||||
import akka.http.javadsl.unmarshalling.Unmarshaller;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.japi.Pair;
|
||||
|
||||
public class FormFieldRequestValsExampleTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testFormFieldVals() {
|
||||
//#simple
|
||||
|
||||
final Route route =
|
||||
formField("name", n ->
|
||||
formField(StringUnmarshallers.INTEGER, "age", a ->
|
||||
complete(String.format("Name: %s, age: %d", n, a))
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
final FormData formData = FormData.create(
|
||||
Pair.create("name", "Blippy"),
|
||||
Pair.create("age", "42"));
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.POST("/")
|
||||
.withEntity(formData.toEntity());
|
||||
testRoute(route).run(request).assertEntity("Name: Blippy, age: 42");
|
||||
|
||||
//#simple
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormFieldValsUnmarshaling() {
|
||||
//#custom-unmarshal
|
||||
Unmarshaller<String, SampleId> SAMPLE_ID = StringUnmarshaller.sync(s -> new SampleId(Integer.valueOf(s)));
|
||||
|
||||
final Route route =
|
||||
formField(SAMPLE_ID, "id", sid ->
|
||||
complete(String.format("SampleId: %s", sid.id))
|
||||
);
|
||||
|
||||
// tests:
|
||||
final FormData formData = FormData.create(Pair.create("id", "1337"));
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.POST("/")
|
||||
.withEntity(formData.toEntity());
|
||||
testRoute(route).run(request).assertEntity("SampleId: 1337");
|
||||
|
||||
//#custom-unmarshal
|
||||
}
|
||||
|
||||
static class SampleId {
|
||||
public final int id;
|
||||
|
||||
SampleId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.model.headers.RawHeader;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
|
||||
public class HeaderRequestValsExampleTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testHeaderVals() {
|
||||
//#by-class
|
||||
|
||||
final Route route =
|
||||
extractHost(host ->
|
||||
complete(String.format("Host header was: %s", host))
|
||||
);
|
||||
|
||||
// tests:
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"));
|
||||
testRoute(route).run(request).assertEntity("Host header was: akka.io");
|
||||
|
||||
//#by-class
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderByName() {
|
||||
//#by-name
|
||||
|
||||
final Route route =
|
||||
// extract the `value` of the header:
|
||||
headerValueByName("X-Fish-Name", xFishName ->
|
||||
complete(String.format("The `X-Fish-Name` header's value was: %s", xFishName))
|
||||
);
|
||||
|
||||
// tests:
|
||||
final HttpRequest request =
|
||||
HttpRequest
|
||||
.GET("/")
|
||||
.addHeader(RawHeader.create("X-Fish-Name", "Blippy"));
|
||||
testRoute(route).run(request).assertEntity("The `X-Fish-Name` header's value was: Blippy");
|
||||
|
||||
//#by-name
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
//#binding-failure-high-level-example
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
public class HighLevelServerBindFailureExample {
|
||||
public static void main(String[] args) throws IOException {
|
||||
// boot up server using the route as defined below
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
// HttpApp.bindRoute expects a route being provided by HttpApp.createRoute
|
||||
final HighLevelServerExample app = new HighLevelServerExample();
|
||||
final Route route = app.createRoute();
|
||||
|
||||
final Flow<HttpRequest, HttpResponse, NotUsed> handler = route.flow(system, materializer);
|
||||
final CompletionStage<ServerBinding> binding = Http.get(system).bindAndHandle(handler, ConnectHttp.toHost("127.0.0.1", 8080), materializer);
|
||||
|
||||
binding.exceptionally(failure -> {
|
||||
System.err.println("Something very bad happened! " + failure.getMessage());
|
||||
system.terminate();
|
||||
return null;
|
||||
});
|
||||
|
||||
system.terminate();
|
||||
}
|
||||
}
|
||||
//#binding-failure-high-level-example
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
//#high-level-server-example
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.ContentTypes;
|
||||
import akka.http.javadsl.model.HttpEntities;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.server.AllDirectives;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
public class HighLevelServerExample extends AllDirectives {
|
||||
public static void main(String[] args) throws IOException {
|
||||
// boot up server using the route as defined below
|
||||
ActorSystem system = ActorSystem.create();
|
||||
|
||||
// HttpApp.bindRoute expects a route being provided by HttpApp.createRoute
|
||||
final HighLevelServerExample app = new HighLevelServerExample();
|
||||
|
||||
final Http http = Http.get(system);
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = app.createRoute().flow(system, materializer);
|
||||
final CompletionStage<ServerBinding> binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost("localhost", 8080), materializer);
|
||||
|
||||
System.out.println("Type RETURN to exit");
|
||||
System.in.read();
|
||||
|
||||
binding
|
||||
.thenCompose(ServerBinding::unbind)
|
||||
.thenAccept(unbound -> system.terminate());
|
||||
}
|
||||
|
||||
public Route createRoute() {
|
||||
// This handler generates responses to `/hello?name=XXX` requests
|
||||
Route helloRoute =
|
||||
parameterOptional("name", optName -> {
|
||||
String name = optName.orElse("Mister X");
|
||||
return complete("Hello " + name + "!");
|
||||
});
|
||||
|
||||
return
|
||||
// here the complete behavior for this server is defined
|
||||
|
||||
// only handle GET requests
|
||||
get(() -> route(
|
||||
// matches the empty path
|
||||
pathSingleSlash(() ->
|
||||
// return a constant string with a certain content type
|
||||
complete(HttpEntities.create(ContentTypes.TEXT_HTML_UTF8, "<html><body>Hello world!</body></html>"))
|
||||
),
|
||||
path("ping", () ->
|
||||
// return a simple `text/plain` response
|
||||
complete("PONG!")
|
||||
),
|
||||
path("hello", () ->
|
||||
// uses the route defined above
|
||||
helloRoute
|
||||
)
|
||||
));
|
||||
}
|
||||
}
|
||||
//#high-level-server-example
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.scaladsl.model.headers.Authorization;
|
||||
|
||||
public class HttpBasicAuthenticatorExample extends JUnitRouteTest {
|
||||
|
||||
private final String hardcodedPassword = "correcthorsebatterystaple";
|
||||
|
||||
private Optional<String> authenticate(Optional<ProvidedCredentials> creds) {
|
||||
// this is where your actual authentication logic would go
|
||||
return creds
|
||||
.filter(c -> c.verify(hardcodedPassword)) // Only allow users that provide the right password
|
||||
.map(c -> c.identifier()); // Provide the username down to the inner route
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicAuthenticator() {
|
||||
//#basic-authenticator-java
|
||||
|
||||
final Route route =
|
||||
authenticateBasic("My realm", this::authenticate, user ->
|
||||
complete("Hello " + user + "!")
|
||||
);
|
||||
|
||||
// tests:
|
||||
final HttpRequest okRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.basic("randal", "correcthorsebatterystaple"));
|
||||
testRoute(route).run(okRequest).assertEntity("Hello randal!");
|
||||
|
||||
final HttpRequest badRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.basic("randal", "123abc"));
|
||||
testRoute(route).run(badRequest).assertStatusCode(401);
|
||||
|
||||
//#basic-authenticator-java
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.Done;
|
||||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.IncomingConnection;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.marshallers.jackson.Jackson;
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.model.headers.Connection;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.unmarshalling.Unmarshaller;
|
||||
import akka.japi.function.Function;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.IOResult;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.FileIO;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.util.ByteString;
|
||||
import scala.concurrent.ExecutionContextExecutor;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static akka.http.javadsl.server.Directives.*;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HttpServerExampleDocTest {
|
||||
|
||||
public static void bindingExample() throws Exception {
|
||||
//#binding-example
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, CompletionStage<ServerBinding>> serverSource =
|
||||
Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer);
|
||||
|
||||
CompletionStage<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(connection -> {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
// ... and then actually handle the connection
|
||||
}
|
||||
)).run(materializer);
|
||||
//#binding-example
|
||||
serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static void bindingFailureExample() throws Exception {
|
||||
//#binding-failure-handling
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, CompletionStage<ServerBinding>> serverSource =
|
||||
Http.get(system).bind(ConnectHttp.toHost("localhost", 80), materializer);
|
||||
|
||||
CompletionStage<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(connection -> {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
// ... and then actually handle the connection
|
||||
}
|
||||
)).run(materializer);
|
||||
|
||||
serverBindingFuture.whenCompleteAsync((binding, failure) -> {
|
||||
// possibly report the failure somewhere...
|
||||
}, system.dispatcher());
|
||||
//#binding-failure-handling
|
||||
serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static void connectionSourceFailureExample() throws Exception {
|
||||
//#incoming-connections-source-failure-handling
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, CompletionStage<ServerBinding>> serverSource =
|
||||
Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer);
|
||||
|
||||
Flow<IncomingConnection, IncomingConnection, NotUsed> failureDetection =
|
||||
Flow.of(IncomingConnection.class).watchTermination((notUsed, termination) -> {
|
||||
termination.whenComplete((done, cause) -> {
|
||||
if (cause != null) {
|
||||
// signal the failure to external monitoring service!
|
||||
}
|
||||
});
|
||||
return NotUsed.getInstance();
|
||||
});
|
||||
|
||||
CompletionStage<ServerBinding> serverBindingFuture =
|
||||
serverSource
|
||||
.via(failureDetection) // feed signals through our custom stage
|
||||
.to(Sink.foreach(connection -> {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
// ... and then actually handle the connection
|
||||
}))
|
||||
.run(materializer);
|
||||
//#incoming-connections-source-failure-handling
|
||||
serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static void connectionStreamFailureExample() throws Exception {
|
||||
//#connection-stream-failure-handling
|
||||
ActorSystem system = ActorSystem.create();
|
||||
Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, CompletionStage<ServerBinding>> serverSource =
|
||||
Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer);
|
||||
|
||||
Flow<HttpRequest, HttpRequest, NotUsed> failureDetection =
|
||||
Flow.of(HttpRequest.class)
|
||||
.watchTermination((notUsed, termination) -> {
|
||||
termination.whenComplete((done, cause) -> {
|
||||
if (cause != null) {
|
||||
// signal the failure to external monitoring service!
|
||||
}
|
||||
});
|
||||
return NotUsed.getInstance();
|
||||
});
|
||||
|
||||
Flow<HttpRequest, HttpResponse, NotUsed> httpEcho =
|
||||
Flow.of(HttpRequest.class)
|
||||
.via(failureDetection)
|
||||
.map(request -> {
|
||||
Source<ByteString, Object> bytes = request.entity().getDataBytes();
|
||||
HttpEntity.Chunked entity = HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, bytes);
|
||||
|
||||
return HttpResponse.create()
|
||||
.withEntity(entity);
|
||||
});
|
||||
|
||||
CompletionStage<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(conn -> {
|
||||
System.out.println("Accepted new connection from " + conn.remoteAddress());
|
||||
conn.handleWith(httpEcho, materializer);
|
||||
}
|
||||
)).run(materializer);
|
||||
//#connection-stream-failure-handling
|
||||
serverBindingFuture.toCompletableFuture().get(3, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static void fullServerExample() throws Exception {
|
||||
//#full-server-example
|
||||
ActorSystem system = ActorSystem.create();
|
||||
//#full-server-example
|
||||
try {
|
||||
//#full-server-example
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
Source<IncomingConnection, CompletionStage<ServerBinding>> serverSource =
|
||||
Http.get(system).bind(ConnectHttp.toHost("localhost", 8080), materializer);
|
||||
|
||||
//#request-handler
|
||||
final Function<HttpRequest, HttpResponse> requestHandler =
|
||||
new Function<HttpRequest, HttpResponse>() {
|
||||
private final HttpResponse NOT_FOUND =
|
||||
HttpResponse.create()
|
||||
.withStatus(404)
|
||||
.withEntity("Unknown resource!");
|
||||
|
||||
|
||||
@Override
|
||||
public HttpResponse apply(HttpRequest request) throws Exception {
|
||||
Uri uri = request.getUri();
|
||||
if (request.method() == HttpMethods.GET) {
|
||||
if (uri.path().equals("/")) {
|
||||
return
|
||||
HttpResponse.create()
|
||||
.withEntity(ContentTypes.TEXT_HTML_UTF8,
|
||||
"<html><body>Hello world!</body></html>");
|
||||
} else if (uri.path().equals("/hello")) {
|
||||
String name = uri.query().get("name").orElse("Mister X");
|
||||
|
||||
return
|
||||
HttpResponse.create()
|
||||
.withEntity("Hello " + name + "!");
|
||||
} else if (uri.path().equals("/ping")) {
|
||||
return HttpResponse.create().withEntity("PONG!");
|
||||
} else {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
} else {
|
||||
return NOT_FOUND;
|
||||
}
|
||||
}
|
||||
};
|
||||
//#request-handler
|
||||
|
||||
CompletionStage<ServerBinding> serverBindingFuture =
|
||||
serverSource.to(Sink.foreach(connection -> {
|
||||
System.out.println("Accepted new connection from " + connection.remoteAddress());
|
||||
|
||||
connection.handleWithSyncHandler(requestHandler, materializer);
|
||||
// this is equivalent to
|
||||
//connection.handleWith(Flow.of(HttpRequest.class).map(requestHandler), materializer);
|
||||
})).run(materializer);
|
||||
//#full-server-example
|
||||
|
||||
serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS); // will throw if binding fails
|
||||
System.out.println("Press ENTER to stop.");
|
||||
new BufferedReader(new InputStreamReader(System.in)).readLine();
|
||||
} finally {
|
||||
system.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
fullServerExample();
|
||||
}
|
||||
|
||||
|
||||
//#consume-entity-directive
|
||||
class Bid {
|
||||
final String userId;
|
||||
final int bid;
|
||||
|
||||
Bid(String userId, int bid) {
|
||||
this.userId = userId;
|
||||
this.bid = bid;
|
||||
}
|
||||
}
|
||||
//#consume-entity-directive
|
||||
|
||||
void consumeEntityUsingEntityDirective() {
|
||||
//#consume-entity-directive
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Unmarshaller<HttpEntity, Bid> asBid = Jackson.unmarshaller(Bid.class);
|
||||
|
||||
final Route s = path("bid", () ->
|
||||
put(() ->
|
||||
entity(asBid, bid ->
|
||||
// incoming entity is fully consumed and converted into a Bid
|
||||
complete("The bid was: " + bid)
|
||||
)
|
||||
)
|
||||
);
|
||||
//#consume-entity-directive
|
||||
}
|
||||
|
||||
void consumeEntityUsingRawDataBytes() {
|
||||
//#consume-raw-dataBytes
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Route s =
|
||||
put(() ->
|
||||
path("lines", () ->
|
||||
withoutSizeLimit(() ->
|
||||
extractDataBytes(bytes -> {
|
||||
final CompletionStage<IOResult> res = bytes.runWith(FileIO.toPath(new File("/tmp/example.out").toPath()), materializer);
|
||||
|
||||
return onComplete(() -> res, ioResult ->
|
||||
// we only want to respond once the incoming data has been handled:
|
||||
complete("Finished writing data :" + ioResult));
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
//#consume-raw-dataBytes
|
||||
}
|
||||
|
||||
void discardEntityUsingRawBytes() {
|
||||
//#discard-discardEntityBytes
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Route s =
|
||||
put(() ->
|
||||
path("lines", () ->
|
||||
withoutSizeLimit(() ->
|
||||
extractRequest(r -> {
|
||||
final CompletionStage<Done> res = r.discardEntityBytes(materializer).completionStage();
|
||||
|
||||
return onComplete(() -> res, done ->
|
||||
// we only want to respond once the incoming data has been handled:
|
||||
complete("Finished writing data :" + done));
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
//#discard-discardEntityBytes
|
||||
}
|
||||
|
||||
void discardEntityManuallyCloseConnections() {
|
||||
//#discard-close-connections
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ExecutionContextExecutor dispatcher = system.dispatcher();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Route s =
|
||||
put(() ->
|
||||
path("lines", () ->
|
||||
withoutSizeLimit(() ->
|
||||
extractDataBytes(bytes -> {
|
||||
// Closing connections, method 1 (eager):
|
||||
// we deem this request as illegal, and close the connection right away:
|
||||
bytes.runWith(Sink.cancelled(), materializer); // "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:
|
||||
return respondWithHeader(Connection.create("close"), () ->
|
||||
complete(StatusCodes.FORBIDDEN, "Not allowed!")
|
||||
);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
//#discard-close-connections
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import com.typesafe.sslconfig.akka.AkkaSSLConfig;
|
||||
import org.junit.Test;
|
||||
import org.scalatest.junit.JUnitSuite;
|
||||
|
||||
/* COMPILE ONLY TEST */
|
||||
public class HttpsServerExampleTest extends JUnitSuite {
|
||||
|
||||
@Test
|
||||
public void compileOnlySpec() throws Exception {
|
||||
// just making sure for it to be really compiled / run even if empty
|
||||
}
|
||||
|
||||
void sslConfigGet() {
|
||||
//#akka-ssl-config
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
|
||||
final AkkaSSLConfig sslConfig = AkkaSSLConfig.get(system);
|
||||
//#
|
||||
}
|
||||
}
|
||||
|
|
@ -1,179 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.http.javadsl.common.CsvEntityStreamingSupport;
|
||||
import akka.http.javadsl.common.JsonEntityStreamingSupport;
|
||||
import akka.http.javadsl.marshallers.jackson.Jackson;
|
||||
import akka.http.javadsl.marshalling.Marshaller;
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.model.headers.Accept;
|
||||
import akka.http.javadsl.server.*;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.javadsl.testkit.TestRoute;
|
||||
import akka.http.javadsl.unmarshalling.StringUnmarshallers;
|
||||
import akka.http.javadsl.common.EntityStreamingSupport;
|
||||
import akka.http.javadsl.unmarshalling.Unmarshaller;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
public class JsonStreamingExamplesTest extends JUnitRouteTest {
|
||||
|
||||
//#routes
|
||||
final Route tweets() {
|
||||
//#formats
|
||||
final Unmarshaller<ByteString, JavaTweet> JavaTweets = Jackson.byteStringUnmarshaller(JavaTweet.class);
|
||||
//#formats
|
||||
|
||||
//#response-streaming
|
||||
|
||||
// Step 1: Enable JSON streaming
|
||||
// we're not using this in the example, but it's the simplest way to start:
|
||||
// The default rendering is a JSON array: `[el, el, el , ...]`
|
||||
final JsonEntityStreamingSupport jsonStreaming = EntityStreamingSupport.json();
|
||||
|
||||
// Step 1.1: Enable and customise how we'll render the JSON, as a compact array:
|
||||
final ByteString start = ByteString.fromString("[");
|
||||
final ByteString between = ByteString.fromString(",");
|
||||
final ByteString end = ByteString.fromString("]");
|
||||
final Flow<ByteString, ByteString, NotUsed> compactArrayRendering =
|
||||
Flow.of(ByteString.class).intersperse(start, between, end);
|
||||
|
||||
final JsonEntityStreamingSupport compactJsonSupport = EntityStreamingSupport.json()
|
||||
.withFramingRendererFlow(compactArrayRendering);
|
||||
|
||||
|
||||
// Step 2: implement the route
|
||||
final Route responseStreaming = path("tweets", () ->
|
||||
get(() ->
|
||||
parameter(StringUnmarshallers.INTEGER, "n", n -> {
|
||||
final Source<JavaTweet, NotUsed> tws =
|
||||
Source.repeat(new JavaTweet(12, "Hello World!")).take(n);
|
||||
|
||||
// Step 3: call complete* with your source, marshaller, and stream rendering mode
|
||||
return completeOKWithSource(tws, Jackson.marshaller(), compactJsonSupport);
|
||||
})
|
||||
)
|
||||
);
|
||||
//#response-streaming
|
||||
|
||||
//#incoming-request-streaming
|
||||
final Route incomingStreaming = path("tweets", () ->
|
||||
post(() ->
|
||||
extractMaterializer(mat -> {
|
||||
final JsonEntityStreamingSupport jsonSupport = EntityStreamingSupport.json();
|
||||
|
||||
return entityAsSourceOf(JavaTweets, jsonSupport, sourceOfTweets -> {
|
||||
final CompletionStage<Integer> tweetsCount = sourceOfTweets.runFold(0, (acc, tweet) -> acc + 1, mat);
|
||||
return onComplete(tweetsCount, c -> complete("Total number of tweets: " + c));
|
||||
});
|
||||
}
|
||||
)
|
||||
)
|
||||
);
|
||||
//#incoming-request-streaming
|
||||
|
||||
return responseStreaming.orElse(incomingStreaming);
|
||||
}
|
||||
|
||||
final Route csvTweets() {
|
||||
//#csv-example
|
||||
final Marshaller<JavaTweet, ByteString> renderAsCsv =
|
||||
Marshaller.withFixedContentType(ContentTypes.TEXT_CSV_UTF8, t ->
|
||||
ByteString.fromString(t.getId() + "," + t.getMessage())
|
||||
);
|
||||
|
||||
final CsvEntityStreamingSupport compactJsonSupport = EntityStreamingSupport.csv();
|
||||
|
||||
final Route responseStreaming = path("tweets", () ->
|
||||
get(() ->
|
||||
parameter(StringUnmarshallers.INTEGER, "n", n -> {
|
||||
final Source<JavaTweet, NotUsed> tws =
|
||||
Source.repeat(new JavaTweet(12, "Hello World!")).take(n);
|
||||
return completeWithSource(tws, renderAsCsv, compactJsonSupport);
|
||||
})
|
||||
)
|
||||
);
|
||||
//#csv-example
|
||||
|
||||
return responseStreaming;
|
||||
}
|
||||
//#routes
|
||||
|
||||
@Test
|
||||
public void getTweetsTest() {
|
||||
//#response-streaming
|
||||
// tests:
|
||||
final TestRoute routes = testRoute(tweets());
|
||||
|
||||
// test happy path
|
||||
final Accept acceptApplication = Accept.create(MediaRanges.create(MediaTypes.APPLICATION_JSON));
|
||||
routes.run(HttpRequest.GET("/tweets?n=2").addHeader(acceptApplication))
|
||||
.assertStatusCode(200)
|
||||
.assertEntity("[{\"id\":12,\"message\":\"Hello World!\"},{\"id\":12,\"message\":\"Hello World!\"}]");
|
||||
|
||||
// test responses to potential errors
|
||||
final Accept acceptText = Accept.create(MediaRanges.ALL_TEXT);
|
||||
routes.run(HttpRequest.GET("/tweets?n=3").addHeader(acceptText))
|
||||
.assertStatusCode(StatusCodes.NOT_ACCEPTABLE) // 406
|
||||
.assertEntity("Resource representation is only available with these types:\napplication/json");
|
||||
//#response-streaming
|
||||
}
|
||||
|
||||
@Test
|
||||
public void csvExampleTweetsTest() {
|
||||
//#response-streaming
|
||||
// tests --------------------------------------------
|
||||
final TestRoute routes = testRoute(csvTweets());
|
||||
|
||||
// test happy path
|
||||
final Accept acceptCsv = Accept.create(MediaRanges.create(MediaTypes.TEXT_CSV));
|
||||
routes.run(HttpRequest.GET("/tweets?n=2").addHeader(acceptCsv))
|
||||
.assertStatusCode(200)
|
||||
.assertEntity("12,Hello World!\n" +
|
||||
"12,Hello World!");
|
||||
|
||||
// test responses to potential errors
|
||||
final Accept acceptText = Accept.create(MediaRanges.ALL_APPLICATION);
|
||||
routes.run(HttpRequest.GET("/tweets?n=3").addHeader(acceptText))
|
||||
.assertStatusCode(StatusCodes.NOT_ACCEPTABLE) // 406
|
||||
.assertEntity("Resource representation is only available with these types:\ntext/csv; charset=UTF-8");
|
||||
//#response-streaming
|
||||
}
|
||||
|
||||
//#models
|
||||
private static final class JavaTweet {
|
||||
private int id;
|
||||
private String message;
|
||||
|
||||
public JavaTweet(int id, String message) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(int id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
//#models
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.scaladsl.model.headers.Authorization;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class OAuth2AuthenticatorExample extends JUnitRouteTest {
|
||||
|
||||
private final String hardcodedToken = "token";
|
||||
|
||||
private Optional<String> authenticate(Optional<ProvidedCredentials> creds) {
|
||||
// this is where your actual authentication logic would go, looking up the user
|
||||
// based on the token or something in that direction
|
||||
|
||||
// We will not allow anonymous access.
|
||||
return creds
|
||||
.filter(c -> c.verify(hardcodedToken)) //
|
||||
.map(c -> c.identifier()); // Provide the "identifier" down to the inner route
|
||||
// (for OAuth2, that's actually just the token)
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOAuth2Authenticator() {
|
||||
//#oauth2-authenticator-java
|
||||
final Route route =
|
||||
authenticateOAuth2("My realm", this::authenticate, token ->
|
||||
complete("The secret token is: " + token)
|
||||
);
|
||||
|
||||
|
||||
// tests:
|
||||
final HttpRequest okRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.oauth2("token"));
|
||||
testRoute(route).run(okRequest).assertEntity("The secret token is: token");
|
||||
|
||||
final HttpRequest badRequest =
|
||||
HttpRequest
|
||||
.GET("http://akka.io/")
|
||||
.addHeader(Host.create("akka.io"))
|
||||
.addHeader(Authorization.oauth2("wrong"));
|
||||
testRoute(route).run(badRequest).assertStatusCode(401);
|
||||
|
||||
//#oauth2-authenticator-java
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.PathMatchers;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class PathDirectiveExampleTest extends JUnitRouteTest {
|
||||
@Test
|
||||
public void testPathPrefix() {
|
||||
//#path-examples
|
||||
// matches "/test"
|
||||
path("test", () ->
|
||||
complete(StatusCodes.OK)
|
||||
);
|
||||
|
||||
// matches "/test", as well
|
||||
path(PathMatchers.segment("test"), () ->
|
||||
complete(StatusCodes.OK)
|
||||
);
|
||||
|
||||
// matches "/admin/user"
|
||||
path(PathMatchers.segment("admin")
|
||||
.slash("user"), () ->
|
||||
complete(StatusCodes.OK)
|
||||
);
|
||||
|
||||
// matches "/admin/user", as well
|
||||
pathPrefix("admin", () ->
|
||||
path("user", () ->
|
||||
complete(StatusCodes.OK)
|
||||
)
|
||||
);
|
||||
|
||||
// matches "/admin/user/<user-id>"
|
||||
path(PathMatchers.segment("admin")
|
||||
.slash("user")
|
||||
.slash(PathMatchers.integerSegment()), userId -> {
|
||||
return complete("Hello user " + userId);
|
||||
}
|
||||
);
|
||||
|
||||
// matches "/admin/user/<user-id>", as well
|
||||
pathPrefix("admin", () ->
|
||||
path("user", () ->
|
||||
path(PathMatchers.integerSegment(), userId ->
|
||||
complete("Hello user " + userId)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// never matches
|
||||
path("admin", () -> // oops this only matches "/admin", and no sub-paths
|
||||
path("user", () ->
|
||||
complete(StatusCodes.OK)
|
||||
)
|
||||
);
|
||||
|
||||
// matches "/user/" with the first subroute, "/user" (without a trailing slash)
|
||||
// with the second subroute, and "/user/<user-id>" with the last one.
|
||||
pathPrefix("user", () -> route(
|
||||
pathSingleSlash(() ->
|
||||
complete(StatusCodes.OK)
|
||||
),
|
||||
pathEnd(() ->
|
||||
complete(StatusCodes.OK)
|
||||
),
|
||||
path(PathMatchers.integerSegment(), userId ->
|
||||
complete("Hello user " + userId)
|
||||
)
|
||||
));
|
||||
//#path-examples
|
||||
}
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
//#websocket-example-using-core
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.japi.Function;
|
||||
import akka.japi.JavaPartialFunction;
|
||||
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Source;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
import akka.http.javadsl.model.ws.WebSocket;
|
||||
|
||||
@SuppressWarnings("Convert2MethodRef")
|
||||
public class WebSocketCoreExample {
|
||||
|
||||
//#websocket-handling
|
||||
public static HttpResponse handleRequest(HttpRequest request) {
|
||||
System.out.println("Handling request to " + request.getUri());
|
||||
|
||||
if (request.getUri().path().equals("/greeter")) {
|
||||
final Flow<Message, Message, NotUsed> greeterFlow = greeter();
|
||||
return WebSocket.handleWebSocketRequestWith(request, greeterFlow);
|
||||
} else {
|
||||
return HttpResponse.create().withStatus(404);
|
||||
}
|
||||
}
|
||||
//#websocket-handling
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
ActorSystem system = ActorSystem.create();
|
||||
|
||||
try {
|
||||
final Materializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final Function<HttpRequest, HttpResponse> handler = request -> handleRequest(request);
|
||||
CompletionStage<ServerBinding> serverBindingFuture =
|
||||
Http.get(system).bindAndHandleSync(
|
||||
handler, ConnectHttp.toHost("localhost", 8080), materializer);
|
||||
|
||||
// will throw if binding fails
|
||||
serverBindingFuture.toCompletableFuture().get(1, TimeUnit.SECONDS);
|
||||
System.out.println("Press ENTER to stop.");
|
||||
new BufferedReader(new InputStreamReader(System.in)).readLine();
|
||||
} finally {
|
||||
system.terminate();
|
||||
}
|
||||
}
|
||||
|
||||
//#websocket-handler
|
||||
|
||||
/**
|
||||
* A handler that treats incoming messages as a name,
|
||||
* and responds with a greeting to that name
|
||||
*/
|
||||
public static Flow<Message, Message, NotUsed> greeter() {
|
||||
return
|
||||
Flow.<Message>create()
|
||||
.collect(new JavaPartialFunction<Message, Message>() {
|
||||
@Override
|
||||
public Message apply(Message msg, boolean isCheck) throws Exception {
|
||||
if (isCheck) {
|
||||
if (msg.isText()) {
|
||||
return null;
|
||||
} else {
|
||||
throw noMatch();
|
||||
}
|
||||
} else {
|
||||
return handleTextMessage(msg.asTextMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static TextMessage handleTextMessage(TextMessage msg) {
|
||||
if (msg.isStrict()) // optimization that directly creates a simple response...
|
||||
{
|
||||
return TextMessage.create("Hello " + msg.getStrictText());
|
||||
} else // ... this would suffice to handle all text messages in a streaming fashion
|
||||
{
|
||||
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
|
||||
}
|
||||
}
|
||||
//#websocket-handler
|
||||
}
|
||||
//#websocket-example-using-core
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server;
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.http.javadsl.server.AllDirectives;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.japi.JavaPartialFunction;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
|
||||
public class WebSocketRoutingExample extends AllDirectives {
|
||||
|
||||
//#websocket-route
|
||||
public Route createRoute() {
|
||||
return
|
||||
path("greeter", () ->
|
||||
handleWebSocketMessages(greeter())
|
||||
);
|
||||
}
|
||||
//#websocket-route
|
||||
|
||||
/**
|
||||
* A handler that treats incoming messages as a name,
|
||||
* and responds with a greeting to that name
|
||||
*/
|
||||
public static Flow<Message, Message, NotUsed> greeter() {
|
||||
return
|
||||
Flow.<Message>create()
|
||||
.collect(new JavaPartialFunction<Message, Message>() {
|
||||
@Override
|
||||
public Message apply(Message msg, boolean isCheck) throws Exception {
|
||||
if (isCheck) {
|
||||
if (msg.isText()) return null;
|
||||
else throw noMatch();
|
||||
} else return handleTextMessage(msg.asTextMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static TextMessage handleTextMessage(TextMessage msg) {
|
||||
if (msg.isStrict()) // optimization that directly creates a simple response...
|
||||
{
|
||||
return TextMessage.create("Hello " + msg.getStrictText());
|
||||
} else // ... this would suffice to handle all text messages in a streaming fashion
|
||||
{
|
||||
return TextMessage.create(Source.single("Hello ").concat(msg.getStreamedText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,893 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.dispatch.ExecutionContexts;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.http.javadsl.model.ContentTypes;
|
||||
import akka.http.javadsl.model.HttpEntities;
|
||||
import akka.http.javadsl.model.HttpEntity;
|
||||
import akka.http.javadsl.model.HttpMethods;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.ResponseEntity;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.RawHeader;
|
||||
import akka.http.javadsl.model.headers.Server;
|
||||
import akka.http.javadsl.model.headers.ProductVersion;
|
||||
import akka.http.javadsl.settings.RoutingSettings;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.javadsl.server.*;
|
||||
import akka.japi.pf.PFBuilder;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.ActorMaterializerSettings;
|
||||
import akka.stream.javadsl.FileIO;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.ExecutionContextExecutor;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
public class BasicDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testExtract() {
|
||||
//#extract
|
||||
final Route route = extract(
|
||||
ctx -> ctx.getRequest().getUri().toString().length(),
|
||||
len -> complete("The length of the request URI is " + len)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/abcdef"))
|
||||
.assertEntity("The length of the request URI is 25");
|
||||
//#extract
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractLog() {
|
||||
//#extractLog
|
||||
final Route route = extractLog(log -> {
|
||||
log.debug("I'm logging things in much detail..!");
|
||||
return complete("It's amazing!");
|
||||
});
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/abcdef"))
|
||||
.assertEntity("It's amazing!");
|
||||
//#extractLog
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithMaterializer() {
|
||||
//#withMaterializer
|
||||
final ActorMaterializerSettings settings = ActorMaterializerSettings.create(system());
|
||||
final ActorMaterializer special = ActorMaterializer.create(settings, system(), "special");
|
||||
|
||||
final Route sample = path("sample", () ->
|
||||
extractMaterializer(mat ->
|
||||
onSuccess(() ->
|
||||
// explicitly use the materializer:
|
||||
Source.single("Materialized by " + mat.hashCode() + "!")
|
||||
.runWith(Sink.head(), mat), this::complete
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
final Route route = route(
|
||||
pathPrefix("special", () ->
|
||||
withMaterializer(special, () -> sample) // `special` materializer will be used
|
||||
),
|
||||
sample // default materializer will be used
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/sample"))
|
||||
.assertEntity("Materialized by " + materializer().hashCode()+ "!");
|
||||
testRoute(route).run(HttpRequest.GET("/special/sample"))
|
||||
.assertEntity("Materialized by " + special.hashCode()+ "!");
|
||||
//#withMaterializer
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractMaterializer() {
|
||||
//#extractMaterializer
|
||||
final Route route = path("sample", () ->
|
||||
extractMaterializer(mat ->
|
||||
onSuccess(() ->
|
||||
// explicitly use the materializer:
|
||||
Source.single("Materialized by " + mat.hashCode() + "!")
|
||||
.runWith(Sink.head(), mat), this::complete
|
||||
)
|
||||
)
|
||||
); // default materializer will be used
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/sample"))
|
||||
.assertEntity("Materialized by " + materializer().hashCode()+ "!");
|
||||
//#extractMaterializer
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithExecutionContext() {
|
||||
//#withExecutionContext
|
||||
|
||||
final ExecutionContextExecutor special =
|
||||
ExecutionContexts.fromExecutor(Executors.newFixedThreadPool(1));
|
||||
|
||||
final Route sample = path("sample", () ->
|
||||
extractExecutionContext(executor ->
|
||||
onSuccess(() ->
|
||||
CompletableFuture.supplyAsync(() ->
|
||||
"Run on " + executor.hashCode() + "!", executor
|
||||
), this::complete
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
final Route route = route(
|
||||
pathPrefix("special", () ->
|
||||
// `special` execution context will be used
|
||||
withExecutionContext(special, () -> sample)
|
||||
),
|
||||
sample // default execution context will be used
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/sample"))
|
||||
.assertEntity("Run on " + system().dispatcher().hashCode() + "!");
|
||||
testRoute(route).run(HttpRequest.GET("/special/sample"))
|
||||
.assertEntity("Run on " + special.hashCode() + "!");
|
||||
//#withExecutionContext
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractExecutionContext() {
|
||||
//#extractExecutionContext
|
||||
final Route route = path("sample", () ->
|
||||
extractExecutionContext(executor ->
|
||||
onSuccess(() ->
|
||||
CompletableFuture.supplyAsync(
|
||||
// uses the `executor` ExecutionContext
|
||||
() -> "Run on " + executor.hashCode() + "!", executor
|
||||
), str -> complete(str)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
//tests:
|
||||
testRoute(route).run(HttpRequest.GET("/sample"))
|
||||
.assertEntity("Run on " + system().dispatcher().hashCode() + "!");
|
||||
//#extractExecutionContext
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithLog() {
|
||||
//#withLog
|
||||
final LoggingAdapter special = Logging.getLogger(system(), "SpecialRoutes");
|
||||
|
||||
final Route sample = path("sample", () ->
|
||||
extractLog(log -> {
|
||||
final String msg = "Logging using " + log + "!";
|
||||
log.debug(msg);
|
||||
return complete(msg);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
final Route route = route(
|
||||
pathPrefix("special", () ->
|
||||
withLog(special, () -> sample)
|
||||
),
|
||||
sample
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/sample"))
|
||||
.assertEntity("Logging using " + system().log() + "!");
|
||||
testRoute(route).run(HttpRequest.GET("/special/sample"))
|
||||
.assertEntity("Logging using " + special + "!");
|
||||
//#withLog
|
||||
}
|
||||
|
||||
@Ignore("Ignore compile-only test")
|
||||
@Test
|
||||
public void testWithSettings() {
|
||||
//#withSettings
|
||||
final RoutingSettings special =
|
||||
RoutingSettings
|
||||
.create(system().settings().config())
|
||||
.withFileIODispatcher("special-io-dispatcher");
|
||||
|
||||
final Route sample = path("sample", () -> {
|
||||
// internally uses the configured fileIODispatcher:
|
||||
// ContentTypes.APPLICATION_JSON, source
|
||||
final Source<ByteString, Object> source =
|
||||
FileIO.fromPath(Paths.get("example.json"))
|
||||
.mapMaterializedValue(completionStage -> (Object) completionStage);
|
||||
return complete(
|
||||
HttpResponse.create()
|
||||
.withEntity(HttpEntities.create(ContentTypes.APPLICATION_JSON, source))
|
||||
);
|
||||
});
|
||||
|
||||
final Route route = get(() ->
|
||||
route(
|
||||
pathPrefix("special", () ->
|
||||
// `special` file-io-dispatcher will be used to read the file
|
||||
withSettings(special, () -> sample)
|
||||
),
|
||||
sample // default file-io-dispatcher will be used to read the file
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/special/sample"))
|
||||
.assertEntity("{}");
|
||||
testRoute(route).run(HttpRequest.GET("/sample"))
|
||||
.assertEntity("{}");
|
||||
//#withSettings
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapResponse() {
|
||||
//#mapResponse
|
||||
final Route route = mapResponse(
|
||||
response -> response.withStatus(StatusCodes.BAD_GATEWAY),
|
||||
() -> complete("abc")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/abcdef?ghi=12"))
|
||||
.assertStatusCode(StatusCodes.BAD_GATEWAY);
|
||||
//#mapResponse
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapResponseAdvanced() {
|
||||
//#mapResponse-advanced
|
||||
class ApiRoute {
|
||||
|
||||
private final ActorSystem system;
|
||||
|
||||
private final LoggingAdapter log;
|
||||
|
||||
private final HttpEntity nullJsonEntity =
|
||||
HttpEntities.create(ContentTypes.APPLICATION_JSON, "{}");
|
||||
|
||||
public ApiRoute(ActorSystem system) {
|
||||
this.system = system;
|
||||
this.log = Logging.getLogger(system, "ApiRoutes");
|
||||
}
|
||||
|
||||
private HttpResponse nonSuccessToEmptyJsonEntity(HttpResponse response) {
|
||||
if (response.status().isSuccess()) {
|
||||
return response;
|
||||
} else {
|
||||
log.warning(
|
||||
"Dropping response entity since response status code was: " + response.status());
|
||||
return response.withEntity((ResponseEntity) nullJsonEntity);
|
||||
}
|
||||
}
|
||||
|
||||
/** Wrapper for all of our JSON API routes */
|
||||
private Route apiRoute(Supplier<Route> innerRoutes) {
|
||||
return mapResponse(this::nonSuccessToEmptyJsonEntity, innerRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
final ApiRoute api = new ApiRoute(system());
|
||||
|
||||
final Route route = api.apiRoute(() ->
|
||||
get(() -> complete(StatusCodes.INTERNAL_SERVER_ERROR))
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("{}");
|
||||
//#mapResponse-advanced
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRouteResult() {
|
||||
//#mapRouteResult
|
||||
// this directive is a joke, don't do that :-)
|
||||
final Route route = mapRouteResult(r -> {
|
||||
if (r instanceof Complete) {
|
||||
final HttpResponse response = ((Complete) r).getResponse();
|
||||
return RouteResults.complete(response.withStatus(200));
|
||||
} else {
|
||||
return r;
|
||||
}
|
||||
}, () -> complete(StatusCodes.ACCEPTED));
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#mapRouteResult
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRouteResultFuture() {
|
||||
//#mapRouteResultFuture
|
||||
final Route route = mapRouteResultFuture(cr ->
|
||||
cr.exceptionally(t -> {
|
||||
if (t instanceof IllegalArgumentException) {
|
||||
return RouteResults.complete(
|
||||
HttpResponse.create().withStatus(StatusCodes.INTERNAL_SERVER_ERROR));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}).thenApply(rr -> {
|
||||
if (rr instanceof Complete) {
|
||||
final HttpResponse res = ((Complete) rr).getResponse();
|
||||
return RouteResults.complete(
|
||||
res.addHeader(Server.create(ProductVersion.create("MyServer", "1.0"))));
|
||||
} else {
|
||||
return rr;
|
||||
}
|
||||
}), () -> complete("Hello world!"));
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertHeaderExists(Server.create(ProductVersion.create("MyServer", "1.0")));
|
||||
//#mapRouteResultFuture
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapResponseEntity() {
|
||||
//#mapResponseEntity
|
||||
final Function<ResponseEntity, ResponseEntity> prefixEntity = entity -> {
|
||||
if (entity instanceof HttpEntity.Strict) {
|
||||
final HttpEntity.Strict strict = (HttpEntity.Strict) entity;
|
||||
return HttpEntities.create(
|
||||
strict.getContentType(),
|
||||
ByteString.fromString("test").concat(strict.getData()));
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected entity type");
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = mapResponseEntity(prefixEntity, () -> complete("abc"));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("testabc");
|
||||
//#mapResponseEntity
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapResponseHeaders() {
|
||||
//#mapResponseHeaders
|
||||
// adds all request headers to the response
|
||||
final Route echoRequestHeaders = extract(
|
||||
ctx -> ctx.getRequest().getHeaders(),
|
||||
headers -> respondWithHeaders(headers, () -> complete("test"))
|
||||
);
|
||||
|
||||
final Route route = mapResponseHeaders(headers -> {
|
||||
headers.removeIf(header -> header.lowercaseName().equals("id"));
|
||||
return headers;
|
||||
}, () -> echoRequestHeaders);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeaders(
|
||||
Arrays.asList(RawHeader.create("id", "12345"),RawHeader.create("id2", "67890"))))
|
||||
.assertHeaderKindNotExists("id")
|
||||
.assertHeaderExists("id2", "67890");
|
||||
//#mapResponseHeaders
|
||||
}
|
||||
|
||||
@Ignore("Not implemented yet")
|
||||
@Test
|
||||
public void testMapInnerRoute() {
|
||||
//#mapInnerRoute
|
||||
// TODO: implement mapInnerRoute
|
||||
//#mapInnerRoute
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRejections() {
|
||||
//#mapRejections
|
||||
// ignore any rejections and replace them by AuthorizationFailedRejection
|
||||
final Route route = mapRejections(
|
||||
rejections -> Collections.singletonList((Rejection) Rejections.authorizationFailed()),
|
||||
() -> path("abc", () -> complete("abc"))
|
||||
);
|
||||
|
||||
// tests:
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections(Rejections.authorizationFailed());
|
||||
testRoute(route).run(HttpRequest.GET("/abc"))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#mapRejections
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverRejections() {
|
||||
//#recoverRejections
|
||||
final Function<Optional<ProvidedCredentials>, Optional<Object>> neverAuth =
|
||||
creds -> Optional.empty();
|
||||
final Function<Optional<ProvidedCredentials>, Optional<Object>> alwaysAuth =
|
||||
creds -> Optional.of("id");
|
||||
|
||||
final Route originalRoute = pathPrefix("auth", () ->
|
||||
route(
|
||||
path("never", () ->
|
||||
authenticateBasic("my-realm", neverAuth, obj -> complete("Welcome to the bat-cave!"))
|
||||
),
|
||||
path("always", () ->
|
||||
authenticateBasic("my-realm", alwaysAuth, obj -> complete("Welcome to the secret place!"))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
final Function<Iterable<Rejection>, Boolean> existsAuthenticationFailedRejection =
|
||||
rejections ->
|
||||
StreamSupport.stream(rejections.spliterator(), false)
|
||||
.anyMatch(r -> r instanceof AuthenticationFailedRejection);
|
||||
|
||||
final Route route = recoverRejections(rejections -> {
|
||||
if (existsAuthenticationFailedRejection.apply(rejections)) {
|
||||
return RouteResults.complete(
|
||||
HttpResponse.create().withEntity("Nothing to see here, move along."));
|
||||
} else if (!rejections.iterator().hasNext()) { // see "Empty Rejections" for more details
|
||||
return RouteResults.complete(
|
||||
HttpResponse.create().withStatus(StatusCodes.NOT_FOUND)
|
||||
.withEntity("Literally nothing to see here."));
|
||||
} else {
|
||||
return RouteResults.rejected(rejections);
|
||||
}
|
||||
}, () -> originalRoute);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/auth/never"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("Nothing to see here, move along.");
|
||||
testRoute(route).run(HttpRequest.GET("/auth/always"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("Welcome to the secret place!");
|
||||
testRoute(route).run(HttpRequest.GET("/auth/does_not_exist"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND)
|
||||
.assertEntity("Literally nothing to see here.");
|
||||
//#recoverRejections
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecoverRejectionsWith() {
|
||||
//#recoverRejectionsWith
|
||||
final Function<Optional<ProvidedCredentials>, Optional<Object>> neverAuth =
|
||||
creds -> Optional.empty();
|
||||
|
||||
final Route originalRoute = pathPrefix("auth", () ->
|
||||
path("never", () ->
|
||||
authenticateBasic("my-realm", neverAuth, obj -> complete("Welcome to the bat-cave!"))
|
||||
)
|
||||
);
|
||||
|
||||
final Function<Iterable<Rejection>, Boolean> existsAuthenticationFailedRejection =
|
||||
rejections ->
|
||||
StreamSupport.stream(rejections.spliterator(), false)
|
||||
.anyMatch(r -> r instanceof AuthenticationFailedRejection);
|
||||
|
||||
final Route route = recoverRejectionsWith(
|
||||
rejections -> CompletableFuture.supplyAsync(() -> {
|
||||
if (existsAuthenticationFailedRejection.apply(rejections)) {
|
||||
return RouteResults.complete(
|
||||
HttpResponse.create().withEntity("Nothing to see here, move along."));
|
||||
} else {
|
||||
return RouteResults.rejected(rejections);
|
||||
}
|
||||
}), () -> originalRoute);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/auth/never"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("Nothing to see here, move along.");
|
||||
//#recoverRejectionsWith
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRequest() {
|
||||
//#mapRequest
|
||||
final Route route = mapRequest(req ->
|
||||
req.withMethod(HttpMethods.POST), () ->
|
||||
extractRequest(req -> complete("The request method was " + req.method().name()))
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("The request method was POST");
|
||||
//#mapRequest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRequestContext() {
|
||||
//#mapRequestContext
|
||||
final Route route = mapRequestContext(ctx ->
|
||||
ctx.withRequest(HttpRequest.create().withMethod(HttpMethods.POST)), () ->
|
||||
extractRequest(req -> complete(req.method().value()))
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/abc/def/ghi"))
|
||||
.assertEntity("POST");
|
||||
//#mapRequestContext
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRouteResult0() {
|
||||
//#mapRouteResult
|
||||
final Route route = mapRouteResult(rr -> {
|
||||
final Iterable<Rejection> rejections = Collections.singletonList(Rejections.authorizationFailed());
|
||||
return RouteResults.rejected(rejections);
|
||||
}, () -> complete("abc"));
|
||||
|
||||
// tests:
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections(Rejections.authorizationFailed());
|
||||
//#mapRouteResult
|
||||
}
|
||||
|
||||
public static final class MyCustomRejection implements CustomRejection {}
|
||||
|
||||
@Test
|
||||
public void testMapRouteResultPF() {
|
||||
//#mapRouteResultPF
|
||||
final Route route = mapRouteResultPF(
|
||||
new PFBuilder<RouteResult, RouteResult>()
|
||||
.match(Rejected.class, rejected -> {
|
||||
final Iterable<Rejection> rejections =
|
||||
Collections.singletonList(Rejections.authorizationFailed());
|
||||
return RouteResults.rejected(rejections);
|
||||
}).build(), () -> reject(new MyCustomRejection()));
|
||||
|
||||
// tests:
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections(Rejections.authorizationFailed());
|
||||
//#mapRouteResultPF
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRouteResultWithPF() {
|
||||
//#mapRouteResultWithPF
|
||||
final Route route = mapRouteResultWithPF(
|
||||
new PFBuilder<RouteResult, CompletionStage<RouteResult>>()
|
||||
.match(Rejected.class, rejected -> CompletableFuture.supplyAsync(() -> {
|
||||
final Iterable<Rejection> rejections =
|
||||
Collections.singletonList(Rejections.authorizationFailed());
|
||||
return RouteResults.rejected(rejections);
|
||||
})
|
||||
).build(), () -> reject(new MyCustomRejection()));
|
||||
|
||||
// tests:
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections(Rejections.authorizationFailed());
|
||||
//#mapRouteResultWithPF
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapRouteResultWith() {
|
||||
//#mapRouteResultWith
|
||||
final Route route = mapRouteResultWith(rr -> CompletableFuture.supplyAsync(() -> {
|
||||
if (rr instanceof Rejected) {
|
||||
final Iterable<Rejection> rejections =
|
||||
Collections.singletonList(Rejections.authorizationFailed());
|
||||
return RouteResults.rejected(rejections);
|
||||
} else {
|
||||
return rr;
|
||||
}
|
||||
}), () -> reject(new MyCustomRejection()));
|
||||
|
||||
// tests:
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections(Rejections.authorizationFailed());
|
||||
//#mapRouteResultWith
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPass() {
|
||||
//#pass
|
||||
final Route route = pass(() -> complete("abc"));
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("abc");
|
||||
//#pass
|
||||
}
|
||||
|
||||
private Route providePrefixedStringRoute(String value) {
|
||||
return provide("prefix:" + value, this::complete);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvide() {
|
||||
//#provide
|
||||
final Route route = providePrefixedStringRoute("test");
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("prefix:test");
|
||||
//#provide
|
||||
}
|
||||
|
||||
@Ignore("Test failed")
|
||||
@Test
|
||||
public void testCancelRejections() {
|
||||
//#cancelRejections
|
||||
final Predicate<Rejection> isMethodRejection = p -> p instanceof MethodRejection;
|
||||
final Route route = cancelRejections(
|
||||
isMethodRejection, () -> post(() -> complete("Result"))
|
||||
);
|
||||
|
||||
// tests:
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections();
|
||||
//#cancelRejections
|
||||
}
|
||||
|
||||
@Ignore("Test failed")
|
||||
@Test
|
||||
public void testCancelRejection() {
|
||||
//#cancelRejection
|
||||
final Route route = cancelRejection(Rejections.method(HttpMethods.POST), () ->
|
||||
post(() -> complete("Result"))
|
||||
);
|
||||
|
||||
// tests:
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections();
|
||||
//#cancelRejection
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractRequest() {
|
||||
//#extractRequest
|
||||
final Route route = extractRequest(request ->
|
||||
complete("Request method is " + request.method().name() +
|
||||
" and content-type is " + request.entity().getContentType())
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity("text"))
|
||||
.assertEntity("Request method is POST and content-type is text/plain; charset=UTF-8");
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("Request method is GET and content-type is none/none");
|
||||
//#extractRequest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractSettings() {
|
||||
//#extractSettings
|
||||
final Route route = extractSettings(settings ->
|
||||
complete("RoutingSettings.renderVanityFooter = " + settings.getRenderVanityFooter())
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("RoutingSettings.renderVanityFooter = true");
|
||||
//#extractSettings
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapSettings() {
|
||||
//#mapSettings
|
||||
final Route route = mapSettings(settings ->
|
||||
settings.withFileGetConditional(false), () ->
|
||||
extractSettings(settings ->
|
||||
complete("RoutingSettings.fileGetConditional = " + settings.getFileGetConditional())
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("RoutingSettings.fileGetConditional = false");
|
||||
//#mapSettings
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractRequestContext() {
|
||||
//#extractRequestContext
|
||||
final Route route = extractRequestContext(ctx -> {
|
||||
ctx.getLog().debug("Using access to additional context availablethings, like the logger.");
|
||||
final HttpRequest request = ctx.getRequest();
|
||||
return complete("Request method is " + request.method().name() +
|
||||
" and content-type is " + request.entity().getContentType());
|
||||
});
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity("text"))
|
||||
.assertEntity("Request method is POST and content-type is text/plain; charset=UTF-8");
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("Request method is GET and content-type is none/none");
|
||||
//#extractRequestContext
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractUri() {
|
||||
//#extractUri
|
||||
final Route route = extractUri(uri ->
|
||||
complete("Full URI: " + uri)
|
||||
);
|
||||
|
||||
// tests:
|
||||
// tests are executed with the host assumed to be "example.com"
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("Full URI: http://example.com/");
|
||||
testRoute(route).run(HttpRequest.GET("/test"))
|
||||
.assertEntity("Full URI: http://example.com/test");
|
||||
//#extractUri
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMapUnmatchedPath() {
|
||||
//#mapUnmatchedPath
|
||||
final Function<String, String> ignore456 = path -> {
|
||||
int slashPos = path.indexOf("/");
|
||||
if (slashPos != -1) {
|
||||
String head = path.substring(0, slashPos);
|
||||
String tail = path.substring(slashPos);
|
||||
if (head.length() <= 3) {
|
||||
return tail;
|
||||
} else {
|
||||
return path.substring(3);
|
||||
}
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = pathPrefix("123", () ->
|
||||
mapUnmatchedPath(ignore456, () ->
|
||||
path("abc", () ->
|
||||
complete("Content")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/123/abc"))
|
||||
.assertEntity("Content");
|
||||
testRoute(route).run(HttpRequest.GET("/123456/abc"))
|
||||
.assertEntity("Content");
|
||||
//#mapUnmatchedPath
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractUnmatchedPath() {
|
||||
//#extractUnmatchedPath
|
||||
final Route route = pathPrefix("abc", () ->
|
||||
extractUnmatchedPath(remaining ->
|
||||
complete("Unmatched: '" + remaining + "'")
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/abc"))
|
||||
.assertEntity("Unmatched: ''");
|
||||
testRoute(route).run(HttpRequest.GET("/abc/456"))
|
||||
.assertEntity("Unmatched: '/456'");
|
||||
//#extractUnmatchedPath
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractRequestEntity() {
|
||||
//#extractRequestEntity
|
||||
final Route route = extractRequestEntity(entity ->
|
||||
complete("Request entity content-type is " + entity.getContentType())
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("/abc")
|
||||
.withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, "req"))
|
||||
).assertEntity("Request entity content-type is text/plain; charset=UTF-8");
|
||||
//#extractRequestEntity
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractDataBytes() {
|
||||
//#extractDataBytes
|
||||
final Route route = extractDataBytes(data -> {
|
||||
final CompletionStage<Integer> sum = data.runFold(0, (acc, i) ->
|
||||
acc + Integer.valueOf(i.utf8String()), materializer());
|
||||
return onSuccess(() -> sum, s ->
|
||||
complete(HttpResponse.create().withEntity(HttpEntities.create(s.toString()))));
|
||||
});
|
||||
|
||||
// tests:
|
||||
final Iterator iterator = Arrays.asList(
|
||||
ByteString.fromString("1"),
|
||||
ByteString.fromString("2"),
|
||||
ByteString.fromString("3")).iterator();
|
||||
final Source<ByteString, NotUsed> dataBytes = Source.fromIterator(() -> iterator);
|
||||
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("abc")
|
||||
.withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, dataBytes))
|
||||
).assertEntity("6");
|
||||
//#extractDataBytes
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractStrictEntity() {
|
||||
//#extractStrictEntity
|
||||
final FiniteDuration timeout = FiniteDuration.create(3, TimeUnit.SECONDS);
|
||||
final Route route = extractStrictEntity(timeout, strict ->
|
||||
complete(strict.getData().utf8String())
|
||||
);
|
||||
|
||||
// tests:
|
||||
final Iterator iterator = Arrays.asList(
|
||||
ByteString.fromString("1"),
|
||||
ByteString.fromString("2"),
|
||||
ByteString.fromString("3")).iterator();
|
||||
final Source<ByteString, NotUsed> dataBytes = Source.fromIterator(() -> iterator);
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("/")
|
||||
.withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, dataBytes))
|
||||
).assertEntity("123");
|
||||
//#extractStrictEntity
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStrictEntity() {
|
||||
//#toStrictEntity
|
||||
final FiniteDuration timeout = FiniteDuration.create(3, TimeUnit.SECONDS);
|
||||
final Route route = toStrictEntity(timeout, () ->
|
||||
extractRequest(req -> {
|
||||
if (req.entity() instanceof HttpEntity.Strict) {
|
||||
final HttpEntity.Strict strict = (HttpEntity.Strict)req.entity();
|
||||
return complete("Request entity is strict, data=" + strict.getData().utf8String());
|
||||
} else {
|
||||
return complete("Ooops, request entity is not strict!");
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// tests:
|
||||
final Iterator iterator = Arrays.asList(
|
||||
ByteString.fromString("1"),
|
||||
ByteString.fromString("2"),
|
||||
ByteString.fromString("3")).iterator();
|
||||
final Source<ByteString, NotUsed> dataBytes = Source.fromIterator(() -> iterator);
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("/")
|
||||
.withEntity(HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8, dataBytes))
|
||||
).assertEntity("Request entity is strict, data=123");
|
||||
//#toStrictEntity
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractActorSystem() {
|
||||
//#extractActorSystem
|
||||
final Route route = extractActorSystem(actorSystem ->
|
||||
complete("Actor System extracted, hash=" + actorSystem.hashCode())
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("Actor System extracted, hash=" + system().hashCode());
|
||||
//#extractActorSystem
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.AcceptEncoding;
|
||||
import akka.http.javadsl.model.headers.ContentEncoding;
|
||||
import akka.http.javadsl.model.headers.HttpEncodings;
|
||||
import akka.http.javadsl.coding.Coder;
|
||||
import akka.http.javadsl.server.Rejections;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static akka.http.javadsl.unmarshalling.Unmarshaller.entityToString;
|
||||
|
||||
public class CodingDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testResponseEncodingAccepted() {
|
||||
//#responseEncodingAccepted
|
||||
final Route route = responseEncodingAccepted(HttpEncodings.GZIP, () ->
|
||||
complete("content")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("content");
|
||||
runRouteUnSealed(route,
|
||||
HttpRequest.GET("/")
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE)))
|
||||
.assertRejections(Rejections.unacceptedResponseEncoding(HttpEncodings.GZIP));
|
||||
//#responseEncodingAccepted
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeResponse() {
|
||||
//#encodeResponse
|
||||
final Route route = encodeResponse(() -> complete("content"));
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(
|
||||
HttpRequest.GET("/")
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.GZIP))
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE))
|
||||
).assertHeaderExists(ContentEncoding.create(HttpEncodings.GZIP));
|
||||
|
||||
testRoute(route).run(
|
||||
HttpRequest.GET("/")
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE))
|
||||
).assertHeaderExists(ContentEncoding.create(HttpEncodings.DEFLATE));
|
||||
|
||||
// This case failed!
|
||||
// testRoute(route).run(
|
||||
// HttpRequest.GET("/")
|
||||
// .addHeader(AcceptEncoding.create(HttpEncodings.IDENTITY))
|
||||
// ).assertHeaderExists(ContentEncoding.create(HttpEncodings.IDENTITY));
|
||||
|
||||
//#encodeResponse
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncodeResponseWith() {
|
||||
//#encodeResponseWith
|
||||
final Route route = encodeResponseWith(
|
||||
Collections.singletonList(Coder.Gzip),
|
||||
() -> complete("content")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertHeaderExists(ContentEncoding.create(HttpEncodings.GZIP));
|
||||
|
||||
testRoute(route).run(
|
||||
HttpRequest.GET("/")
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.GZIP))
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE))
|
||||
).assertHeaderExists(ContentEncoding.create(HttpEncodings.GZIP));
|
||||
|
||||
runRouteUnSealed(route,
|
||||
HttpRequest.GET("/")
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.DEFLATE))
|
||||
).assertRejections(Rejections.unacceptedResponseEncoding(HttpEncodings.GZIP));
|
||||
|
||||
runRouteUnSealed(route,
|
||||
HttpRequest.GET("/")
|
||||
.addHeader(AcceptEncoding.create(HttpEncodings.IDENTITY))
|
||||
).assertRejections(Rejections.unacceptedResponseEncoding(HttpEncodings.GZIP));
|
||||
//#encodeResponseWith
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeRequest() {
|
||||
//#decodeRequest
|
||||
final ByteString helloGzipped = Coder.Gzip.encode(ByteString.fromString("Hello"));
|
||||
final ByteString helloDeflated = Coder.Deflate.encode(ByteString.fromString("Hello"));
|
||||
|
||||
final Route route = decodeRequest(() ->
|
||||
entity(entityToString(), content ->
|
||||
complete("Request content: '" + content + "'")
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("/").withEntity(helloGzipped)
|
||||
.addHeader(ContentEncoding.create(HttpEncodings.GZIP)))
|
||||
.assertEntity("Request content: 'Hello'");
|
||||
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("/").withEntity(helloDeflated)
|
||||
.addHeader(ContentEncoding.create(HttpEncodings.DEFLATE)))
|
||||
.assertEntity("Request content: 'Hello'");
|
||||
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("/").withEntity("hello uncompressed")
|
||||
.addHeader(ContentEncoding.create(HttpEncodings.IDENTITY)))
|
||||
.assertEntity( "Request content: 'hello uncompressed'");
|
||||
//#decodeRequest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecodeRequestWith() {
|
||||
//#decodeRequestWith
|
||||
final ByteString helloGzipped = Coder.Gzip.encode(ByteString.fromString("Hello"));
|
||||
final ByteString helloDeflated = Coder.Deflate.encode(ByteString.fromString("Hello"));
|
||||
|
||||
final Route route = decodeRequestWith(Coder.Gzip, () ->
|
||||
entity(entityToString(), content ->
|
||||
complete("Request content: '" + content + "'")
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(
|
||||
HttpRequest.POST("/").withEntity(helloGzipped)
|
||||
.addHeader(ContentEncoding.create(HttpEncodings.GZIP)))
|
||||
.assertEntity("Request content: 'Hello'");
|
||||
|
||||
runRouteUnSealed(route,
|
||||
HttpRequest.POST("/").withEntity(helloDeflated)
|
||||
.addHeader(ContentEncoding.create(HttpEncodings.DEFLATE)))
|
||||
.assertRejections(Rejections.unsupportedRequestEncoding(HttpEncodings.GZIP));
|
||||
|
||||
runRouteUnSealed(route,
|
||||
HttpRequest.POST("/").withEntity("hello")
|
||||
.addHeader(ContentEncoding.create(HttpEncodings.IDENTITY)))
|
||||
.assertRejections(Rejections.unsupportedRequestEncoding(HttpEncodings.GZIP));
|
||||
//#decodeRequestWith
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpHeader;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.Cookie;
|
||||
import akka.http.javadsl.model.headers.HttpCookie;
|
||||
import akka.http.javadsl.model.headers.SetCookie;
|
||||
import akka.http.javadsl.server.Rejections;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.scaladsl.model.DateTime;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
public class CookieDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testCookie() {
|
||||
//#cookie
|
||||
final Route route = cookie("userName", nameCookie ->
|
||||
complete("The logged in user is '" + nameCookie.value() + "'")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Cookie.create("userName", "paul")))
|
||||
.assertEntity("The logged in user is 'paul'");
|
||||
// missing cookie
|
||||
runRouteUnSealed(route, HttpRequest.GET("/"))
|
||||
.assertRejections(Rejections.missingCookie("userName"));
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("Request is missing required cookie 'userName'");
|
||||
//#cookie
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalCookie() {
|
||||
//#optionalCookie
|
||||
final Route route = optionalCookie("userName", optNameCookie -> {
|
||||
if (optNameCookie.isPresent()) {
|
||||
return complete("The logged in user is '" + optNameCookie.get().value() + "'");
|
||||
} else {
|
||||
return complete("No user logged in");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Cookie.create("userName", "paul")))
|
||||
.assertEntity("The logged in user is 'paul'");
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("No user logged in");
|
||||
//#optionalCookie
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteCookie() {
|
||||
//#deleteCookie
|
||||
final Route route = deleteCookie("userName", () ->
|
||||
complete("The user was logged out")
|
||||
);
|
||||
|
||||
// tests:
|
||||
final HttpHeader expected = SetCookie.create(
|
||||
HttpCookie.create(
|
||||
"userName",
|
||||
"deleted",
|
||||
Optional.of(DateTime.MinValue()),
|
||||
OptionalLong.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
false,
|
||||
false,
|
||||
Optional.empty()));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("The user was logged out")
|
||||
.assertHeaderExists(expected);
|
||||
//#deleteCookie
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetCookie() {
|
||||
//#setCookie
|
||||
final Route route = setCookie(HttpCookie.create("userName", "paul"), () ->
|
||||
complete("The user was logged in")
|
||||
);
|
||||
|
||||
// tests:
|
||||
final HttpHeader expected = SetCookie.create(HttpCookie.create("userName", "paul"));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("The user was logged in")
|
||||
.assertHeaderExists(expected);
|
||||
//#setCookie
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,126 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.RawHeader;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class CustomDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
//#labeling-1
|
||||
public Route getOrPut(Supplier<Route> inner) {
|
||||
return get(inner).orElse(put(inner));
|
||||
}
|
||||
//#
|
||||
|
||||
@Test
|
||||
public void testLabeling() {
|
||||
// tests:
|
||||
|
||||
//#labeling-2
|
||||
Route route = getOrPut(() -> complete("ok"));
|
||||
//#
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
|
||||
testRoute(route).run(HttpRequest.PUT("/"))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static class MyCredentials {
|
||||
private final String userId;
|
||||
private final String secret;
|
||||
|
||||
public MyCredentials(String userId, String secret) {
|
||||
this.userId = userId;
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public boolean safeSecretVerification(String correct) {
|
||||
// of course this is not what you would do in a real app
|
||||
return correct.equals(secret);
|
||||
}
|
||||
|
||||
}
|
||||
public static enum MyRole {
|
||||
USER,
|
||||
ADMIN
|
||||
}
|
||||
|
||||
//#composition-1
|
||||
// the composed custom directive
|
||||
/**
|
||||
* @param authenticate A function returns a set of roles for the credentials of a user
|
||||
* @param inner Inner route to execute if the provided credentials has the given role
|
||||
* if not, the request is completed with a
|
||||
*/
|
||||
public Route headerBasedAuth(Function<MyCredentials, Set<MyRole>> authenticate, MyRole requiredRole, Supplier<Route> inner) {
|
||||
return headerValueByName("X-My-User-Id", (userId) -> {
|
||||
return headerValueByName("X-My-User-Secret", (secret) -> {
|
||||
Set<MyRole> userRoles = authenticate.apply(new MyCredentials(userId, secret));
|
||||
if (userRoles.contains(requiredRole)) {
|
||||
return inner.get();
|
||||
} else {
|
||||
return complete(StatusCodes.FORBIDDEN, "Role " + requiredRole + " required for access");
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
//#
|
||||
|
||||
@Test
|
||||
public void testComposition() {
|
||||
// tests:
|
||||
|
||||
//#composition-2
|
||||
// a function for authentication
|
||||
Function<MyCredentials, Set<MyRole>> authLogic =
|
||||
(credentials) -> {
|
||||
if (credentials.userId.equals("admin") && credentials.safeSecretVerification("secret"))
|
||||
return new HashSet<>(Arrays.asList(MyRole.USER, MyRole.ADMIN));
|
||||
else
|
||||
return Collections.emptySet();
|
||||
};
|
||||
|
||||
// and then using the custom route
|
||||
Route route = get(() ->
|
||||
path("admin", () ->
|
||||
headerBasedAuth(authLogic, MyRole.ADMIN, () -> complete(StatusCodes.OK, "admin stuff"))
|
||||
)
|
||||
);
|
||||
//#
|
||||
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/admin"))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/admin").addHeaders(
|
||||
Arrays.asList(RawHeader.create("X-My-User-Id", "user"), RawHeader.create("X-My-User-Secret", "wrong"))))
|
||||
.assertStatusCode(StatusCodes.FORBIDDEN);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/admin").addHeaders(
|
||||
Arrays.asList(RawHeader.create("X-My-User-Id", "admin"), RawHeader.create("X-My-User-Secret", "secret"))))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.event.NoLogging;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.settings.ParserSettings;
|
||||
import akka.http.javadsl.settings.ServerSettings;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.stream.Materializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import static akka.http.javadsl.model.HttpProtocols.HTTP_1_0;
|
||||
import static akka.http.javadsl.model.RequestEntityAcceptances.Expected;
|
||||
|
||||
public class CustomHttpMethodExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testComposition() throws InterruptedException, ExecutionException {
|
||||
ActorSystem system = system();
|
||||
Materializer materializer = materializer();
|
||||
LoggingAdapter loggingAdapter = NoLogging.getInstance();
|
||||
|
||||
int port = 9090;
|
||||
String host = "127.0.0.1";
|
||||
|
||||
//#customHttpMethod
|
||||
HttpMethod BOLT =
|
||||
HttpMethods.createCustom("BOLT", false, true, Expected);
|
||||
final ParserSettings parserSettings =
|
||||
ParserSettings.create(system).withCustomMethods(BOLT);
|
||||
final ServerSettings serverSettings =
|
||||
ServerSettings.create(system).withParserSettings(parserSettings);
|
||||
|
||||
final Route routes = route(
|
||||
extractMethod( method ->
|
||||
complete( "This is a " + method.name() + " request.")
|
||||
)
|
||||
);
|
||||
final Flow<HttpRequest, HttpResponse, NotUsed> handler = routes.flow(system, materializer);
|
||||
final Http http = Http.get(system);
|
||||
final CompletionStage<ServerBinding> binding =
|
||||
http.bindAndHandle(
|
||||
handler,
|
||||
ConnectHttp.toHost(host, port),
|
||||
serverSettings,
|
||||
loggingAdapter,
|
||||
materializer);
|
||||
|
||||
HttpRequest request = HttpRequest.create()
|
||||
.withUri("http://" + host + ":" + Integer.toString(port))
|
||||
.withMethod(BOLT)
|
||||
.withProtocol(HTTP_1_0);
|
||||
|
||||
CompletionStage<HttpResponse> response = http.singleRequest(request, materializer);
|
||||
//#customHttpMethod
|
||||
|
||||
assertEquals(StatusCodes.OK, response.toCompletableFuture().get().status());
|
||||
assertEquals(
|
||||
"This is a BOLT request.",
|
||||
response.toCompletableFuture().get().entity().toStrict(3000, materializer).toCompletableFuture().get().getData().utf8String()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.RequestContext;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import akka.http.javadsl.model.Uri;
|
||||
import akka.http.javadsl.model.headers.Location;
|
||||
import akka.http.javadsl.server.directives.DebuggingDirectives;
|
||||
import akka.http.javadsl.server.directives.RouteDirectives;
|
||||
import akka.event.Logging;
|
||||
import akka.event.Logging.LogLevel;
|
||||
import akka.http.javadsl.server.directives.LogEntry;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import akka.http.javadsl.server.Rejection;
|
||||
|
||||
import static akka.event.Logging.InfoLevel;
|
||||
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.Optional;
|
||||
|
||||
public class DebuggingDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testLogRequest() {
|
||||
//#logRequest
|
||||
// logs request with "get-user"
|
||||
final Route routeBasicLogRequest = get(() ->
|
||||
logRequest("get-user", () -> complete("logged")));
|
||||
|
||||
// logs request with "get-user" as Info
|
||||
final Route routeBasicLogRequestAsInfo = get(() ->
|
||||
logRequest("get-user", InfoLevel(), () -> complete("logged")));
|
||||
|
||||
// logs just the request method at info level
|
||||
Function<HttpRequest, LogEntry> requestMethodAsInfo = (request) ->
|
||||
LogEntry.create(request.method().name(), InfoLevel());
|
||||
|
||||
final Route routeUsingFunction = get(() ->
|
||||
logRequest(requestMethodAsInfo, () -> complete("logged")));
|
||||
|
||||
// tests:
|
||||
testRoute(routeBasicLogRequest).run(HttpRequest.GET("/"))
|
||||
.assertEntity("logged");
|
||||
//#logRequest
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogRequestResult() {
|
||||
//#logRequestResult
|
||||
// using logRequestResult
|
||||
// handle request to optionally generate a log entry
|
||||
BiFunction<HttpRequest, HttpResponse, Optional<LogEntry>> requestMethodAsInfo =
|
||||
(request, response) ->
|
||||
(response.status().isSuccess()) ?
|
||||
Optional.of(
|
||||
LogEntry.create(
|
||||
request.method().name() + ":" + response.status().intValue(),
|
||||
InfoLevel()))
|
||||
: Optional.empty(); // not a successful response
|
||||
|
||||
// handle rejections to optionally generate a log entry
|
||||
BiFunction<HttpRequest, List<Rejection>, Optional<LogEntry>> rejectionsAsInfo =
|
||||
(request, rejections) ->
|
||||
(!rejections.isEmpty()) ?
|
||||
Optional.of(
|
||||
LogEntry.create(
|
||||
rejections
|
||||
.stream()
|
||||
.map(Rejection::toString)
|
||||
.collect(Collectors.joining(", ")),
|
||||
InfoLevel()))
|
||||
: Optional.empty(); // no rejections
|
||||
|
||||
final Route route = get(() -> logRequestResultOptional(
|
||||
requestMethodAsInfo,
|
||||
rejectionsAsInfo,
|
||||
() -> complete("logged")));
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity("logged");
|
||||
//#logRequestResult
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogResult() {
|
||||
//#logResult
|
||||
// logs result with "get-user"
|
||||
final Route routeBasicLogResult = get(() ->
|
||||
logResult("get-user", () -> complete("logged")));
|
||||
|
||||
// logs result with "get-user" as Info
|
||||
final Route routeBasicLogResultAsInfo = get(() ->
|
||||
logResult("get-user", InfoLevel(), () -> complete("logged")));
|
||||
|
||||
// logs the result and the rejections as LogEntry
|
||||
Function<HttpResponse, LogEntry> showSuccessAsInfo = (response) ->
|
||||
LogEntry.create(String.format("Response code '%d'", response.status().intValue()),
|
||||
InfoLevel());
|
||||
|
||||
Function<List<Rejection>, LogEntry> showRejectionAsInfo = (rejections) ->
|
||||
LogEntry.create(
|
||||
rejections
|
||||
.stream()
|
||||
.map(rejection -> rejection.toString())
|
||||
.collect(Collectors.joining(", ")),
|
||||
InfoLevel());
|
||||
|
||||
final Route routeUsingFunction = get(() ->
|
||||
logResult(showSuccessAsInfo, showRejectionAsInfo, () -> complete("logged")));
|
||||
// tests:
|
||||
testRoute(routeBasicLogResult).run(HttpRequest.GET("/"))
|
||||
.assertEntity("logged");
|
||||
//#logResult
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLogRequestResultWithResponseTime() {
|
||||
//#logRequestResultWithResponseTime
|
||||
// using logRequestResultOptional for generating Response Time
|
||||
// handle request to optionally generate a log entry
|
||||
|
||||
BiFunction<HttpRequest, HttpResponse, Optional<LogEntry>> requestMethodAsInfo =
|
||||
(request, response) -> {
|
||||
Long requestTime = System.nanoTime();
|
||||
return printResponseTime(request, response, requestTime);
|
||||
};
|
||||
|
||||
// handle rejections to optionally generate a log entry
|
||||
BiFunction<HttpRequest, List<Rejection>, Optional<LogEntry>> rejectionsAsInfo =
|
||||
(request, rejections) ->
|
||||
(!rejections.isEmpty()) ?
|
||||
Optional.of(
|
||||
LogEntry.create(
|
||||
rejections
|
||||
.stream()
|
||||
.map(Rejection::toString)
|
||||
.collect(Collectors.joining(", ")),
|
||||
InfoLevel()))
|
||||
: Optional.empty(); // no rejections
|
||||
|
||||
final Route route = get(() -> logRequestResultOptional(
|
||||
requestMethodAsInfo,
|
||||
rejectionsAsInfo,
|
||||
() -> complete("logged")));
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity("logged");
|
||||
//#logRequestResult
|
||||
}
|
||||
|
||||
// A function for the logging of Time
|
||||
public static Optional<LogEntry> printResponseTime(HttpRequest request, HttpResponse response, Long requestTime) {
|
||||
if (response.status().isSuccess()) {
|
||||
Long elapsedTime = (requestTime - System.nanoTime()) / 1000000;
|
||||
return Optional.of(
|
||||
LogEntry.create(
|
||||
"Logged Request:" + request.method().name() + ":" + request.getUri() + ":" + response.status() + ":" + elapsedTime,
|
||||
InfoLevel()));
|
||||
} else {
|
||||
return Optional.empty(); //not a successfull response
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.ExceptionHandler;
|
||||
import akka.http.javadsl.server.PathMatchers;
|
||||
import akka.http.javadsl.server.RejectionHandler;
|
||||
import akka.http.javadsl.server.Rejections;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.ValidationRejection;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import static akka.http.javadsl.server.PathMatchers.integerSegment;
|
||||
|
||||
public class ExecutionDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testHandleExceptions() {
|
||||
//#handleExceptions
|
||||
final ExceptionHandler divByZeroHandler = ExceptionHandler.newBuilder()
|
||||
.match(ArithmeticException.class, x ->
|
||||
complete(StatusCodes.BAD_REQUEST, "You've got your arithmetic wrong, fool!"))
|
||||
.build();
|
||||
|
||||
final Route route =
|
||||
path(PathMatchers.segment("divide").slash(integerSegment()).slash(integerSegment()), (a, b) ->
|
||||
handleExceptions(divByZeroHandler, () -> complete("The result is " + (a / b)))
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/divide/10/5"))
|
||||
.assertEntity("The result is 2");
|
||||
testRoute(route).run(HttpRequest.GET("/divide/10/0"))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST)
|
||||
.assertEntity("You've got your arithmetic wrong, fool!");
|
||||
//#handleExceptions
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleRejections() {
|
||||
//#handleRejections
|
||||
final RejectionHandler totallyMissingHandler = RejectionHandler.newBuilder()
|
||||
.handleNotFound(complete(StatusCodes.NOT_FOUND, "Oh man, what you are looking for is long gone."))
|
||||
.handle(ValidationRejection.class, r -> complete(StatusCodes.INTERNAL_SERVER_ERROR, r.message()))
|
||||
.build();
|
||||
|
||||
final Route route = pathPrefix("handled", () ->
|
||||
handleRejections(totallyMissingHandler, () ->
|
||||
route(
|
||||
path("existing", () -> complete("This path exists")),
|
||||
path("boom", () -> reject(Rejections.validationRejection("This didn't work.")))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/handled/existing"))
|
||||
.assertEntity("This path exists");
|
||||
// applies default handler
|
||||
testRoute(route).run(HttpRequest.GET("/missing"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND)
|
||||
.assertEntity("The requested resource could not be found.");
|
||||
testRoute(route).run(HttpRequest.GET("/handled/missing"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND)
|
||||
.assertEntity("Oh man, what you are looking for is long gone.");
|
||||
testRoute(route).run(HttpRequest.GET("/handled/boom"))
|
||||
.assertStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||
.assertEntity("This didn't work.");
|
||||
//#handleRejections
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.PathMatchers;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.directives.DirectoryRenderer;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import scala.NotImplementedError;
|
||||
|
||||
import static akka.http.javadsl.server.PathMatchers.segment;
|
||||
|
||||
public class FileAndResourceDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testGetFromFile() {
|
||||
//#getFromFile
|
||||
final Route route = path(PathMatchers.segment("logs").slash(segment()), name ->
|
||||
getFromFile(name + ".log")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/logs/example"))
|
||||
.assertEntity("example file contents");
|
||||
//#getFromFile
|
||||
}
|
||||
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testGetFromResource() {
|
||||
//#getFromResource
|
||||
final Route route = path(PathMatchers.segment("logs").slash(segment()), name ->
|
||||
getFromResource(name + ".log")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/logs/example"))
|
||||
.assertEntity("example file contents");
|
||||
//#getFromResource
|
||||
}
|
||||
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testListDirectoryContents() {
|
||||
//#listDirectoryContents
|
||||
final Route route = route(
|
||||
path("tmp", () -> listDirectoryContents("/tmp")),
|
||||
path("custom", () -> {
|
||||
// implement your custom renderer here
|
||||
final DirectoryRenderer renderer = renderVanityFooter -> {
|
||||
throw new NotImplementedError();
|
||||
};
|
||||
return listDirectoryContents(renderer, "/tmp");
|
||||
})
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/logs/example"))
|
||||
.assertEntity("example file contents");
|
||||
//#listDirectoryContents
|
||||
}
|
||||
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testGetFromBrowseableDirectory() {
|
||||
//#getFromBrowseableDirectory
|
||||
final Route route = path("tmp", () ->
|
||||
getFromBrowseableDirectory("/tmp")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/tmp"))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#getFromBrowseableDirectory
|
||||
}
|
||||
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testGetFromBrowseableDirectories() {
|
||||
//#getFromBrowseableDirectories
|
||||
final Route route = path("tmp", () ->
|
||||
getFromBrowseableDirectories("/main", "/backups")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/tmp"))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#getFromBrowseableDirectories
|
||||
}
|
||||
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testGetFromDirectory() {
|
||||
//#getFromDirectory
|
||||
final Route route = pathPrefix("tmp", () ->
|
||||
getFromDirectory("/tmp")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/tmp/example"))
|
||||
.assertEntity("example file contents");
|
||||
//#getFromDirectory
|
||||
}
|
||||
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testGetFromResourceDirectory() {
|
||||
//#getFromResourceDirectory
|
||||
final Route route = pathPrefix("examples", () ->
|
||||
getFromResourceDirectory("/examples")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/examples/example-1"))
|
||||
.assertEntity("example file contents");
|
||||
//#getFromResourceDirectory
|
||||
}
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.impl.engine.rendering.BodyPartRenderer;
|
||||
import akka.http.javadsl.model.*;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.unmarshalling.Unmarshaller;
|
||||
import akka.http.javadsl.server.directives.FileInfo;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.stream.javadsl.Framing;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class FileUploadDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testUploadedFile() {
|
||||
//#uploadedFile
|
||||
// function (FileInfo, File) => Route to process the file metadata and file itself
|
||||
BiFunction<FileInfo, File, Route> infoFileRoute =
|
||||
(info, file) -> {
|
||||
// do something with the file and file metadata ...
|
||||
file.delete();
|
||||
return complete(StatusCodes.OK);
|
||||
};
|
||||
|
||||
|
||||
final Route route = uploadedFile("csv", infoFileRoute);
|
||||
|
||||
Map<String, String> filenameMapping = new HashMap<>();
|
||||
filenameMapping.put("filename", "data.csv");
|
||||
|
||||
akka.http.javadsl.model.Multipart.FormData multipartForm =
|
||||
Multiparts.createStrictFormDataFromParts(Multiparts.createFormDataBodyPartStrict("csv",
|
||||
HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8,
|
||||
"1,5,7\n11,13,17"), filenameMapping));
|
||||
|
||||
// test:
|
||||
testRoute(route).run(HttpRequest.POST("/")
|
||||
.withEntity(
|
||||
multipartForm.toEntity(HttpCharsets.UTF_8,
|
||||
BodyPartRenderer
|
||||
.randomBoundaryWithDefaults())))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileUpload() {
|
||||
//#fileUpload
|
||||
final Route route = extractRequestContext(ctx -> {
|
||||
// function (FileInfo, Source<ByteString,Object>) => Route to process the file contents
|
||||
BiFunction<FileInfo, Source<ByteString, Object>, Route> processUploadedFile =
|
||||
(metadata, byteSource) -> {
|
||||
CompletionStage<Integer> sumF = byteSource.via(Framing.delimiter(
|
||||
ByteString.fromString("\n"), 1024))
|
||||
.mapConcat(bs -> Arrays.asList(bs.utf8String().split(",")))
|
||||
.map(s -> Integer.parseInt(s))
|
||||
.runFold(0, (acc, n) -> acc + n, ctx.getMaterializer());
|
||||
return onSuccess(() -> sumF, sum -> complete("Sum: " + sum));
|
||||
};
|
||||
return fileUpload("csv", processUploadedFile);
|
||||
});
|
||||
|
||||
Map<String, String> filenameMapping = new HashMap<>();
|
||||
filenameMapping.put("filename", "primes.csv");
|
||||
|
||||
akka.http.javadsl.model.Multipart.FormData multipartForm =
|
||||
Multiparts.createStrictFormDataFromParts(
|
||||
Multiparts.createFormDataBodyPartStrict("csv",
|
||||
HttpEntities.create(ContentTypes.TEXT_PLAIN_UTF8,
|
||||
"2,3,5\n7,11,13,17,23\n29,31,37\n"), filenameMapping));
|
||||
|
||||
// test:
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(
|
||||
multipartForm.toEntity(HttpCharsets.UTF_8, BodyPartRenderer.randomBoundaryWithDefaults())))
|
||||
.assertStatusCode(StatusCodes.OK).assertEntityAs(Unmarshaller.entityToString(), "Sum: 178");
|
||||
//#
|
||||
}
|
||||
|
||||
@Ignore("compileOnly")
|
||||
@Test
|
||||
public void testFileProcessing() {
|
||||
//#fileProcessing
|
||||
final Route route = extractRequestContext(ctx -> {
|
||||
// function (FileInfo, Source<ByteString,Object>) => Route to process the file contents
|
||||
BiFunction<FileInfo, Source<ByteString, Object>, Route> processUploadedFile =
|
||||
(metadata, byteSource) -> {
|
||||
CompletionStage<Integer> sumF = byteSource.via(Framing.delimiter(
|
||||
ByteString.fromString("\n"), 1024))
|
||||
.mapConcat(bs -> Arrays.asList(bs.utf8String().split(",")))
|
||||
.map(s -> Integer.parseInt(s))
|
||||
.runFold(0, (acc, n) -> acc + n, ctx.getMaterializer());
|
||||
return onSuccess(() -> sumF, sum -> complete("Sum: " + sum));
|
||||
};
|
||||
return fileUpload("csv", processUploadedFile);
|
||||
});
|
||||
|
||||
Map<String, String> filenameMapping = new HashMap<>();
|
||||
filenameMapping.put("filename", "primes.csv");
|
||||
|
||||
String prefix = "primes";
|
||||
String suffix = ".csv";
|
||||
|
||||
File tempFile = null;
|
||||
try {
|
||||
tempFile = File.createTempFile(prefix, suffix);
|
||||
tempFile.deleteOnExit();
|
||||
Files.write(tempFile.toPath(), Arrays.asList("2,3,5", "7,11,13,17,23", "29,31,37"), Charset.forName("UTF-8"));
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
|
||||
akka.http.javadsl.model.Multipart.FormData multipartForm =
|
||||
Multiparts.createFormDataFromPath("csv", ContentTypes.TEXT_PLAIN_UTF8, tempFile.toPath());
|
||||
|
||||
// test:
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(
|
||||
multipartForm.toEntity(HttpCharsets.UTF_8, BodyPartRenderer.randomBoundaryWithDefaults())))
|
||||
.assertStatusCode(StatusCodes.OK).assertEntityAs(Unmarshaller.entityToString(), "Sum: 178");
|
||||
//#
|
||||
}
|
||||
}
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.FormData;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.unmarshalling.StringUnmarshallers;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.japi.Pair;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class FormFieldDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testFormField() {
|
||||
//#formField
|
||||
final Route route = route(
|
||||
formField("color", color ->
|
||||
complete("The color is '" + color + "'")
|
||||
),
|
||||
formField(StringUnmarshallers.INTEGER, "id", id ->
|
||||
complete("The id is '" + id + "'")
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
final FormData formData = FormData.create(Pair.create("color", "blue"));
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(formData.toEntity()))
|
||||
.assertEntity("The color is 'blue'");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST)
|
||||
.assertEntity("Request is missing required form field 'color'");
|
||||
//#formField
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormFieldMap() {
|
||||
//#formFieldMap
|
||||
final Function<Map<String, String>, String> mapToString = map ->
|
||||
map.entrySet()
|
||||
.stream()
|
||||
.map(e -> e.getKey() + " = '" + e.getValue() +"'")
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
|
||||
final Route route = formFieldMap(fields ->
|
||||
complete("The form fields are " + mapToString.apply(fields))
|
||||
);
|
||||
|
||||
// tests:
|
||||
final FormData formDataDiffKey =
|
||||
FormData.create(
|
||||
Pair.create("color", "blue"),
|
||||
Pair.create("count", "42"));
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(formDataDiffKey.toEntity()))
|
||||
.assertEntity("The form fields are color = 'blue', count = '42'");
|
||||
|
||||
final FormData formDataSameKey =
|
||||
FormData.create(
|
||||
Pair.create("x", "1"),
|
||||
Pair.create("x", "5"));
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(formDataSameKey.toEntity()))
|
||||
.assertEntity( "The form fields are x = '5'");
|
||||
//#formFieldMap
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormFieldMultiMap() {
|
||||
//#formFieldMultiMap
|
||||
final Function<Map<String, List<String>>, String> mapToString = map ->
|
||||
map.entrySet()
|
||||
.stream()
|
||||
.map(e -> e.getKey() + " -> " + e.getValue().size())
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
final Route route = formFieldMultiMap(fields ->
|
||||
complete("There are form fields " + mapToString.apply(fields))
|
||||
);
|
||||
|
||||
// test:
|
||||
final FormData formDataDiffKey =
|
||||
FormData.create(
|
||||
Pair.create("color", "blue"),
|
||||
Pair.create("count", "42"));
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(formDataDiffKey.toEntity()))
|
||||
.assertEntity("There are form fields color -> 1, count -> 1");
|
||||
|
||||
final FormData formDataSameKey =
|
||||
FormData.create(
|
||||
Pair.create("x", "23"),
|
||||
Pair.create("x", "4"),
|
||||
Pair.create("x", "89"));
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(formDataSameKey.toEntity()))
|
||||
.assertEntity("There are form fields x -> 3");
|
||||
//#formFieldMultiMap
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormFieldList() {
|
||||
//#formFieldList
|
||||
final Function<List<Entry<String, String>>, String> listToString = list ->
|
||||
list.stream()
|
||||
.map(e -> e.getKey() + " = '" + e.getValue() +"'")
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
final Route route = formFieldList(fields ->
|
||||
complete("The form fields are " + listToString.apply(fields))
|
||||
);
|
||||
|
||||
// tests:
|
||||
final FormData formDataDiffKey =
|
||||
FormData.create(
|
||||
Pair.create("color", "blue"),
|
||||
Pair.create("count", "42"));
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(formDataDiffKey.toEntity()))
|
||||
.assertEntity("The form fields are color = 'blue', count = '42'");
|
||||
|
||||
final FormData formDataSameKey =
|
||||
FormData.create(
|
||||
Pair.create("x", "23"),
|
||||
Pair.create("x", "4"),
|
||||
Pair.create("x", "89"));
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity(formDataSameKey.toEntity()))
|
||||
.assertEntity("The form fields are x = '23', x = '4', x = '89'");
|
||||
//#formFieldList
|
||||
}
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.marshalling.Marshaller;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.scaladsl.model.StatusCodes;
|
||||
import akka.japi.pf.PFBuilder;
|
||||
import akka.pattern.CircuitBreaker;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import static akka.http.javadsl.server.PathMatchers.*;
|
||||
import static scala.compat.java8.JFunction.func;
|
||||
|
||||
public class FutureDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testOnComplete() {
|
||||
//#onComplete
|
||||
// import static scala.compat.java8.JFunction.func;
|
||||
// import static akka.http.javadsl.server.PathMatchers.*;
|
||||
|
||||
final Route route = path(segment("divide").slash(integerSegment()).slash(integerSegment()),
|
||||
(a, b) -> onComplete(
|
||||
() -> CompletableFuture.supplyAsync(() -> a / b),
|
||||
maybeResult -> maybeResult
|
||||
.map(func(result -> complete("The result was " + result)))
|
||||
.recover(new PFBuilder<Throwable, Route>()
|
||||
.matchAny(ex -> complete(StatusCodes.InternalServerError(),
|
||||
"An error occurred: " + ex.getMessage())
|
||||
)
|
||||
.build())
|
||||
.get()
|
||||
)
|
||||
);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/divide/10/2"))
|
||||
.assertEntity("The result was 5");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/divide/10/0"))
|
||||
.assertStatusCode(StatusCodes.InternalServerError())
|
||||
.assertEntity("An error occurred: / by zero");
|
||||
//#onComplete
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnSuccess() {
|
||||
//#onSuccess
|
||||
final Route route = path("success", () ->
|
||||
onSuccess(() -> CompletableFuture.supplyAsync(() -> "Ok"),
|
||||
extraction -> complete(extraction)
|
||||
)
|
||||
).orElse(path("failure", () ->
|
||||
onSuccess(() -> CompletableFuture.supplyAsync(() -> {
|
||||
throw new RuntimeException();
|
||||
}),
|
||||
extraction -> complete("never reaches here"))
|
||||
));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/success"))
|
||||
.assertEntity("Ok");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/failure"))
|
||||
.assertStatusCode(StatusCodes.InternalServerError())
|
||||
.assertEntity("There was an internal server error.");
|
||||
//#onSuccess
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompleteOrRecoverWith() {
|
||||
//#completeOrRecoverWith
|
||||
final Route route = path("success", () ->
|
||||
completeOrRecoverWith(
|
||||
() -> CompletableFuture.supplyAsync(() -> "Ok"),
|
||||
Marshaller.stringToEntity(),
|
||||
extraction -> failWith(extraction) // not executed
|
||||
)
|
||||
).orElse(path("failure", () ->
|
||||
completeOrRecoverWith(
|
||||
() -> CompletableFuture.supplyAsync(() -> {
|
||||
throw new RuntimeException();
|
||||
}),
|
||||
Marshaller.stringToEntity(),
|
||||
extraction -> failWith(extraction))
|
||||
));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/success"))
|
||||
.assertEntity("Ok");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/failure"))
|
||||
.assertStatusCode(StatusCodes.InternalServerError())
|
||||
.assertEntity("There was an internal server error.");
|
||||
//#completeOrRecoverWith
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnCompleteWithBreaker() throws InterruptedException {
|
||||
//#onCompleteWithBreaker
|
||||
// import static scala.compat.java8.JFunction.func;
|
||||
// import static akka.http.javadsl.server.PathMatchers.*;
|
||||
|
||||
final int maxFailures = 1;
|
||||
final FiniteDuration callTimeout = FiniteDuration.create(5, TimeUnit.SECONDS);
|
||||
final FiniteDuration resetTimeout = FiniteDuration.create(1, TimeUnit.SECONDS);
|
||||
final CircuitBreaker breaker = CircuitBreaker.create(system().scheduler(), maxFailures, callTimeout, resetTimeout);
|
||||
|
||||
final Route route = path(segment("divide").slash(integerSegment()).slash(integerSegment()),
|
||||
(a, b) -> onCompleteWithBreaker(breaker,
|
||||
() -> CompletableFuture.supplyAsync(() -> a / b),
|
||||
maybeResult -> maybeResult
|
||||
.map(func(result -> complete("The result was " + result)))
|
||||
.recover(new PFBuilder<Throwable, Route>()
|
||||
.matchAny(ex -> complete(StatusCodes.InternalServerError(),
|
||||
"An error occurred: " + ex.getMessage())
|
||||
)
|
||||
.build())
|
||||
.get()
|
||||
)
|
||||
);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/divide/10/2"))
|
||||
.assertEntity("The result was 5");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/divide/10/0"))
|
||||
.assertStatusCode(StatusCodes.InternalServerError())
|
||||
.assertEntity("An error occurred: / by zero");
|
||||
// opened the circuit-breaker
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/divide/10/0"))
|
||||
.assertStatusCode(StatusCodes.ServiceUnavailable())
|
||||
.assertEntity("The server is currently unavailable (because it is overloaded or down for maintenance).");
|
||||
|
||||
Thread.sleep(resetTimeout.toMillis() + 300);
|
||||
// circuit breaker resets after this time
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/divide/8/2"))
|
||||
.assertEntity("The result was 4");
|
||||
|
||||
//#onCompleteWithBreaker
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,261 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpHeader;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.model.headers.HttpOrigin;
|
||||
import akka.http.javadsl.model.headers.HttpOriginRange;
|
||||
import akka.http.javadsl.model.headers.Origin;
|
||||
import akka.http.javadsl.model.headers.RawHeader;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.japi.JavaPartialFunction;
|
||||
import akka.http.javadsl.testkit.TestRoute;
|
||||
import scala.PartialFunction;
|
||||
|
||||
public class HeaderDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testHeaderValue() {
|
||||
//#headerValue
|
||||
final Function<HttpHeader, Optional<Host>> extractHostPort = header -> {
|
||||
if (header instanceof Host) {
|
||||
return Optional.of((Host) header);
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = headerValue(extractHostPort, host ->
|
||||
complete("The port was " + host.port())
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("example.com", 5043)))
|
||||
.assertEntity("The port was 5043");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND)
|
||||
.assertEntity("The requested resource could not be found.");
|
||||
//#headerValue
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderValueByName() {
|
||||
//#headerValueByName
|
||||
final Route route = headerValueByName("X-User-Id", userId ->
|
||||
complete("The user is " + userId)
|
||||
);
|
||||
|
||||
// tests:
|
||||
final RawHeader header = RawHeader.create("X-User-Id", "Joe42");
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(header))
|
||||
.assertEntity("The user is Joe42");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST)
|
||||
.assertEntity("Request is missing required HTTP header 'X-User-Id'");
|
||||
//#headerValueByName
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderValueByType() {
|
||||
//#headerValueByType
|
||||
final Route route = headerValueByType(Origin.class, origin ->
|
||||
complete("The first origin was " + origin.getOrigins().iterator().next())
|
||||
);
|
||||
|
||||
// tests:
|
||||
final Host host = Host.create("localhost", 8080);
|
||||
final Origin originHeader = Origin.create(HttpOrigin.create("http", host));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("abc").addHeader(originHeader))
|
||||
.assertEntity("The first origin was http://localhost:8080");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("abc"))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST)
|
||||
.assertEntity("Request is missing required HTTP header 'Origin'");
|
||||
//#headerValueByType
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderValuePF() {
|
||||
//#headerValuePF
|
||||
final PartialFunction<HttpHeader, Integer> extractHostPort =
|
||||
new JavaPartialFunction<HttpHeader, Integer>() {
|
||||
@Override
|
||||
public Integer apply(HttpHeader x, boolean isCheck) throws Exception {
|
||||
if (x instanceof Host) {
|
||||
if (isCheck) {
|
||||
return null;
|
||||
} else {
|
||||
return ((Host) x).port();
|
||||
}
|
||||
} else {
|
||||
throw noMatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = headerValuePF(extractHostPort, port ->
|
||||
complete("The port was " + port)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("example.com", 5043)))
|
||||
.assertEntity("The port was 5043");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND)
|
||||
.assertEntity("The requested resource could not be found.");
|
||||
//#headerValuePF
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalHeaderValue() {
|
||||
//#optionalHeaderValue
|
||||
final Function<HttpHeader, Optional<Integer>> extractHostPort = header -> {
|
||||
if (header instanceof Host) {
|
||||
return Optional.of(((Host) header).port());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = optionalHeaderValue(extractHostPort, port -> {
|
||||
if (port.isPresent()) {
|
||||
return complete("The port was " + port.get());
|
||||
} else {
|
||||
return complete("The port was not provided explicitly");
|
||||
}
|
||||
});
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("example.com", 5043)))
|
||||
.assertEntity("The port was 5043");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("The port was not provided explicitly");
|
||||
//#optionalHeaderValue
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalHeaderValueByName() {
|
||||
//#optionalHeaderValueByName
|
||||
final Route route = optionalHeaderValueByName("X-User-Id", userId -> {
|
||||
if (userId.isPresent()) {
|
||||
return complete("The user is " + userId.get());
|
||||
} else {
|
||||
return complete("No user was provided");
|
||||
}
|
||||
});
|
||||
|
||||
// tests:
|
||||
final RawHeader header = RawHeader.create("X-User-Id", "Joe42");
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(header))
|
||||
.assertEntity("The user is Joe42");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity("No user was provided");
|
||||
//#optionalHeaderValueByName
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalHeaderValueByType() {
|
||||
//#optionalHeaderValueByType
|
||||
final Route route = optionalHeaderValueByType(Origin.class, origin -> {
|
||||
if (origin.isPresent()) {
|
||||
return complete("The first origin was " + origin.get().getOrigins().iterator().next());
|
||||
} else {
|
||||
return complete("No Origin header found.");
|
||||
}
|
||||
});
|
||||
|
||||
// tests:
|
||||
|
||||
// extract Some(header) if the type is matching
|
||||
Host host = Host.create("localhost", 8080);
|
||||
Origin originHeader = Origin.create(HttpOrigin.create("http", host));
|
||||
testRoute(route).run(HttpRequest.GET("abc").addHeader(originHeader))
|
||||
.assertEntity("The first origin was http://localhost:8080");
|
||||
|
||||
// extract None if no header of the given type is present
|
||||
testRoute(route).run(HttpRequest.GET("abc")).assertEntity("No Origin header found.");
|
||||
|
||||
//#optionalHeaderValueByType
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptionalHeaderValuePF() {
|
||||
//#optionalHeaderValuePF
|
||||
final PartialFunction<HttpHeader, Integer> extractHostPort =
|
||||
new JavaPartialFunction<HttpHeader, Integer>() {
|
||||
@Override
|
||||
public Integer apply(HttpHeader x, boolean isCheck) throws Exception {
|
||||
if (x instanceof Host) {
|
||||
if (isCheck) {
|
||||
return null;
|
||||
} else {
|
||||
return ((Host) x).port();
|
||||
}
|
||||
} else {
|
||||
throw noMatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = optionalHeaderValuePF(extractHostPort, port -> {
|
||||
if (port.isPresent()) {
|
||||
return complete("The port was " + port.get());
|
||||
} else {
|
||||
return complete("The port was not provided explicitly");
|
||||
}
|
||||
});
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("example.com", 5043)))
|
||||
.assertEntity("The port was 5043");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("The port was not provided explicitly");
|
||||
//#optionalHeaderValuePF
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckSameOrigin() {
|
||||
//#checkSameOrigin
|
||||
final HttpOrigin validOriginHeader =
|
||||
HttpOrigin.create("http://localhost", Host.create("8080"));
|
||||
|
||||
final HttpOriginRange validOriginRange = HttpOriginRange.create(validOriginHeader);
|
||||
|
||||
final TestRoute route = testRoute(
|
||||
checkSameOrigin(validOriginRange,
|
||||
() -> complete("Result")));
|
||||
|
||||
route
|
||||
.run(HttpRequest.create().addHeader(Origin.create(validOriginHeader)))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("Result");
|
||||
|
||||
route
|
||||
.run(HttpRequest.create())
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST);
|
||||
|
||||
final HttpOrigin invalidOriginHeader =
|
||||
HttpOrigin.create("http://invalid.com", Host.create("8080"));
|
||||
|
||||
route
|
||||
.run(HttpRequest.create().addHeader(Origin.create(invalidOriginHeader)))
|
||||
.assertStatusCode(StatusCodes.FORBIDDEN);
|
||||
//#checkSameOrigin
|
||||
}
|
||||
}
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
|
||||
public class HostDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testListOfHost() {
|
||||
//#host1
|
||||
final Route matchListOfHosts = host(
|
||||
Arrays.asList("api.company.com", "rest.company.com"),
|
||||
() -> complete(StatusCodes.OK));
|
||||
|
||||
testRoute(matchListOfHosts).run(HttpRequest.GET("/").addHeader(Host.create("api.company.com")))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#host1
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHostPredicate() {
|
||||
//#host2
|
||||
final Route shortOnly = host(hostname -> hostname.length() < 10,
|
||||
() -> complete(StatusCodes.OK));
|
||||
|
||||
testRoute(shortOnly).run(HttpRequest.GET("/").addHeader(Host.create("short.com")))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
|
||||
testRoute(shortOnly).run(HttpRequest.GET("/").addHeader(Host.create("verylonghostname.com")))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
//#host2
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractHost() {
|
||||
//#extractHostname
|
||||
|
||||
final Route route = extractHost(hn ->
|
||||
complete("Hostname: " + hn));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("company.com", 9090)))
|
||||
.assertEntity("Hostname: company.com");
|
||||
//#extractHostname
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMatchAndExtractHost() {
|
||||
//#matchAndExtractHost
|
||||
|
||||
final Route hostPrefixRoute = host(Pattern.compile("api|rest"), prefix ->
|
||||
complete("Extracted prefix: " + prefix));
|
||||
|
||||
final Route hostPartRoute = host(Pattern.compile("public.(my|your)company.com"), captured ->
|
||||
complete("You came through " + captured
|
||||
+ " company"));
|
||||
|
||||
final Route route = route(hostPrefixRoute, hostPartRoute);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("api.company.com")))
|
||||
.assertStatusCode(StatusCodes.OK).assertEntity("Extracted prefix: api");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/").addHeader(Host.create("public.mycompany.com")))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("You came through my company");
|
||||
//#matchAndExtractHost
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void testFailingMatchAndExtractHost() {
|
||||
//#failing-matchAndExtractHost
|
||||
// this will throw IllegalArgumentException
|
||||
final Route hostRegex = host(Pattern.compile("server-([0-9]).company.(com|net|org)"), s ->
|
||||
// will not reach here
|
||||
complete(s)
|
||||
);
|
||||
//#failing-matchAndExtractHost
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,150 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpMethods;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
|
||||
public class MethodDirectivesExamplesTest extends JUnitRouteTest {
|
||||
@Test
|
||||
public void testDelete() {
|
||||
//#delete
|
||||
final Route route = delete(() -> complete("This is a DELETE request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.DELETE("/")).assertEntity(
|
||||
"This is a DELETE request.");
|
||||
//#delete
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGet() {
|
||||
//#get
|
||||
final Route route = get(() -> complete("This is a GET request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity(
|
||||
"This is a GET request.");
|
||||
//#get
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHead() {
|
||||
//#head
|
||||
final Route route = head(() -> complete("This is a HEAD request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.HEAD("/")).assertEntity(
|
||||
"This is a HEAD request.");
|
||||
//#head
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOptions() {
|
||||
//#options
|
||||
final Route route = options(() -> complete("This is a OPTIONS request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.OPTIONS("/")).assertEntity(
|
||||
"This is a OPTIONS request.");
|
||||
//#options
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPatch() {
|
||||
//#patch
|
||||
final Route route = patch(() -> complete("This is a PATCH request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.PATCH("/").withEntity("patch content"))
|
||||
.assertEntity("This is a PATCH request.");
|
||||
//#patch
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPost() {
|
||||
//#post
|
||||
final Route route = post(() -> complete("This is a POST request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.POST("/").withEntity("post content"))
|
||||
.assertEntity("This is a POST request.");
|
||||
//#post
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() {
|
||||
//#put
|
||||
final Route route = put(() -> complete("This is a PUT request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.PUT("/").withEntity("put content"))
|
||||
.assertEntity("This is a PUT request.");
|
||||
//#put
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodExample() {
|
||||
//#method-example
|
||||
final Route route = method(HttpMethods.PUT,
|
||||
() -> complete("This is a PUT request."));
|
||||
|
||||
testRoute(route).run(HttpRequest.PUT("/").withEntity("put content"))
|
||||
.assertEntity("This is a PUT request.");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertStatusCode(
|
||||
StatusCodes.METHOD_NOT_ALLOWED);
|
||||
//#method-example
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractMethodExample() {
|
||||
//#extractMethod
|
||||
|
||||
final Route route = route(
|
||||
get(() ->
|
||||
complete("This is a GET request.")
|
||||
),
|
||||
extractMethod(method ->
|
||||
complete("This " + method.value() + " request, clearly is not a GET!")
|
||||
)
|
||||
);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity(
|
||||
"This is a GET request.");
|
||||
|
||||
testRoute(route).run(HttpRequest.PUT("/").withEntity("put content"))
|
||||
.assertEntity("This PUT request, clearly is not a GET!");
|
||||
|
||||
testRoute(route).run(HttpRequest.HEAD("/")).assertEntity(
|
||||
"This HEAD request, clearly is not a GET!");
|
||||
//#extractMethod
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverrideMethodWithParameter() {
|
||||
//#overrideMethodWithParameter
|
||||
|
||||
final Route route = route(
|
||||
overrideMethodWithParameter("method", () ->
|
||||
route(
|
||||
get(() -> complete("This looks like a GET request.")),
|
||||
post(() -> complete("This looks like a POST request."))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/?method=POST")).assertEntity(
|
||||
"This looks like a POST request.");
|
||||
|
||||
testRoute(route).run(HttpRequest.POST("/?method=get"))
|
||||
.assertEntity("This looks like a GET request.");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/?method=hallo")).assertEntity(
|
||||
"The server either does not recognize the request method, or it lacks the ability to fulfill the request.");
|
||||
|
||||
//#overrideMethodWithParameter
|
||||
}
|
||||
}
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.unmarshalling.Unmarshaller;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class MiscDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testWithSizeLimit() {
|
||||
//#withSizeLimitExample
|
||||
final Route route = withSizeLimit(500, () ->
|
||||
entity(Unmarshaller.entityToString(), (entity) ->
|
||||
complete("ok")
|
||||
)
|
||||
);
|
||||
|
||||
Function<Integer, HttpRequest> withEntityOfSize = (sizeLimit) -> {
|
||||
char[] charArray = new char[sizeLimit];
|
||||
Arrays.fill(charArray, '0');
|
||||
return HttpRequest.POST("/").withEntity(new String(charArray));
|
||||
};
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(withEntityOfSize.apply(500))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
|
||||
testRoute(route).run(withEntityOfSize.apply(501))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST);
|
||||
//#withSizeLimitExample
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithoutSizeLimit() {
|
||||
//#withoutSizeLimitExample
|
||||
final Route route = withoutSizeLimit(() ->
|
||||
entity(Unmarshaller.entityToString(), (entity) ->
|
||||
complete("ok")
|
||||
)
|
||||
);
|
||||
|
||||
Function<Integer, HttpRequest> withEntityOfSize = (sizeLimit) -> {
|
||||
char[] charArray = new char[sizeLimit];
|
||||
Arrays.fill(charArray, '0');
|
||||
return HttpRequest.POST("/").withEntity(new String(charArray));
|
||||
};
|
||||
|
||||
// tests:
|
||||
// will work even if you have configured akka.http.parsing.max-content-length = 500
|
||||
testRoute(route).run(withEntityOfSize.apply(501))
|
||||
.assertStatusCode(StatusCodes.OK);
|
||||
//#withoutSizeLimitExample
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ParameterDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testParameter() {
|
||||
//#parameter
|
||||
final Route route = parameter("color", color ->
|
||||
complete("The color is '" + color + "'")
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/?color=blue"))
|
||||
.assertEntity("The color is 'blue'");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND)
|
||||
.assertEntity("Request is missing required query parameter 'color'");
|
||||
//#parameter
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameters() {
|
||||
//#parameters
|
||||
final Route route = parameter("color", color ->
|
||||
parameter("backgroundColor", backgroundColor ->
|
||||
complete("The color is '" + color
|
||||
+ "' and the background is '" + backgroundColor + "'")
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/?color=blue&backgroundColor=red"))
|
||||
.assertEntity("The color is 'blue' and the background is 'red'");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/?color=blue"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND)
|
||||
.assertEntity("Request is missing required query parameter 'backgroundColor'");
|
||||
//#parameters
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterMap() {
|
||||
//#parameterMap
|
||||
final Function<Entry, String> paramString =
|
||||
entry -> entry.getKey() + " = '" + entry.getValue() + "'";
|
||||
|
||||
final Route route = parameterMap(params -> {
|
||||
final String pString = params.entrySet()
|
||||
.stream()
|
||||
.map(paramString::apply)
|
||||
.collect(Collectors.joining(", "));
|
||||
return complete("The parameters are " + pString);
|
||||
});
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/?color=blue&count=42"))
|
||||
.assertEntity("The parameters are color = 'blue', count = '42'");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/?x=1&x=2"))
|
||||
.assertEntity("The parameters are x = '2'");
|
||||
//#parameterMap
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterMultiMap() {
|
||||
//#parameterMultiMap
|
||||
final Route route = parameterMultiMap(params -> {
|
||||
final String pString = params.entrySet()
|
||||
.stream()
|
||||
.map(e -> e.getKey() + " -> " + e.getValue().size())
|
||||
.collect(Collectors.joining(", "));
|
||||
return complete("There are parameters " + pString);
|
||||
});
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/?color=blue&count=42"))
|
||||
.assertEntity("There are parameters color -> 1, count -> 1");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/?x=23&x=42"))
|
||||
.assertEntity("There are parameters x -> 2");
|
||||
//#parameterMultiMap
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParameterSeq() {
|
||||
//#parameterSeq
|
||||
final Function<Entry, String> paramString =
|
||||
entry -> entry.getKey() + " = '" + entry.getValue() + "'";
|
||||
|
||||
final Route route = parameterList(params -> {
|
||||
final String pString = params.stream()
|
||||
.map(paramString::apply)
|
||||
.collect(Collectors.joining(", "));
|
||||
|
||||
return complete("The parameters are " + pString);
|
||||
});
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/?color=blue&count=42"))
|
||||
.assertEntity("The parameters are color = 'blue', count = '42'");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/?x=1&x=2"))
|
||||
.assertEntity("The parameters are x = '1', x = '2'");
|
||||
//#parameterSeq
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,322 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import static akka.http.javadsl.server.PathMatchers.segment;
|
||||
import static akka.http.javadsl.server.PathMatchers.segments;
|
||||
import static akka.http.javadsl.server.PathMatchers.integerSegment;
|
||||
import static akka.http.javadsl.server.PathMatchers.neutral;
|
||||
import static akka.http.javadsl.server.PathMatchers.slash;
|
||||
import java.util.function.Supplier;
|
||||
import akka.http.javadsl.server.directives.RouteAdapter;
|
||||
import static java.util.regex.Pattern.compile;
|
||||
|
||||
public class PathDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
//# path-prefix-test, path-suffix, raw-path-prefix, raw-path-prefix-test
|
||||
Supplier<RouteAdapter> completeWithUnmatchedPath = ()->
|
||||
extractUnmatchedPath((path) -> complete(path.toString()));
|
||||
//#
|
||||
|
||||
@Test
|
||||
public void testPathExamples() {
|
||||
//# path-dsl
|
||||
// matches /foo/
|
||||
path(segment("foo").slash(), () -> complete(StatusCodes.OK));
|
||||
|
||||
// matches e.g. /foo/123 and extracts "123" as a String
|
||||
path(segment("foo").slash(segment(compile("\\d+"))), (value) ->
|
||||
complete(StatusCodes.OK));
|
||||
|
||||
// matches e.g. /foo/bar123 and extracts "123" as a String
|
||||
path(segment("foo").slash(segment(compile("bar(\\d+)"))), (value) ->
|
||||
complete(StatusCodes.OK));
|
||||
|
||||
// similar to `path(Segments)`
|
||||
path(neutral().repeat(0, 10), () -> complete(StatusCodes.OK));
|
||||
|
||||
// identical to path("foo" ~ (PathEnd | Slash))
|
||||
path(segment("foo").orElse(slash()), () -> complete(StatusCodes.OK));
|
||||
//# path-dsl
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBasicExamples() {
|
||||
path("test", () -> complete(StatusCodes.OK));
|
||||
|
||||
// matches "/test", as well
|
||||
path(segment("test"), () -> complete(StatusCodes.OK));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathExample() {
|
||||
//# pathPrefix
|
||||
final Route route =
|
||||
route(
|
||||
path("foo", () -> complete("/foo")),
|
||||
path(segment("foo").slash("bar"), () -> complete("/foo/bar")),
|
||||
pathPrefix("ball", () ->
|
||||
route(
|
||||
pathEnd(() -> complete("/ball")),
|
||||
path(integerSegment(), (i) ->
|
||||
complete((i % 2 == 0) ? "even ball" : "odd ball"))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
testRoute(route).run(HttpRequest.GET("/foo")).assertEntity("/foo");
|
||||
testRoute(route).run(HttpRequest.GET("/foo/bar")).assertEntity("/foo/bar");
|
||||
testRoute(route).run(HttpRequest.GET("/ball/1337")).assertEntity("odd ball");
|
||||
//# pathPrefix
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathEnd() {
|
||||
//# path-end
|
||||
final Route route =
|
||||
route(
|
||||
pathPrefix("foo", () ->
|
||||
route(
|
||||
pathEnd(() -> complete("/foo")),
|
||||
path("bar", () -> complete("/foo/bar"))
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo")).assertEntity("/foo");
|
||||
testRoute(route).run(HttpRequest.GET("/foo/")).assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
testRoute(route).run(HttpRequest.GET("/foo/bar")).assertEntity("/foo/bar");
|
||||
//# path-end
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathEndOrSingleSlash() {
|
||||
//# path-end-or-single-slash
|
||||
final Route route =
|
||||
route(
|
||||
pathPrefix("foo", () ->
|
||||
route(
|
||||
pathEndOrSingleSlash(() -> complete("/foo")),
|
||||
path("bar", () -> complete("/foo/bar"))
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo")).assertEntity("/foo");
|
||||
testRoute(route).run(HttpRequest.GET("/foo/")).assertEntity("/foo");
|
||||
testRoute(route).run(HttpRequest.GET("/foo/bar")).assertEntity("/foo/bar");
|
||||
//# path-end-or-single-slash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathPrefix() {
|
||||
//# path-prefix
|
||||
final Route route =
|
||||
route(
|
||||
pathPrefix("ball", () ->
|
||||
route(
|
||||
pathEnd(() -> complete("/ball")),
|
||||
path(integerSegment(), (i) ->
|
||||
complete((i % 2 == 0) ? "even ball" : "odd ball"))
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
testRoute(route).run(HttpRequest.GET("/ball")).assertEntity("/ball");
|
||||
testRoute(route).run(HttpRequest.GET("/ball/1337")).assertEntity("odd ball");
|
||||
//# path-prefix
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathPrefixTest() {
|
||||
//# path-prefix-test
|
||||
final Route route =
|
||||
route(
|
||||
pathPrefixTest(segment("foo").orElse("bar"), () ->
|
||||
route(
|
||||
pathPrefix("foo", () -> completeWithUnmatchedPath.get()),
|
||||
pathPrefix("bar", () -> completeWithUnmatchedPath.get())
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo/doo")).assertEntity("/doo");
|
||||
testRoute(route).run(HttpRequest.GET("/bar/yes")).assertEntity("/yes");
|
||||
//# path-prefix-test
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathSingleSlash() {
|
||||
//# path-single-slash
|
||||
final Route route =
|
||||
route(
|
||||
pathSingleSlash(() -> complete("root")),
|
||||
pathPrefix("ball", () ->
|
||||
route(
|
||||
pathSingleSlash(() -> complete("/ball/")),
|
||||
path(integerSegment(), (i) -> complete((i % 2 == 0) ? "even ball" : "odd ball"))
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/")).assertEntity("root");
|
||||
testRoute(route).run(HttpRequest.GET("/ball")).assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
testRoute(route).run(HttpRequest.GET("/ball/")).assertEntity("/ball/");
|
||||
testRoute(route).run(HttpRequest.GET("/ball/1337")).assertEntity("odd ball");
|
||||
//# path-single-slash
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathSuffix() {
|
||||
//# path-suffix
|
||||
final Route route =
|
||||
route(
|
||||
pathPrefix("start", () ->
|
||||
route(
|
||||
pathSuffix("end", () -> completeWithUnmatchedPath.get()),
|
||||
pathSuffix(segment("foo").slash("bar").concat("baz"), () ->
|
||||
completeWithUnmatchedPath.get())
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/start/middle/end")).assertEntity("/middle/");
|
||||
testRoute(route).run(HttpRequest.GET("/start/something/barbaz/foo")).assertEntity("/something/");
|
||||
//# path-suffix
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathSuffixTest() {
|
||||
//# path-suffix-test
|
||||
final Route route =
|
||||
route(
|
||||
pathSuffixTest(slash(), () -> complete("slashed")),
|
||||
complete("unslashed")
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo/")).assertEntity("slashed");
|
||||
testRoute(route).run(HttpRequest.GET("/foo")).assertEntity("unslashed");
|
||||
//# path-suffix-test
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawPathPrefix() {
|
||||
//# raw-path-prefix
|
||||
final Route route =
|
||||
route(
|
||||
pathPrefix("foo", () ->
|
||||
route(
|
||||
rawPathPrefix("bar", () -> completeWithUnmatchedPath.get()),
|
||||
rawPathPrefix("doo", () -> completeWithUnmatchedPath.get())
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foobar/baz")).assertEntity("/baz");
|
||||
testRoute(route).run(HttpRequest.GET("/foodoo/baz")).assertEntity("/baz");
|
||||
//# raw-path-prefix
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRawPathPrefixTest() {
|
||||
//# raw-path-prefix-test
|
||||
final Route route =
|
||||
route(
|
||||
pathPrefix("foo", () ->
|
||||
rawPathPrefixTest("bar", () -> completeWithUnmatchedPath.get())
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foobar")).assertEntity("bar");
|
||||
testRoute(route).run(HttpRequest.GET("/foobaz")).assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
//# raw-path-prefix-test
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedirectToNoTrailingSlashIfMissing() {
|
||||
//# redirect-notrailing-slash-missing
|
||||
final Route route =
|
||||
redirectToTrailingSlashIfMissing(
|
||||
StatusCodes.MOVED_PERMANENTLY, () ->
|
||||
route(
|
||||
path(segment("foo").slash(), () -> complete("OK")),
|
||||
path(segment("bad-1"), () ->
|
||||
// MISTAKE!
|
||||
// Missing `/` in path, causes this path to never match,
|
||||
// because it is inside a `redirectToTrailingSlashIfMissing`
|
||||
complete(StatusCodes.NOT_IMPLEMENTED)
|
||||
),
|
||||
path(segment("bad-2").slash(), () ->
|
||||
// MISTAKE!
|
||||
// / should be explicit as path element separator and not *in* the path element
|
||||
// So it should be: "bad-1" /
|
||||
complete(StatusCodes.NOT_IMPLEMENTED)
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo"))
|
||||
.assertStatusCode(StatusCodes.MOVED_PERMANENTLY)
|
||||
.assertEntity("This and all future requests should be directed to " +
|
||||
"<a href=\"http://example.com/foo/\">this URI</a>.");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/foo/"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("OK");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/bad-1/"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
//# redirect-notrailing-slash-missing
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedirectToNoTrailingSlashIfPresent() {
|
||||
//# redirect-notrailing-slash-present
|
||||
final Route route =
|
||||
redirectToNoTrailingSlashIfPresent(
|
||||
StatusCodes.MOVED_PERMANENTLY, () ->
|
||||
route(
|
||||
path("foo", () -> complete("OK")),
|
||||
path(segment("bad").slash(), () ->
|
||||
// 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.
|
||||
complete(StatusCodes.NOT_IMPLEMENTED)
|
||||
)
|
||||
)
|
||||
);
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo/"))
|
||||
.assertStatusCode(StatusCodes.MOVED_PERMANENTLY)
|
||||
.assertEntity("This and all future requests should be directed to " +
|
||||
"<a href=\"http://example.com/foo\">this URI</a>.");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/foo"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("OK");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/bad"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND);
|
||||
//# redirect-notrailing-slash-present
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.Multipart;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.ByteRange;
|
||||
import akka.http.javadsl.model.headers.ContentRange;
|
||||
import akka.http.javadsl.model.headers.Range;
|
||||
import akka.http.javadsl.model.headers.RangeUnits;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.unmarshalling.Unmarshaller;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.javadsl.testkit.TestRouteResult;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.util.ByteString;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import org.junit.Test;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RangeDirectivesExamplesTest extends JUnitRouteTest {
|
||||
@Override
|
||||
public Config additionalConfig() {
|
||||
return ConfigFactory.parseString("akka.http.routing.range-coalescing-threshold=2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithRangeSupport() {
|
||||
//#withRangeSupport
|
||||
final Route route = withRangeSupport(() -> complete("ABCDEFGH"));
|
||||
|
||||
// test:
|
||||
final String bytes348Range = ContentRange.create(RangeUnits.BYTES,
|
||||
akka.http.javadsl.model.ContentRange.create(3, 4, 8)).value();
|
||||
final akka.http.javadsl.model.ContentRange bytes028Range =
|
||||
akka.http.javadsl.model.ContentRange.create(0, 2, 8);
|
||||
final akka.http.javadsl.model.ContentRange bytes678Range =
|
||||
akka.http.javadsl.model.ContentRange.create(6, 7, 8);
|
||||
final ActorMaterializer materializer = systemResource().materializer();
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/")
|
||||
.addHeader(Range.create(RangeUnits.BYTES, ByteRange.createSlice(3, 4))))
|
||||
.assertHeaderKindExists("Content-Range")
|
||||
.assertHeaderExists("Content-Range", bytes348Range)
|
||||
.assertStatusCode(StatusCodes.PARTIAL_CONTENT)
|
||||
.assertEntity("DE");
|
||||
|
||||
// we set "akka.http.routing.range-coalescing-threshold = 2"
|
||||
// above to make sure we get two BodyParts
|
||||
final TestRouteResult response = testRoute(route).run(HttpRequest.GET("/")
|
||||
.addHeader(Range.create(RangeUnits.BYTES,
|
||||
ByteRange.createSlice(0, 1), ByteRange.createSlice(1, 2), ByteRange.createSlice(6, 7))));
|
||||
response.assertHeaderKindNotExists("Content-Range");
|
||||
|
||||
final CompletionStage<List<Multipart.ByteRanges.BodyPart>> completionStage =
|
||||
response.entity(Unmarshaller.entityToMultipartByteRanges()).getParts()
|
||||
.runFold(new ArrayList<>(), (acc, n) -> {
|
||||
acc.add(n);
|
||||
return acc;
|
||||
}, materializer);
|
||||
try {
|
||||
final List<Multipart.ByteRanges.BodyPart> bodyParts =
|
||||
completionStage.toCompletableFuture().get(3, TimeUnit.SECONDS);
|
||||
assertEquals(2, bodyParts.toArray().length);
|
||||
|
||||
final Multipart.ByteRanges.BodyPart part1 = bodyParts.get(0);
|
||||
assertEquals(bytes028Range, part1.getContentRange());
|
||||
assertEquals(ByteString.fromString("ABC"),
|
||||
part1.toStrict(1000, materializer).toCompletableFuture().get().getEntity().getData());
|
||||
|
||||
final Multipart.ByteRanges.BodyPart part2 = bodyParts.get(1);
|
||||
assertEquals(bytes678Range, part2.getContentRange());
|
||||
assertEquals(ByteString.fromString("GH"),
|
||||
part2.toStrict(1000, materializer).toCompletableFuture().get().getEntity().getData());
|
||||
|
||||
} catch (Exception e) {
|
||||
// please handle this in production code
|
||||
}
|
||||
//#
|
||||
}
|
||||
}
|
||||
|
|
@ -1,135 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.List;
|
||||
import akka.http.javadsl.model.HttpHeader;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.headers.HttpOrigin;
|
||||
import akka.http.javadsl.model.headers.Origin;
|
||||
import akka.http.javadsl.model.headers.RawHeader;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RespondWithDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testRespondWithHeader() {
|
||||
//#respondWithHeader
|
||||
final Route route = path("foo", () ->
|
||||
respondWithHeader(RawHeader.create("Funky-Muppet", "gonzo"), () ->
|
||||
complete("beep")));
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/foo"))
|
||||
.assertHeaderExists("Funky-Muppet", "gonzo")
|
||||
.assertEntity("beep");
|
||||
//#respondWithHeader
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRespondWithDefaultHeader() {
|
||||
//#respondWithDefaultHeader
|
||||
//custom headers
|
||||
final RawHeader blippy = RawHeader.create("X-Fish-Name", "Blippy");
|
||||
final RawHeader elTonno = RawHeader.create("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
|
||||
final Route route =
|
||||
respondWithDefaultHeader(blippy, () -> // blippy
|
||||
respondWithHeader(elTonno, () -> // / el tonno
|
||||
path("el-tonno", () -> // | /
|
||||
complete("¡Ay blippy!") // | |- el tonno
|
||||
).orElse( // | |
|
||||
path("los-tonnos", () -> // | |
|
||||
complete("¡Ay ay blippy!") // | |- el tonno
|
||||
) // | |
|
||||
) // | |
|
||||
).orElse( // | x
|
||||
complete("Blip!") // |- blippy
|
||||
) // x
|
||||
);
|
||||
//format: ON
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertHeaderExists("X-Fish-Name", "Blippy")
|
||||
.assertEntity("Blip!");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/el-tonno"))
|
||||
.assertHeaderExists("X-Fish-Name", "El Tonno")
|
||||
.assertEntity("¡Ay blippy!");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/los-tonnos"))
|
||||
.assertHeaderExists("X-Fish-Name", "El Tonno")
|
||||
.assertEntity("¡Ay ay blippy!");
|
||||
//#respondWithDefaultHeader
|
||||
}
|
||||
|
||||
@Test
|
||||
public void respondWithHeaders() {
|
||||
//#respondWithHeaders
|
||||
final HttpHeader gonzo = RawHeader.create("Funky-Muppet", "gonzo");
|
||||
final HttpHeader akka = Origin.create(HttpOrigin.parse("http://akka.io"));
|
||||
|
||||
final Route route = path("foo", () ->
|
||||
respondWithHeaders(Lists.newArrayList(gonzo, akka), () ->
|
||||
complete("beep")
|
||||
)
|
||||
);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/foo"))
|
||||
.assertHeaderExists("Funky-Muppet", "gonzo")
|
||||
.assertHeaderExists("Origin", "http://akka.io")
|
||||
.assertEntity("beep");
|
||||
|
||||
//#respondWithHeaders
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRespondWithDefaultHeaders() {
|
||||
//#respondWithDefaultHeaders
|
||||
//custom headers
|
||||
final RawHeader blippy = RawHeader.create("X-Fish-Name", "Blippy");
|
||||
final HttpHeader akka = Origin.create(HttpOrigin.parse("http://akka.io"));
|
||||
final List<HttpHeader> defaultHeaders = Lists.newArrayList(blippy, akka);
|
||||
final RawHeader elTonno = RawHeader.create("X-Fish-Name", "El Tonno");
|
||||
|
||||
// format: OFF
|
||||
// by default always include the Blippy and Akka headers,
|
||||
// unless a more specific X-Fish-Name is given by the inner route
|
||||
final Route route =
|
||||
respondWithDefaultHeaders(defaultHeaders, () -> // blippy and akka
|
||||
respondWithHeader(elTonno, () -> // / el tonno
|
||||
path("el-tonno", () -> // | /
|
||||
complete("¡Ay blippy!") // | |- el tonno
|
||||
).orElse( // | |
|
||||
path("los-tonnos", () -> // | |
|
||||
complete("¡Ay ay blippy!") // | |- el tonno
|
||||
) // | |
|
||||
) // | |
|
||||
).orElse( // | x
|
||||
complete("Blip!") // |- blippy and akka
|
||||
) // x
|
||||
);
|
||||
//format: ON
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertHeaderExists("X-Fish-Name", "Blippy")
|
||||
.assertHeaderExists("Origin", "http://akka.io")
|
||||
.assertEntity("Blip!");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/el-tonno"))
|
||||
.assertHeaderExists("X-Fish-Name", "El Tonno")
|
||||
.assertEntity("¡Ay blippy!");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/los-tonnos"))
|
||||
.assertHeaderExists("X-Fish-Name", "El Tonno")
|
||||
.assertEntity("¡Ay ay blippy!");
|
||||
//#respondWithDefaultHeaders
|
||||
}
|
||||
}
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpEntities;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.Uri;
|
||||
import akka.http.javadsl.model.headers.ContentType;
|
||||
import akka.http.javadsl.model.ContentTypes;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.Rejections;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class RouteDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testComplete() {
|
||||
//#complete
|
||||
final Route route = route(
|
||||
path("a", () -> complete(HttpResponse.create().withEntity("foo"))),
|
||||
path("b", () -> complete(StatusCodes.OK)),
|
||||
path("c", () -> complete(StatusCodes.CREATED, "bar")),
|
||||
path("d", () -> complete(StatusCodes.get(201), "bar")),
|
||||
path("e", () ->
|
||||
complete(StatusCodes.CREATED,
|
||||
Collections.singletonList(ContentType.create(ContentTypes.TEXT_PLAIN_UTF8)),
|
||||
HttpEntities.create("bar"))),
|
||||
path("f", () ->
|
||||
complete(StatusCodes.get(201),
|
||||
Collections.singletonList(ContentType.create(ContentTypes.TEXT_PLAIN_UTF8)),
|
||||
HttpEntities.create("bar"))),
|
||||
path("g", () -> complete("baz"))
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/a"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("foo");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/b"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("OK");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/c"))
|
||||
.assertStatusCode(StatusCodes.CREATED)
|
||||
.assertEntity("bar");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/d"))
|
||||
.assertStatusCode(StatusCodes.CREATED)
|
||||
.assertEntity("bar");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/e"))
|
||||
.assertStatusCode(StatusCodes.CREATED)
|
||||
.assertHeaderExists(ContentType.create(ContentTypes.TEXT_PLAIN_UTF8))
|
||||
.assertEntity("bar");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/f"))
|
||||
.assertStatusCode(StatusCodes.CREATED)
|
||||
.assertHeaderExists(ContentType.create(ContentTypes.TEXT_PLAIN_UTF8))
|
||||
.assertEntity("bar");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/g"))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("baz");
|
||||
//#complete
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReject() {
|
||||
//#reject
|
||||
final Route route = route(
|
||||
path("a", this::reject), // don't handle here, continue on
|
||||
path("a", () -> complete("foo")),
|
||||
path("b", () -> reject(Rejections.validationRejection("Restricted!")))
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/a"))
|
||||
.assertEntity("foo");
|
||||
|
||||
runRouteUnSealed(route, HttpRequest.GET("/b"))
|
||||
.assertRejections(Rejections.validationRejection("Restricted!"));
|
||||
//#reject
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedirect() {
|
||||
//#redirect
|
||||
final Route route = pathPrefix("foo", () ->
|
||||
route(
|
||||
pathSingleSlash(() -> complete("yes")),
|
||||
pathEnd(() -> redirect(Uri.create("/foo/"), StatusCodes.PERMANENT_REDIRECT))
|
||||
)
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo/"))
|
||||
.assertEntity("yes");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/foo"))
|
||||
.assertStatusCode(StatusCodes.PERMANENT_REDIRECT)
|
||||
.assertEntity("The request, and all future requests should be repeated using <a href=\"/foo/\">this URI</a>.");
|
||||
//#redirect
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailWith() {
|
||||
//#failWith
|
||||
final Route route = path("foo", () ->
|
||||
failWith(new RuntimeException("Oops."))
|
||||
);
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/foo"))
|
||||
.assertStatusCode(StatusCodes.INTERNAL_SERVER_ERROR)
|
||||
.assertEntity("There was an internal server error.");
|
||||
//#failWith
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2015-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.Host;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.server.RequestContext;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import java.util.function.Function;
|
||||
import akka.http.javadsl.model.Uri;
|
||||
import akka.http.javadsl.model.headers.Location;
|
||||
|
||||
public class SchemeDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testScheme() {
|
||||
//#extractScheme
|
||||
final Route route = extractScheme((scheme) ->
|
||||
complete(String.format("The scheme is '%s'", scheme)));
|
||||
testRoute(route).run(HttpRequest.GET("https://www.example.com/"))
|
||||
.assertEntity("The scheme is 'https'");
|
||||
//#extractScheme
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRedirection() {
|
||||
//#scheme
|
||||
final Route route = route(
|
||||
scheme("http", ()->
|
||||
extract((ctx) -> ctx.getRequest().getUri(), (uri)->
|
||||
redirect(uri.scheme("https"), StatusCodes.MOVED_PERMANENTLY)
|
||||
)
|
||||
),
|
||||
scheme("https", ()->
|
||||
complete("Safe and secure!")
|
||||
)
|
||||
);
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("http://www.example.com/hello"))
|
||||
.assertStatusCode(StatusCodes.MOVED_PERMANENTLY)
|
||||
.assertHeaderExists(Location.create("https://www.example.com/hello"))
|
||||
;
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("https://www.example.com/hello"))
|
||||
.assertEntity("Safe and secure!");
|
||||
//#scheme
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,364 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.headers.BasicHttpCredentials;
|
||||
import akka.http.javadsl.model.headers.HttpChallenge;
|
||||
import akka.http.javadsl.model.headers.HttpCredentials;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.japi.JavaPartialFunction;
|
||||
import org.junit.Test;
|
||||
import scala.PartialFunction;
|
||||
import scala.util.Either;
|
||||
import scala.util.Left;
|
||||
import scala.util.Right;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
import java.util.function.Function;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SecurityDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testAuthenticateBasic() {
|
||||
//#authenticateBasic
|
||||
final Function<Optional<ProvidedCredentials>, Optional<String>> myUserPassAuthenticator =
|
||||
credentials ->
|
||||
credentials.filter(c -> c.verify("p4ssw0rd")).map(ProvidedCredentials::identifier);
|
||||
|
||||
final Route route = path("secured", () ->
|
||||
authenticateBasic("secure site", myUserPassAuthenticator, userName ->
|
||||
complete("The user is '" + userName + "'")
|
||||
)
|
||||
).seal(system(), materializer());
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/secured"))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The resource requires authentication, which was not supplied with the request")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
|
||||
final HttpCredentials validCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials))
|
||||
.assertEntity("The user is 'John'");
|
||||
|
||||
final HttpCredentials invalidCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The supplied authentication is invalid")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
//#authenticateBasic
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAuthenticateBasicPF() {
|
||||
//#authenticateBasicPF
|
||||
final PartialFunction<Optional<ProvidedCredentials>, String> myUserPassAuthenticator =
|
||||
new JavaPartialFunction<Optional<ProvidedCredentials>, String>() {
|
||||
@Override
|
||||
public String apply(Optional<ProvidedCredentials> opt, boolean isCheck) throws Exception {
|
||||
if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) {
|
||||
if (isCheck) return null;
|
||||
else return opt.get().identifier();
|
||||
} else if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd-special")).isPresent()) {
|
||||
if (isCheck) return null;
|
||||
else return opt.get().identifier() + "-admin";
|
||||
} else {
|
||||
throw noMatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = path("secured", () ->
|
||||
authenticateBasicPF("secure site", myUserPassAuthenticator, userName ->
|
||||
complete("The user is '" + userName + "'")
|
||||
)
|
||||
).seal(system(), materializer());
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/secured"))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The resource requires authentication, which was not supplied with the request")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
|
||||
final HttpCredentials validCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials))
|
||||
.assertEntity("The user is 'John'");
|
||||
|
||||
final HttpCredentials validAdminCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd-special");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validAdminCredentials))
|
||||
.assertEntity("The user is 'John-admin'");
|
||||
|
||||
final HttpCredentials invalidCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The supplied authentication is invalid")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
//#authenticateBasicPF
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateBasicPFAsync() {
|
||||
//#authenticateBasicPFAsync
|
||||
class User {
|
||||
private final String id;
|
||||
public User(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
final PartialFunction<Optional<ProvidedCredentials>, CompletionStage<User>> myUserPassAuthenticator =
|
||||
new JavaPartialFunction<Optional<ProvidedCredentials>,CompletionStage<User>>() {
|
||||
@Override
|
||||
public CompletionStage<User> apply(Optional<ProvidedCredentials> opt, boolean isCheck) throws Exception {
|
||||
if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) {
|
||||
if (isCheck) return CompletableFuture.completedFuture(null);
|
||||
else return CompletableFuture.completedFuture(new User(opt.get().identifier()));
|
||||
} else {
|
||||
throw noMatch();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = path("secured", () ->
|
||||
authenticateBasicPFAsync("secure site", myUserPassAuthenticator, user ->
|
||||
complete("The user is '" + user.getId() + "'"))
|
||||
).seal(system(), materializer());
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/secured"))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The resource requires authentication, which was not supplied with the request")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
|
||||
final HttpCredentials validCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials))
|
||||
.assertEntity("The user is 'John'");
|
||||
|
||||
final HttpCredentials invalidCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The supplied authentication is invalid")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
//#authenticateBasicPFAsync
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateBasicAsync() {
|
||||
//#authenticateBasicAsync
|
||||
final Function<Optional<ProvidedCredentials>, CompletionStage<Optional<String>>> myUserPassAuthenticator = opt -> {
|
||||
if (opt.filter(c -> (c != null) && c.verify("p4ssw0rd")).isPresent()) {
|
||||
return CompletableFuture.completedFuture(Optional.of(opt.get().identifier()));
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(Optional.empty());
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = path("secured", () ->
|
||||
authenticateBasicAsync("secure site", myUserPassAuthenticator, userName ->
|
||||
complete("The user is '" + userName + "'")
|
||||
)
|
||||
).seal(system(), materializer());
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/secured"))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The resource requires authentication, which was not supplied with the request")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
|
||||
final HttpCredentials validCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials))
|
||||
.assertEntity("The user is 'John'");
|
||||
|
||||
final HttpCredentials invalidCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(invalidCredentials))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The supplied authentication is invalid")
|
||||
.assertHeaderExists("WWW-Authenticate", "Basic realm=\"secure site\"");
|
||||
//#authenticateBasicAsync
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthenticateOrRejectWithChallenge() {
|
||||
//#authenticateOrRejectWithChallenge
|
||||
final HttpChallenge challenge = HttpChallenge.create("MyAuth", "MyRealm");
|
||||
|
||||
// your custom authentication logic:
|
||||
final Function<HttpCredentials, Boolean> auth = credentials -> true;
|
||||
|
||||
final Function<Optional<HttpCredentials>, CompletionStage<Either<HttpChallenge, String>>> myUserPassAuthenticator =
|
||||
opt -> {
|
||||
if (opt.isPresent() && auth.apply(opt.get())) {
|
||||
return CompletableFuture.completedFuture(Right.apply("some-user-name-from-creds"));
|
||||
} else {
|
||||
return CompletableFuture.completedFuture(Left.apply(challenge));
|
||||
}
|
||||
};
|
||||
|
||||
final Route route = path("secured", () ->
|
||||
authenticateOrRejectWithChallenge(myUserPassAuthenticator, userName ->
|
||||
complete("Authenticated!")
|
||||
)
|
||||
).seal(system(), materializer());
|
||||
|
||||
// tests:
|
||||
testRoute(route).run(HttpRequest.GET("/secured"))
|
||||
.assertStatusCode(StatusCodes.UNAUTHORIZED)
|
||||
.assertEntity("The resource requires authentication, which was not supplied with the request")
|
||||
.assertHeaderExists("WWW-Authenticate", "MyAuth realm=\"MyRealm\"");
|
||||
|
||||
final HttpCredentials validCredentials =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/secured").addCredentials(validCredentials))
|
||||
.assertStatusCode(StatusCodes.OK)
|
||||
.assertEntity("Authenticated!");
|
||||
//#authenticateOrRejectWithChallenge
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorize() {
|
||||
//#authorize
|
||||
class User {
|
||||
private final String name;
|
||||
public User(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// authenticate the user:
|
||||
final Function<Optional<ProvidedCredentials>, Optional<User>> myUserPassAuthenticator =
|
||||
opt -> {
|
||||
if (opt.isPresent()) {
|
||||
return Optional.of(new User(opt.get().identifier()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
|
||||
// check if user is authorized to perform admin actions:
|
||||
final Set<String> admins = new HashSet<>();
|
||||
admins.add("Peter");
|
||||
final Function<User, Boolean> hasAdminPermissions = user -> admins.contains(user.getName());
|
||||
|
||||
final Route route = authenticateBasic("secure site", myUserPassAuthenticator, user ->
|
||||
path("peters-lair", () ->
|
||||
authorize(() -> hasAdminPermissions.apply(user), () ->
|
||||
complete("'" + user.getName() +"' visited Peter's lair")
|
||||
)
|
||||
)
|
||||
).seal(system(), materializer());
|
||||
|
||||
// tests:
|
||||
final HttpCredentials johnsCred =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(johnsCred))
|
||||
.assertStatusCode(StatusCodes.FORBIDDEN)
|
||||
.assertEntity("The supplied authentication is not authorized to access this resource");
|
||||
|
||||
final HttpCredentials petersCred =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan");
|
||||
testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(petersCred))
|
||||
.assertEntity("'Peter' visited Peter's lair");
|
||||
//#authorize
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAuthorizeAsync() {
|
||||
//#authorizeAsync
|
||||
class User {
|
||||
private final String name;
|
||||
public User(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
// authenticate the user:
|
||||
final Function<Optional<ProvidedCredentials>, Optional<User>> myUserPassAuthenticator =
|
||||
opt -> {
|
||||
if (opt.isPresent()) {
|
||||
return Optional.of(new User(opt.get().identifier()));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
};
|
||||
|
||||
// check if user is authorized to perform admin actions,
|
||||
// this could potentially be a long operation so it would return a Future
|
||||
final Set<String> admins = new HashSet<>();
|
||||
admins.add("Peter");
|
||||
final Set<String> synchronizedAdmins = Collections.synchronizedSet(admins);
|
||||
|
||||
final Function<User, CompletionStage<Object>> hasAdminPermissions =
|
||||
user -> CompletableFuture.completedFuture(synchronizedAdmins.contains(user.getName()));
|
||||
|
||||
final Route route = authenticateBasic("secure site", myUserPassAuthenticator, user ->
|
||||
path("peters-lair", () ->
|
||||
authorizeAsync(() -> hasAdminPermissions.apply(user), () ->
|
||||
complete("'" + user.getName() +"' visited Peter's lair")
|
||||
)
|
||||
)
|
||||
).seal(system(), materializer());
|
||||
|
||||
// tests:
|
||||
final HttpCredentials johnsCred =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(johnsCred))
|
||||
.assertStatusCode(StatusCodes.FORBIDDEN)
|
||||
.assertEntity("The supplied authentication is not authorized to access this resource");
|
||||
|
||||
final HttpCredentials petersCred =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("Peter", "pan");
|
||||
testRoute(route).run(HttpRequest.GET("/peters-lair").addCredentials(petersCred))
|
||||
.assertEntity("'Peter' visited Peter's lair");
|
||||
//#authorizeAsync
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExtractCredentials() {
|
||||
//#extractCredentials
|
||||
final Route route = extractCredentials(optCreds -> {
|
||||
if (optCreds.isPresent()) {
|
||||
return complete("Credentials: " + optCreds.get());
|
||||
} else {
|
||||
return complete("No credentials");
|
||||
}
|
||||
});
|
||||
|
||||
// tests:
|
||||
final HttpCredentials johnsCred =
|
||||
BasicHttpCredentials.createBasicHttpCredentials("John", "p4ssw0rd");
|
||||
testRoute(route).run(HttpRequest.GET("/").addCredentials(johnsCred))
|
||||
.assertEntity("Credentials: Basic Sm9objpwNHNzdzByZA==");
|
||||
|
||||
testRoute(route).run(HttpRequest.GET("/"))
|
||||
.assertEntity("No credentials");
|
||||
//#extractCredentials
|
||||
}
|
||||
}
|
||||
|
|
@ -1,180 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.ServerBinding;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.HttpResponse;
|
||||
import akka.http.javadsl.model.StatusCode;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.server.AllDirectives;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.scaladsl.TestUtils;
|
||||
import akka.stream.ActorMaterializer;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.testkit.TestKit;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import scala.Tuple2;
|
||||
import scala.Tuple3;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.runtime.BoxedUnit;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class TimeoutDirectivesExamplesTest extends AllDirectives {
|
||||
//#testSetup
|
||||
private final Config testConf = ConfigFactory.parseString("akka.loggers = [\"akka.testkit.TestEventListener\"]\n"
|
||||
+ "akka.loglevel = ERROR\n"
|
||||
+ "akka.stdout-loglevel = ERROR\n"
|
||||
+ "windows-connection-abort-workaround-enabled = auto\n"
|
||||
+ "akka.log-dead-letters = OFF\n"
|
||||
+ "akka.http.server.request-timeout = 1000s");
|
||||
// large timeout - 1000s (please note - setting to infinite will disable Timeout-Access header
|
||||
// and withRequestTimeout will not work)
|
||||
|
||||
private final ActorSystem system = ActorSystem.create("TimeoutDirectivesExamplesTest", testConf);
|
||||
|
||||
private final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
private final Http http = Http.get(system);
|
||||
|
||||
private CompletionStage<Void> shutdown(CompletionStage<ServerBinding> binding) {
|
||||
return binding.thenAccept(b -> {
|
||||
System.out.println(String.format("Unbinding from %s", b.localAddress()));
|
||||
|
||||
final CompletionStage<BoxedUnit> unbound = b.unbind();
|
||||
try {
|
||||
unbound.toCompletableFuture().get(3, TimeUnit.SECONDS); // block...
|
||||
} catch (TimeoutException | InterruptedException | ExecutionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<HttpResponse> runRoute(ActorSystem system, ActorMaterializer materializer, Route route, String routePath) {
|
||||
final Tuple3<InetSocketAddress, String, Object> inetaddrHostAndPort = TestUtils.temporaryServerHostnameAndPort("127.0.0.1");
|
||||
Tuple2<String, Integer> hostAndPort = new Tuple2<>(
|
||||
inetaddrHostAndPort._2(),
|
||||
(Integer) inetaddrHostAndPort._3()
|
||||
);
|
||||
|
||||
final Flow<HttpRequest, HttpResponse, NotUsed> routeFlow = route.flow(system, materializer);
|
||||
final CompletionStage<ServerBinding> binding = http.bindAndHandle(routeFlow, ConnectHttp.toHost(hostAndPort._1(), hostAndPort._2()), materializer);
|
||||
|
||||
final CompletionStage<HttpResponse> responseCompletionStage = http.singleRequest(HttpRequest.create("http://" + hostAndPort._1() + ":" + hostAndPort._2() + "/" + routePath), materializer);
|
||||
|
||||
CompletableFuture<HttpResponse> responseFuture = responseCompletionStage.toCompletableFuture();
|
||||
|
||||
Optional<HttpResponse> responseOptional;
|
||||
try {
|
||||
responseOptional = Optional.of(responseFuture.get(3, TimeUnit.SECONDS)); // patienceConfig
|
||||
} catch (Exception e) {
|
||||
responseOptional = Optional.empty();
|
||||
}
|
||||
|
||||
shutdown(binding);
|
||||
|
||||
return responseOptional;
|
||||
}
|
||||
//#
|
||||
|
||||
@After
|
||||
public void shutDown() {
|
||||
TestKit.shutdownActorSystem(system, Duration.create(1, TimeUnit.SECONDS), false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestTimeoutIsConfigurable() {
|
||||
//#withRequestTimeout-plain
|
||||
final Duration timeout = Duration.create(1, TimeUnit.SECONDS);
|
||||
CompletionStage<String> slowFuture = new CompletableFuture<>();
|
||||
|
||||
final Route route = path("timeout", () ->
|
||||
withRequestTimeout(timeout, () -> {
|
||||
return completeOKWithFutureString(slowFuture); // very slow
|
||||
})
|
||||
);
|
||||
|
||||
// test:
|
||||
StatusCode statusCode = runRoute(system, materializer, route, "timeout").get().status();
|
||||
assert (StatusCodes.SERVICE_UNAVAILABLE.equals(statusCode));
|
||||
//#
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestWithoutTimeoutCancelsTimeout() {
|
||||
//#withoutRequestTimeout-1
|
||||
CompletionStage<String> slowFuture = new CompletableFuture<>();
|
||||
|
||||
final Route route = path("timeout", () ->
|
||||
withoutRequestTimeout(() -> {
|
||||
return completeOKWithFutureString(slowFuture); // very slow
|
||||
})
|
||||
);
|
||||
|
||||
// test:
|
||||
Boolean receivedReply = runRoute(system, materializer, route, "timeout").isPresent();
|
||||
assert (!receivedReply); // timed-out
|
||||
//#
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRequestTimeoutAllowsCustomResponse() {
|
||||
//#withRequestTimeout-with-handler
|
||||
final Duration timeout = Duration.create(1, TimeUnit.MILLISECONDS);
|
||||
CompletionStage<String> slowFuture = new CompletableFuture<>();
|
||||
|
||||
HttpResponse enhanceYourCalmResponse = HttpResponse.create()
|
||||
.withStatus(StatusCodes.ENHANCE_YOUR_CALM)
|
||||
.withEntity("Unable to serve response within time limit, please enhance your calm.");
|
||||
|
||||
final Route route = path("timeout", () ->
|
||||
withRequestTimeout(timeout, (request) -> enhanceYourCalmResponse, () -> {
|
||||
return completeOKWithFutureString(slowFuture); // very slow
|
||||
})
|
||||
);
|
||||
|
||||
// test:
|
||||
StatusCode statusCode = runRoute(system, materializer, route, "timeout").get().status();
|
||||
assert (StatusCodes.ENHANCE_YOUR_CALM.equals(statusCode));
|
||||
//#
|
||||
}
|
||||
|
||||
// make it compile only to avoid flaking in slow builds
|
||||
@Ignore("Compile only test")
|
||||
@Test
|
||||
public void testRequestTimeoutCustomResponseCanBeAddedSeparately() {
|
||||
//#withRequestTimeoutResponse
|
||||
final Duration timeout = Duration.create(100, TimeUnit.MILLISECONDS);
|
||||
CompletionStage<String> slowFuture = new CompletableFuture<>();
|
||||
|
||||
HttpResponse enhanceYourCalmResponse = HttpResponse.create()
|
||||
.withStatus(StatusCodes.ENHANCE_YOUR_CALM)
|
||||
.withEntity("Unable to serve response within time limit, please enhance your calm.");
|
||||
|
||||
final Route route = path("timeout", () ->
|
||||
withRequestTimeout(timeout, () ->
|
||||
// racy! for a very short timeout like 1.milli you can still get 503
|
||||
withRequestTimeoutResponse((request) -> enhanceYourCalmResponse, () -> {
|
||||
return completeOKWithFutureString(slowFuture); // very slow
|
||||
}))
|
||||
);
|
||||
|
||||
// test:
|
||||
StatusCode statusCode = runRoute(system, materializer, route, "timeout").get().status();
|
||||
assert (StatusCodes.ENHANCE_YOUR_CALM.equals(statusCode));
|
||||
//#
|
||||
}
|
||||
}
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package docs.http.javadsl.server.directives;
|
||||
|
||||
import akka.NotUsed;
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.model.Uri;
|
||||
import akka.http.javadsl.model.headers.SecWebSocketProtocol;
|
||||
import akka.http.javadsl.model.ws.BinaryMessage;
|
||||
import akka.http.javadsl.model.ws.Message;
|
||||
import akka.http.javadsl.model.ws.TextMessage;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.javadsl.testkit.WSProbe;
|
||||
import akka.stream.OverflowStrategy;
|
||||
import akka.stream.javadsl.Flow;
|
||||
import akka.stream.javadsl.Sink;
|
||||
import akka.stream.javadsl.Source;
|
||||
import akka.util.ByteString;
|
||||
import org.junit.Test;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class WebSocketDirectivesExamplesTest extends JUnitRouteTest {
|
||||
|
||||
@Test
|
||||
public void testHandleWebSocketMessages() {
|
||||
//#handleWebSocketMessages
|
||||
final Flow<Message, Message, NotUsed> greeter = Flow.of(Message.class).mapConcat(msg -> {
|
||||
if (msg instanceof TextMessage) {
|
||||
final TextMessage tm = (TextMessage) msg;
|
||||
final TextMessage ret = TextMessage.create(Source.single("Hello ").concat(tm.getStreamedText()).concat(Source.single("!")));
|
||||
return Collections.singletonList(ret);
|
||||
} else if (msg instanceof BinaryMessage) {
|
||||
final BinaryMessage bm = (BinaryMessage) msg;
|
||||
bm.getStreamedData().runWith(Sink.ignore(), materializer());
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported message type!");
|
||||
}
|
||||
});
|
||||
|
||||
final Route websocketRoute = path("greeter", () ->
|
||||
handleWebSocketMessages(greeter)
|
||||
);
|
||||
|
||||
// create a testing probe representing the client-side
|
||||
final WSProbe wsClient = WSProbe.create(system(), materializer());
|
||||
|
||||
// WS creates a WebSocket request for testing
|
||||
testRoute(websocketRoute).run(WS(Uri.create("/greeter"), wsClient.flow(), materializer()))
|
||||
.assertStatusCode(StatusCodes.SWITCHING_PROTOCOLS);
|
||||
|
||||
// manually run a WS conversation
|
||||
wsClient.sendMessage("Peter");
|
||||
wsClient.expectMessage("Hello Peter!");
|
||||
|
||||
wsClient.sendMessage(BinaryMessage.create(ByteString.fromString("abcdef")));
|
||||
wsClient.expectNoMessage(FiniteDuration.create(100, TimeUnit.MILLISECONDS));
|
||||
|
||||
wsClient.sendMessage("John");
|
||||
wsClient.expectMessage("Hello John!");
|
||||
|
||||
wsClient.sendCompletion();
|
||||
wsClient.expectCompletion();
|
||||
//#handleWebSocketMessages
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHandleWebSocketMessagesForProtocol() {
|
||||
//#handleWebSocketMessagesForProtocol
|
||||
final Flow<Message, Message, NotUsed> greeterService = Flow.of(Message.class).mapConcat(msg -> {
|
||||
if (msg instanceof TextMessage) {
|
||||
final TextMessage tm = (TextMessage) msg;
|
||||
final TextMessage ret = TextMessage.create(Source.single("Hello ").concat(tm.getStreamedText()).concat(Source.single("!")));
|
||||
return Collections.singletonList(ret);
|
||||
} else if (msg instanceof BinaryMessage) {
|
||||
final BinaryMessage bm = (BinaryMessage) msg;
|
||||
bm.getStreamedData().runWith(Sink.ignore(), materializer());
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported message type!");
|
||||
}
|
||||
});
|
||||
|
||||
final Flow<Message, Message, NotUsed> echoService = Flow.of(Message.class).buffer(1, OverflowStrategy.backpressure());
|
||||
|
||||
final Route websocketMultipleProtocolRoute = path("services", () ->
|
||||
route(
|
||||
handleWebSocketMessagesForProtocol(greeterService, "greeter"),
|
||||
handleWebSocketMessagesForProtocol(echoService, "echo")
|
||||
)
|
||||
);
|
||||
|
||||
// create a testing probe representing the client-side
|
||||
final WSProbe wsClient = WSProbe.create(system(), materializer());
|
||||
|
||||
// WS creates a WebSocket request for testing
|
||||
testRoute(websocketMultipleProtocolRoute)
|
||||
.run(WS(Uri.create("/services"), wsClient.flow(), materializer(), Arrays.asList("other", "echo")))
|
||||
.assertHeaderExists(SecWebSocketProtocol.create("echo"));
|
||||
|
||||
wsClient.sendMessage("Peter");
|
||||
wsClient.expectMessage("Peter");
|
||||
|
||||
wsClient.sendMessage(BinaryMessage.create(ByteString.fromString("abcdef")));
|
||||
wsClient.expectMessage(ByteString.fromString("abcdef"));
|
||||
|
||||
wsClient.sendMessage("John");
|
||||
wsClient.expectMessage("John");
|
||||
|
||||
wsClient.sendCompletion();
|
||||
wsClient.expectCompletion();
|
||||
//#handleWebSocketMessagesForProtocol
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.testkit;
|
||||
|
||||
//#simple-app
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.http.javadsl.ConnectHttp;
|
||||
import akka.http.javadsl.Http;
|
||||
import akka.http.javadsl.server.AllDirectives;
|
||||
import akka.http.javadsl.server.Route;
|
||||
import akka.http.javadsl.unmarshalling.StringUnmarshallers;
|
||||
import akka.http.javadsl.server.examples.simple.SimpleServerApp;
|
||||
import akka.stream.ActorMaterializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MyAppService extends AllDirectives {
|
||||
|
||||
public String add(double x, double y) {
|
||||
return "x + y = " + (x + y);
|
||||
}
|
||||
|
||||
public Route createRoute() {
|
||||
return
|
||||
get(() ->
|
||||
pathPrefix("calculator", () ->
|
||||
path("add", () ->
|
||||
parameter(StringUnmarshallers.DOUBLE, "x", x ->
|
||||
parameter(StringUnmarshallers.DOUBLE, "y", y ->
|
||||
complete(add(x, y))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
final ActorSystem system = ActorSystem.create();
|
||||
final ActorMaterializer materializer = ActorMaterializer.create(system);
|
||||
|
||||
final SimpleServerApp app = new SimpleServerApp();
|
||||
|
||||
final ConnectHttp host = ConnectHttp.toHost("127.0.0.1");
|
||||
|
||||
Http.get(system).bindAndHandle(app.createRoute().flow(system, materializer), host, materializer);
|
||||
|
||||
System.console().readLine("Type RETURN to exit...");
|
||||
system.terminate();
|
||||
}
|
||||
}
|
||||
//#simple-app
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2009-2016 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package docs.http.javadsl.server.testkit;
|
||||
|
||||
//#simple-app-testing
|
||||
import akka.http.javadsl.model.HttpRequest;
|
||||
import akka.http.javadsl.model.StatusCodes;
|
||||
import akka.http.javadsl.testkit.JUnitRouteTest;
|
||||
import akka.http.javadsl.testkit.TestRoute;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestkitExampleTest extends JUnitRouteTest {
|
||||
TestRoute appRoute = testRoute(new MyAppService().createRoute());
|
||||
|
||||
@Test
|
||||
public void testCalculatorAdd() {
|
||||
// test happy path
|
||||
appRoute.run(HttpRequest.GET("/calculator/add?x=4.2&y=2.3"))
|
||||
.assertStatusCode(200)
|
||||
.assertEntity("x + y = 6.5");
|
||||
|
||||
// test responses to potential errors
|
||||
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2"))
|
||||
.assertStatusCode(StatusCodes.NOT_FOUND) // 404
|
||||
.assertEntity("Request is missing required query parameter 'y'");
|
||||
|
||||
// test responses to potential errors
|
||||
appRoute.run(HttpRequest.GET("/calculator/add?x=3.2&y=three"))
|
||||
.assertStatusCode(StatusCodes.BAD_REQUEST)
|
||||
.assertEntity("The query parameter 'y' was malformed:\n" +
|
||||
"'three' is not a valid 64-bit floating point value");
|
||||
}
|
||||
}
|
||||
//#simple-app-testing
|
||||
|
|
@ -1,137 +0,0 @@
|
|||
.. _clientSideHTTPS-java:
|
||||
|
||||
Client-Side HTTPS Support
|
||||
=========================
|
||||
|
||||
Akka HTTP supports TLS encryption on the client-side as well as on the :ref:`server-side <serverSideHTTPS-java>`.
|
||||
|
||||
.. warning:
|
||||
|
||||
Akka HTTP 1.0 does not completely validate certificates when using HTTPS. Please do not treat HTTPS connections
|
||||
made with this version as secure. Requests are vulnerable to a Man-In-The-Middle attack via certificate substitution.
|
||||
|
||||
The central vehicle for configuring encryption is the ``HttpsConnectionContext``, which can be created using
|
||||
the static method ``ConnectionContext.https`` which is defined like this:
|
||||
|
||||
.. includecode:: /../../akka-http-core/src/main/scala/akka/http/javadsl/ConnectionContext.scala
|
||||
:include: https-context-creation
|
||||
|
||||
In addition to the ``outgoingConnection``, ``newHostConnectionPool`` and ``cachedHostConnectionPool`` methods the
|
||||
`akka.http.javadsl.Http`_ extension also defines ``outgoingConnectionTls``, ``newHostConnectionPoolTls`` and
|
||||
``cachedHostConnectionPoolTls``. These methods work identically to their counterparts without the ``-Tls`` suffix,
|
||||
with the exception that all connections will always be encrypted.
|
||||
|
||||
The ``singleRequest`` and ``superPool`` methods determine the encryption state via the scheme of the incoming request,
|
||||
i.e. requests to an "https" URI will be encrypted, while requests to an "http" URI won't.
|
||||
|
||||
The encryption configuration for all HTTPS connections, i.e. the ``HttpsContext`` is determined according to the
|
||||
following logic:
|
||||
|
||||
1. If the optional ``httpsContext`` method parameter is defined it contains the configuration to be used (and thus
|
||||
takes precedence over any potentially set default client-side ``HttpsContext``).
|
||||
|
||||
2. If the optional ``httpsContext`` method parameter is undefined (which is the default) the default client-side
|
||||
``HttpsContext`` is used, which can be set via the ``setDefaultClientHttpsContext`` on the ``Http`` extension.
|
||||
|
||||
3. If no default client-side ``HttpsContext`` has been set via the ``setDefaultClientHttpsContext`` on the ``Http``
|
||||
extension the default system configuration is used.
|
||||
|
||||
Usually the process is, if the default system TLS configuration is not good enough for your application's needs,
|
||||
that you configure a custom ``HttpsContext`` instance and set it via ``Http.get(system).setDefaultClientHttpsContext``.
|
||||
Afterwards you simply use ``outgoingConnectionTls``, ``newHostConnectionPoolTls``, ``cachedHostConnectionPoolTls``,
|
||||
``superPool`` or ``singleRequest`` without a specific ``httpsContext`` argument, which causes encrypted connections
|
||||
to rely on the configured default client-side ``HttpsContext``.
|
||||
|
||||
If no custom ``HttpsContext`` is defined the default context uses Java's default TLS settings. Customizing the
|
||||
``HttpsContext`` can make the Https client less secure. Understand what you are doing!
|
||||
|
||||
SSL-Config
|
||||
----------
|
||||
|
||||
Akka HTTP heavily relies on, and delegates most configuration of any SSL/TLS related options to
|
||||
`Lightbend SSL-Config`_, which is a library specialized in providing an secure-by-default SSLContext
|
||||
and related options.
|
||||
|
||||
Please refer to the `Lightbend SSL-Config`_ documentation for detailed documentation of all available settings.
|
||||
|
||||
SSL Config settings used by Akka HTTP (as well as Streaming TCP) are located under the `akka.ssl-config` namespace.
|
||||
|
||||
.. _Lightbend SSL-Config: http://typesafehub.github.io/ssl-config/
|
||||
|
||||
Detailed configuration and workarounds
|
||||
--------------------------------------
|
||||
|
||||
Akka HTTP relies on `Typesafe SSL-Config`_ which is a library maintained by Lightbend that makes configuring
|
||||
things related to SSL/TLS much simpler than using the raw SSL APIs provided by the JDK. Please refer to its
|
||||
documentation to learn more about it.
|
||||
|
||||
All configuration options available to this library may be set under the ``akka.ssl-context`` configuration for Akka HTTP applications.
|
||||
|
||||
.. note::
|
||||
When encountering problems connecting to HTTPS hosts we highly encourage to reading up on the excellent ssl-config
|
||||
configuration. Especially the quick start sections about `adding certificates to the trust store`_ should prove
|
||||
very useful, for example to easily trust a self-signed certificate that applications might use in development mode.
|
||||
|
||||
.. warning::
|
||||
While it is possible to disable certain checks using the so called "loose" settings in SSL Config, we **strongly recommend**
|
||||
to instead attempt to solve these issues by properly configuring TLS–for example by adding trusted keys to the keystore.
|
||||
|
||||
If however certain checks really need to be disabled because of misconfigured (or legacy) servers that your
|
||||
application has to speak to, instead of disabling the checks globally (i.e. in ``application.conf``) we suggest
|
||||
configuring the loose settings for *specific connections* that are known to need them disabled (and trusted for some other reason).
|
||||
The pattern of doing so is documented in the folowing sub-sections.
|
||||
|
||||
.. _adding certificates to the trust store: http://typesafehub.github.io/ssl-config/WSQuickStart.html#connecting-to-a-remote-server-over-https
|
||||
|
||||
Hostname verification
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Hostname verification proves that the Akka HTTP client is actually communicating with the server it intended to
|
||||
communicate with. Without this check a man-in-the-middle attack is possible. In the attack scenario, an alternative
|
||||
certificate would be presented which was issued for another host name. Checking the host name in the certificate
|
||||
against the host name the connection was opened against is therefore vital.
|
||||
|
||||
The default ``HttpsContext`` enables hostname verification. Akka HTTP relies on the `Typesafe SSL-Config`_ library
|
||||
to implement this and security options for SSL/TLS. Hostname verification is provided by the JDK
|
||||
and used by Akka HTTP since Java 7, and on Java 6 the verification is implemented by ssl-config manually.
|
||||
|
||||
For further recommended reading we would like to highlight the `fixing hostname verification blog post`_ by blog post by Will Sargent.
|
||||
|
||||
.. _Typesafe SSL-Config: http://typesafehub.github.io/ssl-config
|
||||
.. _fixing hostname verification blog post: https://tersesystems.com/2014/03/23/fixing-hostname-verification/
|
||||
.. _akka.http.javadsl.Http: @github@/akka-http-core/src/main/scala/akka/http/javadsl/Http.scala
|
||||
|
||||
|
||||
Server Name Indication (SNI)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
SNI is an TLS extension which aims to guard against man-in-the-middle attacks. It does so by having the client send the
|
||||
name of the virtual domain it is expecting to talk to as part of the TLS handshake.
|
||||
|
||||
It is specified as part of `RFC 6066`_.
|
||||
|
||||
Disabling TLS security features, at your own risk
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. warning::
|
||||
It is highly discouraged to disable any of the security features of TLS, however do acknowlage that workarounds may sometimes be needed.
|
||||
|
||||
Before disabling any of the features one should consider if they may be solvable *within* the TLS world,
|
||||
for example by `trusting a certificate`_, or `configuring the trusted cipher suites`_.
|
||||
There's also a very important section in the ssl-config docs titled `LooseSSL - Please read this before turning anything off!`_.
|
||||
|
||||
If disabling features is indeed desired, we recommend doing so for *specific connections*,
|
||||
instead of globally configuring it via ``application.conf``.
|
||||
|
||||
The following shows an example of disabling SNI for a given connection:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpsExamplesDocTest.java
|
||||
:include: disable-sni-connection
|
||||
|
||||
The ``badSslConfig`` is a copy of the default ``AkkaSSLConfig`` with with the slightly changed configuration to disable SNI.
|
||||
This value can be cached and used for connections which should indeed not use this feature.
|
||||
|
||||
.. _RFC 6066: https://tools.ietf.org/html/rfc6066#page-6
|
||||
.. _LooseSSL - Please read this before turning anything off!: http://typesafehub.github.io/ssl-config/LooseSSL.html#please-read-this-before-turning-anything-off
|
||||
.. _trusting a certificate: http://typesafehub.github.io/ssl-config/WSQuickStart.html
|
||||
.. _configuring the trusted cipher suites: http://typesafehub.github.io/ssl-config/CipherSuites.html
|
||||
|
|
@ -1,81 +0,0 @@
|
|||
.. _connection-level-api-java:
|
||||
|
||||
Connection-Level Client-Side API
|
||||
================================
|
||||
|
||||
The connection-level API is the lowest-level client-side API Akka HTTP provides. It gives you full control over when
|
||||
HTTP connections are opened and closed and how requests are to be send across which connection. As such it offers the
|
||||
highest flexibility at the cost of providing the least convenience.
|
||||
|
||||
|
||||
Opening HTTP Connections
|
||||
------------------------
|
||||
With the connection-level API you open a new HTTP connection to a target endpoint by materializing a ``Flow``
|
||||
returned by the ``Http.get(system).outgoingConnection(...)`` method. Here is an example:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#outgoing-connection-example
|
||||
|
||||
Apart from the host name and port the ``Http.get(system).outgoingConnection(...)`` method also allows you to specify socket options
|
||||
and a number of configuration settings for the connection.
|
||||
|
||||
Note that no connection is attempted until the returned flow is actually materialized! If the flow is materialized
|
||||
several times then several independent connections will be opened (one per materialization).
|
||||
If the connection attempt fails, for whatever reason, the materialized flow will be immediately terminated with a
|
||||
respective exception.
|
||||
|
||||
|
||||
Request-Response Cycle
|
||||
----------------------
|
||||
|
||||
Once the connection flow has been materialized it is ready to consume ``HttpRequest`` instances from the source it is
|
||||
attached to. Each request is sent across the connection and incoming responses dispatched to the downstream pipeline.
|
||||
Of course and as always, back-pressure is adequately maintained across all parts of the
|
||||
connection. This means that, if the downstream pipeline consuming the HTTP responses is slow, the request source will
|
||||
eventually be slowed down in sending requests.
|
||||
|
||||
Any errors occurring on the underlying connection are surfaced as exceptions terminating the response stream (and
|
||||
canceling the request source).
|
||||
|
||||
Note that, if the source produces subsequent requests before the prior responses have arrived, these requests will be
|
||||
pipelined__ across the connection, which is something that is not supported by all HTTP servers.
|
||||
Also, if the server closes the connection before responses to all requests have been received this will result in the
|
||||
response stream being terminated with a truncation error.
|
||||
|
||||
__ http://en.wikipedia.org/wiki/HTTP_pipelining
|
||||
|
||||
|
||||
Closing Connections
|
||||
-------------------
|
||||
|
||||
Akka HTTP actively closes an established connection upon reception of a response containing ``Connection: close`` header.
|
||||
The connection can also be closed by the server.
|
||||
|
||||
An application can actively trigger the closing of the connection by completing the request stream. In this case the
|
||||
underlying TCP connection will be closed when the last pending response has been received.
|
||||
|
||||
The connection will also be closed if the response entity is cancelled (e.g. by attaching it to ``Sink.cancelled()``)
|
||||
or consumed only partially (e.g. by using ``take`` combinator). In order to prevent this behaviour the entity should be
|
||||
explicitly drained by attaching it to ``Sink.ignore()``.
|
||||
|
||||
|
||||
Timeouts
|
||||
--------
|
||||
|
||||
Timeouts are configured in the same way for Scala and Akka. See :ref:`http-timeouts-java` .
|
||||
|
||||
.. _http-client-layer-java:
|
||||
|
||||
Stand-Alone HTTP Layer Usage
|
||||
----------------------------
|
||||
|
||||
Due to its Reactive-Streams-based nature the Akka HTTP layer is fully detachable from the underlying TCP
|
||||
interface. While in most applications this "feature" will not be crucial it can be useful in certain cases to be able
|
||||
to "run" the HTTP layer (and, potentially, higher-layers) against data that do not come from the network but rather
|
||||
some other source. Potential scenarios where this might be useful include tests, debugging or low-level event-sourcing
|
||||
(e.g by replaying network traffic).
|
||||
|
||||
On the client-side the stand-alone HTTP layer forms a ``BidiFlow<HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed>``,
|
||||
that is a stage that "upgrades" a potentially encrypted raw connection to the HTTP level.
|
||||
|
||||
You create an instance of the layer by calling one of the two overloads of the ``Http.get(system).clientLayer`` method,
|
||||
which also allows for varying degrees of configuration.
|
||||
|
|
@ -1,156 +0,0 @@
|
|||
.. _host-level-api-java:
|
||||
|
||||
Host-Level Client-Side API
|
||||
==========================
|
||||
|
||||
As opposed to the :ref:`connection-level-api` the host-level API relieves you from manually managing individual HTTP
|
||||
connections. It autonomously manages a configurable pool of connections to *one particular target endpoint* (i.e.
|
||||
host/port combination).
|
||||
|
||||
|
||||
Requesting a Host Connection Pool
|
||||
---------------------------------
|
||||
|
||||
The best way to get a hold of a connection pool to a given target endpoint is the ``Http.get(system).cachedHostConnectionPool(...)``
|
||||
method, which returns a ``Flow`` that can be "baked" into an application-level stream setup. This flow is also called
|
||||
a "pool client flow".
|
||||
|
||||
The connection pool underlying a pool client flow is cached. For every ``ActorSystem``, target endpoint and pool
|
||||
configuration there will never be more than a single pool live at any time.
|
||||
|
||||
Also, the HTTP layer transparently manages idle shutdown and restarting of connection pools as configured.
|
||||
The client flow instances therefore remain valid throughout the lifetime of the application, i.e. they can be
|
||||
materialized as often as required and the time between individual materialization is of no importance.
|
||||
|
||||
When you request a pool client flow with ``Http.get(system).cachedHostConnectionPool(...)`` Akka HTTP will immediately start
|
||||
the pool, even before the first client flow materialization. However, this running pool will not actually open the
|
||||
first connection to the target endpoint until the first request has arrived.
|
||||
|
||||
|
||||
Configuring a Host Connection Pool
|
||||
----------------------------------
|
||||
|
||||
Apart from the connection-level config settings and socket options there are a number of settings that allow you to
|
||||
influence the behavior of the connection pool logic itself.
|
||||
Check out the ``akka.http.host-connection-pool`` section of the Akka HTTP :ref:`akka-http-configuration-java` for
|
||||
more information about which settings are available and what they mean.
|
||||
|
||||
Note that, if you request pools with different configurations for the same target host you will get *independent* pools.
|
||||
This means that, in total, your application might open more concurrent HTTP connections to the target endpoint than any
|
||||
of the individual pool's ``max-connections`` settings allow!
|
||||
|
||||
There is one setting that likely deserves a bit deeper explanation: ``max-open-requests``.
|
||||
This setting limits the maximum number of requests that can be in-flight at any time for a single connection pool.
|
||||
If an application calls ``Http.get(system).cachedHostConnectionPool(...)`` 3 times (with the same endpoint and settings) it will get
|
||||
back ``3`` different client flow instances for the same pool. If each of these client flows is then materialized ``4`` times
|
||||
(concurrently) the application will have 12 concurrently running client flow materializations.
|
||||
All of these share the resources of the single pool.
|
||||
|
||||
This means that, if the pool's ``pipelining-limit`` is left at ``1`` (effecitvely disabeling pipelining), no more than 12 requests can be open at any time.
|
||||
With a ``pipelining-limit`` of ``8`` and 12 concurrent client flow materializations the theoretical open requests
|
||||
maximum is ``96``.
|
||||
|
||||
The ``max-open-requests`` config setting allows for applying a hard limit which serves mainly as a protection against
|
||||
erroneous connection pool use, e.g. because the application is materializing too many client flows that all compete for
|
||||
the same pooled connections.
|
||||
|
||||
.. _using-a-host-connection-pool-java:
|
||||
|
||||
Using a Host Connection Pool
|
||||
----------------------------
|
||||
|
||||
The "pool client flow" returned by ``Http.get(system).cachedHostConnectionPool(...)`` has the following type::
|
||||
|
||||
// TODO Tuple2 will be changed to be `akka.japi.Pair`
|
||||
Flow[Tuple2[HttpRequest, T], Tuple2[Try[HttpResponse], T], HostConnectionPool]
|
||||
|
||||
This means it consumes tuples of type ``(HttpRequest, T)`` and produces tuples of type ``(Try[HttpResponse], T)``
|
||||
which might appear more complicated than necessary on first sight.
|
||||
The reason why the pool API includes objects of custom type ``T`` on both ends lies in the fact that the underlying
|
||||
transport usually comprises more than a single connection and as such the pool client flow often generates responses in
|
||||
an order that doesn't directly match the consumed requests.
|
||||
We could have built the pool logic in a way that reorders responses according to their requests before dispatching them
|
||||
to the application, but this would have meant that a single slow response could block the delivery of potentially many
|
||||
responses that would otherwise be ready for consumption by the application.
|
||||
|
||||
In order to prevent unnecessary head-of-line blocking the pool client-flow is allowed to dispatch responses as soon as
|
||||
they arrive, independently of the request order. Of course this means that there needs to be another way to associate a
|
||||
response with its respective request. The way that this is done is by allowing the application to pass along a custom
|
||||
"context" object with the request, which is then passed back to the application with the respective response.
|
||||
This context object of type ``T`` is completely opaque to Akka HTTP, i.e. you can pick whatever works best for your
|
||||
particular application scenario.
|
||||
|
||||
.. note::
|
||||
A consequence of using a pool is that long-running requests block a connection while running and may starve other
|
||||
requests. Make sure not to use a connection pool for long-running requests like long-polling GET requests.
|
||||
Use the :ref:`connection-level-api-java` instead.
|
||||
|
||||
Connection Allocation Logic
|
||||
---------------------------
|
||||
|
||||
This is how Akka HTTP allocates incoming requests to the available connection "slots":
|
||||
|
||||
1. If there is a connection alive and currently idle then schedule the request across this connection.
|
||||
2. If no connection is idle and there is still an unconnected slot then establish a new connection.
|
||||
3. If all connections are already established and "loaded" with other requests then pick the connection with the least
|
||||
open requests (< the configured ``pipelining-limit``) that only has requests with idempotent methods scheduled to it,
|
||||
if there is one.
|
||||
4. Otherwise apply back-pressure to the request source, i.e. stop accepting new requests.
|
||||
|
||||
For more information about scheduling more than one request at a time across a single connection see
|
||||
`this wikipedia entry on HTTP pipelining`__.
|
||||
|
||||
__ http://en.wikipedia.org/wiki/HTTP_pipelining
|
||||
|
||||
|
||||
|
||||
Retrying a Request
|
||||
------------------
|
||||
|
||||
If the ``max-retries`` pool config setting is greater than zero the pool retries idempotent requests for which
|
||||
a response could not be successfully retrieved. Idempotent requests are those whose HTTP method is defined to be
|
||||
idempotent by the HTTP spec, which are all the ones currently modelled by Akka HTTP except for the ``POST``, ``PATCH``
|
||||
and ``CONNECT`` methods.
|
||||
|
||||
When a response could not be received for a certain request there are essentially three possible error scenarios:
|
||||
|
||||
1. The request got lost on the way to the server.
|
||||
2. The server experiences a problem while processing the request.
|
||||
3. The response from the server got lost on the way back.
|
||||
|
||||
Since the host connector cannot know which one of these possible reasons caused the problem and therefore ``PATCH`` and
|
||||
``POST`` requests could have already triggered a non-idempotent action on the server these requests cannot be retried.
|
||||
|
||||
In these cases, as well as when all retries have not yielded a proper response, the pool produces a failed ``Try``
|
||||
(i.e. a ``scala.util.Failure``) together with the custom request context.
|
||||
|
||||
|
||||
Pool Shutdown
|
||||
-------------
|
||||
|
||||
Completing a pool client flow will simply detach the flow from the pool. The connection pool itself will continue to run
|
||||
as it may be serving other client flows concurrently or in the future. Only after the configured ``idle-timeout`` for
|
||||
the pool has expired will Akka HTTP automatically terminate the pool and free all its resources.
|
||||
|
||||
If a new client flow is requested with ``Http.get(system).cachedHostConnectionPool(...)`` or if an already existing client flow is
|
||||
re-materialized the respective pool is automatically and transparently restarted.
|
||||
|
||||
In addition to the automatic shutdown via the configured idle timeouts it's also possible to trigger the immediate
|
||||
shutdown of a specific pool by calling ``shutdown()`` on the :class:`HostConnectionPool` instance that the pool client
|
||||
flow materializes into. This ``shutdown()`` call produces a ``CompletionStage<Done>`` which is fulfilled when the pool
|
||||
termination has been completed.
|
||||
|
||||
It's also possible to trigger the immediate termination of *all* connection pools in the ``ActorSystem`` at the same
|
||||
time by calling ``Http.get(system).shutdownAllConnectionPools()``. This call too produces a ``CompletionStage<Done>`` which is fulfilled when
|
||||
all pools have terminated.
|
||||
|
||||
.. note::
|
||||
When encoutering unexpected ``akka.stream.AbruptTerminationException`` exceptions during ``ActorSystem`` **shutdown**
|
||||
please make sure that active connections are shut down before shutting down the entire system, this can be done by
|
||||
calling the ``Http.get(system).shutdownAllConnectionPools()`` method, and only once its CompletionStage completes,
|
||||
shutting down the actor system.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#host-level-example
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
.. _http-client-side-java:
|
||||
|
||||
Consuming HTTP-based Services (Client-Side)
|
||||
===========================================
|
||||
|
||||
All client-side functionality of Akka HTTP, for consuming HTTP-based services offered by other endpoints, is currently
|
||||
provided by the ``akka-http-core`` module.
|
||||
|
||||
Depending on your application's specific needs you can choose from three different API levels:
|
||||
|
||||
:ref:`connection-level-api-java`
|
||||
for full-control over when HTTP connections are opened/closed and how requests are scheduled across them
|
||||
|
||||
:ref:`host-level-api-java`
|
||||
for letting Akka HTTP manage a connection-pool to *one specific* host/port endpoint
|
||||
|
||||
:ref:`request-level-api-java`
|
||||
for letting Akka HTTP perform all connection management
|
||||
|
||||
You can interact with different API levels at the same time and, independently of which API level you choose,
|
||||
Akka HTTP will happily handle many thousand concurrent connections to a single or many different hosts.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
connection-level
|
||||
host-level
|
||||
request-level
|
||||
client-https-support
|
||||
websocket-support
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
.. _request-level-api-java:
|
||||
|
||||
Request-Level Client-Side API
|
||||
=============================
|
||||
|
||||
The request-level API is the most convenient way of using Akka HTTP's client-side functionality. It internally builds upon the
|
||||
:ref:`host-level-api-java` to provide you with a simple and easy-to-use way of retrieving HTTP responses from remote servers.
|
||||
Depending on your preference you can pick the flow-based or the future-based variant.
|
||||
|
||||
.. note::
|
||||
The request-level API is implemented on top of a connection pool that is shared inside the ActorSystem. A consequence of
|
||||
using a pool is that long-running requests block a connection while running and starve other requests. Make sure not to use
|
||||
the request-level API for long-running requests like long-polling GET requests. Use the :ref:`connection-level-api-java` instead.
|
||||
|
||||
Flow-Based Variant
|
||||
------------------
|
||||
|
||||
The flow-based variant of the request-level client-side API is presented by the ``Http().superPool(...)`` method.
|
||||
It creates a new "super connection pool flow", which routes incoming requests to a (cached) host connection pool
|
||||
depending on their respective effective URIs.
|
||||
|
||||
The ``Flow`` returned by ``Http().superPool(...)`` is very similar to the one from the :ref:`host-level-api-java`, so the
|
||||
:ref:`using-a-host-connection-pool-java` section also applies here.
|
||||
|
||||
However, there is one notable difference between a "host connection pool client flow" for the host-level API and a
|
||||
"super-pool flow":
|
||||
Since in the former case the flow has an implicit target host context the requests it takes don't need to have absolute
|
||||
URIs or a valid ``Host`` header. The host connection pool will automatically add a ``Host`` header if required.
|
||||
|
||||
For a super-pool flow this is not the case. All requests to a super-pool must either have an absolute URI or a valid
|
||||
``Host`` header, because otherwise it'd be impossible to find out which target endpoint to direct the request to.
|
||||
|
||||
|
||||
Future-Based Variant
|
||||
--------------------
|
||||
|
||||
Sometimes your HTTP client needs are very basic. You simply need the HTTP response for a certain request and don't
|
||||
want to bother with setting up a full-blown streaming infrastructure.
|
||||
|
||||
For these cases Akka HTTP offers the ``Http().singleRequest(...)`` method, which simply turns an ``HttpRequest`` instance
|
||||
into ``CompletionStage<HttpResponse>``. Internally the request is dispatched across the (cached) host connection pool for the
|
||||
request's effective URI.
|
||||
|
||||
Just like in the case of the super-pool flow described above the request must have either an absolute URI or a valid
|
||||
``Host`` header, otherwise the returned future will be completed with an error.
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#single-request-example
|
||||
|
||||
Using the Future-Based API in Actors
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
When using the ``CompletionStage`` based API from inside an ``Actor``, all the usual caveats apply to how one should deal
|
||||
with the futures completion. For example you should not access the Actors state from within the CompletionStage's callbacks
|
||||
(such as ``map``, ``onComplete``, ...) and instead you should use the ``pipe`` pattern to pipe the result back
|
||||
to the Actor as a message:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/HttpClientExampleDocTest.java#single-request-in-actor-example
|
||||
|
||||
|
||||
.. warning::
|
||||
Be sure to consume the response entities ``dataBytes:Source[ByteString,Unit]`` by for example connecting it
|
||||
to a ``Sink`` (for example ``response.discardEntityBytes(Materializer)`` if you don't care about the
|
||||
response entity), since otherwise Akka HTTP (and the underlying Streams infrastructure) will understand the
|
||||
lack of entity consumption as a back-pressure signal and stop reading from the underlying TCP connection!
|
||||
|
||||
This is a feature of Akka HTTP that allows consuming entities (and pulling them through the network) in
|
||||
a streaming fashion, and only *on demand* when the client is ready to consume the bytes -
|
||||
it may be a bit surprising at first though.
|
||||
|
||||
There are tickets open about automatically dropping entities if not consumed (`#18716`_ and `#18540`_),
|
||||
so these may be implemented in the near future.
|
||||
|
||||
.. _#18540: https://github.com/akka/akka/issues/18540
|
||||
.. _#18716: https://github.com/akka/akka/issues/18716
|
||||
|
|
@ -1,128 +0,0 @@
|
|||
.. _client-side-websocket-support-java:
|
||||
|
||||
Client-Side WebSocket Support
|
||||
=============================
|
||||
|
||||
Client side WebSocket support is available through ``Http.singleWebSocketRequest`` ,
|
||||
``Http.webSocketClientFlow`` and ``Http.webSocketClientLayer``.
|
||||
|
||||
A WebSocket consists of two streams of messages, incoming messages (a :class:`Sink`) and outgoing messages
|
||||
(a :class:`Source`) where either may be signalled first; or even be the only direction in which messages flow
|
||||
during the lifetime of the connection. Therefore a WebSocket connection is modelled as either something you connect a
|
||||
``Flow<Message, Message, Mat>`` to or a ``Flow<Message, Message, Mat>`` that you connect a ``Source<Message, Mat>``
|
||||
and a ``Sink<Message, Mat>`` to.
|
||||
|
||||
A WebSocket request starts with a regular HTTP request which contains an ``Upgrade`` header (and possibly
|
||||
other regular HTTP request properties), so in addition to the flow of messages there also is an initial response
|
||||
from the server, this is modelled with :class:`WebSocketUpgradeResponse`.
|
||||
|
||||
The methods of the WebSocket client API handle the upgrade to WebSocket on connection success and materializes
|
||||
the connected WebSocket stream. If the connection fails, for example with a ``404 NotFound`` error, this regular
|
||||
HTTP result can be found in ``WebSocketUpgradeResponse.response``
|
||||
|
||||
|
||||
.. note::
|
||||
Make sure to read and understand the section about :ref:`half-closed-client-websockets-java` as the behavior
|
||||
when using WebSockets for one-way communication may not be what you would expect.
|
||||
|
||||
Message
|
||||
-------
|
||||
Messages sent and received over a WebSocket can be either :class:`TextMessage` s or :class:`BinaryMessage` s and each
|
||||
of those can be either strict (all data in one chunk) or streamed. In typical applications messages will be strict as
|
||||
WebSockets are usually deployed to communicate using small messages not stream data, the protocol does however
|
||||
allow this (by not marking the first fragment as final, as described in `rfc 6455 section 5.2`__).
|
||||
|
||||
__ https://tools.ietf.org/html/rfc6455#section-5.2
|
||||
|
||||
The strict text is available from ``TextMessage.getStrictText`` and strict binary data from
|
||||
``BinaryMessage.getStrictData``.
|
||||
|
||||
For streamed messages ``BinaryMessage.getStreamedData`` and ``TextMessage.getStreamedText`` is used to access the data.
|
||||
In these cases the data is provided as a ``Source<ByteString, NotUsed>`` for binary and ``Source<String, NotUsed>``
|
||||
for text messages.
|
||||
|
||||
|
||||
singleWebSocketRequest
|
||||
----------------------
|
||||
``singleWebSocketRequest`` takes a :class:`WebSocketRequest` and a flow it will connect to the source and
|
||||
sink of the WebSocket connection. It will trigger the request right away and returns a tuple containing a
|
||||
``CompletionStage<WebSocketUpgradeResponse>`` and the materialized value from the flow passed to the method.
|
||||
|
||||
The future will succeed when the WebSocket connection has been established or the server returned a regular
|
||||
HTTP response, or fail if the connection fails with an exception.
|
||||
|
||||
Simple example sending a message and printing any incoming message:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/WebSocketClientExampleTest.java
|
||||
:include: single-WebSocket-request
|
||||
|
||||
The websocket request may also include additional headers, like in this example, HTTP Basic Auth:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/WebSocketClientExampleTest.java
|
||||
:include: authorized-single-WebSocket-request
|
||||
|
||||
|
||||
webSocketClientFlow
|
||||
-------------------
|
||||
``webSocketClientFlow`` takes a request, and returns a ``Flow<Message, Message, CompletionStage<WebSocketUpgradeResponse>>``.
|
||||
|
||||
The future that is materialized from the flow will succeed when the WebSocket connection has been established or
|
||||
the server returned a regular HTTP response, or fail if the connection fails with an exception.
|
||||
|
||||
.. note::
|
||||
The :class:`Flow` that is returned by this method can only be materialized once. For each request a new
|
||||
flow must be acquired by calling the method again.
|
||||
|
||||
Simple example sending a message and printing any incoming message:
|
||||
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/WebSocketClientExampleTest.java
|
||||
:include: WebSocket-client-flow
|
||||
|
||||
|
||||
webSocketClientLayer
|
||||
--------------------
|
||||
Just like the :ref:`http-client-layer-java` for regular HTTP requests, the WebSocket layer can be used fully detached from the
|
||||
underlying TCP interface. The same scenarios as described for regular HTTP requests apply here.
|
||||
|
||||
The returned layer forms a ``BidiFlow<Message, SslTlsOutbound, SslTlsInbound, Message, CompletionStage<WebSocketUpgradeResponse>>``.
|
||||
|
||||
.. _half-closed-client-websockets-java:
|
||||
|
||||
|
||||
Half-Closed WebSockets
|
||||
----------------------
|
||||
The Akka HTTP WebSocket API does not support half-closed connections which means that if the either stream completes the
|
||||
entire connection is closed (after a "Closing Handshake" has been exchanged or a timeout of 3 seconds has passed).
|
||||
This may lead to unexpected behavior, for example if we are trying to only consume messages coming from the server,
|
||||
like this:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/WebSocketClientExampleTest.java
|
||||
:include: half-closed-WebSocket-closing
|
||||
|
||||
This will in fact quickly close the connection because of the ``Source.empty`` being completed immediately when the
|
||||
stream is materialized. To solve this you can make sure to not complete the outgoing source by using for example
|
||||
``Source.maybe`` like this:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/WebSocketClientExampleTest.java
|
||||
:include: half-closed-WebSocket-working
|
||||
|
||||
This will keep the outgoing source from completing, but without emitting any elements until the ``CompletableFuture`` is manually
|
||||
completed which makes the ``Source`` complete and the connection to close.
|
||||
|
||||
The same problem holds true if emitting a finite number of elements, as soon as the last element is reached the ``Source``
|
||||
will close and cause the connection to close. To avoid that you can concatenate ``Source.maybe`` to the finite stream:
|
||||
|
||||
.. includecode:: ../../code/docs/http/javadsl/WebSocketClientExampleTest.java
|
||||
:include: half-closed-WebSocket-finite
|
||||
|
||||
Scenarios that exist with the two streams in a WebSocket and possible ways to deal with it:
|
||||
|
||||
=========================================== ================================================================================
|
||||
Scenario Possible solution
|
||||
=========================================== ================================================================================
|
||||
Two-way communication ``Flow.fromSinkAndSource``, or ``Flow.map`` for a request-response protocol
|
||||
Infinite incoming stream, no outgoing ``Flow.fromSinkAndSource(someSink, Source.maybe())``
|
||||
Infinite outgoing stream, no incoming ``Flow.fromSinkAndSource(Sink.ignore(), yourSource)``
|
||||
=========================================== ================================================================================
|
||||
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
Encoding / Decoding
|
||||
===================
|
||||
|
||||
The `HTTP spec`_ defines a ``Content-Encoding`` header, which signifies whether the entity body of an HTTP message is
|
||||
"encoded" and, if so, by which algorithm. The only commonly used content encodings are compression algorithms.
|
||||
|
||||
Currently Akka HTTP supports the compression and decompression of HTTP requests and responses with the ``gzip`` or
|
||||
``deflate`` encodings.
|
||||
The core logic for this lives in the `akka.http.scaladsl.coding`_ package.
|
||||
|
||||
The support is not enabled automatically, but must be explicitly requested.
|
||||
For enabling message encoding/decoding with :ref:`Routing DSL <http-high-level-server-side-api>` see the :ref:`CodingDirectives`.
|
||||
|
||||
.. _HTTP spec: http://tools.ietf.org/html/rfc7231#section-3.1.2.1
|
||||
.. _akka.http.scaladsl.coding: @github@/akka-http/src/main/scala/akka/http/scaladsl/coding
|
||||
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
.. _http-java-common:
|
||||
|
||||
Common Abstractions (Client- and Server-Side)
|
||||
=============================================
|
||||
|
||||
HTTP and related specifications define a great number of concepts and functionality that is not specific to either
|
||||
HTTP's client- or server-side since they are meaningful on both end of an HTTP connection.
|
||||
The documentation for their counterparts in Akka HTTP lives in this section rather than in the ones for the
|
||||
:ref:`Client-Side API <http-client-side>`, :ref:`http-low-level-server-side-api` or :ref:`http-high-level-server-side-api`,
|
||||
which are specific to one side only.
|
||||
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
../http-model
|
||||
marshalling
|
||||
unmarshalling
|
||||
de-coding
|
||||
json-support
|
||||
timeouts
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
.. _json-support-java:
|
||||
|
||||
Json Support
|
||||
============
|
||||
|
||||
akka-http provides support to convert application-domain objects from and to JSON using jackson_ in an
|
||||
extra artifact.
|
||||
|
||||
Integration with other JSON libraries may be supported by the community.
|
||||
See `the list of current community extensions for Akka HTTP`_.
|
||||
|
||||
.. _`the list of current community extensions for Akka HTTP`: http://akka.io/community/#extensions-to-akka-http
|
||||
|
||||
.. _json-jackson-support-java:
|
||||
|
||||
Json Support via Jackson
|
||||
------------------------
|
||||
|
||||
To make use of the support module, you need to add a dependency on `akka-http-jackson-experimental`.
|
||||
|
||||
Use ``akka.http.javadsl.marshallers.jackson.Jackson.unmarshaller(T.class)`` to create an ``Unmarshaller<HttpEntity,T>`` which expects the request
|
||||
body (HttpEntity) to be of type ``application/json`` and converts it to ``T`` using Jackson.
|
||||
|
||||
See `this example`__ in the sources for an example.
|
||||
|
||||
Use ``akka.http.javadsl.marshallers.jackson.Jackson.marshaller(T.class)`` to create a ``Marshaller<T,RequestEntity>`` which can be used with
|
||||
``RequestContext.complete`` or ``RouteDirectives.complete`` to convert a POJO to an HttpResponse.
|
||||
|
||||
|
||||
.. _jackson: https://github.com/FasterXML/jackson
|
||||
__ @github@/akka-http-tests/src/main/java/akka/http/javadsl/server/examples/petstore/PetStoreExample.java
|
||||
|
|
@ -1,166 +0,0 @@
|
|||
.. _http-marshalling-java:
|
||||
|
||||
Marshalling
|
||||
===========
|
||||
TODO overhaul for Java
|
||||
|
||||
"Marshalling" is the process of converting a higher-level (object) structure into some kind of lower-level
|
||||
representation, often a "wire format". Other popular names for it are "Serialization" or "Pickling".
|
||||
|
||||
In Akka HTTP "Marshalling" means the conversion of an object of type ``T`` into a lower-level target type,
|
||||
e.g. a ``MessageEntity`` (which forms the "entity body" of an HTTP request or response) or a full ``HttpRequest`` or
|
||||
``HttpResponse``.
|
||||
|
||||
|
||||
Basic Design
|
||||
------------
|
||||
|
||||
Marshalling of instances of type ``A`` into instances of type ``B`` is performed by a ``Marshaller[A, B]``.
|
||||
Akka HTTP also predefines a number of helpful aliases for the types of marshallers that you'll likely work with most:
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/marshalling/package.scala
|
||||
:snippet: marshaller-aliases
|
||||
|
||||
Contrary to what you might initially expect ``Marshaller[A, B]`` is not a plain function ``A => B`` but rather
|
||||
essentially a function ``A => Future[List[Marshalling[B]]]``.
|
||||
Let's dissect this rather complicated looking signature piece by piece to understand what marshallers are designed this
|
||||
way.
|
||||
Given an instance of type ``A`` a ``Marshaller[A, B]`` produces:
|
||||
|
||||
1. A ``Future``: This is probably quite clear. Marshallers are not required to synchronously produce a result, so instead
|
||||
they return a future, which allows for asynchronicity in the marshalling process.
|
||||
|
||||
2. of ``List``: Rather than only a single target representation for ``A`` marshallers can offer several ones. Which
|
||||
one will be rendered onto the wire in the end is decided by content negotiation.
|
||||
For example, the ``ToEntityMarshaller[OrderConfirmation]`` might offer a JSON as well as an XML representation.
|
||||
The client can decide through the addition of an ``Accept`` request header which one is preferred. If the client doesn't
|
||||
express a preference the first representation is picked.
|
||||
|
||||
3. of ``Marshalling[B]``: Rather than returning an instance of ``B`` directly marshallers first produce a
|
||||
``Marshalling[B]``. This allows for querying the ``MediaType`` and potentially the ``HttpCharset`` that the marshaller
|
||||
will produce before the actual marshalling is triggered. Apart from enabling content negotiation this design allows for
|
||||
delaying the actual construction of the marshalling target instance to the very last moment when it is really needed.
|
||||
|
||||
This is how ``Marshalling`` is defined:
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala
|
||||
:snippet: marshalling
|
||||
|
||||
|
||||
Predefined Marshallers
|
||||
----------------------
|
||||
|
||||
Akka HTTP already predefines a number of marshallers for the most common types.
|
||||
Specifically these are:
|
||||
|
||||
- PredefinedToEntityMarshallers_
|
||||
|
||||
- ``Array[Byte]``
|
||||
- ``ByteString``
|
||||
- ``Array[Char]``
|
||||
- ``String``
|
||||
- ``akka.http.scaladsl.model.FormData``
|
||||
- ``akka.http.scaladsl.model.MessageEntity``
|
||||
- ``T <: akka.http.scaladsl.model.Multipart``
|
||||
|
||||
- PredefinedToResponseMarshallers_
|
||||
|
||||
- ``T``, if a ``ToEntityMarshaller[T]`` is available
|
||||
- ``HttpResponse``
|
||||
- ``StatusCode``
|
||||
- ``(StatusCode, T)``, if a ``ToEntityMarshaller[T]`` is available
|
||||
- ``(Int, T)``, if a ``ToEntityMarshaller[T]`` is available
|
||||
- ``(StatusCode, immutable.Seq[HttpHeader], T)``, if a ``ToEntityMarshaller[T]`` is available
|
||||
- ``(Int, immutable.Seq[HttpHeader], T)``, if a ``ToEntityMarshaller[T]`` is available
|
||||
|
||||
- PredefinedToRequestMarshallers_
|
||||
|
||||
- ``HttpRequest``
|
||||
- ``Uri``
|
||||
- ``(HttpMethod, Uri, T)``, if a ``ToEntityMarshaller[T]`` is available
|
||||
- ``(HttpMethod, Uri, immutable.Seq[HttpHeader], T)``, if a ``ToEntityMarshaller[T]`` is available
|
||||
|
||||
- GenericMarshallers_
|
||||
|
||||
- ``Marshaller[Throwable, T]``
|
||||
- ``Marshaller[Option[A], B]``, if a ``Marshaller[A, B]`` and an ``EmptyValue[B]`` is available
|
||||
- ``Marshaller[Either[A1, A2], B]``, if a ``Marshaller[A1, B]`` and a ``Marshaller[A2, B]`` is available
|
||||
- ``Marshaller[Future[A], B]``, if a ``Marshaller[A, B]`` is available
|
||||
- ``Marshaller[Try[A], B]``, if a ``Marshaller[A, B]`` is available
|
||||
|
||||
.. _PredefinedToEntityMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToEntityMarshallers.scala
|
||||
.. _PredefinedToResponseMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToResponseMarshallers.scala
|
||||
.. _PredefinedToRequestMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/PredefinedToRequestMarshallers.scala
|
||||
.. _GenericMarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/marshalling/GenericMarshallers.scala
|
||||
|
||||
|
||||
Implicit Resolution
|
||||
-------------------
|
||||
|
||||
The marshalling infrastructure of Akka HTTP relies on a type-class based approach, which means that ``Marshaller``
|
||||
instances from a certain type ``A`` to a certain type ``B`` have to be available implicitly.
|
||||
|
||||
The implicits for most of the predefined marshallers in Akka HTTP are provided through the companion object of the
|
||||
``Marshaller`` trait. This means that they are always available and never need to be explicitly imported.
|
||||
Additionally, you can simply "override" them by bringing your own custom version into local scope.
|
||||
|
||||
|
||||
Custom Marshallers
|
||||
------------------
|
||||
|
||||
Akka HTTP gives you a few convenience tools for constructing marshallers for your own types.
|
||||
Before you do that you need to think about what kind of marshaller you want to create.
|
||||
If all your marshaller needs to produce is a ``MessageEntity`` then you should probably provide a
|
||||
``ToEntityMarshaller[T]``. The advantage here is that it will work on both the client- as well as the server-side since
|
||||
a ``ToResponseMarshaller[T]`` as well as a ``ToRequestMarshaller[T]`` can automatically be created if a
|
||||
``ToEntityMarshaller[T]`` is available.
|
||||
|
||||
If, however, your marshaller also needs to set things like the response status code, the request method, the request URI
|
||||
or any headers then a ``ToEntityMarshaller[T]`` won't work. You'll need to fall down to providing a
|
||||
``ToResponseMarshaller[T]`` or a ``ToRequestMarshaller[T]`` directly.
|
||||
|
||||
For writing your own marshallers you won't have to "manually" implement the ``Marshaller`` trait directly.
|
||||
Rather, it should be possible to use one of the convenience construction helpers defined on the ``Marshaller``
|
||||
companion:
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/marshalling/Marshaller.scala
|
||||
:snippet: marshaller-creation
|
||||
|
||||
|
||||
Deriving Marshallers
|
||||
--------------------
|
||||
|
||||
Sometimes you can save yourself some work by reusing existing marshallers for your custom ones.
|
||||
The idea is to "wrap" an existing marshaller with some logic to "re-target" it to your type.
|
||||
|
||||
In this regard wrapping a marshaller can mean one or both of the following two things:
|
||||
|
||||
- Transform the input before it reaches the wrapped marshaller
|
||||
- Transform the output of the wrapped marshaller
|
||||
|
||||
For the latter (transforming the output) you can use ``baseMarshaller.map``, which works exactly as it does for functions.
|
||||
For the former (transforming the input) you have four alternatives:
|
||||
|
||||
- ``baseMarshaller.compose``
|
||||
- ``baseMarshaller.composeWithEC``
|
||||
- ``baseMarshaller.wrap``
|
||||
- ``baseMarshaller.wrapWithEC``
|
||||
|
||||
``compose`` works just like it does for functions.
|
||||
``wrap`` is a compose that allows you to also change the ``ContentType`` that the marshaller marshals to.
|
||||
The ``...WithEC`` variants allow you to receive an ``ExecutionContext`` internally if you need one, without having to
|
||||
depend on one being available implicitly at the usage site.
|
||||
|
||||
|
||||
Using Marshallers
|
||||
-----------------
|
||||
|
||||
In many places throughput Akka HTTP marshallers are used implicitly, e.g. when you define how to :ref:`-complete-` a
|
||||
request using the :ref:`Routing DSL <http-high-level-server-side-api>`.
|
||||
|
||||
However, you can also use the marshalling infrastructure directly if you wish, which can be useful for example in tests.
|
||||
The best entry point for this is the ``akka.http.scaladsl.marshalling.Marshal`` object, which you can use like this:
|
||||
|
||||
.. TODO rewrite for Java
|
||||
.. .. includecode2:: ../../code/docs/http/scaladsl/MarshalSpec.scala
|
||||
:snippet: use marshal
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
.. _http-timeouts-java:
|
||||
|
||||
Akka HTTP Timeouts
|
||||
==================
|
||||
|
||||
Akka HTTP comes with a variety of built-in timeout mechanisms to protect your servers from malicious attacks or
|
||||
programming mistakes. Some of these are simply configuration options (which may be overriden in code) while others
|
||||
are left to the streaming APIs and are easily implementable as patterns in user-code directly.
|
||||
|
||||
Common timeouts
|
||||
---------------
|
||||
|
||||
.. _idle-timeouts-java:
|
||||
|
||||
Idle timeouts
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
The ``idle-timeout`` is a global setting which sets the maximum inactivity time of a given connection.
|
||||
In other words, if a connection is open but no request/response is being written to it for over ``idle-timeout`` time,
|
||||
the connection will be automatically closed.
|
||||
|
||||
The setting works the same way for all connections, be it server-side or client-side, and it's configurable
|
||||
independently for each of those using the following keys::
|
||||
|
||||
akka.http.server.idle-timeout
|
||||
akka.http.client.idle-timeout
|
||||
akka.http.host-connection-pool.idle-timeout
|
||||
akka.http.host-connection-pool.client.idle-timeout
|
||||
|
||||
.. note::
|
||||
For the connection pooled client side the idle period is counted only when the pool has no pending requests waiting.
|
||||
|
||||
|
||||
Server timeouts
|
||||
---------------
|
||||
|
||||
.. _request-timeout-java:
|
||||
|
||||
Request timeout
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Request timeouts are a mechanism that limits the maximum time it may take to produce an ``HttpResponse`` from a route.
|
||||
If that deadline is not met the server will automatically inject a Service Unavailable HTTP response and close the connection
|
||||
to prevent it from leaking and staying around indefinitely (for example if by programming error a Future would never complete,
|
||||
never sending the real response otherwise).
|
||||
|
||||
The default ``HttpResponse`` that is written when a request timeout is exceeded looks like this:
|
||||
|
||||
.. includecode2:: /../../akka-http-core/src/main/scala/akka/http/impl/engine/server/HttpServerBluePrint.scala
|
||||
:snippet: default-request-timeout-httpresponse
|
||||
|
||||
A default request timeout is applied globally to all routes and can be configured using the
|
||||
``akka.http.server.request-timeout`` setting (which defaults to 20 seconds).
|
||||
|
||||
.. note::
|
||||
Please note that if multiple requests (``R1,R2,R3,...``) were sent by a client (see "HTTP pipelining")
|
||||
using the same connection and the ``n-th`` request triggers a request timeout the server will reply with an Http Response
|
||||
and close the connection, leaving the ``(n+1)-th`` (and subsequent requests on the same connection) unhandled.
|
||||
|
||||
The request timeout can be configured at run-time for a given route using the any of the :ref:`TimeoutDirectives`.
|
||||
|
||||
Bind timeout
|
||||
^^^^^^^^^^^^
|
||||
|
||||
The bind timeout is the time period within which the TCP binding process must be completed (using any of the ``Http().bind*`` methods).
|
||||
It can be configured using the ``akka.http.server.bind-timeout`` setting.
|
||||
|
||||
Client timeouts
|
||||
---------------
|
||||
|
||||
Connecting timeout
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The connecting timeout is the time period within which the TCP connecting process must be completed.
|
||||
Tweaking it should rarely be required, but it allows erroring out the connection in case a connection
|
||||
is unable to be established for a given amount of time.
|
||||
|
||||
it can be configured using the ``akka.http.client.connecting-timeout`` setting.
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
.. _http-unmarshalling-java:
|
||||
|
||||
Unmarshalling
|
||||
=============
|
||||
TODO overhaul for Java
|
||||
|
||||
"Unmarshalling" is the process of converting some kind of a lower-level representation, often a "wire format", into a
|
||||
higher-level (object) structure. Other popular names for it are "Deserialization" or "Unpickling".
|
||||
|
||||
In Akka HTTP "Unmarshalling" means the conversion of a lower-level source object, e.g. a ``MessageEntity``
|
||||
(which forms the "entity body" of an HTTP request or response) or a full ``HttpRequest`` or ``HttpResponse``,
|
||||
into an instance of type ``T``.
|
||||
|
||||
|
||||
Basic Design
|
||||
------------
|
||||
|
||||
Unmarshalling of instances of type ``A`` into instances of type ``B`` is performed by an ``Unmarshaller[A, B]``.
|
||||
Akka HTTP also predefines a number of helpful aliases for the types of unmarshallers that you'll likely work with most:
|
||||
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/package.scala
|
||||
:snippet: unmarshaller-aliases
|
||||
|
||||
At its core an ``Unmarshaller[A, B]`` is very similar to a ``Function<A, CompletionStage<B>>`` and as such quite a bit simpler
|
||||
than its :ref:`marshalling <http-marshalling-java>` counterpart. The process of unmarshalling does not have to support
|
||||
content negotiation which saves two additional layers of indirection that are required on the marshalling side.
|
||||
|
||||
|
||||
Predefined Unmarshallers
|
||||
------------------------
|
||||
|
||||
Akka HTTP already predefines a number of marshallers for the most common types.
|
||||
Specifically these are:
|
||||
|
||||
- PredefinedFromStringUnmarshallers_
|
||||
|
||||
- ``Byte``
|
||||
- ``Short``
|
||||
- ``Int``
|
||||
- ``Long``
|
||||
- ``Float``
|
||||
- ``Double``
|
||||
- ``Boolean``
|
||||
|
||||
- PredefinedFromEntityUnmarshallers_
|
||||
|
||||
- ``Array[Byte]``
|
||||
- ``ByteString``
|
||||
- ``Array[Char]``
|
||||
- ``String``
|
||||
- ``akka.http.scaladsl.model.FormData``
|
||||
|
||||
- GenericUnmarshallers_
|
||||
|
||||
- ``Unmarshaller[T, T]`` (identity unmarshaller)
|
||||
- ``Unmarshaller[Option[A], B]``, if an ``Unmarshaller[A, B]`` is available
|
||||
- ``Unmarshaller[A, Option[B]]``, if an ``Unmarshaller[A, B]`` is available
|
||||
|
||||
.. _PredefinedFromStringUnmarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromStringUnmarshallers.scala
|
||||
.. _PredefinedFromEntityUnmarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/PredefinedFromEntityUnmarshallers.scala
|
||||
.. _GenericUnmarshallers: @github@/akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/GenericUnmarshallers.scala
|
||||
|
||||
|
||||
Implicit Resolution
|
||||
-------------------
|
||||
|
||||
The unmarshalling infrastructure of Akka HTTP relies on a type-class based approach, which means that ``Unmarshaller``
|
||||
instances from a certain type ``A`` to a certain type ``B`` have to be available implicitly.
|
||||
|
||||
The implicits for most of the predefined unmarshallers in Akka HTTP are provided through the companion object of the
|
||||
``Unmarshaller`` trait. This means that they are always available and never need to be explicitly imported.
|
||||
Additionally, you can simply "override" them by bringing your own custom version into local scope.
|
||||
|
||||
|
||||
Custom Unmarshallers
|
||||
--------------------
|
||||
|
||||
Akka HTTP gives you a few convenience tools for constructing unmarshallers for your own types.
|
||||
Usually you won't have to "manually" implement the ``Unmarshaller`` trait directly.
|
||||
Rather, it should be possible to use one of the convenience construction helpers defined on the ``Unmarshaller``
|
||||
companion:
|
||||
|
||||
TODO rewrite sample for Java
|
||||
|
||||
..
|
||||
.. includecode2:: /../../akka-http/src/main/scala/akka/http/scaladsl/unmarshalling/Unmarshaller.scala
|
||||
:snippet: unmarshaller-creation
|
||||
|
||||
|
||||
Deriving Unmarshallers
|
||||
----------------------
|
||||
|
||||
Sometimes you can save yourself some work by reusing existing unmarshallers for your custom ones.
|
||||
The idea is to "wrap" an existing unmarshaller with some logic to "re-target" it to your type.
|
||||
|
||||
Usually what you want to do is to transform the output of some existing unmarshaller and convert it to your type.
|
||||
For this type of unmarshaller transformation Akka HTTP defines these methods:
|
||||
|
||||
- ``baseUnmarshaller.transform``
|
||||
- ``baseUnmarshaller.map``
|
||||
- ``baseUnmarshaller.mapWithInput``
|
||||
- ``baseUnmarshaller.flatMap``
|
||||
- ``baseUnmarshaller.flatMapWithInput``
|
||||
- ``baseUnmarshaller.recover``
|
||||
- ``baseUnmarshaller.withDefaultValue``
|
||||
- ``baseUnmarshaller.mapWithCharset`` (only available for FromEntityUnmarshallers)
|
||||
- ``baseUnmarshaller.forContentTypes`` (only available for FromEntityUnmarshallers)
|
||||
|
||||
The method signatures should make their semantics relatively clear.
|
||||
|
||||
|
||||
Using Unmarshallers
|
||||
-------------------
|
||||
|
||||
In many places throughput Akka HTTP unmarshallers are used implicitly, e.g. when you want to access the :ref:`-entity-`
|
||||
of a request using the :ref:`Routing DSL <http-high-level-server-side-api>`.
|
||||
|
||||
However, you can also use the unmarshalling infrastructure directly if you wish, which can be useful for example in tests.
|
||||
The best entry point for this is the ``akka.http.scaladsl.unmarshalling.Unmarshal`` object, which you can use like this:
|
||||
|
||||
.. TODO rewrite for java
|
||||
.. .. includecode2:: ../../code/docs/http/scaladsl/UnmarshalSpec.scala
|
||||
:snippet: use unmarshal
|
||||
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
.. _akka-http-configuration-java:
|
||||
|
||||
Configuration
|
||||
=============
|
||||
|
||||
Just like any other Akka module Akka HTTP is configured via `Typesafe Config`_.
|
||||
Usually this means that you provide an ``application.conf`` which contains all the application-specific settings that
|
||||
differ from the default ones provided by the reference configuration files from the individual Akka modules.
|
||||
|
||||
These are the relevant default configuration values for the Akka HTTP modules.
|
||||
|
||||
akka-http-core
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
.. literalinclude:: ../../../../akka-http-core/src/main/resources/reference.conf
|
||||
:language: none
|
||||
|
||||
|
||||
akka-http
|
||||
~~~~~~~~~
|
||||
|
||||
.. literalinclude:: ../../../../akka-http/src/main/resources/reference.conf
|
||||
:language: none
|
||||
|
||||
|
||||
The other Akka HTTP modules do not offer any configuration via `Typesafe Config`_.
|
||||
|
||||
.. _Typesafe Config: https://github.com/typesafehub/config
|
||||
|
|
@ -1,286 +0,0 @@
|
|||
.. _http-model-java:
|
||||
|
||||
HTTP Model
|
||||
==========
|
||||
|
||||
Akka HTTP model contains a deeply structured, fully immutable, case-class based model of all the major HTTP data
|
||||
structures, like HTTP requests, responses and common headers.
|
||||
It lives in the *akka-http-core* module and forms the basis for most of Akka HTTP's APIs.
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
Since akka-http-core provides the central HTTP data structures you will find the following import in quite a
|
||||
few places around the code base (and probably your own code as well):
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: import-model
|
||||
|
||||
This brings all of the most relevant types in scope, mainly:
|
||||
|
||||
- ``HttpRequest`` and ``HttpResponse``, the central message model
|
||||
- ``headers``, the package containing all the predefined HTTP header models and supporting types
|
||||
- Supporting types like ``Uri``, ``HttpMethods``, ``MediaTypes``, ``StatusCodes``, etc.
|
||||
|
||||
A common pattern is that the model of a certain entity is represented by an immutable type (class or trait),
|
||||
while the actual instances of the entity defined by the HTTP spec live in an accompanying object carrying the name of
|
||||
the type plus a trailing plural 's'.
|
||||
|
||||
For example:
|
||||
|
||||
- Defined ``HttpMethod`` instances are defined as static fields of the ``HttpMethods`` class.
|
||||
- Defined ``HttpCharset`` instances are defined as static fields of the ``HttpCharsets`` class.
|
||||
- Defined ``HttpEncoding`` instances are defined as static fields of the ``HttpEncodings`` class.
|
||||
- Defined ``HttpProtocol`` instances are defined as static fields of the ``HttpProtocols`` class.
|
||||
- Defined ``MediaType`` instances are defined as static fields of the ``MediaTypes`` class.
|
||||
- Defined ``StatusCode`` instances are defined as static fields of the ``StatusCodes`` class.
|
||||
|
||||
HttpRequest
|
||||
-----------
|
||||
|
||||
``HttpRequest`` and ``HttpResponse`` are the basic immutable classes representing HTTP messages.
|
||||
|
||||
An ``HttpRequest`` consists of
|
||||
|
||||
- a method (GET, POST, etc.)
|
||||
- a URI
|
||||
- a seq of headers
|
||||
- an entity (body data)
|
||||
- a protocol
|
||||
|
||||
Here are some examples how to construct an ``HttpRequest``:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: construct-request
|
||||
|
||||
In its basic form ``HttpRequest.create`` creates an empty default GET request without headers which can then be
|
||||
transformed using one of the ``withX`` methods, ``addHeader``, or ``addHeaders``. Each of those will create a
|
||||
new immutable instance, so instances can be shared freely. There exist some overloads for ``HttpRequest.create`` that
|
||||
simplify creating requests for common cases. Also, to aid readability, there are predefined alternatives for ``create``
|
||||
named after HTTP methods to create a request with a given method and uri directly.
|
||||
|
||||
HttpResponse
|
||||
------------
|
||||
|
||||
An ``HttpResponse`` consists of
|
||||
|
||||
- a status code
|
||||
- a list of headers
|
||||
- an entity (body data)
|
||||
- a protocol
|
||||
|
||||
Here are some examples how to construct an ``HttpResponse``:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: construct-response
|
||||
|
||||
In addition to the simple ``HttpEntities.create`` methods which create an entity from a fixed ``String`` or ``ByteString``
|
||||
as shown here the Akka HTTP model defines a number of subclasses of ``HttpEntity`` which allow body data to be specified as a
|
||||
stream of bytes. All of these types can be created using the method on ``HttpEntites``.
|
||||
|
||||
|
||||
.. _HttpEntity-java:
|
||||
|
||||
HttpEntity
|
||||
----------
|
||||
|
||||
An ``HttpEntity`` carries the data bytes of a message together with its Content-Type and, if known, its Content-Length.
|
||||
In Akka HTTP there are five different kinds of entities which model the various ways that message content can be
|
||||
received or sent:
|
||||
|
||||
HttpEntityStrict
|
||||
The simplest entity, which is used when all the entity are already available in memory.
|
||||
It wraps a plain ``ByteString`` and represents a standard, unchunked entity with a known ``Content-Length``.
|
||||
|
||||
|
||||
HttpEntityDefault
|
||||
The general, unchunked HTTP/1.1 message entity.
|
||||
It has a known length and presents its data as a ``Source[ByteString]`` which can be only materialized once.
|
||||
It is an error if the provided source doesn't produce exactly as many bytes as specified.
|
||||
The distinction of ``HttpEntityStrict`` and ``HttpEntityDefault`` is an API-only one. One the wire,
|
||||
both kinds of entities look the same.
|
||||
|
||||
|
||||
HttpEntityChunked
|
||||
The model for HTTP/1.1 `chunked content`__ (i.e. sent with ``Transfer-Encoding: chunked``).
|
||||
The content length is unknown and the individual chunks are presented as a ``Source[ChunkStreamPart]``.
|
||||
A ``ChunkStreamPart`` is either a non-empty chunk or the empty last chunk containing optional trailer headers.
|
||||
The stream consists of zero or more non-empty chunks parts and can be terminated by an optional last chunk.
|
||||
|
||||
|
||||
HttpEntityCloseDelimited
|
||||
An unchunked entity of unknown length that is implicitly delimited by closing the connection (``Connection: close``).
|
||||
Content data is presented as a ``Source[ByteString]``.
|
||||
Since the connection must be closed after sending an entity of this type it can only be used on the server-side for
|
||||
sending a response.
|
||||
Also, the main purpose of ``CloseDelimited`` entities is compatibility with HTTP/1.0 peers, which do not support
|
||||
chunked transfer encoding. If you are building a new application and are not constrained by legacy requirements you
|
||||
shouldn't rely on ``CloseDelimited`` entities, since implicit terminate-by-connection-close is not a robust way of
|
||||
signaling response end, especially in the presence of proxies. Additionally this type of entity prevents connection
|
||||
reuse which can seriously degrade performance. Use ``HttpEntityChunked`` instead!
|
||||
|
||||
|
||||
HttpEntityIndefiniteLength
|
||||
A streaming entity of unspecified length for use in a ``Multipart.BodyPart``.
|
||||
|
||||
__ http://tools.ietf.org/html/rfc7230#section-4.1
|
||||
|
||||
Entity types ``HttpEntityStrict``, ``HttpEntityDefault``, and ``HttpEntityChunked`` are a subtype of ``RequestEntity``
|
||||
which allows to use them for requests and responses. In contrast, ``HttpEntityCloseDelimited`` can only be used for responses.
|
||||
|
||||
Streaming entity types (i.e. all but ``HttpEntityStrict``) cannot be shared or serialized. To create a strict, sharable copy of an
|
||||
entity or message use ``HttpEntity.toStrict`` or ``HttpMessage.toStrict`` which returns a ``CompletionStage`` of the object with
|
||||
the body data collected into a ``ByteString``.
|
||||
|
||||
The class ``HttpEntities`` contains static methods to create entities from common types easily.
|
||||
|
||||
You can use the ``isX` methods of ``HttpEntity`` to find out of which subclass an entity is if you want to provide
|
||||
special handling for each of the subtypes. However, in many cases a recipient of an ``HttpEntity`` doesn't care about
|
||||
of which subtype an entity is (and how data is transported exactly on the HTTP layer). Therefore, the general method
|
||||
``HttpEntity.getDataBytes()`` is provided which returns a ``Source<ByteString, ?>`` that allows access to the data of an
|
||||
entity regardless of its concrete subtype.
|
||||
|
||||
.. note::
|
||||
|
||||
When to use which subtype?
|
||||
- Use ``HttpEntityStrict`` if the amount of data is "small" and already available in memory (e.g. as a ``String`` or ``ByteString``)
|
||||
- Use ``HttpEntityDefault`` if the data is generated by a streaming data source and the size of the data is known
|
||||
- Use ``HttpEntityChunked`` for an entity of unknown length
|
||||
- Use ``HttpEntityCloseDelimited`` for a response as a legacy alternative to ``HttpEntityChunked`` if the client
|
||||
doesn't support chunked transfer encoding. Otherwise use ``HttpEntityChunked``!
|
||||
- In a ``Multipart.Bodypart`` use ``HttpEntityIndefiniteLength`` for content of unknown length.
|
||||
|
||||
.. caution::
|
||||
|
||||
When you receive a non-strict message from a connection then additional data is only read from the network when you
|
||||
request it by consuming the entity data stream. This means that, if you *don't* consume the entity stream then the
|
||||
connection will effectively be stalled. In particular, no subsequent message (request or response) will be read from
|
||||
the connection as the entity of the current message "blocks" the stream.
|
||||
Therefore you must make sure that you always consume the entity data, even in the case that you are not actually
|
||||
interested in it!
|
||||
|
||||
Special processing for HEAD requests
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
`RFC 7230`_ defines very clear rules for the entity length of HTTP messages.
|
||||
|
||||
Especially this rule requires special treatment in Akka HTTP:
|
||||
|
||||
Any response to a HEAD request and any response with a 1xx
|
||||
(Informational), 204 (No Content), or 304 (Not Modified) status
|
||||
code is always terminated by the first empty line after the
|
||||
header fields, regardless of the header fields present in the
|
||||
message, and thus cannot contain a message body.
|
||||
|
||||
Responses to HEAD requests introduce the complexity that `Content-Length` or `Transfer-Encoding` headers
|
||||
can be present but the entity is empty. This is modeled by allowing `HttpEntityDefault` and `HttpEntityChunked`
|
||||
to be used for HEAD responses with an empty data stream.
|
||||
|
||||
Also, when a HEAD response has an `HttpEntityCloseDelimited` entity the Akka HTTP implementation will *not* close the
|
||||
connection after the response has been sent. This allows the sending of HEAD responses without `Content-Length`
|
||||
header across persistent HTTP connections.
|
||||
|
||||
.. _RFC 7230: http://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||
|
||||
|
||||
Header Model
|
||||
------------
|
||||
|
||||
Akka HTTP contains a rich model of the most common HTTP headers. Parsing and rendering is done automatically so that
|
||||
applications don't need to care for the actual syntax of headers. Headers not modelled explicitly are represented
|
||||
as a ``RawHeader`` (which is essentially a String/String name/value pair).
|
||||
|
||||
See these examples of how to deal with headers:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/ModelDocTest.java
|
||||
:include: headers
|
||||
|
||||
|
||||
HTTP Headers
|
||||
------------
|
||||
|
||||
When the Akka HTTP server receives an HTTP request it tries to parse all its headers into their respective
|
||||
model classes. Independently of whether this succeeds or not, the HTTP layer will
|
||||
always pass on all received headers to the application. Unknown headers as well as ones with invalid syntax (according
|
||||
to the header parser) will be made available as ``RawHeader`` instances. For the ones exhibiting parsing errors a
|
||||
warning message is logged depending on the value of the ``illegal-header-warnings`` config setting.
|
||||
|
||||
Some headers have special status in HTTP and are therefore treated differently from "regular" headers:
|
||||
|
||||
Content-Type
|
||||
The Content-Type of an HTTP message is modeled as the ``contentType`` field of the ``HttpEntity``.
|
||||
The ``Content-Type`` header therefore doesn't appear in the ``headers`` sequence of a message.
|
||||
Also, a ``Content-Type`` header instance that is explicitly added to the ``headers`` of a request or response will
|
||||
not be rendered onto the wire and trigger a warning being logged instead!
|
||||
|
||||
Transfer-Encoding
|
||||
Messages with ``Transfer-Encoding: chunked`` are represented as a ``HttpEntityChunked`` entity.
|
||||
As such chunked messages that do not have another deeper nested transfer encoding will not have a ``Transfer-Encoding``
|
||||
header in their ``headers`` list.
|
||||
Similarly, a ``Transfer-Encoding`` header instance that is explicitly added to the ``headers`` of a request or
|
||||
response will not be rendered onto the wire and trigger a warning being logged instead!
|
||||
|
||||
Content-Length
|
||||
The content length of a message is modelled via its :ref:`HttpEntity-java`. As such no ``Content-Length`` header will ever
|
||||
be part of a message's ``header`` sequence.
|
||||
Similarly, a ``Content-Length`` header instance that is explicitly added to the ``headers`` of a request or
|
||||
response will not be rendered onto the wire and trigger a warning being logged instead!
|
||||
|
||||
Server
|
||||
A ``Server`` header is usually added automatically to any response and its value can be configured via the
|
||||
``akka.http.server.server-header`` setting. Additionally an application can override the configured header with a
|
||||
custom one by adding it to the response's ``header`` sequence.
|
||||
|
||||
User-Agent
|
||||
A ``User-Agent`` header is usually added automatically to any request and its value can be configured via the
|
||||
``akka.http.client.user-agent-header`` setting. Additionally an application can override the configured header with a
|
||||
custom one by adding it to the request's ``header`` sequence.
|
||||
|
||||
Date
|
||||
The ``Date`` response header is added automatically but can be overridden by supplying it manually.
|
||||
|
||||
Connection
|
||||
On the server-side Akka HTTP watches for explicitly added ``Connection: close`` response headers and as such honors
|
||||
the potential wish of the application to close the connection after the respective response has been sent out.
|
||||
The actual logic for determining whether to close the connection is quite involved. It takes into account the
|
||||
request's method, protocol and potential ``Connection`` header as well as the response's protocol, entity and
|
||||
potential ``Connection`` header. See `this test`__ for a full table of what happens when.
|
||||
|
||||
__ @github@/akka-http-core/src/test/scala/akka/http/impl/engine/rendering/ResponseRendererSpec.scala#L422
|
||||
|
||||
|
||||
Parsing / Rendering
|
||||
-------------------
|
||||
|
||||
Parsing and rendering of HTTP data structures is heavily optimized and for most types there's currently no public API
|
||||
provided to parse (or render to) Strings or byte arrays.
|
||||
|
||||
.. note::
|
||||
Various parsing and rendering settings are available to tweak in the configuration under ``akka.http.client[.parsing]``,
|
||||
``akka.http.server[.parsing]`` and ``akka.http.host-connection-pool[.client.parsing]``, with defaults for all of these
|
||||
being defined in the ``akka.http.parsing`` configuration section.
|
||||
|
||||
For example, if you want to change a parsing setting for all components, you can set the ``akka.http.parsing.illegal-header-warnings = off``
|
||||
value. However this setting can be stil overriden by the more specific sections, like for example ``akka.http.server.parsing.illegal-header-warnings = on``.
|
||||
In this case both ``client`` and ``host-connection-pool`` APIs will see the setting ``off``, however the server will see ``on``.
|
||||
|
||||
In the case of ``akka.http.host-connection-pool.client`` settings, they default to settings set in ``akka.http.client``,
|
||||
and can override them if needed. This is useful, since both ``client`` and ``host-connection-pool`` APIs,
|
||||
such as the Client API ``Http.get(sys).outgoingConnection`` or the Host Connection Pool APIs ``Http.get(sys).singleRequest``
|
||||
or ``Http.get(sys).superPool``, usually need the same settings, however the ``server`` most likely has a very different set of settings.
|
||||
|
||||
The URI model
|
||||
-------------
|
||||
|
||||
Akka HTTP offers its own specialised URI model class which is tuned for both performance and idiomatic usage within
|
||||
other types of the HTTP model. For example, an HTTPRequest's target URI is parsed into this type, where all character
|
||||
escaping and other URI specific semantics are applied.
|
||||
|
||||
Obtaining the Raw Request URI
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sometimes it may be needed to obtain the "raw" value of an incoming URI, without applying any escaping or parsing to it.
|
||||
While this use-case is rare, it comes up every once in a while. It is possible to obtain the "raw" request URI in Akka
|
||||
HTTP Server side by turning on the ``akka.http.server.raw-request-uri-header`` flag.
|
||||
When enabled, a ``Raw-Request-URI`` header will be added to each request. This header will hold the original raw request's
|
||||
URI that was used. For an example check the reference configuration.
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
.. _implications-of-streaming-http-entities-java:
|
||||
|
||||
Implications of the streaming nature of Request/Response Entities
|
||||
-----------------------------------------------------------------
|
||||
|
||||
Akka HTTP is streaming *all the way through*, which means that the back-pressure mechanisms enabled by Akka Streams
|
||||
are exposed through all layers–from the TCP layer, through the HTTP server, all the way up to the user-facing ``HttpRequest``
|
||||
and ``HttpResponse`` and their ``HttpEntity`` APIs.
|
||||
|
||||
This has surprising implications if you are used to non-streaming / not-reactive HTTP clients.
|
||||
Specifically it means that: "*lack of consumption of the HTTP Entity, is signaled as back-pressure to the other
|
||||
side of the connection*". This is a feature, as it allows one only to consume the entity, and back-pressure servers/clients
|
||||
from overwhelming our application, possibly causing un-necessary buffering of the entity in memory.
|
||||
|
||||
.. warning::
|
||||
Consuming (or discarding) the Entity of a request is mandatory!
|
||||
If *accidentally* left neither consumed or discarded Akka HTTP will
|
||||
assume the incoming data should remain back-pressured, and will stall the incoming data via TCP back-pressure mechanisms.
|
||||
A client should consume the Entity regardless of the status of the ``HttpResponse``.
|
||||
|
||||
Client-Side handling of streaming HTTP Entities
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Consuming the HTTP Response Entity (Client)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The most common use-case of course is consuming the response entity, which can be done via
|
||||
running the underlying ``dataBytes`` Source. This is as simple as running the dataBytes source,
|
||||
(or on the server-side using directives such as ``BasicDirectives.extractDataBytes``).
|
||||
|
||||
It is encouraged to use various streaming techniques to utilise the underlying infrastructure to its fullest,
|
||||
for example by framing the incoming chunks, parsing them line-by-line and then connecting the flow into another
|
||||
destination Sink, such as a File or other Akka Streams connector:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/HttpClientExampleDocTest.java#manual-entity-consume-example-1
|
||||
|
||||
however sometimes the need may arise to consume the entire entity as ``Strict`` entity (which means that it is
|
||||
completely loaded into memory). Akka HTTP provides a special ``toStrict(timeout, materializer)`` method which can be used to
|
||||
eagerly consume the entity and make it available in memory:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/HttpClientExampleDocTest.java#manual-entity-consume-example-2
|
||||
|
||||
|
||||
Discarding the HTTP Response Entity (Client)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Sometimes when calling HTTP services we do not care about their response payload (e.g. all we care about is the response code),
|
||||
yet as explained above entity still has to be consumed in some way, otherwise we'll be exherting back-pressure on the
|
||||
underlying TCP connection.
|
||||
|
||||
The ``discardEntityBytes`` convenience method serves the purpose of easily discarding the entity if it has no purpose for us.
|
||||
It does so by piping the incoming bytes directly into an ``Sink.ignore``.
|
||||
|
||||
The two snippets below are equivalent, and work the same way on the server-side for incoming HTTP Requests:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/HttpClientExampleDocTest.java#manual-entity-discard-example-1
|
||||
|
||||
Or the equivalent low-level code achieving the same result:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/HttpClientExampleDocTest.java#manual-entity-discard-example-2
|
||||
|
||||
Server-Side handling of streaming HTTP Entities
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Similarily as with the Client-side, HTTP Entities are directly linked to Streams which are fed by the underlying
|
||||
TCP connection. Thus, if request entities remain not consumed, the server will back-pressure the connection, expecting
|
||||
that the user-code will eventually decide what to do with the incoming data.
|
||||
|
||||
Note that some directives force an implicit ``toStrict`` operation, such as ``entity(exampleUnmarshaller, example -> {})`` and similar ones.
|
||||
|
||||
Consuming the HTTP Request Entity (Server)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The simplest way of consuming the incoming request entity is to simply transform it into an actual domain object,
|
||||
for example by using the :ref:`-entity-java-` directive:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/server/HttpServerExampleDocTest.java#consume-entity-directive
|
||||
|
||||
Of course you can access the raw dataBytes as well and run the underlying stream, for example piping it into an
|
||||
FileIO Sink, that signals completion via a ``CompletionStage<IoResult>`` once all the data has been written into the file:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/server/HttpServerExampleDocTest.java#consume-raw-dataBytes
|
||||
|
||||
Discarding the HTTP Request Entity (Server)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Sometimes, depending on some validation (e.g. checking if given user is allowed to perform uploads or not)
|
||||
you may want to decide to discard the uploaded entity.
|
||||
|
||||
Please note that discarding means that the entire upload will proceed, even though you are not interested in the data
|
||||
being streamed to the server - this may be useful if you are simply not interested in the given entity, however
|
||||
you don't want to abort the entire connection (which we'll demonstrate as well), since there may be more requests
|
||||
pending on the same connection still.
|
||||
|
||||
In order to discard the databytes explicitly you can invoke the ``discardEntityBytes`` bytes of the incoming ``HTTPRequest``:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/server/HttpServerExampleDocTest.java#discard-discardEntityBytes
|
||||
|
||||
A related concept is *cancelling* the incoming ``entity.getDataBytes()`` stream, which results in Akka HTTP
|
||||
*abruptly closing the connection from the Client*. This may be useful when you detect that the given user should not be allowed to make any
|
||||
uploads at all, and you want to drop the connection (instead of reading and ignoring the incoming data).
|
||||
This can be done by attaching the incoming ``entity.getDataBytes()`` to a ``Sink.cancelled`` which will cancel
|
||||
the entity stream, which in turn will cause the underlying connection to be shut-down by the server –
|
||||
effectively hard-aborting the incoming request:
|
||||
|
||||
.. includecode:: ../code/docs/http/javadsl/server/HttpServerExampleDocTest.java#discard-close-connections
|
||||
|
||||
Closing connections is also explained in depth in the :ref:`http-closing-connection-low-level-java` section of the docs.
|
||||
|
||||
Pending: Automatic discarding of not used entities
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Under certain conditions it is possible to detect an entity is very unlikely to be used by the user for a given request,
|
||||
and issue warnings or discard the entity automatically. This advanced feature has not been implemented yet, see the below
|
||||
note and issues for further discussion and ideas.
|
||||
|
||||
.. note::
|
||||
An advanced feature code named "auto draining" has been discussed and proposed for Akka HTTP, and we're hoping
|
||||
to implement or help the community implement it.
|
||||
|
||||
You can read more about it in `issue #18716 <https://github.com/akka/akka/issues/18716>`_
|
||||
as well as `issue #18540 <https://github.com/akka/akka/issues/18540>`_ ; as always, contributions are very welcome!
|
||||
|
||||
|
|
@ -1,45 +1,5 @@
|
|||
.. _http-java:
|
||||
Akka HTTP Documentation (Java) moved!
|
||||
=====================================
|
||||
|
||||
Akka HTTP
|
||||
=========
|
||||
|
||||
The Akka HTTP modules implement a full server- and client-side HTTP stack on top of *akka-actor* and *akka-stream*. It's
|
||||
not a web-framework but rather a more general toolkit for providing and consuming HTTP-based services. While interaction
|
||||
with a browser is of course also in scope it is not the primary focus of Akka HTTP.
|
||||
|
||||
Akka HTTP follows a rather open design and many times offers several different API levels for "doing the same thing".
|
||||
You get to pick the API level of abstraction that is most suitable for your application.
|
||||
This means that, if you have trouble achieving something using a high-level API, there's a good chance that you can get
|
||||
it done with a low-level API, which offers more flexibility but might require you to write more application code.
|
||||
|
||||
Akka HTTP is structured into several modules:
|
||||
|
||||
akka-http-core
|
||||
A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets).
|
||||
Includes a model of all things HTTP.
|
||||
|
||||
akka-http
|
||||
Higher-level functionality, like (un)marshalling, (de)compression as well as a powerful DSL
|
||||
for defining HTTP-based APIs on the server-side
|
||||
|
||||
akka-http-testkit
|
||||
A test harness and set of utilities for verifying server-side service implementations
|
||||
|
||||
akka-http-jackson
|
||||
Predefined glue-code for (de)serializing custom types from/to JSON with jackson_
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
http-model
|
||||
server-side/low-level-server-side-api
|
||||
server-side/websocket-support
|
||||
routing-dsl/index
|
||||
client-side/index
|
||||
common/index
|
||||
implications-of-streaming-http-entity
|
||||
configuration
|
||||
server-side-https-support
|
||||
../../scala/http/migration-guide-2.4.x-experimental
|
||||
|
||||
.. _jackson: https://github.com/FasterXML/jackson
|
||||
Akka HTTP has been released as independent stable module (from Akka HTTP 3.x onwards).
|
||||
The documentation is available under `doc.akka.io/akka-http/current/ <http://doc.akka.io/docs/akka-http/current/java.html>`_.
|
||||
|
|
|
|||
|
|
@ -1,152 +0,0 @@
|
|||
.. _Predefined Directives-java:
|
||||
|
||||
Predefined Directives (alphabetically)
|
||||
======================================
|
||||
|
||||
================================================ ============================================================================
|
||||
Directive Description
|
||||
================================================ ============================================================================
|
||||
:ref:`-authenticateBasic-java-` Wraps the inner route with Http Basic authentication support using a given ``Authenticator<T>``
|
||||
:ref:`-authenticateBasicAsync-java-` Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticator<T>``
|
||||
:ref:`-authenticateBasicPF-java-` Wraps the inner route with Http Basic authentication support using a given ``AuthenticatorPF<T>``
|
||||
:ref:`-authenticateBasicPFAsync-java-` Wraps the inner route with Http Basic authentication support using a given ``AsyncAuthenticatorPF<T>``
|
||||
:ref:`-authenticateOAuth2-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AuthenticatorPF<T>``
|
||||
:ref:`-authenticateOAuth2Async-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AsyncAuthenticator<T>``
|
||||
:ref:`-authenticateOAuth2PF-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AuthenticatorPF<T>``
|
||||
:ref:`-authenticateOAuth2PFAsync-java-` Wraps the inner route with OAuth Bearer Token authentication support using a given ``AsyncAuthenticatorPF<T>``
|
||||
:ref:`-authenticateOrRejectWithChallenge-java-` Lifts an authenticator function into a directive
|
||||
:ref:`-authorize-java-` Applies the given authorization check to the request
|
||||
:ref:`-authorizeAsync-java-` Applies the given asynchronous authorization check to the request
|
||||
:ref:`-cancelRejection-java-` Adds a ``TransformationRejection`` cancelling all rejections equal to the given one to the rejections potentially coming back from the inner route.
|
||||
:ref:`-cancelRejections-java-` Adds a ``TransformationRejection`` cancelling all matching rejections to the rejections potentially coming back from the inner route
|
||||
:ref:`-checkSameOrigin-java-` Checks that the request comes from the same origin
|
||||
:ref:`-complete-java-` Completes the request using the given arguments
|
||||
:ref:`-completeOrRecoverWith-java-` "Unwraps" a ``CompletionStage<T>`` and runs the inner route when the future has failed with the error as an extraction of type ``Throwable``
|
||||
:ref:`-completeWith-java-` Uses the marshaller for a given type to extract a completion function
|
||||
:ref:`-conditional-java-` Wraps its inner route with support for conditional requests as defined by http://tools.ietf.org/html/rfc7232
|
||||
:ref:`-cookie-java-` Extracts the ``HttpCookie`` with the given name
|
||||
:ref:`-decodeRequest-java-` Decompresses the request if it is ``gzip`` or ``deflate`` compressed
|
||||
:ref:`-decodeRequestWith-java-` Decodes the incoming request using one of the given decoders
|
||||
:ref:`-delete-java-` Rejects all non-DELETE requests
|
||||
:ref:`-deleteCookie-java-` Adds a ``Set-Cookie`` response header expiring the given cookies
|
||||
:ref:`-encodeResponse-java-` Encodes the response with the encoding that is requested by the client via the ``Accept-Encoding`` header (``NoCoding``, ``Gzip`` and ``Deflate``)
|
||||
:ref:`-encodeResponseWith-java-` Encodes the response with the encoding that is requested by the client via the ``Accept-Encoding`` header (from a user-defined set)
|
||||
:ref:`-entity-java-` Extracts the request entity unmarshalled to a given type
|
||||
:ref:`-extract-java-` Extracts a single value using a ``RequestContext ⇒ T`` function
|
||||
:ref:`-extractClientIP-java-` Extracts the client's IP from either the ``X-Forwarded-``, ``Remote-Address`` or ``X-Real-IP`` header
|
||||
:ref:`-extractCredentials-java-` Extracts the potentially present ``HttpCredentials`` provided with the request's ``Authorization`` header
|
||||
:ref:`-extractExecutionContext-java-` Extracts the ``ExecutionContext`` from the ``RequestContext``
|
||||
:ref:`-extractMaterializer-java-` Extracts the ``Materializer`` from the ``RequestContext``
|
||||
:ref:`-extractHost-java-` Extracts the hostname part of the Host request header value
|
||||
:ref:`-extractLog-java-` Extracts the ``LoggingAdapter`` from the ``RequestContext``
|
||||
:ref:`-extractMethod-java-` Extracts the request method
|
||||
:ref:`-extractRequest-java-` Extracts the current ``HttpRequest`` instance
|
||||
:ref:`-extractRequestContext-java-` Extracts the ``RequestContext`` itself
|
||||
:ref:`-extractScheme-java-` Extracts the URI scheme from the request
|
||||
:ref:`-extractSettings-java-` Extracts the ``RoutingSettings`` from the ``RequestContext``
|
||||
:ref:`-extractUnmatchedPath-java-` Extracts the yet unmatched path from the ``RequestContext``
|
||||
:ref:`-extractUri-java-` Extracts the complete request URI
|
||||
:ref:`-failWith-java-` Bubbles the given error up the response chain where it is dealt with by the closest :ref:`-handleExceptions-java-` directive and its ``ExceptionHandler``
|
||||
:ref:`-fileUpload-java-` Provides a stream of an uploaded file from a multipart request
|
||||
:ref:`-formField-java-` Extracts an HTTP form field from the request
|
||||
:ref:`-formFieldMap-java-` Extracts a number of HTTP form field from the request as a ``Map<String, String>``
|
||||
:ref:`-formFieldMultiMap-java-` Extracts a number of HTTP form field from the request as a ``Map<String, List<String>``
|
||||
:ref:`-formFieldList-java-` Extracts a number of HTTP form field from the request as a ``List<Pair<String, String>>``
|
||||
:ref:`-get-java-` Rejects all non-GET requests
|
||||
:ref:`-getFromBrowseableDirectories-java-` Serves the content of the given directories as a file-system browser, i.e. files are sent and directories served as browseable listings
|
||||
:ref:`-getFromBrowseableDirectory-java-` Serves the content of the given directory as a file-system browser, i.e. files are sent and directories served as browseable listings
|
||||
:ref:`-getFromDirectory-java-` Completes GET requests with the content of a file underneath a given file-system directory
|
||||
:ref:`-getFromFile-java-` Completes GET requests with the content of a given file
|
||||
:ref:`-getFromResource-java-` Completes GET requests with the content of a given class-path resource
|
||||
:ref:`-getFromResourceDirectory-java-` Completes GET requests with the content of a file underneath a given "class-path resource directory"
|
||||
:ref:`-handleExceptions-java-` Transforms exceptions thrown during evaluation of the inner route using the given ``ExceptionHandler``
|
||||
:ref:`-handleRejections-java-` Transforms rejections produced by the inner route using the given ``RejectionHandler``
|
||||
:ref:`-handleWebSocketMessages-java-` Handles websocket requests with the given handler and rejects other requests with an ``ExpectedWebSocketRequestRejection``
|
||||
:ref:`-handleWebSocketMessagesForProtocol-java-` Handles websocket requests with the given handler if the subprotocol matches and rejects other requests with an ``ExpectedWebSocketRequestRejection`` or an ``UnsupportedWebSocketSubprotocolRejection``.
|
||||
:ref:`-handleWith-java-` Completes the request using a given function
|
||||
:ref:`-head-java-` Rejects all non-HEAD requests
|
||||
:ref:`-headerValue-java-` Extracts an HTTP header value using a given ``HttpHeader ⇒ Option<T>`` function
|
||||
:ref:`-headerValueByName-java-` Extracts the value of the first HTTP request header with a given name
|
||||
:ref:`-headerValueByType-java-` Extracts the first HTTP request header of the given type
|
||||
:ref:`-headerValuePF-java-` Extracts an HTTP header value using a given ``PartialFunction<HttpHeader, T>``
|
||||
:ref:`-host-java-` Rejects all requests with a non-matching host name
|
||||
:ref:`-listDirectoryContents-java-` Completes GET requests with a unified listing of the contents of all given file-system directories
|
||||
:ref:`-logRequest-java-` Produces a log entry for every incoming request
|
||||
:ref:`-logRequestResult-java-` Produces a log entry for every incoming request and ``RouteResult``
|
||||
:ref:`-logResult-java-` Produces a log entry for every ``RouteResult``
|
||||
:ref:`-mapInnerRoute-java-` Transforms its inner ``Route`` with a ``Route => Route`` function
|
||||
:ref:`-mapRejections-java-` Transforms rejections from a previous route with an ``List<Rejection] ⇒ List<Rejection>`` function
|
||||
:ref:`-mapRequest-java-` Transforms the request with an ``HttpRequest => HttpRequest`` function
|
||||
:ref:`-mapRequestContext-java-` Transforms the ``RequestContext`` with a ``RequestContext => RequestContext`` function
|
||||
:ref:`-mapResponse-java-` Transforms the response with an ``HttpResponse => HttpResponse`` function
|
||||
:ref:`-mapResponseEntity-java-` Transforms the response entity with an ``ResponseEntity ⇒ ResponseEntity`` function
|
||||
:ref:`-mapResponseHeaders-java-` Transforms the response headers with an ``List<HttpHeader] ⇒ List<HttpHeader>`` function
|
||||
:ref:`-mapRouteResult-java-` Transforms the ``RouteResult`` with a ``RouteResult ⇒ RouteResult`` function
|
||||
:ref:`-mapRouteResultFuture-java-` Transforms the ``RouteResult`` future with a ``CompletionStage<RouteResult] ⇒ CompletionStage<RouteResult>`` function
|
||||
:ref:`-mapRouteResultPF-java-` Transforms the ``RouteResult`` with a ``PartialFunction<RouteResult, RouteResult>``
|
||||
:ref:`-mapRouteResultWith-java-` Transforms the ``RouteResult`` with a ``RouteResult ⇒ CompletionStage<RouteResult>`` function
|
||||
:ref:`-mapRouteResultWithPF-java-` Transforms the ``RouteResult`` with a ``PartialFunction<RouteResult, CompletionStage<RouteResult]>``
|
||||
:ref:`-mapSettings-java-` Transforms the ``RoutingSettings`` with a ``RoutingSettings ⇒ RoutingSettings`` function
|
||||
:ref:`-mapUnmatchedPath-java-` Transforms the ``unmatchedPath`` of the ``RequestContext`` using a ``Uri.Path ⇒ Uri.Path`` function
|
||||
:ref:`-method-java-` Rejects all requests whose HTTP method does not match the given one
|
||||
:ref:`-onComplete-java-` "Unwraps" a ``CompletionStage<T>`` and runs the inner route after future completion with the future's value as an extraction of type ``Try<T>``
|
||||
:ref:`-onCompleteWithBreaker-java-` "Unwraps" a ``CompletionStage<T>`` inside a ``CircuitBreaker`` and runs the inner route after future completion with the future's value as an extraction of type ``Try<T>``
|
||||
:ref:`-onSuccess-java-` "Unwraps" a ``CompletionStage<T>`` and runs the inner route after future completion with the future's value as an extraction of type ``T``
|
||||
:ref:`-optionalCookie-java-` Extracts the ``HttpCookiePair`` with the given name as an ``Option<HttpCookiePair>``
|
||||
:ref:`-optionalHeaderValue-java-` Extracts an optional HTTP header value using a given ``HttpHeader ⇒ Option<T>`` function
|
||||
:ref:`-optionalHeaderValueByName-java-` Extracts the value of the first optional HTTP request header with a given name
|
||||
:ref:`-optionalHeaderValueByType-java-` Extracts the first optional HTTP request header of the given type
|
||||
:ref:`-optionalHeaderValuePF-java-` Extracts an optional HTTP header value using a given ``PartialFunction<HttpHeader, T>``
|
||||
:ref:`-options-java-` Rejects all non-OPTIONS requests
|
||||
:ref:`-overrideMethodWithParameter-java-` Changes the request method to the value of the specified query parameter
|
||||
:ref:`-parameter-java-` Extracts a query parameter value from the request
|
||||
:ref:`-parameterMap-java-` Extracts the request's query parameters as a ``Map<String, String>``
|
||||
:ref:`-parameterMultiMap-java-` Extracts the request's query parameters as a ``Map<String, List<String>>``
|
||||
:ref:`-parameterList-java-` Extracts the request's query parameters as a ``Seq<Pair<String, String>>``
|
||||
:ref:`-pass-java-` Always simply passes the request on to its inner route, i.e. doesn't do anything, neither with the request nor the response
|
||||
:ref:`-patch-java-` Rejects all non-PATCH requests
|
||||
:ref:`-path-java-` Applies the given ``PathMatcher`` to the remaining unmatched path after consuming a leading slash
|
||||
:ref:`-pathEnd-java-` Only passes on the request to its inner route if the request path has been matched completely
|
||||
:ref:`-pathEndOrSingleSlash-java-` Only passes on the request to its inner route if the request path has been matched completely or only consists of exactly one remaining slash
|
||||
:ref:`-pathPrefix-java-` Applies the given ``PathMatcher`` to a prefix of the remaining unmatched path after consuming a leading slash
|
||||
:ref:`-pathPrefixTest-java-` Checks whether the unmatchedPath has a prefix matched by the given ``PathMatcher`` after implicitly consuming a leading slash
|
||||
:ref:`-pathSingleSlash-java-` Only passes on the request to its inner route if the request path consists of exactly one remaining slash
|
||||
:ref:`-pathSuffix-java-` Applies the given ``PathMatcher`` to a suffix of the remaining unmatched path (Caution: check java!)
|
||||
:ref:`-pathSuffixTest-java-` Checks whether the unmatched path has a suffix matched by the given ``PathMatcher`` (Caution: check java!)
|
||||
:ref:`-post-java-` Rejects all non-POST requests
|
||||
:ref:`-provide-java-` Injects a given value into a directive
|
||||
:ref:`-put-java-` Rejects all non-PUT requests
|
||||
:ref:`-rawPathPrefix-java-` Applies the given matcher directly to a prefix of the unmatched path of the ``RequestContext``, without implicitly consuming a leading slash
|
||||
:ref:`-rawPathPrefixTest-java-` Checks whether the unmatchedPath has a prefix matched by the given ``PathMatcher``
|
||||
:ref:`-recoverRejections-java-` Transforms rejections from the inner route with an ``List<Rejection] ⇒ RouteResult`` function
|
||||
:ref:`-recoverRejectionsWith-java-` Transforms rejections from the inner route with an ``List<Rejection] ⇒ CompletionStage<RouteResult>`` function
|
||||
:ref:`-redirect-java-` Completes the request with redirection response of the given type to the given URI
|
||||
:ref:`-redirectToNoTrailingSlashIfPresent-java-` If the request path ends with a slash, redirects to the same uri without trailing slash in the path
|
||||
:ref:`-redirectToTrailingSlashIfMissing-java-` If the request path doesn't end with a slash, redirects to the same uri with trailing slash in the path
|
||||
:ref:`-reject-java-` Rejects the request with the given rejections
|
||||
:ref:`-rejectEmptyResponse-java-` Converts responses with an empty entity into (empty) rejections
|
||||
:ref:`-requestEncodedWith-java-` Rejects the request with an ``UnsupportedRequestEncodingRejection`` if its encoding doesn't match the given one
|
||||
:ref:`-requestEntityEmpty-java-` Rejects if the request entity is non-empty
|
||||
:ref:`-requestEntityPresent-java-` Rejects with a ``RequestEntityExpectedRejection`` if the request entity is empty
|
||||
:ref:`-respondWithDefaultHeader-java-` Adds a given response header if the response doesn't already contain a header with the same name
|
||||
:ref:`-respondWithDefaultHeaders-java-` Adds the subset of the given headers to the response which doesn't already have a header with the respective name present in the response
|
||||
:ref:`-respondWithHeader-java-` Unconditionally adds a given header to the outgoing response
|
||||
:ref:`-respondWithHeaders-java-` Unconditionally adds the given headers to the outgoing response
|
||||
:ref:`-responseEncodingAccepted-java-` Rejects the request with an ``UnacceptedResponseEncodingRejection`` if the given response encoding is not accepted by the client
|
||||
:ref:`-scheme-java-` Rejects all requests whose URI scheme doesn't match the given one
|
||||
:ref:`-selectPreferredLanguage-java-` Inspects the request's ``Accept-Language`` header and determines, which of a given set of language alternatives is preferred by the client
|
||||
:ref:`-setCookie-java-` Adds a ``Set-Cookie`` response header with the given cookies
|
||||
:ref:`-uploadedFile-java-` Streams one uploaded file from a multipart request to a file on disk
|
||||
:ref:`-validate-java-` Checks a given condition before running its inner route
|
||||
:ref:`-withoutRequestTimeout-java-` Disables :ref:`request timeouts <request-timeout-java>` for a given route.
|
||||
:ref:`-withoutSizeLimit-java-` Skips request entity size check
|
||||
:ref:`-withExecutionContext-java-` Runs its inner route with the given alternative ``ExecutionContext``
|
||||
:ref:`-withMaterializer-java-` Runs its inner route with the given alternative ``Materializer``
|
||||
:ref:`-withLog-java-` Runs its inner route with the given alternative ``LoggingAdapter``
|
||||
:ref:`-withRangeSupport-java-` Adds ``Accept-Ranges: bytes`` to responses to GET requests, produces partial responses if the initial request contained a valid ``Range`` header
|
||||
:ref:`-withRequestTimeout-java-` Configures the :ref:`request timeouts <request-timeout-java>` for a given route.
|
||||
:ref:`-withRequestTimeoutResponse-java-` Prepares the ``HttpResponse`` that is emitted if a request timeout is triggered. ``RequestContext => RequestContext`` function
|
||||
:ref:`-withSettings-java-` Runs its inner route with the given alternative ``RoutingSettings``
|
||||
:ref:`-withSizeLimit-java-` Applies request entity size check
|
||||
================================================ ============================================================================
|
||||
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.. _-cancelRejection-java-:
|
||||
|
||||
cancelRejection
|
||||
===============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Adds a ``TransformationRejection`` cancelling all rejections equal to the
|
||||
given one to the rejections potentially coming back from the inner route.
|
||||
|
||||
Read :ref:`rejections-java` to learn more about rejections.
|
||||
|
||||
For more advanced handling of rejections refer to the :ref:`-handleRejections-java-` directive
|
||||
which provides a nicer DSL for building rejection handlers.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#cancelRejection
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
.. _-cancelRejections-java-:
|
||||
|
||||
cancelRejections
|
||||
================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Adds a ``TransformationRejection`` cancelling all rejections created by the inner route for which
|
||||
the condition argument function returns ``true``.
|
||||
|
||||
See also :ref:`-cancelRejection-java-`, for canceling a specific rejection.
|
||||
|
||||
Read :ref:`rejections-java` to learn more about rejections.
|
||||
|
||||
For more advanced handling of rejections refer to the :ref:`-handleRejections-java-` directive
|
||||
which provides a nicer DSL for building rejection handlers.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#cancelRejections
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.. _-extract-java-:
|
||||
|
||||
extract
|
||||
=======
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The ``extract`` directive is used as a building block for :ref:`Custom Directives-java` to extract data from the
|
||||
``RequestContext`` and provide it to the inner route.
|
||||
|
||||
See :ref:`ProvideDirectives-java` for an overview of similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extract
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
.. _-extractActorSystem-java-:
|
||||
|
||||
extractActorSystem
|
||||
==================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the ``ActorSystem`` from the ``RequestContext``, which can be useful when the external API
|
||||
in your route needs one.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is only supported when the available Materializer is an ActorMaterializer.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractActorSystem
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
.. _-extractDataBytes-java-:
|
||||
|
||||
extractDataBytes
|
||||
================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the entities data bytes as ``Source[ByteString, Any]`` from the :class:`RequestContext`.
|
||||
|
||||
The directive returns a stream containing the request data bytes.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractDataBytes
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.. _-extractExecutionContext-java-:
|
||||
|
||||
extractExecutionContext
|
||||
=======================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the ``ExecutionContext`` from the ``RequestContext``.
|
||||
|
||||
See :ref:`-withExecutionContext-java-` to see how to customise the execution context provided for an inner route.
|
||||
|
||||
See :ref:`-extract-java-` to learn more about how extractions work.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractExecutionContext
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
.. _-extractLog-java-:
|
||||
|
||||
extractLog
|
||||
==========
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts a :class:`LoggingAdapter` from the request context which can be used for logging inside the route.
|
||||
|
||||
The ``extractLog`` directive is used for providing logging to routes, such that they don't have to depend on
|
||||
closing over a logger provided in the class body.
|
||||
|
||||
See :ref:`-extract-java-` and :ref:`ProvideDirectives-java` for an overview of similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractLog
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.. _-extractMaterializer-java-:
|
||||
|
||||
extractMaterializer
|
||||
===================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the ``Materializer`` from the ``RequestContext``, which can be useful when you want to run an
|
||||
Akka Stream directly in your route.
|
||||
|
||||
See also :ref:`-withMaterializer-java-` to see how to customise the used materializer for specific inner routes.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractMaterializer
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.. _-extractRequest-java-:
|
||||
|
||||
extractRequest
|
||||
==============
|
||||
|
||||
Description
|
||||
-----------
|
||||
Extracts the complete ``HttpRequest`` instance.
|
||||
|
||||
Use ``extractRequest`` to extract just the complete URI of the request. Usually there's little use of
|
||||
extracting the complete request because extracting of most of the aspects of HttpRequests is handled by specialized
|
||||
directives. See :ref:`Request Directives-java`.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractRequest
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.. _-extractRequestContext-java-:
|
||||
|
||||
extractRequestContext
|
||||
=====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the request's underlying :class:`RequestContext`.
|
||||
|
||||
This directive is used as a building block for most of the other directives,
|
||||
which extract the context and by inspecting some of it's values can decide
|
||||
what to do with the request - for example provide a value, or reject the request.
|
||||
|
||||
See also :ref:`-extractRequest-java-` if only interested in the :class:`HttpRequest` instance itself.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractRequestContext
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.. _-extractRequestEntity-java-:
|
||||
|
||||
extractRequestEntity
|
||||
====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the ``RequestEntity`` from the :class:`RequestContext`.
|
||||
|
||||
The directive returns a ``RequestEntity`` without unmarshalling the request. To extract domain entity,
|
||||
:ref:`-entity-java-` should be used.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractRequestEntity
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.. _-extractSettings-java-:
|
||||
|
||||
extractSettings
|
||||
===============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the :class:`RoutingSettings` from the :class:`RequestContext`.
|
||||
|
||||
By default the settings of the ``Http()`` extension running the route will be returned.
|
||||
It is possible to override the settings for specific sub-routes by using the :ref:`-withSettings-java-` directive.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractRequestContext
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
.. _-extractStrictEntity-java-:
|
||||
|
||||
extractStrictEntity
|
||||
===================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Extracts the strict http entity as ``HttpEntity.Strict`` from the :class:`RequestContext`.
|
||||
|
||||
A timeout parameter is given and if the stream isn't completed after the timeout, the directive will be failed.
|
||||
|
||||
.. warning::
|
||||
|
||||
The directive will read the request entity into memory within the size limit(8M by default) and effectively disable streaming.
|
||||
The size limit can be configured globally with ``akka.http.parsing.max-content-length`` or
|
||||
overridden by wrapping with :ref:`-withSizeLimit-java-` or :ref:`-withoutSizeLimit-java-` directive.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractStrictEntity
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
.. _-extractUnmatchedPath-java-:
|
||||
|
||||
extractUnmatchedPath
|
||||
====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
Extracts the unmatched path from the request context.
|
||||
|
||||
The ``extractUnmatchedPath`` directive extracts the remaining path that was not yet matched by any of the :ref:`PathDirectives-java`
|
||||
(or any custom ones that change the unmatched path field of the request context). You can use it for building directives
|
||||
that handle complete suffixes of paths (like the ``getFromDirectory`` directives and similar ones).
|
||||
|
||||
Use ``mapUnmatchedPath`` to change the value of the unmatched path.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractUnmatchedPath
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
.. _-extractUri-java-:
|
||||
|
||||
extractUri
|
||||
==========
|
||||
|
||||
Description
|
||||
-----------
|
||||
Access the full URI of the request.
|
||||
|
||||
Use :ref:`SchemeDirectives-java`, :ref:`HostDirectives-java`, :ref:`PathDirectives-java`, and :ref:`ParameterDirectives-java` for more
|
||||
targeted access to parts of the URI.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#extractUri
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
.. _BasicDirectives-java:
|
||||
|
||||
BasicDirectives
|
||||
===============
|
||||
|
||||
Basic directives are building blocks for building :ref:`Custom Directives`. As such they
|
||||
usually aren't used in a route directly but rather in the definition of new directives.
|
||||
|
||||
|
||||
.. _ProvideDirectives-java:
|
||||
|
||||
Providing Values to Inner Routes
|
||||
--------------------------------
|
||||
|
||||
These directives provide values to the inner routes with extractions. They can be distinguished
|
||||
on two axes: a) provide a constant value or extract a value from the ``RequestContext`` b) provide
|
||||
a single value or a tuple of values.
|
||||
|
||||
* :ref:`-extract-java-`
|
||||
* :ref:`-extractActorSystem-java-`
|
||||
* :ref:`-extractDataBytes-java-`
|
||||
* :ref:`-extractExecutionContext-java-`
|
||||
* :ref:`-extractMaterializer-java-`
|
||||
* :ref:`-extractStrictEntity-java-`
|
||||
* :ref:`-extractLog-java-`
|
||||
* :ref:`-extractRequest-java-`
|
||||
* :ref:`-extractRequestContext-java-`
|
||||
* :ref:`-extractRequestEntity-java-`
|
||||
* :ref:`-extractSettings-java-`
|
||||
* :ref:`-extractUnmatchedPath-java-`
|
||||
* :ref:`-extractUri-java-`
|
||||
* :ref:`-provide-java-`
|
||||
|
||||
|
||||
.. _Request Transforming Directives-java:
|
||||
|
||||
Transforming the Request(Context)
|
||||
---------------------------------
|
||||
|
||||
* :ref:`-mapRequest-java-`
|
||||
* :ref:`-mapRequestContext-java-`
|
||||
* :ref:`-mapSettings-java-`
|
||||
* :ref:`-mapUnmatchedPath-java-`
|
||||
* :ref:`-withExecutionContext-java-`
|
||||
* :ref:`-withMaterializer-java-`
|
||||
* :ref:`-withLog-java-`
|
||||
* :ref:`-withSettings-java-`
|
||||
* :ref:`-toStrictEntity-java-`
|
||||
|
||||
|
||||
.. _Response Transforming Directives-java:
|
||||
|
||||
Transforming the Response
|
||||
-------------------------
|
||||
|
||||
These directives allow to hook into the response path and transform the complete response or
|
||||
the parts of a response or the list of rejections:
|
||||
|
||||
* :ref:`-mapResponse-java-`
|
||||
* :ref:`-mapResponseEntity-java-`
|
||||
* :ref:`-mapResponseHeaders-java-`
|
||||
|
||||
|
||||
.. _Result Transformation Directives-java:
|
||||
|
||||
Transforming the RouteResult
|
||||
----------------------------
|
||||
|
||||
These directives allow to transform the RouteResult of the inner route.
|
||||
|
||||
* :ref:`-cancelRejection-java-`
|
||||
* :ref:`-cancelRejections-java-`
|
||||
* :ref:`-mapRejections-java-`
|
||||
* :ref:`-mapRouteResult-java-`
|
||||
* :ref:`-mapRouteResultFuture-java-`
|
||||
* :ref:`-mapRouteResultPF-java-`
|
||||
* :ref:`-mapRouteResultWith-java-`
|
||||
* :ref:`-mapRouteResultWithPF-java-`
|
||||
* :ref:`-recoverRejections-java-`
|
||||
* :ref:`-recoverRejectionsWith-java-`
|
||||
|
||||
|
||||
Other
|
||||
-----
|
||||
|
||||
* :ref:`-mapInnerRoute-java-`
|
||||
* :ref:`-pass-java-`
|
||||
|
||||
|
||||
Alphabetically
|
||||
--------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
cancelRejection
|
||||
cancelRejections
|
||||
extract
|
||||
extractActorSystem
|
||||
extractDataBytes
|
||||
extractExecutionContext
|
||||
extractMaterializer
|
||||
extractStrictEntity
|
||||
extractLog
|
||||
extractRequest
|
||||
extractRequestContext
|
||||
extractRequestEntity
|
||||
extractSettings
|
||||
extractUnmatchedPath
|
||||
extractUri
|
||||
mapInnerRoute
|
||||
mapRejections
|
||||
mapRequest
|
||||
mapRequestContext
|
||||
mapResponse
|
||||
mapResponseEntity
|
||||
mapResponseHeaders
|
||||
mapRouteResult
|
||||
mapRouteResultFuture
|
||||
mapRouteResultPF
|
||||
mapRouteResultWith
|
||||
mapRouteResultWithPF
|
||||
mapSettings
|
||||
mapUnmatchedPath
|
||||
pass
|
||||
provide
|
||||
recoverRejections
|
||||
recoverRejectionsWith
|
||||
toStrictEntity
|
||||
withExecutionContext
|
||||
withMaterializer
|
||||
withLog
|
||||
withSettings
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
.. _-mapInnerRoute-java-:
|
||||
|
||||
mapInnerRoute
|
||||
=============
|
||||
|
||||
Description
|
||||
-----------
|
||||
Changes the execution model of the inner route by wrapping it with arbitrary logic.
|
||||
|
||||
The ``mapInnerRoute`` directive is used as a building block for :ref:`Custom Directives-java` to replace the inner route
|
||||
with any other route. Usually, the returned route wraps the original one with custom execution logic.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapInnerRoute
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.. _-mapRejections-java-:
|
||||
|
||||
mapRejections
|
||||
=============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
**Low level directive** – unless you're sure you need to be working on this low-level you might instead
|
||||
want to try the :ref:`-handleRejections-java-` directive which provides a nicer DSL for building rejection handlers.
|
||||
|
||||
The ``mapRejections`` directive is used as a building block for :ref:`Custom Directives-java` to transform a list
|
||||
of rejections from the inner route to a new list of rejections.
|
||||
|
||||
See :ref:`Response Transforming Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRejections
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.. _-mapRequest-java-:
|
||||
|
||||
mapRequest
|
||||
==========
|
||||
|
||||
Description
|
||||
-----------
|
||||
Transforms the request before it is handled by the inner route.
|
||||
|
||||
The ``mapRequest`` directive is used as a building block for :ref:`Custom Directives-java` to transform a request before it
|
||||
is handled by the inner route. Changing the ``request.uri`` parameter has no effect on path matching in the inner route
|
||||
because the unmatched path is a separate field of the ``RequestContext`` value which is passed into routes. To change
|
||||
the unmatched path or other fields of the ``RequestContext`` use the :ref:`-mapRequestContext-java-` directive.
|
||||
|
||||
See :ref:`Request Transforming Directives-java` for an overview of similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRequest
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
.. _-mapRequestContext-java-:
|
||||
|
||||
mapRequestContext
|
||||
=================
|
||||
|
||||
Description
|
||||
-----------
|
||||
Transforms the ``RequestContext`` before it is passed to the inner route.
|
||||
|
||||
The ``mapRequestContext`` directive is used as a building block for :ref:`Custom Directives-java` to transform
|
||||
the request context before it is passed to the inner route. To change only the request value itself the
|
||||
:ref:`-mapRequest-java-` directive can be used instead.
|
||||
|
||||
See :ref:`Request Transforming Directives-java` for an overview of similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRequestContext
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
.. _-mapResponse-java-:
|
||||
|
||||
mapResponse
|
||||
===========
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The ``mapResponse`` directive is used as a building block for :ref:`Custom Directives-java` to transform a response that
|
||||
was generated by the inner route. This directive transforms complete responses.
|
||||
|
||||
See also :ref:`-mapResponseHeaders-java-` or :ref:`-mapResponseEntity-java-` for more specialized variants and
|
||||
:ref:`Response Transforming Directives-java` for similar directives.
|
||||
|
||||
Example: Override status
|
||||
------------------------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapResponse
|
||||
|
||||
Example: Default to empty JSON response on errors
|
||||
-------------------------------------------------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapResponse-advanced
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.. _-mapResponseEntity-java-:
|
||||
|
||||
mapResponseEntity
|
||||
=================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
The ``mapResponseEntity`` directive is used as a building block for :ref:`Custom Directives-java` to transform a
|
||||
response entity that was generated by the inner route.
|
||||
|
||||
See :ref:`Response Transforming Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapResponseEntity
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.. _-mapResponseHeaders-java-:
|
||||
|
||||
mapResponseHeaders
|
||||
==================
|
||||
|
||||
Description
|
||||
-----------
|
||||
Changes the list of response headers that was generated by the inner route.
|
||||
|
||||
The ``mapResponseHeaders`` directive is used as a building block for :ref:`Custom Directives-java` to transform the list of
|
||||
response headers that was generated by the inner route.
|
||||
|
||||
See :ref:`Response Transforming Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapResponseHeaders
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.. _-mapRouteResult-java-:
|
||||
|
||||
mapRouteResult
|
||||
==============
|
||||
|
||||
Description
|
||||
-----------
|
||||
Changes the message the inner route sends to the responder.
|
||||
|
||||
The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the
|
||||
:class:`RouteResult` coming back from the inner route.
|
||||
|
||||
See :ref:`Result Transformation Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRouteResult
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
.. _-mapRouteResultFuture-java-:
|
||||
|
||||
mapRouteResultFuture
|
||||
====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Asynchronous version of :ref:`-mapRouteResult-java-`.
|
||||
|
||||
It's similar to :ref:`-mapRouteResultWith-java-`, however it's
|
||||
``Function<CompletionStage<RouteResult>, CompletionStage<RouteResult>>``
|
||||
instead of ``Function<RouteResult, CompletionStage<RouteResult>>`` which may be useful when
|
||||
combining multiple transformations and / or wanting to ``recover`` from a failed route result.
|
||||
|
||||
See :ref:`Result Transformation Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRouteResultFuture
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
.. _-mapRouteResultPF-java-:
|
||||
|
||||
mapRouteResultPF
|
||||
================
|
||||
|
||||
Description
|
||||
-----------
|
||||
*Partial Function* version of :ref:`-mapRouteResult-java-`.
|
||||
|
||||
Changes the message the inner route sends to the responder.
|
||||
|
||||
The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the
|
||||
:class:`RouteResult` coming back from the inner route. It's similar to the :ref:`-mapRouteResult-java-` directive but allows to
|
||||
specify a partial function that doesn't have to handle all potential ``RouteResult`` instances.
|
||||
|
||||
See :ref:`Result Transformation Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRouteResultPF
|
||||
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
.. _-mapRouteResultWith-java-:
|
||||
|
||||
mapRouteResultWith
|
||||
==================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Changes the message the inner route sends to the responder.
|
||||
|
||||
The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the
|
||||
:class:`RouteResult` coming back from the inner route. It's similar to the :ref:`-mapRouteResult-java-` directive but
|
||||
returning a ``CompletionStage`` instead of a result immediately, which may be useful for longer running transformations.
|
||||
|
||||
See :ref:`Result Transformation Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRouteResultWith
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
.. _-mapRouteResultWithPF-java-:
|
||||
|
||||
mapRouteResultWithPF
|
||||
====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Asynchronous variant of :ref:`-mapRouteResultPF-java-`.
|
||||
|
||||
Changes the message the inner route sends to the responder.
|
||||
|
||||
The ``mapRouteResult`` directive is used as a building block for :ref:`Custom Directives-java` to transform the
|
||||
:class:`RouteResult` coming back from the inner route.
|
||||
|
||||
See :ref:`Result Transformation Directives-java` for similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapRouteResultWithPF
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
.. _-mapSettings-java-:
|
||||
|
||||
mapSettings
|
||||
===========
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Transforms the ``RoutingSettings`` with a ``Function<RoutingSettings, RoutingSettings>``.
|
||||
|
||||
See also :ref:`-withSettings-java-` or :ref:`-extractSettings-java-`.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapSettings
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.. _-mapUnmatchedPath-java-:
|
||||
|
||||
mapUnmatchedPath
|
||||
================
|
||||
|
||||
Description
|
||||
-----------
|
||||
Transforms the unmatchedPath field of the request context for inner routes.
|
||||
|
||||
The ``mapUnmatchedPath`` directive is used as a building block for writing :ref:`Custom Directives-java`. You can use it
|
||||
for implementing custom path matching directives.
|
||||
|
||||
Use ``extractUnmatchedPath`` for extracting the current value of the unmatched path.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#mapUnmatchedPath
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
.. _-pass-java-:
|
||||
|
||||
pass
|
||||
====
|
||||
|
||||
Description
|
||||
-----------
|
||||
A directive that passes the request unchanged to its inner route.
|
||||
|
||||
It is usually used as a "neutral element" when combining directives generically.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#pass
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
.. _-provide-java-:
|
||||
|
||||
provide
|
||||
=======
|
||||
Description
|
||||
-----------
|
||||
Provides a constant value to the inner route.
|
||||
|
||||
The `provide` directive is used as a building block for :ref:`Custom Directives-java` to provide a single value to the
|
||||
inner route.
|
||||
|
||||
See :ref:`ProvideDirectives-java` for an overview of similar directives.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#provide
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
.. _-recoverRejections-java-:
|
||||
|
||||
recoverRejections
|
||||
=================
|
||||
Description
|
||||
-----------
|
||||
|
||||
**Low level directive** – unless you're sure you need to be working on this low-level you might instead
|
||||
want to try the :ref:`-handleRejections-java-` directive which provides a nicer DSL for building rejection handlers.
|
||||
|
||||
Transforms rejections from the inner route with a ``Function<Iterable<Rejection>, RouteResult>``.
|
||||
A ``RouteResult`` is either a ``Complete`` containing the ``HttpResponse`` or a ``Rejected`` containing the
|
||||
rejections.
|
||||
|
||||
.. note::
|
||||
To learn more about how and why rejections work read the :ref:`rejections-java` section of the documentation.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#recoverRejections
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
.. _-recoverRejectionsWith-java-:
|
||||
|
||||
recoverRejectionsWith
|
||||
=====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
**Low level directive** – unless you're sure you need to be working on this low-level you might instead
|
||||
want to try the :ref:`-handleRejections-java-` directive which provides a nicer DSL for building rejection handlers.
|
||||
|
||||
Transforms rejections from the inner route with a ``Function<Iterable<Rejection>, CompletionStage<RouteResult>>``.
|
||||
|
||||
Asynchronous version of :ref:`-recoverRejections-java-`.
|
||||
|
||||
See :ref:`-recoverRejections-java-` (the synchronous equivalent of this directive) for a detailed description.
|
||||
|
||||
.. note::
|
||||
To learn more about how and why rejections work read the :ref:`rejections-java` section of the documentation.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#recoverRejectionsWith
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
.. _-toStrictEntity-java-:
|
||||
|
||||
toStrictEntity
|
||||
==============
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Transforms the request entity to strict entity before it is handled by the inner route.
|
||||
|
||||
A timeout parameter is given and if the stream isn't completed after the timeout, the directive will be failed.
|
||||
|
||||
.. warning::
|
||||
|
||||
The directive will read the request entity into memory within the size limit(8M by default) and effectively disable streaming.
|
||||
The size limit can be configured globally with ``akka.http.parsing.max-content-length`` or
|
||||
overridden by wrapping with :ref:`-withSizeLimit-java-` or :ref:`-withoutSizeLimit-java-` directive.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#toStrictEntity
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
.. _-withExecutionContext-java-:
|
||||
|
||||
withExecutionContext
|
||||
====================
|
||||
|
||||
Description
|
||||
-----------
|
||||
|
||||
Allows running an inner route using an alternative ``ExecutionContextExecutor`` in place of the default one.
|
||||
|
||||
The execution context can be extracted in an inner route using :ref:`-extractExecutionContext-java-` directly,
|
||||
or used by directives which internally extract the materializer without sufracing this fact in the API.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. includecode:: ../../../../code/docs/http/javadsl/server/directives/BasicDirectivesExamplesTest.java#withExecutionContext
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue