aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md29
-rw-r--r--async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala113
-rw-r--r--build.sbt12
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala6
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala3
5 files changed, 161 insertions, 2 deletions
diff --git a/README.md b/README.md
index 1647afe..811da8b 100644
--- a/README.md
+++ b/README.md
@@ -188,7 +188,7 @@ This handler depends on [akka-http](http://doc.akka.io/docs/akka-http/current/sc
A fully **asynchronous** handler. Sending a request returns a response wrapped
in a `Future`.
-To use, add an implicit value:
+Next you'll need to add an implicit value:
```scala
implicit val sttpHandler = new AkkaHttpSttpHandler()
@@ -235,6 +235,33 @@ val response: Future[Response[Source[ByteString, Any]]] =
.send()
```
+### `AsyncHttpClientHandler`
+
+To use, add the following dependency to your project:
+
+```scala
+"com.softwaremill.sttp" %% "async-http-client-handler" % version
+```
+
+This handler depends on [async-http-handler](https://github.com/AsyncHttpClient/async-http-client).
+A fully **asynchronous** handler, which uses [Netty](http://netty.io) behind the
+scenes. Sending a request returns a response wrapped in a `Future`. Different
+wrappers will be added in the future.
+
+Next you'll need to add an implicit value:
+
+```scala
+implicit val sttpHandler = new AsyncHttpClientHandler()
+
+// or, if you'd like to use custom configuration:
+implicit val sttpHandler = new AsyncHttpClientHandler(asyncHttpClientConfig)
+
+// or, if you'd like to instantiate the AsyncHttpClient yourself:
+implicit val sttpHandler = new AsyncHttpClientHandler(asyncHttpClient)
+```
+
+Streaming is not (yet) supported.
+
## Request type
All request descriptions have type `RequestT[U, T, S]` (T as in Template).
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
new file mode 100644
index 0000000..ecd49cd
--- /dev/null
+++ b/async-http-client-handler/src/main/scala/com/softwaremill/sttp/asynchttpclient/AsyncHttpClientHandler.scala
@@ -0,0 +1,113 @@
+package com.softwaremill.sttp.asynchttpclient
+
+import java.nio.charset.Charset
+
+import com.softwaremill.sttp.model._
+import com.softwaremill.sttp.{Request, Response, SttpHandler}
+import org.asynchttpclient.{
+ AsyncCompletionHandler,
+ AsyncHttpClient,
+ AsyncHttpClientConfig,
+ DefaultAsyncHttpClient,
+ RequestBuilder,
+ Request => AsyncRequest,
+ Response => AsyncResponse
+}
+
+import scala.concurrent.{Future, Promise}
+import scala.collection.JavaConverters._
+
+class AsyncHttpClientHandler(asyncHttpClient: AsyncHttpClient)
+ extends SttpHandler[Future, Nothing] {
+ def this() = this(new DefaultAsyncHttpClient())
+ def this(cfg: AsyncHttpClientConfig) = this(new DefaultAsyncHttpClient(cfg))
+
+ override def send[T](r: Request[T, Nothing]): Future[Response[T]] = {
+ val p = Promise[Response[T]]()
+ asyncHttpClient
+ .prepareRequest(requestToAsync(r))
+ .execute(new AsyncCompletionHandler[AsyncResponse] {
+ override def onCompleted(response: AsyncResponse): AsyncResponse = {
+ p.success(readResponse(response, r.responseAs))
+ response
+ }
+ override def onThrowable(t: Throwable): Unit = p.failure(t)
+ })
+
+ p.future
+ }
+
+ private def requestToAsync(r: Request[_, Nothing]): AsyncRequest = {
+ val rb = new RequestBuilder(r.method.m).setUrl(r.uri.toString)
+ r.headers.foreach { case (k, v) => rb.setHeader(k, v) }
+ setBody(r.body, rb)
+ rb.build()
+ }
+
+ private def setBody(body: RequestBody[Nothing], rb: RequestBuilder): Unit = {
+ body match {
+ case NoBody => // skip
+
+ case StringBody(b, encoding) =>
+ rb.setBody(b.getBytes(encoding))
+
+ case ByteArrayBody(b) =>
+ rb.setBody(b)
+
+ case ByteBufferBody(b) =>
+ rb.setBody(b)
+
+ case InputStreamBody(b) =>
+ rb.setBody(b)
+
+ case PathBody(b) =>
+ rb.setBody(b.toFile)
+
+ case SerializableBody(f, t) =>
+ setBody(f(t), rb)
+
+ case StreamBody(s) =>
+ // we have an instance of nothing - everything's possible!
+ s
+ }
+ }
+
+ private def readResponse[T](
+ response: AsyncResponse,
+ responseAs: ResponseAs[T, Nothing]): Response[T] = {
+ Response(readResponseBody(response, responseAs),
+ response.getStatusCode,
+ response.getHeaders
+ .iterator()
+ .asScala
+ .map(e => (e.getKey, e.getValue))
+ .toList)
+ }
+
+ private def readResponseBody[T](response: AsyncResponse,
+ responseAs: ResponseAs[T, Nothing]): T = {
+
+ def asString(enc: String) = response.getResponseBody(Charset.forName(enc))
+
+ responseAs match {
+ case IgnoreResponse(g) =>
+ // getting the body and discarding it
+ response.getResponseBodyAsBytes
+ g(())
+
+ case ResponseAsString(enc, g) =>
+ g(asString(enc))
+
+ case ResponseAsByteArray(g) =>
+ g(response.getResponseBodyAsBytes)
+
+ case r @ ResponseAsParams(enc, g) =>
+ g(r.parse(asString(enc)))
+
+ case ResponseAsStream(_) =>
+ // only possible when the user requests the response as a stream of
+ // Nothing. Oh well ...
+ throw new IllegalStateException()
+ }
+ }
+}
diff --git a/build.sbt b/build.sbt
index 17fa08d..581a443 100644
--- a/build.sbt
+++ b/build.sbt
@@ -68,6 +68,16 @@ lazy val akkaHttpHandler: Project = (project in file("akka-http-handler"))
)
) dependsOn (core)
+lazy val asyncHttpClientHandler: Project = (project in file(
+ "async-http-client-handler"))
+ .settings(commonSettings: _*)
+ .settings(
+ name := "async-http-client-handler",
+ libraryDependencies ++= Seq(
+ "org.asynchttpclient" % "async-http-client" % "2.0.33"
+ )
+ ) dependsOn (core)
+
lazy val tests: Project = (project in file("tests"))
.settings(commonSettings: _*)
.settings(
@@ -81,4 +91,4 @@ lazy val tests: Project = (project in file("tests"))
"ch.qos.logback" % "logback-classic" % "1.2.3"
).map(_ % "test"),
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value % "test"
- ) dependsOn (core, akkaHttpHandler)
+ ) dependsOn (core, akkaHttpHandler, asyncHttpClientHandler)
diff --git a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala
index f0f1fc9..0562fc2 100644
--- a/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala
+++ b/core/src/main/scala/com/softwaremill/sttp/SttpHandler.scala
@@ -2,6 +2,12 @@ package com.softwaremill.sttp
import scala.language.higherKinds
+/**
+ * @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]]
}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
index 2b3bbab..0f66cc8 100644
--- a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
+++ b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
@@ -15,6 +15,7 @@ import com.typesafe.scalalogging.StrictLogging
import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}
import better.files._
+import com.softwaremill.sttp.asynchttpclient.AsyncHttpClientHandler
import scala.language.higherKinds
@@ -111,6 +112,8 @@ class BasicTests
ForceWrappedValue.id)
runTests("Akka HTTP")(new AkkaHttpSttpHandler(actorSystem),
ForceWrappedValue.future)
+ runTests("Async Http Client")(new AsyncHttpClientHandler(),
+ ForceWrappedValue.future)
def runTests[R[_]](name: String)(
implicit handler: SttpHandler[R, Nothing],