From 4b48875962377a52dbb7cf126a7a42b710672f02 Mon Sep 17 00:00:00 2001 From: Ian Forsey Date: Mon, 19 Aug 2013 15:20:41 +0100 Subject: [PATCH] ! can: introduce dedicated exceptions for connection failure and request timeout for host-level API --- .../spray/can/client/SprayCanClientSpec.scala | 2 +- spray-can/src/main/scala/spray/can/Http.scala | 10 ++++++++- .../can/client/HttpHostConnectionSlot.scala | 22 ++++++++++++------- 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/spray-can-tests/src/test/scala/spray/can/client/SprayCanClientSpec.scala b/spray-can-tests/src/test/scala/spray/can/client/SprayCanClientSpec.scala index a104cc7a37..42f40f16a5 100644 --- a/spray-can-tests/src/test/scala/spray/can/client/SprayCanClientSpec.scala +++ b/spray-can-tests/src/test/scala/spray/can/client/SprayCanClientSpec.scala @@ -203,7 +203,7 @@ class SprayCanClientSpec extends Specification { val probe = TestProbe() probe.send(IO(Http), Get("/abc") ~> Host(hostname, port)) acceptConnection() - probe.expectMsgType[Status.Failure].cause.getMessage must startWith("Request timeout") + probe.expectMsgType[Status.Failure].cause must beAnInstanceOf[Http.RequestTimeoutException] } } diff --git a/spray-can/src/main/scala/spray/can/Http.scala b/spray-can/src/main/scala/spray/can/Http.scala index dee83b5bec..4bcf61bc27 100644 --- a/spray-can/src/main/scala/spray/can/Http.scala +++ b/spray-can/src/main/scala/spray/can/Http.scala @@ -24,7 +24,7 @@ import akka.actor._ import spray.can.server.ServerSettings import spray.can.client.{ HostConnectorSettings, ClientConnectionSettings } import spray.io.{ ConnectionTimeouts, ClientSSLEngineProvider, ServerSSLEngineProvider } -import spray.http.{ HttpResponse, HttpRequest, HttpMessagePart, HttpMessagePartWrapper } +import spray.http._ import spray.util.actorSystem object Http extends ExtensionKey[HttpExt] { @@ -122,6 +122,14 @@ object Http extends ExtensionKey[HttpExt] { case class MessageEvent(ev: HttpMessagePart) extends Event case class HostConnectorInfo(hostConnector: ActorRef, setup: HostConnectorSetup) extends Event + + // exceptions + class ConnectionException(message: String) extends RuntimeException(message) + + class ConnectionAttemptFailedException(val host: String, val port: Int) extends ConnectionException(s"Connection attempt to $host:$port failed") + + class RequestTimeoutException(val request: HttpRequestPart with HttpMessageStart, message: String) + extends ConnectionException(message) } class HttpExt(system: ExtendedActorSystem) extends akka.io.IO.Extension { diff --git a/spray-can/src/main/scala/spray/can/client/HttpHostConnectionSlot.scala b/spray-can/src/main/scala/spray/can/client/HttpHostConnectionSlot.scala index 3e4486b72c..305dd42b41 100644 --- a/spray-can/src/main/scala/spray/can/client/HttpHostConnectionSlot.scala +++ b/spray-can/src/main/scala/spray/can/client/HttpHostConnectionSlot.scala @@ -82,7 +82,8 @@ private[client] class HttpHostConnectionSlot(host: String, port: Int, case _: Http.CommandFailed ⇒ log.debug("Connection attempt failed") - openRequests foreach clear("Connection attempt failed", retry = false) + val error = new Http.ConnectionAttemptFailedException(host, port) + openRequests foreach clear(error, retry = false) if (aborted.isEmpty) { context.parent ! Disconnected(openRequests.size) context.become(unconnected) @@ -126,7 +127,7 @@ private[client] class HttpHostConnectionSlot(host: String, port: Int, case ev @ Timedout(part) ⇒ log.debug("{} timed out, closing connection", format(part)) - context.become(closing(httpConnection, openRequests, "Request timeout", retry = true)) + context.become(closing(httpConnection, openRequests, new Http.RequestTimeoutException(part, format(part) + " timed out"), retry = true)) case cmd: Http.CloseCommand ⇒ httpConnection ! cmd @@ -149,12 +150,15 @@ private[client] class HttpHostConnectionSlot(host: String, port: Int, context.become(unconnected) } - def closing(httpConnection: ActorRef, openRequests: Queue[RequestContext], errorMsg: String, + def closing(httpConnection: ActorRef, openRequests: Queue[RequestContext], error: String, retry: Boolean): Receive = + closing(httpConnection, openRequests, new Http.ConnectionException(error), retry) + + def closing(httpConnection: ActorRef, openRequests: Queue[RequestContext], error: Http.ConnectionException, retry: Boolean): Receive = { case ev @ (_: Http.ConnectionClosed | Terminated(`httpConnection`)) ⇒ context.parent ! Disconnected(openRequests.size) - openRequests foreach clear(errorMsg, retry) + openRequests foreach clear(error, retry) context.unwatch(httpConnection) context.become(unconnected) } @@ -164,14 +168,16 @@ private[client] class HttpHostConnectionSlot(host: String, port: Int, case Terminated(`httpConnection`) ⇒ context.stop(self) } - def clear(errorMsg: String, retry: Boolean): RequestContext ⇒ Unit = { + def clear(error: String, retry: Boolean): RequestContext ⇒ Unit = clear(new Http.ConnectionException(error), retry) + + def clear(error: Http.ConnectionException, retry: Boolean): RequestContext ⇒ Unit = { case ctx @ RequestContext(request, retriesLeft, _) if retry && request.canBeRetried && retriesLeft > 0 ⇒ - log.warning("{} in response to {} with {} retries left, retrying...", errorMsg, format(request), retriesLeft) + log.warning("{} in response to {} with {} retries left, retrying...", error.getMessage, format(request), retriesLeft) context.parent ! ctx.copy(retriesLeft = retriesLeft - 1) case RequestContext(request, _, commander) ⇒ - log.warning("{} in response to {} with no retries left, dispatching error...", errorMsg, format(request)) - commander ! Status.Failure(new RuntimeException(errorMsg)) + log.warning("{} in response to {} with no retries left, dispatching error...", error.getMessage, format(request)) + commander ! Status.Failure(error) } def dispatchToServer(httpConnection: ActorRef)(ctx: RequestContext): Unit = {