From 5bc89ddefab16dd814d0b716a72490451b697b32 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 31 Aug 2017 12:26:46 +0200 Subject: Follow-redirect support --- .../sttp/HttpURLConnectionHandler.scala | 5 ++- .../scala/com/softwaremill/sttp/RequestT.scala | 8 ++++- .../scala/com/softwaremill/sttp/SttpHandler.scala | 36 +++++++++++++++++++++- .../main/scala/com/softwaremill/sttp/package.scala | 8 ++++- 4 files changed, 53 insertions(+), 4 deletions(-) (limited to 'core') diff --git a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala index 76d897b..548dd9b 100644 --- a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala +++ b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala @@ -13,13 +13,16 @@ import scala.io.Source import scala.collection.JavaConverters._ object HttpURLConnectionHandler extends SttpHandler[Id, Nothing] { - override def send[T](r: Request[T, Nothing]): Response[T] = { + override protected def doSend[T](r: Request[T, Nothing]): Response[T] = { val c = new URL(r.uri.toString).openConnection().asInstanceOf[HttpURLConnection] c.setRequestMethod(r.method.m) r.headers.foreach { case (k, v) => c.setRequestProperty(k, v) } c.setDoInput(true) + // redirects are handled in SttpHandler + c.setInstanceFollowRedirects(false) + if (r.body != NoBody) { c.setDoOutput(true) // we need to take care to: diff --git a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala index 5e4d33e..95dc56b 100644 --- a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala +++ b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala @@ -31,7 +31,8 @@ case class RequestT[U[_], T, +S]( uri: U[Uri], body: RequestBody[S], headers: Seq[(String, String)], - response: ResponseAs[T, S] + response: ResponseAs[T, S], + options: RequestOptions ) { def get(uri: Uri): Request[T, S] = this.copy[Id, T, S](uri = uri, method = Method.GET) @@ -218,6 +219,9 @@ case class RequestT[U[_], T, +S]( def mapResponse[T2](f: T => T2): RequestT[U, T2, S] = this.copy(response = response.map(f)) + def followRedirects(fr: Boolean): RequestT[U, T, S] = + this.copy(options = options.copy(followRedirects = fr)) + def send[R[_]]()(implicit handler: SttpHandler[R, S], isIdInRequest: IsIdInRequest[U]): R[Response[T]] = { // we could avoid the asInstanceOf by creating an artificial copy @@ -268,3 +272,5 @@ class SpecifyAuthScheme[U[_], T, +S](hn: String, rt: RequestT[U, T, S]) { def bearer(token: String): RequestT[U, T, S] = rt.header(hn, s"Bearer $token") } + +case class RequestOptions(followRedirects: Boolean) diff --git a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala index c6df151..fd836bd 100644 --- a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala +++ b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala @@ -1,5 +1,7 @@ package com.softwaremill.sttp +import java.net.URI + import scala.language.higherKinds /** @@ -9,9 +11,41 @@ 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]] + def send[T](request: Request[T, S]): R[Response[T]] = { + val resp = doSend(request) + if (request.options.followRedirects) { + responseMonad.flatMap(resp, { response: Response[T] => + if (response.isRedirect) { + followRedirect(request, response) + } else { + responseMonad.unit(response) + } + }) + } else { + resp + } + } + + private def followRedirect[T](request: Request[T, S], + response: Response[T]): R[Response[T]] = { + def isRelative(uri: String) = !uri.contains("://") + + response.header(LocationHeader).fold(responseMonad.unit(response)) { loc => + 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" + } + + send(request.copy[Id, T, S](uri = uri)) + } + } + 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]]. diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala index a0a6c57..3c4e844 100644 --- a/core/src/main/scala/com/softwaremill/sttp/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/package.scala @@ -37,6 +37,7 @@ package object sttp { private[sttp] val AcceptEncodingHeader = "Accept-Encoding" private[sttp] val ContentEncodingHeader = "Content-Encoding" private[sttp] val ContentDispositionHeader = "Content-Disposition" + private[sttp] val LocationHeader = "Location" private[sttp] val Utf8 = "utf-8" private[sttp] val Iso88591 = "iso-8859-1" private[sttp] val CrLf = "\r\n" @@ -54,7 +55,12 @@ package object sttp { * An empty request with no headers. */ val emptyRequest: RequestT[Empty, String, Nothing] = - RequestT[Empty, String, Nothing](None, None, NoBody, Vector(), asString) + RequestT[Empty, String, Nothing](None, + None, + NoBody, + Vector(), + asString, + RequestOptions(followRedirects = true)) /** * A starting request, with the following modifications comparing to -- cgit v1.2.3