+str #16889: Configurable TCP close modes

This commit is contained in:
Endre Sándor Varga 2015-06-19 13:30:48 +02:00
parent e5305af485
commit 1c6b49b1aa
7 changed files with 179 additions and 30 deletions

View file

@ -58,7 +58,7 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
val effectivePort = if (port >= 0) port else if (httpsContext.isEmpty) 80 else 443 val effectivePort = if (port >= 0) port else if (httpsContext.isEmpty) 80 else 443
val tlsStage = sslTlsStage(httpsContext, Server) val tlsStage = sslTlsStage(httpsContext, Server)
val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] = val connections: Source[Tcp.IncomingConnection, Future[Tcp.ServerBinding]] =
Tcp().bind(interface, effectivePort, settings.backlog, settings.socketOptions, settings.timeouts.idleTimeout) Tcp().bind(interface, effectivePort, settings.backlog, settings.socketOptions, halfClose = false, settings.timeouts.idleTimeout)
connections.map { connections.map {
case Tcp.IncomingConnection(localAddress, remoteAddress, flow) case Tcp.IncomingConnection(localAddress, remoteAddress, flow)
val layer = serverLayer(settings, Some(remoteAddress), log) val layer = serverLayer(settings, Some(remoteAddress), log)
@ -189,7 +189,7 @@ class HttpExt(config: Config)(implicit system: ActorSystem) extends akka.actor.E
val layer = clientLayer(hostHeader, settings, log) val layer = clientLayer(hostHeader, settings, log)
val tlsStage = sslTlsStage(httpsContext, Client) val tlsStage = sslTlsStage(httpsContext, Client)
val transportFlow = Tcp().outgoingConnection(new InetSocketAddress(host, port), localAddress, val transportFlow = Tcp().outgoingConnection(new InetSocketAddress(host, port), localAddress,
settings.socketOptions, settings.connectingTimeout, settings.idleTimeout) settings.socketOptions, halfClose = true, settings.connectingTimeout, settings.idleTimeout)
layer.atop(tlsStage).joinMat(transportFlow) { (_, tcpConnFuture) layer.atop(tlsStage).joinMat(transportFlow) { (_, tcpConnFuture)
import system.dispatcher import system.dispatcher

View file

@ -5,6 +5,7 @@ package akka.stream.io
import akka.stream.scaladsl.Tcp.OutgoingConnection import akka.stream.scaladsl.Tcp.OutgoingConnection
import scala.collection.immutable
import scala.concurrent.{ Future, Await } import scala.concurrent.{ Future, Await }
import akka.io.Tcp._ import akka.io.Tcp._
@ -19,7 +20,7 @@ import akka.stream.testkit.Utils._
import akka.stream.scaladsl._ import akka.stream.scaladsl._
import akka.stream.testkit.TestUtils.temporaryServerAddress import akka.stream.testkit.TestUtils.temporaryServerAddress
class TcpSpec extends AkkaSpec with TcpHelper { class TcpSpec extends AkkaSpec("akka.io.tcp.windows-connection-abort-workaround-enabled=auto") with TcpHelper {
import akka.stream.io.TcpHelper._ import akka.stream.io.TcpHelper._
var demand = 0L var demand = 0L
@ -341,6 +342,44 @@ class TcpSpec extends AkkaSpec with TcpHelper {
server.close() server.close()
} }
"properly full-close if requested" in assertAllStagesStopped {
import system.dispatcher
val serverAddress = temporaryServerAddress()
val writeButIgnoreRead: Flow[ByteString, ByteString, Unit] =
Flow.wrap(Sink.ignore, Source.single(ByteString("Early response")))(Keep.right)
val binding = Tcp().bind(serverAddress.getHostName, serverAddress.getPort, halfClose = false).toMat(Sink.foreach { conn
conn.flow.join(writeButIgnoreRead).run()
})(Keep.left).run()
val result = Source(() Iterator.continually(ByteString("client data")))
.via(Tcp().outgoingConnection(serverAddress.getHostName, serverAddress.getPort))
.runFold(ByteString.empty)(_ ++ _)
Await.result(result, 3.seconds) should ===(ByteString("Early response"))
binding.map(_.unbind())
}
"Echo should work even if server is in full close mode" in {
import system.dispatcher
val serverAddress = temporaryServerAddress()
val binding = Tcp().bind(serverAddress.getHostName, serverAddress.getPort, halfClose = false).toMat(Sink.foreach { conn
conn.flow.join(Flow[ByteString]).run()
})(Keep.left).run()
val result = Source(immutable.Iterable.fill(10000)(ByteString(0)))
.via(Tcp().outgoingConnection(serverAddress, halfClose = true))
.runFold(0)(_ + _.size)
Await.result(result, 3.seconds) should ===(10000)
binding.map(_.unbind())
}
} }
"TCP listen stream" must { "TCP listen stream" must {

View file

@ -33,6 +33,7 @@ private[akka] object StreamTcpManager {
localAddressPromise: Promise[InetSocketAddress], localAddressPromise: Promise[InetSocketAddress],
remoteAddress: InetSocketAddress, remoteAddress: InetSocketAddress,
localAddress: Option[InetSocketAddress], localAddress: Option[InetSocketAddress],
halfClose: Boolean,
options: immutable.Traversable[SocketOption], options: immutable.Traversable[SocketOption],
connectTimeout: Duration, connectTimeout: Duration,
idleTimeout: Duration) idleTimeout: Duration)
@ -47,6 +48,7 @@ private[akka] object StreamTcpManager {
flowSubscriber: Subscriber[StreamTcp.IncomingConnection], flowSubscriber: Subscriber[StreamTcp.IncomingConnection],
endpoint: InetSocketAddress, endpoint: InetSocketAddress,
backlog: Int, backlog: Int,
halfClose: Boolean,
options: immutable.Traversable[SocketOption], options: immutable.Traversable[SocketOption],
idleTimeout: Duration) idleTimeout: Duration)
extends DeadLetterSuppression with NoSerializationVerificationNeeded extends DeadLetterSuppression with NoSerializationVerificationNeeded
@ -72,18 +74,18 @@ private[akka] class StreamTcpManager extends Actor {
} }
def receive: Receive = { def receive: Receive = {
case Connect(processorPromise, localAddressPromise, remoteAddress, localAddress, options, connectTimeout, _) case Connect(processorPromise, localAddressPromise, remoteAddress, localAddress, halfClose, options, connectTimeout, _)
val connTimeout = connectTimeout match { val connTimeout = connectTimeout match {
case x: FiniteDuration Some(x) case x: FiniteDuration Some(x)
case _ None case _ None
} }
val processorActor = context.actorOf(TcpStreamActor.outboundProps(processorPromise, localAddressPromise, val processorActor = context.actorOf(TcpStreamActor.outboundProps(processorPromise, localAddressPromise, halfClose,
Tcp.Connect(remoteAddress, localAddress, options, connTimeout, pullMode = true), Tcp.Connect(remoteAddress, localAddress, options, connTimeout, pullMode = true),
materializerSettings = ActorFlowMaterializerSettings(context.system)), name = encName("client", remoteAddress)) materializerSettings = ActorFlowMaterializerSettings(context.system)), name = encName("client", remoteAddress))
processorActor ! ExposedProcessor(ActorProcessor[ByteString, ByteString](processorActor)) processorActor ! ExposedProcessor(ActorProcessor[ByteString, ByteString](processorActor))
case Bind(localAddressPromise, unbindPromise, flowSubscriber, endpoint, backlog, options, _) case Bind(localAddressPromise, unbindPromise, flowSubscriber, endpoint, backlog, halfClose, options, _)
val props = TcpListenStreamActor.props(localAddressPromise, unbindPromise, flowSubscriber, val props = TcpListenStreamActor.props(localAddressPromise, unbindPromise, flowSubscriber, halfClose,
Tcp.Bind(context.system.deadLetters, endpoint, backlog, options, pullMode = true), Tcp.Bind(context.system.deadLetters, endpoint, backlog, options, pullMode = true),
ActorFlowMaterializerSettings(context.system)) ActorFlowMaterializerSettings(context.system))
.withDispatcher(context.props.dispatcher) .withDispatcher(context.props.dispatcher)

View file

@ -24,19 +24,21 @@ private[akka] object TcpStreamActor {
def outboundProps(processorPromise: Promise[Processor[ByteString, ByteString]], def outboundProps(processorPromise: Promise[Processor[ByteString, ByteString]],
localAddressPromise: Promise[InetSocketAddress], localAddressPromise: Promise[InetSocketAddress],
halfClose: Boolean,
connectCmd: Connect, connectCmd: Connect,
materializerSettings: ActorFlowMaterializerSettings): Props = materializerSettings: ActorFlowMaterializerSettings): Props =
Props(new OutboundTcpStreamActor(processorPromise, localAddressPromise, connectCmd, Props(new OutboundTcpStreamActor(processorPromise, localAddressPromise, halfClose, connectCmd,
materializerSettings)).withDispatcher(materializerSettings.dispatcher).withDeploy(Deploy.local) materializerSettings)).withDispatcher(materializerSettings.dispatcher).withDeploy(Deploy.local)
def inboundProps(connection: ActorRef, settings: ActorFlowMaterializerSettings): Props = def inboundProps(connection: ActorRef, halfClose: Boolean, settings: ActorFlowMaterializerSettings): Props =
Props(new InboundTcpStreamActor(connection, settings)).withDispatcher(settings.dispatcher).withDeploy(Deploy.local) Props(new InboundTcpStreamActor(connection, halfClose, settings)).withDispatcher(settings.dispatcher).withDeploy(Deploy.local)
} }
/** /**
* INTERNAL API * INTERNAL API
*/ */
private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerSettings) extends Actor private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerSettings, halfClose: Boolean) extends Actor
with ActorLogging { with ActorLogging {
import TcpStreamActor._ import TcpStreamActor._
@ -47,6 +49,8 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
val primaryOutputs: Outputs = new SimpleOutputs(self, readPump) val primaryOutputs: Outputs = new SimpleOutputs(self, readPump)
def fullClose: Boolean = !halfClose
object tcpInputs extends DefaultInputTransferStates { object tcpInputs extends DefaultInputTransferStates {
private var closed: Boolean = false private var closed: Boolean = false
private var pendingElement: ByteString = null private var pendingElement: ByteString = null
@ -112,10 +116,13 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
object tcpOutputs extends DefaultOutputTransferStates { object tcpOutputs extends DefaultOutputTransferStates {
private var closed: Boolean = false private var closed: Boolean = false
private var pendingDemand = true private var lastWriteAcked = true
private var connection: ActorRef = _ private var connection: ActorRef = _
def isClosed: Boolean = closed def isClosed: Boolean = closed
// Full-close mode needs to wait for the last write Ack before sending Close to avoid doing a connection reset
def isFlushed: Boolean = closed && (halfClose || lastWriteAcked)
private def initialized: Boolean = connection ne null private def initialized: Boolean = connection ne null
def setConnection(c: ActorRef): Unit = { def setConnection(c: ActorRef): Unit = {
@ -128,7 +135,12 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
def handleWrite: Receive = { def handleWrite: Receive = {
case WriteAck case WriteAck
pendingDemand = true lastWriteAcked = true
if (fullClose && closed) {
// Finish the closing after the last write has been flushed in full close mode.
connection ! Close
tryShutdown()
}
writePump.pump() writePump.pump()
} }
@ -141,9 +153,16 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
override def complete(): Unit = { override def complete(): Unit = {
if (!closed && initialized) { if (!closed && initialized) {
closed = true closed = true
if (tcpInputs.isClosed) if (tcpInputs.isClosed && (halfClose || lastWriteAcked)) {
// We can immediately close if
// - half close mode, and read size already finished
// - full close mode, and last write has been acked
//
// if in full close mode, and has a non-acknowledged write, we will do the closing in handleWrite
// when the Ack arrives
connection ! Close connection ! Close
else tryShutdown()
} else
connection ! ConfirmedClose connection ! ConfirmedClose
} }
} }
@ -153,10 +172,10 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
override def enqueueOutputElement(elem: Any): Unit = { override def enqueueOutputElement(elem: Any): Unit = {
ReactiveStreamsCompliance.requireNonNullElement(elem) ReactiveStreamsCompliance.requireNonNullElement(elem)
connection ! Write(elem.asInstanceOf[ByteString], WriteAck) connection ! Write(elem.asInstanceOf[ByteString], WriteAck)
pendingDemand = false lastWriteAcked = false
} }
override def demandAvailable: Boolean = pendingDemand override def demandAvailable: Boolean = lastWriteAcked
} }
object writePump extends Pump { object writePump extends Pump {
@ -168,6 +187,12 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
} }
override protected def pumpFinished(): Unit = { override protected def pumpFinished(): Unit = {
if (fullClose) {
// In full close mode we shut down the read size immediately once the write side is finished
tcpInputs.cancel()
primaryOutputs.complete()
readPump.pump()
}
tcpOutputs.complete() tcpOutputs.complete()
primaryInputs.cancel() primaryInputs.cancel()
tryShutdown() tryShutdown()
@ -228,7 +253,7 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
} }
def tryShutdown(): Unit = def tryShutdown(): Unit =
if (primaryInputs.isClosed && tcpInputs.isClosed && tcpOutputs.isClosed) if (primaryInputs.isClosed && tcpInputs.isClosed && tcpOutputs.isFlushed)
context.stop(self) context.stop(self)
override def postStop(): Unit = { override def postStop(): Unit = {
@ -245,8 +270,8 @@ private[akka] abstract class TcpStreamActor(val settings: ActorFlowMaterializerS
* INTERNAL API * INTERNAL API
*/ */
private[akka] class InboundTcpStreamActor( private[akka] class InboundTcpStreamActor(
val connection: ActorRef, _settings: ActorFlowMaterializerSettings) val connection: ActorRef, _halfClose: Boolean, _settings: ActorFlowMaterializerSettings)
extends TcpStreamActor(_settings) { extends TcpStreamActor(_settings, _halfClose) {
connection ! Register(self, keepOpenOnPeerClosed = true, useResumeWriting = false) connection ! Register(self, keepOpenOnPeerClosed = true, useResumeWriting = false)
tcpInputs.setConnection(connection) tcpInputs.setConnection(connection)
@ -258,8 +283,9 @@ private[akka] class InboundTcpStreamActor(
*/ */
private[akka] class OutboundTcpStreamActor(processorPromise: Promise[Processor[ByteString, ByteString]], private[akka] class OutboundTcpStreamActor(processorPromise: Promise[Processor[ByteString, ByteString]],
localAddressPromise: Promise[InetSocketAddress], localAddressPromise: Promise[InetSocketAddress],
_halfClose: Boolean,
val connectCmd: Connect, _settings: ActorFlowMaterializerSettings) val connectCmd: Connect, _settings: ActorFlowMaterializerSettings)
extends TcpStreamActor(_settings) { extends TcpStreamActor(_settings, _halfClose) {
import TcpStreamActor._ import TcpStreamActor._
import context.system import context.system

View file

@ -24,8 +24,9 @@ private[akka] object TcpListenStreamActor {
def props(localAddressPromise: Promise[InetSocketAddress], def props(localAddressPromise: Promise[InetSocketAddress],
unbindPromise: Promise[() Future[Unit]], unbindPromise: Promise[() Future[Unit]],
flowSubscriber: Subscriber[StreamTcp.IncomingConnection], flowSubscriber: Subscriber[StreamTcp.IncomingConnection],
halfClose: Boolean,
bindCmd: Tcp.Bind, materializerSettings: ActorFlowMaterializerSettings): Props = { bindCmd: Tcp.Bind, materializerSettings: ActorFlowMaterializerSettings): Props = {
Props(new TcpListenStreamActor(localAddressPromise, unbindPromise, flowSubscriber, bindCmd, materializerSettings)) Props(new TcpListenStreamActor(localAddressPromise, unbindPromise, flowSubscriber, halfClose, bindCmd, materializerSettings))
.withDeploy(Deploy.local) .withDeploy(Deploy.local)
} }
} }
@ -36,6 +37,7 @@ private[akka] object TcpListenStreamActor {
private[akka] class TcpListenStreamActor(localAddressPromise: Promise[InetSocketAddress], private[akka] class TcpListenStreamActor(localAddressPromise: Promise[InetSocketAddress],
unbindPromise: Promise[() Future[Unit]], unbindPromise: Promise[() Future[Unit]],
flowSubscriber: Subscriber[StreamTcp.IncomingConnection], flowSubscriber: Subscriber[StreamTcp.IncomingConnection],
halfClose: Boolean,
bindCmd: Tcp.Bind, settings: ActorFlowMaterializerSettings) extends Actor bindCmd: Tcp.Bind, settings: ActorFlowMaterializerSettings) extends Actor
with Pump with ActorLogging { with Pump with ActorLogging {
import ReactiveStreamsCompliance._ import ReactiveStreamsCompliance._
@ -141,7 +143,7 @@ private[akka] class TcpListenStreamActor(localAddressPromise: Promise[InetSocket
def runningPhase = TransferPhase(primaryOutputs.NeedsDemand && incomingConnections.NeedsInput) { () def runningPhase = TransferPhase(primaryOutputs.NeedsDemand && incomingConnections.NeedsInput) { ()
val (connected: Connected, connection: ActorRef) = incomingConnections.dequeueInputElement() val (connected: Connected, connection: ActorRef) = incomingConnections.dequeueInputElement()
val tcpStreamActor = context.actorOf(TcpStreamActor.inboundProps(connection, settings)) val tcpStreamActor = context.actorOf(TcpStreamActor.inboundProps(connection, halfClose, settings))
val processor = ActorProcessor[ByteString, ByteString](tcpStreamActor) val processor = ActorProcessor[ByteString, ByteString](tcpStreamActor)
val conn = StreamTcp.IncomingConnection( val conn = StreamTcp.IncomingConnection(
connected.localAddress, connected.localAddress,

View file

@ -99,13 +99,28 @@ class Tcp(system: ExtendedActorSystem) extends akka.actor.Extension {
/** /**
* Creates a [[Tcp.ServerBinding]] instance which represents a prospective TCP server binding on the given `endpoint`. * Creates a [[Tcp.ServerBinding]] instance which represents a prospective TCP server binding on the given `endpoint`.
*
* @param interface The interface to listen on
* @param port The port to listen on
* @param backlog Controls the size of the connection backlog
* @param options TCP options for the connections, see [[akka.io.Tcp]] for details
* @param halfClose
* Controls whether the connection is kept open even after writing has been completed to the accepted
* TCP connections.
* If set to true, the connection will implement the TCP half-close mechanism, allowing the client to
* write to the connection even after the server has finished writing. The TCP socket is only closed
* after both the client and server finished writing.
* If set to false, the connection will immediately closed once the server closes its write side,
* independently whether the client is still attempting to write. This setting is recommended
* for servers, and therefore it is the default setting.
*/ */
def bind(interface: String, def bind(interface: String,
port: Int, port: Int,
backlog: Int, backlog: Int,
options: JIterable[SocketOption], options: JIterable[SocketOption],
halfClose: Boolean,
idleTimeout: Duration): Source[IncomingConnection, Future[ServerBinding]] = idleTimeout: Duration): Source[IncomingConnection, Future[ServerBinding]] =
Source.adapt(delegate.bind(interface, port, backlog, immutableSeq(options), idleTimeout) Source.adapt(delegate.bind(interface, port, backlog, immutableSeq(options), halfClose, idleTimeout)
.map(new IncomingConnection(_)) .map(new IncomingConnection(_))
.mapMaterializedValue(_.map(new ServerBinding(_))(ec))) .mapMaterializedValue(_.map(new ServerBinding(_))(ec)))
@ -120,13 +135,27 @@ class Tcp(system: ExtendedActorSystem) extends akka.actor.Extension {
/** /**
* Creates an [[Tcp.OutgoingConnection]] instance representing a prospective TCP client connection to the given endpoint. * Creates an [[Tcp.OutgoingConnection]] instance representing a prospective TCP client connection to the given endpoint.
*
* @param remoteAddress The remote address to connect to
* @param localAddress Optional local address for the connection
* @param options TCP options for the connections, see [[akka.io.Tcp]] for details
* @param halfClose
* Controls whether the connection is kept open even after writing has been completed to the accepted
* TCP connections.
* If set to true, the connection will implement the TCP half-close mechanism, allowing the server to
* write to the connection even after the client has finished writing. The TCP socket is only closed
* after both the client and server finished writing. This setting is recommended for clients and
* therefore it is the default setting.
* If set to false, the connection will immediately closed once the client closes its write side,
* independently whether the server is still attempting to write.
*/ */
def outgoingConnection(remoteAddress: InetSocketAddress, def outgoingConnection(remoteAddress: InetSocketAddress,
localAddress: Option[InetSocketAddress], localAddress: Option[InetSocketAddress],
options: JIterable[SocketOption], options: JIterable[SocketOption],
halfClose: Boolean,
connectTimeout: Duration, connectTimeout: Duration,
idleTimeout: Duration): Flow[ByteString, ByteString, Future[OutgoingConnection]] = idleTimeout: Duration): Flow[ByteString, ByteString, Future[OutgoingConnection]] =
Flow.adapt(delegate.outgoingConnection(remoteAddress, localAddress, immutableSeq(options), connectTimeout, idleTimeout) Flow.adapt(delegate.outgoingConnection(remoteAddress, localAddress, immutableSeq(options), halfClose, connectTimeout, idleTimeout)
.mapMaterializedValue(_.map(new OutgoingConnection(_))(ec))) .mapMaterializedValue(_.map(new OutgoingConnection(_))(ec)))
/** /**

View file

@ -76,6 +76,7 @@ class Tcp(system: ExtendedActorSystem) extends akka.actor.Extension {
val endpoint: InetSocketAddress, val endpoint: InetSocketAddress,
val backlog: Int, val backlog: Int,
val options: immutable.Traversable[SocketOption], val options: immutable.Traversable[SocketOption],
val halfClose: Boolean,
val idleTimeout: Duration = Duration.Inf, val idleTimeout: Duration = Duration.Inf,
val attributes: OperationAttributes, val attributes: OperationAttributes,
_shape: SourceShape[IncomingConnection]) extends SourceModule[IncomingConnection, Future[ServerBinding]](_shape) { _shape: SourceShape[IncomingConnection]) extends SourceModule[IncomingConnection, Future[ServerBinding]](_shape) {
@ -93,6 +94,7 @@ class Tcp(system: ExtendedActorSystem) extends akka.actor.Extension {
s.asInstanceOf[Subscriber[IncomingConnection]], s.asInstanceOf[Subscriber[IncomingConnection]],
endpoint, endpoint,
backlog, backlog,
halfClose,
options, options,
idleTimeout) idleTimeout)
} }
@ -109,41 +111,90 @@ class Tcp(system: ExtendedActorSystem) extends akka.actor.Extension {
} }
override protected def newInstance(s: SourceShape[IncomingConnection]): SourceModule[IncomingConnection, Future[ServerBinding]] = override protected def newInstance(s: SourceShape[IncomingConnection]): SourceModule[IncomingConnection, Future[ServerBinding]] =
new BindSource(endpoint, backlog, options, idleTimeout, attributes, shape) new BindSource(endpoint, backlog, options, halfClose, idleTimeout, attributes, shape)
override def withAttributes(attr: OperationAttributes): Module = override def withAttributes(attr: OperationAttributes): Module =
new BindSource(endpoint, backlog, options, idleTimeout, attr, shape) new BindSource(endpoint, backlog, options, halfClose, idleTimeout, attr, shape)
} }
/** /**
* Creates a [[Tcp.ServerBinding]] instance which represents a prospective TCP server binding on the given `endpoint`. * Creates a [[Tcp.ServerBinding]] instance which represents a prospective TCP server binding on the given `endpoint`.
*
* @param interface The interface to listen on
* @param port The port to listen on
* @param backlog Controls the size of the connection backlog
* @param options TCP options for the connections, see [[akka.io.Tcp]] for details
* @param halfClose
* Controls whether the connection is kept open even after writing has been completed to the accepted
* TCP connections.
* If set to true, the connection will implement the TCP half-close mechanism, allowing the client to
* write to the connection even after the server has finished writing. The TCP socket is only closed
* after both the client and server finished writing.
* If set to false, the connection will immediately closed once the server closes its write side,
* independently whether the client is still attempting to write. This setting is recommended
* for servers, and therefore it is the default setting.
*/ */
def bind(interface: String, def bind(interface: String,
port: Int, port: Int,
backlog: Int = 100, backlog: Int = 100,
options: immutable.Traversable[SocketOption] = Nil, options: immutable.Traversable[SocketOption] = Nil,
halfClose: Boolean = false,
idleTimeout: Duration = Duration.Inf): Source[IncomingConnection, Future[ServerBinding]] = { idleTimeout: Duration = Duration.Inf): Source[IncomingConnection, Future[ServerBinding]] = {
new Source(new BindSource(new InetSocketAddress(interface, port), backlog, options, idleTimeout, new Source(new BindSource(new InetSocketAddress(interface, port), backlog, options, halfClose, idleTimeout,
OperationAttributes.none, SourceShape(new Outlet("BindSource.out")))) OperationAttributes.none, SourceShape(new Outlet("BindSource.out"))))
} }
/**
* Creates a [[Tcp.ServerBinding]] instance which represents a prospective TCP server binding on the given `endpoint`
* handling the incoming connections using the provided Flow.
*
* @param handler A Flow that represents the server logic
* @param interface The interface to listen on
* @param port The port to listen on
* @param backlog Controls the size of the connection backlog
* @param options TCP options for the connections, see [[akka.io.Tcp]] for details
* @param halfClose
* Controls whether the connection is kept open even after writing has been completed to the accepted
* TCP connections.
* If set to true, the connection will implement the TCP half-close mechanism, allowing the client to
* write to the connection even after the server has finished writing. The TCP socket is only closed
* after both the client and server finished writing.
* If set to false, the connection will immediately closed once the server closes its write side,
* independently whether the client is still attempting to write. This setting is recommended
* for servers, and therefore it is the default setting.
*/
def bindAndHandle( def bindAndHandle(
handler: Flow[ByteString, ByteString, _], handler: Flow[ByteString, ByteString, _],
interface: String, interface: String,
port: Int, port: Int,
backlog: Int = 100, backlog: Int = 100,
options: immutable.Traversable[SocketOption] = Nil, options: immutable.Traversable[SocketOption] = Nil,
halfClose: Boolean = false,
idleTimeout: Duration = Duration.Inf)(implicit m: FlowMaterializer): Future[ServerBinding] = { idleTimeout: Duration = Duration.Inf)(implicit m: FlowMaterializer): Future[ServerBinding] = {
bind(interface, port, backlog, options, idleTimeout).to(Sink.foreach { conn: IncomingConnection bind(interface, port, backlog, options, halfClose, idleTimeout).to(Sink.foreach { conn: IncomingConnection
conn.flow.join(handler).run() conn.flow.join(handler).run()
}).run() }).run()
} }
/** /**
* Creates an [[Tcp.OutgoingConnection]] instance representing a prospective TCP client connection to the given endpoint. * Creates an [[Tcp.OutgoingConnection]] instance representing a prospective TCP client connection to the given endpoint.
*
* @param remoteAddress The remote address to connect to
* @param localAddress Optional local address for the connection
* @param options TCP options for the connections, see [[akka.io.Tcp]] for details
* @param halfClose
* Controls whether the connection is kept open even after writing has been completed to the accepted
* TCP connections.
* If set to true, the connection will implement the TCP half-close mechanism, allowing the server to
* write to the connection even after the client has finished writing. The TCP socket is only closed
* after both the client and server finished writing. This setting is recommended for clients and
* therefore it is the default setting.
* If set to false, the connection will immediately closed once the client closes its write side,
* independently whether the server is still attempting to write.
*/ */
def outgoingConnection(remoteAddress: InetSocketAddress, def outgoingConnection(remoteAddress: InetSocketAddress,
localAddress: Option[InetSocketAddress] = None, localAddress: Option[InetSocketAddress] = None,
options: immutable.Traversable[SocketOption] = Nil, options: immutable.Traversable[SocketOption] = Nil,
halfClose: Boolean = true,
connectTimeout: Duration = Duration.Inf, connectTimeout: Duration = Duration.Inf,
idleTimeout: Duration = Duration.Inf): Flow[ByteString, ByteString, Future[OutgoingConnection]] = { idleTimeout: Duration = Duration.Inf): Flow[ByteString, ByteString, Future[OutgoingConnection]] = {
@ -152,7 +203,7 @@ class Tcp(system: ExtendedActorSystem) extends akka.actor.Extension {
Flow[ByteString].andThenMat(() { Flow[ByteString].andThenMat(() {
val processorPromise = Promise[Processor[ByteString, ByteString]]() val processorPromise = Promise[Processor[ByteString, ByteString]]()
val localAddressPromise = Promise[InetSocketAddress]() val localAddressPromise = Promise[InetSocketAddress]()
manager ! StreamTcpManager.Connect(processorPromise, localAddressPromise, remoteAddress, localAddress, options, manager ! StreamTcpManager.Connect(processorPromise, localAddressPromise, remoteAddress, localAddress, halfClose, options,
connectTimeout, idleTimeout) connectTimeout, idleTimeout)
import system.dispatcher import system.dispatcher
val outgoingConnection = localAddressPromise.future.map(OutgoingConnection(remoteAddress, _)) val outgoingConnection = localAddressPromise.future.map(OutgoingConnection(remoteAddress, _))