From 09da476faedc382d7f2604be142a353e5851a1e3 Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 4 Jul 2017 13:46:27 +0200 Subject: First version of unified send --- .../sttp/HttpConnectionSttpHandler.scala | 6 +- .../scala/com/softwaremill/sttp/SttpHandler.scala | 33 ++++++++-- .../com/softwaremill/sttp/model/package.scala | 14 +++-- .../main/scala/com/softwaremill/sttp/package.scala | 72 ++++------------------ 4 files changed, 53 insertions(+), 72 deletions(-) (limited to 'core/src') diff --git a/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala index 362bdab..f7292d7 100644 --- a/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala +++ b/core/src/main/scala/com/softwaremill/sttp/HttpConnectionSttpHandler.scala @@ -10,8 +10,8 @@ import com.softwaremill.sttp.model._ import scala.annotation.tailrec import scala.io.Source -object HttpConnectionSttpHandler extends SttpHandler[Id] { - override def send[T](r: Request, responseAs: ResponseAs[T]): Response[T] = { +object HttpConnectionSttpHandler extends SttpHandler[Id, Any, ResponseAsBasic] { + override def send[T](r: Request, responseAs: ResponseAsBasic[T, Any]): Response[T] = { val c = r.uri.toURL.openConnection().asInstanceOf[HttpURLConnection] c.setRequestMethod(r.method.m) r.headers.foreach { case (k, v) => c.setRequestProperty(k, v) } @@ -68,7 +68,7 @@ object HttpConnectionSttpHandler extends SttpHandler[Id] { } } - private def readResponse[T](is: InputStream, responseAs: ResponseAs[T]): T = responseAs match { + private def readResponse[T](is: InputStream, responseAs: ResponseAsBasic[T, Any]): T = responseAs match { case IgnoreResponse => @tailrec def consume(): Unit = if (is.read() != -1) consume() consume() diff --git a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala index 05c7199..50ccd35 100644 --- a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala +++ b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala @@ -4,10 +4,33 @@ import com.softwaremill.sttp.model.{ResponseAs, ResponseAsStream} import scala.language.higherKinds -trait SttpHandler[R[_]] { - def send[T](request: Request, responseAs: ResponseAs[T]): R[Response[T]] +trait SttpHandler[R[_], +S, -AcceptsResponseAs[x, -s] <: ResponseAs[x, s]] { + def send[T](request: Request, responseAs: AcceptsResponseAs[T, S]): R[Response[T]] } -trait SttpStreamHandler[R[_], S] extends SttpHandler[R] { - def send(request: Request, responseAsStream: ResponseAsStream[S]): R[Response[S]] -} \ No newline at end of file +//trait SttpStreamHandler[R[_], S] extends SttpHandler[R] { +// def send(request: Request, responseAsStream: ResponseAsStream[S]): R[Response[S]] +//} + + +/* + +Cat <: Animal +Dog <: Animal + +x: Animal := Cat + +Contravariant: +def eat(x: Cat) := def eat(x: Animal) +Animal => Cat := Animal => Unit + +Covariant: +def create: Animal := def create: Cat +Unit => Animal := Unit => Cat + +--- + +RA >: RAS +Handler[RAS] >: Handler[RA] + + */ \ No newline at end of file diff --git a/core/src/main/scala/com/softwaremill/sttp/model/package.scala b/core/src/main/scala/com/softwaremill/sttp/model/package.scala index e50426c..3c0e563 100644 --- a/core/src/main/scala/com/softwaremill/sttp/model/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/model/package.scala @@ -4,6 +4,8 @@ import java.io.{File, InputStream} import java.nio.ByteBuffer import java.nio.file.Path +import scala.language.higherKinds + package object model { case class Method(m: String) extends AnyVal object Method { @@ -36,11 +38,13 @@ package object model { case class FileBody(f: File) extends BasicRequestBody case class PathBody(f: Path) extends BasicRequestBody - sealed trait ResponseAs[T] - object IgnoreResponse extends ResponseAs[Unit] - case class ResponseAsString(encoding: String) extends ResponseAs[String] - object ResponseAsByteArray extends ResponseAs[Array[Byte]] + sealed trait ResponseAs[T, -S] + + sealed trait ResponseAsBasic[T, -S <: Any] extends ResponseAs[T, S] + object IgnoreResponse extends ResponseAsBasic[Unit, Any] + case class ResponseAsString(encoding: String) extends ResponseAsBasic[String, Any] + object ResponseAsByteArray extends ResponseAsBasic[Array[Byte], Any] // response as params - case class ResponseAsStream[S]() + case class ResponseAsStream[T, S]()(implicit val x: S =:= T) extends ResponseAs[T, S] } diff --git a/core/src/main/scala/com/softwaremill/sttp/package.scala b/core/src/main/scala/com/softwaremill/sttp/package.scala index 40b8d82..e4f5311 100644 --- a/core/src/main/scala/com/softwaremill/sttp/package.scala +++ b/core/src/main/scala/com/softwaremill/sttp/package.scala @@ -12,62 +12,17 @@ import scala.language.higherKinds import scala.collection.immutable.Seq package object sttp { - /* - - - set headers - - set cookies (set from response) - - partial request (no uri + method) / full request - - start with an empty partial request - - multi-part uploads - - body: bytes, input stream (?), task/future, stream (fs2/akka), form data, file - - auth - - access uri/method/headers/cookies/body spec - - proxy - - user agent, buffer size - - charset - - zipped encodings - - SSL - mutual? (client side) - - - stream responses (sendStreamAndReceive?) / strict responses - - make sure response is consumed - only fire request when we know what to do with response? - - - reuse connections / connection pooling - in handler - - - handler restriction? AnyHandler <: Handler Restriction - - Options: - - timeouts (connection/read) - - follow redirect - - ignore SSL - - // - - We want to serialize to: - - string - - byte array - - input stream - - handler-specific stream of bytes/strings - - post: - - data (bytes/is/string - but which encoding?) - - form data (kv pairs - application/x-www-form-urlencoded) - - multipart (files mixed with forms - multipart/form-data) - - */ - - // - type Id[X] = X type Empty[X] = None.type - def ignoreResponse: ResponseAs[Unit] = IgnoreResponse + def ignoreResponse: ResponseAsBasic[Unit, Any] = IgnoreResponse /** * Uses `utf-8` encoding. */ - def responseAsString: ResponseAs[String] = responseAsString(Utf8) - def responseAsString(encoding: String): ResponseAs[String] = ResponseAsString(encoding) - def responseAsByteArray: ResponseAs[Array[Byte]] = ResponseAsByteArray - def responseAsStream[S]: ResponseAsStream[S] = ResponseAsStream[S]() + def responseAsString: ResponseAsBasic[String, Any] = responseAsString(Utf8) + def responseAsString(encoding: String): ResponseAsBasic[String, Any] = ResponseAsString(encoding) + def responseAsByteArray: ResponseAsBasic[Array[Byte], Any] = ResponseAsByteArray + def responseAsStream[S]: ResponseAs[S, S] = ResponseAsStream[S, S]() /** * Use the factory methods `multiPart` to conveniently create instances of this class. A part can be then @@ -189,15 +144,14 @@ package object sttp { def multipartData(parts: MultiPart*): RequestTemplate[U] = ??? - def send[R[_], T](responseAs: ResponseAs[T])( - implicit handler: SttpHandler[R], isRequest: IsRequest[U]): R[Response[T]] = { - - handler.send(this, responseAs) - } - - def send[R[_], S](responseAs: ResponseAsStream[S])( - implicit handler: SttpStreamHandler[R, S], isRequest: IsRequest[U]): R[Response[S]] = { - + /** + * @param responseAs What's the target type to which the response should be read. Needs to be specified upfront + * so that the response is always consumed and hence there are no requirements on 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. + */ + def send[R[_], S, T, ResponseAsType[x, -s] <: ResponseAs[x, s]](responseAs: ResponseAsType[T, S])( + implicit handler: SttpHandler[R, S, ResponseAsType], isRequest: IsRequest[U]): R[Response[T]] = { handler.send(this, responseAs) } } -- cgit v1.2.3