Scala http客户端不重用连接

时间:2018-01-12 07:59:15

标签: scala akka-http

在Scala中,我有一个带有一些本地绑定的akka​​ http客户端类:

    class AkkaConPoolingHttpClient(
        override val timeout: Option[FiniteDuration] = None,
        val localBinding: Option[InetSocketAddress] = None,
        val userAgentHeader: Option[String] = None)(
        implicit val config: HttpClient.Config,
        val system: ActorSystem,
        val materializer: Materializer)
      extends AkkaHttpClient  {

  protected val http = Http()

  override def dispatch(request: HttpRequest): Future[HttpResponse] = {

    val effectivePort = request.uri.effectivePort

    val connection =
      http.outgoingConnection(
        request.uri.authority.host.address(),
        port = effectivePort,
        localAddress = localBinding)

    val preparedRequest = userAgentHeader match {
      case Some(userAgent) => fixUri(request.withHeaders(request.headers ++ Seq(headers.`User-Agent`(userAgent))))
      case None => fixUri(request)
     }

    Source.single(preparedRequest) via connection runWith Sink.head
  }

object AkkaConPoolingHttpClient {
  private def fixUri(request: HttpRequest): HttpRequest =
    request.withUri(request.uri.toRelative)
}

我正试图看看它是否重用了连接而且它似乎没有:

      val connectionCount = new AtomicInteger()
      val testServerFuture = Http().bind("127.0.0.1", 0).to {
        Sink.foreach { incomingConnection =>
          connectionCount.incrementAndGet()
          incomingConnection.flow.join(Flow[HttpRequest].map(_ => HttpResponse())).run()
        }
      }.run()

      val testServerPort = Await.result(testServerFuture, defaultExpectTimeout)
        .localAddress.getPort
      val address = "127.0.0.1"
      val addr = Some(new InetSocketAddress(address, 0))
      val client = new AkkaConPoolingHttpClient(localBinding = addr)

      // Send some requests concurrently
      val requests = List(
        Get(s"http://127.0.0.1:$testServerPort/1"),
        Get(s"http://127.0.0.1:$testServerPort/2"),
        Get(s"http://127.0.0.1:$testServerPort/3"))

      val responses = Await.result(
        Future.sequence(requests.map(client.sendRequest)),
        defaultExpectTimeout)

      // Send some more requests -- the connections from before should be reused
      Thread.sleep(500)
      val responses2 = Await.result(
        Future.sequence(requests.map(client.sendRequest)),
        defaultExpectTimeout)

      // Usually this is "3", occasionally "4". 
      connectionCount.get() must beLessThanOrEqualTo(4)

不幸的是,测试失败,connectionCount.get()有6个连接。为什么不重用连接?这段代码出了什么问题?

我也尝试过:

val effectivePort = request.uri.effectivePort
val clientSettings = ClientConnectionSettings(system).withSocketOptions(SO.ReuseAddress(true) :: Nil)
val connectionFlow: Flow[HttpRequest, HttpResponse, Future[Http.OutgoingConnection]] =
  Http().outgoingConnection(
        request.uri.authority.host.address(),
        port = effectivePort,
        localAddress = localBinding,
        settings = clientSettings
  )
..................
Source.single(preparedRequest)
      .via(connectionFlow)
      .runWith(Sink.head)

但我的测试中仍有6个连接...

1 个答案:

答案 0 :(得分:1)

<强>问题

问题的根源在于您为每个connection创建了一个新的request。客户端代码实际上非常清楚:

Source.single(preparedRequest) via connection runWith Sink.head

每个请求都是通过新实例化的连接发送的。这是由于您从请求中获取地址的一般设计缺陷:

val connection =
  http.outgoingConnection(
    request.uri.authority.host.address(), //address comes from request
    port = effectivePort,
    localAddress = localBinding)

建立一次地址(确保单个连接)会更有效,然后每个请求只需要路径。

<强>解决方案

要使用单个连接,您必须创建一个Flow并通过as described here发送所有请求。