From fbc71ee712635ed64c50ca694735a84ec794eb11 Mon Sep 17 00:00:00 2001 From: adamw Date: Thu, 14 Sep 2017 11:03:21 +0100 Subject: Renaming "handler" to "backend" --- .../softwaremill/sttp/FollowRedirectsBackend.scala | 73 ++++++ .../softwaremill/sttp/FollowRedirectsHandler.scala | 73 ------ .../sttp/HttpURLConnectionBackend.scala | 266 +++++++++++++++++++++ .../sttp/HttpURLConnectionHandler.scala | 266 --------------------- .../scala/com/softwaremill/sttp/RequestT.scala | 6 +- .../scala/com/softwaremill/sttp/SttpBackend.scala | 26 ++ .../scala/com/softwaremill/sttp/SttpHandler.scala | 26 -- .../main/scala/com/softwaremill/sttp/package.scala | 2 +- 8 files changed, 369 insertions(+), 369 deletions(-) create mode 100644 core/src/main/scala/com/softwaremill/sttp/FollowRedirectsBackend.scala delete mode 100644 core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala create mode 100644 core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionBackend.scala delete mode 100644 core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala create mode 100644 core/src/main/scala/com/softwaremill/sttp/SttpBackend.scala delete mode 100644 core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala (limited to 'core') diff --git a/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsBackend.scala b/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsBackend.scala new file mode 100644 index 0000000..accd6b5 --- /dev/null +++ b/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsBackend.scala @@ -0,0 +1,73 @@ +package com.softwaremill.sttp + +import java.net.URI +import scala.language.higherKinds + +class FollowRedirectsBackend[R[_], S](delegate: SttpBackend[R, S]) + extends SttpBackend[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 >= FollowRedirectsBackend.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 FollowRedirectsBackend { + private[sttp] val MaxRedirects = 32 +} diff --git a/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala b/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala deleted file mode 100644 index 6fe9ce9..0000000 --- a/core/src/main/scala/com/softwaremill/sttp/FollowRedirectsHandler.scala +++ /dev/null @@ -1,73 +0,0 @@ -package com.softwaremill.sttp - -import java.net.URI -import scala.language.higherKinds - -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/HttpURLConnectionBackend.scala b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionBackend.scala new file mode 100644 index 0000000..2dbb13d --- /dev/null +++ b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionBackend.scala @@ -0,0 +1,266 @@ +package com.softwaremill.sttp + +import java.io._ +import java.net.{HttpURLConnection, URL} +import java.nio.channels.Channels +import java.nio.charset.CharacterCodingException +import java.nio.file.Files +import java.util.concurrent.ThreadLocalRandom +import java.util.zip.{GZIPInputStream, InflaterInputStream} + +import scala.annotation.tailrec +import scala.io.Source +import scala.collection.JavaConverters._ +import scala.concurrent.duration.{Duration, FiniteDuration} + +class HttpURLConnectionBackend private (connectionTimeout: FiniteDuration) + extends SttpBackend[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) + r.headers.foreach { case (k, v) => c.setRequestProperty(k, v) } + c.setDoInput(true) + c.setReadTimeout(timeout(r.options.readTimeout)) + c.setConnectTimeout(timeout(connectionTimeout)) + + // redirects are handled by FollowRedirectsBackend + c.setInstanceFollowRedirects(false) + + if (r.body != NoBody) { + c.setDoOutput(true) + // we need to take care to: + // (1) only call getOutputStream after the headers are set + // (2) call it ony once + writeBody(r.body, c).foreach { os => + os.flush() + os.close() + } + } + + try { + val is = c.getInputStream + readResponse(c, is, r.response) + } catch { + case e: CharacterCodingException => throw e + case e: UnsupportedEncodingException => throw e + case _: IOException if c.getResponseCode != -1 => + readResponse(c, c.getErrorStream, r.response) + } + } + + override def responseMonad: MonadError[Id] = IdMonad + + private def writeBody(body: RequestBody[Nothing], + c: HttpURLConnection): Option[OutputStream] = { + body match { + case NoBody => + // skip + None + + case b: BasicRequestBody => + val os = c.getOutputStream + writeBasicBody(b, os) + Some(os) + + case StreamBody(s) => + // we have an instance of nothing - everything's possible! + None + + case mp: MultipartBody => + setMultipartBody(mp, c) + } + } + + private def timeout(t: Duration): Int = + if (t.isFinite()) t.toMillis.toInt + else 0 + + private def writeBasicBody(body: BasicRequestBody, os: OutputStream): Unit = { + body match { + case StringBody(b, encoding, _) => + val writer = new OutputStreamWriter(os, encoding) + writer.write(b) + // don't close - as this will close the underlying OS and cause errors + // with multi-part + writer.flush() + + case ByteArrayBody(b, _) => + os.write(b) + + case ByteBufferBody(b, _) => + val channel = Channels.newChannel(os) + channel.write(b) + + case InputStreamBody(b, _) => + transfer(b, os) + + case PathBody(b, _) => + Files.copy(b, os) + } + } + + private val BoundaryChars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray + + private def setMultipartBody(mp: MultipartBody, + c: HttpURLConnection): Option[OutputStream] = { + val boundary = { + val tlr = ThreadLocalRandom.current() + List + .fill(32)(BoundaryChars(tlr.nextInt(BoundaryChars.length))) + .mkString + } + + // inspired by: https://github.com/scalaj/scalaj-http/blob/master/src/main/scala/scalaj/http/Http.scala#L542 + val partsWithHeaders = mp.parts.map { p => + val contentDisposition = + s"$ContentDispositionHeader: ${p.contentDispositionHeaderValue}" + val contentTypeHeader = + p.contentType.map(ct => s"$ContentTypeHeader: $ct") + val otherHeaders = p.additionalHeaders.map(h => s"${h._1}: ${h._2}") + val allHeaders = List(contentDisposition) ++ contentTypeHeader.toList ++ otherHeaders + (allHeaders.mkString(CrLf), p) + } + + val dashes = "--" + + val dashesLen = dashes.length.toLong + val crLfLen = CrLf.length.toLong + val boundaryLen = boundary.length.toLong + val finalBoundaryLen = dashesLen + boundaryLen + dashesLen + crLfLen + + // https://stackoverflow.com/questions/31406022/how-is-an-http-multipart-content-length-header-value-calculated + val contentLength = partsWithHeaders + .map { + case (headers, p) => + val bodyLen: Option[Long] = p.body match { + case StringBody(b, encoding, _) => + Some(b.getBytes(encoding).length.toLong) + case ByteArrayBody(b, _) => Some(b.length.toLong) + case ByteBufferBody(b, _) => None + case InputStreamBody(b, _) => None + case PathBody(b, _) => Some(b.toFile.length()) + } + + val headersLen = headers.getBytes(Iso88591).length + + bodyLen.map(bl => + dashesLen + boundaryLen + crLfLen + headersLen + crLfLen + crLfLen + bl + crLfLen) + } + .foldLeft(Option(finalBoundaryLen)) { + case (Some(acc), Some(l)) => Some(acc + l) + case _ => None + } + + c.setRequestProperty(ContentTypeHeader, + "multipart/form-data; boundary=" + boundary) + + contentLength.foreach { cl => + c.setFixedLengthStreamingMode(cl) + c.setRequestProperty(ContentLengthHeader, cl.toString) + } + + var total = 0L + + val os = c.getOutputStream + def writeMeta(s: String): Unit = { + os.write(s.getBytes(Iso88591)) + total += s.getBytes(Iso88591).length.toLong + } + + partsWithHeaders.foreach { + case (headers, p) => + writeMeta(dashes) + writeMeta(boundary) + writeMeta(CrLf) + writeMeta(headers) + writeMeta(CrLf) + writeMeta(CrLf) + writeBasicBody(p.body, os) + writeMeta(CrLf) + } + + // final boundary + writeMeta(dashes) + writeMeta(boundary) + writeMeta(dashes) + writeMeta(CrLf) + + Some(os) + } + + private def readResponse[T]( + c: HttpURLConnection, + is: InputStream, + responseAs: ResponseAs[T, Nothing]): Response[T] = { + + val headers = c.getHeaderFields.asScala.toVector + .filter(_._1 != null) + .flatMap { case (k, vv) => vv.asScala.map((k, _)) } + val contentEncoding = Option(c.getHeaderField(ContentEncodingHeader)) + val code = c.getResponseCode + val wrappedIs = wrapInput(contentEncoding, is) + val body = if (codeIsSuccess(code)) { + Right(readResponseBody(wrappedIs, responseAs)) + } else { + Left(readResponseBody(wrappedIs, asString)) + } + + Response(body, code, headers, Nil) + } + + private def readResponseBody[T](is: InputStream, + responseAs: ResponseAs[T, Nothing]): T = { + + def asString(enc: String) = Source.fromInputStream(is, enc).mkString + + responseAs match { + case MappedResponseAs(raw, g) => g(readResponseBody(is, raw)) + + case IgnoreResponse => + @tailrec def consume(): Unit = if (is.read() != -1) consume() + consume() + + case ResponseAsString(enc) => + asString(enc) + + case ResponseAsByteArray => + val os = new ByteArrayOutputStream + + transfer(is, os) + + os.toByteArray + + case ResponseAsStream() => + // only possible when the user requests the response as a stream of + // Nothing. Oh well ... + throw new IllegalStateException() + + case ResponseAsFile(input, overwrite) => + ResponseAs.saveFile(input, is, overwrite) + + } + } + + private def wrapInput(contentEncoding: Option[String], + is: InputStream): InputStream = + contentEncoding.map(_.toLowerCase) match { + case None => is + case Some("gzip") => new GZIPInputStream(is) + case Some("deflate") => new InflaterInputStream(is) + case Some(ce) => + throw new UnsupportedEncodingException(s"Unsupported encoding: $ce") + } + + override def close(): Unit = {} +} + +object HttpURLConnectionBackend { + + def apply( + connectionTimeout: FiniteDuration = SttpBackend.DefaultConnectionTimeout) + : SttpBackend[Id, Nothing] = + new FollowRedirectsBackend[Id, Nothing]( + new HttpURLConnectionBackend(connectionTimeout)) +} diff --git a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala b/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala deleted file mode 100644 index c482e6d..0000000 --- a/core/src/main/scala/com/softwaremill/sttp/HttpURLConnectionHandler.scala +++ /dev/null @@ -1,266 +0,0 @@ -package com.softwaremill.sttp - -import java.io._ -import java.net.{HttpURLConnection, URL} -import java.nio.channels.Channels -import java.nio.charset.CharacterCodingException -import java.nio.file.Files -import java.util.concurrent.ThreadLocalRandom -import java.util.zip.{GZIPInputStream, InflaterInputStream} - -import scala.annotation.tailrec -import scala.io.Source -import scala.collection.JavaConverters._ -import scala.concurrent.duration.{Duration, FiniteDuration} - -class HttpURLConnectionHandler private (connectionTimeout: FiniteDuration) - 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) - r.headers.foreach { case (k, v) => c.setRequestProperty(k, v) } - c.setDoInput(true) - c.setReadTimeout(timeout(r.options.readTimeout)) - c.setConnectTimeout(timeout(connectionTimeout)) - - // redirects are handled in SttpHandler - c.setInstanceFollowRedirects(false) - - if (r.body != NoBody) { - c.setDoOutput(true) - // we need to take care to: - // (1) only call getOutputStream after the headers are set - // (2) call it ony once - writeBody(r.body, c).foreach { os => - os.flush() - os.close() - } - } - - try { - val is = c.getInputStream - readResponse(c, is, r.response) - } catch { - case e: CharacterCodingException => throw e - case e: UnsupportedEncodingException => throw e - case _: IOException if c.getResponseCode != -1 => - readResponse(c, c.getErrorStream, r.response) - } - } - - override def responseMonad: MonadError[Id] = IdMonad - - private def writeBody(body: RequestBody[Nothing], - c: HttpURLConnection): Option[OutputStream] = { - body match { - case NoBody => - // skip - None - - case b: BasicRequestBody => - val os = c.getOutputStream - writeBasicBody(b, os) - Some(os) - - case StreamBody(s) => - // we have an instance of nothing - everything's possible! - None - - case mp: MultipartBody => - setMultipartBody(mp, c) - } - } - - private def timeout(t: Duration): Int = - if (t.isFinite()) t.toMillis.toInt - else 0 - - private def writeBasicBody(body: BasicRequestBody, os: OutputStream): Unit = { - body match { - case StringBody(b, encoding, _) => - val writer = new OutputStreamWriter(os, encoding) - writer.write(b) - // don't close - as this will close the underlying OS and cause errors - // with multi-part - writer.flush() - - case ByteArrayBody(b, _) => - os.write(b) - - case ByteBufferBody(b, _) => - val channel = Channels.newChannel(os) - channel.write(b) - - case InputStreamBody(b, _) => - transfer(b, os) - - case PathBody(b, _) => - Files.copy(b, os) - } - } - - private val BoundaryChars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".toCharArray - - private def setMultipartBody(mp: MultipartBody, - c: HttpURLConnection): Option[OutputStream] = { - val boundary = { - val tlr = ThreadLocalRandom.current() - List - .fill(32)(BoundaryChars(tlr.nextInt(BoundaryChars.length))) - .mkString - } - - // inspired by: https://github.com/scalaj/scalaj-http/blob/master/src/main/scala/scalaj/http/Http.scala#L542 - val partsWithHeaders = mp.parts.map { p => - val contentDisposition = - s"$ContentDispositionHeader: ${p.contentDispositionHeaderValue}" - val contentTypeHeader = - p.contentType.map(ct => s"$ContentTypeHeader: $ct") - val otherHeaders = p.additionalHeaders.map(h => s"${h._1}: ${h._2}") - val allHeaders = List(contentDisposition) ++ contentTypeHeader.toList ++ otherHeaders - (allHeaders.mkString(CrLf), p) - } - - val dashes = "--" - - val dashesLen = dashes.length.toLong - val crLfLen = CrLf.length.toLong - val boundaryLen = boundary.length.toLong - val finalBoundaryLen = dashesLen + boundaryLen + dashesLen + crLfLen - - // https://stackoverflow.com/questions/31406022/how-is-an-http-multipart-content-length-header-value-calculated - val contentLength = partsWithHeaders - .map { - case (headers, p) => - val bodyLen: Option[Long] = p.body match { - case StringBody(b, encoding, _) => - Some(b.getBytes(encoding).length.toLong) - case ByteArrayBody(b, _) => Some(b.length.toLong) - case ByteBufferBody(b, _) => None - case InputStreamBody(b, _) => None - case PathBody(b, _) => Some(b.toFile.length()) - } - - val headersLen = headers.getBytes(Iso88591).length - - bodyLen.map(bl => - dashesLen + boundaryLen + crLfLen + headersLen + crLfLen + crLfLen + bl + crLfLen) - } - .foldLeft(Option(finalBoundaryLen)) { - case (Some(acc), Some(l)) => Some(acc + l) - case _ => None - } - - c.setRequestProperty(ContentTypeHeader, - "multipart/form-data; boundary=" + boundary) - - contentLength.foreach { cl => - c.setFixedLengthStreamingMode(cl) - c.setRequestProperty(ContentLengthHeader, cl.toString) - } - - var total = 0L - - val os = c.getOutputStream - def writeMeta(s: String): Unit = { - os.write(s.getBytes(Iso88591)) - total += s.getBytes(Iso88591).length.toLong - } - - partsWithHeaders.foreach { - case (headers, p) => - writeMeta(dashes) - writeMeta(boundary) - writeMeta(CrLf) - writeMeta(headers) - writeMeta(CrLf) - writeMeta(CrLf) - writeBasicBody(p.body, os) - writeMeta(CrLf) - } - - // final boundary - writeMeta(dashes) - writeMeta(boundary) - writeMeta(dashes) - writeMeta(CrLf) - - Some(os) - } - - private def readResponse[T]( - c: HttpURLConnection, - is: InputStream, - responseAs: ResponseAs[T, Nothing]): Response[T] = { - - val headers = c.getHeaderFields.asScala.toVector - .filter(_._1 != null) - .flatMap { case (k, vv) => vv.asScala.map((k, _)) } - val contentEncoding = Option(c.getHeaderField(ContentEncodingHeader)) - val code = c.getResponseCode - val wrappedIs = wrapInput(contentEncoding, is) - val body = if (codeIsSuccess(code)) { - Right(readResponseBody(wrappedIs, responseAs)) - } else { - Left(readResponseBody(wrappedIs, asString)) - } - - Response(body, code, headers, Nil) - } - - private def readResponseBody[T](is: InputStream, - responseAs: ResponseAs[T, Nothing]): T = { - - def asString(enc: String) = Source.fromInputStream(is, enc).mkString - - responseAs match { - case MappedResponseAs(raw, g) => g(readResponseBody(is, raw)) - - case IgnoreResponse => - @tailrec def consume(): Unit = if (is.read() != -1) consume() - consume() - - case ResponseAsString(enc) => - asString(enc) - - case ResponseAsByteArray => - val os = new ByteArrayOutputStream - - transfer(is, os) - - os.toByteArray - - case ResponseAsStream() => - // only possible when the user requests the response as a stream of - // Nothing. Oh well ... - throw new IllegalStateException() - - case ResponseAsFile(input, overwrite) => - ResponseAs.saveFile(input, is, overwrite) - - } - } - - private def wrapInput(contentEncoding: Option[String], - is: InputStream): InputStream = - contentEncoding.map(_.toLowerCase) match { - case None => is - case Some("gzip") => new GZIPInputStream(is) - case Some("deflate") => new InflaterInputStream(is) - case Some(ce) => - throw new UnsupportedEncodingException(s"Unsupported encoding: $ce") - } - - override def close(): Unit = {} -} - -object HttpURLConnectionHandler { - - def apply( - connectionTimeout: FiniteDuration = SttpHandler.DefaultConnectionTimeout) - : SttpHandler[Id, Nothing] = - new FollowRedirectsHandler[Id, Nothing]( - new HttpURLConnectionHandler(connectionTimeout)) -} diff --git a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala index 6e76f6f..320efef 100644 --- a/core/src/main/scala/com/softwaremill/sttp/RequestT.scala +++ b/core/src/main/scala/com/softwaremill/sttp/RequestT.scala @@ -16,7 +16,7 @@ import scala.language.higherKinds * client code to consume it. An exception to this are * streaming responses, which need to fully consumed by the * client if such a response type is requested. - * @param tags Request-specific tags which can be used by handlers for + * @param tags Request-specific tags which can be used by backends for * logging, metrics, etc. Not used by default. * @tparam U Specifies if the method & uri are specified. By default can be * either: @@ -233,13 +233,13 @@ case class RequestT[U[_], T, +S]( def tag(k: String): Option[Any] = tags.get(k) - def send[R[_]]()(implicit handler: SttpHandler[R, S], + def send[R[_]]()(implicit backend: SttpBackend[R, S], isIdInRequest: IsIdInRequest[U]): R[Response[T]] = { // we could avoid the asInstanceOf by creating an artificial copy // changing the method & url fields using `isIdInRequest`, but that // would be only to satisfy the type checker, and a needless copy at // runtime. - handler.send(this.asInstanceOf[RequestT[Id, T, S]]) + backend.send(this.asInstanceOf[RequestT[Id, T, S]]) } private def hasContentType: Boolean = diff --git a/core/src/main/scala/com/softwaremill/sttp/SttpBackend.scala b/core/src/main/scala/com/softwaremill/sttp/SttpBackend.scala new file mode 100644 index 0000000..6eb312f --- /dev/null +++ b/core/src/main/scala/com/softwaremill/sttp/SttpBackend.scala @@ -0,0 +1,26 @@ +package com.softwaremill.sttp + +import scala.language.higherKinds +import scala.concurrent.duration._ + +/** + * @tparam R The type constructor in which responses are wrapped. E.g. `Id` + * for synchronous backends, `Future` for asynchronous backends. + * @tparam S The type of streams that are supported by the backend. `Nothing`, + * if streaming requests/responses is not supported by this backend. + */ +trait SttpBackend[R[_], -S] { + def send[T](request: Request[T, S]): R[Response[T]] + + def close(): Unit + + /** + * The monad in which the responses are wrapped. Allows writing wrapper + * backends, which map/flatMap over the return value of [[send]]. + */ + def responseMonad: MonadError[R] +} + +object SttpBackend { + private[sttp] val DefaultConnectionTimeout = 30.seconds +} diff --git a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala deleted file mode 100644 index cb03567..0000000 --- a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala +++ /dev/null @@ -1,26 +0,0 @@ -package com.softwaremill.sttp - -import scala.language.higherKinds -import scala.concurrent.duration._ - -/** - * @tparam R The type constructor in which responses are wrapped. E.g. `Id` - * for synchronous handlers, `Future` for asynchronous handlers. - * @tparam S The type of streams that are supported by the handler. `Nothing`, - * 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 close(): Unit - - /** - * 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 DefaultConnectionTimeout = 30.seconds -} diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala index a9950be..c1178d9 100644 --- a/core/src/main/scala/com/softwaremill/sttp/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/package.scala @@ -23,7 +23,7 @@ package object sttp { /** * Provide an implicit value of this type to serialize arbitrary classes into a request body. - * Handlers might also provide special logic for serializer instances which they define (e.g. to handle streaming). + * Backends might also provide special logic for serializer instances which they define (e.g. to handle streaming). */ type BodySerializer[B] = B => BasicRequestBody -- cgit v1.2.3