diff options
author | Adam Warski <adam@warski.org> | 2017-08-01 20:08:52 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-08-01 20:08:52 +0200 |
commit | 284860a8506589b19ca1afd2a33cecb45747f340 (patch) | |
tree | 91ba8a3b3c490567d362e4a9f3aa24342734d98f | |
parent | 65228c3a9029400eee056b661938b7cd81388124 (diff) | |
parent | 63d72b4775ba707fea260c378a0f496a25de43a4 (diff) | |
download | sttp-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.yml | 2 | ||||
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | build.sbt | 31 | ||||
-rw-r--r-- | core/src/test/scala/com/softwaremill/sttp/UriTests.scala | 2 | ||||
-rw-r--r-- | okhttp-client-handler/src/main/scala/com/softwaremill/sttp/okhttp/OkHttpClientHandler.scala | 134 | ||||
-rw-r--r-- | tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala | 8 |
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 @@ -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 @@ -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], |