{"action":"created","comment":{"url":"https://api.github.com/repos/http4s/http4s/pulls/comments/187620846","pull_request_review_id":119436060,"id":187620846,"diff_hunk":"@@ -0,0 +1,140 @@\n+package org.http4s.client.okhttp\n+\n+import java.io.IOException\n+\n+import cats.data._\n+import cats.effect._\n+import cats.effect.implicits._\n+import okhttp3.{\n+  Call,\n+  Callback,\n+  OkHttpClient,\n+  Protocol,\n+  RequestBody,\n+  Headers => OKHeaders,\n+  MediaType => OKMediaType,\n+  Request => OKRequest,\n+  Response => OKResponse\n+}\n+import okio.BufferedSink\n+import org.http4s.{Header, Headers, HttpVersion, Method, Request, Response, Status}\n+import org.http4s.client.{Client, DisposableResponse}\n+import fs2.Stream._\n+import fs2._\n+import fs2.io._\n+\n+import scala.collection.JavaConverters._\n+import scala.concurrent.ExecutionContext\n+\n+object OkHttp {\n+\n+  def defaultconfig[F[_]]()(implicit F: Effect[F]): F[OkHttpClient.Builder] = F.delay {\n+    new OkHttpClient.Builder()\n+      .protocols(\n+        List(\n+          Protocol.HTTP_2,\n+          Protocol.HTTP_1_1\n+        ).asJava)\n+  }\n+\n+\n+  def apply[F[_]](config: F[OkHttpClient.Builder] = null)(\n+      implicit F: Effect[F],\n+      ec: ExecutionContext): F[Client[F]] = F.map(Option(config).getOrElse(defaultconfig[F]())) { c =>\n+    val client = c.build()\n+    Client(\n+      Kleisli { req =>\n+        F.async[DisposableResponse[F]] { cb =>\n+          client.newCall(toOkHttpRequest(req)).enqueue(handler(cb))\n+          ()\n+        }\n+      },\n+      F.delay({\n+        client.dispatcher.executorService().shutdown()\n+        client.connectionPool().evictAll()\n+        if (client.cache() != null) {\n+          client.cache().close()\n+        }\n+      })\n+    )\n+  }\n+\n+  def stream[F[_]](config: F[OkHttpClient.Builder] = null)(\n+      implicit F: Effect[F],\n+      ec: ExecutionContext): Stream[F, Client[F]] =\n+    Stream.bracket(apply(Option(config).getOrElse(defaultconfig[F]())))(c => Stream.emit(c), _.shutdown)\n+\n+  private def handler[F[_]](cb: Either[Throwable, DisposableResponse[F]] => Unit)(\n+      implicit F: Effect[F],\n+      ec: ExecutionContext): Callback =\n+    new Callback {\n+      override def onFailure(call: Call, e: IOException): Unit =\n+        ec.execute(new Runnable { override def run(): Unit = cb(Left(e)) })\n+\n+      override def onResponse(call: Call, response: OKResponse): Unit = {\n+        val protocol = response.protocol() match {\n+          case Protocol.HTTP_2 => HttpVersion.`HTTP/2.0`\n+          case Protocol.HTTP_1_1 => HttpVersion.`HTTP/1.1`\n+          case Protocol.HTTP_1_0 => HttpVersion.`HTTP/1.0`\n+          case _ => HttpVersion.`HTTP/1.1`\n+        }\n+        val bodyStream = response.body.byteStream()\n+        val dr = new DisposableResponse[F](\n+          Response[F](headers = getHeaders(response), httpVersion = protocol)\n+            .withStatus(\n+              Status\n+                .fromInt(response.code())\n+                .getOrElse(Status.apply(response.code())))\n+            .withBodyStream(\n+              readInputStream(F.pure(bodyStream), chunkSize = 1024, closeAfterUse = true)),\n+          F.delay({ bodyStream.close(); () })\n+        )\n+        ec.execute(new Runnable {\n+          override def run(): Unit = cb(Right(dr))\n+        })\n+      }\n+    }\n+\n+  private def getHeaders(response: OKResponse): Headers =\n+    Headers(response.headers().names().asScala.toList.flatMap { v =>\n+      response.headers().values(v).asScala.map(Header(v, _))\n+    })\n+\n+  private def toOkHttpRequest[F[_]](req: Request[F])(implicit F: Effect[F]): OKRequest = {\n+    val body = req match {\n+      case _ if req.isChunked || req.contentLength.isDefined =>\n+        new RequestBody {\n+          override def contentType(): OKMediaType =\n+            req.contentType.map(c => OKMediaType.parse(c.toString())).orNull\n+\n+          override def writeTo(sink: BufferedSink): Unit =\n+            req.body.chunks\n+              .map(_.toArray)\n+              .to(Sink { b: Array[Byte] =>\n+                F.delay {\n+                  sink.write(b); ()\n+                }\n+              })\n+              .compile\n+              .drain\n+              .runAsync(_ => IO.unit)","path":"okhttp-client/src/main/scala/org/http4s/client/okhttp/OkHttp.scala","position":120,"original_position":120,"commit_id":"5b4a5eff61499a3afab712b4a8f11adc3ee55ef9","original_commit_id":"5b4a5eff61499a3afab712b4a8f11adc3ee55ef9","user":{"login":"rossabaker","id":142698,"avatar_url":"https://avatars3.githubusercontent.com/u/142698?v=4","gravatar_id":"","url":"https://api.github.com/users/rossabaker","html_url":"https://github.com/rossabaker","followers_url":"https://api.github.com/users/rossabaker/followers","following_url":"https://api.github.com/users/rossabaker/following{/other_user}","gists_url":"https://api.github.com/users/rossabaker/gists{/gist_id}","starred_url":"https://api.github.com/users/rossabaker/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rossabaker/subscriptions","organizations_url":"https://api.github.com/users/rossabaker/orgs","repos_url":"https://api.github.com/users/rossabaker/repos","events_url":"https://api.github.com/users/rossabaker/events{/privacy}","received_events_url":"https://api.github.com/users/rossabaker/received_events","type":"User","site_admin":false},"body":"This swallows errors.  We should add something like cats-effect's `Logger.reportFailure`.","created_at":"2018-05-11T13:54:39Z","updated_at":"2018-05-11T13:55:29Z","html_url":"https://github.com/http4s/http4s/pull/1851#discussion_r187620846","pull_request_url":"https://api.github.com/repos/http4s/http4s/pulls/1851","author_association":"OWNER","_links":{"self":{"href":"https://api.github.com/repos/http4s/http4s/pulls/comments/187620846"},"html":{"href":"https://github.com/http4s/http4s/pull/1851#discussion_r187620846"},"pull_request":{"href":"https://api.github.com/repos/http4s/http4s/pulls/1851"}}},"pull_request":{"url":"https://api.github.com/repos/http4s/http4s/pulls/1851","id":187309463,"html_url":"https://github.com/http4s/http4s/pull/1851","diff_url":"https://github.com/http4s/http4s/pull/1851.diff","patch_url":"https://github.com/http4s/http4s/pull/1851.patch","issue_url":"https://api.github.com/repos/http4s/http4s/issues/1851","number":1851,"state":"open","locked":false,"title":"Add an OkHttp client (with HTTP/2)","user":{"login":"andimiller","id":4661434,"avatar_url":"https://avatars3.githubusercontent.com/u/4661434?v=4","gravatar_id":"","url":"https://api.github.com/users/andimiller","html_url":"https://github.com/andimiller","followers_url":"https://api.github.com/users/andimiller/followers","following_url":"https://api.github.com/users/andimiller/following{/other_user}","gists_url":"https://api.github.com/users/andimiller/gists{/gist_id}","starred_url":"https://api.github.com/users/andimiller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andimiller/subscriptions","organizations_url":"https://api.github.com/users/andimiller/orgs","repos_url":"https://api.github.com/users/andimiller/repos","events_url":"https://api.github.com/users/andimiller/events{/privacy}","received_events_url":"https://api.github.com/users/andimiller/received_events","type":"User","site_admin":false},"body":"This adds an OkHttp implementation for Client.\r\n\r\nThis is heavily based on the one for async-http-client.\r\n\r\nThe motivation for this is to have a Client backend which is capable of HTTP/2.\r\n\r\nRemaining questions are what the `chunkSize` should be when reading the body, if I've correctly hooked up the `dispose` on the `DisposableResponse`, and if the `writeTo` for writing bodies looks alright.","created_at":"2018-05-10T21:14:54Z","updated_at":"2018-05-11T13:55:29Z","closed_at":null,"merged_at":null,"merge_commit_sha":"9ecc8768592c36a7c75037af00d353c3a64963b1","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/http4s/http4s/pulls/1851/commits","review_comments_url":"https://api.github.com/repos/http4s/http4s/pulls/1851/comments","review_comment_url":"https://api.github.com/repos/http4s/http4s/pulls/comments{/number}","comments_url":"https://api.github.com/repos/http4s/http4s/issues/1851/comments","statuses_url":"https://api.github.com/repos/http4s/http4s/statuses/5b4a5eff61499a3afab712b4a8f11adc3ee55ef9","head":{"label":"andimiller:feature/okhttp","ref":"feature/okhttp","sha":"5b4a5eff61499a3afab712b4a8f11adc3ee55ef9","user":{"login":"andimiller","id":4661434,"avatar_url":"https://avatars3.githubusercontent.com/u/4661434?v=4","gravatar_id":"","url":"https://api.github.com/users/andimiller","html_url":"https://github.com/andimiller","followers_url":"https://api.github.com/users/andimiller/followers","following_url":"https://api.github.com/users/andimiller/following{/other_user}","gists_url":"https://api.github.com/users/andimiller/gists{/gist_id}","starred_url":"https://api.github.com/users/andimiller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andimiller/subscriptions","organizations_url":"https://api.github.com/users/andimiller/orgs","repos_url":"https://api.github.com/users/andimiller/repos","events_url":"https://api.github.com/users/andimiller/events{/privacy}","received_events_url":"https://api.github.com/users/andimiller/received_events","type":"User","site_admin":false},"repo":{"id":78681798,"name":"http4s","full_name":"andimiller/http4s","owner":{"login":"andimiller","id":4661434,"avatar_url":"https://avatars3.githubusercontent.com/u/4661434?v=4","gravatar_id":"","url":"https://api.github.com/users/andimiller","html_url":"https://github.com/andimiller","followers_url":"https://api.github.com/users/andimiller/followers","following_url":"https://api.github.com/users/andimiller/following{/other_user}","gists_url":"https://api.github.com/users/andimiller/gists{/gist_id}","starred_url":"https://api.github.com/users/andimiller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/andimiller/subscriptions","organizations_url":"https://api.github.com/users/andimiller/orgs","repos_url":"https://api.github.com/users/andimiller/repos","events_url":"https://api.github.com/users/andimiller/events{/privacy}","received_events_url":"https://api.github.com/users/andimiller/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/andimiller/http4s","description":"A minimal, idiomatic Scala interface for HTTP","fork":true,"url":"https://api.github.com/repos/andimiller/http4s","forks_url":"https://api.github.com/repos/andimiller/http4s/forks","keys_url":"https://api.github.com/repos/andimiller/http4s/keys{/key_id}","collaborators_url":"https://api.github.com/repos/andimiller/http4s/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/andimiller/http4s/teams","hooks_url":"https://api.github.com/repos/andimiller/http4s/hooks","issue_events_url":"https://api.github.com/repos/andimiller/http4s/issues/events{/number}","events_url":"https://api.github.com/repos/andimiller/http4s/events","assignees_url":"https://api.github.com/repos/andimiller/http4s/assignees{/user}","branches_url":"https://api.github.com/repos/andimiller/http4s/branches{/branch}","tags_url":"https://api.github.com/repos/andimiller/http4s/tags","blobs_url":"https://api.github.com/repos/andimiller/http4s/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/andimiller/http4s/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/andimiller/http4s/git/refs{/sha}","trees_url":"https://api.github.com/repos/andimiller/http4s/git/trees{/sha}","statuses_url":"https://api.github.com/repos/andimiller/http4s/statuses/{sha}","languages_url":"https://api.github.com/repos/andimiller/http4s/languages","stargazers_url":"https://api.github.com/repos/andimiller/http4s/stargazers","contributors_url":"https://api.github.com/repos/andimiller/http4s/contributors","subscribers_url":"https://api.github.com/repos/andimiller/http4s/subscribers","subscription_url":"https://api.github.com/repos/andimiller/http4s/subscription","commits_url":"https://api.github.com/repos/andimiller/http4s/commits{/sha}","git_commits_url":"https://api.github.com/repos/andimiller/http4s/git/commits{/sha}","comments_url":"https://api.github.com/repos/andimiller/http4s/comments{/number}","issue_comment_url":"https://api.github.com/repos/andimiller/http4s/issues/comments{/number}","contents_url":"https://api.github.com/repos/andimiller/http4s/contents/{+path}","compare_url":"https://api.github.com/repos/andimiller/http4s/compare/{base}...{head}","merges_url":"https://api.github.com/repos/andimiller/http4s/merges","archive_url":"https://api.github.com/repos/andimiller/http4s/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/andimiller/http4s/downloads","issues_url":"https://api.github.com/repos/andimiller/http4s/issues{/number}","pulls_url":"https://api.github.com/repos/andimiller/http4s/pulls{/number}","milestones_url":"https://api.github.com/repos/andimiller/http4s/milestones{/number}","notifications_url":"https://api.github.com/repos/andimiller/http4s/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/andimiller/http4s/labels{/name}","releases_url":"https://api.github.com/repos/andimiller/http4s/releases{/id}","deployments_url":"https://api.github.com/repos/andimiller/http4s/deployments","created_at":"2017-01-11T21:32:44Z","updated_at":"2018-05-10T20:34:23Z","pushed_at":"2018-05-11T13:07:26Z","git_url":"git://github.com/andimiller/http4s.git","ssh_url":"git@github.com:andimiller/http4s.git","clone_url":"https://github.com/andimiller/http4s.git","svn_url":"https://github.com/andimiller/http4s","homepage":"http://http4s.org/","size":27918,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"archived":false,"open_issues_count":0,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"http4s:master","ref":"master","sha":"ec8c696e36051c2d5cf6993fc1f177a7c6083ceb","user":{"login":"http4s","id":1527492,"avatar_url":"https://avatars3.githubusercontent.com/u/1527492?v=4","gravatar_id":"","url":"https://api.github.com/users/http4s","html_url":"https://github.com/http4s","followers_url":"https://api.github.com/users/http4s/followers","following_url":"https://api.github.com/users/http4s/following{/other_user}","gists_url":"https://api.github.com/users/http4s/gists{/gist_id}","starred_url":"https://api.github.com/users/http4s/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/http4s/subscriptions","organizations_url":"https://api.github.com/users/http4s/orgs","repos_url":"https://api.github.com/users/http4s/repos","events_url":"https://api.github.com/users/http4s/events{/privacy}","received_events_url":"https://api.github.com/users/http4s/received_events","type":"Organization","site_admin":false},"repo":{"id":3692188,"name":"http4s","full_name":"http4s/http4s","owner":{"login":"http4s","id":1527492,"avatar_url":"https://avatars3.githubusercontent.com/u/1527492?v=4","gravatar_id":"","url":"https://api.github.com/users/http4s","html_url":"https://github.com/http4s","followers_url":"https://api.github.com/users/http4s/followers","following_url":"https://api.github.com/users/http4s/following{/other_user}","gists_url":"https://api.github.com/users/http4s/gists{/gist_id}","starred_url":"https://api.github.com/users/http4s/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/http4s/subscriptions","organizations_url":"https://api.github.com/users/http4s/orgs","repos_url":"https://api.github.com/users/http4s/repos","events_url":"https://api.github.com/users/http4s/events{/privacy}","received_events_url":"https://api.github.com/users/http4s/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/http4s/http4s","description":"A minimal, idiomatic Scala interface for HTTP","fork":false,"url":"https://api.github.com/repos/http4s/http4s","forks_url":"https://api.github.com/repos/http4s/http4s/forks","keys_url":"https://api.github.com/repos/http4s/http4s/keys{/key_id}","collaborators_url":"https://api.github.com/repos/http4s/http4s/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/http4s/http4s/teams","hooks_url":"https://api.github.com/repos/http4s/http4s/hooks","issue_events_url":"https://api.github.com/repos/http4s/http4s/issues/events{/number}","events_url":"https://api.github.com/repos/http4s/http4s/events","assignees_url":"https://api.github.com/repos/http4s/http4s/assignees{/user}","branches_url":"https://api.github.com/repos/http4s/http4s/branches{/branch}","tags_url":"https://api.github.com/repos/http4s/http4s/tags","blobs_url":"https://api.github.com/repos/http4s/http4s/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/http4s/http4s/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/http4s/http4s/git/refs{/sha}","trees_url":"https://api.github.com/repos/http4s/http4s/git/trees{/sha}","statuses_url":"https://api.github.com/repos/http4s/http4s/statuses/{sha}","languages_url":"https://api.github.com/repos/http4s/http4s/languages","stargazers_url":"https://api.github.com/repos/http4s/http4s/stargazers","contributors_url":"https://api.github.com/repos/http4s/http4s/contributors","subscribers_url":"https://api.github.com/repos/http4s/http4s/subscribers","subscription_url":"https://api.github.com/repos/http4s/http4s/subscription","commits_url":"https://api.github.com/repos/http4s/http4s/commits{/sha}","git_commits_url":"https://api.github.com/repos/http4s/http4s/git/commits{/sha}","comments_url":"https://api.github.com/repos/http4s/http4s/comments{/number}","issue_comment_url":"https://api.github.com/repos/http4s/http4s/issues/comments{/number}","contents_url":"https://api.github.com/repos/http4s/http4s/contents/{+path}","compare_url":"https://api.github.com/repos/http4s/http4s/compare/{base}...{head}","merges_url":"https://api.github.com/repos/http4s/http4s/merges","archive_url":"https://api.github.com/repos/http4s/http4s/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/http4s/http4s/downloads","issues_url":"https://api.github.com/repos/http4s/http4s/issues{/number}","pulls_url":"https://api.github.com/repos/http4s/http4s/pulls{/number}","milestones_url":"https://api.github.com/repos/http4s/http4s/milestones{/number}","notifications_url":"https://api.github.com/repos/http4s/http4s/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/http4s/http4s/labels{/name}","releases_url":"https://api.github.com/repos/http4s/http4s/releases{/id}","deployments_url":"https://api.github.com/repos/http4s/http4s/deployments","created_at":"2012-03-12T04:41:20Z","updated_at":"2018-05-10T20:57:39Z","pushed_at":"2018-05-11T13:07:28Z","git_url":"git://github.com/http4s/http4s.git","ssh_url":"git@github.com:http4s/http4s.git","clone_url":"https://github.com/http4s/http4s.git","svn_url":"https://github.com/http4s/http4s","homepage":"https://http4s.org/","size":119643,"stargazers_count":1078,"watchers_count":1078,"language":"Scala","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":false,"has_pages":true,"forks_count":283,"mirror_url":null,"archived":false,"open_issues_count":123,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0"},"forks":283,"open_issues":123,"watchers":1078,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/http4s/http4s/pulls/1851"},"html":{"href":"https://github.com/http4s/http4s/pull/1851"},"issue":{"href":"https://api.github.com/repos/http4s/http4s/issues/1851"},"comments":{"href":"https://api.github.com/repos/http4s/http4s/issues/1851/comments"},"review_comments":{"href":"https://api.github.com/repos/http4s/http4s/pulls/1851/comments"},"review_comment":{"href":"https://api.github.com/repos/http4s/http4s/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/http4s/http4s/pulls/1851/commits"},"statuses":{"href":"https://api.github.com/repos/http4s/http4s/statuses/5b4a5eff61499a3afab712b4a8f11adc3ee55ef9"}},"author_association":"CONTRIBUTOR"}}
{"actor":{"display_login":"rossabaker"}}