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

com.twitter.finagle.builder.ClientBuilder.scala Maven / Gradle / Ivy

There is a newer version: 7.0.0
Show newest version
package com.twitter.finagle.builder

import com.twitter.conversions.time._
import com.twitter.finagle._
import com.twitter.finagle.client.Transporter.Credentials
import com.twitter.finagle.client.{DefaultPool, StackClient, StdStackClient}
import com.twitter.finagle.client.{StackBasedClient, Transporter}
import com.twitter.finagle.factory.{BindingFactory, TimeoutFactory}
import com.twitter.finagle.filter.ExceptionSourceFilter
import com.twitter.finagle.loadbalancer.LoadBalancerFactory
import com.twitter.finagle.netty3.Netty3Transporter
import com.twitter.finagle.service.FailFastFactory.FailFast
import com.twitter.finagle.service._
import com.twitter.finagle.ssl.Ssl
import com.twitter.finagle.stats.{NullStatsReceiver, StatsReceiver}
import com.twitter.finagle.tracing.{NullTracer, TraceInitializerFilter}
import com.twitter.finagle.transport.Transport
import com.twitter.finagle.util._
import com.twitter.util
import com.twitter.util.{Duration, Future, NullMonitor, Time, Try}
import java.net.{InetSocketAddress, SocketAddress}
import java.util.concurrent.atomic.AtomicBoolean
import java.util.logging.Level
import javax.net.ssl.SSLContext
import org.jboss.netty.channel.{Channel, ChannelFactory}
import scala.annotation.{implicitNotFound, varargs}

/**
 * Factory for [[com.twitter.finagle.builder.ClientBuilder]] instances
 */
object ClientBuilder {
  type Complete[Req, Rep] =
    ClientBuilder[Req, Rep, ClientConfig.Yes, ClientConfig.Yes, ClientConfig.Yes]
  type NoCluster[Req, Rep] =
    ClientBuilder[Req, Rep, Nothing, ClientConfig.Yes, ClientConfig.Yes]
  type NoCodec =
    ClientBuilder[_, _, ClientConfig.Yes, Nothing, ClientConfig.Yes]

  def apply(): ClientBuilder[Nothing, Nothing, Nothing, Nothing, Nothing] =
    new ClientBuilder()

  /**
   * Used for Java access.
   */
  def get(): ClientBuilder[Nothing, Nothing, Nothing, Nothing, Nothing] =
    apply()

  /**
   * Provides a typesafe `build` for Java.
   */
  def safeBuild[Req, Rep](builder: Complete[Req, Rep]): Service[Req, Rep] =
    builder.build()(ClientConfigEvidence.FullyConfigured)

  /**
   * Provides a typesafe `buildFactory` for Java.
   */
  def safeBuildFactory[Req, Rep](builder: Complete[Req, Rep]): ServiceFactory[Req, Rep] =
    builder.buildFactory()(ClientConfigEvidence.FullyConfigured)

  /**
   * Returns a [[com.twitter.finagle.client.StackClient]] which is equivalent to a
   * `ClientBuilder` configured with the same codec; that is, given
   * {{{
   *   val cb = ClientBuilder()
   *     .dest(dest)
   *     .name(name)
   *     .codec(codec)
   *
   *   val sc = ClientBuilder.stackClientOfCodec(codec)
   * }}}
   * then the following are equivalent
   * {{{
   *   cb.build()
   *   sc.newService(dest, name)
   * }}}
   * and the following are also equivalent
   * {{{
   *   cb.buildFactory()
   *   sc.newClient(dest, name)
   * }}}
   */
  def stackClientOfCodec[Req, Rep](
    codecFactory: CodecFactory[Req, Rep]#Client
  ): StackClient[Req, Rep] =
    ClientBuilderClient(CodecClient[Req, Rep](codecFactory))
}

object ClientConfig {
  sealed trait Yes
  type FullySpecified[Req, Rep] = ClientConfig[Req, Rep, Yes, Yes, Yes]
  val DefaultName = "client"

  private case class NilClient[Req, Rep](
      stack: Stack[ServiceFactory[Req, Rep]] = StackClient.newStack[Req, Rep],
      params: Stack.Params = DefaultParams)
    extends StackBasedClient[Req, Rep] {

    def withParams(ps: Stack.Params): StackBasedClient[Req, Rep] = copy(params = ps)
    def transformed(t: Stack.Transformer): StackBasedClient[Req, Rep] = copy(stack = t(stack))

    def newService(dest: Name, label: String): Service[Req, Rep] =
      newClient(dest, label).toService

    def newClient(dest: Name, label: String): ServiceFactory[Req, Rep] =
      ServiceFactory(() => Future.value(Service.mk[Req, Rep](_ => Future.exception(
        new Exception("unimplemented")))))
  }

  def nilClient[Req, Rep]: StackBasedClient[Req, Rep] = NilClient[Req, Rep]()

  // params specific to ClientBuilder
  private[builder] case class DestName(name: Name) {
    def mk(): (DestName, Stack.Param[DestName]) =
      (this, DestName.param)
  }
  private[builder] object DestName {
    implicit val param = Stack.Param(DestName(Name.empty))
  }

  private[builder] case class GlobalTimeout(timeout: Duration) {
    def mk(): (GlobalTimeout, Stack.Param[GlobalTimeout]) =
      (this, GlobalTimeout.param)
  }
  private[builder] object GlobalTimeout {
    implicit val param = Stack.Param(GlobalTimeout(Duration.Top))
  }

  private[builder] case class Daemonize(onOrOff: Boolean) {
    def mk(): (Daemonize, Stack.Param[Daemonize]) =
      (this, Daemonize.param)
  }
  private[builder] object Daemonize {
    implicit val param = Stack.Param(Daemonize(true))
  }

  private[builder] case class MonitorFactory(mFactory: String => util.Monitor) {
    def mk(): (MonitorFactory, Stack.Param[MonitorFactory]) =
      (this, MonitorFactory.param)
  }
  private[builder] object MonitorFactory {
    implicit val param = Stack.Param(MonitorFactory(_ => NullMonitor))
  }

  // historical defaults for ClientBuilder
  private[builder] val DefaultParams = Stack.Params.empty +
    param.Stats(NullStatsReceiver) +
    param.Label(DefaultName) +
    DefaultPool.Param(low = 1, high = Int.MaxValue,
    bufferSize = 0, idleTime = 5.seconds, maxWaiters = Int.MaxValue) +
    param.Tracer(NullTracer) +
    param.Monitor(NullMonitor) +
    param.Reporter(NullReporterFactory) +
    Daemonize(false)
}

@implicitNotFound("Builder is not fully configured: Cluster: ${HasCluster}, Codec: ${HasCodec}, HostConnectionLimit: ${HasHostConnectionLimit}")
trait ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]

private[builder] object ClientConfigEvidence {
  implicit object FullyConfigured extends ClientConfigEvidence[ClientConfig.Yes, ClientConfig.Yes, ClientConfig.Yes]
}

/**
 * TODO: do we really need to specify HasCodec? -- it's implied in a
 * way by the proper Req, Rep.
 *
 * Note: these are documented in ClientBuilder, as that is where they
 * are accessed by the end-user.
 */
private[builder] final class ClientConfig[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit]

/**
 * A builder of Finagle [[com.twitter.finagle.Client Clients]].
 *
 * Please see the
 * [[http://twitter.github.io/finagle/guide/Configuration.html Finagle user guide]]
 * for information on the preferred `with`-style client-construction APIs.
 *
 * {{{
 * val client = ClientBuilder()
 *   .codec(Http)
 *   .hosts("localhost:10000,localhost:10001,localhost:10003")
 *   .hostConnectionLimit(1)
 *   .tcpConnectTimeout(1.second)        // max time to spend establishing a TCP connection.
 *   .retries(2)                         // (1) per-request retries
 *   .reportTo(DefaultStatsReceiver)     // export host-level load data to the loaded-StatsReceiver
 *   .build()
 * }}}
 *
 * The `ClientBuilder` requires the definition of `cluster`, `codec`,
 * and `hostConnectionLimit`. In Scala, these are statically type
 * checked, and in Java the lack of any of the above causes a runtime
 * error.
 *
 * The `build` method uses an implicit argument to statically
 * typecheck the builder (to ensure completeness, see above). The Java
 * compiler cannot provide such implicit, so we provide a separate
 * function in Java to accomplish this. Thus, the Java code for the
 * above is
 *
 * {{{
 * Service service =
 *  ClientBuilder.safeBuild(
 *    ClientBuilder.get()
 *      .codec(new Http())
 *      .hosts("localhost:10000,localhost:10001,localhost:10003")
 *      .hostConnectionLimit(1)
 *      .tcpConnectTimeout(1.second)
 *      .retries(2)
 *      .reportTo(DefaultStatsReceiver)
 * }}}
 *
 * Alternatively, using the `unsafeBuild` method on `ClientBuilder`
 * verifies the builder dynamically, resulting in a runtime error
 * instead of a compiler error.
 *
 * =Defaults=
 *
 * The following defaults are applied to clients constructed via ClientBuilder,
 * unless overridden with the corresponding method. These defaults were chosen
 * carefully so as to work well for most use cases.
 *
 * Commonly-configured options:
 *
 *  - `connectTimeout`: [[com.twitter.util.Duration.Top Duration.Top]]
 *  - `tcpConnectTimeout`: 1 second
 *  - `requestTimeout`: [[com.twitter.util.Duration.Top Duration.Top]]
 *  - `timeout`: [[com.twitter.util.Duration.Top Duration.Top]]
 *  - `hostConnectionLimit`: `Int.MaxValue`
 *  - `hostConnectionCoresize`: 0
 *  - `hostConnectionIdleTime`: [[com.twitter.util.Duration.Top Duration.Top]]
 *  - `hostConnectionMaxWaiters`: `Int.MaxValue`
 *  - `failFast`: true
 *  - `failureAccrualParams`, `failureAccrualFactory`:
 *    `numFailures` = 5, `markDeadFor` = 5 seconds
 *
 * Advanced options:
 *
 * ''Before changing any of these, make sure that you know exactly how they will
 * affect your application -- these options are typically only changed by expert
 * users.''
 *
 *  - `keepAlive`: Unspecified, in which case the
 *    [[http://docs.oracle.com/javase/7/docs/api/java/net/StandardSocketOptions.html?is-external=true#SO_KEEPALIVE Java default]]
 *    of `false` is used
 *  - `hostConnectionMaxIdleTime`: [[com.twitter.util.Duration.Top Duration.Top]]
 *  - `hostConnectionMaxLifeTime`: [[com.twitter.util.Duration.Top Duration.Top]]
 *
 * @see The [[http://twitter.github.io/finagle/guide/Configuration.html user guide]]
 *      for information on the preferred `with`-style APIs insead.
 */
class ClientBuilder[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit] private[finagle](
    client: StackBasedClient[Req, Rep]) {
  import ClientConfig._
  import com.twitter.finagle.param._

  // Convenient aliases.
  type FullySpecifiedConfig = FullySpecified[Req, Rep]
  type ThisConfig           = ClientConfig[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit]
  type This                 = ClientBuilder[Req, Rep, HasCluster, HasCodec, HasHostConnectionLimit]

  private[builder] def this() = this(ClientConfig.nilClient)

  override def toString: String = "ClientBuilder(%s)".format(params)

  private def copy[Req1, Rep1, HasCluster1, HasCodec1, HasHostConnectionLimit1](
    client: StackBasedClient[Req1, Rep1]
  ): ClientBuilder[Req1, Rep1, HasCluster1, HasCodec1, HasHostConnectionLimit1] =
    new ClientBuilder(client)

  private def _configured[P, HasCluster1, HasCodec1, HasHostConnectionLimit1](
    param: P
  )(
    implicit stackParam: Stack.Param[P]
  ): ClientBuilder[Req, Rep, HasCluster1, HasCodec1, HasHostConnectionLimit1] =
    copy(client.configured(param))

  /**
   * Configure the underlying [[Stack.Param Params]].
   *
   * Java users may find it easier to use the `Tuple2` version below.
   */
  def configured[P](param: P)(implicit stackParam: Stack.Param[P]): This =
    copy(client.configured(param))

  /**
   * Java friendly API for configuring the underlying [[Stack.Param Params]].
   *
   * The `Tuple2` can often be created by calls to a `mk(): (P, Stack.Param[P])`
   * method on parameters (see
   * [[com.twitter.finagle.loadbalancer.LoadBalancerFactory.Param.mk()]]
   * as an example).
   */
  def configured[P](paramAndStackParam: (P, Stack.Param[P])): This =
    copy(client.configured(paramAndStackParam._1)(paramAndStackParam._2))

  /**
   * The underlying [[Stack.Param Params]] used for configuration.
   */
  def params: Stack.Params = client.params

  // Used in deprecated KetamaClientBuilder, remove when we drop it in
  // favor of the finagle.Memcached protocol object.
  private[finagle] def underlying: StackBasedClient[Req, Rep] = client

  /**
   * Specify the set of hosts to connect this client to.  Requests
   * will be load balanced across these.  This is a shorthand form for
   * specifying a cluster.
   *
   * One of the `hosts` variations or direct specification of the
   * cluster (via `cluster`) is required.
   *
   * To migrate to the Stack-based APIs, pass the hostname and port
   * pairs into `com.twitter.finagle.Client.newService(String)`. For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.newService("hostnameA:portA,hostnameB:portB")
   * }}}
   *
   * @param hostnamePortCombinations comma-separated "host:port"
   * string.
   */
  def hosts(
    hostnamePortCombinations: String
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] = {
    val addresses = InetSocketAddressUtil.parseHosts(hostnamePortCombinations)
    hosts(addresses)
  }

  /**
   * A variant of `hosts` that takes a sequence of
   * [[java.net.InetSocketAddress]] instead.
   *
   * To migrate to the Stack-based APIs,
   * use `com.twitter.finagle.Client.newService(Name, String)`.
   * For the label String, use the scope you want for your [[StatsReceiver]].
   * For example:
   * {{{
   * import com.twitter.finagle.{Address, Http, Name}
   *
   * val addresses: Seq[Address] = sockaddrs.map(Address(_))
   * val name: Name = Name.bound(addresses: _*)
   * Http.client.newService(name, "the_client_name")
   * }}}
   */
  def hosts(
    sockaddrs: Seq[InetSocketAddress]
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
    addrs(sockaddrs.map(Address(_)): _*)

  /**
   * A convenience method for specifying a one-host
   * [[java.net.SocketAddress]] client.
   *
   * To migrate to the Stack-based APIs,
   * use `com.twitter.finagle.Client.newService(Name, String)`.
   * For the label String, use the scope you want for your [[StatsReceiver]].
   * For example:
   * {{{
   * import com.twitter.finagle.{Address, Http, Name}
   *
   * val name: Name = Name.bound(Address(address))
   * Http.client.newService(name, "the_client_name")
   * }}}
   */
  def hosts(
    address: InetSocketAddress
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
    hosts(Seq(address))

  /**
   * A convenience method for specifying a client with one or more
   * [[com.twitter.finagle.Address]]s.
   *
   * To migrate to the Stack-based APIs,
   * use `com.twitter.finagle.Client.newService(Name, String)`.
   * For the label String, use the scope you want for your [[StatsReceiver]].
   * For example:
   * {{{
   * import com.twitter.finagle.{Http, Name}
   *
   * val name: Name = Name.bound(addrs: _*)
   * Http.client.newService(name, "the_client_name")
   * }}}
   */
  @varargs
  def addrs(
    addrs: Address*
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
    dest(Name.bound(addrs:_*))

  /**
   * The logical destination of requests dispatched through this
   * client, as evaluated by a resolver. If the name evaluates a
   * label, this replaces the builder's current name.
   *
   * To migrating to the Stack-based APIs, you pass the destination
   * to `newClient` or `newService`. If the `addr` is labeled,
   * additionally, use `CommonParams.withLabel`
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client
   *   .withLabel("client_name")
   *   .newService(name)
   * }}}
   */
  def dest(
    addr: String
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] = {
    Resolver.evalLabeled(addr) match {
      case (n, "") => dest(n)
      case (n, l) =>
        val Label(label) = params[Label]
        val cb =
          if (label.isEmpty || l != addr)
            this.name(l)
          else
            this

        cb.dest(n)
    }
  }

  /**
   * The logical destination of requests dispatched through this
   * client.
   *
   * To migrate to the Stack-based APIs, use this in the call to `newClient`
   * or `newService`. For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.newService(name)
   * }}}
   */
  def dest(
    name: Name
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
    _configured(DestName(name))

  /**
   * The base [[com.twitter.finagle.Dtab]] used to interpret logical
   * destinations for this client. (This is given as a function to
   * permit late initialization of [[com.twitter.finagle.Dtab.base]].)
   *
   * To migrate to the Stack-based APIs, use `configured`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   * import com.twitter.finagle.factory.BindingFactory
   *
   * Http.client.configured(BindingFactory.BaseDtab(baseDtab))
   * }}}
   */
  def baseDtab(baseDtab: () => Dtab): This =
    configured(BindingFactory.BaseDtab(baseDtab))

  /**
   * Specify a cluster directly.  A
   * [[com.twitter.finagle.builder.Cluster]] defines a dynamic
   * mechanism for specifying a set of endpoints to which this client
   * remains connected.
   */
  def cluster(
    cluster: Cluster[SocketAddress]
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
    group(Group.fromCluster(cluster))

  def group(
    group: Group[SocketAddress]
  ): ClientBuilder[Req, Rep, Yes, HasCodec, HasHostConnectionLimit] =
    dest(Name.fromGroup(group))

  /**
   * Specify a load balancer.  The load balancer implements
   * a strategy for choosing one host from a set to service a request.
   *
   * To migrate to the Stack-based APIs, use `withLoadBalancer(LoadBalancerFactory)`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withLoadBalancer(loadBalancer)
   * }}}
   */
  def loadBalancer(loadBalancer: LoadBalancerFactory): This =
    configured(LoadBalancerFactory.Param(loadBalancer))

  /**
   * Specify the codec. The codec implements the network protocol
   * used by the client, and consequently determines the `Req` and `Rep`
   * type variables. One of the codec variations is required.
   *
   * To migrate to the Stack-based APIs, use `ClientBuilder.stack(Protocol.client)`
   * instead. For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * ClientBuilder().stack(Http.client)
   * }}}
   */
  def codec[Req1, Rep1](
    codec: Codec[Req1, Rep1]
  ): ClientBuilder[Req1, Rep1, HasCluster, Yes, HasHostConnectionLimit] =
    this.codec(Function.const(codec)(_))

  /**
   * A variation of `codec` that supports codec factories.  This is
   * used by codecs that need dynamic construction, but should be
   * transparent to the user.
   *
   * To migrate to the Stack-based APIs, use `ClientBuilder.stack(Protocol.client)`
   * instead. For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * ClientBuilder().stack(Http.client)
   * }}}
   */
  def codec[Req1, Rep1](
    codecFactory: CodecFactory[Req1, Rep1]
  ): ClientBuilder[Req1, Rep1, HasCluster, Yes, HasHostConnectionLimit] =
    this.codec(codecFactory.client)

  /**
   * A variation of codec for codecs that support only client-codecs.
   *
   * To migrate to the Stack-based APIs, use `ClientBuilder.stack(Protocol.client)`
   * instead. For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * ClientBuilder().stack(Http.client)
   * }}}
   */
  def codec[Req1, Rep1](
    codecFactory: CodecFactory[Req1, Rep1]#Client
  ): ClientBuilder[Req1, Rep1, HasCluster, Yes, HasHostConnectionLimit] = {
    // in order to know the protocol library name, we need to produce
    // a throw-away codec. given that the codec API is on its way out
    // in favor of Stack, this is a reasonable compromise.
    val codec = codecFactory(ClientCodecConfig("ClientBuilder protocolLibraryName"))
    copy(CodecClient[Req1, Rep1](codecFactory).withParams(params))
      .configured(ProtocolLibrary(codec.protocolLibraryName))
  }

  /**
   * Overrides the stack and [[com.twitter.finagle.Client]] that will be used
   * by this builder.
   *
   * @param client A `StackBasedClient` representation of a
   * [[com.twitter.finagle.Client]]. `client` is materialized with the state of
   * configuration when `build` is called. There is no guarantee that all
   * builder parameters will be used by the resultant `Client`; it is up to the
   * discretion of `client` itself and the protocol implementation. For example,
   * the Mux protocol has no use for most connection pool parameters (e.g.
   * `hostConnectionLimit`). Thus when configuring
   * `com.twitter.finagle.ThriftMux` clients (via `stack(ThriftMux.client)`),
   * such connection pool parameters will not be applied.
   */
  def stack[Req1, Rep1](
    client: StackBasedClient[Req1, Rep1]
  ): ClientBuilder[Req1, Rep1, HasCluster, Yes, Yes] = {
    copy(client.withParams(client.params ++ params))
  }

  /**
   * Specify the TCP connection timeout.
   *
   * To migrate to the Stack-based APIs, use `ClientTransportParams.connectTimeout`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.connectTimeout(duration)
   * }}}
   */
  def tcpConnectTimeout(duration: Duration): This =
    configured(Transporter.ConnectTimeout(duration))

  /**
   * The request timeout is the time given to a *single* request (if
   * there are retries, they each get a fresh request timeout).  The
   * timeout is applied only after a connection has been acquired.
   * That is: it is applied to the interval between the dispatch of
   * the request and the receipt of the response.
   *
   * To migrate to the Stack-based APIs, use `CommonParams.withRequestTimeout`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withRequestTimeout(duration)
   * }}}
   *
   * @note if the request is not complete after `duration` the work that is
   *       in progress will be interrupted via [[Future.raise]].
   *
   * @see [[timeout(Duration)]]
   */
  def requestTimeout(duration: Duration): This =
    configured(TimeoutFilter.Param(duration))

  /**
   * The connect timeout is the timeout applied to the acquisition of
   * a Service.  This includes both queueing time (eg.  because we
   * cannot create more connections due to `hostConnectionLimit` and
   * there are more than `hostConnectionLimit` requests outstanding)
   * as well as physical connection time.  Futures returned from
   * `factory()` will always be satisfied within this timeout.
   *
   * This timeout is also used for name resolution, separately from
   * queueing and physical connection time, so in the worst case the
   * time to acquire a service may be double the given duration before
   * timing out.
   *
   * To migrate to the Stack-based APIs, use `SessionParams.acquisitionTimeout`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSession.acquisitionTimeout(duration)
   * }}}
   */
  def connectTimeout(duration: Duration): This =
    configured(TimeoutFactory.Param(duration))

  /**
   * Total request timeout.  This timeout is applied from the issuance
   * of a request (through `service(request)`) until the
   * satisfaction of that reply future.  No request will take longer
   * than this.
   *
   * Applicable only to service-builds (`build()`)
   *
   * To migrate to the Stack-based APIs, use this method in conjunction with
   * `ClientBuilder.stack`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * ClientBuilder()
   *   .stack(Http.client)
   *   .timeout(duration)
   * }}}
   *
   * @note if the request is not complete after `duration` the work that is
   *       in progress will be interrupted via [[Future.raise]].
   *
   * @see [[requestTimeout(Duration)]]
   */
  def timeout(duration: Duration): This =
    configured(GlobalTimeout(duration))

  /**
   * Apply TCP keepAlive (`SO_KEEPALIVE` socket option).
   *
   * To migrate to the Stack-based APIs, use `configured`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   * import com.twitter.finagle.transport.Transport.Liveness
   *
   * val client = Http.client
   * client.configured(client.params[Transport.Liveness].copy(keepAlive = Some(value)))
   * }}}
   */
  def keepAlive(value: Boolean): This =
    configured(params[Transport.Liveness].copy(keepAlive = Some(value)))

  /**
   * The maximum time a connection may have received no data.
   *
   * To migrate to the Stack-based APIs, use `TransportParams.readTimeout`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.readTimeout(duration)
   * }}}
   */
  @deprecated(
    "Use `configured` or the Stack-based API `TransportParams.readTimeout`",
    "2016-10-05")
  def readerIdleTimeout(duration: Duration): This =
    configured(params[Transport.Liveness].copy(readTimeout = duration))

  /**
   * The maximum time a connection may not have sent any data.
   *
   * To migrate to the Stack-based APIs, use `TransportParams.writeTimeout`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.writeTimeout(duration)
   * }}}
   */
  @deprecated(
    "Use `configured` or the Stack-based API `TransportParams.writeTimeout`",
    "2016-10-05")
  def writerIdleTimeout(duration: Duration): This =
    configured(params[Transport.Liveness].copy(writeTimeout = duration))

  /**
   * Report stats to the given `StatsReceiver`.  This will report
   * verbose global statistics and counters, that in turn may be
   * exported to monitoring applications.
   *
   * To migrate to the Stack-based APIs, use `CommonParams.withStatsReceiver`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withStatsReceiver(receiver)
   * }}}
   *
   * @note Per hosts statistics will '''NOT''' be exported to this receiver
   *
   * @see [[ClientBuilder.reportHostStats]]
   */
  def reportTo(receiver: StatsReceiver): This =
    configured(Stats(receiver))

  /**
   * Report per host stats to the given `StatsReceiver`.
   * The statsReceiver will be scoped per client, like this:
   * client/connect_latency_ms_max/0.0.0.0:64754
   *
   * To migrate to the Stack-based APIs, use `configured`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   * import com.twitter.finagle.loadbalancer.LoadBalancerFactory
   *
   * Http.client.configured(LoadBalancerFactory.HostStats(receiver))
   * }}}
   */
  def reportHostStats(receiver: StatsReceiver): This =
    configured(LoadBalancerFactory.HostStats(receiver))

  /**
   * Give a meaningful name to the client. Required.
   *
   * To migrate to the Stack-based APIs, use `CommonParams.withLabel`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withLabel("my_cool_client")
   * }}}
   */
  def name(value: String): This =
    configured(Label(value))

  /**
   * The maximum number of connections that are allowed per host.
   * Required.  Finagle guarantees to never have more active
   * connections than this limit.
   *
   * To migrate to the Stack-based APIs, use `SessionPoolingParams.maxSize`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSessionPool.maxSize(value)
   * }}}
   *
   * @note not all protocol implementations support this style of connection
   *       pooling, such as `com.twitter.finagle.ThriftMux` and
   *       `com.twitter.finagle.Memcached`.
   */
  def hostConnectionLimit(value: Int): ClientBuilder[Req, Rep, HasCluster, HasCodec, Yes] =
    _configured(params[DefaultPool.Param].copy(high = value))

  /**
   * The core size of the connection pool: the pool is not shrinked below this limit.
   *
   * To migrate to the Stack-based APIs, use `SessionPoolingParams.minSize`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSessionPool.minSize(value)
   * }}}
   *
   * @note not all protocol implementations support this style of connection
   *       pooling, such as `com.twitter.finagle.ThriftMux` and
   *       `com.twitter.finagle.Memcached`.
   */
  def hostConnectionCoresize(value: Int): This =
    configured(params[DefaultPool.Param].copy(low = value))

  /**
   * The amount of time a connection is allowed to linger (when it
   * otherwise would have been closed by the pool) before being
   * closed.
   *
   * @note not all protocol implementations support this style of connection
   *       pooling, such as `com.twitter.finagle.ThriftMux` and
   *       `com.twitter.finagle.Memcached`.
   */
  def hostConnectionIdleTime(timeout: Duration): This =
    configured(params[DefaultPool.Param].copy(idleTime = timeout))

  /**
   * The maximum queue size for the connection pool.
   *
   * To migrate to the Stack-based APIs, use `SessionPoolingParams.maxWaiters`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSessionPool.maxWaiters(nWaiters)
   * }}}
   *
   * @note not all protocol implementations support this style of connection
   *       pooling, such as `com.twitter.finagle.ThriftMux` and
   *       `com.twitter.finagle.Memcached`.
   */
  def hostConnectionMaxWaiters(nWaiters: Int): This =
    configured(params[DefaultPool.Param].copy(maxWaiters = nWaiters))

  /**
   * The maximum time a connection is allowed to linger unused.
   *
   * To migrate to the Stack-based APIs, use `SessionParams.maxIdleTime`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSession.maxIdleTime(timeout)
   * }}}
   */
  def hostConnectionMaxIdleTime(timeout: Duration): This =
    configured(params[ExpiringService.Param].copy(idleTime = timeout))

  /**
   * The maximum time a connection is allowed to exist, regardless of occupancy.
   *
   * To migrate to the Stack-based APIs, use `SessionParams.maxLifeTime`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSession.maxLifeTime(timeout)
   * }}}
   */
  def hostConnectionMaxLifeTime(timeout: Duration): This =
    configured(params[ExpiringService.Param].copy(lifeTime = timeout))

  /**
   * Experimental option to buffer `size` connections from the pool.
   * The buffer is fast and lock-free, reducing contention for
   * services with very high requests rates. The buffer size should
   * be sized roughly to the expected concurrency. Buffers sized by
   * power-of-twos may be faster due to the use of modular
   * arithmetic.
   *
   * @note This will be integrated into the mainline pool, at
   * which time the experimental option will go away.
   *
   * @note not all protocol implementations support this style of connection
   *       pooling, such as `com.twitter.finagle.ThriftMux` and
   *       `com.twitter.finagle.Memcached`.
   */
  @deprecated("Use `configured`", "2016-10-05")
  def expHostConnectionBufferSize(size: Int): This =
    configured(params[DefaultPool.Param].copy(bufferSize = size))

  /**
   * Configure a [[com.twitter.finagle.service.ResponseClassifier]]
   * which is used to determine the result of a request/response.
   *
   * This allows developers to give Finagle the additional application-specific
   * knowledge necessary in order to properly classify them. Without this,
   * Finagle cannot make judgements about application level failures as it only
   * has a narrow understanding of failures (for example: transport level, timeouts,
   * and nacks).
   *
   * As an example take an HTTP client that receives a response with a 500 status
   * code back from a server. To Finagle this is a successful request/response
   * based solely on the transport level. The application developer may want to
   * treat all 500 status codes as failures and can do so via a
   * [[com.twitter.finagle.service.ResponseClassifier]].
   *
   * It is a [[PartialFunction]] and as such multiple classifiers can be composed
   * together via [[PartialFunction.orElse]].
   *
   * Response classification is independently configured on the client and server.
   * For server-side response classification using [[com.twitter.finagle.builder.ServerBuilder]],
   * see [[com.twitter.finagle.builder.ServerBuilder.responseClassifier]]
   *
   * To migrate to the Stack-based APIs, use `CommonParams.withResponseClassifier`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withResponseClassifier(classifier)
   * }}}
   *
   * @see `com.twitter.finagle.http.service.HttpResponseClassifier` for some
   * HTTP classification tools.
   *
   * @note If unspecified, the default classifier is
   * [[com.twitter.finagle.service.ResponseClassifier.Default]]
   * which is a total function fully covering the input domain.
   */
  def responseClassifier(classifier: com.twitter.finagle.service.ResponseClassifier): This =
    configured(param.ResponseClassifier(classifier))

  /**
   * The currently configured [[com.twitter.finagle.service.ResponseClassifier]].
   *
   * @note If unspecified, the default classifier is
   * [[com.twitter.finagle.service.ResponseClassifier.Default]].
   */
  def responseClassifier: com.twitter.finagle.service.ResponseClassifier =
    params[param.ResponseClassifier].responseClassifier

  /**
   * Retry (some) failed requests up to `value - 1` times.
   *
   * Retries are only done if the request failed with something
   * known to be safe to retry. This includes [[WriteException WriteExceptions]]
   * and [[Failure]]s that are marked [[Failure.Restartable restartable]].
   *
   * The configured policy has jittered backoffs between retries.
   *
   * To migrate to the Stack-based APIs, use this method in conjunction with
   * `ClientBuilder.stack`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * ClientBuilder()
   *   .stack(Http.client)
   *   .retries(value)
   * }}}
   *
   * @param value the maximum number of attempts (including retries) that
   *              can be made.
   *               - A value of `1` means one attempt and no retries
   *              on failure.
   *               - A value of `2` means one attempt and then a
   *              single retry if the failure is known to be safe to retry.
   *
   * @note The failures seen in the client will '''not include'''
   *       application level failures. This is particularly important for
   *       codecs that include exceptions, such as `Thrift`.
   *
   *       This is only applicable to service-builds (`build()`).
   *
   * @see [[com.twitter.finagle.service.RetryPolicy.tries]]
   *
   * @see [[retryBudget]] for governing how many failed requests are
   * eligible for retries.
   */
  def retries(value: Int): This =
    retryPolicy(RetryPolicy.tries(value))

  /**
   * Retry failed requests according to the given [[RetryPolicy]].
   *
   * To migrate to the Stack-based APIs, use this method in conjunction with
   * `ClientBuilder.stack`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * ClientBuilder()
   *   .stack(Http.client)
   *   .retryPolicy(value)
   * }}}
   *
   * @note The failures seen in the client will '''not include'''
   *       application level failures. This is particularly important for
   *       codecs that include exceptions, such as `Thrift`.
   *
   *       This is only applicable to service-builds (`build()`).
   *
   * @see [[retryBudget]] for governing how many failed requests are
   * eligible for retries.
   */
  def retryPolicy(value: RetryPolicy[Try[Nothing]]): This =
    configured(Retries.Policy(value))

  /**
   * The [[RetryBudget budget]] is shared across requests and governs
   * the number of retries that can be made.
   *
   * Helps prevent clients from overwhelming the downstream service.
   *
   * To migrate to the Stack-based APIs, use `ClientParams.withRetryBudget`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withRetryBudget(budget)
   * }}}
   *
   * @see [[retryPolicy]] for per-request rules on which failures are
   * eligible for retries.
   */
  def retryBudget(budget: RetryBudget): This =
    configured(Retries.Budget(budget))

  /**
   * The [[RetryBudget budget]] is shared across requests and governs
   * the number of retries that can be made. When used for requeues,
   * includes a stream of delays used to delay each retry.
   *
   * Helps prevent clients from overwhelming the downstream service.
   *
   * To migrate to the Stack-based APIs, use `ClientParams.withRetryBudget`
   * and `ClientParams.withRetryBackoff`
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client
   *   .withRetryBudget(budget)
   *   .withRetryBackoff(backoffSchedule)
   * }}}
   *
   * @see [[retryPolicy]] for per-request rules on which failures are
   * eligible for retries.
   */
  def retryBudget(budget: RetryBudget, backoffSchedule: Stream[Duration]): This =
    configured(Retries.Budget(budget, backoffSchedule))

  /**
   * Sets the TCP send buffer size.
   *
   * To migrate to the Stack-based APIs, use `TransportParams.sendBufferSize`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.sendBufferSize(value)
   * }}}
   */
  @deprecated(
    "Use `configured` or the Stack-based API `TransportParams.sendBufferSize`",
    "2016-10-05")
  def sendBufferSize(value: Int): This =
    configured(params[Transport.BufferSizes].copy(send = Some(value)))

  /**
   * Sets the TCP recv buffer size.
   *
   * To migrate to the Stack-based APIs, use `TransportParams.receiveBufferSize`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.receiveBufferSize(value)
   * }}}
   */
  @deprecated(
    "Use `configured` or the Stack-based API `TransportParams.receiveBufferSize`",
    "2016-10-05")
  def recvBufferSize(value: Int): This =
    configured(params[Transport.BufferSizes].copy(recv = Some(value)))

  /**
   * Use the given channel factory instead of the default. Note that
   * when using a non-default ChannelFactory, finagle can't
   * meaningfully reference count factory usage, and so the caller is
   * responsible for calling `releaseExternalResources()`.
   */
  @deprecated("Use `configured`", "2016-10-05")
  def channelFactory(cf: ChannelFactory): This =
    configured(Netty3Transporter.ChannelFactory(cf))

  /**
   * Encrypt the connection with SSL.  Hostname verification will be
   * provided against the given hostname.
   *
   * To migrate to the Stack-based APIs, use `ClientTransportParams.tls`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.tls(hostname)
   * }}}
   */
  def tls(hostname: String): This = {
    configured(Transport.TLSClientEngine(Some {
      case inet: InetSocketAddress => Ssl.client(hostname, inet.getPort)
      case _ => Ssl.client()
    }))
      .configured(Transporter.TLSHostname(Some(hostname)))
  }

  /**
   * Encrypt the connection with SSL.  The Engine to use can be passed into the client.
   * This allows the user to use client certificates
   * No SSL Hostname Validation is performed
   *
   * To migrate to the Stack-based APIs, use `ClientTransportParams.tls`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.tls(sslContext)
   * }}}
   */
  def tls(sslContext: SSLContext): This =
    configured(Transport.TLSClientEngine(Some {
      case inet: InetSocketAddress => Ssl.client(sslContext, inet.getHostName, inet.getPort)
      case _ => Ssl.client(sslContext)
    }))

  /**
   * Encrypt the connection with SSL.  The Engine to use can be passed into the client.
   * This allows the user to use client certificates
   * SSL Hostname Validation is performed, on the passed in hostname
   */
  def tls(sslContext: SSLContext, hostname: Option[String]): This =
    configured(Transport.TLSClientEngine(Some {
      case inet: InetSocketAddress => Ssl.client(sslContext, hostname.getOrElse(inet.getHostName), inet.getPort)
      case _ => Ssl.client(sslContext)
    }))
      .configured(Transporter.TLSHostname(hostname))

  /**
   * Do not perform TLS validation. Probably dangerous.
   *
   * To migrate to the Stack-based APIs, use `ClientTransportParams.tlsWithoutValidation`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.tlsWithoutValidation
   * }}}
   */
  def tlsWithoutValidation(): This =
    configured(Transport.TLSClientEngine(Some({
      case inet: InetSocketAddress => Ssl.clientWithoutCertificateValidation(inet.getHostName, inet.getPort)
      case _ => Ssl.clientWithoutCertificateValidation()
    })))

  /**
   * Make connections via the given HTTP proxy.
   * If this is defined concurrently with socksProxy, the order in which they are applied is undefined.
   */
  def httpProxy(httpProxy: SocketAddress): This =
    configured(params[Transporter.HttpProxy].copy(sa = Some(httpProxy)))

  /**
    * Make connections via the given HTTP proxy by host name and port.
    * The host name is resolved every transport connection.
    * This API is experiment.
    * If this is defined concurrently with socksProxy, the order in which they are applied is undefined.
    */
  def expHttpProxy(hostName: String, port: Int): This =
    configured(params[Transporter.HttpProxy].copy(sa = Some(InetSocketAddress.createUnresolved(hostName, port))))

  /**
   * For the http proxy use these [[Credentials]] for authentication.
   */
  def httpProxyUsernameAndPassword(credentials: Credentials): This =
    configured(params[Transporter.HttpProxy].copy(credentials = Some(credentials)))

  /**
   * Make connections via the given SOCKS proxy.
   * If this is defined concurrently with httpProxy, the order in which they are applied is undefined.
   *
   * To migrate to the Stack-based APIs, use `ClientTransportParams.socksProxy`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTransport.socksProxy(socketAddress, socksProxy)
   * }}}
   */
  def socksProxy(socksProxy: Option[SocketAddress]): This =
    configured(params[Transporter.SocksProxy].copy(sa = socksProxy))

  /**
    * Make connections via the given HTTP proxy by host name and port.
    * The host name is resolved every transport connection.
    * This API is experiment.
    * If this is defined concurrently with httpProxy, the order in which they are applied is undefined.
    */
  def expSocksProxy(hostName: String, port: Int): This =
    configured(
      params[Transporter.SocksProxy].copy(sa = Some(InetSocketAddress.createUnresolved(hostName, port)))
    )

  /**
   * For the socks proxy use this username for authentication.
   * socksPassword and socksProxy must be set as well
   */
  def socksUsernameAndPassword(credentials: (String,String)): This =
    configured(params[Transporter.SocksProxy].copy(credentials = Some(credentials)))

  /**
   * Specifies a tracer that receives trace events.
   * See [[com.twitter.finagle.tracing]] for details.
   */
  @deprecated("Use tracer() instead", "7.0.0")
  def tracerFactory(factory: com.twitter.finagle.tracing.Tracer.Factory): This =
    tracer(factory())

  /**
   * Specifies a tracer that receives trace events.
   * See [[com.twitter.finagle.tracing]] for details.
   *
   * To migrate to the Stack-based APIs, use `CommonParams.withTracer`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withTracer(t)
   * }}}
   */
  def tracer(t: com.twitter.finagle.tracing.Tracer): This =
    configured(Tracer(t))

  /**
   * To migrate to the Stack-based APIs, use `CommonParams.withMonitor`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   * import com.twitter.util.Monitor
   *
   * val monitor: Monitor = ???
   * Http.client.withMonitor(monitor)
   * }}}
   */
  def monitor(mFactory: String => com.twitter.util.Monitor): This =
    configured(MonitorFactory(mFactory))

  /**
   * Log very detailed debug information to the given logger.
   *
   * To migrate to the Stack-based APIs, use `configured`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   * import com.twitter.finagle.param.Logger
   *
   * Http.client.configured(Logger(logger))
   * }}}
   */
  def logger(logger: java.util.logging.Logger): This =
    configured(Logger(logger))

  /**
   * Use the given parameters for failure accrual.  The first parameter
   * is the number of *successive* failures that are required to mark
   * a host failed.  The second parameter specifies how long the host
   * is dead for, once marked.
   *
   * To completely disable [[FailureAccrualFactory]] use `noFailureAccrual`.
   */
  def failureAccrualParams(pair: (Int, Duration)): This = {
    val (numFailures, markDeadFor) = pair
    configured(FailureAccrualFactory.Param(numFailures, () => markDeadFor))
  }

  /**
   * Disables [[FailureAccrualFactory]].
   *
   * To replace the [[FailureAccrualFactory]] use `failureAccrualFactory`.
   *
   * To migrate to the Stack-based APIs, use `SessionQualificationParams.noFailureAccrual`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSessionQualifier.noFailureAccrual
   * }}}
   */
  def noFailureAccrual: This =
    configured(FailureAccrualFactory.Disabled)

  /**
   * Completely replaces the [[FailureAccrualFactory]] from the underlying stack
   * with the [[ServiceFactoryWrapper]] returned from the given function `factory`.
   *
   * To completely disable [[FailureAccrualFactory]] use `noFailureAccrual`.
   */
  def failureAccrualFactory(factory: util.Timer => ServiceFactoryWrapper): This =
    configured(FailureAccrualFactory.Replaced(factory))

  /**
   * Marks a host dead on connection failure. The host remains dead
   * until we successfully connect. Intermediate connection attempts
   * *are* respected, but host availability is turned off during the
   * reconnection period.
   *
   * To migrate to the Stack-based APIs, use `SessionQualificationParams.noFailFast`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withSessionQualifier.noFailFast
   * }}}
   */
  def failFast(enabled: Boolean): This =
    configured(FailFast(enabled))

  /**
   * When true, the client is daemonized. As with java threads, a
   * process can exit only when all remaining clients are daemonized.
   * False by default.
   *
   * The default for the Stack-based APIs is for the client to
   * be daemonized.
   */
  def daemon(daemonize: Boolean): This =
    configured(Daemonize(daemonize))

  /**
   * Provide an alternative to putting all request exceptions under
   * a "failures" stat.  Typical implementations may report any
   * cancellations or validation errors separately so success rate
   * considers only valid non cancelled requests.
   *
   * To migrate to the Stack-based APIs, use `CommonParams.withExceptionStatsHandler`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.withExceptionStatsHandler(exceptionStatsHandler)
   * }}}
   *
   * @param exceptionStatsHandler function to record failure details.
   */
  def exceptionCategorizer(exceptionStatsHandler: stats.ExceptionStatsHandler): This =
    configured(ExceptionStatsHandler(exceptionStatsHandler))

  /**
   * Configures the traffic class.
   *
   * @see [[Transporter.TrafficClass]]
   */
  def trafficClass(value: Option[Int]): This =
    configured(Transporter.TrafficClass(value))

  /*** BUILD ***/

  // This is only used for client alterations outside of the stack.
  // a more ideal usage would be to retrieve the stats param inside your specific module
  // instead of using this statsReceiver as it keeps the params closer to where they're used
  private[finagle] lazy val statsReceiver = {
    val Stats(sr) = params[Stats]
    val Label(label) = params[Label]
    sr.scope(label)
  }

  /**
   * Construct a ServiceFactory. This is useful for stateful protocols
   * (e.g., those that support transactions or authentication).
   *
   * To migrate to the Stack-based APIs, use `Client.newClient`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.newClient(destination)
   * }}}
   */
  def buildFactory()(
    implicit THE_BUILDER_IS_NOT_FULLY_SPECIFIED_SEE_ClientBuilder_DOCUMENTATION:
      ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]
  ): ServiceFactory[Req, Rep] = {
    val Label(label) = params[Label]
    val DestName(dest) = params[DestName]
    ClientBuilderClient.newClient(client, dest, label)
  }

  /**
   * Construct a Service.
   *
   * To migrate to the Stack-based APIs, use `Client.newService`.
   * For example:
   * {{{
   * import com.twitter.finagle.Http
   *
   * Http.client.newService(destination)
   * }}}
   */
  def build()(
    implicit THE_BUILDER_IS_NOT_FULLY_SPECIFIED_SEE_ClientBuilder_DOCUMENTATION:
      ClientConfigEvidence[HasCluster, HasCodec, HasHostConnectionLimit]
  ): Service[Req, Rep] = {
    val Label(label) = params[Label]
    val DestName(dest) = params[DestName]
    ClientBuilderClient.newService(client, dest, label)
  }

  private[this] def validated = {
    if (!params.contains[DestName])
      throw new IncompleteSpecification("No destination was specified")

    this.asInstanceOf[ClientBuilder[Req, Rep, Yes, Yes, Yes]]
  }

  /**
   * Construct a Service, with runtime checks for builder
   * completeness.
   */
  def unsafeBuild(): Service[Req, Rep] =
    validated.build()

  /**
   * Construct a ServiceFactory, with runtime checks for builder
   * completeness.
   */
  def unsafeBuildFactory(): ServiceFactory[Req, Rep] =
    validated.buildFactory()
}

/**
 * A [[com.twitter.finagle.client.StackClient]] which adds the
 * filters historically included in `ClientBuilder` clients.
 */
private case class ClientBuilderClient[Req, Rep](
    client: StackClient[Req, Rep])
  extends StackClient[Req, Rep] {

  def params: Stack.Params = client.params
  def withParams(ps: Stack.Params): StackClient[Req, Rep] = copy(client.withParams(ps))
  def stack: Stack[ServiceFactory[Req, Rep]] = client.stack
  def withStack(stack: Stack[ServiceFactory[Req, Rep]]): StackClient[Req, Rep] = copy(client.withStack(stack))

  def newClient(dest: Name, label: String): ServiceFactory[Req, Rep] =
    ClientBuilderClient.newClient(client, dest, label)

  def newService(dest: Name, label: String): Service[Req, Rep] =
    ClientBuilderClient.newService(client, dest, label)
}

private object ClientBuilderClient {
  import ClientConfig._
  import com.twitter.finagle.param._

  private class StatsFilterModule[Req, Rep]
      extends Stack.Module2[Stats, ExceptionStatsHandler, ServiceFactory[Req, Rep]] {
    val role: Stack.Role = Stack.Role("ClientBuilder StatsFilter")
    val description: String =
      "Record request stats scoped to 'tries', measured after any retries have occurred"

    def make(
      statsP: Stats,
      exceptionStatsHandlerP: ExceptionStatsHandler,
      next: ServiceFactory[Req, Rep]
    ): ServiceFactory[Req, Rep] = {
      val Stats(statsReceiver) = statsP
      val ExceptionStatsHandler(categorizer) = exceptionStatsHandlerP

      val stats = new StatsFilter[Req, Rep](statsReceiver.scope("tries"), categorizer)
      stats andThen next
    }
  }

  private class GlobalTimeoutModule[Req, Rep]
      extends Stack.Module2[GlobalTimeout, Timer, ServiceFactory[Req, Rep]] {
    val role: Stack.Role = Stack.Role("ClientBuilder GlobalTimeoutFilter")
    val description: String = "Application-configured global timeout"

    def make(
      globalTimeoutP: GlobalTimeout,
      timerP: Timer,
      next: ServiceFactory[Req, Rep]
    ): ServiceFactory[Req, Rep] = {
      val GlobalTimeout(timeout) = globalTimeoutP
      val Timer(timer) = timerP

      if (timeout == Duration.Top) next
      else {
        val exception = new GlobalRequestTimeoutException(timeout)
        val globalTimeout = new TimeoutFilter[Req, Rep](timeout, exception, timer)
        globalTimeout andThen next
      }
    }
  }

  private class ExceptionSourceFilterModule[Req, Rep]
      extends Stack.Module1[Label, ServiceFactory[Req, Rep]] {
    val role: Stack.Role = Stack.Role("ClientBuilder ExceptionSourceFilter")
    val description: String = "Exception source filter"

    def make(
      labelP: Label,
      next: ServiceFactory[Req, Rep]
    ): ServiceFactory[Req, Rep] = {
      val Label(label) = labelP

      val exceptionSource = new ExceptionSourceFilter[Req, Rep](label)
      exceptionSource andThen next
    }
  }

  def newClient[Req, Rep](
    client: StackBasedClient[Req, Rep],
    dest: Name,
    label: String
  ): ServiceFactory[Req, Rep] = {
    val params = client.params
    val Daemonize(daemon) = params[Daemonize]
    val Logger(logger) = params[Logger]
    val MonitorFactory(mFactory) = params[MonitorFactory]

    val clientParams = params + Monitor(mFactory(label))

    val factory = client.withParams(clientParams).newClient(dest, label)

    val exitGuard = if (!daemon) Some(ExitGuard.guard(s"client for '$label'")) else None
    new ServiceFactoryProxy[Req, Rep](factory) {
      private[this] val closed = new AtomicBoolean(false)
      override def close(deadline: Time): Future[Unit] = {
        if (!closed.compareAndSet(false, true)) {
          logger.log(Level.WARNING, "Close on ServiceFactory called multiple times!",
            new Exception/*stack trace please*/)
          return Future.exception(new IllegalStateException)
        }

        super.close(deadline) ensure {
          exitGuard.foreach(_.unguard())
        }
      }
    }
  }

  def newService[Req, Rep](
    client0: StackBasedClient[Req, Rep],
    dest: Name,
    label: String
  ): Service[Req, Rep] = {
    val client =
      client0
        .transformed(new Stack.Transformer {
          def apply[Request, Response](stack: Stack[ServiceFactory[Request, Response]]) =
            stack
              .insertBefore(Retries.Role, new StatsFilterModule[Request, Response])
              .replace(Retries.Role, Retries.moduleWithRetryPolicy[Request, Response])
              .prepend(new GlobalTimeoutModule[Request, Response])
              .prepend(new ExceptionSourceFilterModule[Request, Response])
        })
        .configured(FactoryToService.Enabled(true))

    val factory = newClient(client, dest, label)
    val service: Service[Req, Rep] = new FactoryToService[Req, Rep](factory)

    new ServiceProxy[Req, Rep](service) {
      private[this] val released = new AtomicBoolean(false)
      override def close(deadline: Time): Future[Unit] = {
        if (!released.compareAndSet(false, true)) {
          val Logger(logger) = client.params[Logger]
          logger.log(java.util.logging.Level.WARNING, "Release on Service called multiple times!",
            new Exception/*stack trace please*/)
          return Future.exception(new IllegalStateException)
        }
        super.close(deadline)
      }
    }
  }
}

/**
 * A [[com.twitter.finagle.client.StackClient]] based on a
 * [[com.twitter.finagle.Codec]].
 */
private case class CodecClient[Req, Rep](
    codecFactory: CodecFactory[Req, Rep]#Client,
    stack: Stack[ServiceFactory[Req, Rep]] = StackClient.newStack[Req, Rep],
    params: Stack.Params = ClientConfig.DefaultParams)
  extends StackClient[Req, Rep] {
  import com.twitter.finagle.param._

  def withParams(ps: Stack.Params): StackClient[Req, Rep] = copy(params = ps)
  def withStack(stack: Stack[ServiceFactory[Req, Rep]]): StackClient[Req, Rep] = copy(stack = stack)

  def newClient(dest: Name, label: String): ServiceFactory[Req, Rep] = {
    val codec = codecFactory(ClientCodecConfig(label))

    val prepConn = new Stack.ModuleParams[ServiceFactory[Req, Rep]] {
      def parameters: Seq[Stack.Param[_]] = Nil
      val role: Stack.Role = StackClient.Role.prepConn
      val description = "Connection preparation phase as defined by a Codec"
      def make(ps: Stack.Params, next: ServiceFactory[Req, Rep]): ServiceFactory[Req, Rep] = {
        val Stats(stats) = ps[Stats]
        val underlying = codec.prepareConnFactory(next, ps)
        new ServiceFactoryProxy(underlying) {
          private val stat = stats.stat("codec_connection_preparation_latency_ms")
          override def apply(conn: ClientConnection): Future[Service[Req, Rep]] = {
            val begin = Time.now
            super.apply(conn) ensure {
              stat.add((Time.now - begin).inMilliseconds)
            }
          }
        }
      }
    }

    val clientStack = {
      val stack0 = stack
        .replace(StackClient.Role.prepConn, prepConn)
        .replace(StackClient.Role.prepFactory, (next: ServiceFactory[Req, Rep]) =>
        codec.prepareServiceFactory(next))
        .replace(TraceInitializerFilter.role, codec.newTraceInitializer)

      // disable failFast if the codec requests it or it is
      // disabled via the ClientBuilder parameter.
      val FailFast(failFast) = params[FailFast]
      if (!codec.failFastOk || !failFast) stack0.remove(FailFastFactory.role) else stack0
    }

    case class Client(
      stack: Stack[ServiceFactory[Req, Rep]] = clientStack,
      params: Stack.Params = params
    ) extends StdStackClient[Req, Rep, Client] {
      protected def copy1(
        stack: Stack[ServiceFactory[Req, Rep]] = this.stack,
        params: Stack.Params = this.params): Client = copy(stack, params)

      protected type In = Any
      protected type Out = Any

      protected def newTransporter(): Transporter[Any, Any] = {
        val Stats(stats) = params[Stats]
        val newTransport = (ch: Channel) => codec.newClientTransport(ch, stats)
        Netty3Transporter[Any, Any](codec.pipelineFactory,
          params + Netty3Transporter.TransportFactory(newTransport))
      }

      protected def newDispatcher(transport: Transport[In, Out]): Service[Req, Rep] =
        codec.newClientDispatcher(transport, params)
    }

    val proto = params[ProtocolLibrary]

    // don't override a configured protocol value
    val clientParams =
      if (proto != ProtocolLibrary.param.default) params
      else params + ProtocolLibrary(codec.protocolLibraryName)

    Client(
      stack = clientStack,
      params = clientParams
    ).newClient(dest, label)
  }

  // not called
  def newService(dest: Name, label: String): Service[Req, Rep] = ???
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy