Skip to content

Commit

Permalink
core: polishing and docs for ServerBuilder API (#3382)
Browse files Browse the repository at this point in the history
Most importantly, rename `ServerBuilder.bind` overload to `connectionSource`

This should signify that the binding is not yet done when the method is
called but only later when the source is materialized.

Also, avoiding overloading `bind` is good on its own.

Co-authored-by: Arnout Engelen <github@bzzt.net>
  • Loading branch information
jrudolph and raboof authored Jul 21, 2020
1 parent 05ae282 commit f23a027
Show file tree
Hide file tree
Showing 20 changed files with 173 additions and 59 deletions.
12 changes: 6 additions & 6 deletions akka-http-core/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ akka.http {
# "PREVIEW" features that are not yet fully production ready.
# These flags can change or be removed between patch releases.
preview {
# ONLY WORKS WITH `bindAndHandleAsync` (currently)
#
# If this setting is enabled AND the akka-http2-support is found
# on the classpath the usual Http().bind... method calls will bind
# using HTTP/2. Please note that you must configure HTTPS while doing so.
# on the classpath, `Http().newServerAt(...).bind` and `bindSync`
# will be enabled to use HTTP/2.
#
# `Http().newServerAt(...).bindFlow` and `connectionSource()` are not supported.
enable-http2 = off
}

Expand Down Expand Up @@ -71,8 +71,8 @@ akka.http {
# Set to 'infinite' to disable automatic connection closure (which will risk to leak connections).
linger-timeout = 1 min

# The maximum number of concurrently accepted connections when using the
# `Http().bindAndHandle` methods.
# The maximum number of concurrently accepted connections when binding a server using
# `Http().newServerAt().bindXYZ()` methods.
#
# This setting doesn't apply to the `Http().bind` method which will still
# deliver an unlimited backpressured stream of incoming connections.
Expand Down
9 changes: 4 additions & 5 deletions akka-http-core/src/main/scala/akka/http/javadsl/Http.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ import akka.http.scaladsl.{ model => sm }
import akka.http.javadsl.model.ws._
import akka.http.javadsl.settings.{ ClientConnectionSettings, ConnectionPoolSettings, ServerSettings }
import akka.japi.Pair
import akka.japi.function.Function
import akka.stream.TLSProtocol._
import akka.stream.{ Materializer, SystemMaterializer }
import akka.stream.Materializer
import akka.stream.javadsl.{ BidiFlow, Flow, Source }
import akka.stream.scaladsl.Keep

Expand Down Expand Up @@ -105,7 +104,7 @@ class Http(system: ExtendedActorSystem) extends akka.actor.Extension {
* The server will be bound using HTTPS if the [[ConnectHttp]] object is configured with an [[HttpsConnectionContext]],
* or the [[defaultServerHttpContext]] has been configured to be an [[HttpsConnectionContext]].
*
* @deprecated since 10.2.0: Use Http.get(system).newServerAt(interface, port).bind() instead
* @deprecated since 10.2.0: Use Http.get(system).newServerAt(interface, port).connectionSource() instead
*/
@Deprecated
@deprecated("Use newServerAt instead", since = "10.2.0")
Expand All @@ -131,7 +130,7 @@ class Http(system: ExtendedActorSystem) extends akka.actor.Extension {
* The server will be bound using HTTPS if the [[ConnectHttp]] object is configured with an [[HttpsConnectionContext]],
* or the [[defaultServerHttpContext]] has been configured to be an [[HttpsConnectionContext]].
*
* @deprecated since 10.2.0: Use Http.get(system).newServerAt(interface, port).withSettings(settings).bind() instead
* @deprecated since 10.2.0: Use Http.get(system).newServerAt(interface, port).withSettings(settings).connectionSource() instead
*/
@Deprecated
@deprecated("Use newServerAt instead", since = "10.2.0")
Expand Down Expand Up @@ -159,7 +158,7 @@ class Http(system: ExtendedActorSystem) extends akka.actor.Extension {
* The server will be bound using HTTPS if the [[ConnectHttp]] object is configured with an [[HttpsConnectionContext]],
* or the [[defaultServerHttpContext]] has been configured to be an [[HttpsConnectionContext]].
*
* @deprecated since 10.2.0: Use Http.get(system).newServerAt(interface, port).withSettings(settings).logTo(log).bind() instead
* @deprecated since 10.2.0: Use Http.get(system).newServerAt(interface, port).withSettings(settings).logTo(log).connectionSource() instead
*/
@Deprecated
@deprecated("Use newServerAt instead", since = "10.2.0")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import akka.http.javadsl.model.{ HttpRequest, HttpResponse }
import akka.http.javadsl.settings.ServerSettings
import akka.http.scaladsl
import akka.http.scaladsl.util.FastFuture
import akka.http.scaladsl.{ HttpExt, model => sm }
import akka.http.scaladsl.{ model => sm }
import akka.japi.function.Function
import akka.stream.javadsl.Flow
import akka.stream.scaladsl.Source
Expand All @@ -26,7 +26,7 @@ import scala.concurrent.ExecutionContext
/**
* Builder API to create server bindings.
*
* Use [[HttpExt.newServerAt()]] to create a builder, use methods to customize settings,
* Use [[Http.newServerAt()]] to create a builder, use methods to customize settings,
* and then call one of the bind* methods to bind a server.
*/
trait ServerBuilder {
Expand Down Expand Up @@ -106,18 +106,17 @@ trait ServerBuilder {
* Creates a [[akka.stream.javadsl.Source]] of [[akka.http.javadsl.IncomingConnection]] instances which represents a prospective HTTP server binding
* on the given `endpoint`.
*
* If the configured port is 0 the resulting source can be materialized several times. Each materialization will
* Note that each materialization will create a new binding, so
*
* * if the configured port is 0 the resulting source can be materialized several times. Each materialization will
* then be assigned a new local port by the operating system, which can then be retrieved by the materialized
* [[akka.http.javadsl.ServerBinding]].
*
* If the configured port is non-zero subsequent materialization attempts of the produced source will immediately
* * if the configured port is non-zero subsequent materialization attempts of the produced source will immediately
* fail, unless the first materialization has already been unbound. Unbinding can be triggered via the materialized
* [[akka.http.javadsl.ServerBinding]].
*
* If no `port` is explicitly given (or the port value is negative) the protocol's default port will be used,
* which is 80 for HTTP and 443 for HTTPS.
*/
def bind(): Source[IncomingConnection, CompletionStage[ServerBinding]]
def connectionSource(): Source[IncomingConnection, CompletionStage[ServerBinding]]
}

/**
Expand Down Expand Up @@ -172,7 +171,7 @@ object ServerBuilder {
interface, port, context.asScala, settings.asScala, log)
.map(new ServerBinding(_)).toJava

def bind(): Source[IncomingConnection, CompletionStage[ServerBinding]] =
def connectionSource(): Source[IncomingConnection, CompletionStage[ServerBinding]] =
http.bindImpl(interface, port, context.asScala, settings.asScala, log)
.map(new IncomingConnection(_))
.mapMaterializedValue(_.map(new ServerBinding(_))(ExecutionContexts.sameThreadExecutionContext).toJava)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class HttpExt private[http] (private val config: Config)(implicit val system: Ex
* To configure additional settings for a server started using this method,
* use the `akka.http.server` config section or pass in a [[akka.http.scaladsl.settings.ServerSettings]] explicitly.
*/
@deprecated("Use Http.newServerAt(...)...bind() to create server bindings.", since = "10.2.0")
@deprecated("Use Http.newServerAt(...)...connectionSource() to create a source that can be materialized to a binding.", since = "10.2.0")
def bind(interface: String, port: Int = DefaultPortForProtocol,
connectionContext: ConnectionContext = defaultServerHttpContext,
settings: ServerSettings = ServerSettings(system),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,17 @@ trait ServerBuilder {
* Creates a [[akka.stream.scaladsl.Source]] of [[akka.http.scaladsl.Http.IncomingConnection]] instances which represents a prospective HTTP server binding
* on the given `endpoint`.
*
* If the configured port is 0 the resulting source can be materialized several times. Each materialization will
* Note that each materialization will create a new binding, so
*
* * if the configured port is 0 the resulting source can be materialized several times. Each materialization will
* then be assigned a new local port by the operating system, which can then be retrieved by the materialized
* [[akka.http.scaladsl.Http.ServerBinding]].
*
* If the configured port is non-zero subsequent materialization attempts of the produced source will immediately
* * if the configured port is non-zero subsequent materialization attempts of the produced source will immediately
* fail, unless the first materialization has already been unbound. Unbinding can be triggered via the materialized
* [[akka.http.scaladsl.Http.ServerBinding]].
*
* If no `port` is explicitly given (or the port value is negative) the protocol's default port will be used,
* which is 80 for HTTP and 443 for HTTPS.
*/
def bind(): Source[Http.IncomingConnection, Future[ServerBinding]]
def connectionSource(): Source[Http.IncomingConnection, Future[ServerBinding]]
}

/**
Expand Down Expand Up @@ -128,7 +127,7 @@ private[http] object ServerBuilder {
def adaptSettings(f: ServerSettings => ServerSettings): ServerBuilder = copy(settings = f(settings))
def enableHttps(newContext: HttpsConnectionContext): ServerBuilder = copy(context = newContext)

def bind(): Source[Http.IncomingConnection, Future[ServerBinding]] =
def connectionSource(): Source[Http.IncomingConnection, Future[ServerBinding]] =
http.bindImpl(interface, port, context, settings, log)

def bindFlow(handlerFlow: Flow[HttpRequest, HttpResponse, _]): Future[ServerBinding] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ class HostConnectionPoolSpec extends AkkaSpecWithMaterializer(

/** Transport that uses actual top-level Http APIs to establish a plaintext HTTP connection */
case object AkkaHttpEngineTCP extends TopLevelApiClientServerImplementation {
protected override def bindServerSource = Http().newServerAt("localhost", 0).bind()
protected override def bindServerSource = Http().newServerAt("localhost", 0).connectionSource()
protected def clientConnectionFlow(serverBinding: ServerBinding, connectionKillSwitch: SharedKillSwitch): Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = {
val clientConnectionSettings = ClientConnectionSettings(system).withTransport(new KillSwitchedClientTransport(connectionKillSwitch))
Http().outgoingConnectionUsingContext(host = "localhost", port = serverBinding.localAddress.getPort, connectionContext = ConnectionContext.noEncryption(), settings = clientConnectionSettings)
Expand All @@ -738,7 +738,7 @@ class HostConnectionPoolSpec extends AkkaSpecWithMaterializer(
* Currently requires an /etc/hosts entry that points akka.example.org to a locally bindable address.
*/
case object AkkaHttpEngineTLS extends TopLevelApiClientServerImplementation {
protected override def bindServerSource = Http().newServerAt("akka.example.org", 0).enableHttps(ExampleHttpContexts.exampleServerContext).bind()
protected override def bindServerSource = Http().newServerAt("akka.example.org", 0).enableHttps(ExampleHttpContexts.exampleServerContext).connectionSource()
protected def clientConnectionFlow(serverBinding: ServerBinding, connectionKillSwitch: SharedKillSwitch): Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] = {
val clientConnectionSettings = ClientConnectionSettings(system).withTransport(new KillSwitchedClientTransport(connectionKillSwitch))
Http().outgoingConnectionUsingContext(host = "akka.example.org", port = serverBinding.localAddress.getPort, connectionContext = ExampleHttpContexts.exampleClientContext, settings = clientConnectionSettings)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ClientServerSpec extends AkkaSpecWithMaterializer(

"properly bind a server" in {
val probe = TestSubscriber.manualProbe[Http.IncomingConnection]()
val binding = Http().newServerAt("127.0.0.1", 0).bind().to(Sink.fromSubscriber(probe)).run()
val binding = Http().newServerAt("127.0.0.1", 0).connectionSource().to(Sink.fromSubscriber(probe)).run()
val sub = probe.expectSubscription() // if we get it we are bound
Await.result(binding, 1.second.dilated).unbind()
sub.cancel()
Expand All @@ -68,7 +68,7 @@ class ClientServerSpec extends AkkaSpecWithMaterializer(
val probe = TestSubscriber.manualProbe[Http.IncomingConnection]()
// not really testing anything here, problem is that it is hard to find an unused port otherwise
val settings = ServerSettings(system).withDefaultHttpPort(0)
val bindingF = Http().newServerAt("0.0.0.0", 0).withSettings(settings).bind().to(Sink.fromSubscriber(probe)).run()
val bindingF = Http().newServerAt("0.0.0.0", 0).withSettings(settings).connectionSource().to(Sink.fromSubscriber(probe)).run()
val sub = probe.expectSubscription() // if we get it we are bound
val binding = Await.result(bindingF, 1.second.dilated)
// though, that wouldn't probably happen because binding ports < 1024 is restricted in most environments
Expand All @@ -79,7 +79,7 @@ class ClientServerSpec extends AkkaSpecWithMaterializer(

"report failure if bind fails" in EventFilter[BindException](occurrences = 2).intercept {
val (hostname, port) = SocketUtil.temporaryServerHostnameAndPort()
val binding = Http().newServerAt(hostname, port).bind()
val binding = Http().newServerAt(hostname, port).connectionSource()
val probe1 = TestSubscriber.manualProbe[Http.IncomingConnection]()
// Bind succeeded, we have a local address
val b1 = Await.result(binding.to(Sink.fromSubscriber(probe1)).run(), 3.seconds.dilated)
Expand Down Expand Up @@ -180,7 +180,7 @@ class ClientServerSpec extends AkkaSpecWithMaterializer(

"be added when using bind API" in new RemoteAddressTestScenario {
def createBinding(): Future[ServerBinding] =
Http().newServerAt(hostname, port).withSettings(settings).bind()
Http().newServerAt(hostname, port).withSettings(settings).connectionSource()
.map(_.flow.join(Flow[HttpRequest].map(handler)).run())
.to(Sink.ignore)
.run()
Expand Down Expand Up @@ -883,7 +883,7 @@ Host: example.com
// automatically bind a server
val (connSource, binding: Future[ServerBinding]) = {
val settings = configOverrides.toOption.fold(ServerSettings(system))(ServerSettings(_))
val connections = Http().newServerAt(hostname, port).withSettings(settings).bind()
val connections = Http().newServerAt(hostname, port).withSettings(settings).connectionSource()
val probe = TestSubscriber.manualProbe[Http.IncomingConnection]()
val binding = connections.to(Sink.fromSubscriber(probe)).run()
(probe, binding)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ private[http] object RouteTest {
def runRouteClientServer(request: HttpRequest, route: Route, serverSettings: ServerSettings)(implicit system: ActorSystem): Future[HttpResponse] = {
import system.dispatcher
for {
binding <- Http().bindAndHandle(route, "127.0.0.1", 0, settings = serverSettings)
binding <- Http().newServerAt("127.0.0.1", 0).withSettings(settings = serverSettings).bind(route)
port = binding.localAddress.getPort
targetUri = request.uri.withHost("127.0.0.1").withPort(port).withScheme("http")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class AkkaHttpServerLatencyMultiNodeSpec extends MultiNodeSpec(AkkaHttpServerLat
runOn(server) {
val (_, port) = SocketUtil.temporaryServerHostnameAndPort()
info(s"Binding Akka HTTP Server to port: $port @ ${myself}")
val futureBinding = Http().bindAndHandle(routes, "0.0.0.0", port)
val futureBinding = Http().newServerAt("0.0.0.0", port).bind(routes)

_binding = Some(futureBinding.futureValue)
setServerPort(port)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public void compileOnly() throws Exception {
ConnectionPoolSettings conSettings = null;
LoggingAdapter log = null;

http.newServerAt("127.0.0.1", 8080).bind();
http.newServerAt("127.0.0.1", 8080).enableHttps(httpsContext).bind();
http.newServerAt("127.0.0.1", 8080).connectionSource();
http.newServerAt("127.0.0.1", 8080).enableHttps(httpsContext).connectionSource();

final Flow<HttpRequest, HttpResponse, ?> handler = null;
http.newServerAt("127.0.0.1", 8080).bindFlow(handler);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,10 @@ abstract class HttpApp extends Directives {
implicit val materializer = ActorMaterializer()
implicit val executionContext: ExecutionContextExecutor = theSystem.dispatcher

val bindingFuture = Http().bindAndHandle(
handler = routes,
interface = host,
port = port,
settings = settings)
val bindingFuture =
Http().newServerAt(host, port)
.withSettings(settings)
.bind(routes)

bindingFuture.onComplete {
case Success(binding) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ IO(Http)(system) ! Http.Bind(service, "0.0.0.0", port = 8080)
needs to be changed into:

```scala
Http().bindAndHandle(routes, "0.0.0.0", port = 8080)
Http().newServerAt("0.0.0.0", port = 8080).bind(routes)
```

### Other removed features
Expand Down
25 changes: 25 additions & 0 deletions docs/src/main/paradox/migration-guide/migration-guide-10.2.x.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,31 @@ If you find an unexpected incompatibility please let us know, so we can check wh

## Akka HTTP 10.1.11 -> 10.2.0

### Hiding Materializer

Recent Akka versions introduced a singleton system materializer that can be summoned from an ActorSystem automatically. In many cases,
`Materializer` arguments are now optional and APIs have been simplified to require materializers in fewer places. In fact, most common
uses do not require a materializer any more at all.

### New ServerBuilder API to create server bindings

To simplify binding servers (and to make it more consistent between Java and Scala), a new @apidoc[ServerBuilder] API has been introduced.
The most common change needed to bind a server to handle routes will be from:

Scala
: @@snip [AkkaHttp1020MigrationSpec.scala]($test$/scala/docs/http/scaladsl/server/AkkaHttp1020MigrationSpec.scala) { #old-binding }

Java
: @@snip [AkkaHttp1020MigrationExample.java]($test$/java/docs/http/javadsl/server/AkkaHttp1020MigrationExample.java) { #old-binding }

to:

Scala
: @@snip [AkkaHttp1020MigrationSpec.scala]($test$/scala/docs/http/scaladsl/server/AkkaHttp1020MigrationSpec.scala) { #new-binding }

Java
: @@snip [AkkaHttp1020MigrationExample.java]($test$/java/docs/http/javadsl/server/AkkaHttp1020MigrationExample.java) { #new-binding }

### HTTP/2 support requires JDK 8 update 252 or later

JVM support for ALPN has been backported to JDK 8u252 which is now widely available. Support for using the Jetty ALPN
Expand Down
3 changes: 2 additions & 1 deletion docs/src/main/paradox/release-notes/10.2.x.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# 10.2.x Release Notes

Among other things, 10.2.0-RC1 contains:
Among other things, 10.2.0-RC2 contains:

* APIs and documentation now provide a seamless experience with Akka 2.6 and the new Actor APIs. Akka HTTP 10.2.x will still be supporting Akka 2.5 to ease incremental updating.
* A new @ref[ServerBuilder API](../migration-guide/migration-guide-10.2.x.md#new-serverbuilder-api-to-create-server-bindings) to simplify and streamline binding servers.
* Some new features, including the ability to [attach attributes to requests and responses](https://doc.akka.io/docs/akka-http/10.2.0-M1/common/http-model.html#attributes).
* Allow client setting overrides for target hosts @ref[via configuration](../client-side/configuration.md#per-host-overrides) [#2574](https://github.com/akka/akka-http/pull/2574)
* Improved default configuration, such as [disabling transparent HEAD support by default](https://github.com/akka/akka-http/issues/2088).
Expand Down
Loading

0 comments on commit f23a027

Please sign in to comment.