/finagle-thriftmux/src/main/scala/com/twitter/finagle/ThriftMux.scala

http://github.com/twitter/finagle · Scala · 685 lines · 406 code · 75 blank · 204 comment · 7 complexity · 457427d4d4cf0cb9f3565300a65df6aa MD5 · raw file

  1. package com.twitter.finagle
  2. import com.twitter.finagle.client.{
  3. ClientRegistry,
  4. ExceptionRemoteInfoFactory,
  5. StackBasedClient,
  6. StackClient
  7. }
  8. import com.twitter.finagle.context.Contexts
  9. import com.twitter.finagle.context.RemoteInfo.Upstream
  10. import com.twitter.finagle.mux.transport.{MuxFailure, OpportunisticTls}
  11. import com.twitter.finagle.mux.{OpportunisticTlsParams, WithCompressionPreferences}
  12. import com.twitter.finagle.naming.BindingFactory
  13. import com.twitter.finagle.param.{
  14. ExceptionStatsHandler => _,
  15. Monitor => _,
  16. ResponseClassifier => _,
  17. Tracer => _,
  18. _
  19. }
  20. import com.twitter.finagle.server.{BackupRequest, StackBasedServer, StackServer}
  21. import com.twitter.finagle.service._
  22. import com.twitter.finagle.stats.{
  23. ClientStatsReceiver,
  24. ExceptionStatsHandler,
  25. ServerStatsReceiver,
  26. StatsReceiver
  27. }
  28. import com.twitter.finagle.thrift._
  29. import com.twitter.finagle.thrift.exp.partitioning.ThriftPartitioningService.ReqRepMarshallable
  30. import com.twitter.finagle.thrift.exp.partitioning.{
  31. PartitioningParams,
  32. ThriftPartitioningService,
  33. WithThriftPartitioningStrategy
  34. }
  35. import com.twitter.finagle.thrift.service.{Filterable, ServicePerEndpointBuilder}
  36. import com.twitter.finagle.thriftmux.pushsession.MuxDowngradingNegotiator
  37. import com.twitter.finagle.thriftmux.service.ThriftMuxResponseClassifier
  38. import com.twitter.finagle.tracing.{TraceInitializerFilter, Tracer}
  39. import com.twitter.io.Buf
  40. import com.twitter.scrooge.TReusableBuffer
  41. import com.twitter.util._
  42. import java.net.SocketAddress
  43. import org.apache.thrift.TException
  44. import org.apache.thrift.protocol.TProtocolFactory
  45. /**
  46. * The `ThriftMux` object is both a `com.twitter.finagle.Client` and a
  47. * `com.twitter.finagle.Server` for the Thrift protocol served over
  48. * [[com.twitter.finagle.mux]]. Rich interfaces are provided to adhere to those
  49. * generated from a [[https://thrift.apache.org/docs/idl Thrift IDL]] by
  50. * [[https://twitter.github.io/scrooge/ Scrooge]] or
  51. * [[https://github.com/mariusaeriksen/thrift-finagle thrift-finagle]].
  52. *
  53. * == Clients ==
  54. *
  55. * Clients can be created directly from an interface generated from
  56. * a Thrift IDL:
  57. *
  58. * For example, this IDL:
  59. *
  60. * {{{
  61. * service TestService {
  62. * string query(1: string x)
  63. * }
  64. * }}}
  65. *
  66. * compiled with Scrooge, generates the interface
  67. * `TestService.FutureIface`. This is then passed
  68. * into `ThriftMux.Client.newIface`:
  69. *
  70. * {{{
  71. * ThriftMux.client.newIface[TestService.FutureIface](
  72. * addr, classOf[TestService.FutureIface])
  73. * }}}
  74. *
  75. * However note that the Scala compiler can insert the latter
  76. * `Class` for us, for which another variant of `newIface` is
  77. * provided:
  78. *
  79. * {{{
  80. * ThriftMux.client.newIface[TestService.FutureIface](addr)
  81. * }}}
  82. *
  83. * In Java, we need to provide the class object:
  84. *
  85. * {{{
  86. * TestService.FutureIface client =
  87. * ThriftMux.client.newIface(addr, TestService.FutureIface.class);
  88. * }}}
  89. *
  90. * == Servers ==
  91. *
  92. * Servers are also simple to expose:
  93. *
  94. * `TestService.FutureIface` must be implemented and passed
  95. * into `serveIface`:
  96. *
  97. * {{{
  98. * // An echo service
  99. * ThriftMux.server.serveIface(":*", new TestService.FutureIface {
  100. * def query(x: String): Future[String] = Future.value(x)
  101. * })
  102. * }}}
  103. *
  104. * This object does not expose any configuration options. Both clients and servers
  105. * are instantiated with sane defaults. Clients are labeled with the "clnt/thrift"
  106. * prefix and servers with "srv/thrift". If you'd like more configuration, see the
  107. * [[https://twitter.github.io/finagle/guide/Configuration.html#clients-and-servers
  108. * configuration documentation]].
  109. */
  110. object ThriftMux
  111. extends Client[ThriftClientRequest, Array[Byte]]
  112. with Server[Array[Byte], Array[Byte]] {
  113. /**
  114. * Base [[com.twitter.finagle.Stack]] for ThriftMux clients.
  115. */
  116. val BaseClientStack: Stack[ServiceFactory[mux.Request, mux.Response]] = {
  117. val stack = ThriftMuxUtil.protocolRecorder +: Mux.client.stack
  118. /** ThriftMux helper for message marshalling */
  119. object ThriftMuxMarshallable extends ReqRepMarshallable[mux.Request, mux.Response] {
  120. def framePartitionedRequest(
  121. rawRequest: ThriftClientRequest,
  122. original: mux.Request
  123. ): mux.Request =
  124. mux.Request(
  125. original.destination,
  126. original.contexts,
  127. Buf.ByteArray.Owned(rawRequest.message))
  128. def isOneway(original: mux.Request): Boolean = false
  129. def fromResponseToBytes(rep: mux.Response): Array[Byte] =
  130. Buf.ByteArray.Owned.extract(rep.body)
  131. val emptyResponse: mux.Response = mux.Response.empty
  132. }
  133. // this module does Tracing and as such it's important to be added
  134. // after the tracing context is initialized.
  135. stack
  136. .insertAfter(
  137. TraceInitializerFilter.role,
  138. thriftmux.service.ClientTraceAnnotationsFilter.module)
  139. .insertAfter(BindingFactory.role, ThriftPartitioningService.module(ThriftMuxMarshallable))
  140. }
  141. /**
  142. * Base [[com.twitter.finagle.Stack]] for ThriftMux servers.
  143. */
  144. val BaseServerStack: Stack[ServiceFactory[mux.Request, mux.Response]] =
  145. // NOTE: ideally this would not use the `prepConn` role, but it's conveniently
  146. // located in the right location of the stack and is defaulted to a no-op.
  147. // We would like this located anywhere before the StatsFilter so that success
  148. // and failure can be measured properly before converting the exceptions into
  149. // byte arrays. see CSL-1351
  150. ThriftMuxUtil.protocolRecorder +:
  151. Mux.server.stack
  152. .insertBefore(StackServer.Role.preparer, Server.ServerToReqRepPreparer)
  153. .replace(StackServer.Role.preparer, Server.ExnHandler)
  154. // this filter adds tracing annotations and as such must come after trace initialization.
  155. // however, mux removes the `TraceInitializerFilter` as it happens in the mux codec.
  156. .prepend(BackupRequest.traceAnnotationModule[mux.Request, mux.Response])
  157. /**
  158. * Base [[com.twitter.finagle.Stack.Params]] for ThriftMux servers.
  159. */
  160. val BaseServerParams: Stack.Params = Mux.Server.params + ProtocolLibrary("thriftmux")
  161. object Client extends ThriftClient {
  162. def apply(): Client =
  163. new Client()
  164. .withLabel("thrift")
  165. .withStatsReceiver(ClientStatsReceiver)
  166. def standardMuxer: StackClient[mux.Request, mux.Response] =
  167. Mux.client
  168. .copy(stack = BaseClientStack)
  169. .configured(ProtocolLibrary("thriftmux"))
  170. }
  171. /**
  172. * A ThriftMux `com.twitter.finagle.Client`.
  173. *
  174. * @see [[https://twitter.github.io/finagle/guide/Configuration.html#clients-and-servers Configuration]] documentation
  175. * @see [[https://twitter.github.io/finagle/guide/Protocols.html#thrift Thrift]] documentation
  176. * @see [[https://twitter.github.io/finagle/guide/Protocols.html#mux Mux]] documentation
  177. */
  178. case class Client(muxer: StackClient[mux.Request, mux.Response] = Client.standardMuxer)
  179. extends StackBasedClient[ThriftClientRequest, Array[Byte]]
  180. with Stack.Parameterized[Client]
  181. with Stack.Transformable[Client]
  182. with CommonParams[Client]
  183. with ClientParams[Client]
  184. with WithClientTransport[Client]
  185. with WithClientAdmissionControl[Client]
  186. with WithClientSession[Client]
  187. with WithSessionQualifier[Client]
  188. with WithDefaultLoadBalancer[Client]
  189. with WithThriftPartitioningStrategy[Client]
  190. with ThriftRichClient
  191. with OpportunisticTlsParams[Client]
  192. with WithCompressionPreferences[Client] {
  193. def stack: Stack[ServiceFactory[mux.Request, mux.Response]] =
  194. muxer.stack
  195. def params: Stack.Params = muxer.params
  196. protected lazy val Label(defaultClientName) = params[Label]
  197. protected val clientParam: RichClientParam = RichClientParam(
  198. protocolFactory = params[Thrift.param.ProtocolFactory].protocolFactory,
  199. maxThriftBufferSize = params[Thrift.param.MaxReusableBufferSize].maxReusableBufferSize,
  200. clientStats = params[Stats].statsReceiver,
  201. responseClassifier = params[com.twitter.finagle.param.ResponseClassifier].responseClassifier,
  202. perEndpointStats = params[Thrift.param.PerEndpointStats].enabled
  203. )
  204. def withParams(ps: Stack.Params): Client =
  205. copy(muxer = muxer.withParams(ps))
  206. def transformed(t: Stack.Transformer): Client =
  207. copy(muxer = muxer.transformed(t))
  208. /**
  209. * Produce a [[com.twitter.finagle.ThriftMux.Client]] using the provided
  210. * client ID.
  211. */
  212. def withClientId(clientId: ClientId): Client =
  213. configured(Thrift.param.ClientId(Some(clientId)))
  214. // overridden for better Java compatibility
  215. override def withOpportunisticTls(level: OpportunisticTls.Level): Client =
  216. super.withOpportunisticTls(level)
  217. // overridden for better Java compatibility
  218. override def withNoOpportunisticTls: Client =
  219. super.withNoOpportunisticTls
  220. /**
  221. * Produce a [[com.twitter.finagle.ThriftMux.Client]] using the provided
  222. * protocolFactory.
  223. */
  224. def withProtocolFactory(pf: TProtocolFactory): Client =
  225. configured(Thrift.param.ProtocolFactory(pf))
  226. /**
  227. * Produce a [[com.twitter.finagle.ThriftMux.Client]] using the provided stack.
  228. */
  229. def withStack(stack: Stack[ServiceFactory[mux.Request, mux.Response]]): Client =
  230. this.copy(muxer = muxer.withStack(stack))
  231. def withStack(
  232. fn: Stack[ServiceFactory[mux.Request, mux.Response]] => Stack[
  233. ServiceFactory[mux.Request, mux.Response]
  234. ]
  235. ): Client =
  236. withStack(fn(stack))
  237. /**
  238. * Prepends `filter` to the top of the client. That is, after materializing
  239. * the client (newClient/newService) `filter` will be the first element which
  240. * requests flow through. This is a familiar chaining combinator for filters.
  241. */
  242. def filtered(filter: Filter[mux.Request, mux.Response, mux.Request, mux.Response]): Client = {
  243. val role = Stack.Role(filter.getClass.getSimpleName)
  244. val stackable = Filter.canStackFromFac.toStackable(role, filter)
  245. withStack(stackable +: stack)
  246. }
  247. /**
  248. * Produce a [[com.twitter.finagle.ThriftMux.Client]] with the specified max
  249. * size of the reusable buffer for thrift responses. If this size
  250. * is exceeded, the buffer is not reused and a new buffer is
  251. * allocated for the next thrift response.
  252. * The default max size is 16Kb.
  253. *
  254. * @note MaxReusableBufferSize will be ignored if TReusableBufferFactory is set.
  255. *
  256. * @param size Max size of the reusable buffer for thrift responses in bytes.
  257. */
  258. def withMaxReusableBufferSize(size: Int): Client =
  259. configured(Thrift.param.MaxReusableBufferSize(size))
  260. /**
  261. * Produce a [[com.twitter.finagle.ThriftMux.Client]] with a factory creates new
  262. * TReusableBuffer, the TReusableBuffer can be shared with other client instance.
  263. * If set, the MaxReusableBufferSize will be ignored.
  264. */
  265. def withTReusableBufferFactory(tReusableBufferFactory: () => TReusableBuffer): Client =
  266. configured(Thrift.param.TReusableBufferFactory(tReusableBufferFactory))
  267. /**
  268. * Produce a [[com.twitter.finagle.ThriftMux.Client]] with per-endpoint stats filters
  269. */
  270. def withPerEndpointStats: Client =
  271. configured(Thrift.param.PerEndpointStats(true))
  272. private[this] def clientId: Option[ClientId] = params[Thrift.param.ClientId].clientId
  273. private[this] object ThriftMuxToMux
  274. extends Filter[ThriftClientRequest, Array[Byte], mux.Request, mux.Response] {
  275. private val extractResponseBytesFn = (response: mux.Response) => {
  276. val responseCtx = Contexts.local.getOrElse(Headers.Response.Key, EmptyResponseHeadersFn)
  277. responseCtx.set(response.contexts)
  278. Buf.ByteArray.Owned.extract(response.body)
  279. }
  280. private val EmptyRequestHeadersFn: () => Headers.Values = () => Headers.Request.newValues
  281. private val EmptyResponseHeadersFn: () => Headers.Values = () => Headers.Response.newValues
  282. def apply(
  283. req: ThriftClientRequest,
  284. service: Service[mux.Request, mux.Response]
  285. ): Future[Array[Byte]] = {
  286. if (req.oneway)
  287. return Future.exception(
  288. new UnsupportedOperationException("ThriftMux does not support one-way messages")
  289. )
  290. // We set ClientId a bit early, because ThriftMux relies on that broadcast
  291. // context to be set when dispatching.
  292. ExceptionRemoteInfoFactory.letUpstream(Upstream.addr, ClientId.current.map(_.name)) {
  293. ClientId.let(clientId) {
  294. val requestCtx = Contexts.local.getOrElse(Headers.Request.Key, EmptyRequestHeadersFn)
  295. // TODO set the Path here.
  296. val muxRequest =
  297. mux.Request(Path.empty, requestCtx.values, Buf.ByteArray.Owned(req.message))
  298. service(muxRequest).map(extractResponseBytesFn)
  299. }
  300. }
  301. }
  302. }
  303. private[this] def withDeserializingClassifier: StackClient[mux.Request, mux.Response] = {
  304. // Note: what type of deserializer used is important if none is specified
  305. // so that we keep the prior behavior of Thrift exceptions
  306. // being counted as a success. Otherwise, even using the default
  307. // ResponseClassifier would then see that response as a `Throw` and thus
  308. // a failure. So, when none is specified, a "deserializing-only"
  309. // classifier is used to make when deserialization happens in the stack
  310. // uniform whether or not a `ResponseClassifier` is wired up.
  311. val classifier = if (params.contains[param.ResponseClassifier]) {
  312. ThriftMuxResponseClassifier.usingDeserializeCtx(
  313. params[param.ResponseClassifier].responseClassifier
  314. )
  315. } else {
  316. ThriftMuxResponseClassifier.DeserializeCtxOnly
  317. }
  318. muxer.configured(param.ResponseClassifier(classifier))
  319. }
  320. def newService(dest: Name, label: String): Service[ThriftClientRequest, Array[Byte]] = {
  321. clientId.foreach(id => ClientRegistry.export(params, "ClientId", id.name))
  322. ThriftMuxToMux.andThen(withDeserializingClassifier.newService(dest, label))
  323. }
  324. def newClient(dest: Name, label: String): ServiceFactory[ThriftClientRequest, Array[Byte]] = {
  325. clientId.foreach(id => ClientRegistry.export(params, "ClientId", id.name))
  326. ThriftMuxToMux.andThen(withDeserializingClassifier.newClient(dest, label))
  327. }
  328. /**
  329. * Create a [[thriftmux.MethodBuilder]] for a given destination.
  330. *
  331. * @see [[https://twitter.github.io/finagle/guide/MethodBuilder.html user guide]]
  332. */
  333. def methodBuilder(dest: String): thriftmux.MethodBuilder =
  334. thriftmux.MethodBuilder.from(dest, this)
  335. /**
  336. * Create a [[thriftmux.MethodBuilder]] for a given destination.
  337. *
  338. * @see [[https://twitter.github.io/finagle/guide/MethodBuilder.html user guide]]
  339. */
  340. def methodBuilder(dest: Name): thriftmux.MethodBuilder =
  341. thriftmux.MethodBuilder.from(dest, this)
  342. /**
  343. * $servicePerEndpoint
  344. *
  345. * @param service The Finagle [[Service]] to be used.
  346. * @param label Assign a label for scoped stats.
  347. * @param builder The builder type is generated by Scrooge for a thrift service.
  348. */
  349. private[finagle] def servicePerEndpoint[ServicePerEndpoint <: Filterable[ServicePerEndpoint]](
  350. service: Service[ThriftClientRequest, Array[Byte]],
  351. label: String
  352. )(
  353. implicit builder: ServicePerEndpointBuilder[ServicePerEndpoint]
  354. ): ServicePerEndpoint = super.newServicePerEndpoint(service, label)
  355. // Java-friendly forwarders
  356. // See https://issues.scala-lang.org/browse/SI-8905
  357. override val withTransport: ClientTransportParams[Client] =
  358. new ClientTransportParams(this)
  359. override val withSession: ClientSessionParams[Client] =
  360. new ClientSessionParams(this)
  361. override val withLoadBalancer: DefaultLoadBalancingParams[Client] =
  362. new DefaultLoadBalancingParams(this)
  363. override val withSessionQualifier: SessionQualificationParams[Client] =
  364. new SessionQualificationParams(this)
  365. override val withAdmissionControl: ClientAdmissionControlParams[Client] =
  366. new ClientAdmissionControlParams(this)
  367. override val withPartitioning: PartitioningParams[Client] =
  368. new PartitioningParams(this)
  369. override def withLabel(label: String): Client = super.withLabel(label)
  370. override def withStatsReceiver(statsReceiver: StatsReceiver): Client =
  371. super.withStatsReceiver(statsReceiver)
  372. override def withMonitor(monitor: Monitor): Client = super.withMonitor(monitor)
  373. override def withTracer(tracer: Tracer): Client = super.withTracer(tracer)
  374. override def withExceptionStatsHandler(exceptionStatsHandler: ExceptionStatsHandler): Client =
  375. super.withExceptionStatsHandler(exceptionStatsHandler)
  376. override def withRequestTimeout(timeout: Duration): Client = super.withRequestTimeout(timeout)
  377. override def withResponseClassifier(responseClassifier: ResponseClassifier): Client =
  378. super.withResponseClassifier(responseClassifier)
  379. override def withRetryBudget(budget: RetryBudget): Client = super.withRetryBudget(budget)
  380. override def withRetryBackoff(backoff: Stream[Duration]): Client =
  381. super.withRetryBackoff(backoff)
  382. override def configured[P](psp: (P, Stack.Param[P])): Client = super.configured(psp)
  383. }
  384. def client: ThriftMux.Client = Client()
  385. protected val Thrift.param.ProtocolFactory(protocolFactory) =
  386. client.params[Thrift.param.ProtocolFactory]
  387. def newClient(dest: Name, label: String): ServiceFactory[ThriftClientRequest, Array[Byte]] =
  388. client.newClient(dest, label)
  389. def newService(dest: Name, label: String): Service[ThriftClientRequest, Array[Byte]] =
  390. client.newService(dest, label)
  391. object Server {
  392. def defaultMuxer: StackServer[mux.Request, mux.Response] = {
  393. Mux.server
  394. .copy(
  395. stack = BaseServerStack,
  396. params = BaseServerParams,
  397. sessionFactory = MuxDowngradingNegotiator.build(_, _, _, _, _)
  398. )
  399. }
  400. private val MuxToArrayFilter =
  401. new Filter[mux.Request, mux.Response, Array[Byte], Array[Byte]] {
  402. private[this] val responseBytesToMuxResponseFn = (responseBytes: Array[Byte]) => {
  403. mux.Response(
  404. ctxts = Contexts.local(Headers.Response.Key).values,
  405. buf = Buf.ByteArray.Owned(responseBytes)
  406. )
  407. }
  408. def apply(
  409. request: mux.Request,
  410. service: Service[Array[Byte], Array[Byte]]
  411. ): Future[mux.Response] = {
  412. val reqBytes = Buf.ByteArray.Owned.extract(request.body)
  413. Contexts.local.let(
  414. Headers.Request.Key,
  415. Headers.Values(request.contexts),
  416. Headers.Response.Key,
  417. Headers.Response.newValues
  418. ) {
  419. service(reqBytes).map(responseBytesToMuxResponseFn)
  420. }
  421. }
  422. }
  423. // Convert unhandled exceptions to TApplicationExceptions, but pass
  424. // com.twitter.finagle.FailureFlags that are flagged in mux-compatible ways
  425. // to mux for transmission.
  426. private[this] class ExnFilter(protocolFactory: TProtocolFactory)
  427. extends SimpleFilter[mux.Request, mux.Response] {
  428. def apply(
  429. request: mux.Request,
  430. service: Service[mux.Request, mux.Response]
  431. ): Future[mux.Response] =
  432. service(request).rescue {
  433. case f: FailureFlags[_] if MuxFailure.FromThrow.isDefinedAt(f) => Future.exception(f)
  434. case e if !e.isInstanceOf[TException] =>
  435. val msg =
  436. UncaughtAppExceptionFilter.writeExceptionMessage(request.body, e, protocolFactory)
  437. Future.value(mux.Response(Nil, msg))
  438. }
  439. }
  440. private[ThriftMux] val ExnHandler =
  441. new Stack.Module1[Thrift.param.ProtocolFactory, ServiceFactory[mux.Request, mux.Response]] {
  442. val role = Stack.Role("appExceptionHandling")
  443. val description = "Translates uncaught application exceptions into Thrift messages"
  444. def make(
  445. _pf: Thrift.param.ProtocolFactory,
  446. next: ServiceFactory[mux.Request, mux.Response]
  447. ): ServiceFactory[mux.Request, mux.Response] = {
  448. val Thrift.param.ProtocolFactory(pf) = _pf
  449. val exnFilter = new ExnFilter(pf)
  450. exnFilter.andThen(next)
  451. }
  452. }
  453. // Set an empty ServerToReqRep context in the stack. Scrooge generated finagle service should
  454. // then set the value.
  455. private[ThriftMux] val ServerToReqRepPreparer =
  456. new Stack.Module0[ServiceFactory[mux.Request, mux.Response]] {
  457. val role: Stack.Role = Stack.Role("ServerToReqRep Preparer")
  458. val description: String = "Set an empty bytes to ReqRep context in the local contexts, " +
  459. "scrooge generated service should set the value."
  460. def make(
  461. next: ServiceFactory[mux.Request, mux.Response]
  462. ): ServiceFactory[mux.Request, mux.Response] = {
  463. val svcDeserializeCtxFilter = new SimpleFilter[mux.Request, mux.Response] {
  464. def apply(
  465. request: mux.Request,
  466. service: Service[mux.Request, mux.Response]
  467. ): Future[mux.Response] = {
  468. val deserCtx = new ServerToReqRep
  469. Contexts.local.let(ServerToReqRep.Key, deserCtx) {
  470. service(request)
  471. }
  472. }
  473. }
  474. svcDeserializeCtxFilter.andThen(next)
  475. }
  476. }
  477. }
  478. /**
  479. * A ThriftMux `com.twitter.finagle.Server`.
  480. *
  481. * @see [[https://twitter.github.io/finagle/guide/Configuration.html#clients-and-servers Configuration]] documentation
  482. * @see [[https://twitter.github.io/finagle/guide/Protocols.html#thrift Thrift]] documentation
  483. * @see [[https://twitter.github.io/finagle/guide/Protocols.html#mux Mux]] documentation
  484. */
  485. final case class Server(muxer: StackServer[mux.Request, mux.Response] = Server.defaultMuxer)
  486. extends StackBasedServer[Array[Byte], Array[Byte]]
  487. with ThriftRichServer
  488. with Stack.Parameterized[Server]
  489. with CommonParams[Server]
  490. with WithServerTransport[Server]
  491. with WithServerSession[Server]
  492. with WithServerAdmissionControl[Server]
  493. with OpportunisticTlsParams[Server]
  494. with WithCompressionPreferences[Server] {
  495. import Server.MuxToArrayFilter
  496. def stack: Stack[ServiceFactory[mux.Request, mux.Response]] =
  497. muxer.stack
  498. protected val serverParam: RichServerParam = RichServerParam(
  499. protocolFactory = params[Thrift.param.ProtocolFactory].protocolFactory,
  500. serviceName = params[Label].label,
  501. maxThriftBufferSize = params[Thrift.param.MaxReusableBufferSize].maxReusableBufferSize,
  502. serverStats = params[Stats].statsReceiver,
  503. responseClassifier = params[com.twitter.finagle.param.ResponseClassifier].responseClassifier,
  504. perEndpointStats = params[Thrift.param.PerEndpointStats].enabled
  505. )
  506. def params: Stack.Params = muxer.params
  507. /**
  508. * Produce a [[com.twitter.finagle.ThriftMux.Server]] using the provided
  509. * `TProtocolFactory`.
  510. */
  511. def withProtocolFactory(pf: TProtocolFactory): Server =
  512. configured(Thrift.param.ProtocolFactory(pf))
  513. /**
  514. * Produce a [[com.twitter.finagle.ThriftMux.Server]] using the provided stack.
  515. */
  516. def withStack(stack: Stack[ServiceFactory[mux.Request, mux.Response]]): Server =
  517. this.copy(muxer = muxer.withStack(stack))
  518. def withStack(
  519. fn: Stack[ServiceFactory[mux.Request, mux.Response]] => Stack[
  520. ServiceFactory[mux.Request, mux.Response]
  521. ]
  522. ): Server =
  523. withStack(fn(stack))
  524. /**
  525. * Prepends `filter` to the top of the server. That is, after materializing
  526. * the server (newService) `filter` will be the first element which requests
  527. * flow through. This is a familiar chaining combinator for filters.
  528. */
  529. def filtered(filter: Filter[mux.Request, mux.Response, mux.Request, mux.Response]): Server = {
  530. val role = Stack.Role(filter.getClass.getSimpleName)
  531. val stackable = Filter.canStackFromFac.toStackable(role, filter)
  532. withStack(stackable +: stack)
  533. }
  534. /**
  535. * Produce a [[com.twitter.finagle.ThriftMux.Server]] with the specified max
  536. * size of the reusable buffer for thrift responses. If this size
  537. * is exceeded, the buffer is not reused and a new buffer is
  538. * allocated for the next thrift response.
  539. * The default max size is 16Kb.
  540. *
  541. * @param size Max size of the reusable buffer for thrift responses in bytes.
  542. */
  543. def withMaxReusableBufferSize(size: Int): Server =
  544. configured(Thrift.param.MaxReusableBufferSize(size))
  545. /**
  546. * Produce a [[com.twitter.finagle.ThriftMux.Server]] with per-endpoint stats filters
  547. */
  548. def withPerEndpointStats: Server =
  549. configured(Thrift.param.PerEndpointStats(true))
  550. def withParams(ps: Stack.Params): Server =
  551. copy(muxer = muxer.withParams(ps))
  552. def transformed(t: Stack.Transformer): Server =
  553. copy(muxer = muxer.transformed(t))
  554. // overridden for better Java compatibility
  555. override def withOpportunisticTls(level: OpportunisticTls.Level): Server =
  556. super.withOpportunisticTls(level)
  557. // overridden for better Java compatibility
  558. override def withNoOpportunisticTls: Server =
  559. super.withNoOpportunisticTls
  560. private[this] def withDeserializingClassifier: StackServer[mux.Request, mux.Response] = {
  561. // Note: what type of deserializer used is important if none is specified
  562. // so that we keep the prior behavior of Thrift exceptions
  563. // being counted as a success. Otherwise, even using the default
  564. // ResponseClassifier would then see that response as a `Throw` and thus
  565. // a failure. So, when none is specified, a "deserializing-only"
  566. // classifier is used to make when deserialization happens in the stack
  567. // uniform whether or not a `ResponseClassifier` is wired up.
  568. val classifier = if (params.contains[com.twitter.finagle.param.ResponseClassifier]) {
  569. ThriftMuxResponseClassifier.usingReqRepCtx(
  570. params[com.twitter.finagle.param.ResponseClassifier].responseClassifier
  571. )
  572. } else {
  573. ThriftMuxResponseClassifier.ReqRepCtxOnly
  574. }
  575. muxer.configured(com.twitter.finagle.param.ResponseClassifier(classifier))
  576. }
  577. def serve(
  578. addr: SocketAddress,
  579. factory: ServiceFactory[Array[Byte], Array[Byte]]
  580. ): ListeningServer = {
  581. withDeserializingClassifier.serve(addr, MuxToArrayFilter.andThen(factory))
  582. }
  583. // Java-friendly forwarders
  584. // See https://issues.scala-lang.org/browse/SI-8905
  585. override val withTransport: ServerTransportParams[Server] =
  586. new ServerTransportParams(this)
  587. override val withSession: ServerSessionParams[Server] =
  588. new ServerSessionParams(this)
  589. override val withAdmissionControl: ServerAdmissionControlParams[Server] =
  590. new ServerAdmissionControlParams(this)
  591. override def withLabel(label: String): Server = super.withLabel(label)
  592. override def withStatsReceiver(statsReceiver: StatsReceiver): Server =
  593. super.withStatsReceiver(statsReceiver)
  594. override def withMonitor(monitor: Monitor): Server = super.withMonitor(monitor)
  595. override def withTracer(tracer: Tracer): Server = super.withTracer(tracer)
  596. override def withExceptionStatsHandler(exceptionStatsHandler: ExceptionStatsHandler): Server =
  597. super.withExceptionStatsHandler(exceptionStatsHandler)
  598. override def withRequestTimeout(timeout: Duration): Server = super.withRequestTimeout(timeout)
  599. override def configured[P](psp: (P, Stack.Param[P])): Server = super.configured(psp)
  600. }
  601. def server: ThriftMux.Server =
  602. Server()
  603. .configured(Label("thrift"))
  604. .configured(Stats(ServerStatsReceiver))
  605. def serve(
  606. addr: SocketAddress,
  607. factory: ServiceFactory[Array[Byte], Array[Byte]]
  608. ): ListeningServer =
  609. server.serve(addr, factory)
  610. }