aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Warski <adam@warski.org>2017-08-01 20:08:52 +0200
committerGitHub <noreply@github.com>2017-08-01 20:08:52 +0200
commit284860a8506589b19ca1afd2a33cecb45747f340 (patch)
tree91ba8a3b3c490567d362e4a9f3aa24342734d98f
parent65228c3a9029400eee056b661938b7cd81388124 (diff)
parent63d72b4775ba707fea260c378a0f496a25de43a4 (diff)
downloadsttp-284860a8506589b19ca1afd2a33cecb45747f340.tar.gz
sttp-284860a8506589b19ca1afd2a33cecb45747f340.tar.bz2
sttp-284860a8506589b19ca1afd2a33cecb45747f340.zip
Merge pull request #14 from omainegra/okhttp3
Initial support for OkHttp3 as backend
-rw-r--r--.travis.yml2
-rw-r--r--README.md3
-rw-r--r--build.sbt31
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/UriTests.scala2
-rw-r--r--okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala134
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala8
6 files changed, 168 insertions, 12 deletions
diff --git a/.travis.yml b/.travis.yml
index 91737bf..a858cb2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,7 +4,7 @@ jdk:
- oraclejdk8
scala:
- 2.12.3
-- 2.11.8
+- 2.11.11
script:
- sbt ++$TRAVIS_SCALA_VERSION test
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && sbt updateImpactSubmit || true' \ No newline at end of file
diff --git a/README.md b/README.md
index 8d6dc24..5e091f4 100644
--- a/README.md
+++ b/README.md
@@ -402,4 +402,5 @@ and pick a task you'd like to work on!
## Credits
* [Tomasz SzymaƄski](https://github.com/szimano)
-* [Adam Warski](https://github.com/adamw) \ No newline at end of file
+* [Adam Warski](https://github.com/adamw)
+* [Omar Alejandro Mainegra Sarduy](https://github.com/omainegra) \ No newline at end of file
diff --git a/build.sbt b/build.sbt
index 35ac8cd..10e1dd5 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1,7 +1,7 @@
val commonSettings = Seq(
organization := "com.softwaremill.sttp",
scalaVersion := "2.12.3",
- crossScalaVersions := Seq(scalaVersion.value, "2.11.8"),
+ crossScalaVersions := Seq(scalaVersion.value, "2.11.11"),
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature", "-Xlint"),
scalafmtOnCompile := true,
scalafmtVersion := "1.1.0",
@@ -38,13 +38,16 @@ val scalaTest = "org.scalatest" %% "scalatest" % "3.0.3"
lazy val rootProject = (project in file("."))
.settings(commonSettings: _*)
.settings(publishArtifact := false, name := "sttp")
- .aggregate(core,
- akkaHttpHandler,
- asyncHttpClientHandler,
- futureAsyncHttpClientHandler,
- scalazAsyncHttpClientHandler,
- monixAsyncHttpClientHandler,
- tests)
+ .aggregate(
+ core,
+ akkaHttpHandler,
+ asyncHttpClientHandler,
+ futureAsyncHttpClientHandler,
+ scalazAsyncHttpClientHandler,
+ monixAsyncHttpClientHandler,
+ okhttpClientHandler,
+ tests
+ )
lazy val core: Project = (project in file("core"))
.settings(commonSettings: _*)
@@ -102,6 +105,16 @@ lazy val monixAsyncHttpClientHandler: Project = (project in file(
)
) dependsOn asyncHttpClientHandler
+lazy val okhttpClientHandler: Project = (project in file(
+ "okhttp-client-handler"))
+ .settings(commonSettings: _*)
+ .settings(
+ name := "okhttp-client-handler",
+ libraryDependencies ++= Seq(
+ "com.squareup.okhttp3" % "okhttp" % "3.8.1"
+ )
+ ) dependsOn core
+
lazy val tests: Project = (project in file("tests"))
.settings(commonSettings: _*)
.settings(
@@ -116,4 +129,4 @@ lazy val tests: Project = (project in file("tests"))
).map(_ % "test"),
libraryDependencies += "org.scala-lang" % "scala-compiler" % scalaVersion.value % "test"
) dependsOn (core, akkaHttpHandler, futureAsyncHttpClientHandler, scalazAsyncHttpClientHandler,
-monixAsyncHttpClientHandler)
+monixAsyncHttpClientHandler, okhttpClientHandler)
diff --git a/core/src/test/scala/com/softwaremill/sttp/UriTests.scala b/core/src/test/scala/com/softwaremill/sttp/UriTests.scala
index 628df6d..26e7936 100644
--- a/core/src/test/scala/com/softwaremill/sttp/UriTests.scala
+++ b/core/src/test/scala/com/softwaremill/sttp/UriTests.scala
@@ -39,7 +39,7 @@ class UriTests extends FunSuite with Matchers {
Nil,
Nil,
None) ->
- "http://us%26er:pa%20ss@example.com",
+ "http://us%26er:pa%20ss@example.com"
)
for {
diff --git a/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala b/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala
new file mode 100644
index 0000000..99f0770
--- /dev/null
+++ b/okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala
@@ -0,0 +1,134 @@
+package com.softwaremill.sttp.okhttp
+
+import java.io.IOException
+import java.nio.charset.Charset
+
+import com.softwaremill.sttp._
+import com.softwaremill.sttp.model._
+import okhttp3.internal.http.HttpMethod
+import okhttp3.{
+ Call,
+ Callback,
+ MediaType,
+ OkHttpClient,
+ Request => OkHttpRequest,
+ RequestBody => OkHttpRequestBody,
+ Response => OkHttpResponse
+}
+import okio.{BufferedSink, Okio}
+
+import scala.collection.JavaConverters._
+import scala.concurrent.{Future, Promise}
+import scala.language.higherKinds
+
+abstract class OkHttpClientHandler[R[_], S](client: OkHttpClient)
+ extends SttpHandler[R, S] {
+ private[okhttp] def convertRequest[T](request: Request[T, S]): OkHttpRequest = {
+ val builder = new OkHttpRequest.Builder()
+ .url(request.uri.toString)
+
+ val body = setBody(request.body)
+ builder.method(request.method.m, body.getOrElse {
+ if (HttpMethod.requiresRequestBody(request.method.m))
+ OkHttpRequestBody.create(null, "")
+ else null
+ })
+
+ //OkHttp support automatic gzip compression
+ request.headers
+ .filter(_._1.equalsIgnoreCase(AcceptEncodingHeader) == false)
+ .foreach {
+ case (name, value) => builder.addHeader(name, value)
+ }
+
+ builder.build()
+ }
+
+ private def setBody(requestBody: RequestBody[S]): Option[OkHttpRequestBody] = {
+ requestBody match {
+ case NoBody => None
+ case StringBody(b, encoding) =>
+ Some(OkHttpRequestBody.create(MediaType.parse(encoding), b))
+ case ByteArrayBody(b) => Some(OkHttpRequestBody.create(null, b))
+ case ByteBufferBody(b) => Some(OkHttpRequestBody.create(null, b.array()))
+ case InputStreamBody(b) =>
+ Some(new OkHttpRequestBody() {
+ override def writeTo(sink: BufferedSink): Unit =
+ sink.writeAll(Okio.source(b))
+ override def contentType(): MediaType = null
+ })
+ case PathBody(b) => Some(OkHttpRequestBody.create(null, b.toFile))
+ case SerializableBody(f, t) => setBody(f(t))
+ case StreamBody(s) => None
+ }
+ }
+
+ private[okhttp] def readResponse[T](
+ res: OkHttpResponse,
+ responseAs: ResponseAs[T, S]): Response[T] = {
+ val body = readResponseBody(res, responseAs)
+
+ val headers = res
+ .headers()
+ .names()
+ .asScala
+ .flatMap(name => res.headers().values(name).asScala.map((name, _)))
+ Response(body, res.code(), headers.toList)
+ }
+
+ private def readResponseBody[T](res: OkHttpResponse,
+ responseAs: ResponseAs[T, S]): T = {
+ responseAs match {
+ case IgnoreResponse => res.body().close()
+ case ResponseAsString(encoding) =>
+ res.body().source().readString(Charset.forName(encoding))
+ case ResponseAsByteArray => res.body().bytes()
+ case MappedResponseAs(raw, g) => g(readResponseBody(res, raw))
+ case r @ ResponseAsParams(enc) =>
+ r.parse(res.body().source().readString(Charset.forName(enc)))
+ case ResponseAsStream() => throw new IllegalStateException()
+ }
+ }
+}
+
+class OkHttpSyncClientHandler(client: OkHttpClient)
+ extends OkHttpClientHandler[Id, Nothing](client) {
+ override def send[T](r: Request[T, Nothing]): Response[T] = {
+ val request = convertRequest(r)
+ val response = client.newCall(request).execute()
+ readResponse(response, r.responseAs)
+ }
+}
+
+object OkHttpSyncClientHandler {
+ def apply(okhttpClient: OkHttpClient = new OkHttpClient())
+ : OkHttpSyncClientHandler =
+ new OkHttpSyncClientHandler(okhttpClient)
+}
+
+class OkHttpFutureClientHandler(client: OkHttpClient)
+ extends OkHttpClientHandler[Future, Nothing](client) {
+
+ override def send[T](r: Request[T, Nothing]): Future[Response[T]] = {
+ val request = convertRequest(r)
+ val promise = Promise[Response[T]]()
+
+ client
+ .newCall(request)
+ .enqueue(new Callback {
+ override def onFailure(call: Call, e: IOException): Unit =
+ promise.failure(e)
+
+ override def onResponse(call: Call, response: OkHttpResponse): Unit =
+ promise.success(readResponse(response, r.responseAs))
+ })
+
+ promise.future
+ }
+}
+
+object OkHttpFutureClientHandler {
+ def apply(okhttpClient: OkHttpClient = new OkHttpClient())
+ : OkHttpFutureClientHandler =
+ new OkHttpFutureClientHandler(okhttpClient)
+}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
index 90aab02..5f17a7a 100644
--- a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
+++ b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
@@ -19,6 +19,10 @@ import better.files._
import com.softwaremill.sttp.asynchttpclient.future.FutureAsyncHttpClientHandler
import com.softwaremill.sttp.asynchttpclient.monix.MonixAsyncHttpClientHandler
import com.softwaremill.sttp.asynchttpclient.scalaz.ScalazAsyncHttpClientHandler
+import com.softwaremill.sttp.okhttp.{
+ OkHttpFutureClientHandler,
+ OkHttpSyncClientHandler
+}
import scala.language.higherKinds
@@ -126,6 +130,10 @@ class BasicTests
ForceWrappedValue.scalazTask)
runTests("Async Http Client - Monix")(MonixAsyncHttpClientHandler(),
ForceWrappedValue.monixTask)
+ runTests("OkHttpSyncClientHandler")(OkHttpSyncClientHandler(),
+ ForceWrappedValue.id)
+ runTests("OkHttpSyncClientHandler - Future")(OkHttpFutureClientHandler(),
+ ForceWrappedValue.future)
def runTests[R[_]](name: String)(
implicit handler: SttpHandler[R, Nothing],