diff options
author | adamw <adam@warski.org> | 2017-07-04 13:46:27 +0200 |
---|---|---|
committer | adamw <adam@warski.org> | 2017-07-04 13:46:27 +0200 |
commit | 09da476faedc382d7f2604be142a353e5851a1e3 (patch) | |
tree | bb70efb1e7bcb100c6fb2234aa277daaa84a393b | |
parent | 446dfa70716928c9a92c30ced94a7be6e62d73f0 (diff) | |
download | sttp-09da476faedc382d7f2604be142a353e5851a1e3.tar.gz sttp-09da476faedc382d7f2604be142a353e5851a1e3.tar.bz2 sttp-09da476faedc382d7f2604be142a353e5851a1e3.zip |
First version of unified send
5 files changed, 61 insertions, 82 deletions
diff --git a/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala b/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala index 7c36da6..668da90 100644 --- a/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala +++ b/akka-http-handler/src/main/scala/com/softwaremill/sttp/akkahttp/AkkaHttpSttpHandler.scala @@ -14,27 +14,22 @@ import com.softwaremill.sttp.model._ import scala.concurrent.Future -class AkkaHttpSttpHandler(actorSystem: ActorSystem) extends SttpStreamHandler[Future, Source[ByteString, Any]] { +class AkkaHttpSttpHandler(actorSystem: ActorSystem) + extends SttpHandler[Future, Source[ByteString, Any], ResponseAs] { + def this() = this(ActorSystem("sttp")) private implicit val as = actorSystem private implicit val materializer = ActorMaterializer() import as.dispatcher - override def send[T](r: Request, responseAs: ResponseAs[T]): Future[Response[T]] = { + override def send[T](r: Request, responseAs: ResponseAs[T, Source[ByteString, Any]]): Future[Response[T]] = { requestToAkka(r).flatMap(setBodyOnAkka(r, r.body, _)).flatMap(Http().singleRequest(_)).flatMap { hr => val code = hr.status.intValue() bodyFromAkka(responseAs, hr).map(Response(code, _)) } } - override def send(r: Request, responseAsStream: ResponseAsStream[Source[ByteString, Any]]): Future[Response[Source[ByteString, Any]]] = { - requestToAkka(r).flatMap(setBodyOnAkka(r, r.body, _)).flatMap(Http().singleRequest(_)).map { hr => - val code = hr.status.intValue() - Response(code, hr.entity.dataBytes) - } - } - private def methodToAkka(m: Method): HttpMethod = m match { case Method.GET => HttpMethods.GET case Method.HEAD => HttpMethods.HEAD @@ -48,7 +43,7 @@ class AkkaHttpSttpHandler(actorSystem: ActorSystem) extends SttpStreamHandler[Fu case _ => HttpMethod.custom(m.m) } - private def bodyFromAkka[T](rr: ResponseAs[T], hr: HttpResponse): Future[T] = { + private def bodyFromAkka[T](rr: ResponseAs[T, Source[ByteString, Any]], hr: HttpResponse): Future[T] = { def asByteArray = hr.entity.dataBytes .runFold(ByteString(""))(_ ++ _) .map(_.toArray[Byte]) @@ -63,6 +58,9 @@ class AkkaHttpSttpHandler(actorSystem: ActorSystem) extends SttpStreamHandler[Fu case ResponseAsByteArray => asByteArray + + case r @ ResponseAsStream() => + Future.successful(r.x(hr.entity.dataBytes)) } } 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) } } |