diff options
author | adamw <adam@warski.org> | 2017-08-04 13:06:36 +0200 |
---|---|---|
committer | adamw <adam@warski.org> | 2017-08-04 13:06:36 +0200 |
commit | 69d7ee463848a620b80742a8c38c5798af909479 (patch) | |
tree | 53645133d1060960b9c5b5eaf0a4e2112258206c | |
parent | ab566fd5cf0f54e8e69b2917b32e57f2321d49ee (diff) | |
download | sttp-69d7ee463848a620b80742a8c38c5798af909479.tar.gz sttp-69d7ee463848a620b80742a8c38c5798af909479.tar.bz2 sttp-69d7ee463848a620b80742a8c38c5798af909479.zip |
Extracting common logic for handling basic responses eagerly
4 files changed, 80 insertions, 49 deletions
diff --git a/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala b/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala index f5b4569..9043882 100644 --- a/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala +++ b/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala @@ -3,6 +3,7 @@ package com.softwaremill.sttp.asynchttpclient import java.nio.ByteBuffer import java.nio.charset.Charset +import com.softwaremill.sttp.model.ResponseAs.EagerResponseHandler import com.softwaremill.sttp.model._ import com.softwaremill.sttp.{ ContentLengthHeader, @@ -29,6 +30,7 @@ import org.reactivestreams.{Publisher, Subscriber, Subscription} import scala.collection.JavaConverters._ import scala.language.higherKinds +import scala.util.{Failure, Try} abstract class AsyncHttpClientHandler[R[_], S](asyncHttpClient: AsyncHttpClient, rm: MonadAsyncError[R], @@ -187,7 +189,7 @@ abstract class AsyncHttpClientHandler[R[_], S](asyncHttpClient: AsyncHttpClient, private def readEagerResponse[T]( response: AsyncResponse, responseAs: ResponseAs[T, S]): R[Response[T]] = { - val body = readEagerResponseBody(response, responseAs) + val body = eagerResponseHandler(response).handle(responseAs, rm) rm.map(body, (b: T) => readResponseNoBody(response).copy(body = b)) } @@ -201,34 +203,27 @@ abstract class AsyncHttpClientHandler[R[_], S](asyncHttpClient: AsyncHttpClient, .toList) } - private def readEagerResponseBody[T](response: AsyncResponse, - responseAs: ResponseAs[T, S]): R[T] = { - - def asString(enc: String) = response.getResponseBody(Charset.forName(enc)) - - responseAs match { - case MappedResponseAs(raw, g) => - rm.map(readEagerResponseBody(response, raw), g) - - case IgnoreResponse => - // getting the body and discarding it - response.getResponseBodyAsBytes - rm.unit(()) - - case ResponseAsString(enc) => - rm.unit(asString(enc)) - - case ResponseAsByteArray => - rm.unit(response.getResponseBodyAsBytes) - - case ResponseAsStream() => - // only possible when the user requests the response as a stream of - // Nothing. Oh well ... - rm.error( - new IllegalStateException( - "Requested a streaming response, trying to read eagerly.")) + private def eagerResponseHandler(response: AsyncResponse) = + new EagerResponseHandler[S] { + override def handleBasic[T](bra: BasicResponseAs[T, S]): Try[T] = + bra match { + case IgnoreResponse => + // getting the body and discarding it + response.getResponseBodyAsBytes + Try(()) + + case ResponseAsString(enc) => + Try(response.getResponseBody(Charset.forName(enc))) + + case ResponseAsByteArray => + Try(response.getResponseBodyAsBytes) + + case ResponseAsStream() => + Failure( + new IllegalStateException( + "Requested a streaming response, trying to read eagerly.")) + } } - } override def close(): Unit = { if (closeClient) diff --git a/core/src/main/scala/com/softwaremill/sttp/MonadError.scala b/core/src/main/scala/com/softwaremill/sttp/MonadError.scala index e5e7ab8..40cea4b 100644 --- a/core/src/main/scala/com/softwaremill/sttp/MonadError.scala +++ b/core/src/main/scala/com/softwaremill/sttp/MonadError.scala @@ -2,6 +2,7 @@ package com.softwaremill.sttp import scala.concurrent.{ExecutionContext, Future, Promise} import scala.language.higherKinds +import scala.util.Try trait MonadError[R[_]] { def unit[T](t: T): R[T] @@ -10,6 +11,8 @@ trait MonadError[R[_]] { def error[T](t: Throwable): R[T] def flatten[T](ffa: R[R[T]]): R[T] = flatMap[R[T], T](ffa, identity) + + def fromTry[T](t: Try[T]): R[T] = t.fold(error, unit) } trait MonadAsyncError[R[_]] extends MonadError[R] { diff --git a/core/src/main/scala/com/softwaremill/sttp/model/ResponseAs.scala b/core/src/main/scala/com/softwaremill/sttp/model/ResponseAs.scala index cf9bc12..3862483 100644 --- a/core/src/main/scala/com/softwaremill/sttp/model/ResponseAs.scala +++ b/core/src/main/scala/com/softwaremill/sttp/model/ResponseAs.scala @@ -2,24 +2,37 @@ package com.softwaremill.sttp.model import java.net.URLDecoder +import com.softwaremill.sttp.MonadError + import scala.collection.immutable.Seq +import scala.language.higherKinds +import scala.util.Try /** * @tparam T Target type as which the response will be read. * @tparam S If `T` is a stream, the type of the stream. Otherwise, `Nothing`. */ sealed trait ResponseAs[T, +S] { - def map[T2](f: T => T2): ResponseAs[T2, S] = + def map[T2](f: T => T2): ResponseAs[T2, S] +} + +/** + * Response handling specification which isn't derived from another response + * handling method, but needs to be handled directly by the backend. + */ +sealed trait BasicResponseAs[T, +S] extends ResponseAs[T, S] { + override def map[T2](f: (T) => T2): ResponseAs[T2, S] = MappedResponseAs[T, T2, S](this, f) } -case object IgnoreResponse extends ResponseAs[Unit, Nothing] +case object IgnoreResponse extends BasicResponseAs[Unit, Nothing] case class ResponseAsString(encoding: String) - extends ResponseAs[String, Nothing] -case object ResponseAsByteArray extends ResponseAs[Array[Byte], Nothing] + extends BasicResponseAs[String, Nothing] +case object ResponseAsByteArray extends BasicResponseAs[Array[Byte], Nothing] case class ResponseAsStream[T, S]()(implicit val responseIsStream: S =:= T) - extends ResponseAs[T, S] -case class MappedResponseAs[T, T2, S](raw: ResponseAs[T, S], g: T => T2) + extends BasicResponseAs[T, S] + +case class MappedResponseAs[T, T2, S](raw: BasicResponseAs[T, S], g: T => T2) extends ResponseAs[T2, S] { override def map[T3](f: T2 => T3): ResponseAs[T3, S] = MappedResponseAs[T, T3, S](raw, g andThen f) @@ -38,4 +51,25 @@ object ResponseAs { case _ => None }) } + + /** + * Handles responses according to the given specification when basic + * response specifications can be handled eagerly, that is without + * wrapping the result in the target monad (`handleBasic` returns + * `Try[T]`, not `R[T]`). + */ + private[sttp] trait EagerResponseHandler[S] { + def handleBasic[T](bra: BasicResponseAs[T, S]): Try[T] + + def handle[T, R[_]](responseAs: ResponseAs[T, S], + responseMonad: MonadError[R]): R[T] = { + + responseAs match { + case mra @ MappedResponseAs(raw, g) => + responseMonad.map(responseMonad.fromTry(handleBasic(mra.raw)), mra.g) + case bra: BasicResponseAs[T, S] => + responseMonad.fromTry(handleBasic(bra)) + } + } + } } diff --git a/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala b/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala index cb1103b..f57487f 100644 --- a/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala +++ b/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala @@ -4,6 +4,7 @@ import java.io.IOException import java.nio.charset.Charset import com.softwaremill.sttp._ +import com.softwaremill.sttp.model.ResponseAs.EagerResponseHandler import com.softwaremill.sttp.model._ import okhttp3.internal.http.HttpMethod import okhttp3.{ @@ -20,6 +21,7 @@ import okio.{BufferedSink, Okio} import scala.collection.JavaConverters._ import scala.concurrent.{ExecutionContext, Future, Promise} import scala.language.higherKinds +import scala.util.{Failure, Try} abstract class OkHttpClientHandler[R[_], S](client: OkHttpClient) extends SttpHandler[R, S] { @@ -66,7 +68,7 @@ abstract class OkHttpClientHandler[R[_], S](client: OkHttpClient) private[okhttp] def readResponse[T]( res: OkHttpResponse, responseAs: ResponseAs[T, S]): R[Response[T]] = { - val body = readResponseBody(res, responseAs) + val body = responseHandler(res).handle(responseAs, responseMonad) val headers = res .headers() @@ -77,21 +79,18 @@ abstract class OkHttpClientHandler[R[_], S](client: OkHttpClient) responseMonad.map(body, Response(_: T, res.code(), headers.toList)) } - private def readResponseBody[T](res: OkHttpResponse, - responseAs: ResponseAs[T, S]): R[T] = { - responseAs match { - case IgnoreResponse => responseMonad.unit(res.body().close()) - case ResponseAsString(encoding) => - responseMonad.unit( - res.body().source().readString(Charset.forName(encoding))) - case ResponseAsByteArray => responseMonad.unit(res.body().bytes()) - case MappedResponseAs(raw, g) => - responseMonad.map(readResponseBody(res, raw), g) - case ResponseAsStream() => - responseMonad.error( - new IllegalStateException("Streaming isn't supported")) + private def responseHandler(res: OkHttpResponse) = + new EagerResponseHandler[S] { + override def handleBasic[T](bra: BasicResponseAs[T, S]): Try[T] = + bra match { + case IgnoreResponse => Try(res.body().close()) + case ResponseAsString(encoding) => + Try(res.body().source().readString(Charset.forName(encoding))) + case ResponseAsByteArray => Try(res.body().bytes()) + case ResponseAsStream() => + Failure(new IllegalStateException("Streaming isn't supported")) + } } - } } class OkHttpSyncClientHandler private (client: OkHttpClient) |