aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala51
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/MonadError.scala3
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/model/ResponseAs.scala46
-rw-r--r--okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala29
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)