All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.finagle.http.exp.ServerDispatcher.scala Maven / Gradle / Ivy

package com.twitter.finagle.http.exp

import com.twitter.finagle.Failure
import com.twitter.finagle.context.{Contexts, RemoteInfo}
import com.twitter.finagle.transport.Transport
import com.twitter.finagle.CancelledRequestException
import com.twitter.util._
import java.util.concurrent.atomic.AtomicReference

private[finagle] object GenSerialServerDispatcher {
  private val Eof = Future.exception(Failure("EOF"))
  // We don't use Future.never here, because object equality is important here
  private val Idle = new NoFuture
  private val Closed = new NoFuture
}

/**
 * A generic version of
 * [[com.twitter.finagle.dispatch.SerialServerDispatcher SerialServerDispatcher]],
 * allowing the implementor to furnish custom dispatchers & handlers.
 */
private[finagle] abstract class GenSerialServerDispatcher[Req, Rep, In, Out](trans: StreamTransport[In, Out])
  extends Closable {

  def this(trans: Transport[In, Out]) = this(new IdentityStreamTransport(trans))

  import GenSerialServerDispatcher._

  private[this] val state = new AtomicReference[Future[_]](Idle)
  private[this] val cancelled = new CancelledRequestException

  /**
   * Dispatches a request. The first argument is the request. The second
   * argument `eos` (end-of-stream promise) must be fulfilled when the request
   * is complete.
   *
   * For non-streaming requests, `eos.setDone()` should be called immediately,
   * since the entire request is present. For streaming requests,
   * `eos.setDone()` must be called at the end of stream (in HTTP, this is on
   * receipt of last chunk). Refer to the implementation in
   * [[com.twitter.finagle.http.codec.HttpServerDispatcher]].
   */
  protected def dispatch(req: Out): Future[Rep]
  protected def handle(rep: Rep): Future[Unit]

  private[this] def loop(): Future[Unit] = {
    state.set(Idle)
    trans.read().flatMap { case Multi(req, eos) =>
      val p = new Promise[Rep]
      if (state.compareAndSet(Idle, p)) {
        val save = Local.save()
        try {
          Contexts.local.let(RemoteInfo.Upstream.AddressCtx, trans.remoteAddress) {
            trans.peerCertificate match {
              case None => p.become(dispatch(req))
              case Some(cert) => Contexts.local.let(Transport.peerCertCtx, cert) {
                p.become(dispatch(req))
              }
            }
          }
        } finally Local.restore(save)
        p.map { res => (res, eos) }
      } else Eof
    }.flatMap { case (rep, eos) =>
      Future.join(handle(rep), eos).unit
    }.respond {
      case Return(()) if state.get ne Closed =>
        loop()
      case _ =>
        trans.close()
    }
  }

  // Clear all locals to start the loop; we want a clean slate.
  private[this] val looping = Local.letClear { loop() }

  trans.onClose.ensure {
    looping.raise(cancelled)
    state.getAndSet(Closed).raise(cancelled)
  }

  /** Exposed for testing */
  protected[exp] def isClosing: Boolean = state.get() eq Closed

  // Note: this is racy, but that's inherent in draining (without
  // protocol support). Presumably, half-closing a TCP connection is
  // also possible.
  def close(deadline: Time) = {
    if (state.getAndSet(Closed) eq Idle)
      trans.close(deadline)
    trans.onClose.unit
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy