Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

spray.can.Http$ConnectionException: Premature connection close

Tags:

scala

spray

In my below test, I tried to simulate a timeout and then send a normal request. however, I got spray.can.Http$ConnectionException: Premature connection close (the server doesn't appear to support request pipelining)

class SprayCanTest extends ModuleTestKit("/SprayCanTest.conf") with FlatSpecLike with Matchers {

  import system.dispatcher

  var app = Actor.noSender

  protected override def beforeAll(): Unit = {
    super.beforeAll()
    app = system.actorOf(Props(new MockServer))
  }

  override protected def afterAll(): Unit = {
    system.stop(app)
    super.afterAll()
  }


  "response time out" should "work" in {
    val setup = Http.HostConnectorSetup("localhost", 9101, false)

    connect(setup).onComplete {
      case Success(conn) => {
        conn ! HttpRequest(HttpMethods.GET, "/timeout")
      }
    }

    expectMsgPF() {
      case Status.Failure(t) =>
        t shouldBe a[RequestTimeoutException]
    }


  }

  "normal http response" should "work" in {

    //Thread.sleep(5000)
    val setup = Http.HostConnectorSetup("localhost", 9101, false)

    connect(setup).onComplete {
      case Success(conn) => {
        conn ! HttpRequest(HttpMethods.GET, "/hello")
      }
    }

    expectMsgPF() {
      case HttpResponse(status, entity, _, _) =>
        status should be(StatusCodes.OK)
        entity should be(HttpEntity("Helloworld"))
    }
  }

  def connect(setup: HostConnectorSetup)(implicit system: ActorSystem) = {
    // for the actor 'asks'
    import system.dispatcher
    implicit val timeout: Timeout = Timeout(1 second)
    (IO(Http) ? setup) map {
      case Http.HostConnectorInfo(connector, _) => connector
    }
  }

  class MockServer extends Actor {
    //implicit val timeout: Timeout = 1.second
    implicit val system = context.system

    // Register connection service
    IO(Http) ! Http.Bind(self, interface = "localhost", port = 9101)

    def receive: Actor.Receive = {
      case _: Http.Connected => sender ! Http.Register(self)

      case HttpRequest(GET, Uri.Path("/timeout"), _, _, _) => {
        Thread.sleep(3000)
        sender ! HttpResponse(entity = HttpEntity("ok"))
      }

      case HttpRequest(GET, Uri.Path("/hello"), _, _, _) => {
        sender ! HttpResponse(entity = HttpEntity("Helloworld"))
      }
    }
  }


}

and My config for test:

spray {
  can {
    client {
      response-chunk-aggregation-limit = 0
      connecting-timeout = 1s
      request-timeout = 1s
    }
    host-connector {
      max-retries = 0
    }
  }
}

I found that in both cases, the "conn" object is the same. So I guess when RequestTimeoutException happens, spray put back the conn to the pool (by default 4?) and the next case will use the same conn but at this time, this conn is keep alive, so the server will treat it as chunked request.

If I put some sleep in the second case, it will just passed. So I guess I must close the conn when got RequestTimeoutException and make sure the second case use a fresh new connection, right?

How should I do? Any configurations?

Thanks

Leon

like image 582
anuni Avatar asked Sep 16 '14 03:09

anuni


1 Answers

You should not block inside an Actor (your MockServer). When it is blocked, it is unable to respond to any messages. You can wrap the Thread.sleep and response inside a Future. Or even better: use the Akka Scheduler. Be sure to assign the sender to a val because it may change when you respond to the request asynchronously. This should do the trick:

val savedSender = sender()
context.system.scheduler.scheduleOnce(3 seconds){
  savedSender ! HttpResponse(entity = HttpEntity("ok"))
}
like image 74
Jan-Pieter Avatar answered Oct 12 '22 07:10

Jan-Pieter