aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoradamw <adam@warski.org>2017-09-04 16:27:43 +0200
committeradamw <adam@warski.org>2017-09-04 16:27:43 +0200
commit2761923dcf1ea49f7671cc2f18df9bd07f0df40b (patch)
tree1e20c4d823f0ff734d77c54e96ab738f4186c740
parent41bbe08c125bb3714aee862ec9dc55419c29986d (diff)
downloadsttp-2761923dcf1ea49f7671cc2f18df9bd07f0df40b.tar.gz
sttp-2761923dcf1ea49f7671cc2f18df9bd07f0df40b.tar.bz2
sttp-2761923dcf1ea49f7671cc2f18df9bd07f0df40b.zip
Extracting FollowRedirectsHandler as a wrapper-handler
-rw-r--r--akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpHandler.scala19
-rw-r--r--async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHandler.scala21
-rw-r--r--async-http-client-handler/fs2/src/main/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2Handler.scala17
-rw-r--r--async-http-client-handler/future/src/main/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHandler.scala17
-rw-r--r--async-http-client-handler/monix/src/main/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHandler.scala17
-rw-r--r--async-http-client-handler/scalaz/src/main/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHandler.scala21
-rw-r--r--async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala2
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala72
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala4
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala65
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/package.scala5
-rw-r--r--okhttp-handler/monix/src/main/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHandler.scala2
-rw-r--r--okhttp-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala9
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala2
14 files changed, 166 insertions, 107 deletions
diff --git a/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpHandler.scala b/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpHandler.scala
index a2695e2..2aa4251 100644
--- a/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpHandler.scala
+++ b/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpHandler.scala
@@ -30,11 +30,11 @@ class AkkaHttpHandler private (actorSystem: ActorSystem,
// the supported stream type
private type S = Source[ByteString, Any]
- private implicit val as = actorSystem
- private implicit val materializer = ActorMaterializer()
+ private implicit val as: ActorSystem = actorSystem
+ private implicit val materializer: ActorMaterializer = ActorMaterializer()
- override protected def doSend[T](r: Request[T, S]): Future[Response[T]] = {
- implicit val ec = this.ec
+ override def send[T](r: Request[T, S]): Future[Response[T]] = {
+ implicit val ec: ExecutionContext = this.ec
requestToAkka(r)
.flatMap(setBodyOnAkka(r, r.body, _))
.toFuture
@@ -269,6 +269,13 @@ class AkkaHttpHandler private (actorSystem: ActorSystem,
object AkkaHttpHandler {
+ private def apply(actorSystem: ActorSystem,
+ ec: ExecutionContext,
+ terminateActorSystemOnClose: Boolean)
+ : SttpHandler[Future, Source[ByteString, Any]] =
+ new FollowRedirectsHandler(
+ new AkkaHttpHandler(actorSystem, ec, terminateActorSystemOnClose))
+
/**
* @param ec The execution context for running non-network related operations,
* e.g. mapping responses. Defaults to the global execution
@@ -276,7 +283,7 @@ object AkkaHttpHandler {
*/
def apply()(implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[Future, Source[ByteString, Any]] =
- new AkkaHttpHandler(ActorSystem("sttp"), ec, true)
+ AkkaHttpHandler(ActorSystem("sttp"), ec, terminateActorSystemOnClose = true)
/**
* @param actorSystem The actor system which will be used for the http-client
@@ -288,5 +295,5 @@ object AkkaHttpHandler {
def usingActorSystem(actorSystem: ActorSystem)(
implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[Future, Source[ByteString, Any]] =
- new AkkaHttpHandler(actorSystem, ec, false)
+ AkkaHttpHandler(actorSystem, ec, terminateActorSystemOnClose = false)
}
diff --git a/async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHandler.scala b/async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHandler.scala
index a2c374a..18949c0 100644
--- a/async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHandler.scala
+++ b/async-http-client-handler/cats/src/main/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHandler.scala
@@ -4,7 +4,11 @@ import java.nio.ByteBuffer
import cats.effect._
import com.softwaremill.sttp.asynchttpclient.AsyncHttpClientHandler
-import com.softwaremill.sttp.{MonadAsyncError, SttpHandler}
+import com.softwaremill.sttp.{
+ FollowRedirectsHandler,
+ MonadAsyncError,
+ SttpHandler
+}
import org.asynchttpclient.{
AsyncHttpClient,
AsyncHttpClientConfig,
@@ -36,18 +40,23 @@ class AsyncHttpClientCatsHandler[F[_]: Async] private (
object AsyncHttpClientCatsHandler {
+ private def apply[F[_]: Async](
+ asyncHttpClient: AsyncHttpClient,
+ closeClient: Boolean): SttpHandler[F, Nothing] =
+ new FollowRedirectsHandler[F, Nothing](
+ new AsyncHttpClientCatsHandler(asyncHttpClient, closeClient))
+
def apply[F[_]: Async](): SttpHandler[F, Nothing] =
- new AsyncHttpClientCatsHandler(new DefaultAsyncHttpClient(),
- closeClient = true)
+ AsyncHttpClientCatsHandler(new DefaultAsyncHttpClient(), closeClient = true)
def usingConfig[F[_]: Async](
cfg: AsyncHttpClientConfig): SttpHandler[F, Nothing] =
- new AsyncHttpClientCatsHandler(new DefaultAsyncHttpClient(cfg),
- closeClient = true)
+ AsyncHttpClientCatsHandler(new DefaultAsyncHttpClient(cfg),
+ closeClient = true)
def usingClient[F[_]: Async](
client: AsyncHttpClient): SttpHandler[F, Nothing] =
- new AsyncHttpClientCatsHandler(client, closeClient = false)
+ AsyncHttpClientCatsHandler(client, closeClient = false)
}
private[cats] class AsyncMonad[F[_]](implicit F: Async[F])
diff --git a/async-http-client-handler/fs2/src/main/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2Handler.scala b/async-http-client-handler/fs2/src/main/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2Handler.scala
index 1686b25..56e7b8c 100644
--- a/async-http-client-handler/fs2/src/main/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2Handler.scala
+++ b/async-http-client-handler/fs2/src/main/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2Handler.scala
@@ -5,6 +5,7 @@ import java.nio.ByteBuffer
import cats.effect._
import com.softwaremill.sttp.asynchttpclient.AsyncHttpClientHandler
import com.softwaremill.sttp.{
+ FollowRedirectsHandler,
MonadAsyncError,
SttpHandler,
Utf8,
@@ -51,6 +52,12 @@ class AsyncHttpClientFs2Handler[F[_]: Effect] private (
object AsyncHttpClientFs2Handler {
+ private def apply[F[_]: Effect](asyncHttpClient: AsyncHttpClient,
+ closeClient: Boolean)(
+ implicit ec: ExecutionContext): SttpHandler[F, Stream[F, ByteBuffer]] =
+ new FollowRedirectsHandler(
+ new AsyncHttpClientFs2Handler(asyncHttpClient, closeClient))
+
/**
* @param ec The execution context for running non-network related operations,
* e.g. mapping responses. Defaults to the global execution
@@ -59,8 +66,8 @@ object AsyncHttpClientFs2Handler {
def apply[F[_]: Effect]()(
implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[F, Stream[F, ByteBuffer]] =
- new AsyncHttpClientFs2Handler[F](new DefaultAsyncHttpClient(),
- closeClient = true)
+ AsyncHttpClientFs2Handler[F](new DefaultAsyncHttpClient(),
+ closeClient = true)
/**
* @param ec The execution context for running non-network related operations,
@@ -70,8 +77,8 @@ object AsyncHttpClientFs2Handler {
def usingConfig[F[_]: Effect](cfg: AsyncHttpClientConfig)(
implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[F, Stream[F, ByteBuffer]] =
- new AsyncHttpClientFs2Handler[F](new DefaultAsyncHttpClient(cfg),
- closeClient = true)
+ AsyncHttpClientFs2Handler[F](new DefaultAsyncHttpClient(cfg),
+ closeClient = true)
/**
* @param ec The execution context for running non-network related operations,
@@ -81,7 +88,7 @@ object AsyncHttpClientFs2Handler {
def usingClient[F[_]: Effect](client: AsyncHttpClient)(
implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[F, Stream[F, ByteBuffer]] =
- new AsyncHttpClientFs2Handler[F](client, closeClient = false)
+ AsyncHttpClientFs2Handler[F](client, closeClient = false)
}
private[fs2] class EffectMonad[F[_]](implicit F: Effect[F])
diff --git a/async-http-client-handler/future/src/main/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHandler.scala b/async-http-client-handler/future/src/main/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHandler.scala
index 81dee9e..b80a91e 100644
--- a/async-http-client-handler/future/src/main/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHandler.scala
+++ b/async-http-client-handler/future/src/main/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHandler.scala
@@ -3,7 +3,7 @@ package com.softwaremill.sttp.asynchttpclient.future
import java.nio.ByteBuffer
import com.softwaremill.sttp.asynchttpclient.AsyncHttpClientHandler
-import com.softwaremill.sttp.{FutureMonad, SttpHandler}
+import com.softwaremill.sttp.{FollowRedirectsHandler, FutureMonad, SttpHandler}
import org.asynchttpclient.{
AsyncHttpClient,
AsyncHttpClientConfig,
@@ -34,6 +34,11 @@ class AsyncHttpClientFutureHandler private (
object AsyncHttpClientFutureHandler {
+ private def apply(asyncHttpClient: AsyncHttpClient, closeClient: Boolean)(
+ implicit ec: ExecutionContext): SttpHandler[Future, Nothing] =
+ new FollowRedirectsHandler[Future, Nothing](
+ new AsyncHttpClientFutureHandler(asyncHttpClient, closeClient))
+
/**
* @param ec The execution context for running non-network related operations,
* e.g. mapping responses. Defaults to the global execution
@@ -41,8 +46,8 @@ object AsyncHttpClientFutureHandler {
*/
def apply()(implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[Future, Nothing] =
- new AsyncHttpClientFutureHandler(new DefaultAsyncHttpClient(),
- closeClient = true)
+ AsyncHttpClientFutureHandler(new DefaultAsyncHttpClient(),
+ closeClient = true)
/**
* @param ec The execution context for running non-network related operations,
@@ -52,8 +57,8 @@ object AsyncHttpClientFutureHandler {
def usingConfig(cfg: AsyncHttpClientConfig)(
implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[Future, Nothing] =
- new AsyncHttpClientFutureHandler(new DefaultAsyncHttpClient(cfg),
- closeClient = true)
+ AsyncHttpClientFutureHandler(new DefaultAsyncHttpClient(cfg),
+ closeClient = true)
/**
* @param ec The execution context for running non-network related operations,
@@ -63,5 +68,5 @@ object AsyncHttpClientFutureHandler {
def usingClient(client: AsyncHttpClient)(implicit ec: ExecutionContext =
ExecutionContext.Implicits.global)
: SttpHandler[Future, Nothing] =
- new AsyncHttpClientFutureHandler(client, closeClient = false)
+ AsyncHttpClientFutureHandler(client, closeClient = false)
}
diff --git a/async-http-client-handler/monix/src/main/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHandler.scala b/async-http-client-handler/monix/src/main/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHandler.scala
index 786f176..311d3ea 100644
--- a/async-http-client-handler/monix/src/main/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHandler.scala
+++ b/async-http-client-handler/monix/src/main/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHandler.scala
@@ -3,6 +3,7 @@ package com.softwaremill.sttp.asynchttpclient.monix
import java.nio.ByteBuffer
import com.softwaremill.sttp.{
+ FollowRedirectsHandler,
MonadAsyncError,
SttpHandler,
Utf8,
@@ -50,14 +51,20 @@ class AsyncHttpClientMonixHandler private (
object AsyncHttpClientMonixHandler {
+ private def apply(asyncHttpClient: AsyncHttpClient, closeClient: Boolean)(
+ implicit scheduler: Scheduler)
+ : SttpHandler[Task, Observable[ByteBuffer]] =
+ new FollowRedirectsHandler(
+ new AsyncHttpClientMonixHandler(asyncHttpClient, closeClient))
+
/**
* @param s The scheduler used for streaming request bodies. Defaults to the
* global scheduler.
*/
def apply()(implicit s: Scheduler = Scheduler.Implicits.global)
: SttpHandler[Task, Observable[ByteBuffer]] =
- new AsyncHttpClientMonixHandler(new DefaultAsyncHttpClient(),
- closeClient = true)
+ AsyncHttpClientMonixHandler(new DefaultAsyncHttpClient(),
+ closeClient = true)
/**
* @param s The scheduler used for streaming request bodies. Defaults to the
@@ -66,8 +73,8 @@ object AsyncHttpClientMonixHandler {
def usingConfig(cfg: AsyncHttpClientConfig)(implicit s: Scheduler =
Scheduler.Implicits.global)
: SttpHandler[Task, Observable[ByteBuffer]] =
- new AsyncHttpClientMonixHandler(new DefaultAsyncHttpClient(cfg),
- closeClient = true)
+ AsyncHttpClientMonixHandler(new DefaultAsyncHttpClient(cfg),
+ closeClient = true)
/**
* @param s The scheduler used for streaming request bodies. Defaults to the
@@ -76,7 +83,7 @@ object AsyncHttpClientMonixHandler {
def usingClient(client: AsyncHttpClient)(implicit s: Scheduler =
Scheduler.Implicits.global)
: SttpHandler[Task, Observable[ByteBuffer]] =
- new AsyncHttpClientMonixHandler(client, closeClient = false)
+ AsyncHttpClientMonixHandler(client, closeClient = false)
}
private[monix] object TaskMonad extends MonadAsyncError[Task] {
diff --git a/async-http-client-handler/scalaz/src/main/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHandler.scala b/async-http-client-handler/scalaz/src/main/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHandler.scala
index aea6c0e..b470606 100644
--- a/async-http-client-handler/scalaz/src/main/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHandler.scala
+++ b/async-http-client-handler/scalaz/src/main/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHandler.scala
@@ -2,7 +2,11 @@ package com.softwaremill.sttp.asynchttpclient.scalaz
import java.nio.ByteBuffer
-import com.softwaremill.sttp.{MonadAsyncError, SttpHandler}
+import com.softwaremill.sttp.{
+ FollowRedirectsHandler,
+ MonadAsyncError,
+ SttpHandler
+}
import com.softwaremill.sttp.asynchttpclient.AsyncHttpClientHandler
import org.asynchttpclient.{
AsyncHttpClient,
@@ -33,14 +37,19 @@ class AsyncHttpClientScalazHandler private (asyncHttpClient: AsyncHttpClient,
}
object AsyncHttpClientScalazHandler {
+ private def apply(asyncHttpClient: AsyncHttpClient,
+ closeClient: Boolean): SttpHandler[Task, Nothing] =
+ new FollowRedirectsHandler[Task, Nothing](
+ new AsyncHttpClientScalazHandler(asyncHttpClient, closeClient))
+
def apply(): SttpHandler[Task, Nothing] =
- new AsyncHttpClientScalazHandler(new DefaultAsyncHttpClient(),
- closeClient = true)
+ AsyncHttpClientScalazHandler(new DefaultAsyncHttpClient(),
+ closeClient = true)
def usingConfig(cfg: AsyncHttpClientConfig): SttpHandler[Task, Nothing] =
- new AsyncHttpClientScalazHandler(new DefaultAsyncHttpClient(cfg),
- closeClient = true)
+ AsyncHttpClientScalazHandler(new DefaultAsyncHttpClient(cfg),
+ closeClient = true)
def usingClient(client: AsyncHttpClient): SttpHandler[Task, Nothing] =
- new AsyncHttpClientScalazHandler(client, closeClient = false)
+ AsyncHttpClientScalazHandler(client, closeClient = false)
}
private[scalaz] object TaskMonad extends MonadAsyncError[Task] {
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 06124b7..b6c9249 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
@@ -36,7 +36,7 @@ abstract class AsyncHttpClientHandler[R[_], S](asyncHttpClient: AsyncHttpClient,
closeClient: Boolean)
extends SttpHandler[R, S] {
- override protected def doSend[T](r: Request[T, S]): R[Response[T]] = {
+ override def send[T](r: Request[T, S]): R[Response[T]] = {
val preparedRequest = asyncHttpClient
.prepareRequest(requestToAsync(r))
diff --git a/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala b/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala
new file mode 100644
index 0000000..12aa0a8
--- /dev/null
+++ b/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala
@@ -0,0 +1,72 @@
+package com.softwaremill.sttp
+
+import java.net.URI
+
+class FollowRedirectsHandler[R[_], S](delegate: SttpHandler[R, S])
+ extends SttpHandler[R, S] {
+
+ def send[T](request: Request[T, S]): R[Response[T]] = {
+ sendWithCounter(request, 0)
+ }
+
+ private def sendWithCounter[T](request: Request[T, S],
+ redirects: Int): R[Response[T]] = {
+ val resp = delegate.send(request)
+ if (request.options.followRedirects) {
+ responseMonad.flatMap(resp) { response: Response[T] =>
+ if (response.isRedirect) {
+ followRedirect(request, response, redirects)
+ } else {
+ responseMonad.unit(response)
+ }
+ }
+ } else {
+ resp
+ }
+ }
+
+ private def followRedirect[T](request: Request[T, S],
+ response: Response[T],
+ redirects: Int): R[Response[T]] = {
+
+ response.header(LocationHeader).fold(responseMonad.unit(response)) { loc =>
+ if (redirects >= FollowRedirectsHandler.MaxRedirects) {
+ responseMonad.unit(Response(Left("Too many redirects"), 0, Nil, Nil))
+ } else {
+ followRedirect(request, response, redirects, loc)
+ }
+ }
+ }
+
+ private def followRedirect[T](request: Request[T, S],
+ response: Response[T],
+ redirects: Int,
+ loc: String): R[Response[T]] = {
+
+ def isRelative(uri: String) = !uri.contains("://")
+
+ val uri = if (isRelative(loc)) {
+ // using java's URI to resolve a relative URI
+ uri"${new URI(request.uri.toString).resolve(loc).toString}"
+ } else {
+ uri"$loc"
+ }
+
+ val redirectResponse =
+ sendWithCounter(request.copy[Id, T, S](uri = uri), redirects + 1)
+
+ responseMonad.map(redirectResponse) { rr =>
+ val responseNoBody =
+ response.copy(body = response.body.right.map(_ => ()))
+ rr.copy(history = responseNoBody :: rr.history)
+ }
+ }
+
+ override def close(): Unit = delegate.close()
+
+ override def responseMonad: MonadError[R] = delegate.responseMonad
+}
+
+object FollowRedirectsHandler {
+ private[sttp] val MaxRedirects = 32
+}
diff --git a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala
index ab10261..45a0448 100644
--- a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala
+++ b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala
@@ -12,8 +12,8 @@ import scala.annotation.tailrec
import scala.io.Source
import scala.collection.JavaConverters._
-object HttpURLConnectionHandler extends SttpHandler[Id, Nothing] {
- override protected def doSend[T](r: Request[T, Nothing]): Response[T] = {
+class HttpURLConnectionHandler extends SttpHandler[Id, Nothing] {
+ override def send[T](r: Request[T, Nothing]): Response[T] = {
val c =
new URL(r.uri.toString).openConnection().asInstanceOf[HttpURLConnection]
c.setRequestMethod(r.method.m)
diff --git a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala
index cc34102..248356d 100644
--- a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala
+++ b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala
@@ -1,7 +1,5 @@
package com.softwaremill.sttp
-import java.net.URI
-
import scala.language.higherKinds
/**
@@ -11,74 +9,13 @@ import scala.language.higherKinds
* if streaming requests/responses is not supported by this handler.
*/
trait SttpHandler[R[_], -S] {
- def send[T](request: Request[T, S]): R[Response[T]] = {
- sendWithCounter(request, 0)
- }
-
- private def sendWithCounter[T](request: Request[T, S],
- redirects: Int): R[Response[T]] = {
- val resp = doSend(request)
- if (request.options.followRedirects) {
- responseMonad.flatMap(resp) { response: Response[T] =>
- if (response.isRedirect) {
- followRedirect(request, response, redirects)
- } else {
- responseMonad.unit(response)
- }
- }
- } else {
- resp
- }
- }
-
- private def followRedirect[T](request: Request[T, S],
- response: Response[T],
- redirects: Int): R[Response[T]] = {
-
- response.header(LocationHeader).fold(responseMonad.unit(response)) { loc =>
- if (redirects >= SttpHandler.MaxRedirects) {
- responseMonad.unit(Response(Left("Too many redirects"), 0, Nil, Nil))
- } else {
- followRedirect(request, response, redirects, loc)
- }
- }
- }
-
- private def followRedirect[T](request: Request[T, S],
- response: Response[T],
- redirects: Int,
- loc: String): R[Response[T]] = {
-
- def isRelative(uri: String) = !uri.contains("://")
-
- val uri = if (isRelative(loc)) {
- // using java's URI to resolve a relative URI
- uri"${new URI(request.uri.toString).resolve(loc).toString}"
- } else {
- uri"$loc"
- }
-
- val redirectResponse =
- sendWithCounter(request.copy[Id, T, S](uri = uri), redirects + 1)
-
- responseMonad.map(redirectResponse) { rr =>
- val responseNoBody =
- response.copy(body = response.body.right.map(_ => ()))
- rr.copy(history = responseNoBody :: rr.history)
- }
- }
+ def send[T](request: Request[T, S]): R[Response[T]]
def close(): Unit
- protected def doSend[T](request: Request[T, S]): R[Response[T]]
-
/**
* The monad in which the responses are wrapped. Allows writing wrapper
* handlers, which map/flatMap over the return value of [[send]].
*/
def responseMonad: MonadError[R]
}
-
-object SttpHandler {
- private[sttp] val MaxRedirects = 32
-}
diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala
index dc51ab2..d64acfe 100644
--- a/core/src/main/scala/com/softwaremill/sttp/package.scala
+++ b/core/src/main/scala/com/softwaremill/sttp/package.scala
@@ -266,4 +266,9 @@ package object sttp {
implicit class UriContext(val sc: StringContext) extends AnyVal {
def uri(args: Any*): Uri = UriInterpolator.interpolate(sc, args: _*)
}
+
+ // default handler
+
+ val HttpURLConnectionHandler: SttpHandler[Id, Nothing] =
+ new FollowRedirectsHandler[Id, Nothing](new HttpURLConnectionHandler())
}
diff --git a/okhttp-handler/monix/src/main/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHandler.scala b/okhttp-handler/monix/src/main/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHandler.scala
index 7a5c7cb..bcca7c4 100644
--- a/okhttp-handler/monix/src/main/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHandler.scala
+++ b/okhttp-handler/monix/src/main/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHandler.scala
@@ -81,7 +81,7 @@ object OkHttpMonixHandler {
okhttpClient: OkHttpClient = OkHttpHandler.buildClientNoRedirects())(
implicit s: Scheduler = Scheduler.Implicits.global)
: SttpHandler[Task, Observable[ByteBuffer]] =
- new OkHttpMonixHandler(okhttpClient)(s)
+ new FollowRedirectsHandler(new OkHttpMonixHandler(okhttpClient)(s))
}
private[monix] object TaskMonad extends MonadAsyncError[Task] {
diff --git a/okhttp-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala b/okhttp-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala
index 5574417..327a752 100644
--- a/okhttp-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala
+++ b/okhttp-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala
@@ -148,7 +148,7 @@ object OkHttpHandler {
class OkHttpSyncHandler private (client: OkHttpClient)
extends OkHttpHandler[Id, Nothing](client) {
- override protected def doSend[T](r: Request[T, Nothing]): Response[T] = {
+ override def send[T](r: Request[T, Nothing]): Response[T] = {
val request = convertRequest(r)
val response = client.newCall(request).execute()
readResponse(response, r.response)
@@ -160,13 +160,13 @@ class OkHttpSyncHandler private (client: OkHttpClient)
object OkHttpSyncHandler {
def apply(okhttpClient: OkHttpClient = OkHttpHandler.buildClientNoRedirects())
: SttpHandler[Id, Nothing] =
- new OkHttpSyncHandler(okhttpClient)
+ new FollowRedirectsHandler[Id, Nothing](new OkHttpSyncHandler(okhttpClient))
}
abstract class OkHttpAsyncHandler[R[_], S](client: OkHttpClient,
rm: MonadAsyncError[R])
extends OkHttpHandler[R, S](client) {
- override protected def doSend[T](r: Request[T, S]): R[Response[T]] = {
+ override def send[T](r: Request[T, S]): R[Response[T]] = {
val request = convertRequest(r)
rm.flatten(rm.async[R[Response[T]]] { cb =>
@@ -198,5 +198,6 @@ object OkHttpFutureHandler {
okhttpClient: OkHttpClient = OkHttpHandler.buildClientNoRedirects())(
implicit ec: ExecutionContext = ExecutionContext.Implicits.global)
: SttpHandler[Future, Nothing] =
- new OkHttpFutureHandler(okhttpClient)
+ new FollowRedirectsHandler[Future, Nothing](
+ new OkHttpFutureHandler(okhttpClient))
}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
index d43c43f..aaa6f4c 100644
--- a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
+++ b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
@@ -630,7 +630,7 @@ class BasicTests
name should "break redirect loops" in {
val resp = loop.send().force()
resp.code should be(0)
- resp.history should have size (SttpHandler.MaxRedirects)
+ resp.history should have size (FollowRedirectsHandler.MaxRedirects)
}
}
}