[
  {
    "path": ".gitignore",
    "content": "*.class\n*.log\n\n# sbt specific\n.cache\n.history\n.lib/\ndist/*\ntarget/\nlib_managed/\nsrc_managed/\nproject/boot/\nproject/plugins/project/\n\n# Scala-IDE specific\n.scala_dependencies\n.worksheet\n.idea/*\n.DS_Store\n"
  },
  {
    "path": ".travis.yml",
    "content": "\nlanguage : scala\n\nscala:\n  - 2.11.12\n  - 2.12.6\n\ncache:\n  directories:\n  - $HOME/.ivy2\n  - $HOME/.sbt\n\njdk:\n  - oraclejdk8\n\nscript:\n  - sbt ++$TRAVIS_SCALA_VERSION -Dfile.encoding=UTF8 \"project fs2-http\" test\n\n\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Spinoco\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# fs2-http\n\nMinimalistic yet powerful http client and server with scala fs2 library.\n\n[![Build Status](https://travis-ci.org/Spinoco/fs2-http.svg?branch=master)](https://travis-ci.org/Spinoco/fs2-http)\n[![Gitter Chat](https://badges.gitter.im/functional-streams-for-scala/fs2.svg)](https://gitter.im/fs2-http/Lobby)\n\n## Overview\n\nfs2-http is a simple client and server library that allows you to build http clients and servers using scala fs2.\nThe aim of fs2-http is to provide simple and reusable components that enable fast work with various\nhttp protocols.\n\nAll the code is fully asynchronous and non-blocking. Thanks to fs2, this comes with back-pressure and streaming support.\n\nfs2-http was built by compiling the internal projects Spinoco uses for building its [product](http://www.spinoco.com/), where the server side is completely implemented in fs2.\n\nCurrently the project only has three dependencies: fs2, scodec and shapeless. As such you are free to use this with any other\nfunctional library, such as scalaz or cats.\n\n\n### Features\n\n- HTTP 1.1 Client (request/reply, websocket, server-side-events) with SSL support\n- HTTP 1.1 Server (request/reply, routing, websocket, server-side-events)\n- HTTP Chunked encoding\n\n### SBT\n\nAdd this to your sbt build file :\n\n```\nlibraryDependencies += \"com.spinoco\" %% \"fs2-http\" % \"0.4.0\"\n```\n\n\n### Dependencies\n\nversion  |    scala  |   fs2  |  scodec | shapeless      \n---------|-----------|--------|---------|-----------\n0.4.0    | 2.11, 2.12| 1.0.0  | 1.10.3  | 2.3.2 \n\n\n## Usage\n\nThroughout this usage guide, the following imports are required in order for you to be able to run the examples test:console:\n\n```\nimport fs2._\nimport fs2.util.syntax._\nimport cats.effect._\nimport cats.syntax.all._\nimport spinoco.fs2.http\nimport http._\nimport http.websocket._\nimport spinoco.protocol.http.header._\nimport spinoco.protocol.http._\nimport spinoco.protocol.http.header.value._\n\n// import resources (Executor, Strategy, Asynchronous Channel Group, ...)\nimport spinoco.fs2.http.Resources._\n```\n\n\n### HTTP Client\n\nCurrently fs2-http supports HTTP 1.1 protocol and allows you to connect to server with either http:// or https:// scheme.\nA simple client that requests https page body data with the GET method from `https://github.com/Spinoco/fs2-http` may be constructed, for example, as:\n\n```\nhttp.client[IO]().flatMap { client =>\n  val request = HttpRequest.get[IO](Uri.https(\"github.com\", \"/Spinoco/fs2-http\"))\n  client.request(request).flatMap { resp =>\n    Stream.eval(resp.bodyAsString)\n  }.runLog.map {\n    println\n  }\n}.unsafeRunSync()\n```\n\nThe above code snippet only \"builds\" the http client, resulting in `IO` that will be evaluated once run (using `unsafeRunSync()`).\nThe line with `Stream.eval(resp.bodyAsString)` on it actually evaluates the consumed body of the response. The body of the\nresponse can be evaluated strictly (meaning all output is first collected and then converted to the desired type), or it can be streamed (meaning it will be converted to the desired type as it is received from the server). A streamed body is accessible as `resp.body`.\n\nRequests to the server are modeled with [HttpRequest\\[F\\]](https://github.com/Spinoco/fs2-http/blob/master/src/main/scala/spinoco/fs2/http/HttpRequestOrResponse.scala#L116), and responses are modeled as [HttpResponse\\[F\\]](https://github.com/Spinoco/fs2-http/blob/master/src/main/scala/spinoco/fs2/http/HttpRequestOrResponse.scala#L232). Both of them share several [helpers](https://github.com/Spinoco/fs2-http/blob/master/src/main/scala/spinoco/fs2/http/HttpRequestOrResponse.scala#L17) to help you work easily with the body.  \n\nThere is also a simple way to sent (stream) arbitrary data to server. It is easily achieved by modifying the request accordingly:\n\n```\nval stringStream: Stream[IO, String] = ???\nimplicit val encoder = StreamBodyEncoder.utf8StringEncoder[IO]\n\nHttpRequest.get(Uri.https(\"github.com\", \"/Spinoco/fs2-http\"))\n.withMethod(HttpMethod.POST)\n.withStreamBody(stringStream)\n```\n\nIn the example above the request is build as such, to ensure that when run by the client it will consume `stringStream` and send it with PUT request as utf8 encoded body to server.\n\n\n### WebSocket\n\nfs2-http has support for websocket clients (RFC 6455). A websocket client is built with the following construct:\n\n```\ndef wsPipe: Pipe[IO, Frame[String], Frame[String]] = { inbound =>\n  val output =  time.awakeEvery[IO](1.second).map { dur => println(s\"SENT $dur\"); Frame.Text(s\" ECHO $dur\") }.take(5)\n  output.concurrently(inbound.take(5).map { in => println((\"RECEIVED \", in)) })\n}\n\nhttp.client[IO]().flatMap { client =>\n  val request = WebSocketRequest.ws(\"echo.websocket.org\", \"/\", \"encoding\" -> \"text\")  \n  client.websocket(request, wsPipe).run  \n}.unsafeRun()\n```\n\nThe above code will create a pipe that receives websocket frames and expects the server to echo them back. As you see,\nthere is no direct access to response or body, instead websockets are always supplied with fs2 `Pipe` to send and receive data.\nThis is in fact quite a powerful construct that allows you to asynchronously send and receive data to/from server over http/https with full back-pressure support.\n\nWebsockets use `Frame[A]` to send and receive data. Frame is used to tag a given frame as binary or text. To encode/decode `A` the `scodec.Encoder` and `scodec.Decoder` is used.\n\n### HTTP Server\n\nfs2-http supports building simple yet fully functional HTTP servers. The following construct builds a very simple echo server:\n\n```\n import java.net.InetSocketAddress\n import java.util.concurrent.Executors\n import java.nio.channels.AsynchronousChannelGroup\n\n val ES = Executors.newCachedThreadPool(Strategy.daemonThreadFactory(\"ACG\"))\n implicit val ACG = AsynchronousChannelGroup.withThreadPool(ES) // http.server requires a group\n implicit val S = Strategy.fromExecutor(ES) // Async (Task) requires a strategy\n\n def service(request: HttpRequestHeader, body: Stream[IO,Byte]): Stream[IO,HttpResponse[IO]] = {\n    if (request.path != Uri.Path / \"echo\") Stream.emit(HttpResponse(HttpStatusCode.Ok).withUtf8Body(\"Hello World\"))\n    else {\n      val ct =  request.headers.collectFirst { case `Content-Type`(ct) => ct }.getOrElse(ContentType(MediaType.`application/octet-stream`, None, None))\n      val size = request.headers.collectFirst { case `Content-Length`(sz) => sz }.getOrElse(0l)\n      val ok = HttpResponse(HttpStatusCode.Ok).chunkedEncoding.withContentType(ct).withBodySize(size)\n\n      Stream.emit(ok.copy(body = body.take(size)))\n    }\n  }\n\n  http.server(new InetSocketAddress(\"127.0.0.1\", 9090))(service).run.unsafeRun()\n```\n\nAs you see the server creates a simple `Stream[F,Unit]` that, when run, will bind itself to 127.0.0.1 port 9090 and will serve the results of the `service` function.\nThe service function is defined as `(HttpRequestHeader, Stream[F, Body])  => Stream[F, HttpResponse[F]` and allows you to perform arbitrary functionality, all wrapped in `fs2.Stream`.\n\nWriting a server service function manually may not be fun and may result in unreadable and hard to maintain code. As such the last component of fs2-http is server routing.    \n\n### HTTP Server Routing\n\nServer routing is a micro-dsl language to allow fast monadic composition of a parser, that is essentially a function `(HttpRequestHeader, Stream[F, Body]) => Either[HttpResponse[F], Stream[F, HttpResponse[F]]`\nwhere on the right side there is the result when the parser matches, and on the left side there is the response when the parser fails to match.\n\nThanks to the parser's ability to compose, you can build quite complex routing constructs, that remain readable:\n\n```\nimport spinoco.fs2.http.routing._\nimport shapeless.{HNil, ::}\n\nroute[IO] ( choice(\n  \"example1\" / \"path\" map { case _ => ??? }\n  , \"example2\" / as[Int] :/: as[String] map { case int :: s :: HNil => ??? }\n  , \"example3\" / body.as[Foo] :: choice(Post, Put) map { case foo :: postOrPut :: HNil => ??? }\n  , \"example4\" / header[`Content-Type`] map { case contentType  => ??? }\n  , \"example5\" / param[Int](\"count\") :: param[String](\"query\") map { case count :: query :: HNil => ??? }\n  , \"example6\" / eval(someEffect) map { case result => ??? }\n))\n\n```\n\nHere the choice indicates that any of the supplied routes may match, starting with the very first route. Instead of ??? you may supply any function producing the `Stream[IO, HttpResponse[IO]]`, that will be evaluated when the route will match.\n\nThe meaning of the individual routes is as follows:\n\n- example1 : will match path \"/example1/path\"\n- example2 : will match path \"/example2/23/some_string\" and will produce 23 :: \"some_string\" :: HNil to map\n- example3 : will match path \"/example3\" and will consume body to produce `Foo` class. Map is supplied with Foo :: HttpMethod.Value :: HNil\n- example4 : will match path \"/example4\" and will match if header `Content-Type` is present supplying that header to map.\n- example5 : will match path \"/example5?count=1&query=sql_query\" supplying 1 :: \"sql:query\" :: HNil to map\n- example6 : will match path \"/example6\" and then evaluating `someEffect` where the result of someEffect will be passed to map  \n\n### Other documentation and helpful links\n\n- [Using custom headers](https://github.com/Spinoco/fs2-http/blob/master/doc/custom_codec.md)\n\n### Comparing to http://http4s.org/\n\nHttp4s.org is a very useful library for http, originally started with scalaz-stream and currently fully supporting fs2. \nThe main differences between http4s.org and fs2-http is that unlike http4s.org, fs2-http is purely functional, including the network stack \nwhich is completely impliemented in fs2. Also the fs2-http focuses to be minimalistic both on dependencies and functionality provided. \n"
  },
  {
    "path": "build.sbt",
    "content": "import com.typesafe.sbt.pgp.PgpKeys.publishSigned\n\nval ReleaseTag = \"\"\"^release/([\\d\\.]+a?)$\"\"\".r\n\nlazy val contributors = Seq(\n \"pchlupacek\" -> \"Pavel Chlupáček\"\n)\n\n\nlazy val commonSettings = Seq(\n   organization := \"com.spinoco\",\n   scalaVersion := \"2.12.6\",\n   crossScalaVersions := Seq(\"2.11.12\", \"2.12.6\"),\n   scalacOptions ++= Seq(\n    \"-feature\",\n    \"-deprecation\",\n    \"-language:implicitConversions\",\n    \"-language:higherKinds\",\n    \"-language:existentials\",\n    \"-language:postfixOps\",\n    \"-Xfatal-warnings\",\n    \"-Yno-adapted-args\",\n    \"-Ywarn-value-discard\",\n    \"-Ywarn-unused-import\"\n   ),\n   scalacOptions in (Compile, console) ~= {_.filterNot(\"-Ywarn-unused-import\" == _)},\n   scalacOptions in (Test, console) := (scalacOptions in (Compile, console)).value,\n   libraryDependencies ++= Seq(\n     compilerPlugin(\"org.scalamacros\" % \"paradise\" % \"2.1.0\" cross CrossVersion.full)\n     , \"org.scodec\" %% \"scodec-bits\" % \"1.1.4\"\n     , \"org.scodec\" %% \"scodec-core\" % \"1.10.3\"\n     , \"com.spinoco\" %% \"protocol-http\" % \"0.3.15\"\n     , \"com.spinoco\" %% \"protocol-websocket\" % \"0.3.15\"\n     , \"co.fs2\" %% \"fs2-core\" % \"1.0.0\"\n     , \"co.fs2\" %% \"fs2-io\" % \"1.0.0\"\n     , \"com.spinoco\" %% \"fs2-crypto\" % \"0.4.0\"\n     , \"org.scalacheck\" %% \"scalacheck\" % \"1.13.4\" % \"test\"\n   ),\n   scmInfo := Some(ScmInfo(url(\"https://github.com/Spinoco/fs2-http\"), \"git@github.com:Spinoco/fs2-http.git\")),\n   homepage := None,\n   licenses += (\"MIT\", url(\"http://opensource.org/licenses/MIT\")),\n   initialCommands := s\"\"\"\n   import fs2._\n   import fs2.util.syntax._\n   import spinoco.fs2.http\n   import http.Resources._\n   import spinoco.protocol.http.header._\n  \"\"\"\n) ++ testSettings ++ scaladocSettings ++ publishingSettings ++ releaseSettings\n\nlazy val testSettings = Seq(\n  parallelExecution in Test := false,\n  testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, \"-oDF\"),\n  publishArtifact in Test := true\n)\n\nlazy val scaladocSettings = Seq(\n   scalacOptions in (Compile, doc) ++= Seq(\n    \"-doc-source-url\", scmInfo.value.get.browseUrl + \"/tree/master€{FILE_PATH}.scala\",\n    \"-sourcepath\", baseDirectory.in(LocalRootProject).value.getAbsolutePath,\n    \"-implicits\",\n    \"-implicits-show-all\"\n  ),\n   scalacOptions in (Compile, doc) ~= { _ filterNot { _ == \"-Xfatal-warnings\" } },\n   autoAPIMappings := true\n)\n\nlazy val publishingSettings = Seq(\n  publishTo := {\n   val nexus = \"https://oss.sonatype.org/\"\n   if (version.value.trim.endsWith(\"SNAPSHOT\"))\n     Some(\"snapshots\" at nexus + \"content/repositories/snapshots\")\n   else\n     Some(\"releases\" at nexus + \"service/local/staging/deploy/maven2\")\n  },\n  credentials ++= (for {\n   username <- Option(System.getenv().get(\"SONATYPE_USERNAME\"))\n   password <- Option(System.getenv().get(\"SONATYPE_PASSWORD\"))\n  } yield Credentials(\"Sonatype Nexus Repository Manager\", \"oss.sonatype.org\", username, password)).toSeq,\n  publishMavenStyle := true,\n  pomIncludeRepository := { _ => false },\n  pomExtra := {\n    <url>https://github.com/Spinoco/fs2-http</url>\n    <developers>\n      {for ((username, name) <- contributors) yield\n      <developer>\n        <id>{username}</id>\n        <name>{name}</name>\n        <url>http://github.com/{username}</url>\n      </developer>\n      }\n    </developers>\n  },\n  pomPostProcess := { node =>\n   import scala.xml._\n   import scala.xml.transform._\n   def stripIf(f: Node => Boolean) = new RewriteRule {\n     override def transform(n: Node) =\n       if (f(n)) NodeSeq.Empty else n\n   }\n   val stripTestScope = stripIf { n => n.label == \"dependency\" && (n \\ \"scope\").text == \"test\" }\n   new RuleTransformer(stripTestScope).transform(node)(0)\n  }\n  , resolvers += Resolver.mavenLocal\n)\n\nlazy val releaseSettings = Seq(\n  releaseCrossBuild := true,\n  releasePublishArtifactsAction := PgpKeys.publishSigned.value\n)\n\nlazy val `fs2-http`=\n  project.in(file(\"./\"))\n  .settings(commonSettings)\n  .settings(\n    name := \"fs2-http\"\n  )\n\n\n"
  },
  {
    "path": "doc/custom_codec.md",
    "content": "# Using custom codec for http headers and requests. \n\nOcassionally it is required to extends headers supported by fs2-http by some custom headers of user choice. Behind the scenes fs2-http is using scodec library for encoding and decoding codecs. So generally addin any codec is quite straigthforward. \n\n## Using custom Generic Header\n\nIf you are ok with receiveving your header as simple String value pair, there is simple technique using the `GenericHeader`. This allows you to encode and decode any Http Header with simple string key and value pair, where key is name of the header and value is anything after : in http header. For example : \n\n```\nAuthorization: Token someAuthorizationToken\n\n```\nmay be decoded as \n\n```scala\n\nGenericHeader(\"Authorization\", \"Token someAuthorizationToken\") \n\n```\n\nHowever to do so we need to supply this codec to the http client and http server. In both cases this is pretty straightforward to do: \n\n```scala\nimport spinoco.protocol.http\nimport spinoco.protocol.http.codec.HttpHeaderCodec\n\nval genericHeaderAuthCodec: HttpCodec[HttpHeader] = \n utf8.xmap[GenericHeader](s => GenericHeader(\"Authorization\", s), _.value).upcast[HttpHeader]\n \nval headerCodec: Codec[HttpHeader]= \n HttpHeaderCodec.codec(Int.MaxValue, (\"Authorization\" -> genericHeaderAuthCodec))\n \n\nhttp.client(\n   requestCodec = HttpRequestHeaderCodec.codec(headerCodec)\n   , responseCodec = HttpResponseHeaderCodec.codec(headerCodec)\n) map { client => \n /** your code with client **/\n}\n\nhttp.server(\n bindTo = ??? // your ip where you want bind server to \n   , requestCodec = HttpRequestHeaderCodec.codec(headerCodec)\n   , responseCodec = HttpResponseHeaderCodec.codec(headerCodec)\n) flatMap { server => \n  /** your code with server **/\n}\n  \n\n\n```\n\nNote that this technique, effectivelly causes to turn-off any already supported Authorization header codecs, which you man not want to. Well, in next section we describe exactly solution for that. \n\n\n## Using custom header codec\n\nCustom header codecs allow you to write any header codec available or extends it by your own functionality. So lets say we would like to extend Authorization header with our own version of Authorization header while still keeping the current Authroization header codec in place. \n\nLet's say we ahve our own Authorization header case class : \n```scala\n\ncase class MyAuthorizationTokenHeader(token: String) extends HttpHeader\n\n```\n\nFirst we need to create codec that will encode authorization of our own, and then, when that won't pass, we will try to decode with default. This is quite simply achievable by following code snipped: {\n\n```scala\nimport scodec.codecs._\nimport spinoco.protocol.http.codec.helper._\nimport spinoco.procotol.http.header.value.Authorization \n\nobject MyAuthorizationTokenHeader {\n\n  // this is simple codec to decode essentially line `Token sometoken`\n  val codec : Codec[MyAuthorizationTokenHeader) = \n  (asciiConstant(\"Token\") ~> (whitespace() ~> utf8String)).xmap(\n      { token => MyAuthorizationTokenHeader(token)}\n      , _.token\n    )\n    \n  // this is new codec, that will first try to decode by our codec and then if that fails, will use default authroization codec.   \n  val customAuthorizationHeader: Codec[HttpHeader] = choice(\n     codec.upcast[HttpHeader]\n     , Authroization.codec\n  )\n\n}\n\n```\n\nOnce we have that custom codec setup, we only need to plug it to client and/or server likewise we did for GenericHeader before. For example: \n\n```scala\n\nimport spinoco.protocol.http\nimport spinoco.protocol.http.codec.HttpHeaderCodec\n\n  \nval headerCodec: Codec[HttpHeader]= \n HttpHeaderCodec.codec(Int.MaxValue, (\"Authorization\" -> MyAuthorizationTokenHeader.customAuthorizationHeader))\n \n\nhttp.client(\n   requestCodec = HttpRequestHeaderCodec.codec(headerCodec)\n   , responseCodec = HttpResponseHeaderCodec.codec(headerCodec)\n) map { client => \n /** your code with client **/\n}\n\nhttp.server(\n bindTo = ??? // your ip where you want bind server to \n   , requestCodec = HttpRequestHeaderCodec.codec(headerCodec)\n   , responseCodec = HttpResponseHeaderCodec.codec(headerCodec)\n) flatMap { server => \n  /** your code with server **/\n}\n\n\n```\n\n## Summary\n\nBoth of these techniques have own advantages and drawbacks. It is up to user to decide whichever suits best. However, as you may see with a little effort you may plug very complex encoding and decoding schemes (even including any binary data) that your application may require. \n\n\n\n"
  },
  {
    "path": "project/build.properties",
    "content": "sbt.version=1.1.6\n"
  },
  {
    "path": "project/plugins.sbt",
    "content": "addSbtPlugin(\"com.github.tkawachi\" % \"sbt-doctest\" % \"0.8.0\")\naddSbtPlugin(\"com.github.gseitz\" % \"sbt-release\" % \"1.0.9\")\naddSbtPlugin(\"com.jsuereth\" % \"sbt-pgp\" % \"1.1.2\")\naddSbtPlugin(\"org.xerial.sbt\" % \"sbt-sonatype\" % \"2.3\")"
  },
  {
    "path": "sbt",
    "content": "#!/usr/bin/env bash\n#\n# A more capable sbt runner, coincidentally also called sbt.\n# Author: Paul Phillips <paulp@improving.org>\n\nset -o pipefail\n\n# todo - make this dynamic\ndeclare -r sbt_release_version=\"0.13.11\"\ndeclare -r sbt_unreleased_version=\"0.13.11\"\ndeclare -r buildProps=\"project/build.properties\"\n\ndeclare sbt_jar sbt_dir sbt_create sbt_version\ndeclare scala_version sbt_explicit_version\ndeclare verbose noshare batch trace_level log_level\ndeclare sbt_saved_stty debugUs\n\nechoerr () { echo >&2 \"$@\"; }\nvlog ()    { [[ -n \"$verbose\" ]] && echoerr \"$@\"; }\n\n# spaces are possible, e.g. sbt.version = 0.13.0\nbuild_props_sbt () {\n  [[ -r \"$buildProps\" ]] && \\\n    grep '^sbt\\.version' \"$buildProps\" | tr '=\\r' ' ' | awk '{ print $2; }'\n}\n\nupdate_build_props_sbt () {\n  local ver=\"$1\"\n  local old=\"$(build_props_sbt)\"\n\n  [[ -r \"$buildProps\" ]] && [[ \"$ver\" != \"$old\" ]] && {\n    perl -pi -e \"s/^sbt\\.version\\b.*\\$/sbt.version=${ver}/\" \"$buildProps\"\n    grep -q '^sbt.version[ =]' \"$buildProps\" || printf \"\\nsbt.version=%s\\n\" \"$ver\" >> \"$buildProps\"\n\n    vlog \"!!!\"\n    vlog \"!!! Updated file $buildProps setting sbt.version to: $ver\"\n    vlog \"!!! Previous value was: $old\"\n    vlog \"!!!\"\n  }\n}\n\nset_sbt_version () {\n  sbt_version=\"${sbt_explicit_version:-$(build_props_sbt)}\"\n  [[ -n \"$sbt_version\" ]] || sbt_version=$sbt_release_version\n  export sbt_version\n}\n\n# restore stty settings (echo in particular)\nonSbtRunnerExit() {\n  [[ -n \"$sbt_saved_stty\" ]] || return\n  vlog \"\"\n  vlog \"restoring stty: $sbt_saved_stty\"\n  stty \"$sbt_saved_stty\"\n  unset sbt_saved_stty\n}\n\n# save stty and trap exit, to ensure echo is reenabled if we are interrupted.\ntrap onSbtRunnerExit EXIT\nsbt_saved_stty=\"$(stty -g 2>/dev/null)\"\nvlog \"Saved stty: $sbt_saved_stty\"\n\n# this seems to cover the bases on OSX, and someone will\n# have to tell me about the others.\nget_script_path () {\n  local path=\"$1\"\n  [[ -L \"$path\" ]] || { echo \"$path\" ; return; }\n\n  local target=\"$(readlink \"$path\")\"\n  if [[ \"${target:0:1}\" == \"/\" ]]; then\n    echo \"$target\"\n  else\n    echo \"${path%/*}/$target\"\n  fi\n}\n\ndie() {\n  echo \"Aborting: $@\"\n  exit 1\n}\n\nurl_base () {\n  local version=\"$1\"\n\n  case \"$version\" in\n        0.7.*) echo \"http://simple-build-tool.googlecode.com\" ;;\n      0.10.* ) echo \"$sbt_launch_release_repo\" ;;\n    0.11.[12]) echo \"$sbt_launch_release_repo\" ;;\n    *-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie \"*-yyyymmdd-hhMMss\"\n               echo \"$sbt_launch_snapshot_repo\" ;;\n            *) echo \"$sbt_launch_release_repo\" ;;\n  esac\n}\n\nmake_url () {\n  local version=\"$1\"\n\n  local base=\"${sbt_launch_repo:-$(url_base \"$version\")}\"\n\n  case \"$version\" in\n        0.7.*) echo \"$base/files/sbt-launch-0.7.7.jar\" ;;\n      0.10.* ) echo \"$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar\" ;;\n    0.11.[12]) echo \"$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar\" ;;\n            *) echo \"$base/org.scala-sbt/sbt-launch/$version/sbt-launch.jar\" ;;\n  esac\n}\n\ninit_default_option_file () {\n  local overriding_var=\"${!1}\"\n  local default_file=\"$2\"\n  if [[ ! -r \"$default_file\" && \"$overriding_var\" =~ ^@(.*)$ ]]; then\n    local envvar_file=\"${BASH_REMATCH[1]}\"\n    if [[ -r \"$envvar_file\" ]]; then\n      default_file=\"$envvar_file\"\n    fi\n  fi\n  echo \"$default_file\"\n}\n\ndeclare -r cms_opts=\"-XX:+CMSClassUnloadingEnabled -XX:+UseConcMarkSweepGC\"\ndeclare -r jit_opts=\"-XX:ReservedCodeCacheSize=256m -XX:+TieredCompilation\"\ndeclare -r default_jvm_opts_common=\"-Xms512m -Xmx1536m -Xss2m $jit_opts $cms_opts\"\ndeclare -r noshare_opts=\"-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy\"\ndeclare -r latest_28=\"2.8.2\"\ndeclare -r latest_29=\"2.9.3\"\ndeclare -r latest_210=\"2.10.6\"\ndeclare -r latest_211=\"2.11.8\"\ndeclare -r latest_212=\"2.12.0-M3\"\ndeclare -r sbt_launch_release_repo=\"http://repo.typesafe.com/typesafe/ivy-releases\"\ndeclare -r sbt_launch_snapshot_repo=\"https://repo.scala-sbt.org/scalasbt/ivy-snapshots\"\n\ndeclare -r script_path=\"$(get_script_path \"$BASH_SOURCE\")\"\ndeclare -r script_name=\"${script_path##*/}\"\n\n# some non-read-onlies set with defaults\ndeclare java_cmd=\"java\"\ndeclare sbt_opts_file=\"$(init_default_option_file SBT_OPTS .sbtopts)\"\ndeclare jvm_opts_file=\"$(init_default_option_file JVM_OPTS .jvmopts)\"\ndeclare sbt_launch_dir=\"$HOME/.sbt/launchers\"\n\ndeclare sbt_launch_repo\n\n# pull -J and -D options to give to java.\ndeclare -a residual_args\ndeclare -a java_args\ndeclare -a scalac_args\ndeclare -a sbt_commands\n\n# args to jvm/sbt via files or environment variables\ndeclare -a extra_jvm_opts extra_sbt_opts\n\naddJava () {\n  vlog \"[addJava] arg = '$1'\"\n  java_args+=(\"$1\")\n}\naddSbt () {\n  vlog \"[addSbt] arg = '$1'\"\n  sbt_commands+=(\"$1\")\n}\nsetThisBuild () {\n  vlog \"[addBuild] args = '$@'\"\n  local key=\"$1\" && shift\n  addSbt \"set $key in ThisBuild := $@\"\n}\naddScalac () {\n  vlog \"[addScalac] arg = '$1'\"\n  scalac_args+=(\"$1\")\n}\naddResidual () {\n  vlog \"[residual] arg = '$1'\"\n  residual_args+=(\"$1\")\n}\naddResolver () {\n  addSbt \"set resolvers += $1\"\n}\naddDebugger () {\n  addJava \"-Xdebug\"\n  addJava \"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1\"\n}\nsetScalaVersion () {\n  [[ \"$1\" == *\"-SNAPSHOT\" ]] && addResolver 'Resolver.sonatypeRepo(\"snapshots\")'\n  addSbt \"++ $1\"\n}\nsetJavaHome () {\n  java_cmd=\"$1/bin/java\"\n  setThisBuild javaHome \"scala.Some(file(\\\"$1\\\"))\"\n  export JAVA_HOME=\"$1\"\n  export JDK_HOME=\"$1\"\n  export PATH=\"$JAVA_HOME/bin:$PATH\"\n}\nsetJavaHomeQuietly () {\n  addSbt warn\n  setJavaHome \"$1\"\n  addSbt info\n}\n\n# if set, use JDK_HOME/JAVA_HOME over java found in path\nif [[ -e \"$JDK_HOME/lib/tools.jar\" ]]; then\n  setJavaHomeQuietly \"$JDK_HOME\"\nelif [[ -e \"$JAVA_HOME/bin/java\" ]]; then\n  setJavaHomeQuietly \"$JAVA_HOME\"\nfi\n\n# directory to store sbt launchers\n[[ -d \"$sbt_launch_dir\" ]] || mkdir -p \"$sbt_launch_dir\"\n[[ -w \"$sbt_launch_dir\" ]] || sbt_launch_dir=\"$(mktemp -d -t sbt_extras_launchers.XXXXXX)\"\n\njava_version () {\n  local version=$(\"$java_cmd\" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d \\\")\n  vlog \"Detected Java version: $version\"\n  echo \"${version:2:1}\"\n}\n\n# MaxPermSize critical on pre-8 jvms but incurs noisy warning on 8+\ndefault_jvm_opts () {\n  local v=\"$(java_version)\"\n  if [[ $v -ge 8 ]]; then\n    echo \"$default_jvm_opts_common\"\n  else\n    echo \"-XX:MaxPermSize=384m $default_jvm_opts_common\"\n  fi\n}\n\nbuild_props_scala () {\n  if [[ -r \"$buildProps\" ]]; then\n    versionLine=\"$(grep '^build.scala.versions' \"$buildProps\")\"\n    versionString=\"${versionLine##build.scala.versions=}\"\n    echo \"${versionString%% .*}\"\n  fi\n}\n\nexecRunner () {\n  # print the arguments one to a line, quoting any containing spaces\n  vlog \"# Executing command line:\" && {\n    for arg; do\n      if [[ -n \"$arg\" ]]; then\n        if printf \"%s\\n\" \"$arg\" | grep -q ' '; then\n          printf >&2 \"\\\"%s\\\"\\n\" \"$arg\"\n        else\n          printf >&2 \"%s\\n\" \"$arg\"\n        fi\n      fi\n    done\n    vlog \"\"\n  }\n\n  [[ -n \"$batch\" ]] && exec </dev/null\n  exec \"$@\"\n}\n\njar_url () {\n  make_url \"$1\"\n}\n\njar_file () {\n  echo \"$sbt_launch_dir/$1/sbt-launch.jar\"\n}\n\ndownload_url () {\n  local url=\"$1\"\n  local jar=\"$2\"\n\n  echoerr \"Downloading sbt launcher for $sbt_version:\"\n  echoerr \"  From  $url\"\n  echoerr \"    To  $jar\"\n\n  mkdir -p \"${jar%/*}\" && {\n    if which curl >/dev/null; then\n      curl --fail --silent --location \"$url\" --output \"$jar\"\n    elif which wget >/dev/null; then\n      wget --quiet -O \"$jar\" \"$url\"\n    fi\n  } && [[ -r \"$jar\" ]]\n}\n\nacquire_sbt_jar () {\n  local sbt_url=\"$(jar_url \"$sbt_version\")\"\n  sbt_jar=\"$(jar_file \"$sbt_version\")\"\n\n  [[ -r \"$sbt_jar\" ]] || download_url \"$sbt_url\" \"$sbt_jar\"\n}\n\nusage () {\n  set_sbt_version\n  cat <<EOM\nUsage: $script_name [options]\nNote that options which are passed along to sbt begin with -- whereas\noptions to this runner use a single dash. Any sbt command can be scheduled\nto run first by prefixing the command with --, so --warn, --error and so on\nare not special.\nOutput filtering: if there is a file in the home directory called .sbtignore\nand this is not an interactive sbt session, the file is treated as a list of\nbash regular expressions. Output lines which match any regex are not echoed.\nOne can see exactly which lines would have been suppressed by starting this\nrunner with the -x option.\n  -h | -help         print this message\n  -v                 verbose operation (this runner is chattier)\n  -d, -w, -q         aliases for --debug, --warn, --error (q means quiet)\n  -x                 debug this script\n  -trace <level>     display stack traces with a max of <level> frames (default: -1, traces suppressed)\n  -debug-inc         enable debugging log for the incremental compiler\n  -no-colors         disable ANSI color codes\n  -sbt-create        start sbt even if current directory contains no sbt project\n  -sbt-dir   <path>  path to global settings/plugins directory (default: ~/.sbt/<version>)\n  -sbt-boot  <path>  path to shared boot directory (default: ~/.sbt/boot in 0.11+)\n  -ivy       <path>  path to local Ivy repository (default: ~/.ivy2)\n  -no-share          use all local caches; no sharing\n  -offline           put sbt in offline mode\n  -jvm-debug <port>  Turn on JVM debugging, open at the given port.\n  -batch             Disable interactive mode\n  -prompt <expr>     Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted\n  # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version)\n  -sbt-force-latest         force the use of the latest release of sbt: $sbt_release_version\n  -sbt-version  <version>   use the specified version of sbt (default: $sbt_release_version)\n  -sbt-dev                  use the latest pre-release version of sbt: $sbt_unreleased_version\n  -sbt-jar      <path>      use the specified jar as the sbt launcher\n  -sbt-launch-dir <path>    directory to hold sbt launchers (default: $sbt_launch_dir)\n  -sbt-launch-repo <url>    repo url for downloading sbt launcher jar (default: $(url_base \"$sbt_version\"))\n  # scala version (default: as chosen by sbt)\n  -28                       use $latest_28\n  -29                       use $latest_29\n  -210                      use $latest_210\n  -211                      use $latest_211\n  -212                      use $latest_212\n  -scala-home <path>        use the scala build at the specified directory\n  -scala-version <version>  use the specified version of scala\n  -binary-version <version> use the specified scala version when searching for dependencies\n  # java version (default: java from PATH, currently $(java -version 2>&1 | grep version))\n  -java-home <path>         alternate JAVA_HOME\n  # passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution\n  # The default set is used if JVM_OPTS is unset and no -jvm-opts file is found\n  <default>        $(default_jvm_opts)\n  JVM_OPTS         environment variable holding either the jvm args directly, or\n                   the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts')\n                   Note: \"@\"-file is overridden by local '.jvmopts' or '-jvm-opts' argument.\n  -jvm-opts <path> file containing jvm args (if not given, .jvmopts in project root is used if present)\n  -Dkey=val        pass -Dkey=val directly to the jvm\n  -J-X             pass option -X directly to the jvm (-J is stripped)\n  # passing options to sbt, OR to this runner\n  SBT_OPTS         environment variable holding either the sbt args directly, or\n                   the reference to a file containing sbt args if given path is prepended by '@' (e.g. '@/etc/sbtopts')\n                   Note: \"@\"-file is overridden by local '.sbtopts' or '-sbt-opts' argument.\n  -sbt-opts <path> file containing sbt args (if not given, .sbtopts in project root is used if present)\n  -S-X             add -X to sbt's scalacOptions (-S is stripped)\nEOM\n}\n\nprocess_args () {\n  require_arg () {\n    local type=\"$1\"\n    local opt=\"$2\"\n    local arg=\"$3\"\n\n    if [[ -z \"$arg\" ]] || [[ \"${arg:0:1}\" == \"-\" ]]; then\n      die \"$opt requires <$type> argument\"\n    fi\n  }\n  while [[ $# -gt 0 ]]; do\n    case \"$1\" in\n          -h|-help) usage; exit 1 ;;\n                -v) verbose=true && shift ;;\n                -d) addSbt \"--debug\" && addSbt debug && shift ;;\n                -w) addSbt \"--warn\"  && addSbt warn  && shift ;;\n                -q) addSbt \"--error\" && addSbt error && shift ;;\n                -x) debugUs=true && shift ;;\n            -trace) require_arg integer \"$1\" \"$2\" && trace_level=\"$2\" && shift 2 ;;\n              -ivy) require_arg path \"$1\" \"$2\" && addJava \"-Dsbt.ivy.home=$2\" && shift 2 ;;\n        -no-colors) addJava \"-Dsbt.log.noformat=true\" && shift ;;\n         -no-share) noshare=true && shift ;;\n         -sbt-boot) require_arg path \"$1\" \"$2\" && addJava \"-Dsbt.boot.directory=$2\" && shift 2 ;;\n          -sbt-dir) require_arg path \"$1\" \"$2\" && sbt_dir=\"$2\" && shift 2 ;;\n        -debug-inc) addJava \"-Dxsbt.inc.debug=true\" && shift ;;\n          -offline) addSbt \"set offline := true\" && shift ;;\n        -jvm-debug) require_arg port \"$1\" \"$2\" && addDebugger \"$2\" && shift 2 ;;\n            -batch) batch=true && shift ;;\n           -prompt) require_arg \"expr\" \"$1\" \"$2\" && setThisBuild shellPrompt \"(s => { val e = Project.extract(s) ; $2 })\" && shift 2 ;;\n\n       -sbt-create) sbt_create=true && shift ;;\n          -sbt-jar) require_arg path \"$1\" \"$2\" && sbt_jar=\"$2\" && shift 2 ;;\n      -sbt-version) require_arg version \"$1\" \"$2\" && sbt_explicit_version=\"$2\" && shift 2 ;;\n -sbt-force-latest) sbt_explicit_version=\"$sbt_release_version\" && shift ;;\n          -sbt-dev) sbt_explicit_version=\"$sbt_unreleased_version\" && shift ;;\n   -sbt-launch-dir) require_arg path \"$1\" \"$2\" && sbt_launch_dir=\"$2\" && shift 2 ;;\n  -sbt-launch-repo) require_arg path \"$1\" \"$2\" && sbt_launch_repo=\"$2\" && shift 2 ;;\n    -scala-version) require_arg version \"$1\" \"$2\" && setScalaVersion \"$2\" && shift 2 ;;\n   -binary-version) require_arg version \"$1\" \"$2\" && setThisBuild scalaBinaryVersion \"\\\"$2\\\"\" && shift 2 ;;\n       -scala-home) require_arg path \"$1\" \"$2\" && setThisBuild scalaHome \"scala.Some(file(\\\"$2\\\"))\" && shift 2 ;;\n        -java-home) require_arg path \"$1\" \"$2\" && setJavaHome \"$2\" && shift 2 ;;\n         -sbt-opts) require_arg path \"$1\" \"$2\" && sbt_opts_file=\"$2\" && shift 2 ;;\n         -jvm-opts) require_arg path \"$1\" \"$2\" && jvm_opts_file=\"$2\" && shift 2 ;;\n\n               -D*) addJava \"$1\" && shift ;;\n               -J*) addJava \"${1:2}\" && shift ;;\n               -S*) addScalac \"${1:2}\" && shift ;;\n               -28) setScalaVersion \"$latest_28\" && shift ;;\n               -29) setScalaVersion \"$latest_29\" && shift ;;\n              -210) setScalaVersion \"$latest_210\" && shift ;;\n              -211) setScalaVersion \"$latest_211\" && shift ;;\n              -212) setScalaVersion \"$latest_212\" && shift ;;\n\n           --debug) addSbt debug && addResidual \"$1\" && shift ;;\n            --warn) addSbt warn  && addResidual \"$1\" && shift ;;\n           --error) addSbt error && addResidual \"$1\" && shift ;;\n                 *) addResidual \"$1\" && shift ;;\n    esac\n  done\n}\n\n# process the direct command line arguments\nprocess_args \"$@\"\n\n# skip #-styled comments and blank lines\nreadConfigFile() {\n  local end=false\n  until $end; do\n    read || end=true\n    [[ $REPLY =~ ^# ]] || [[ -z $REPLY ]] || echo \"$REPLY\"\n  done < \"$1\"\n}\n\n# if there are file/environment sbt_opts, process again so we\n# can supply args to this runner\nif [[ -r \"$sbt_opts_file\" ]]; then\n  vlog \"Using sbt options defined in file $sbt_opts_file\"\n  while read opt; do extra_sbt_opts+=(\"$opt\"); done < <(readConfigFile \"$sbt_opts_file\")\nelif [[ -n \"$SBT_OPTS\" && ! (\"$SBT_OPTS\" =~ ^@.*) ]]; then\n  vlog \"Using sbt options defined in variable \\$SBT_OPTS\"\n  extra_sbt_opts=( $SBT_OPTS )\nelse\n  vlog \"No extra sbt options have been defined\"\nfi\n\n[[ -n \"${extra_sbt_opts[*]}\" ]] && process_args \"${extra_sbt_opts[@]}\"\n\n# reset \"$@\" to the residual args\nset -- \"${residual_args[@]}\"\nargumentCount=$#\n\n# set sbt version\nset_sbt_version\n\n# only exists in 0.12+\nsetTraceLevel() {\n  case \"$sbt_version\" in\n    \"0.7.\"* | \"0.10.\"* | \"0.11.\"* ) echoerr \"Cannot set trace level in sbt version $sbt_version\" ;;\n                                 *) setThisBuild traceLevel $trace_level ;;\n  esac\n}\n\n# set scalacOptions if we were given any -S opts\n[[ ${#scalac_args[@]} -eq 0 ]] || addSbt \"set scalacOptions in ThisBuild += \\\"${scalac_args[@]}\\\"\"\n\n# Update build.properties on disk to set explicit version - sbt gives us no choice\n[[ -n \"$sbt_explicit_version\" ]] && update_build_props_sbt \"$sbt_explicit_version\"\nvlog \"Detected sbt version $sbt_version\"\n\n[[ -n \"$scala_version\" ]] && vlog \"Overriding scala version to $scala_version\"\n\n# no args - alert them there's stuff in here\n(( argumentCount > 0 )) || {\n  vlog \"Starting $script_name: invoke with -help for other options\"\n  residual_args=( shell )\n}\n\n# verify this is an sbt dir or -create was given\n[[ -r ./build.sbt || -d ./project || -n \"$sbt_create\" ]] || {\n  cat <<EOM\n$(pwd) doesn't appear to be an sbt project.\nIf you want to start sbt anyway, run:\n  $0 -sbt-create\nEOM\n  exit 1\n}\n\n# pick up completion if present; todo\n[[ -r .sbt_completion.sh ]] && source .sbt_completion.sh\n\n# no jar? download it.\n[[ -r \"$sbt_jar\" ]] || acquire_sbt_jar || {\n  # still no jar? uh-oh.\n  echo \"Download failed. Obtain the jar manually and place it at $sbt_jar\"\n  exit 1\n}\n\nif [[ -n \"$noshare\" ]]; then\n  for opt in ${noshare_opts}; do\n    addJava \"$opt\"\n  done\nelse\n  case \"$sbt_version\" in\n    \"0.7.\"* | \"0.10.\"* | \"0.11.\"* | \"0.12.\"* )\n      [[ -n \"$sbt_dir\" ]] || {\n        sbt_dir=\"$HOME/.sbt/$sbt_version\"\n        vlog \"Using $sbt_dir as sbt dir, -sbt-dir to override.\"\n      }\n    ;;\n  esac\n\n  if [[ -n \"$sbt_dir\" ]]; then\n    addJava \"-Dsbt.global.base=$sbt_dir\"\n  fi\nfi\n\nif [[ -r \"$jvm_opts_file\" ]]; then\n  vlog \"Using jvm options defined in file $jvm_opts_file\"\n  while read opt; do extra_jvm_opts+=(\"$opt\"); done < <(readConfigFile \"$jvm_opts_file\")\nelif [[ -n \"$JVM_OPTS\" && ! (\"$JVM_OPTS\" =~ ^@.*) ]]; then\n  vlog \"Using jvm options defined in \\$JVM_OPTS variable\"\n  extra_jvm_opts=( $JVM_OPTS )\nelse\n  vlog \"Using default jvm options\"\n  extra_jvm_opts=( $(default_jvm_opts) )\nfi\n\n# traceLevel is 0.12+\n[[ -n \"$trace_level\" ]] && setTraceLevel\n\nmain () {\n  execRunner \"$java_cmd\" \\\n    \"${extra_jvm_opts[@]}\" \\\n    \"${java_args[@]}\" \\\n    -jar \"$sbt_jar\" \\\n    \"${sbt_commands[@]}\" \\\n    \"${residual_args[@]}\"\n}\n\n# sbt inserts this string on certain lines when formatting is enabled:\n#   val OverwriteLine = \"\\r\\u001BM\\u001B[2K\"\n# ...in order not to spam the console with a million \"Resolving\" lines.\n# Unfortunately that makes it that much harder to work with when\n# we're not going to print those lines anyway. We strip that bit of\n# line noise, but leave the other codes to preserve color.\nmainFiltered () {\n  local ansiOverwrite='\\r\\x1BM\\x1B[2K'\n  local excludeRegex=$(egrep -v '^#|^$' ~/.sbtignore | paste -sd'|' -)\n\n  echoLine () {\n    local line=\"$1\"\n    local line1=\"$(echo \"$line\" | sed 's/\\r\\x1BM\\x1B\\[2K//g')\"       # This strips the OverwriteLine code.\n    local line2=\"$(echo \"$line1\" | sed 's/\\x1B\\[[0-9;]*[JKmsu]//g')\" # This strips all codes - we test regexes against this.\n\n    if [[ $line2 =~ $excludeRegex ]]; then\n      [[ -n $debugUs ]] && echo \"[X] $line1\"\n    else\n      [[ -n $debugUs ]] && echo \"    $line1\" || echo \"$line1\"\n    fi\n  }\n\n  echoLine \"Starting sbt with output filtering enabled.\"\n  main | while read -r line; do echoLine \"$line\"; done\n}\n\n# Only filter if there's a filter file and we don't see a known interactive command.\n# Obviously this is super ad hoc but I don't know how to improve on it. Testing whether\n# stdin is a terminal is useless because most of my use cases for this filtering are\n# exactly when I'm at a terminal, running sbt non-interactively.\nshouldFilter () { [[ -f ~/.sbtignore ]] && ! egrep -q '\\b(shell|console|consoleProject)\\b' <<<\"${residual_args[@]}\"; }\n\n# run sbt\nif shouldFilter; then mainFiltered; else main; fi"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/HttpClient.scala",
    "content": "package spinoco.fs2.http\n\nimport java.nio.channels.AsynchronousChannelGroup\nimport java.util.concurrent.TimeUnit\n\nimport cats.Applicative\nimport javax.net.ssl.SSLContext\nimport cats.effect._\nimport fs2._\nimport fs2.concurrent.SignallingRef\nimport fs2.io.tcp.Socket\nimport scodec.{Codec, Decoder, Encoder}\nimport spinoco.fs2.http.internal.{addressForRequest, clientLiftToSecure, readWithTimeout}\nimport spinoco.fs2.http.sse.{SSEDecoder, SSEEncoding}\nimport spinoco.fs2.http.websocket.{Frame, WebSocket, WebSocketRequest}\nimport spinoco.protocol.http.header._\nimport spinoco.protocol.mime.MediaType\nimport spinoco.protocol.http.{HttpRequestHeader, HttpResponseHeader}\n\nimport scala.concurrent.ExecutionContext\nimport scala.concurrent.duration._\n\n\ntrait HttpClient[F[_]] {\n\n  /**\n    * Performs a single `request`. Returns one response if client replied.\n    *\n    * Note that request may contain stream of bytes that shall be sent to client.\n    * The response from server is evaluated _after_ client sent all data, including the body to the server.\n    *\n    * Note that the evaluation of `body` in HttpResponse may not outlive scope of resulting stream. That means\n    * only correct way to process the result is within the flatMap i.e.\n    *  `\n    *  request(thatRequest).flatMap { response =>\n    *    response.body.through(bodyProcessor)\n    *  }\n    *  `\n    *\n    * This methods allows to be supplied with timeout (default is 5s) that the request awaits to be completed before\n    * failure.\n    *\n    * Timeout is computed once the requests was sent and includes also the time for processing the response header\n    * but not the body.\n    *\n    * Resulting stream fails with TimeoutException if the timeout is triggered\n    *\n    * @param request        Request to make to server\n    * @param chunkSize      Size of the chunk to used when receiving response from server\n    * @param timeout        Request will fail if response header and response body is not received within supplied timeout\n    *\n    */\n  def request(\n     request: HttpRequest[F]\n     , chunkSize: Int = 32*1024\n     , maxResponseHeaderSize: Int = 4096\n     , timeout: Duration = 5.seconds\n  ):Stream[F,HttpResponse[F]]\n\n\n  /**\n    * Establishes websocket connection to the server.\n    *\n    * Implementation is according to RFC-6455 (https://tools.ietf.org/html/rfc6455).\n    *\n    * If this is established successfully, then this consults `pipe` to receive/sent any frames\n    * From/To server. Once the connection finishes, this will emit once None.\n    *\n    * If the connection was not established correctly (i.e. Authorization failure) this will not\n    * consult supplied pipe and instead this will immediately emit response received from the server.\n    *\n    * @param request              WebSocket request\n    * @param pipe                 Pipe that is consulted when WebSocket is established correctly\n    * @param maxResponseHeaderSize  Max size of  Http Response header received\n    * @param chunkSize            Size of receive buffer to use\n    * @param maxFrameSize         Maximum size of single WebSocket frame. If the binary size of single frame is larger than\n    *                             supplied value, WebSocket will fail.\n    *\n    */\n  def websocket[I : Decoder, O : Encoder](\n     request: WebSocketRequest\n     , pipe: Pipe[F, Frame[I], Frame[O]]\n     , maxResponseHeaderSize: Int = 4096\n     , chunkSize: Int = 32 * 1024\n     , maxFrameSize: Int = 1024*1024\n  ): Stream[F, Option[HttpResponseHeader]]\n\n  /**\n    * Reads SSE encoded stream of data from the server.\n    *\n    * @param request                  Request to server. Note that this must be `GET` request.\n    * @param maxResponseHeaderSize    Max size of expected response header\n    * @param chunkSize                Max size of the chunk\n    */\n  def sse[A : SSEDecoder](\n    request: HttpRequest[F]\n    , maxResponseHeaderSize: Int = 4096\n    , chunkSize: Int = 32 * 1024\n  ): Stream[F, A]\n\n}\n\n\n object HttpClient {\n\n\n   /**\n     * Creates an Http Client\n     * @param requestCodec    Codec used to decode request header\n     * @param responseCodec   Codec used to encode response header\n     * @param sslExecutionContext     Strategy used when communication with SSL (https or wss)\n     * @param sslContext      SSL Context to use with SSL Client (https, wss)\n     */\n  def apply[F[_] : ConcurrentEffect : ContextShift : Timer](\n   requestCodec         : Codec[HttpRequestHeader]\n   , responseCodec      : Codec[HttpResponseHeader]\n   , sslExecutionContext: => ExecutionContext\n   , sslContext         : => SSLContext\n  )(implicit AG: AsynchronousChannelGroup):F[HttpClient[F]] = Sync[F].delay {\n    lazy val sslCtx = sslContext\n    lazy val sslS = sslExecutionContext\n\n    new HttpClient[F] {\n      def request(\n       request: HttpRequest[F]\n       , chunkSize: Int\n       , maxResponseHeaderSize: Int\n       , timeout: Duration\n      ): Stream[F, HttpResponse[F]] = {\n        Stream.eval(addressForRequest[F](request.scheme, request.host)).flatMap { address =>\n        Stream.resource(io.tcp.client[F](address))\n        .evalMap { socket =>\n          if (!request.isSecure) Applicative[F].pure(socket)\n          else clientLiftToSecure[F](sslS, sslCtx)(socket, request.host)\n        }\n        .flatMap { impl.request[F](request, chunkSize, maxResponseHeaderSize, timeout, requestCodec, responseCodec ) }}\n      }\n\n      def websocket[I : Decoder, O : Encoder](\n        request: WebSocketRequest\n        , pipe: Pipe[F, Frame[I], Frame[O]]\n        , maxResponseHeaderSize: Int\n        , chunkSize: Int\n        , maxFrameSize: Int\n      ): Stream[F, Option[HttpResponseHeader]] =\n        WebSocket.client(request,pipe,maxResponseHeaderSize,chunkSize,maxFrameSize, requestCodec, responseCodec, sslS, sslCtx)\n\n\n      def sse[A : SSEDecoder](rq: HttpRequest[F], maxResponseHeaderSize: Int, chunkSize: Int): Stream[F, A] =\n        request(rq, chunkSize, maxResponseHeaderSize, Duration.Inf).flatMap { resp =>\n          if (resp.header.headers.exists { \n              case `Content-Type`(ct) => ct.mediaType == MediaType.`text/event-stream`\n              case _ => false\n            })\n            resp.body through SSEEncoding.decodeA[F, A]\n          else\n            Stream.raiseError(new Throwable(s\"Received response is not SSE: $resp\"))\n        }\n    }\n  }\n\n\n   private[http] object impl {\n\n     def request[F[_] : Concurrent](\n      request: HttpRequest[F]\n      , chunkSize: Int\n      , maxResponseHeaderSize: Int\n      , timeout: Duration\n      , requestCodec: Codec[HttpRequestHeader]\n      , responseCodec: Codec[HttpResponseHeader]\n     )(socket: Socket[F])(implicit clock: Clock[F]):Stream[F, HttpResponse[F]] = {\n       import Stream._\n       timeout match {\n         case fin: FiniteDuration =>\n           eval(clock.realTime(TimeUnit.MILLISECONDS)).flatMap { start =>\n           HttpRequest.toStream(request, requestCodec).to(socket.writes(Some(fin))).last.onFinalize(socket.endOfOutput).flatMap { _ =>\n           eval(SignallingRef[F, Boolean](true)).flatMap { timeoutSignal =>\n           eval(clock.realTime(TimeUnit.MILLISECONDS)).flatMap { sent =>\n             val remains = fin - (sent - start).millis\n             readWithTimeout(socket, remains, timeoutSignal.get, chunkSize)\n             .through (HttpResponse.fromStream[F](maxResponseHeaderSize, responseCodec))\n             .flatMap { response =>\n               eval_(timeoutSignal.set(false)) ++ emit(response)\n             }\n           }}}}\n\n         case _ =>\n           HttpRequest.toStream(request, requestCodec).to(socket.writes(None)).last.onFinalize(socket.endOfOutput).flatMap { _ =>\n             socket.reads(chunkSize, None) through HttpResponse.fromStream[F](maxResponseHeaderSize, responseCodec)\n           }\n       }\n     }\n\n   }\n\n\n}\n\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/HttpRequestOrResponse.scala",
    "content": "package spinoco.fs2.http\n\n\nimport cats.effect.Sync\nimport fs2.Chunk.ByteVectorChunk\nimport fs2.{Stream, _}\nimport scodec.Attempt.{Failure, Successful}\nimport scodec.{Attempt, Codec, Err}\n\nimport spinoco.fs2.http.body.{BodyDecoder, BodyEncoder, StreamBodyEncoder}\nimport spinoco.protocol.http._\nimport header._\nimport spinoco.protocol.mime.{ContentType, MediaType}\nimport scodec.bits.ByteVector\n\nimport spinoco.fs2.http.sse.{SSEEncoder, SSEEncoding}\n\n\n/** common request/response methods **/\nsealed trait HttpRequestOrResponse[F[_]] { self =>\n  type Self <: HttpRequestOrResponse[F]\n\n  /** yields to true, if body of this request shall be chunked **/\n  lazy val bodyIsChunked : Boolean =\n    withHeaders(internal.bodyIsChunked)\n\n  /** allows to stream arbitrary sized stream of `A` to remote party (i.e. upload) **/\n  def withStreamBody[A](body: Stream[F, A])(implicit E: StreamBodyEncoder[F, A]): Self = {\n    updateBody(body through E.encode)\n    .withContentType(E.contentType)\n    .asInstanceOf[Self]\n  }\n\n  /** like `stream` except one `A` that is streamed lazily **/\n  def withStreamBody1[A](a: => A)(implicit E: StreamBodyEncoder[F, A]): Self =\n    withStreamBody(Stream.suspend(Stream.emit(a)))\n\n  /** sets body size to supplied value **/\n  def withBodySize(sz: Long): Self =\n    updateHeaders(withHeaders(internal.swapHeader(`Content-Length`(sz))))\n\n  /** gets body size, if one specified **/\n  def bodySize: Option[Long] =\n    withHeaders(_.collectFirst { case `Content-Length`(sz) => sz })\n\n  protected def body: Stream[F, Byte]\n\n  /** encodes body `A` given BodyEncoder exists **/\n  def withBody[A](a: A)(implicit W: BodyEncoder[A], ev: RaiseThrowable[F]): Self = {\n    W.encode(a) match {\n      case Failure(err) => updateBody(body = Stream.raiseError(new Throwable(s\"failed to encode $a: $err\")))\n      case Successful(bytes) =>\n        val headers = withHeaders {\n           _.filterNot { h => h.isInstanceOf[`Content-Type`] || h.isInstanceOf[`Content-Length`] } ++\n            List(`Content-Type`(W.contentType), `Content-Length`(bytes.size))\n        }\n\n        updateBody(Stream.chunk(ByteVectorChunk(bytes)))\n        .updateHeaders(headers)\n        .asInstanceOf[Self]\n    }\n  }\n\n  /** encodes body as utf8 string **/\n  def withUtf8Body(s: String)(implicit ev: RaiseThrowable[F]): Self =\n    withBody(s)(BodyEncoder.utf8String, ev)\n\n  /** Decodes body with supplied decoder of `A` **/\n  def bodyAs[A](implicit D: BodyDecoder[A], F: Sync[F]): F[Attempt[A]] = {\n    withHeaders { _.collectFirst { case `Content-Type`(ct) => ct } match {\n      case None => F.pure(Attempt.failure(Err(\"Content type is not known\")))\n      case Some(ct) =>\n        F.map(self.body.chunks.map(util.chunk2ByteVector).compile.toVector) { bs =>\n          if (bs.isEmpty) Attempt.failure(Err(\"Body is empty\"))\n          else D.decode(bs.reduce(_ ++ _), ct)\n        }\n    }}\n  }\n\n  /** gets body as stream of byteVectors **/\n  def bodyAsByteVectorStream:Stream[F,ByteVector] =\n    self.body.chunks.map(util.chunk2ByteVector)\n\n  /** decodes body as string with encoding supplied in ContentType **/\n  def bodyAsString(implicit F: Sync[F]): F[Attempt[String]] =\n    bodyAs[String](BodyDecoder.stringDecoder, F)\n\n  /** updates content type to one specified **/\n  def withContentType(ct: ContentType): Self =\n    updateHeaders(withHeaders(internal.swapHeader(`Content-Type`(ct))))\n\n  /** gets ContentType, if one specififed **/\n  def contentType: Option[ContentType] =\n    withHeaders(_.collectFirst{ case `Content-Type`(ct) => ct })\n\n\n  /** configures encoding as chunked **/\n  def chunkedEncoding: Self =\n    updateHeaders(withHeaders(internal.swapHeader(`Transfer-Encoding`(List(\"chunked\")))))\n\n  def withHeaders[A](f: List[HttpHeader] => A): A = self match {\n    case HttpRequest(_,_,header,_) => f(header.headers)\n    case HttpResponse(header, _) => f(header.headers)\n  }\n\n  /** appends supplied headers **/\n  def appendHeader(header: HttpHeader, headers: HttpHeader*): Self =\n    updateHeaders(withHeaders(_ ++ (header +: headers.toSeq)))\n\n  /** appends supplied headers. Unlike `appendHeader` headers are removed if they already exists **/\n  def withHeader(header : HttpHeader, headers: HttpHeader*): Self =\n    updateHeaders(withHeaders { current =>\n      val allNew = header +: headers\n      val allNewKeys = allNew.map(_.name.toLowerCase).toSet\n      current.filterNot(h => allNewKeys.contains(h.name.toLowerCase)) ++ allNew\n    })\n\n  protected def updateBody(body: Stream[F, Byte]): Self\n\n  protected def updateHeaders(headers: List[HttpHeader]): Self\n\n}\n\n\n\n\n\n/**\n  * Model of Http Request sent by client.\n  *\n  * @param host     Host/port where to perform the request to\n  * @param header   Header of the request\n  * @param body     Body of the request. If empty, no body will be emitted.\n  */\nfinal case class HttpRequest[F[_]](\n scheme: Scheme\n , host: HostPort\n , header: HttpRequestHeader\n , body: Stream[F, Byte]\n) extends HttpRequestOrResponse[F]  { self =>\n\n  type Self = HttpRequest[F]\n\n  def withMethod(method: HttpMethod.Value): HttpRequest[F] = {\n    self.copy(header = self.header.copy(method = method))\n  }\n\n  def isSecure: Boolean = scheme match {\n    case HttpScheme.HTTPS | HttpScheme.WSS => true\n    case _ => false\n  }\n\n  protected def updateBody(body: Stream[F, Byte]): Self =\n    self.copy(body = body)\n\n  protected def updateHeaders(headers: List[HttpHeader]): Self =\n    self.copy(header = self.header.copy(headers = headers))\n\n  /**\n    * Encodes query params to body as `application/x-www-form-urlencoded` content.\n    * That means instead of passing query as part of request, they are encoded as utf8 body.\n    * @return\n    */\n  def withQueryBodyEncoded(q:Uri.Query)(implicit ev: RaiseThrowable[F]): Self =\n    withBody(q)(BodyEncoder.`x-www-form-urlencoded`, ev)\n\n  def bodyAsQuery(implicit F: Sync[F]):F[Attempt[Uri.Query]] =\n    bodyAs[Uri.Query](BodyDecoder.`x-www-form-urlencoded`, F)\n\n  /**\n    * Adds supplied query as param in the Uri\n    */\n  def withQuery(query:Uri.Query): Self =\n   self.copy(header = self.header.copy( query = query ))\n}\n\nobject HttpRequest {\n\n  def get[F[_]](uri:  Uri): HttpRequest[F] =\n    HttpRequest(\n      scheme = uri.scheme\n      , host = uri.host\n      , header = HttpRequestHeader(\n        method = HttpMethod.GET\n        , path = uri.path\n        , query = uri.query\n        , headers = List(\n          Host(uri.host)\n        )\n      )\n      , body = Stream.empty)\n\n  def post[F[_] : RaiseThrowable, A](uri: Uri, a: A)(implicit E: BodyEncoder[A]): HttpRequest[F] =\n    get(uri).withMethod(HttpMethod.POST).withBody(a)\n\n  def put[F[_] : RaiseThrowable, A](uri: Uri, a: A)(implicit E: BodyEncoder[A]): HttpRequest[F] =\n    get(uri).withMethod(HttpMethod.PUT).withBody(a)\n\n  def delete[F[_]](uri: Uri): HttpRequest[F] =\n    get(uri).withMethod(HttpMethod.DELETE)\n\n\n  /**\n    * Reads http header and body from the stream of bytes.\n    *\n    * If the body is encoded in chunked encoding this will decode it\n    *\n    * @param maxHeaderSize    Maximum size of the http header\n    * @param headerCodec      header codec to use\n    * @tparam F\n    * @return\n    */\n  def fromStream[F[_] : RaiseThrowable](\n    maxHeaderSize: Int\n    , headerCodec: Codec[HttpRequestHeader]\n  ): Pipe[F, Byte, (HttpRequestHeader, Stream[F, Byte])] = {\n    import internal._\n    _ through httpHeaderAndBody(maxHeaderSize) flatMap { case (header, bodyRaw) =>\n      headerCodec.decodeValue(header.bits) match {\n        case Failure(err) => Stream.raiseError(new Throwable(s\"Decoding of the request header failed: $err\"))\n        case Successful(decoded) =>\n          val body =\n            if (bodyIsChunked(decoded.headers)) bodyRaw through ChunkedEncoding.decode(1000)\n            else bodyRaw\n\n          Stream.emit(decoded -> body)\n      }\n    }\n  }\n\n\n  /**\n    * Converts the supplied request to binary stream of data to be sent over wire.\n    * Note that this inspects the headers to eventually perform chunked encoding of the stream,\n    * if that indication is present in headers,\n    *\n    * Otherwise this just encodes as binary stream of data after header of the request.\n    *\n    *\n    * @param request        request to convert to stream\n    * @param headerCodec    Codec to convert the header to bytes\n    */\n  def toStream[F[_] : RaiseThrowable](\n    request: HttpRequest[F]\n    , headerCodec: Codec[HttpRequestHeader]\n  ): Stream[F, Byte] = Stream.suspend {\n    import internal._\n\n    headerCodec.encode(request.header) match {\n      case Failure(err) => Stream.raiseError(new Throwable(s\"Encoding of the header failed: $err\"))\n      case Successful(bits) =>\n        val body =\n          if (request.bodyIsChunked)  request.body through ChunkedEncoding.encode\n          else request.body\n\n        Stream.chunk[F, Byte](ByteVectorChunk(bits.bytes ++ `\\r\\n\\r\\n`)) ++ body\n    }\n  }\n\n\n}\n\n\n/**\n* Model of Http Response\n*\n* @param header   Header of the response\n* @param body     Body of the response. If empty, no body will be emitted.\n*/\nfinal case class HttpResponse[F[_]](\n header: HttpResponseHeader\n , body: Stream[F, Byte]\n) extends HttpRequestOrResponse[F] { self =>\n  override type Self = HttpResponse[F]\n\n  protected def updateBody(body: Stream[F, Byte]): Self =\n    self.copy(body = body)\n\n  protected def updateHeaders(headers: List[HttpHeader]): Self =\n    self.copy(header= self.header.copy(headers = headers))\n\n  /** encodes supplied stream of `A` as SSE stream in body **/\n  def sseBody[A](in: Stream[F, A])(implicit E: SSEEncoder[A], ev: RaiseThrowable[F]): Self =\n     self\n     .updateBody(in through SSEEncoding.encodeA[F, A])\n     .updateHeaders(withHeaders(internal.swapHeader(`Content-Type`(ContentType.TextContent(MediaType.`text/event-stream`, None)))))\n}\n\n\nobject HttpResponse {\n\n\n  def apply[F[_]](sc: HttpStatusCode):HttpResponse[F] = {\n    HttpResponse(\n      header = HttpResponseHeader(status = sc, reason = sc.label)\n      , body = Stream.empty\n    )\n  }\n\n\n  /**\n    * Decodes stream of bytes as HttpResponse.\n    */\n  def fromStream[F[_] : RaiseThrowable](\n    maxHeaderSize: Int\n    , responseCodec: Codec[HttpResponseHeader]\n  ): Pipe[F,Byte, HttpResponse[F]] = {\n    import internal._\n\n    _ through httpHeaderAndBody(maxHeaderSize) flatMap { case (header, bodyRaw) =>\n      responseCodec.decodeValue(header.bits) match {\n        case Failure(err) => Stream.raiseError(new Throwable(s\"Failed to decode http response :$err\"))\n        case Successful(response) =>\n          val unboundedBody =\n            if (bodyIsChunked(response.headers)) bodyRaw through ChunkedEncoding.decode(1024)\n            else bodyRaw\n          val contentLengthOpt = response.headers collectFirst {\n            case `Content-Length`(value) => value\n          }\n          val body = contentLengthOpt.fold(unboundedBody)(unboundedBody.take)\n          Stream.emit(HttpResponse(response, body))\n      }\n    }\n  }\n\n\n  /** Encodes response to stream of bytes **/\n  def toStream[F[_] : RaiseThrowable](\n    response: HttpResponse[F]\n    , headerCodec: Codec[HttpResponseHeader]\n  ): Stream[F, Byte] = Stream.suspend {\n    import internal._\n\n    headerCodec.encode(response.header) match {\n      case Failure(err) => Stream.raiseError(new Throwable(s\"Failed to encode http response : $response :$err \"))\n      case Successful(encoded) =>\n        val body =\n          if (bodyIsChunked(response.header.headers)) response.body through ChunkedEncoding.encode\n          else response.body\n\n        Stream.chunk[F, Byte](ByteVectorChunk(encoded.bytes ++ `\\r\\n\\r\\n`)) ++ body\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/HttpServer.scala",
    "content": "package spinoco.fs2.http\n\nimport java.net.InetSocketAddress\nimport java.nio.channels.AsynchronousChannelGroup\n\nimport cats.effect.{ConcurrentEffect, Sync, Timer}\nimport cats.syntax.all._\nimport fs2._\nimport fs2.concurrent.SignallingRef\nimport scodec.Codec\nimport spinoco.protocol.http.codec.{HttpRequestHeaderCodec, HttpResponseHeaderCodec}\nimport spinoco.protocol.http.{HttpRequestHeader, HttpResponseHeader, HttpStatusCode}\n\nimport scala.concurrent.duration._\n\n\nobject HttpServer {\n\n  /**\n    * Creates simple http server,\n    *\n    * Serve will run after the resulting stream is run.\n    *\n    * @param bindTo                       Address and port where to bind server to\n    * @param maxConcurrent                Maximum requests to process concurrently\n    * @param receiveBufferSize            Receive buffer size for each connection\n    * @param maxHeaderSize                Maximum size of http header for incoming requests, in bytes\n    * @param requestHeaderReceiveTimeout  A timeout to await request header to be fully received.\n    *                                     Request will fail, if the header won't be read within this timeout.\n    * @param requestCodec                 Codec for Http Request Header\n    * @param service                      Pipe that defines handling of each incoming request and produces a response\n    * @param requestFailure               A function to be evaluated when server failed to read the request header.\n    *                                     This may generate the default server response on unexpected failure.\n    *                                     This is also evaluated when the server failed to process the request itself (i.e. `service` did not handle the failure )\n    * @param sendFailure                  A function to be evaluated on failure to process the the response.\n    *                                     Request is not suplied if failure happened before request was constructed.\n    *\n    */\n  def apply[F[_] : ConcurrentEffect : Timer](\n    maxConcurrent: Int = Int.MaxValue\n    , receiveBufferSize: Int = 256 * 1024\n    , maxHeaderSize: Int = 10 *1024\n    , requestHeaderReceiveTimeout: Duration = 5.seconds\n    , requestCodec: Codec[HttpRequestHeader] = HttpRequestHeaderCodec.defaultCodec\n    , responseCodec: Codec[HttpResponseHeader] = HttpResponseHeaderCodec.defaultCodec\n    , bindTo: InetSocketAddress\n    , service:  (HttpRequestHeader, Stream[F,Byte]) => Stream[F,HttpResponse[F]]\n    , requestFailure : Throwable => Stream[F, HttpResponse[F]]\n    , sendFailure: (Option[HttpRequestHeader], HttpResponse[F], Throwable) => Stream[F, Nothing]\n  )(\n    implicit\n    AG: AsynchronousChannelGroup\n  ): Stream[F, Unit] = {\n    import Stream._\n    import internal._\n    val (initial, readDuration) = requestHeaderReceiveTimeout match {\n      case fin: FiniteDuration => (true, fin)\n      case _ => (false, 0.millis)\n    }\n\n    io.tcp.server[F](bindTo, receiveBufferSize = receiveBufferSize).map { resource =>\n      Stream.resource(resource).flatMap { socket =>\n      eval(SignallingRef(initial)).flatMap { timeoutSignal =>\n        readWithTimeout[F](socket, readDuration, timeoutSignal.get, receiveBufferSize)\n        .through(HttpRequest.fromStream(maxHeaderSize, requestCodec))\n        .flatMap { case (request, body) =>\n          eval_(timeoutSignal.set(false)) ++\n          service(request, body).take(1).handleErrorWith { rsn => requestFailure(rsn).take(1) }\n          .map { resp => (request, resp) }\n        }\n        .attempt\n        .evalMap { attempt =>\n\n          def send(request:Option[HttpRequestHeader], resp: HttpResponse[F]): F[Unit] = {\n            HttpResponse.toStream(resp, responseCodec).through(socket.writes()).onFinalize(socket.endOfOutput).compile.drain.attempt flatMap {\n              case Left(err) => sendFailure(request, resp, err).compile.drain\n              case Right(()) => Sync[F].pure(())\n            }\n          }\n\n          attempt match {\n            case Right((request, response)) => send(Some(request), response)\n            case Left(err) => requestFailure(err).evalMap { send(None, _) }.compile.drain\n          }\n        }\n        .drain\n      }\n    }}.parJoin(maxConcurrent)\n\n\n  }\n\n  /** default handler for parsing request errors **/\n  def handleRequestParseError[F[_] : RaiseThrowable](err: Throwable): Stream[F, HttpResponse[F]] = {\n    Stream.suspend {\n      err.printStackTrace()\n      Stream.emit(HttpResponse[F](HttpStatusCode.BadRequest))\n    }.covary[F]\n  }\n\n  /** default handler for failures of sending request/response **/\n  def handleSendFailure[F[_]](header: Option[HttpRequestHeader], response: HttpResponse[F], err:Throwable): Stream[F, Nothing] = {\n    Stream.suspend {\n      err.printStackTrace()\n      Stream.empty\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/body/BodyDecoder.scala",
    "content": "package spinoco.fs2.http.body\n\nimport scodec.bits.ByteVector\nimport scodec.{Attempt, Decoder, Err}\nimport spinoco.protocol.http.Uri\nimport spinoco.protocol.mime.{ContentType, MIMECharset, MediaType}\nimport spinoco.fs2.http.util\n\n\ntrait BodyDecoder[A] {\n  def decode(bytes: ByteVector, contentType: ContentType): Attempt[A]\n}\n\n\nobject BodyDecoder {\n\n  @inline def apply[A](implicit instance: BodyDecoder[A]): BodyDecoder[A] = instance\n\n  def instance[A](f: (ByteVector, ContentType) => Attempt[A]): BodyDecoder[A] =\n    new BodyDecoder[A] {\n      def decode(bytes: ByteVector, contentType: ContentType): Attempt[A] =\n        f(bytes, contentType)\n    }\n\n  def forDecoder[A](f: ContentType => Attempt[Decoder[A]]): BodyDecoder[A] =\n    BodyDecoder.instance { (bs, ct) => f(ct).flatMap(_.decodeValue(bs.bits)) }\n\n  val stringDecoder: BodyDecoder[String] = BodyDecoder.instance { case (bytes, ct) =>\n    if (! ct.mediaType.isText) Attempt.Failure(Err(s\"Media Type must be text, but is ${ct.mediaType}\"))\n    else {\n      MIMECharset.asJavaCharset(util.getCharset(ct).getOrElse(MIMECharset.`UTF-8`)).flatMap { implicit chs =>\n        Attempt.fromEither(bytes.decodeString.left.map(ex => Err(s\"Failed to decode string ContentType: $ct, charset: $chs, err: ${ex.getMessage}\")))\n      }\n    }\n  }\n\n  /** decodes body as query encoded as application/x-www-form-urlencoded data **/\n  val `x-www-form-urlencoded`: BodyDecoder[Uri.Query] =\n    forDecoder { ct =>\n      if (ct.mediaType == MediaType.`application/x-www-form-urlencoded`) Attempt.successful(Uri.Query.codec)\n      else Attempt.failure(Err(s\"Unsupported content type : $ct\"))\n    }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/body/BodyEncoder.scala",
    "content": "package spinoco.fs2.http.body\n\nimport scodec.bits.ByteVector\nimport scodec.{Attempt, Encoder, Err}\nimport spinoco.protocol.http.Uri\nimport spinoco.protocol.mime.{ContentType, MIMECharset, MediaType}\n\n/**\n  * Encodes one `A` to body, strictly\n  */\nsealed trait BodyEncoder[A] { self =>\n  def encode(a: A): Attempt[ByteVector]\n  def contentType: ContentType\n\n  /** given f, converts to encoder BodyEncoder[F, B] **/\n  def mapIn[B](f: B => A): BodyEncoder[B] =\n    BodyEncoder.instance(self.contentType) { b => self.encode(f(b)) }\n\n  /** given f, converts to encoder BodyEncoder[F, B] **/\n  def mapInAttempt[B](f: B => Attempt[A]): BodyEncoder[B] =\n    BodyEncoder.instance(self.contentType) { b => f(b).flatMap(self.encode) }\n\n  def withContentType(tpe: ContentType): BodyEncoder[A] =\n    BodyEncoder.instance(tpe)(self.encode)\n}\n\n\nobject BodyEncoder {\n\n  @inline def apply[A](implicit instance: BodyEncoder[A]): BodyEncoder[A] = instance\n\n  def instance[A](tpe: ContentType)(f: A => Attempt[ByteVector]): BodyEncoder[A] =\n    new BodyEncoder[A] {\n      def encode(a: A): Attempt[ByteVector] = f(a)\n      def contentType: ContentType = tpe\n    }\n\n  def byteVector(tpe: ContentType = ContentType.BinaryContent(MediaType.`application/octet-stream`, None)): BodyEncoder[ByteVector] =\n    BodyEncoder.instance(tpe)(Attempt.successful)\n\n  val utf8String: BodyEncoder[String] =\n    BodyEncoder.instance(ContentType.TextContent(MediaType.`text/plain`, Some(MIMECharset.`UTF-8`))){ s =>\n      Attempt.fromEither(ByteVector.encodeUtf8(s).left.map { ex => Err(s\"Failed to encode string: ${ex.getMessage}, ($s)\") })\n    }\n\n  def forEncoder[A](tpe: ContentType)(codec: Encoder[A]):BodyEncoder[A] =\n    BodyEncoder.instance(tpe)(a => codec.encode(a).map(_.bytes))\n\n  /** encodes supplied query as application/x-www-form-urlencoded data **/\n  def `x-www-form-urlencoded`: BodyEncoder[Uri.Query] =\n    forEncoder(ContentType.TextContent(MediaType.`application/x-www-form-urlencoded`, None))(Uri.Query.codec)\n\n}"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/body/StreamBodyDecoder.scala",
    "content": "package spinoco.fs2.http.body\n\nimport fs2._\nimport spinoco.protocol.mime.{ContentType, MIMECharset}\nimport spinoco.fs2.http.util\n\n\nsealed trait StreamBodyDecoder[F[_], A] {\n\n  /** decodes stream with supplied content type. yields to None, if the ContentType is not of required type **/\n  def decode(ct: ContentType): Option[Pipe[F, Byte, A]]\n\n}\n\n\nobject StreamBodyDecoder {\n\n  @inline def apply[F[_], A](implicit instance: StreamBodyDecoder[F, A]): StreamBodyDecoder[F, A] = instance\n\n  def instance[F[_], A](f: ContentType => Option[Pipe[F, Byte, A]]): StreamBodyDecoder[F, A] =\n    new StreamBodyDecoder[F, A] { def decode(ct: ContentType): Option[Pipe[F, Byte, A]] = f(ct) }\n\n  def utf8StringDecoder[F[_]]: StreamBodyDecoder[F, String] =\n    StreamBodyDecoder.instance { ct =>\n      if (ct.mediaType.isText && util.getCharset(ct).contains(MIMECharset.`UTF-8`)) Some(text.utf8Decode[F])\n      else None\n    }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/body/StreamBodyEncoder.scala",
    "content": "package spinoco.fs2.http.body\n\nimport cats.MonadError\nimport fs2.Chunk.ByteVectorChunk\nimport fs2._\nimport scodec.Attempt.{Failure, Successful}\nimport scodec.bits.ByteVector\n\nimport spinoco.protocol.mime.{ContentType, MIMECharset, MediaType}\n\n\ntrait StreamBodyEncoder[F[_], A] {\n  /** an pipe to encode stram of `A` to stream of bytes **/\n  def encode: Pipe[F, A, Byte]\n\n  def contentType: ContentType\n\n  /** given f, converts to encoder BodyEncoder[F, B] **/\n  def mapIn[B](f: B => A): StreamBodyEncoder[F, B] =\n    StreamBodyEncoder.instance(contentType) { _ map f through encode }\n\n  /** given f, converts to encoder BodyEncoder[F, B] **/\n  def mapInF[B](f: B => F[A]): StreamBodyEncoder[F, B] =\n    StreamBodyEncoder.instance(contentType) { _ evalMap  f through encode }\n\n  /** changes content type of this encoder **/\n  def withContentType(tpe: ContentType): StreamBodyEncoder[F, A] =\n    StreamBodyEncoder.instance(tpe)(encode)\n\n}\n\nobject StreamBodyEncoder {\n\n  @inline def apply[F[_], A](implicit instance: StreamBodyEncoder[F, A]): StreamBodyEncoder[F, A] = instance\n\n  def instance[F[_], A](tpe: ContentType)(pipe: Pipe[F, A, Byte]): StreamBodyEncoder[F, A] =\n    new StreamBodyEncoder[F, A] {\n      def contentType: ContentType = tpe\n      def encode: Pipe[F, A, Byte] = pipe\n    }\n\n  /** encoder that encodes bytes as they come in, with `application/octet-stream` content type **/\n  def byteEncoder[F[_]] : StreamBodyEncoder[F, Byte] =\n    StreamBodyEncoder.instance(ContentType.BinaryContent(MediaType.`application/octet-stream`, None)) { identity }\n\n  /** encoder that encodes ByteVector as they come in, with `application/octet-stream` content type **/\n  def byteVectorEncoder[F[_]] : StreamBodyEncoder[F, ByteVector] =\n    StreamBodyEncoder.instance(ContentType.BinaryContent(MediaType.`application/octet-stream`, None)) { _.flatMap { bv => Stream.chunk(ByteVectorChunk(bv)) } }\n\n  /** encoder that encodes utf8 string, with `text/plain` utf8 content type **/\n  def utf8StringEncoder[F[_]](implicit F: MonadError[F, Throwable]) : StreamBodyEncoder[F, String] =\n    byteVectorEncoder mapInF[String] { s =>\n      ByteVector.encodeUtf8(s) match {\n        case Right(bv) => F.pure(bv)\n        case Left(err) => F.raiseError[ByteVector](new Throwable(s\"Failed to encode string: $err ($s) \"))\n      }\n    } withContentType ContentType.TextContent(MediaType.`text/plain`, Some(MIMECharset.`UTF-8`))\n\n  /** a convenience wrapper to convert body encoder to StreamBodyEncoder **/\n  def fromBodyEncoder[F[_] : RaiseThrowable, A](implicit E: BodyEncoder[A]):StreamBodyEncoder[F, A] =\n    StreamBodyEncoder.instance(E.contentType) { _.flatMap { a =>\n      E.encode(a) match {\n        case Failure(err) => Stream.raiseError(new Throwable(s\"Failed to encode: $err ($a)\"))\n        case Successful(bytes) => Stream.chunk(ByteVectorChunk(bytes))\n      }\n    }}\n\n\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/http.scala",
    "content": "package spinoco.fs2\n\nimport java.net.InetSocketAddress\nimport java.nio.channels.AsynchronousChannelGroup\nimport java.util.concurrent.Executors\n\nimport javax.net.ssl.SSLContext\nimport cats.effect.{ConcurrentEffect, ContextShift, Timer}\nimport fs2._\nimport scodec.Codec\nimport spinoco.protocol.http.{HttpRequestHeader, HttpResponseHeader}\nimport spinoco.protocol.http.codec.{HttpRequestHeaderCodec, HttpResponseHeaderCodec}\n\nimport scala.concurrent.ExecutionContext\nimport scala.concurrent.duration._\n\n\npackage object http {\n\n  /**\n    * Creates simple http server,\n    *\n    * Serve will run after the resulting stream is run.\n    *\n    * @param bindTo                       Address and port where to bind server to\n    * @param maxConcurrent                Maximum requests to process concurrently\n    * @param receiveBufferSize            Receive buffer size for each connection\n    * @param maxHeaderSize                Maximum size of http header for incoming requests, in bytes\n    * @param requestHeaderReceiveTimeout  A timeout to await request header to be fully received.\n    *                                     Request will fail, if the header won't be read within this timeout.\n    * @param service                      Pipe that defines handling of each incoming request and produces a response\n    */\n  def server[F[_] : ConcurrentEffect : Timer](\n     bindTo: InetSocketAddress\n     , maxConcurrent: Int = Int.MaxValue\n     , receiveBufferSize: Int = 256 * 1024\n     , maxHeaderSize: Int = 10 *1024\n     , requestHeaderReceiveTimeout: Duration = 5.seconds\n     , requestCodec: Codec[HttpRequestHeader] = HttpRequestHeaderCodec.defaultCodec\n     , responseCodec: Codec[HttpResponseHeader] = HttpResponseHeaderCodec.defaultCodec\n   )(\n     service:  (HttpRequestHeader, Stream[F,Byte]) => Stream[F,HttpResponse[F]]\n   )(implicit AG: AsynchronousChannelGroup):Stream[F,Unit] = HttpServer(\n    maxConcurrent = maxConcurrent\n    , receiveBufferSize = receiveBufferSize\n    , maxHeaderSize = maxHeaderSize\n    , requestHeaderReceiveTimeout = requestHeaderReceiveTimeout\n    , requestCodec = requestCodec\n    , responseCodec = responseCodec\n    , bindTo = bindTo\n    , service = service\n    , requestFailure = HttpServer.handleRequestParseError[F] _\n    , sendFailure = HttpServer.handleSendFailure[F] _\n  )\n\n\n  /**\n    * Creates a client that can be used to make http requests to servers\n    *\n    * @param requestCodec    Codec used to decode request header\n    * @param responseCodec   Codec used to encode response header\n    * @param sslStrategy     Strategy used to perform blocking SSL operations\n    */\n  def client[F[_]: ConcurrentEffect : ContextShift : Timer](\n   requestCodec: Codec[HttpRequestHeader] = HttpRequestHeaderCodec.defaultCodec\n   , responseCodec: Codec[HttpResponseHeader] = HttpResponseHeaderCodec.defaultCodec\n   , sslStrategy: => ExecutionContext =  ExecutionContext.fromExecutorService(Executors.newCachedThreadPool(util.mkThreadFactory(\"fs2-http-ssl\", daemon = true)))\n   , sslContext: => SSLContext = { val ctx = SSLContext.getInstance(\"TLS\"); ctx.init(null,null,null); ctx }\n  )(implicit AG: AsynchronousChannelGroup):F[HttpClient[F]] =\n    HttpClient(requestCodec, responseCodec, sslStrategy, sslContext)\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/internal/ChunkedEncoding.scala",
    "content": "package spinoco.fs2.http.internal\n\nimport fs2.Chunk.ByteVectorChunk\nimport fs2._\nimport scodec.bits.ByteVector\n\nimport spinoco.fs2.http.util.chunk2ByteVector\n\n/**\n  * Created by pach on 20/01/17.\n  */\nobject ChunkedEncoding {\n\n\n  /** decodes from the HTTP chunked encoding. After last chunk this terminates. Allows to specify max header size, after which this terminates\n    * Please see https://en.wikipedia.org/wiki/Chunked_transfer_encoding for details\n    */\n  def decode[F[_] : RaiseThrowable](maxChunkHeaderSize:Int): Pipe[F, Byte, Byte] = {\n    // on left reading the header of chunk (acting as buffer)\n    // on right reading the chunk itself, and storing remaining bytes of the chunk\n    def go(expect:Either[ByteVector,Long], in: Stream[F, Byte]): Pull[F, Byte, Unit] = {\n      in.pull.uncons.flatMap {\n        case None => Pull.done\n        case Some((h, tl)) =>\n          val bv = chunk2ByteVector(h)\n          expect match {\n            case Left(header) =>\n              val nh = header ++ bv\n              val endOfheader = nh.indexOfSlice(`\\r\\n`)\n              if (endOfheader == 0) go(expect, Stream.chunk(ByteVectorChunk(bv.drop(`\\r\\n`.size))) ++ tl) //strip any leading crlf on header, as this starts with /r/n\n              else if (endOfheader < 0 && nh.size > maxChunkHeaderSize) Pull.raiseError(new Throwable(s\"Failed to get Chunk header. Size exceeds max($maxChunkHeaderSize) : ${nh.size} ${nh.decodeUtf8}\"))\n              else if (endOfheader < 0) go(Left(nh), tl)\n              else {\n                val (hdr,rem) = nh.splitAt(endOfheader + `\\r\\n`.size)\n                readChunkedHeader(hdr.dropRight(`\\r\\n`.size)) match {\n                  case None => Pull.raiseError(new Throwable(s\"Failed to parse chunked header : ${hdr.decodeUtf8}\"))\n                  case Some(0) => Pull.done\n                  case Some(sz) => go(Right(sz), Stream.chunk(ByteVectorChunk(rem)) ++ tl)\n                }\n              }\n\n            case Right(remains) =>\n              if (remains == bv.size) Pull.output(ByteVectorChunk(bv)) >> go(Left(ByteVector.empty), tl)\n              else if (remains > bv.size) Pull.output(ByteVectorChunk(bv)) >> go(Right(remains - bv.size), tl)\n              else {\n                val (out,next) = bv.splitAt(remains.toInt)\n                Pull.output(ByteVectorChunk(out)) >> go(Left(ByteVector.empty), Stream.chunk(ByteVectorChunk(next)) ++ tl)\n              }\n          }\n\n      }\n    }\n\n    go(Left(ByteVector.empty), _) stream\n  }\n\n\n  private val lastChunk: Chunk[Byte] = ByteVectorChunk((ByteVector('0') ++ `\\r\\n` ++ `\\r\\n`).compact)\n\n  /**\n    * Encodes chunk of bytes to http chunked encoding.\n    */\n  def encode[F[_]]:Pipe[F,Byte,Byte] = {\n    def encodeChunk(bv:ByteVector):Chunk[Byte] = {\n      if (bv.isEmpty) Chunk.empty\n      else ByteVectorChunk(ByteVector.view(bv.size.toHexString.toUpperCase.getBytes) ++ `\\r\\n` ++ bv ++ `\\r\\n` )\n    }\n    _.mapChunks { ch => encodeChunk(chunk2ByteVector(ch)) } ++ Stream.chunk(lastChunk)\n  }\n\n\n\n  /** yields to size of header in case the chunked header was succesfully parsed, else yields to None **/\n  private def readChunkedHeader(hdr:ByteVector):Option[Long] = {\n    hdr.decodeUtf8.right.toOption.flatMap { s =>\n      val parts = s.split(';') // lets ignore any extensions\n      if (parts.isEmpty) None\n      else {\n        try { Some(java.lang.Long.parseLong(parts(0).trim,16))}\n        catch { case t: Throwable => None }\n      }\n    }\n  }\n\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/internal/internal.scala",
    "content": "package spinoco.fs2.http\n\nimport java.net.InetSocketAddress\nimport java.util.concurrent.TimeoutException\n\nimport javax.net.ssl.{SNIHostName, SNIServerName, SSLContext}\nimport cats.effect.{Concurrent, ContextShift, Sync}\nimport cats.syntax.all._\nimport fs2.Chunk.ByteVectorChunk\nimport fs2.Stream._\nimport fs2.io.tcp.Socket\nimport fs2.{Stream, _}\nimport scodec.bits.ByteVector\nimport spinoco.fs2.crypto.io.tcp.TLSSocket\nimport spinoco.protocol.http.{HostPort, HttpScheme, Scheme}\nimport spinoco.protocol.http.header.{HttpHeader, `Transfer-Encoding`}\n\nimport scala.concurrent.ExecutionContext\nimport scala.concurrent.duration._\nimport scala.reflect.ClassTag\n\n\npackage object internal {\n\n  val `\\n` : ByteVector = ByteVector('\\n')\n\n  val `\\r` : ByteVector = ByteVector('\\r')\n\n  val `\\r\\n`: ByteVector = ByteVector('\\r','\\n')\n\n  val `\\r\\n\\r\\n` = (`\\r\\n` ++ `\\r\\n`).compact\n\n\n\n  /** yields to true, if chunked encoding header is present **/\n  def bodyIsChunked(headers:List[HttpHeader]):Boolean = {\n    headers.exists {\n      case `Transfer-Encoding`(encodings) => encodings.exists(_.equalsIgnoreCase(\"chunked\"))\n      case _ => false\n    }\n  }\n\n\n\n  /**\n    * From the stream of bytes this extracts Http Header and body part.\n    */\n  def httpHeaderAndBody[F[_] : RaiseThrowable](maxHeaderSize: Int): Pipe[F, Byte, (ByteVector, Stream[F, Byte])] = {\n    def go(buff: ByteVector, in: Stream[F, Byte]): Pull[F, (ByteVector, Stream[F, Byte]), Unit] = {\n      in.pull.uncons flatMap {\n        case None =>\n          Pull.raiseError(new Throwable(s\"Incomplete Header received (sz = ${buff.size}): ${buff.decodeUtf8}\"))\n        case Some((chunk, tl)) =>\n          val bv = spinoco.fs2.http.util.chunk2ByteVector(chunk)\n          val all = buff ++ bv\n          val idx = all.indexOfSlice(`\\r\\n\\r\\n`)\n          if (idx < 0) {\n            if (all.size > maxHeaderSize) Pull.raiseError(new Throwable(s\"Size of the header exceeded the limit of $maxHeaderSize (${all.size})\"))\n            else go(all, tl)\n          }\n          else {\n            val (h, t) = all.splitAt(idx)\n            if (h.size > maxHeaderSize)  Pull.raiseError(new Throwable(s\"Size of the header exceeded the limit of $maxHeaderSize (${all.size})\"))\n            else  Pull.output1((h, Stream.chunk(ByteVectorChunk(t.drop(`\\r\\n\\r\\n`.size))) ++ tl))\n\n          }\n      }\n    }\n\n    src => go(ByteVector.empty, src) stream\n  }\n\n\n  /** evaluates address from the host port and scheme, if this is a custom scheme we will default to port 8080**/\n  def addressForRequest[F[_] : Sync](scheme: Scheme, host: HostPort):F[InetSocketAddress] = Sync[F].delay {\n    val port = host.port.getOrElse {\n      scheme match {\n        case HttpScheme.HTTPS | HttpScheme.WSS => 443\n        case HttpScheme.HTTP | HttpScheme.WS => 80\n        case _ => 8080\n      }\n    }\n\n    new InetSocketAddress(host.host, port)\n  }\n\n  /** swaps header `H` for new value. If header exists, it is discarded. Appends header to the end**/\n  def swapHeader[H <: HttpHeader](header: H)(headers: List[HttpHeader])(implicit CT: ClassTag[H]) : List[HttpHeader] = {\n    headers.filterNot(CT.runtimeClass.isInstance) :+ header\n  }\n\n  /**\n    * Reads from supplied socket with timeout until `shallTimeout` yields to true.\n    * @param socket         A socket to read from\n    * @param timeout        A timeout\n    * @param shallTimeout   If true, timeout will be applied, if false timeout won't be applied.\n    * @param chunkSize      Size of chunk to read up to\n    */\n  def readWithTimeout[F[_] : Sync](\n    socket: Socket[F]\n    , timeout: FiniteDuration\n    , shallTimeout: F[Boolean]\n    , chunkSize: Int\n  ) : Stream[F, Byte] = {\n    def go(remains:FiniteDuration) : Stream[F, Byte] = {\n      eval(shallTimeout).flatMap { shallTimeout =>\n        if (!shallTimeout) socket.reads(chunkSize, None)\n        else {\n          if (remains <= 0.millis) Stream.raiseError(new TimeoutException())\n          else {\n            eval(Sync[F].delay(System.currentTimeMillis())).flatMap { start =>\n            eval(socket.read(chunkSize, Some(remains))).flatMap { read =>\n            eval(Sync[F].delay(System.currentTimeMillis())).flatMap { end => read match {\n              case Some(bytes) => Stream.chunk(bytes) ++ go(remains - (end - start).millis)\n              case None => Stream.empty\n            }}}}\n          }\n        }\n      }\n    }\n\n    go(timeout)\n  }\n\n  /** creates a function that lifts supplied socket to secure socket **/\n  def clientLiftToSecure[F[_] : Concurrent : ContextShift](sslES: => ExecutionContext, sslContext: => SSLContext)(socket: Socket[F], server: HostPort): F[Socket[F]] = {\n    import collection.JavaConverters._\n    Sync[F].delay {\n      val engine = sslContext.createSSLEngine(server.host, server.port.getOrElse(443))\n      val sslParams = engine.getSSLParameters\n      sslParams.setServerNames(List[SNIServerName](new SNIHostName(server.host)).asJava)\n      engine.setSSLParameters(sslParams)\n      engine.setUseClientMode(true)\n      engine\n    } flatMap {\n      TLSSocket.instance(socket, _, sslES)\n      .map(identity) //This is here just to make scala understand types properly\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/routing/MatchResult.scala",
    "content": "package spinoco.fs2.http.routing\n\n\nimport spinoco.fs2.http.HttpResponse\nimport spinoco.protocol.http.HttpStatusCode\n\n\ntrait MatchResult[+F[_],+A] { self =>\n  import MatchResult._\n\n  def map[B](f: A => B): MatchResult[F, B] = self match {\n    case Success(a) => Success(f(a))\n    case fail@Failed(_) => fail\n  }\n\n  def isSuccess: Boolean = self match {\n    case Success(_) => true\n    case _ => false\n  }\n\n  def isFailure: Boolean = ! isSuccess\n\n  def covary[F0[_] >: F[_]]: MatchResult[F0, A] = self.asInstanceOf[MatchResult[F0, A]]\n\n}\n\nobject MatchResult {\n\n  implicit class MatchResultInvariantSyntax[F[_], A](val self: MatchResult[F,A]) extends AnyVal {\n    def fold[B](fa: HttpResponse[F] => B, fb: A => B):B = self match {\n      case Success(a) => fb(a)\n      case Failed(resp) => fa(resp.asInstanceOf[HttpResponse[F]])\n    }\n  }\n\n  case class Success[A](result: A) extends MatchResult[Nothing, A]\n\n  case class Failed[F[_]](response: HttpResponse[F]) extends MatchResult[F, Nothing]\n\n  def success[A](a: A) : MatchResult[Nothing, A] = Success(a)\n\n  def reply(code: HttpStatusCode):MatchResult[Nothing,Nothing] =\n    Failed[Nothing](HttpResponse[Nothing](code))\n\n  val NotFoundResponse: MatchResult[Nothing,Nothing] = reply(HttpStatusCode.NotFound)\n\n  val MethodNotAllowed: MatchResult[Nothing, Nothing] = reply(HttpStatusCode.MethodNotAllowed)\n\n  val BadRequest: MatchResult[Nothing, Nothing] = reply(HttpStatusCode.BadRequest)\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/routing/Matcher.scala",
    "content": "package spinoco.fs2.http.routing\n\nimport cats.effect.Sync\nimport fs2._\nimport shapeless.ops.function.FnToProduct\nimport shapeless.ops.hlist.Prepend\nimport shapeless.{::, HList, HNil}\nimport spinoco.fs2.http.HttpResponse\nimport spinoco.fs2.http.routing.MatchResult.{Failed, Success}\nimport spinoco.protocol.http.{HttpRequestHeader, HttpStatusCode, Uri}\n\n\nsealed trait Matcher[+F[_], +A] { self =>\n  import Matcher._\n\n\n  /** transforms this matcher with supplied `f` **/\n  def map[B](f: A => B): Matcher[F, B] =\n    Bind[F, A, B](self, r => Matcher.ofResult(r.map(f)) )\n\n  /** defined ad map { _ => b} **/\n  def *>[B](b: B): Matcher[F, B] =\n    self.map { _ => b }\n\n  /** advances path by one segment, after this matches **/\n  def advance: Matcher[F, A] =\n    Advance(self)\n\n\n  /** matches this or yields to None **/\n  def ? : Matcher[F, Option[A]] =\n    self.map(Some(_)) or Matcher.success(None: Option[A])\n\n\n}\n\n\nobject Matcher {\n\n  implicit class PureMatcherOps[F[_], A](val self: Matcher[F, A]) extends AnyVal {\n\n    /** like `map` but allows to evaluate `F` **/\n    def evalMap[B](f: A => F[B]): Matcher[F, B] =\n      self.flatMap { a => Eval(f(a)) }\n\n    /** transforms this matcher to another matcher with supplied `f` **/\n    def flatMap[B](f: A => Matcher[F, B]):  Matcher[F, B]  =\n      Bind[F, A, B](self, {\n        case success:Success[A]  => f(success.result)\n        case failed:Failed[F] => Matcher.respond[F](failed.response)\n      })\n\n    /** allias for flatMap **/\n    def >>=[B](f: A => Matcher[F, B]):  Matcher[F, B]  =\n      flatMap(f)\n\n    /** defined as flatMap { _ => fb } **/\n    def >>[B](fb: Matcher[F, B]):  Matcher[F, B]  =\n      flatMap(_ => fb)\n\n    /** defined as flatMap { a => fb map { _ => a} } **/\n    def <<[B](fb: Matcher[F, B]):  Matcher[F, A]  =\n      flatMap(a => fb map { _ => a})\n\n    /** defined as advance.flatMap(f) **/\n    def />>=[B](f: A => Matcher[F, B]):  Matcher[F, B]  =\n      self.advance.flatMap(f)\n\n    /** like flatMap, but allows to apply `f` when match failed **/\n    def flatMapR[B](f: MatchResult[F,A] => Matcher[F, B]):  Matcher[F, B]  =\n      Bind[F, A, B](self, f)\n\n    /** applies `f` only when matcher fails to match **/\n    def recover(f: HttpResponse[F] => Matcher[F, A]):  Matcher[F, A] =\n      Bind[F, A, A](self.asInstanceOf[Matcher[F, A]], {\n        case success: Success[A]  => Matcher.success(success.result)\n        case failed: Failed[F] =>  f(failed.response)\n      })\n\n    /** matches and consumes current path segment throwing away `A` **/\n    def /[B](other : Matcher[F, B]): Matcher[F, B] =\n      self.advance.flatMap { _ => other }\n\n    /** matches and consumes current path segment throwing away `B` **/\n    def </[B](other:  Matcher[F, B]):  Matcher[F, A] =\n      self.advance.flatMap { a => other.map { _ => a } }\n\n    /** matches this or alternative **/\n    def or[A0 >: A](alt : => Matcher[F, A0]): Matcher[F, A0] =\n      Bind[F, A, A0](self, {\n        case success: Success[A] => Matcher.ofResult(success.asInstanceOf[MatchResult.Success[A0]])\n        case failed: Failed[F] => alt\n      })\n\n    def covary[F0[_] >: F[_]]:Matcher[F0, A] = self.asInstanceOf[Matcher[F0, A]]\n\n  }\n\n//  implicit class MatcherStringPathSyntax[F[_]](val self: Matcher[F, String]) extends AnyVal {\n//    def /(s: String): Matcher[F, String] =\n//      self.advance.flatMap { _ => uriSegment(s) }\n//\n//    def or(s: String): Matcher[F, String] =\n//      Bind[F, String, String](self, {\n//        case success: Success[String] => Matcher.ofResult(success.asInstanceOf[MatchResult.Success[String]])\n//        case failed: Failed[F] => uriSegment(s)\n//      })\n//  } //TODO why is this here?\n\n\n\n  case class Match[F[_], A](f:(HttpRequestHeader, Stream[F, Byte]) => MatchResult[F, A]) extends Matcher[F, A]\n  case class Bind[F[_], A, B](m: Matcher[F, A], f: MatchResult[F,A] => Matcher[F, B]) extends Matcher[F, B]\n  case class Advance[F[_], A](m: Matcher[F, A]) extends Matcher[F, A]\n  case class Eval[F[_], A](f: F[A]) extends Matcher[F, A]\n\n\n  /** matcher that always succeeds **/\n  def success[A](a: A): Matcher[Nothing, A] =\n    Match[Nothing,A] { (_,_) => MatchResult.Success[A](a) }\n\n  /** matcher that always responds (fails) with supplied response **/\n  def respond[F[_]](response: HttpResponse[F]): Matcher[F, Nothing] =\n    Match[F, Nothing] { (_, _) => MatchResult.Failed[F](response) }\n\n  /** matcher that always responds with supplied status code **/\n  def respondWith(code: HttpStatusCode): Matcher[Nothing, Nothing] =\n    respond(HttpResponse(code))\n\n  /** Matcher that always results in result supplied**/\n  def ofResult[F[_], A](result:MatchResult[F,A]): Matcher[F, A] =\n    Match[F, A] { (_, _) => result }\n\n  /**\n    * Interprets matcher to obtain the result.\n    */\n  def run[F[_], A](matcher: Matcher[F, A])(header: HttpRequestHeader, body: Stream[F, Byte])(implicit F: Sync[F]): F[MatchResult[F, A]] = {\n    def go[B](current:Matcher[F,B], path: Uri.Path):F[(MatchResult[F, B], Uri.Path)] = {\n      current match {\n        case m: Match[F,B] => F.map(F.pure(m.f(header.copy(path = path), body))) { _ -> path }\n        case m: Eval[F, B] => F.map(m.f)(b => Success(b) -> path)\n        case m: Bind[F, _, B] => F.flatMap(F.suspend(go(m.m, path))){ case (r, path0) =>\n          if (r.isSuccess)  go(m.f(r), path0)\n          else go(m.f(r), path)\n        }\n        case m: Advance[F, B] => F.map(F.suspend(go(m.m, path))){ case (r, path0) =>\n          if (r.isSuccess) {\n            if (path0.segments.nonEmpty) r -> path0.copy(segments = path0.segments.tail)\n            else if (path0.trailingSlash) r -> path0.copy(trailingSlash = false)\n            else r -> path0 // no op\n          }\n          else r -> path\n        }\n      }\n    }\n    F.map(go(matcher, header.path)) { _._1 }\n  }\n\n\n  implicit class RequestMatcherHListSyntax[F[_], L <: HList](val self: Matcher[F, L]) extends AnyVal {\n    /** combines two matcher'r result to resulting hlist **/\n    def ::[B](other: Matcher[F, B]): Matcher[F, B :: L] =\n      other.flatMap { b => self.map { l => b :: l } }\n\n    /** combines this matcher with other matcher appending result of other matcher at the end **/\n    def :+[B](other: Matcher[F, B])(implicit P : Prepend[L, B :: HNil]): Matcher[F, P.Out] =\n      self.flatMap { l => other.map { b => l :+ b } }\n\n    /** prepends result of other matcher before the result of this matcher **/\n    def :::[L2 <: HList, HL <: HList](other: Matcher[F, L2])(implicit P: Prepend.Aux[L2, L, HL]): Matcher[F, HL] =\n      other.flatMap { l2 => self.map { l => l2 ::: l } }\n\n    /** combines two matcher'r result to resulting hlist, and advances path between them  **/\n    def :/:[B](other : Matcher[F, B]): Matcher[F, B :: L] =\n      other.advance.flatMap { b => self.map { l => b :: l } }\n\n    /** like `map` but instead (L:HList) => B, takes ordinary function **/\n    def mapH[FF, B](f: FF)(implicit F2P: FnToProduct.Aux[FF, L => B]): Matcher[F, B] =\n      self.map { l => F2P(f)(l) }\n\n\n  }\n\n\n  implicit class RequestMatcherSyntax[F[_], A](val self: Matcher[F, A]) extends AnyVal {\n    /** applies this matcher and if it is is successful then applies `other` returning result in HList B :: A :: HNil */\n    def :: [B](other : Matcher[F, B]): Matcher[F, B :: A :: HNil] =\n      other.flatMap { b => self.map { a => b :: a :: HNil } }\n\n    def :/:[B](other : Matcher[F, B]): Matcher[F, B :: A :: HNil] =\n      other.advance.flatMap { b => self.map { a => b :: a :: HNil } }\n\n\n\n  }\n\n\n\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/routing/StringDecoder.scala",
    "content": "package spinoco.fs2.http.routing\n\nimport scodec.bits.{Bases, ByteVector}\nimport shapeless.tag\nimport shapeless.tag.@@\n\nimport scala.reflect.ClassTag\nimport scala.util.Try\n\n/**\n  * Decoder for `A` to be decoded from supplied String\n  */\nsealed trait StringDecoder[A] { self =>\n  /** decode `A` from supplied string **/\n  def decode(s:String): Option[A]\n\n  def map[B](f: A => B): StringDecoder[B] =\n    StringDecoder { s => self.decode(s).map(f)  }\n\n  def mapO[B](f: A => Option[B]): StringDecoder[B] =\n    StringDecoder { s => self.decode(s).flatMap(f) }\n\n  def filter(f: A => Boolean): StringDecoder[A] =\n    StringDecoder { s => self.decode(s).filter(f) }\n}\n\n\nobject StringDecoder {\n\n  def apply[A]( f: String => Option[A]):StringDecoder[A] =\n    new StringDecoder[A] { def decode(s: String): Option[A] = f(s) }\n\n  implicit val boolInstance: StringDecoder[Boolean] =\n    StringDecoder { s =>\n      if (s.equalsIgnoreCase(\"true\") ) Some(true)\n      else if (s.equalsIgnoreCase(\"false\")) Some(false)\n      else None\n    }\n\n  implicit val stringInstance: StringDecoder[String] =\n    StringDecoder { Some(_) }\n\n  implicit val byteInstance : StringDecoder[Byte] =\n    StringDecoder { s => Try { s.toByte }.toOption }\n\n  implicit val shortInstance : StringDecoder[Short] =\n    StringDecoder { s => Try { s.toShort }.toOption }\n\n  implicit val intInstance : StringDecoder[Int] =\n    StringDecoder { s => Try { s.toInt }.toOption }\n\n  implicit val longInstance : StringDecoder[Long] =\n    StringDecoder { s => Try { s.toLong }.toOption }\n\n  implicit val doubleInstance : StringDecoder[Double] =\n    StringDecoder { s => Try { s.toDouble }.toOption }\n\n  implicit val floatInstance : StringDecoder[Float] =\n    StringDecoder { s => Try { s.toFloat }.toOption }\n\n  implicit val bigIntInstance : StringDecoder[BigInt] =\n    StringDecoder { s => Try { BigInt(s) }.toOption }\n\n  implicit val bigDecimalInstance : StringDecoder[BigDecimal] =\n    StringDecoder { s => Try { BigDecimal(s) }.toOption }\n\n  implicit val base64UrlInstance: StringDecoder[ByteVector @@ Base64Url] =\n    StringDecoder { s => ByteVector.fromBase64(s,Bases.Alphabets.Base64Url).map(tag[Base64Url](_)) }\n\n  implicit def enumInstance[E <: Enumeration : ClassTag] : StringDecoder[E#Value] = {\n    val E = implicitly[ClassTag[E]].runtimeClass.getField(\"MODULE$\").get((): Unit).asInstanceOf[Enumeration]\n    StringDecoder { s => Try { E.withName(s)}.toOption.map(_.asInstanceOf[E#Value]) }\n  }\n\n}"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/routing/routing.scala",
    "content": "package spinoco.fs2.http\n\nimport cats.effect.{Concurrent, Effect, Timer}\nimport fs2._\nimport scodec.{Attempt, Decoder, Encoder}\nimport scodec.bits.Bases.Base64Alphabet\nimport scodec.bits.{Bases, ByteVector}\nimport shapeless.Typeable\n\nimport spinoco.fs2.http.body.{BodyDecoder, StreamBodyDecoder}\nimport spinoco.fs2.http.routing.MatchResult._\nimport spinoco.fs2.http.routing.Matcher.{Eval, Match}\nimport spinoco.protocol.http.header._\nimport spinoco.protocol.http.{HttpMethod, HttpRequestHeader, HttpStatusCode, Uri}\nimport spinoco.fs2.http.util.chunk2ByteVector\nimport spinoco.fs2.http.websocket.{Frame, WebSocket}\nimport scala.concurrent.duration._\n\n\n\npackage object routing {\n  type Route[F[_]] = Matcher[F, Stream[F, HttpResponse[F]]]\n\n  /** tags bytes encoded as Base64Url **/\n  sealed trait Base64Url\n\n\n  /** converts supplied route to function that is handled over to server to perform the routing **/\n  def route[F[_]](r:Route[F])(implicit F: Effect[F]):(HttpRequestHeader, Stream[F, Byte]) => Stream[F, HttpResponse[F]] = {\n    (header, body) =>\n      Stream.eval(Matcher.run[F, Stream[F, HttpResponse[F]]](r)(header, body)).flatMap { mr =>\n        mr.fold((resp : HttpResponse[F]) => Stream.emit(resp), identity )\n      }\n  }\n\n  implicit class StringMatcherSyntax(val self: String) extends AnyVal {\n    def /[F[_], A] (m: Matcher[F, A]) : Matcher[F, A] =\n    string2RequestMatcher(self) / m\n\n    def or[F[_]] (m: Matcher[F, String]) : Matcher[F, String] =\n      string2RequestMatcher(self) or m\n  }\n\n  implicit def string2RequestMatcher(s:String): Matcher[Nothing, String] =\n    as(StringDecoder.stringInstance.filter(_ == s))\n\n\n  /** matches supplied method **/\n  def method[F[_]](method: HttpMethod.Value): Matcher[F, HttpMethod.Value] =\n    Match[F, HttpMethod.Value] { (rq, body) =>\n      if (rq.method == method) MatchResult.Success(method)\n      else MatchResult.MethodNotAllowed\n    }\n\n  val Get = method(HttpMethod.GET)\n  val Put = method(HttpMethod.PUT)\n  val Post = method(HttpMethod.POST)\n  val Delete = method(HttpMethod.DELETE)\n  val Options = method(HttpMethod.OPTIONS)\n\n  /** matches to relative path in current context **/\n  def path: Matcher[Nothing, Uri.Path] = {\n    Match[Nothing, Uri.Path]{ (request, _) =>\n      Success[Uri.Path](request.path.copy(initialSlash = false))\n    }\n  }\n\n  /** matches any supplied matcher **/\n  def choice[F[_],A](matcher: Matcher[F, A], matchers: Matcher[F, A]*): Matcher[F, A] = {\n    def go(m: Matcher[F,A], next: Seq[Matcher[F, A]]): Matcher[F, A] = {\n      next.headOption match {\n        case None => m\n        case Some(nm) => m.flatMapR[A] {\n          case Success(a) => Matcher.success(a)\n          case f: Failed[F] => go(nm, next.tail)\n        }\n      }\n    }\n    go(matcher, matchers)\n  }\n\n  /** matches if remaining path segments are empty **/\n  val empty : Matcher[Nothing, Unit] =\n    Match[Nothing, Unit] { (request, _) =>\n      if (request.path.segments.isEmpty) Success(())\n      else NotFoundResponse\n    }\n\n  /** matches header of type `h` **/\n  def header[H <: HttpHeader](implicit T: Typeable[H]): Matcher[Nothing, H] =\n    Match[Nothing, H] { (request, _) =>\n      request.headers.collectFirst(Function.unlift(T.cast)) match {\n        case None => BadRequest\n        case Some(h) => Success(h)\n      }\n    }\n\n  /**\n    * Matches if query contains `key` and that can be decoded to `A` via supplied decoder\n    */\n  def param[A](key: String)(implicit decoder: StringDecoder[A]) : Matcher[Nothing,A] =\n    Match[Nothing, A] { (header, _) =>\n      header.query.valueOf(key).flatMap(decoder.decode) match {\n        case None => BadRequest\n        case Some(a) => Success(a)\n      }\n    }\n\n  /** Decodes Base64 (Url) encoded binary data in parameter specified by `key` **/\n  def paramBase64(key: String, alphabet: Base64Alphabet = Bases.Alphabets.Base64Url): Matcher[Nothing, ByteVector] =\n    param[String](key).flatMap { s =>\n      ByteVector.fromBase64(s, alphabet) match {\n        case None => Matcher.respondWith(HttpStatusCode.BadRequest)\n        case Some(bv) => Matcher.success(bv)\n      }\n    }\n\n  /** decodes head of the path to `A` givne supplied decoder from string **/\n  def as[A](implicit decoder: StringDecoder[A]): Matcher[Nothing, A] =\n    Match[Nothing, A] { (request, _) =>\n      request.path.segments.headOption.flatMap(decoder.decode) match {\n        case None => NotFoundResponse\n        case Some(a) => Success(a)\n      }\n    }\n\n  /**\n    * Creates a Matcher that when supplied a pipe will create the websocket connection.\n    * `I` is received from the client and `O` is sent to client.\n    * Decoder (for I) and Encoder (for O) must be supplied.\n    *\n    * @param pingInterval     An interval for the Ping / Pong protocol.\n    * @param handshakeTimeout An timeout to await for handshake to be successfull. If the handshake is not completed\n    *                         within supplied period, connection is terminated.\n    * @param maxFrameSize     Maximum size of single websocket frame. If the binary size of single frame is larger than\n    *                         supplied value, websocket will fail.\n    */\n  def websocket[F[_] : Concurrent : Timer, I : Decoder, O : Encoder](\n    pingInterval: Duration = 30.seconds\n    , handshakeTimeout: FiniteDuration = 10.seconds\n    , maxFrameSize: Int = 1024*1024\n  ): Match[Nothing, (Pipe[F, Frame[I], Frame[O]]) => Stream[F, HttpResponse[F]]] =\n    Match[Nothing, (Pipe[F, Frame[I], Frame[O]]) => Stream[F, HttpResponse[F]]] { (request, body) =>\n      Success(\n        WebSocket.server[F, I, O](_, pingInterval, handshakeTimeout, maxFrameSize)(request, body)\n      )\n    }\n\n  /**\n    * Evaluates `f` returning its result as successful matcher\n    */\n  def eval[F[_],A](f: F[A]): Matcher[F, A] =\n    Eval(f)\n\n  /** extracts body of the request **/\n  def body[F[_]]: BodyHelper[F] = new BodyHelper[F] {}\n\n  trait BodyHelper[F[_]] {\n    /**\n      * extract body as raw bytes w/o checking its content type bytes.\n      * If `Content-Length` header is provided, then up to that much bytes is consumed from the body.\n      * Otherwise this prouces a stream of bytes that is terminated after clients signal EOF\n      */\n    def bytes:  Matcher[F, Stream[F, Byte]] =\n      header[`Content-Length`].?.flatMap { maybeSized =>\n        Match[F, Stream[F, Byte]] { (_, body) =>\n          MatchResult.success(maybeSized.map(_.value).fold(body) { sz => body.take(sz) })\n        }\n      }\n\n\n\n    /** extracts body as stream of `A` **/\n    def stream[A](implicit D: StreamBodyDecoder[F, A]):  Matcher[F, Stream[F, A]] =\n      header[`Content-Type`].flatMap { ct =>\n        bytes.flatMap { s =>\n          D.decode(ct.value) match {\n            case None => Matcher.ofResult(BadRequest)\n            case Some(decode) => Matcher.success(s through decode)\n          }\n        }\n      }\n\n    /** extracts last element of the `body` or responds BadRequest if body can't be extracted **/\n    def as[A](implicit D: BodyDecoder[A], F: Effect[F]): Matcher[F, A] = {\n      header[`Content-Type`].flatMap { ct =>\n        bytes.flatMap { s => eval {\n          F.map(s.chunks.compile.toVector) { chunks =>\n            val bytes =\n              if (chunks.isEmpty) ByteVector.empty\n              else chunks.map(chunk2ByteVector).reduce(_ ++ _)\n            D.decode(bytes, ct.value)\n          }\n        }}.flatMap {\n          case Attempt.Successful(a) => Matcher.success(a)\n          case Attempt.Failure(err) => Matcher.ofResult(BadRequest)\n        }\n      }\n    }\n  }\n\n\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/sse/SSEDecoder.scala",
    "content": "package spinoco.fs2.http.sse\n\nimport scodec.Attempt\n\n\nsealed trait SSEDecoder[A] { self =>\n\n  def decode(in: SSEMessage): Attempt[A]\n\n  def map[B](f: A => B): SSEDecoder[B] =\n    SSEDecoder.instance { a => self.decode(a).map(f) }\n\n}\n\n\nobject SSEDecoder {\n\n  @inline def apply[A](implicit instance: SSEDecoder[A]): SSEDecoder[A] = instance\n\n  def instance[A](f: SSEMessage => Attempt[A]): SSEDecoder[A] =\n    new SSEDecoder[A] { def decode(in: SSEMessage): Attempt[A] = f(in) }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/sse/SSEEncoder.scala",
    "content": "package spinoco.fs2.http.sse\n\nimport scodec.Attempt\n\n\nsealed trait SSEEncoder[A] { self =>\n\n  def encode(a: A) : Attempt[SSEMessage]\n\n  def mapIn[B](f: B => A): SSEEncoder[B] =\n    SSEEncoder.instance { b => self.encode(f(b)) }\n\n}\n\n\nobject SSEEncoder {\n\n  @inline def apply[A](implicit instance: SSEEncoder[A]): SSEEncoder[A] = instance\n\n  def instance[A](f: A => Attempt[SSEMessage]): SSEEncoder[A] =\n    new SSEEncoder[A] { def encode(a: A): Attempt[SSEMessage] = f(a) }\n\n  /** simple encoder of string messages **/\n  val stringEncoder: SSEEncoder[String] =\n    SSEEncoder.instance { s => Attempt.successful(SSEMessage.SSEData(Vector(s), None, None)) }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/sse/SSEEncoding.scala",
    "content": "package spinoco.fs2.http.sse\n\nimport fs2.Chunk.ByteVectorChunk\nimport fs2._\nimport scodec.Attempt\nimport scodec.bits.ByteVector\n\nimport spinoco.fs2.http.util.chunk2ByteVector\nimport scala.util.Try\nimport scala.concurrent.duration._\n\n\nobject SSEEncoding {\n\n\n\n  /**\n    * Encodes supplied stream of (messageTag, messageContent) to SSE Stream\n    */\n  def encode[F[_]]: Pipe[F, SSEMessage, Byte] = {\n    _.flatMap {\n      case SSEMessage.SSEData(data, event, id) =>\n        val eventBytes = event.map { s => s\"event: $s\" }.toSeq\n        val dataBytes = data.map { s => s\"data: $s\" }\n        val idBytes = id.map { s => s\"id: $s\" }.toSeq\n        Stream.chunk(ByteVectorChunk(ByteVector.view((\n          eventBytes ++ dataBytes ++ idBytes).mkString(\"\", \"\\n\", \"\\n\\n\").getBytes\n        )))\n\n      case SSEMessage.SSERetry(duration) =>\n        Stream.chunk(ByteVectorChunk(ByteVector.view(\n          s\"retry: ${duration.toMillis}\\n\\n\".getBytes\n        )))\n    }\n  }\n\n  /** encodes stream of `A` as SSE Stream **/\n  def encodeA[F[_] : RaiseThrowable, A](implicit E: SSEEncoder[A]): Pipe[F, A, Byte] = {\n    _ flatMap { a => E.encode(a) match {\n      case Attempt.Successful(msg) => Stream.emit(msg)\n      case Attempt.Failure(err) => Stream.raiseError(new Throwable(s\"Failed to encode $a : $err\"))\n    }} through encode\n  }\n\n  private val StartBom = ByteVector.fromValidHex(\"feff\")\n\n  /**\n    * Decodes stream of bytes to SSE Messages\n    */\n  def decode[F[_] : RaiseThrowable]: Pipe[F, Byte, SSEMessage] = {\n\n    // drops initial Byte Order Mark, if present\n    def dropInitial(buff:ByteVector): Pipe[F, Byte, Byte] = {\n      _.pull.uncons.flatMap {\n        case None => Pull.raiseError(new Throwable(\"SSE Socket did not contain any data\"))\n        case Some((chunk, next)) =>\n          val all = buff ++ chunk2ByteVector(chunk)\n          if (all.size < 2) (next through dropInitial(all)).pull.echo\n          else {\n            if (all.startsWith(StartBom)) Pull.output(ByteVectorChunk(all.drop(2))) >> next.pull.echo\n            else Pull.output(ByteVectorChunk(all)) >> next.pull.echo\n          }\n      }.stream\n    }\n\n    // makes lines out of incoming bytes. Lines are utf-8 decoded\n    // separated by \\r\\n or \\n or \\r\n    def mkLines: Pipe[F, Byte, String] =\n      _ through text.utf8Decode[F] through text.lines[F]\n\n\n    // makes lines for single event\n    // removes all the comments and splits by empty lines\n    // outgoing vectors are guaranteed tobe nonEmpty\n    // note that this splits by empty lines.\n    // the last event is emitted only if it is terminated by empty line\n    def mkEvents: Pipe[F, String, Seq[String]] = {\n      def go(buff: Vector[String]): Stream[F, String] => Pull[F, Seq[String], Unit] = {\n        _.pull.uncons flatMap {\n          case None => Pull.done\n\n          case Some((lines, tl)) =>\n            val event = lines.toList.takeWhile(_.nonEmpty)\n            // size of event lines is NOT equal with size of lines only when there is nonEmpty line\n            if (event.size == lines.size) go(buff ++ event)(tl)\n            else Pull.output1(buff ++ event) >> go(Vector.empty)(Stream.chunk(lines.drop(event.size + 1)) ++ tl)\n        }\n      }\n\n      src => go(Vector.empty)(src.filter(! _.startsWith(\":\"))).stream\n    }\n\n\n\n    // constructs SSE Message\n    // if message contains \"retry\" separate retry event is emitted\n    // im message contains multiple \"event\" or \"id\" values, only last one is used.\n    def mkMessage: Pipe[F, Seq[String], SSEMessage] = {\n       _.flatMap { lines =>\n         val data =\n           lines.map { line =>\n             val idx = line.indexOf(':')\n             if (idx < 0) line -> \"\"\n             else {\n               val (tag, data) = line.splitAt(idx)\n               val dataNoColon = data.drop(1)\n               val dataOut = if (dataNoColon.startsWith(\" \")) dataNoColon.drop(1) else dataNoColon\n               tag -> dataOut\n             }\n           }\n\n         val (mData, mEvent, mId, mRetry) =\n           data.foldLeft((Vector.empty[String], Option.empty[String], Option.empty[String], Option.empty[FiniteDuration])) {\n             case ((d, event, id, retry), next) => next match {\n               case (\"data\", v) => (d :+ v, event, id, retry)\n               case (\"event\", v) => (d, Some(v), id, retry)\n               case (\"id\", v) => (d, event, Some(v), retry)\n               case (\"retry\", v) => (d, event, Some(v), Try { v.trim.toInt.millis }.toOption)\n               case _ => (d, event, id, retry)\n             }\n           }\n\n\n         Stream.emit(SSEMessage.SSEData(mData, mEvent, mId))\n         .filter(m => m.data.nonEmpty || m.event.nonEmpty || m.id.nonEmpty) ++\n         Stream.emits(mRetry.toSeq.map(SSEMessage.SSERetry.apply))\n\n       }\n    }\n\n    _ through dropInitial(ByteVector.empty) through mkLines through mkEvents through mkMessage\n  }\n\n  /** decodes stream of sse messages to `A`, given supplied decoder **/\n  def decodeA[F[_] : RaiseThrowable, A](implicit D: SSEDecoder[A]): Pipe[F, Byte, A] = {\n    _ through decode flatMap { msg =>\n      D.decode(msg) match {\n        case Attempt.Successful(a) => Stream.emit(a)\n        case Attempt.Failure(err) => Stream.raiseError(new Throwable(s\"Failed do decode: $msg : $err\"))\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/sse/SSEMessage.scala",
    "content": "package spinoco.fs2.http.sse\n\nimport scala.concurrent.duration.FiniteDuration\n\n/**\n  * SSE Message modeled after\n  * https://www.w3.org/TR/2011/WD-eventsource-20111020/\n  */\nsealed trait SSEMessage\n\n\nobject SSEMessage {\n\n  /**\n    * SSE Data received\n    * @param data     Data fields, received in singel event\n    * @param event    Name of the event, if one provided, empty otherwise\n    * @param id       Id of the event if one provided, empty otherwise.\n    */\n  case class SSEData(data: Seq[String], event: Option[String], id: Option[String]) extends SSEMessage\n  case class SSERetry(retryIN: FiniteDuration) extends SSEMessage\n\n}"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/util/util.scala",
    "content": "package spinoco.fs2.http\n\nimport java.lang.Thread.UncaughtExceptionHandler\nimport java.util.concurrent.{Executors, ThreadFactory}\nimport java.util.concurrent.atomic.AtomicInteger\n\nimport fs2.Chunk.ByteVectorChunk\nimport fs2._\nimport scodec.bits.{BitVector, ByteVector}\nimport scodec.bits.Bases.{Alphabets, Base64Alphabet}\n\nimport spinoco.protocol.mime.{ContentType, MIMECharset}\nimport scala.concurrent.ExecutionContext\nimport scala.util.control.NonFatal\n\npackage object util {\n\n\n  /**\n    * Encodes bytes to base64 encoded bytes [[http://tools.ietf.org/html/rfc4648#section-5 RF4648 section 5]]\n    * Encoding is done lazily to support very large Base64 bodies i.e. email, attachments..)\n    * @param alphabet   Alphabet to use\n    * @return\n    */\n  def encodeBase64Raw[F[_]](alphabet:Base64Alphabet): Pipe[F, Byte, Byte] = {\n    def go(rem:ByteVector): Stream[F,Byte] => Pull[F, Byte, Unit] = {\n      _.pull.uncons flatMap {\n        case None =>\n          if (rem.size == 0) Pull.done\n          else Pull.output(ByteVectorChunk(ByteVector.view(rem.toBase64(alphabet).getBytes)))\n\n        case Some((chunk, tl)) =>\n          val n = rem ++ chunk2ByteVector(chunk)\n          if (n.size/3 > 0) {\n            val pad = n.size % 3\n            val enc = n.dropRight(pad)\n            val out = Array.ofDim[Byte]((enc.size.toInt / 3) * 4)\n            var pos = 0\n            enc.toBitVector.grouped(6) foreach { group =>\n              val idx = group.padTo(8).shiftRight(2, signExtension = false).toByteVector.head\n              out(pos) = alphabet.toChar(idx).toByte\n              pos = pos + 1\n            }\n            Pull.output(ByteVectorChunk(ByteVector.view(out))) >> go(n.takeRight(pad))(tl)\n          } else {\n            go(n)(tl)\n          }\n\n      }\n\n    }\n    src => go(ByteVector.empty)(src).stream\n  }\n\n  /** encodes base64 encoded stream [[http://tools.ietf.org/html/rfc4648#section-5 RF4648 section 5]]. Whitespaces are ignored **/\n  def encodeBase64Url[F[_]]:Pipe[F, Byte, Byte] =\n    encodeBase64Raw(Alphabets.Base64Url)\n\n  /** encodes base64 encoded stream [[http://tools.ietf.org/html/rfc4648#section-4 RF4648 section 4]] **/\n  def encodeBase64[F[_]]:Pipe[F, Byte, Byte] =\n    encodeBase64Raw[F](Alphabets.Base64)\n\n\n  /**\n    * Decodes base64 encoded stream with supplied alphabet. Whitespaces are ignored.\n    * Decoding is lazy to support very large Base64 bodies (i.e. email)\n    */\n  def decodeBase64Raw[F[_] : RaiseThrowable](alphabet:Base64Alphabet):Pipe[F, Byte, Byte] = {\n    val Pad = alphabet.pad\n    def go(remAcc:BitVector): Stream[F, Byte] => Pull[F, Byte, Unit] = {\n      _.pull.uncons flatMap {\n        case None => Pull.done\n\n        case Some((chunk,tl)) =>\n          val bv = chunk2ByteVector(chunk)\n          var acc = remAcc\n          var idx = 0\n          var term = false\n          try {\n            bv.foreach  { b =>\n              b.toChar match {\n                case c if alphabet.ignore(c) => // ignore no-op\n                case Pad => term = true\n                case c =>\n                  if (!term) acc = acc ++ BitVector(alphabet.toIndex(c)).drop(2)\n                  else {\n                    throw new IllegalArgumentException(s\"Unexpected character '$c' at index $idx after padding character; only '=' and whitespace characters allowed after first padding character\")\n                  }\n              }\n              idx = idx + 1\n            }\n            val aligned = (acc.size / 8) * 8\n            if (aligned <= 0 && !term) go(acc)(tl)\n            else {\n              val (out, rem) = acc.splitAt(aligned)\n              if (term) Pull.output(ByteVectorChunk(out.toByteVector))\n              else Pull.output(ByteVectorChunk(out.toByteVector)) >> go(rem)(tl)\n            }\n\n          } catch {\n            case e: IllegalArgumentException =>\n              Pull.raiseError(new Throwable(s\"Invalid base 64 encoding at index $idx\", e))\n          }\n      }\n    }\n    src => go(BitVector.empty)(src).stream\n\n  }\n\n  /** decodes base64 encoded stream [[http://tools.ietf.org/html/rfc4648#section-5 RF4648 section 5]]. Whitespaces are ignored **/\n  def decodeBase64Url[F[_] : RaiseThrowable]:Pipe[F, Byte, Byte] =\n    decodeBase64Raw(Alphabets.Base64Url)\n\n  /** decodes base64 encoded stream [[http://tools.ietf.org/html/rfc4648#section-4 RF4648 section 4]] **/\n  def decodeBase64[F[_] : RaiseThrowable]:Pipe[F, Byte, Byte] =\n    decodeBase64Raw(Alphabets.Base64)\n\n  /** converts chunk of bytes to ByteVector **/\n  def chunk2ByteVector(chunk: Chunk[Byte]):ByteVector = {\n    chunk match  {\n      case bv: ByteVectorChunk => bv.toByteVector\n      case other =>\n        val bs = other.toBytes\n        ByteVector(bs.values, bs.offset, bs.size)\n    }\n  }\n\n  /** converts ByteVector to chunk **/\n  def byteVector2Chunk(bv: ByteVector): Chunk[Byte] = {\n    ByteVectorChunk(bv)\n  }\n\n  /** helper to create named daemon thread factories **/\n  def mkThreadFactory(name: String, daemon: Boolean, exitJvmOnFatalError: Boolean = true): ThreadFactory = {\n    new ThreadFactory {\n      val idx = new AtomicInteger(0)\n      val defaultFactory = Executors.defaultThreadFactory()\n      def newThread(r: Runnable): Thread = {\n        val t = defaultFactory.newThread(r)\n        t.setName(s\"$name-${idx.incrementAndGet()}\")\n        t.setDaemon(daemon)\n        t.setUncaughtExceptionHandler(new UncaughtExceptionHandler {\n          def uncaughtException(t: Thread, e: Throwable): Unit = {\n            ExecutionContext.defaultReporter(e)\n            if (exitJvmOnFatalError) {\n              e match {\n                case NonFatal(_) => ()\n                case fatal => System.exit(-1)\n              }\n            }\n          }\n        })\n        t\n      }\n    }\n  }\n\n  def getCharset(ct: ContentType): Option[MIMECharset] = {\n    ct match {\n      case ContentType.TextContent(_, maybeCharset) => maybeCharset\n      case _ => None\n    }\n  }\n\n}"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/websocket/Frame.scala",
    "content": "package spinoco.fs2.http.websocket\n\n\nsealed trait Frame[A] { self =>\n  def a: A\n  def isText: Boolean = self match {\n    case Frame.Text(_) => true\n    case _ => false\n  }\n\n  def isBinary = !isText\n}\n\n\nobject Frame {\n\n  case class Binary[A](a: A) extends Frame[A]\n\n  case class Text[A](a: A) extends Frame[A]\n\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/websocket/WebSocket.scala",
    "content": "package spinoco.fs2.http.websocket\n\n\nimport java.nio.channels.AsynchronousChannelGroup\nimport java.util.concurrent.Executors\n\nimport cats.Applicative\nimport javax.net.ssl.SSLContext\nimport cats.effect.{Concurrent, ConcurrentEffect, ContextShift, Timer}\nimport fs2.Chunk.ByteVectorChunk\nimport fs2._\nimport fs2.concurrent.Queue\nimport scodec.Attempt.{Failure, Successful}\nimport scodec.bits.ByteVector\nimport scodec.{Codec, Decoder, Encoder}\nimport spinoco.fs2.http.HttpResponse\nimport spinoco.protocol.http.codec.{HttpRequestHeaderCodec, HttpResponseHeaderCodec}\nimport spinoco.protocol.http.header._\nimport spinoco.protocol.http._\nimport spinoco.protocol.http.header.value.ProductDescription\nimport spinoco.protocol.mime.{ContentType, MIMECharset, MediaType}\nimport spinoco.protocol.websocket.{OpCode, WebSocketFrame}\nimport spinoco.protocol.websocket.codec.WebSocketFrameCodec\nimport spinoco.fs2.http.util.chunk2ByteVector\n\nimport scala.concurrent.ExecutionContext\nimport scala.concurrent.duration._\nimport scala.util.Random\n\n\nobject WebSocket {\n\n  /**\n    * Creates a websocket to be used on server side.\n    *\n    * Implementation is according to RFC-6455 (https://tools.ietf.org/html/rfc6455).\n    *\n    * @param pipe             A websocket pipe. `I` is received from the client and `O` is sent to client.\n    *                         Decoder (for I) and Encoder (for O) must be supplied.\n    * @param pingInterval     An interval for the Ping / Pong protocol.\n    * @param handshakeTimeout An timeout to await for handshake to be successfull. If the handshake is not completed\n    *                         within supplied period, connection is terminated.\n    * @param maxFrameSize     Maximum size of single websocket frame. If the binary size of single frame is larger than\n    *                         supplied value, websocket will fail.\n    * @tparam F\n    * @return\n    */\n  def server[F[_] : Concurrent : Timer, I : Decoder, O : Encoder](\n    pipe: Pipe[F, Frame[I], Frame[O]]\n    , pingInterval: Duration = 30.seconds\n    , handshakeTimeout: FiniteDuration = 10.seconds\n    , maxFrameSize: Int = 1024*1024\n  )(header: HttpRequestHeader, input:Stream[F,Byte]): Stream[F,HttpResponse[F]] = {\n    Stream.emit(\n      impl.verifyHeaderRequest[F](header).right.map { key =>\n        val respHeader = impl.computeHandshakeResponse(header, key)\n        HttpResponse(respHeader, input through impl.webSocketOf(pipe, pingInterval, maxFrameSize, client2Server = false))\n      }.merge\n    )\n  }\n\n\n  /**\n    * Establishes websocket connection to the server.\n    *\n    * Implementation is according to RFC-6455 (https://tools.ietf.org/html/rfc6455).\n    *\n    * If this is established successfully, then this consults `pipe` to receive/sent any frames\n    * From/To server. Once the connection finishes, this will emit once None.\n    *\n    * If the connection was not established correctly (i.e. Authorization failure) this will not\n    * consult supplied pipe and instead this will immediately emit response received from the server.\n    *\n    * @param request              WebSocket request\n    * @param pipe                 Pipe that is consulted when websocket is established correctly\n    * @param maxHeaderSize        Max size of  Http Response header received\n    * @param receiveBufferSize    Size of receive buffer to use\n    * @param maxFrameSize         Maximum size of single websocket frame. If the binary size of single frame is larger than\n    *                             supplied value, websocket will fail.\n    * @param requestCodec         Codec to encode HttpRequests Header\n    * @param responseCodec        Codec to decode HttpResponse Header\n    *\n    */\n  def client[F[_] : ConcurrentEffect : ContextShift : Timer, I : Decoder, O : Encoder](\n    request: WebSocketRequest\n    , pipe: Pipe[F, Frame[I], Frame[O]]\n    , maxHeaderSize: Int = 4096\n    , receiveBufferSize: Int = 256 * 1024\n    , maxFrameSize: Int = 1024*1024\n    , requestCodec: Codec[HttpRequestHeader] = HttpRequestHeaderCodec.defaultCodec\n    , responseCodec: Codec[HttpResponseHeader] = HttpResponseHeaderCodec.defaultCodec\n    , sslES: => ExecutionContext = ExecutionContext.fromExecutorService(Executors.newCachedThreadPool(spinoco.fs2.http.util.mkThreadFactory(\"fs2-http-ssl\", daemon = true)))\n    , sslContext: => SSLContext = { val ctx = SSLContext.getInstance(\"TLS\"); ctx.init(null,null,null); ctx }\n  )(implicit AG: AsynchronousChannelGroup): Stream[F, Option[HttpResponseHeader]] = {\n    import spinoco.fs2.http.internal._\n    import Stream._\n    eval(addressForRequest[F](if (request.secure) HttpScheme.WSS else HttpScheme.WS, request.hostPort)).flatMap { address =>\n    Stream.resource(io.tcp.client[F](address, receiveBufferSize = receiveBufferSize))\n    .evalMap { socket => if (request.secure) clientLiftToSecure(sslES, sslContext)(socket, request.hostPort) else Applicative[F].pure(socket) }\n    .flatMap { socket =>\n      val (header, fingerprint) = impl.createRequestHeaders(request.header)\n      requestCodec.encode(header) match {\n        case Failure(err) => Stream.raiseError(new Throwable(s\"Failed to encode websocket request: $err\"))\n        case Successful(headerBits) =>\n          eval(socket.write(ByteVectorChunk(headerBits.bytes ++ `\\r\\n\\r\\n`))).flatMap { _ =>\n            socket.reads(receiveBufferSize) through httpHeaderAndBody(maxHeaderSize) flatMap { case (respHeaderBytes, body) =>\n              responseCodec.decodeValue(respHeaderBytes.bits) match {\n                case Failure(err) => raiseError(new Throwable(s\"Failed to decode websocket response: $err\"))\n                case Successful(responseHeader) =>\n                  impl.validateResponse[F](header, responseHeader, fingerprint).flatMap {\n                    case Some(resp) => emit(Some(resp))\n                    case None => (body through impl.webSocketOf(pipe, Duration.Undefined, maxFrameSize, client2Server = true) through socket.writes(None)).drain ++ emit(None)\n                  }\n              }\n            }\n          }\n      }\n\n    }}\n\n  }\n\n\n  object impl {\n\n    private sealed trait PingPong\n\n    private object PingPong {\n      object Ping extends PingPong\n      object Pong extends PingPong\n    }\n\n\n    /**\n      * Verifies validity of WebSocket header request (on server) and extracts WebSocket key\n      */\n    def verifyHeaderRequest[F[_]](header: HttpRequestHeader): Either[HttpResponse[F], ByteVector] = {\n      def badRequest(s:String) = HttpResponse[F](\n        header = HttpResponseHeader(\n          status = HttpStatusCode.BadRequest\n          , reason = HttpStatusCode.BadRequest.label\n          , headers = List(\n             `Content-Type`(ContentType.TextContent(MediaType.`text/plain`, Some(MIMECharset.`UTF-8`)))\n          )\n        )\n        , body = Stream.chunk(ByteVectorChunk(ByteVector.view(s.getBytes)))\n      )\n\n      def version: Either[HttpResponse[F], Int] = header.headers.collectFirst {\n        case `Sec-WebSocket-Version`(13) => Right(13)\n        case `Sec-WebSocket-Version`(other) => Left(badRequest(s\"Unsupported websocket version: $other\"))\n      }.getOrElse(Left(badRequest(\"Missing Sec-WebSocket-Version header\")))\n\n      def host: Either[HttpResponse[F], Unit] = header.headers.collectFirst {\n        case Host(_) => Right(())\n      }.getOrElse(Left(badRequest(\"Missing header `Host: hostname`\")))\n\n      def upgrade: Either[HttpResponse[F], Unit] = header.headers.collectFirst {\n        case Upgrade(pds) if pds.exists { pd => pd.name.equalsIgnoreCase(\"websocket\") && pd.comment.isEmpty } => Right(())\n      }.getOrElse(Left(badRequest(\"Missing header `Upgrade: websocket`\")))\n\n      def connection: Either[HttpResponse[F], Unit] = header.headers.collectFirst {\n        case Connection(s) if s.exists(_.equalsIgnoreCase(\"Upgrade\")) => Right(())\n      }.getOrElse(Left(badRequest(\"Missing header `Connection: upgrade`\")))\n\n\n      def webSocketKey: Either[HttpResponse[F], ByteVector] = header.headers.collectFirst {\n        case `Sec-WebSocket-Key`(key) => Right(key)\n      }.getOrElse(Left(badRequest(\"Missing Sec-WebSocket-Key header\")))\n\n      for {\n        _ <- version.right\n        _ <- host.right\n        _ <- upgrade.right\n        _ <- connection.right\n        key <- webSocketKey.right\n      } yield key\n\n    }\n\n    /** creates the handshake response to complete websocket handshake on server side **/\n    def computeHandshakeResponse(header: HttpRequestHeader, key: ByteVector): HttpResponseHeader = {\n      val fingerprint = computeFingerPrint(key)\n      val headers = header.headers.collect {\n        case h: `Sec-WebSocket-Protocol` => h\n      }\n      HttpResponseHeader(\n        status = HttpStatusCode.SwitchingProtocols\n        , reason = HttpStatusCode.SwitchingProtocols.label\n        , headers = List(\n          Upgrade(List(ProductDescription(\"websocket\", None)))\n          , Connection(List(\"Upgrade\"))\n          , `Sec-WebSocket-Accept`(fingerprint)\n        ) ++ headers\n      )\n    }\n\n    /**\n      * Creates websocket  of supplied pipe\n      *\n      * @param pingInterval If Finite, defines duration when keep-alive pings are sent to client\n      *                     If client won't respond with pong to 3x this internal, the websocket will be terminated\n      *                     by server.\n      * @param client2Server When true, this represent client -> server direction, when false this represents reverse direction\n      */\n    def webSocketOf[F[_] : Concurrent : Timer, I : Decoder, O : Encoder](\n     pipe: Pipe[F, Frame[I], Frame[O]]\n     , pingInterval: Duration\n     , maxFrameSize: Int\n     , client2Server: Boolean\n    ):Pipe[F, Byte, Byte] = { source: Stream[F, Byte] => Stream.suspend {\n      Stream.eval(Queue.unbounded[F, PingPong]).flatMap { pingPongQ =>\n        val metronome: Stream[F, Unit] = pingInterval match {\n          case fin: FiniteDuration =>  Stream.awakeEvery[F](fin).map { _ => () }\n          case inf => Stream.empty\n        }\n        val control = controlStream[F](pingPongQ.dequeue, metronome, maxUnanswered = 3, flag = client2Server)\n\n        source\n        .through(decodeWebSocketFrame[F](maxFrameSize, client2Server))\n        .through(webSocketFrame2Frame[F, I](pingPongQ))\n        .through(pipe)\n        .through(frame2WebSocketFrame[F, O](if (client2Server) Some(Random.nextInt()) else None))\n        .mergeHaltBoth(control)\n        .through(encodeWebSocketFrame[F](client2Server))\n\n      }\n    }}\n\n    /**\n      * Cuts necessary data for decoding the frame, done by partially decoding\n      * the frame\n      * Empty if the data couldn't be decoded yet\n      * \n      * @param in Current buffer that may contain full frame\n      */\n    def cutFrame(in:ByteVector): Option[ByteVector] = {\n      val bits = in.bits\n      if (bits.size < 16) None // smallest frame is 16 bits\n      else {\n        val maskSize = if (bits(8)) 4 else 0\n        val sz = bits.drop(9).take(7).toInt(signed = false)\n        val maybeEnough =\n          if (sz < 126) {\n            // no extended payload size, sz bytes expected\n            Some(sz.toLong + 2)\n          } else if (sz == 126) {\n            // next 16 bits is payload size\n            if (bits.size < 32) None\n            else Some(bits.drop(16).take(16).toInt(signed = false).toLong + 4)\n          } else {\n            // next 64 bits is payload size\n            if (bits.size < 80) None\n            else Some(bits.drop(16).take(64).toLong(signed = false) + 10)\n          }\n        maybeEnough.flatMap { sz =>\n          val fullSize = sz + maskSize\n          if (in.size < fullSize) None\n          else Some(in.take(fullSize))\n        }\n      }\n    }\n\n    /**\n      * Decodes websocket frame.\n      *\n      * This will fail when the frame failed to be decoded or when frame is larger than\n      * supplied `maxFrameSize` parameter.\n      *\n      * @param maxFrameSize  Maximum size of the frame, including its header.\n      */\n    def decodeWebSocketFrame[F[_] : RaiseThrowable](maxFrameSize: Int , flag: Boolean): Pipe[F, Byte, WebSocketFrame] = {\n      // Returns list of raw frames and tail of\n      // the buffer. Tail of the buffer cant be empty\n      // (or non-empty if last one frame isn't finalized).\n      def cutFrames(data: ByteVector, acc: Vector[ByteVector] = Vector.empty): (Vector[ByteVector], ByteVector) = {\n        cutFrame(data) match {\n          case Some(frameData) => cutFrames(data.drop(frameData.size), acc :+ frameData)\n          case None => (acc, data)\n        }\n      }\n      def go(buff: ByteVector): Stream[F, Byte] => Pull[F, WebSocketFrame, Unit] = { h0 =>\n        if (buff.size > maxFrameSize) Pull.raiseError(new Throwable(s\"Size of websocket frame exceeded max size: $maxFrameSize, current: ${buff.size}, $buff\"))\n        else {\n          h0.pull.uncons flatMap {\n            case None => Pull.done  // todo: is ok to silently ignore buffer remainder ?\n\n            case Some((chunk, tl)) =>\n              val data = buff ++ chunk2ByteVector(chunk)\n              cutFrames(data) match {\n                case (rawFrames, _) if rawFrames.isEmpty => go(data)(tl)\n                case (rawFrames, dataTail) =>\n                  val pulls = rawFrames.map { data =>\n                    WebSocketFrameCodec.codec.decodeValue(data.bits) match {\n                      case Failure(err) => Pull.raiseError(new Throwable(s\"Failed to decode websocket frame: $err, $data\"))\n                      case Successful(wsFrame) => Pull.output1(wsFrame)\n                    }\n                  }\n                  // pulls nonempty\n                  pulls.reduce(_ >> _) >> go(dataTail)(tl)\n              }\n          }\n        }\n      }\n      src => go(ByteVector.empty)(src).stream\n    }\n\n    /**\n      * Collects incoming frames. to produce and deserialize Frame[A].\n      *\n      * Also interprets WebSocket operations.\n      *   - if Ping is received, supplied Queue is enqueued with true\n      *   - if Pong is received, supplied Queue is enqueued with false\n      *   - if Close is received, the WebSocket is terminated\n      *   - if Continuation is received, the buffer of the frame is enqueued and later used to deserialize to `A`.\n      *\n      * @param pongQ    Queue to notify about ping/pong frames.\n      */\n    def webSocketFrame2Frame[F[_] : RaiseThrowable, A](pongQ: Queue[F, PingPong])(implicit R: Decoder[A]): Pipe[F, WebSocketFrame, Frame[A]] = {\n      def decode(from: Vector[WebSocketFrame]):Pull[F, Frame[A], A] = {\n        val bs = from.map(_.payload).reduce(_ ++ _)\n        R.decodeValue(bs.bits) match {\n          case Failure(err) => Pull.raiseError(new Throwable(s\"Failed to decode value: $err, content: $bs\"))\n          case Successful(a) => Pull.pure(a)\n        }\n      }\n\n      def go(buff:Vector[WebSocketFrame]): Stream[F, WebSocketFrame] => Pull[F, Frame[A], Unit] = {\n        _.pull.uncons1 flatMap {\n          case None => Pull.done  // todo: is ok to ignore remainder in buffer ?\n          case Some((frame, tl)) =>\n            frame.opcode match {\n              case OpCode.Continuation => go(buff :+ frame)(tl)\n              case OpCode.Text => decode(buff :+ frame).flatMap { decoded => Pull.output1(Frame.Text(decoded)) >> go(Vector.empty)(tl) }\n              case OpCode.Binary =>  decode(buff :+ frame).flatMap { decoded => Pull.output1(Frame.Binary(decoded)) >> go(Vector.empty)(tl) }\n              case OpCode.Ping => Pull.eval(pongQ.enqueue1(PingPong.Ping)) >> go(buff)(tl)\n              case OpCode.Pong => Pull.eval(pongQ.enqueue1(PingPong.Pong)) >> go(buff)(tl)\n              case OpCode.Close => Pull.done\n            }\n        }\n      }\n\n      src => go(Vector.empty)(src).stream\n    }\n\n    /**\n      * Encodes received frome to WebSocketFrame.\n      * @param maskKey  A funtion that allows to generate random masking key. Masking is applied at client -> server direction only.\n      */\n    def frame2WebSocketFrame[F[_] : RaiseThrowable, A](maskKey: => Option[Int])(implicit W: Encoder[A]): Pipe[F, Frame[A], WebSocketFrame] = {\n      _.flatMap { frame =>\n        W.encode(frame.a) match {\n          case Failure(err) => Stream.raiseError(new Throwable(s\"Failed to encode frame: $err (frame: $frame)\"))\n          case Successful(payload) =>\n            val opCode = if (frame.isText) OpCode.Text else OpCode.Binary\n            Stream.emit(WebSocketFrame(fin = true, (false, false, false), opCode, payload.bytes, maskKey))\n        }\n      }\n    }\n\n\n    private val pingFrame = WebSocketFrame(fin = true, (false, false, false), OpCode.Ping, ByteVector.empty, None)\n    private val pongFrame = WebSocketFrame(fin = true, (false, false, false), OpCode.Pong, ByteVector.empty, None)\n    private val closeFrame = WebSocketFrame(fin = true, (false, false, false), OpCode.Close, ByteVector.empty, None)\n\n    /**\n      * Encodes incoming frames to wire format.\n      * @tparam F\n      * @return\n      */\n    def encodeWebSocketFrame[F[_] : RaiseThrowable](flag: Boolean): Pipe[F, WebSocketFrame, Byte] = {\n      _.append(Stream.emit(closeFrame)).flatMap { wsf =>\n        WebSocketFrameCodec.codec.encode(wsf) match {\n          case Failure(err) => Stream.raiseError(new Throwable(s\"Failed to encode websocket frame: $err (frame: $wsf)\"))\n          case Successful(data) => Stream.chunk(ByteVectorChunk(data.bytes))\n        }\n      }\n    }\n\n    /**\n      * Creates control stream. When control stream terminates WebSocket will terminate too.\n      *\n      * This takes ping-pong stream, for each Ping, this responds with Pong.\n      * For each Pong received this zeroes number of pings sent.\n      *\n      * @param pingPongs          Stream of ping pongs received\n      * @param metronome          A metronome that emits time to send Ping\n      * @param maxUnanswered      Max unanswered pings to await before the stream terminates.\n      * @tparam F\n      * @return\n      */\n    def controlStream[F[_] : Concurrent](\n       pingPongs: Stream[F, PingPong]\n       , metronome: Stream[F, Unit]\n       , maxUnanswered: Int\n       , flag: Boolean\n    ): Stream[F, WebSocketFrame] = {\n      (pingPongs either metronome)\n      .mapAccumulate(0) { case (pingsSent, in) => in match {\n        case Left(PingPong.Pong) => (0, Stream.empty)\n        case Left(PingPong.Ping) => (pingsSent, Stream.emit(pongFrame))\n        case Right(_) => (pingsSent + 1, Stream.emit(pingFrame))\n      }}\n      .flatMap { case (unconfirmed, out) =>\n        if (unconfirmed < 3) out\n        else Stream.raiseError(new Throwable(s\"Maximum number of unconfirmed pings exceeded: $unconfirmed\"))\n      }\n    }\n\n\n    val magic = ByteVector.view(\"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\".getBytes)\n    def computeFingerPrint(key: ByteVector): ByteVector =\n      (ByteVector.view(key.toBase64.getBytes) ++ magic).digest(\"SHA-1\")\n\n\n    /**\n      * Augments header to be correct for Websocket request (adding Sec-WebSocket-Key header) and\n      * returnng the correct header with expected SHA-1 response from the server\n      * @param header\n      * @param random   Random generator of 16 byte websocket keys\n      * @return\n      */\n    def createRequestHeaders(header:HttpRequestHeader, random: => ByteVector = randomBytes(16)): (HttpRequestHeader, ByteVector) = {\n      val key = random\n      val headers =\n        header.headers.filterNot ( h =>\n          h.isInstanceOf[`Sec-WebSocket-Key`]\n          || h.isInstanceOf[`Sec-WebSocket-Version`]\n          || h.isInstanceOf[Upgrade]\n        ) ++\n        List(\n          `Sec-WebSocket-Key`(key)\n          , `Sec-WebSocket-Version`(13)\n          , Connection(List(\"upgrade\"))\n          , Upgrade(List(ProductDescription(\"websocket\", None)))\n        )\n\n      header.copy(\n        method = HttpMethod.GET\n        , headers = headers\n      ) -> computeFingerPrint(key)\n    }\n\n    /** random generator, ascii compatible **/\n    def randomBytes(size: Int):ByteVector = {\n      ByteVector.view(Random.alphanumeric.take(size).mkString.getBytes)\n    }\n\n    /**\n      * Validates response received. If other than 101 status code is received, this evaluates to Some()\n      * If fingerprint won't match or the websocket headers wont match the request, this fails.\n      * @param request      Sent request header\n      * @param response     received header\n      * @param expectFingerPrint  expected fingerprint in header\n      * @return\n      */\n    def validateResponse[F[_] : RaiseThrowable](\n      request: HttpRequestHeader\n      , response: HttpResponseHeader\n      , expectFingerPrint: ByteVector\n    ): Stream[F, Option[HttpResponseHeader]] = {\n      import Stream._\n\n      def validateFingerPrint: Stream[F,Unit] =\n      response.headers.collectFirst {\n        case `Sec-WebSocket-Accept`(receivedFp) =>\n          if (receivedFp != expectFingerPrint) raiseError(new Throwable(s\"Websocket fingerprints won't match, expected $expectFingerPrint, but got $receivedFp\"))\n          else emit(())\n      }.getOrElse(raiseError(new Throwable(s\"Websocket response is missing the `Sec-WebSocket-Accept` header : $response\")))\n\n      def validateUpgrade: Stream[F,Unit] =\n        response.headers.collectFirst {\n          case Upgrade(pds) if pds.exists { pd => pd.name.equalsIgnoreCase(\"websocket\")  && pd.comment.isEmpty }  => emit(())\n        }.getOrElse(raiseError(new Throwable(s\"WebSocket response must contain header 'Upgrade: websocket' : $response\")))\n\n      def validateConnection: Stream[F,Unit] =\n        response.headers.collectFirst {\n          case Connection(ids) if ids.exists(_.equalsIgnoreCase(\"upgrade\")) => emit(())\n        }.getOrElse(raiseError(new Throwable(s\"WebSocket response must contain header 'Connection: Upgrade' : $response\")))\n\n      def validateProtocols: Stream[F,Unit] = {\n        val received =\n          response.headers.collectFirst {\n            case `Sec-WebSocket-Protocol`(protocols) => protocols\n          }.getOrElse(Nil)\n\n        val expected =\n          request.headers.collectFirst {\n            case `Sec-WebSocket-Protocol`(protocols) => protocols\n          }.getOrElse(Nil)\n\n        if (expected.diff(received).nonEmpty) raiseError(new Throwable(s\"Websocket protocols do not match. Expected $expected, received: $received\"))\n        else emit(())\n      }\n\n      def validateExtensions: Stream[F,Unit] = {\n        val received =\n          response.headers.collectFirst {\n            case `Sec-WebSocket-Extensions`(extensions) => extensions\n          }.getOrElse(Nil)\n\n        val expected =\n          request.headers.collectFirst {\n            case `Sec-WebSocket-Extensions`(extensions) => extensions\n          }.getOrElse(Nil)\n\n        if (expected.diff(received).nonEmpty)  raiseError(new Throwable(s\"Websocket extensions do not match. Expected $expected, received: $received\"))\n        else emit(())\n      }\n\n      if (response.status != HttpStatusCode.SwitchingProtocols) emit(Some(response))\n      else {\n        for {\n          _ <- validateUpgrade\n          _ <- validateConnection\n          _ <- validateFingerPrint\n          _ <- validateProtocols\n          _ <- validateExtensions\n        } yield None: Option[HttpResponseHeader]\n      }\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/websocket/WebSocketRequest.scala",
    "content": "package spinoco.fs2.http.websocket\n\nimport spinoco.protocol.http.Uri.QueryParameter\nimport spinoco.protocol.http.header.Host\nimport spinoco.protocol.http.{HostPort, HttpMethod, HttpRequestHeader, Uri}\n\n\n/**\n  * Request to establish websocket connection from the client\n  * @param hostPort   Host (port) of the server\n  * @param header     Any Header information. Note that Method will be always GET replacing any other method configured.\n  *                   Also any WebSocket Handshake headers will be overriden.\n  * @param secure     True, if the connection shall be secure (wss)\n  */\ncase class WebSocketRequest(\n   hostPort: HostPort\n   , header: HttpRequestHeader\n   , secure: Boolean\n)\n\n\nobject WebSocketRequest {\n\n  def ws(host: String, port: Int, path: String, params: QueryParameter *): WebSocketRequest = {\n    val hostPort = HostPort(host, Some(port))\n    WebSocketRequest(\n      hostPort = hostPort\n      , header = HttpRequestHeader(\n        method = HttpMethod.GET\n        , path = Uri.Path.fromUtf8String(path)\n        , headers = List(\n          Host(hostPort)\n        )\n        , query = Uri.Query(params.toList)\n      )\n      , secure = false\n    )\n  }\n\n  def ws(host: String, path: String, params: QueryParameter *): WebSocketRequest =\n    ws(host, 80, path, params:_*)\n\n\n  def wss(host: String, port: Int, path: String, params: QueryParameter *): WebSocketRequest = {\n    ws(host, port, path, params:_*).copy(secure = true)\n  }\n\n  def wss(host: String, path: String, params: QueryParameter *): WebSocketRequest =\n    wss(host, 443, path, params:_*)\n\n\n\n}\n"
  },
  {
    "path": "src/main/scala/spinoco/fs2/http/websocket/package.scala",
    "content": "package spinoco.fs2.http\n\nimport cats.effect.{Concurrent, Timer}\nimport fs2._\nimport scodec.{Decoder, Encoder}\n\nimport spinoco.protocol.http.HttpRequestHeader\nimport scala.concurrent.duration._\n\n\npackage object websocket {\n\n  /**\n    * Creates a websocket to be used on server side.\n    *\n    * Implementation is according to RFC-6455 (https://tools.ietf.org/html/rfc6455).\n    *\n    * @param pipe             A websocket pipe. `I` is received from the client and `O` is sent to client.\n    *                         Decoder (for I) and Encoder (for O) must be supplied.\n    *                         Note that this function may evaluate on the left, to indicate response to the client before\n    *                         the handshake took place (i.e. Unauthorized).\n    * @param pingInterval     An interval for the Ping / Pong protocol.\n    * @param handshakeTimeout An timeout to await for handshake to be successfull. If the handshake is not completed\n    *                         within supplied period, connection is terminated.\n    * @tparam F\n    * @return\n    */\n  def server[F[_] : Concurrent : Timer, I : Decoder, O : Encoder](\n    pipe: Pipe[F, Frame[I], Frame[O]]\n    , pingInterval: Duration = 30.seconds\n    , handshakeTimeout: FiniteDuration = 10.seconds\n  )(header: HttpRequestHeader, input: Stream[F,Byte]): Stream[F, HttpResponse[F]] =\n    WebSocket.server(pipe, pingInterval, handshakeTimeout)(header, input)\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/HttpRequestSpec.scala",
    "content": "package spinoco.fs2.http\n\nimport cats.effect.IO\nimport fs2._\nimport org.scalacheck.Properties\nimport org.scalacheck.Prop._\nimport spinoco.protocol.http._\nimport spinoco.protocol.http.codec.HttpRequestHeaderCodec\nimport spinoco.protocol.http.header._\nimport spinoco.protocol.mime.{ContentType, MIMECharset, MediaType}\n\n\nobject HttpRequestSpec extends Properties(\"HttpRequest\") {\n  import spinoco.fs2.http.util.chunk2ByteVector\n\n  property(\"encode\") = secure {\n\n    val request =\n      HttpRequest.get[IO](\n        Uri.http(\"www.spinoco.com\", \"/hello-world.html\")\n      ).withUtf8Body(\"Hello World\")\n\n\n    HttpRequest.toStream(request, HttpRequestHeaderCodec.defaultCodec)\n    .chunks.compile.toVector.map { _.map(chunk2ByteVector).reduce { _ ++ _ }.decodeUtf8 }\n    .unsafeRunSync() ?=\n    Right(Seq(\n      \"GET /hello-world.html HTTP/1.1\"\n      , \"Host: www.spinoco.com\"\n      , \"Content-Type: text/plain; charset=utf-8\"\n      , \"Content-Length: 11\"\n      , \"\"\n      , \"Hello World\"\n    ).mkString(\"\\r\\n\"))\n  }\n\n\n  property(\"decode\") = secure {\n    Stream.chunk(Chunk.bytes(\n      Seq(\n        \"GET /hello-world.html HTTP/1.1\"\n        , \"Host: www.spinoco.com\"\n        , \"Content-Type: text/plain; charset=utf-8\"\n        , \"Content-Length: 11\"\n        , \"\"\n        , \"Hello World\"\n      ).mkString(\"\\r\\n\").getBytes\n    ))\n    .covary[IO]\n    .through(HttpRequest.fromStream[IO](4096,HttpRequestHeaderCodec.defaultCodec))\n    .flatMap { case (header, body) =>\n      Stream.eval(body.chunks.compile.toVector.map(_.map(chunk2ByteVector).reduce(_ ++ _).decodeUtf8)).map { bodyString =>\n        header -> bodyString\n      }\n    }.compile.toVector.unsafeRunSync() ?= Vector(\n      HttpRequestHeader(\n        method = HttpMethod.GET\n        , path = Uri.Path / \"hello-world.html\"\n        , headers = List(\n          Host(HostPort(\"www.spinoco.com\", None))\n          , `Content-Type`(ContentType.TextContent(MediaType.`text/plain`, Some(MIMECharset.`UTF-8`)))\n          , `Content-Length`(11)\n        )\n        , query  = Uri.Query.empty\n      ) -> Right(\"Hello World\")\n    )\n\n  }\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/HttpResponseSpec.scala",
    "content": "package spinoco.fs2.http\n\nimport cats.effect.IO\nimport fs2._\nimport org.scalacheck.Properties\nimport org.scalacheck.Prop._\nimport scodec.Attempt\nimport spinoco.protocol.http.header._\nimport spinoco.protocol.http.codec.HttpResponseHeaderCodec\nimport spinoco.protocol.http.{HttpResponseHeader, HttpStatusCode}\nimport spinoco.fs2.http.util.chunk2ByteVector\nimport spinoco.protocol.mime.{ContentType, MIMECharset, MediaType}\n\n\nobject HttpResponseSpec extends Properties(\"HttpResponse\") {\n\n  property(\"encode\") = secure {\n\n    val response =\n      HttpResponse[IO](HttpStatusCode.Ok)\n      .withUtf8Body(\"Hello World\")\n\n    HttpResponse.toStream(response, HttpResponseHeaderCodec.defaultCodec)\n      .chunks.compile.toVector.map { _.map(chunk2ByteVector).reduce { _ ++ _ }.decodeUtf8 }\n      .unsafeRunSync() ?=\n      Right(Seq(\n        \"HTTP/1.1 200 OK\"\n        , \"Content-Type: text/plain; charset=utf-8\"\n        , \"Content-Length: 11\"\n        , \"\"\n        , \"Hello World\"\n      ).mkString(\"\\r\\n\"))\n\n  }\n\n\n  property(\"decode\") = secure {\n\n    Stream.chunk(Chunk.bytes(\n      Seq(\n        \"HTTP/1.1 200 OK\"\n        , \"Content-Type: text/plain; charset=utf-8\"\n        , \"Content-Length: 11\"\n        , \"\"\n        , \"Hello World\"\n      ).mkString(\"\\r\\n\").getBytes\n    ))\n    .covary[IO]\n    .through(HttpResponse.fromStream[IO](4096, HttpResponseHeaderCodec.defaultCodec))\n    .flatMap { response => Stream.eval(response.bodyAsString).map(response.header -> _ ) }\n    .compile.toVector.unsafeRunSync() ?=\n    Vector(\n      HttpResponseHeader(\n        status = HttpStatusCode.Ok\n        , reason = \"OK\"\n        , headers = List(\n          `Content-Type`(ContentType.TextContent(MediaType.`text/plain`, Some(MIMECharset.`UTF-8`)))\n          , `Content-Length`(11)\n        )\n      ) -> Attempt.Successful(\"Hello World\")\n    )\n  }\n\n\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/HttpServerSpec.scala",
    "content": "package spinoco.fs2.http\n\nimport java.net.InetSocketAddress\n\nimport cats.effect.IO\nimport fs2._\nimport org.scalacheck.Properties\nimport org.scalacheck.Prop._\nimport spinoco.fs2.http\nimport spinoco.fs2.http.body.BodyEncoder\nimport spinoco.protocol.http.header.{`Content-Length`, `Content-Type`}\nimport spinoco.protocol.mime.{ContentType, MediaType}\nimport spinoco.protocol.http.{HttpRequestHeader, HttpStatusCode, Uri}\n\nimport scala.concurrent.duration._\n\nobject HttpServerSpec extends Properties(\"HttpServer\"){\n  import Resources._\n\n  val MaxConcurrency: Int = 10\n\n  def echoService(request: HttpRequestHeader, body: Stream[IO,Byte]): Stream[IO,HttpResponse[IO]] = {\n    if (request.path != Uri.Path / \"echo\") Stream.emit(HttpResponse[IO](HttpStatusCode.Ok).withUtf8Body(\"Hello World\")).covary[IO]\n    else {\n      val ct =  request.headers.collectFirst { case `Content-Type`(ct0) => ct0 }.getOrElse(ContentType.BinaryContent(MediaType.`application/octet-stream`, None))\n      val size = request.headers.collectFirst { case `Content-Length`(sz) => sz }.getOrElse(0l)\n      val ok = HttpResponse(HttpStatusCode.Ok).chunkedEncoding.withContentType(ct).withBodySize(size)\n\n      Stream.emit(ok.copy(body = body.take(size)))\n    }\n  }\n\n  def failRouteService(request: HttpRequestHeader, body: Stream[IO,Byte]): Stream[IO,HttpResponse[IO]] = {\n    Stream.raiseError(new Throwable(\"Booom!\"))\n  }\n\n  def failingResponse(request: HttpRequestHeader, body: Stream[IO,Byte]): Stream[IO,HttpResponse[IO]] = Stream {\n    HttpResponse(HttpStatusCode.Ok).copy(body = Stream.raiseError(new Throwable(\"Kaboom!\")).covary[IO])\n  }.covary[IO]\n\n\n  property(\"simultaneous-requests\") = secure {\n    // run up to count parallel requests and then make sure all of them pass within timeout\n    val count = 100\n\n    def clients : Stream[IO, Stream[IO, (Int, Boolean)]] = {\n      val request = HttpRequest.get[IO](Uri.parse(\"http://127.0.0.1:9090/echo\").getOrElse(throw new Throwable(\"Invalid uri\")))\n      Stream.eval(client[IO]()).flatMap { httpClient =>\n      Stream.range(0,count).unchunk.map { idx =>\n        httpClient.request(request).map(resp => idx -> (resp.header.status == HttpStatusCode.Ok))\n      }}\n    }\n\n\n    (Stream(\n      http.server[IO](new InetSocketAddress(\"127.0.0.1\", 9090))(echoService).drain\n    ).covary[IO] ++ Stream.sleep_[IO](1.second) ++ clients)\n    .parJoin(MaxConcurrency)\n    .take(count)\n    .filter { case (idx, success) => success }\n    .compile.toVector.unsafeRunTimed(30.seconds).map { _.size } ?= Some(count)\n\n\n\n  }\n\n  property(\"simultaneous-requests-echo body\") = secure {\n    // run up to count parallel requests with body,  and then make sure all of them pass within timeout with body echoed back\n    val count = 100\n\n    def clients : Stream[IO, Stream[IO, (Int, Boolean)]] = {\n      val request =\n        HttpRequest.get[IO](Uri.parse(\"http://127.0.0.1:9090/echo\").getOrElse(throw new Throwable(\"Invalid uri\")))\n       .withBody(\"Hello\")(BodyEncoder.utf8String, RaiseThrowable.fromApplicativeError[IO])\n\n      Stream.eval(client[IO]()).flatMap { httpClient =>\n        Stream.range(0,count).unchunk.map { idx =>\n          httpClient.request(request).flatMap { resp =>\n            Stream.eval(resp.bodyAsString).map { attempt =>\n              val okResult = resp.header.status == HttpStatusCode.Ok\n              attempt.map(_ == \"Hello\").map(r => idx -> (r && okResult)).getOrElse(idx -> false)\n            }\n          }\n        }}\n    }\n\n    ( Stream.sleep_[IO](3.second) ++\n    (Stream(\n      http.server[IO](new InetSocketAddress(\"127.0.0.1\", 9090))(echoService).drain\n    ).covary[IO] ++ Stream.sleep_[IO](3.second) ++ clients).parJoin(MaxConcurrency))\n    .take(count)\n    .filter { case (idx, success) => success }\n    .compile.toVector.unsafeRunTimed(60.seconds).map { _.size } ?= Some(count)\n\n  }\n\n\n  property(\"request-failed-to-route\") = secure {\n    // run up to count parallel requests with body, server shall fail each, nevertheless response shall be delivered.\n    val count = 1\n\n    def clients : Stream[IO, Stream[IO, (Int, Boolean)]] = {\n      val request =\n        HttpRequest.get[IO](Uri.parse(\"http://127.0.0.1:9090/echo\").getOrElse(throw new Throwable(\"Invalid uri\")))\n\n      Stream.eval(client[IO]()).flatMap { httpClient =>\n      Stream.range(0,count).unchunk.map { idx =>\n      httpClient.request(request).map { resp =>\n        idx -> (resp.header.status == HttpStatusCode.BadRequest)\n      }}}\n    }\n\n    (Stream.sleep_[IO](3.second) ++\n    (Stream(\n      HttpServer[IO](\n        bindTo = new InetSocketAddress(\"127.0.0.1\", 9090)\n        , service = failRouteService\n        , requestFailure = _ => { Stream(HttpResponse[IO](HttpStatusCode.BadRequest)).covary[IO] }\n        , sendFailure = HttpServer.handleSendFailure[IO] _\n      ).drain\n    ).covary[IO] ++ Stream.sleep_[IO](1.second) ++ clients).parJoin(MaxConcurrency))\n    .take(count)\n    .filter { case (idx, success) => success }\n    .compile.toVector.unsafeRunTimed(30.seconds).map { _.size } ?= Some(count)\n  }\n\n\n\n  property(\"request-failed-body-send\") = secure {\n    // run up to count parallel requests with body, server shall fail each (when sending body), nevertheless response shall be delivered.\n    val count = 100\n\n    def clients : Stream[IO, Stream[IO, (Int, Boolean)]] = {\n      val request =\n        HttpRequest.get[IO](Uri.parse(\"http://127.0.0.1:9090/echo\").getOrElse(throw new Throwable(\"Invalid uri\")))\n\n      Stream.eval(client[IO]()).flatMap { httpClient =>\n        Stream.range(0,count).unchunk.map { idx =>\n          httpClient.request(request).map { resp =>\n            idx -> (resp.header.status == HttpStatusCode.Ok) // body won't be consumed, and request was succesfully sent\n          }\n        }\n      }\n    }\n\n    (Stream.sleep_[IO](3.second) ++\n    (Stream(\n      HttpServer[IO](\n        bindTo = new InetSocketAddress(\"127.0.0.1\", 9090)\n        , service = failingResponse\n        , requestFailure = HttpServer.handleRequestParseError[IO] _\n        , sendFailure = (_, _, _) => Stream.empty\n      ).drain\n    ).covary[IO] ++ Stream.sleep_[IO](1.second) ++ clients).parJoin(MaxConcurrency))\n      .take(count)\n      .filter { case (idx, success) => success }\n      .compile.toVector.unsafeRunTimed(30.seconds).map { _.size } ?= Some(count)\n  }\n\n\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/Resources.scala",
    "content": "package spinoco.fs2.http\n\nimport java.nio.channels.AsynchronousChannelGroup\nimport java.util.concurrent.Executors\n\nimport cats.effect.{Concurrent, ContextShift, IO, Timer}\n\nimport scala.concurrent.ExecutionContext\n\n\nobject Resources {\n\n  implicit val _cxs: ContextShift[IO] = IO.contextShift(ExecutionContext.Implicits.global)\n  implicit val _timer: Timer[IO] = IO.timer(ExecutionContext.Implicits.global)\n  implicit val _concurrent: Concurrent[IO] = IO.ioConcurrentEffect(_cxs)\n  implicit val AG: AsynchronousChannelGroup = AsynchronousChannelGroup.withThreadPool(Executors.newCachedThreadPool(util.mkThreadFactory(\"fs2-http-spec-AG\", daemon = true)))\n\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/internal/ChunkedEncodingSpec.scala",
    "content": "package spinoco.fs2.http.internal\n\nimport cats.effect.IO\nimport fs2._\nimport org.scalacheck.Properties\nimport org.scalacheck.Prop._\nimport scodec.bits.ByteVector\nimport spinoco.fs2.http.util.chunk2ByteVector\n\nobject ChunkedEncodingSpec extends Properties(\"ChunkedEncoding\") {\n\n  property(\"encode-decode\") = forAll { strings: List[String] =>\n    val in = strings.foldLeft(Stream.empty.covaryAll[IO, Byte]) { case(s,n) => s ++ Stream.chunk(Chunk.bytes(n.getBytes)) }\n\n\n    (in through ChunkedEncoding.encode through ChunkedEncoding.decode(1024))\n    .chunks\n    .compile.toVector\n    .map(_.foldLeft(ByteVector.empty){ case (bv, n) => bv ++ chunk2ByteVector(n) })\n    .map(_.decodeUtf8)\n    .unsafeRunSync() ?= Right(\n      strings.mkString\n    )\n\n  }\n\n  val wikiExample = \"4\\r\\n\" +\n    \"Wiki\\r\\n\" +\n    \"5\\r\\n\" +\n    \"pedia\\r\\n\" +\n    \"E\\r\\n\" +\n    \" in\\r\\n\"+\n    \"\\r\\n\" +\n    \"chunks.\\r\\n\" +\n    \"0\\r\\n\" +\n    \"\\r\\n\"\n\n  property(\"encoded-wiki-example\") = secure {\n\n\n    (Stream.chunk[IO, Byte](Chunk.bytes(wikiExample.getBytes)) through ChunkedEncoding.decode(1024))\n    .covary[IO]\n    .chunks\n    .compile.toVector\n    .map(_.foldLeft(ByteVector.empty){ case (bv, n) => bv ++ chunk2ByteVector(n) })\n    .map(_.decodeUtf8)\n    .unsafeRunSync() ?= Right(\n    \"Wikipedia in\\r\\n\\r\\nchunks.\"\n    )\n\n  }\n\n  property(\"decoded-wiki-example\") = secure {\n    val chunks:Stream[IO,Byte] = Stream.emits(\n      Seq(\n        \"Wiki\"\n        , \"pedia\"\n        , \" in\\r\\n\\r\\nchunks.\"\n      )\n    ).flatMap(s => Stream.chunk[IO, Byte](Chunk.bytes(s.getBytes)))\n\n    (chunks through ChunkedEncoding.encode)\n      .chunks\n      .compile.toVector\n      .map(_.foldLeft(ByteVector.empty){ case (bv, n) => bv ++ chunk2ByteVector(n) })\n      .map(_.decodeUtf8)\n      .unsafeRunSync() ?= Right(wikiExample)\n  }\n\n\n\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/internal/HttpClientApp.scala",
    "content": "package spinoco.fs2.http.internal\n\nimport cats.effect.IO\nimport fs2._\nimport spinoco.fs2.http\nimport spinoco.fs2.http.HttpRequest\nimport spinoco.protocol.http.Uri\n\n\nobject HttpClientApp extends App {\n\n  import spinoco.fs2.http.Resources._\n\n\n\n  http.client[IO]().flatMap { httpClient =>\n\n    httpClient.request(HttpRequest.get(Uri.https(\"www.google.cz\", \"/\"))).flatMap { resp =>\n      Stream.eval(resp.bodyAsString)\n    }.compile.toVector.map {\n      println\n    }\n\n  }.unsafeRunSync()\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/internal/HttpServerApp.scala",
    "content": "package spinoco.fs2.http.internal\n\nimport java.net.InetSocketAddress\n\nimport cats.effect.IO\nimport fs2._\nimport spinoco.fs2.http\nimport spinoco.fs2.http.HttpResponse\nimport spinoco.protocol.http.header._\nimport spinoco.protocol.mime.{ContentType, MediaType}\nimport spinoco.protocol.http.{HttpRequestHeader, HttpStatusCode, Uri}\n\n\nobject HttpServerApp extends App {\n\n  import spinoco.fs2.http.Resources._\n\n  def service(request: HttpRequestHeader, body: Stream[IO,Byte]): Stream[IO,HttpResponse[IO]] = {\n    if (request.path != Uri.Path / \"echo\") Stream.emit(HttpResponse[IO](HttpStatusCode.Ok).withUtf8Body(\"Hello World\")).covary[IO]\n    else {\n      val ct =  request.headers.collectFirst { case `Content-Type`(ct) => ct }.getOrElse(ContentType.BinaryContent(MediaType.`application/octet-stream`, None))\n      val size = request.headers.collectFirst { case `Content-Length`(sz) => sz }.getOrElse(0l)\n      val ok = HttpResponse(HttpStatusCode.Ok).chunkedEncoding.withContentType(ct).withBodySize(size)\n\n      Stream.emit(ok.copy(body = body.take(size)))\n    }\n  }\n\n  http.server(new InetSocketAddress(\"127.0.0.1\", 9090))(service).compile.drain.unsafeRunSync()\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/routing/MatcherSpec.scala",
    "content": "package spinoco.fs2.http.routing\n\nimport cats.effect.IO\nimport fs2._\nimport org.scalacheck.Properties\nimport org.scalacheck.Prop._\nimport spinoco.fs2.http.HttpResponse\nimport spinoco.protocol.http.{HttpMethod, HttpRequestHeader, HttpStatusCode, Uri}\n\nobject MatcherSpec extends Properties(\"Matcher\"){\n\n  val request = HttpRequestHeader(\n    method = HttpMethod.GET\n  )\n\n  def requestAt(s: String): HttpRequestHeader =\n    request.copy(path = Uri.Path.fromUtf8String(s))\n\n  implicit class RouteTaskInstance(val self: Route[IO]) {\n    def matches(request: HttpRequestHeader, body: Stream[IO, Byte] = Stream.empty.covaryAll[IO, Byte]): MatchResult[IO, Stream[IO, HttpResponse[IO]]] =\n      Matcher.run(self)(request, body).unsafeRunSync()\n  }\n\n  val RespondOk: Stream[IO, HttpResponse[IO]] = Stream.emit(HttpResponse[IO](HttpStatusCode.Ok)).covary[IO]\n\n  property(\"matches-uri\") = secure {\n\n    val r: Route[IO] = \"hello\" / \"world\" map { _ => RespondOk }\n\n    (r matches requestAt(\"/hello/world\") isSuccess) &&\n    (r matches requestAt(\"/hello/world2\") isFailure)\n\n  }\n\n\n  property(\"matches-uri-alternative\") = secure {\n    val r: Route[IO] = \"hello\" / choice(\n      \"world\"\n      , \"nation\" / ( \"greeks\" or \"romans\" )\n      , \"city\" / \"of\" / \"prague\"\n    ) map { _ => RespondOk }\n\n    (r matches requestAt(\"/hello/world\") isSuccess) &&\n    (r matches requestAt(\"/hello/nation/greeks\") isSuccess) &&\n    (r matches requestAt(\"/hello/nation/romans\") isSuccess) &&\n    (r matches requestAt(\"/hello/city/of/prague\") isSuccess) &&\n    (r matches requestAt(\"/bye\") isFailure) &&\n    (r matches requestAt(\"/hello/town\") isFailure) &&\n    (r matches requestAt(\"/hello/nation/egyptians\") isFailure) &&\n    (r matches requestAt(\"/hello/city/of/berlin\") isFailure)\n  }\n\n\n  property(\"matches-deep-uri\") = secure {\n    val r: Route[IO] = (1 until 10000).foldLeft[Matcher[IO, String]](\"deep\" / \"0\") {\n      case (m, next) => m / next.toString\n    } map { _ => RespondOk }\n\n    val path = (1 until 10000).mkString(\"/deep/0/\", \"/\", \"\")\n\n    r matches requestAt(path) isSuccess\n  }\n\n\n  property(\"matcher-hlist\")  = secure {\n    val r: Route[IO] = \"hello\" :/: \"body\" :/: as[Int] :/: \"foo\" :/: as[Long] map { _ => RespondOk }\n\n    r matches(requestAt(\"/hello/body/33/foo/22\")) isSuccess\n\n  }\n\n\n  property(\"matcher-advance-recover\") = secure {\n    val r: Route[IO] = choice(\n      \"hello\" / choice (  \"user\" / \"one\" ) map { _ => RespondOk }\n      , \"hello\" / \"people\" map { _ => RespondOk }\n    )\n\n    (r matches(requestAt(\"/hello/people\")) isSuccess)\n  }\n\n\n  property(\"matches-uri-alternative\") = secure {\n    val r: Route[IO] = \"hello\" / choice(\n      \"world\"\n      , \"nation\" / ( \"greeks\" or \"romans\" )\n      , \"city\" / \"of\" / \"prague\"\n    ) map { _ => RespondOk }\n\n    (r matches requestAt(\"/hello/world\") isSuccess) &&\n    (r matches requestAt(\"/hello/nation/greeks\") isSuccess) &&\n    (r matches requestAt(\"/hello/nation/romans\") isSuccess) &&\n    (r matches requestAt(\"/hello/city/of/prague\") isSuccess) &&\n    (r matches requestAt(\"/bye\") isFailure) &&\n    (r matches requestAt(\"/hello/town\") isFailure) &&\n    (r matches requestAt(\"/hello/nation/egyptians\") isFailure) &&\n    (r matches requestAt(\"/hello/city/of/berlin\") isFailure)\n  }\n\n\n  property(\"matches-deep-uri\") = secure {\n    val r: Route[IO] = (1 until 10000).foldLeft[Matcher[IO, String]](\"deep\" / \"0\") {\n      case (m, next) => m / next.toString\n    } map { _ => RespondOk }\n\n    val path = (1 until 10000).mkString(\"/deep/0/\", \"/\", \"\")\n\n    r matches requestAt(path) isSuccess\n  }\n\n\n  property(\"matcher-hlist\")  = secure {\n    val r: Route[IO] = \"hello\" :/: \"body\" :/: as[Int] :/: \"foo\" :/: as[Long] map { _ => RespondOk }\n\n    r matches(requestAt(\"/hello/body/33/foo/22\")) isSuccess\n\n  }\n\n\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/sse/SSEEncodingSpec.scala",
    "content": "package spinoco.fs2.http.sse\n\nimport cats.effect.IO\nimport fs2.Chunk.ByteVectorChunk\nimport fs2._\nimport org.scalacheck.Properties\nimport org.scalacheck.Prop._\nimport scodec.bits.ByteVector\n\nimport spinoco.fs2.http.sse.SSEMessage.SSEData\nimport spinoco.fs2.http.util.chunk2ByteVector\n\nobject SSEEncodingSpec extends Properties(\"SSEEncoding\") {\n\n\n  property(\"encode\") = secure {\n    Stream(\n      SSEMessage.SSEData(Seq(\"data1\"), None, None)\n      , SSEMessage.SSEData(Seq(\"data2\", \"data3\"), None, None)\n      , SSEMessage.SSEData(Seq(\"data4\"), Some(\"event1\"), None)\n      , SSEMessage.SSEData(Seq(\"data5\"), None, Some(\"id1\"))\n      , SSEMessage.SSEData(Seq(\"data6\"), Some(\"event2\"), Some(\"id2\"))\n    ).covary[IO].through(SSEEncoding.encode[IO]).chunks.compile.toVector.map { _ map chunk2ByteVector reduce (_ ++ _) decodeUtf8  }.unsafeRunSync() ?=\n    Right(\n    \"data: data1\\n\\ndata: data2\\ndata: data3\\n\\nevent: event1\\ndata: data4\\n\\ndata: data5\\nid: id1\\n\\nevent: event2\\ndata: data6\\nid: id2\\n\\n\"\n    )\n\n  }\n\n  property(\"decode.example.1\") = secure {\n\n    Stream.chunk(ByteVectorChunk(ByteVector.view(\n      \": test stream\\n\\ndata: first event\\nid: 1\\n\\ndata:second event\\nid\\n\\ndata:  third event\".getBytes()\n    )))\n    .covary[IO]\n    .through(SSEEncoding.decode[IO]).compile.toVector.unsafeRunSync() ?=\n    Vector(\n      SSEData(Vector(\"first event\"), None, Some(\"1\"))\n      , SSEData(Vector(\"second event\"), None, Some(\"\"))\n    )\n\n  }\n\n  property(\"decode.example.2\") = secure {\n\n    Stream.chunk(ByteVectorChunk(ByteVector.view(\n      \"data\\n\\ndata\\ndata\\n\\ndata:\".getBytes()\n    )))\n    .covary[IO]\n    .through(SSEEncoding.decode[IO]).compile.toVector.unsafeRunSync() ?=\n    Vector(\n      SSEData(Vector(\"\"), None, None)\n      , SSEData(Vector(\"\",\"\"), None, None)\n    )\n\n  }\n\n\n  property(\"decode.example.3\") = secure {\n\n    Stream.chunk(ByteVectorChunk(ByteVector.view(\n      \"data:test\\n\\ndata: test\\n\\n\".getBytes()\n    )))\n    .covary[IO]\n    .through(SSEEncoding.decode[IO]).compile.toVector.unsafeRunSync() ?=\n    Vector(\n      SSEData(Vector(\"test\"), None, None)\n      , SSEData(Vector(\"test\"), None, None)\n    )\n\n  }\n\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/util/UtilSpec.scala",
    "content": "package spinoco.fs2.http.util\n\nimport cats.effect.IO\nimport fs2._\nimport org.scalacheck.Prop._\nimport org.scalacheck.{Arbitrary, Gen, Properties}\nimport scodec.bits.Bases.{Alphabets, Base64Alphabet}\nimport scodec.bits.ByteVector\nimport shapeless.the\nimport spinoco.fs2.http.util\n\n\n\nobject UtilSpec extends Properties(\"util\"){\n\n  case class EncodingSample(chunkSize:Int, text:String, alphabet: Base64Alphabet)\n\n  implicit val encodingTestInstance : Arbitrary[EncodingSample] = Arbitrary {\n    for {\n      s <- the[Arbitrary[String]].arbitrary\n      chunkSize <- Gen.choose(1,s.length max 1)\n      alphabet <- Gen.oneOf(Seq(Alphabets.Base64Url, Alphabets.Base64))\n    } yield EncodingSample(chunkSize, s, alphabet)\n  }\n\n  property(\"encodes.base64\") = forAll { sample: EncodingSample =>\n    Stream.chunk[IO, Byte](Chunk.bytes(sample.text.getBytes)).chunkLimit(sample.chunkSize).flatMap(Stream.chunk[IO, Byte])\n    .through(util.encodeBase64Raw(sample.alphabet))\n    .chunks\n    .fold(ByteVector.empty){ case (acc, n) => acc ++ chunk2ByteVector(n)}\n    .map(_.decodeUtf8)\n    .compile.toVector.unsafeRunSync() ?= Vector(\n      Right(ByteVector.view(sample.text.getBytes).toBase64(sample.alphabet))\n    )\n  }\n\n\n  property(\"decodes.base64\") = forAll { sample: EncodingSample =>\n    val encoded = ByteVector.view(sample.text.getBytes).toBase64(sample.alphabet)\n    Stream.chunk[IO, Byte](Chunk.bytes(encoded.getBytes))\n    .chunkLimit(sample.chunkSize).flatMap(Stream.chunk[IO, Byte])\n    .through(util.decodeBase64Raw(sample.alphabet))\n    .chunks\n    .fold(ByteVector.empty){ case (acc, n) => acc ++ chunk2ByteVector(n)}\n    .map(_.decodeUtf8)\n    .compile.toVector.unsafeRunSync() ?= Vector(\n      Right(sample.text)\n    )\n  }\n\n  property(\"encodes.decodes.base64\") =  forAll { sample: EncodingSample =>\n    val r =\n      Stream.chunk[IO, Byte](Chunk.bytes(sample.text.getBytes)).covary[IO].chunkLimit(sample.chunkSize).flatMap(Stream.chunk[IO, Byte])\n      .through(util.encodeBase64Raw(sample.alphabet))\n      .through(util.decodeBase64Raw(sample.alphabet))\n      .chunks\n      .fold(ByteVector.empty){ case (acc, n) => acc ++ chunk2ByteVector(n)}\n      .compile.toVector.unsafeRunSync()\n\n\n\n    r ?= Vector(ByteVector.view(sample.text.getBytes))\n\n  }\n\n\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/websocket/WebSocketClientApp.scala",
    "content": "package spinoco.fs2.http.websocket\n\n\nimport cats.effect.IO\n\nimport scala.concurrent.duration._\nimport fs2._\nimport scodec.Codec\nimport scodec.codecs._\nimport spinoco.protocol.http.Uri.QueryParameter\n\n\nobject WebSocketClientApp extends App {\n\n  import spinoco.fs2.http.Resources._\n\n\n  def wspipe: Pipe[IO, Frame[String], Frame[String]] = { inbound =>\n    val output =  Stream.awakeEvery[IO](1.second).map { dur => println(s\"SENT $dur\"); Frame.Text(s\" ECHO $dur\") }.take(5)\n    output concurrently inbound.take(5).map { in => println((\"RECEIVED \", in)) }\n  }\n\n  implicit val codecString: Codec[String] = utf8\n\n  WebSocket.client(\n    WebSocketRequest.ws(\"echo.websocket.org\", \"/\", QueryParameter.single(\"encoding\", \"text\"))\n    , wspipe\n  ).map { x =>\n    println((\"RESULT OF WS\", x))\n  }.compile.drain.unsafeRunSync()\n\n}\n"
  },
  {
    "path": "src/test/scala/spinoco/fs2/http/websocket/WebSocketSpec.scala",
    "content": "package spinoco.fs2.http.websocket\n\nimport java.net.InetSocketAddress\n\nimport cats.effect.IO\nimport fs2._\nimport org.scalacheck.{Gen, Prop, Properties}\nimport org.scalacheck.Prop._\nimport scodec.Codec\nimport scodec.bits.ByteVector\nimport scodec.codecs._\nimport spinoco.fs2.http\n\nimport scala.concurrent.duration._\n\nobject WebSocketSpec extends Properties(\"WebSocket\") {\n  import spinoco.fs2.http.Resources._\n\n  property(\"random-bytes-size\") = {\n    val interval = Gen.choose(1,50)\n    Prop.forAll(interval) { size: Int =>\n      WebSocket.impl.randomBytes(size).length == size\n    }\n  }\n\n  property(\"computes-fingerprint\") = secure {\n    val key = ByteVector.fromBase64(\"L54CF9+DxAZSOHDW3AoG1A==\").get\n    val fp = WebSocket.impl.computeFingerPrint(key)\n\n    fp ?= ByteVector.fromBase64(\"rsNEx/DEOf5YTl9Jd/jPWeKlKbk=\").get\n  }\n\n  property(\"websocket-server\") = secure {\n    implicit val codecString: Codec[String] = utf8\n\n    var received:List[Frame[String]] = Nil\n\n    def serverEcho: Pipe[IO, Frame[String], Frame[String]] = { in => in }\n\n    def clientData: Pipe[IO, Frame[String], Frame[String]] = { inbound =>\n      val output =  Stream.awakeEvery[IO](1.seconds).map { dur => Frame.Text(s\" ECHO $dur\") }.take(5)\n\n      output merge inbound.take(5).evalMap { in => IO { received = received :+ in }}.drain\n    }\n\n    val serverStream =\n      http.server[IO](new InetSocketAddress(\"127.0.0.1\", 9090))(\n        server (\n          pipe = serverEcho\n          , pingInterval = 500.millis\n          , handshakeTimeout = 10.seconds\n        )\n      )\n\n    val clientStream =\n      Stream.sleep_[IO](3.seconds) ++\n      WebSocket.client(\n        WebSocketRequest.ws(\"127.0.0.1\", 9090, \"/\")\n        , clientData\n      )\n\n    val resultClient =\n      (serverStream.drain mergeHaltBoth clientStream).compile.toVector.unsafeRunTimed(20.seconds)\n\n    (resultClient ?= Some(Vector(None))) &&\n      (received.size ?= 5)\n\n  }\n\n  property(\"websocket-cut-frame-125-bytes\") = secure{\n    val data = ByteVector.fromHex(\"827d\" + \"aa\"*170).get\n    val cut = WebSocket.impl.cutFrame(data)\n    cut ?= Some(data.take(127))\n  }\n\n  property(\"websocket-cut-frame-256-bytes\") = secure{\n    val data = ByteVector.fromHex(\"827e0100\" + \"aa\"*300).get\n    val cut = WebSocket.impl.cutFrame(data)\n    cut ?= Some(data.take(260))\n  }\n\n  property(\"websocket-cut-frame-65536-bytes\") = secure{\n    val data = ByteVector.fromHex(\"827f0000000000010000\" + \"aa\"*70000).get\n    val cut = WebSocket.impl.cutFrame(data)\n    cut ?= Some(data.take(65546))\n  }\n\n  property(\"websocket-cut-frame-less-16\") = secure{\n    val data = ByteVector.fromHex(\"827\").get\n    val cut = WebSocket.impl.cutFrame(data)\n    cut ?= None\n  }\n\n  property(\"websocket-cut-frame-not-enough\") = secure{\n    val data = ByteVector.fromHex(\"827e0100\" + \"aa\"*100).get\n    val cut = WebSocket.impl.cutFrame(data)\n    cut ?= None\n  }\n\n}\n"
  },
  {
    "path": "version.sbt",
    "content": "version in ThisBuild := \"0.4.2-SNAPSHOT\"\n"
  }
]