[
  {
    "path": ".gitignore",
    "content": "ml\n*.ipr\n*.iws\n*.pyc\n*.tm.epoch\n*.vim\n*/project/boot\n*/project/build/target\n*/project/project.target.config-classes\n*-shim.sbt\n*~\n.#*\n.*.swp\n.DS_Store\n.cache\n.cache\n.classpath\n.codefellow\n.ensime*\n.eprj\n.history\n.idea\n.manager\n.multi-jvm\n.project\n.scala_dependencies\n.scalastyle\n.settings\n.tags\n.tags_sorted_by_file\n.target\n.worksheet\nMakefile\nTAGS\nlib_managed\nlogs\nproject/boot/*\nproject/plugins/project\nsrc_managed\ntarget\ntm*.lck\ntm*.log\ntm.out\nworker*.log\n/bin\nt\ntags\ntq:q\nTODO\n"
  },
  {
    "path": "README.md",
    "content": "advanced-http4s\n===============\n\nCode samples of advanced features of [Http4s](http://http4s.org/) in combination with some features of [Fs2](https://functional-streams-for-scala.github.io/fs2/) not often seen.\n\nStreaming end to end\n--------------------\n\n- **Server**: Streaming responses end to end, from the `FileService` reading all the directories in your `$HOME` directory to the `FileHttpEndpoint`.\n- **StreamClient**: Parsing chunks of the response body produced by the server in a streaming fashion way.\n\n> You'll need two sbt sessions. Run the server in one and after the client in the other to try it out.\n\nMiddleware Composition\n----------------------\n\n- **GZip**: For compressed responses. Client must set the `Accept Encoding` header to `gzip`.\n- **AutoSlash**: To make endpoints work with and without the slash `/` at the end.\n\n> Response compression is verified by `HexNameHttpEndpointSpec`. You can also try it out on Postman or similar.\n\n- **Timeout**: Handling response timeouts with the given middleware.\n\n> The `TimeoutHttpEndpoint` generates a response in a random time to demonstrate the use.\n\n- **NonStreamResponse**: Using the `ChunkAggregator` middleware to wrap the streaming `FileHttpEndpoint` and remove the Chunked Transfer Encoding.\n\n> The endpoint `/v1/nonstream/dirs?depth=3` demonstrates the use case.\n\nMedia Type negotiation\n----------------------\n\n- **XML** and **Json**: Decoding request body with either of these types for the same endpoint.\n\n> The `JsonXmlHttpEndpoint` demonstrates this use case and it's validated in its spec.\n\nMultipart Form Data\n-------------------\n\n- **Server**: The `MultipartHttpEndpoint` is responsible for parsing multipart data with the given multipart decoder.\n- **MultipartClient**: Example uploading both text and an image.\n\n> Similar to the streaming example, you'll need to run both Server and MultipartClient to see how it works.\n\n*NOTE: Beware of the creation of `rick.jpg` file in your HOME directory!*\n\nAuthentication\n--------------\n\n- **Basic Auth**: Using the given middleware as demonstrated by the `BasicAuthHttpEndpoint`.\n- **OAuth 2**: Using GitHub as demonstrated by the `GitHubHttpEndpoint`.\n\n-----------------------------------------------------------------------------\n\nfs2 examples\n============\n\nIn the fs2 package you'll find some practical examples of the few things it's possible to build with this powerful streaming library. This might serve as a starting point, your creativity will do the rest.\n\nfs2.async package\n------------------\n\nApart from the use of the three core types `Stream[F, O]`, `Pipe[F, I, O]` and `Sink[F, I]` you'll find examples of use of the following types:\n\n- `Topic[F, A]`\n- `Signal[F, A]`\n- `Queue[F, A]`\n- `Ref[F, A]`\n- `Promise[F, A]`\n- `Semaphore[F]`\n\nIn addition to the use of some other functions useful in Parallel and Concurrent scenarios.\n\n"
  },
  {
    "path": "build.sbt",
    "content": "import Dependencies._\n\nlazy val root = (project in file(\".\")).\n  settings(\n    inThisBuild(List(\n      organization := \"com.github.gvolpe\",\n      scalaVersion := \"2.12.4\",\n      version      := \"0.1.0-SNAPSHOT\",\n      scalacOptions := Seq(\n        \"-deprecation\",\n        \"-encoding\",\n        \"UTF-8\",\n        \"-feature\",\n        \"-language:existentials\",\n        \"-language:higherKinds\",\n        \"-Ypartial-unification\"\n      )    \n    )),\n    name := \"Advanced Http4s\",\n    libraryDependencies ++= Seq(\n      Libraries.catsEffect,\n      Libraries.monix,\n      Libraries.fs2Core,\n      Libraries.http4sServer,\n      Libraries.http4sClient,\n      Libraries.http4sDsl,\n      Libraries.http4sCirce,\n      Libraries.http4sXml,\n      Libraries.circeCore,\n      Libraries.circeGeneric,\n      Libraries.typesafeConfig,\n      Libraries.logback,\n      Libraries.scalaTest,\n      Libraries.scalaCheck\n    ),\n    addCompilerPlugin(\"org.spire-math\" % \"kind-projector\" % \"0.9.5\" cross CrossVersion.binary)\n  )\n"
  },
  {
    "path": "project/Dependencies.scala",
    "content": "import sbt._\n\nobject Dependencies {\n\n  object Versions {\n    val CatsEffect  = \"0.8\"\n    val Monix       = \"3.0.0-M3\"\n    val Fs2         = \"0.10.2\"\n    val Http4s      = \"0.18.1\"\n    val Tsec        = \"0.0.1-M9\"\n    val Circe       = \"0.9.1\"\n    val ScalaTest   = \"3.0.4\"\n    val ScalaCheck  = \"1.13.4\"\n    val Logback     = \"1.2.1\"\n    val TypesafeCfg = \"1.3.1\"\n  }\n\n  object Libraries {\n    lazy val catsEffect     = \"org.typelevel\"       %% \"cats-effect\"                  % Versions.CatsEffect\n    lazy val monix          = \"io.monix\"            %% \"monix\"                        % Versions.Monix\n\n    lazy val fs2Core        = \"co.fs2\"              %% \"fs2-core\"                     % Versions.Fs2\n    lazy val fs2IO          = \"co.fs2\"              %% \"fs2-io\"                       % Versions.Fs2\n\n    lazy val http4sServer   = \"org.http4s\"          %% \"http4s-blaze-server\"          % Versions.Http4s\n    lazy val http4sClient   = \"org.http4s\"          %% \"http4s-blaze-client\"          % Versions.Http4s\n    lazy val http4sDsl      = \"org.http4s\"          %% \"http4s-dsl\"                   % Versions.Http4s\n    lazy val http4sCirce    = \"org.http4s\"          %% \"http4s-circe\"                 % Versions.Http4s\n    lazy val http4sXml      = \"org.http4s\"          %% \"http4s-scala-xml\"             % Versions.Http4s\n\n    lazy val tsecJwtMac     = \"io.github.jmcardon\"  %% \"tsec-jwt-mac\"                 % Versions.Tsec\n\n    lazy val circeCore      = \"io.circe\"            %% \"circe-core\"                   % Versions.Circe\n    lazy val circeGeneric   = \"io.circe\"            %% \"circe-generic\"                % Versions.Circe\n\n    lazy val typesafeConfig = \"com.typesafe\"        %  \"config\"                       % Versions.TypesafeCfg\n    lazy val logback        = \"ch.qos.logback\"      %  \"logback-classic\"              % Versions.Logback\n\n    lazy val scalaTest      = \"org.scalatest\"       %% \"scalatest\"                    % Versions.ScalaTest   % Test\n    lazy val scalaCheck     = \"org.scalacheck\"      %% \"scalacheck\"                   % Versions.ScalaCheck  % Test\n  }\n\n}\n"
  },
  {
    "path": "project/build.properties",
    "content": "sbt.version=1.1.0\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/fs2/Counter.scala",
    "content": "package com.github.gvolpe.fs2\n\nimport cats.effect.{Effect, IO}\nimport fs2.StreamApp.ExitCode\nimport fs2.async.Ref\nimport fs2.{Scheduler, Sink, Stream, StreamApp, async}\n\nimport scala.concurrent.ExecutionContext.Implicits.global\n\nobject CounterApp extends Counter[IO]\n\n/**\n  * Concurrent counter that demonstrates the use of [[fs2.async.Ref]].\n  *\n  * The workers will concurrently run and modify the value of the Ref so this is one\n  * possible outcome showing \"#worker >> currentCount\":\n  *\n  * #1 >> 0\n  * #3 >> 0\n  * #2 >> 0\n  * #1 >> 2\n  * #2 >> 3\n  * #3 >> 3\n  * */\nclass Counter[F[_] : Effect] extends StreamApp[F] {\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): fs2.Stream[F, ExitCode] =\n    Scheduler(corePoolSize = 10).flatMap { implicit S =>\n      for {\n        ref <- Stream.eval(async.refOf[F, Int](0))\n        w1  = new Worker[F](1, ref)\n        w2  = new Worker[F](2, ref)\n        w3  = new Worker[F](3, ref)\n        ec  <- Stream(w1.start, w2.start, w3.start).join(3).drain ++ Stream.emit(ExitCode.Success)\n      } yield ec\n    }\n\n}\n\nclass Worker[F[_]](number: Int, ref: Ref[F, Int])\n                  (implicit F: Effect[F]) {\n\n  private val sink: Sink[F, Int] = _.evalMap(n => F.delay(println(s\"#$number >> $n\")))\n\n  def start: Stream[F, Unit] =\n    for {\n      _ <- Stream.eval(ref.get) to sink\n      _ <- Stream.eval(ref.modify(_ + 1))\n      _ <- Stream.eval(ref.get) to sink\n    } yield ()\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/fs2/Fifo.scala",
    "content": "package com.github.gvolpe.fs2\n\nimport cats.effect.{Effect, IO}\nimport fs2.StreamApp.ExitCode\nimport fs2.async.mutable.Queue\nimport fs2.{Scheduler, Stream, StreamApp, async}\n\nimport scala.concurrent.ExecutionContext.Implicits.global\nimport scala.concurrent.duration._\n\nobject FifoApp extends Fifo[IO]\n\n/**\n  * Represents a FIFO (First IN First OUT) system built on top of two [[fs2.async.mutable.Queue]].\n  *\n  * q1 has a buffer size of 1 while q2 has a buffer size of 100 so you will notice the buffering when\n  * pulling elements out of the q2.\n  * */\nclass Fifo[F[_]: Effect] extends StreamApp[F] {\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): fs2.Stream[F, ExitCode] =\n    Scheduler(corePoolSize = 4).flatMap { implicit S =>\n      for {\n        q1 <- Stream.eval(async.boundedQueue[F, Int](1))\n        q2 <- Stream.eval(async.boundedQueue[F, Int](100))\n        bp = new Buffering[F](q1, q2)\n        ec <- S.delay(Stream.emit(ExitCode.Success).covary[F], 5.seconds) concurrently bp.start.drain\n      } yield ec\n    }\n\n}\n\nclass Buffering[F[_]](q1: Queue[F, Int], q2: Queue[F, Int])(implicit F: Effect[F]) {\n\n  def start: Stream[F, Unit] =\n    Stream(\n      Stream.range(0, 1000).covary[F] to q1.enqueue,\n      q1.dequeue to q2.enqueue,\n      //.map won't work here as you're trying to map a pure value with a side effect. Use `evalMap` instead.\n      q2.dequeue.evalMap(n => F.delay(println(s\"Pulling out $n from Queue #2\")))\n    ).join(3)\n\n}"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/fs2/Once.scala",
    "content": "package com.github.gvolpe.fs2\n\nimport cats.effect.{Effect, IO}\nimport fs2.StreamApp.ExitCode\nimport fs2.async.Promise\nimport fs2.{Scheduler, Stream, StreamApp, async}\n\nimport scala.concurrent.ExecutionContext.Implicits.global\n\nobject OnceApp extends Once[IO]\n\n/**\n  * Demonstrate the use of [[fs2.async.Promise]]\n  *\n  * Two processes will try to complete the promise at the same time but only one will succeed,\n  * completing the promise exactly once.\n  * The loser one will raise an error when trying to complete a promise already completed,\n  * that's why we call `attempt` on the evaluation.\n  *\n  * Notice that the loser process will remain running in the background and the program will\n  * end on completion of all of the inner streams.\n  *\n  * So it's a \"race\" in the sense that both processes will try to complete the promise at the\n  * same time but conceptually is different from \"race\". So for example, if you schedule one\n  * of the processes to run in 10 seconds from now, then the entire program will finish after\n  * 10 seconds and you can know for sure that the process completing the promise is going to\n  * be the first one.\n  * */\nclass Once[F[_]: Effect] extends StreamApp[F] {\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): fs2.Stream[F, ExitCode] =\n    Scheduler(corePoolSize = 4).flatMap { implicit scheduler =>\n      for {\n        p <- Stream.eval(async.promise[F, Int])\n        e <- new ConcurrentCompletion[F](p).start\n      } yield e\n    }\n\n}\n\nclass ConcurrentCompletion[F[_]](p: Promise[F, Int])(implicit F: Effect[F]) {\n\n  private def attemptPromiseCompletion(n: Int): Stream[F, Unit] =\n    Stream.eval(p.complete(n)).attempt.drain\n\n  def start: Stream[F, ExitCode] =\n    Stream(\n      attemptPromiseCompletion(1),\n      attemptPromiseCompletion(2),\n      Stream.eval(p.get).evalMap(n => F.delay(println(s\"Result: $n\")))\n    ).join(3).drain ++ Stream.emit(ExitCode.Success)\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/fs2/PubSub.scala",
    "content": "package com.github.gvolpe.fs2\n\nimport cats.effect.{Effect, IO}\nimport fs2.StreamApp.ExitCode\nimport fs2.async.mutable.{Signal, Topic}\nimport fs2.{Scheduler, Sink, Stream, StreamApp, async}\n\nimport scala.concurrent.duration._\nimport scala.concurrent.ExecutionContext.Implicits.global\n\nobject PubSubApp extends PubSub[IO]\n\n/**\n  * Single Publisher / Multiple Subscribers application implemented on top of\n  * [[fs2.async.mutable.Topic]] and [[fs2.async.mutable.Signal]].\n  *\n  * The program ends after 15 seconds when the signal interrupts the publishing of more events\n  * given that the final streaming merge halts on the end of its left stream (the publisher).\n  *\n  * - Subscriber #1 should receive 15 events + the initial empty event\n  * - Subscriber #2 should receive 10 events\n  * - Subscriber #3 should receive 5 events\n  * */\nclass PubSub[F[_]: Effect] extends StreamApp[F] {\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): fs2.Stream[F, ExitCode] =\n    Scheduler(corePoolSize = 4).flatMap { implicit S =>\n      for {\n        topic     <- Stream.eval(async.topic[F, Event](Event(\"\")))\n        signal    <- Stream.eval(async.signalOf[F, Boolean](false))\n        service   = new EventService[F](topic, signal)\n        exitCode  <- Stream(\n                      S.delay(Stream.eval(signal.set(true)), 15.seconds),\n                      service.startPublisher concurrently service.startSubscribers\n                    ).join(2).drain ++ Stream.emit(ExitCode.Success)\n      } yield exitCode\n    }\n\n}\n\nclass EventService[F[_]](eventsTopic: Topic[F, Event],\n                         interrupter: Signal[F, Boolean])\n                        (implicit F: Effect[F], S: Scheduler) {\n\n  // Publishing events every one second until signaling interruption\n  def startPublisher: Stream[F, Unit] =\n    S.awakeEvery(1.second).flatMap { _ =>\n      val event = Event(System.currentTimeMillis().toString)\n      Stream.eval(eventsTopic.publish1(event))\n    }.interruptWhen(interrupter)\n\n  // Creating 3 subscribers in a different period of time and join them to run concurrently\n  def startSubscribers: Stream[F, Unit] = {\n    val s1: Stream[F, Event] = eventsTopic.subscribe(10)\n    val s2: Stream[F, Event] = S.delay(eventsTopic.subscribe(10), 5.seconds)\n    val s3: Stream[F, Event] = S.delay(eventsTopic.subscribe(10), 10.seconds)\n\n    def sink(subscriberNumber: Int): Sink[F, Event] =\n      _.evalMap(e => F.delay(println(s\"Subscriber #$subscriberNumber processing event: $e\")))\n\n    Stream(s1 to sink(1), s2 to sink(2), s3 to sink(3)).join(3)\n  }\n\n}"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/fs2/Resources.scala",
    "content": "package com.github.gvolpe.fs2\n\nimport cats.effect.{Effect, IO}\nimport cats.syntax.functor._\nimport fs2.StreamApp.ExitCode\nimport fs2.async.mutable.Semaphore\nimport fs2.{Scheduler, Stream, StreamApp, async}\n\nimport scala.concurrent.ExecutionContext.Implicits.global\nimport scala.concurrent.duration._\n\nobject ResourcesApp extends Resources[IO]\n\n/**\n  * It demonstrates one of the possible uses of [[fs2.async.mutable.Semaphore]]\n  *\n  * Three processes are trying to access a shared resource at the same time but only one at\n  * a time will be granted access and the next process have to wait until the resource gets\n  * available again (availability is one as indicated by the semaphore counter).\n  *\n  * R1, R2 & R3 will request access of the precious resource concurrently so this could be\n  * one possible outcome:\n  *\n  * R1 >> Availability: 1\n  * R2 >> Availability: 1\n  * R2 >> Started | Availability: 0\n  * R3 >> Availability: 0\n  * --------------------------------\n  * R1 >> Started | Availability: 0\n  * R2 >> Done | Availability: 0\n  * --------------------------------\n  * R3 >> Started | Availability: 0\n  * R1 >> Done | Availability: 0\n  * --------------------------------\n  * R3 >> Done | Availability: 1\n  *\n  * This means when R1 and R2 requested the availability it was one and R2 was faster in\n  * getting access to the resource so it started processing. R3 was the slowest and saw\n  * that there was no availability from the beginning.\n  *\n  * Once R2 was done R1 started processing immediately showing no availability.\n  *\n  * Once R1 was done R3 started processing immediately showing no availability.\n  *\n  * Finally, R3 was done showing an availability of one once again.\n  * */\nclass Resources[F[_]: Effect] extends StreamApp[F] {\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): fs2.Stream[F, ExitCode] =\n    Scheduler(corePoolSize = 4).flatMap { implicit scheduler =>\n      for {\n        s   <- Stream.eval(async.semaphore[F](1))\n        r1  = new PreciousResource[F](\"R1\", s)\n        r2  = new PreciousResource[F](\"R2\", s)\n        r3  = new PreciousResource[F](\"R3\", s)\n        ec  <- Stream(r1.use, r2.use, r3.use).join(3).drain ++ Stream.emit(ExitCode.Success)\n      } yield ec\n    }\n\n}\n\nclass PreciousResource[F[_]: Effect](name: String, s: Semaphore[F])\n                                    (implicit S: Scheduler) {\n\n  def use: Stream[F, Unit] =\n    for {\n      _ <- Stream.eval(s.available.map(a => println(s\"$name >> Availability: $a\")))\n      _ <- Stream.eval(s.decrement)\n      _ <- Stream.eval(s.available.map(a => println(s\"$name >> Started | Availability: $a\")))\n      _ <- S.sleep(3.seconds)\n      _ <- Stream.eval(s.increment)\n      _ <- Stream.eval(s.available.map(a => println(s\"$name >> Done | Availability: $a\")))\n    } yield ()\n\n}"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/fs2/package.scala",
    "content": "package com.github.gvolpe\n\npackage object fs2 {\n  case class Event(value: String) extends AnyVal\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/StreamUtils.scala",
    "content": "package com.github.gvolpe.http4s\n\nimport cats.effect.Sync\nimport fs2.Stream\n\ntrait StreamUtils[F[_]] {\n  def evalF[A](thunk: => A)(implicit F: Sync[F]): Stream[F, A] = Stream.eval(F.delay(thunk))\n  def putStrLn(value: String)(implicit F: Sync[F]): Stream[F, Unit] = evalF(println(value))\n  def putStr(value: String)(implicit F: Sync[F]): Stream[F, Unit] = evalF(print(value))\n  def env(name: String)(implicit F: Sync[F]): Stream[F, Option[String]] = evalF(sys.env.get(name))\n  def error(msg: String): Stream[F, String] = Stream.raiseError[String](new Exception(msg)).covary[F]\n}\n\nobject StreamUtils {\n  implicit def syncInstance[F[_]: Sync]: StreamUtils[F] = new StreamUtils[F] {}\n}"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/client/MultipartClient.scala",
    "content": "package com.github.gvolpe.http4s.client\n\nimport java.net.URL\n\nimport cats.effect.Effect\nimport cats.syntax.flatMap._\nimport cats.syntax.functor._\nimport com.github.gvolpe.http4s.StreamUtils\nimport fs2.StreamApp.ExitCode\nimport fs2.{Scheduler, Stream, StreamApp}\nimport monix.eval.Task\nimport monix.execution.Scheduler.Implicits.global\nimport org.http4s.Method._\nimport org.http4s.client.blaze.Http1Client\nimport org.http4s.client.dsl.Http4sClientDsl\nimport org.http4s.headers.`Content-Type`\nimport org.http4s.multipart.{Multipart, Part}\nimport org.http4s.{MediaType, Uri}\n\nobject MultipartClient extends MultipartHttpClient[Task]\n\nclass MultipartHttpClient[F[_]](implicit F: Effect[F], S: StreamUtils[F]) extends StreamApp with Http4sClientDsl[F] {\n\n  private val image: F[URL] = F.delay(getClass.getResource(\"/rick.jpg\"))\n\n  private def multipart(url: URL) = Multipart[F](\n    Vector(\n      Part.formData(\"name\", \"gvolpe\"),\n      Part.fileData(\"rick\", url, `Content-Type`(MediaType.`image/png`))\n    )\n  )\n\n  private val request =\n    for {\n      body <- image.map(multipart)\n      req  <- POST(Uri.uri(\"http://localhost:8080/v1/multipart\"), body)\n    } yield req.replaceAllHeaders(body.headers)\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): Stream[F, ExitCode] = {\n    Scheduler(corePoolSize = 2).flatMap { implicit scheduler =>\n      for {\n        client <- Http1Client.stream[F]()\n        req    <- Stream.eval(request)\n        value  <- Stream.eval(client.expect[String](req))\n        _      <- S.evalF(println(value))\n      } yield ()\n    }.drain\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/client/StreamClient.scala",
    "content": "package com.github.gvolpe.http4s.client\n\nimport cats.effect.Effect\nimport com.github.gvolpe.http4s.StreamUtils\nimport fs2.StreamApp.ExitCode\nimport fs2.{Stream, StreamApp}\nimport io.circe.Json\nimport jawn.Facade\nimport monix.eval.Task\nimport monix.execution.Scheduler.Implicits.global\nimport org.http4s.client.blaze.Http1Client\nimport org.http4s.{Request, Uri}\n\nobject StreamClient extends HttpClient[Task]\n\nclass HttpClient[F[_]](implicit F: Effect[F], S: StreamUtils[F]) extends StreamApp {\n\n  implicit val jsonFacade: Facade[Json] = io.circe.jawn.CirceSupportParser.facade\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): Stream[F, ExitCode] = {\n    Http1Client.stream[F]().flatMap { client =>\n      val request = Request[F](uri = Uri.uri(\"http://localhost:8080/v1/dirs?depth=3\"))\n      for {\n        response <- client.streaming(request)(_.body.chunks.through(fs2.text.utf8DecodeC))\n        _        <- S.putStr(response)\n      } yield ()\n    }.drain\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/Module.scala",
    "content": "package com.github.gvolpe.http4s.server\n\nimport cats.effect.Effect\nimport cats.syntax.semigroupk._ // For <+>\nimport com.github.gvolpe.http4s.server.endpoints._\nimport com.github.gvolpe.http4s.server.endpoints.auth.{BasicAuthHttpEndpoint, GitHubHttpEndpoint}\nimport com.github.gvolpe.http4s.server.service.{FileService, GitHubService}\nimport fs2.Scheduler\nimport org.http4s.HttpService\nimport org.http4s.client.Client\nimport org.http4s.server.HttpMiddleware\nimport org.http4s.server.middleware.{AutoSlash, ChunkAggregator, GZip, Timeout}\n\nimport scala.concurrent.ExecutionContext.Implicits.global\nimport scala.concurrent.duration._\n\nclass Module[F[_]](client: Client[F])(implicit F: Effect[F], S: Scheduler) {\n\n  private val fileService = new FileService[F]\n\n  private val gitHubService = new GitHubService[F](client)\n\n  def middleware: HttpMiddleware[F] = {\n    {(service: HttpService[F]) => GZip(service)(F)} compose\n      { service => AutoSlash(service)(F) }\n  }\n\n  val fileHttpEndpoint: HttpService[F] =\n    new FileHttpEndpoint[F](fileService).service\n\n  val nonStreamFileHttpEndpoint = ChunkAggregator(fileHttpEndpoint)\n\n  private val hexNameHttpEndpoint: HttpService[F] =\n    new HexNameHttpEndpoint[F].service\n\n  private val compressedEndpoints: HttpService[F] =\n    middleware(hexNameHttpEndpoint)\n\n  private val timeoutHttpEndpoint: HttpService[F] =\n    new TimeoutHttpEndpoint[F].service\n\n  private val timeoutEndpoints: HttpService[F] =\n    Timeout(1.second)(timeoutHttpEndpoint)\n\n  private val mediaHttpEndpoint: HttpService[F] =\n    new JsonXmlHttpEndpoint[F].service\n\n  private val multipartHttpEndpoint: HttpService[F] =\n    new MultipartHttpEndpoint[F](fileService).service\n\n  private val gitHubHttpEndpoint: HttpService[F] =\n    new GitHubHttpEndpoint[F](gitHubService).service\n\n  val basicAuthHttpEndpoint: HttpService[F] =\n    new BasicAuthHttpEndpoint[F].service\n\n  // NOTE: If you mix services wrapped in `AuthMiddleware[F, ?]` the entire namespace will be protected.\n  // You'll get 401 (Unauthorized) instead of 404 (Not found). Mount it separately as done in Server.\n  val httpServices: HttpService[F] = (\n    compressedEndpoints <+> timeoutEndpoints\n    <+> mediaHttpEndpoint <+> multipartHttpEndpoint\n    <+> gitHubHttpEndpoint\n  )\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/Server.scala",
    "content": "package com.github.gvolpe.http4s.server\n\nimport cats.effect.Effect\nimport fs2.StreamApp.ExitCode\nimport fs2.{Scheduler, Stream, StreamApp}\nimport monix.eval.Task\nimport monix.execution.Scheduler.Implicits.global\nimport org.http4s.client.blaze.Http1Client\nimport org.http4s.server.blaze.BlazeBuilder\n\nobject Server extends HttpServer[Task]\n\nclass HttpServer[F[_]](implicit F: Effect[F]) extends StreamApp[F] {\n\n  override def stream(args: List[String], requestShutdown: F[Unit]): Stream[F, ExitCode] =\n    Scheduler(corePoolSize = 2).flatMap { implicit scheduler =>\n      for {\n        client   <- Http1Client.stream[F]()\n        ctx      <- Stream(new Module[F](client))\n        exitCode <- BlazeBuilder[F]\n                      .bindHttp(8080, \"0.0.0.0\")\n                      .mountService(ctx.fileHttpEndpoint, s\"/${endpoints.ApiVersion}\")\n                      .mountService(ctx.nonStreamFileHttpEndpoint, s\"/${endpoints.ApiVersion}/nonstream\")\n                      .mountService(ctx.httpServices)\n                      .mountService(ctx.basicAuthHttpEndpoint, s\"/${endpoints.ApiVersion}/protected\")\n                      .serve\n      } yield exitCode\n    }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/FileHttpEndpoint.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints\n\nimport cats.Monad\nimport com.github.gvolpe.http4s.server.service.FileService\nimport org.http4s._\nimport org.http4s.dsl.Http4sDsl\n\nclass FileHttpEndpoint[F[_] : Monad](fileService: FileService[F]) extends Http4sDsl[F] {\n\n  object DepthQueryParamMatcher extends OptionalQueryParamDecoderMatcher[Int](\"depth\")\n\n  val service: HttpService[F] = HttpService {\n    case GET -> Root / \"dirs\" :? DepthQueryParamMatcher(depth) =>\n      Ok(fileService.homeDirectories(depth))\n  }\n\n}"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/HexNameHttpEndpoint.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints\n\nimport cats.Monad\nimport org.http4s._\nimport org.http4s.dsl.Http4sDsl\n\nclass HexNameHttpEndpoint[F[_]: Monad] extends Http4sDsl[F] {\n\n  object NameQueryParamMatcher extends QueryParamDecoderMatcher[String](\"name\")\n\n  val service: HttpService[F] = HttpService {\n    case GET -> Root / ApiVersion / \"hex\" :? NameQueryParamMatcher(name) =>\n      Ok(name.getBytes(\"UTF-8\").map(\"%02x\".format(_)).mkString)\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/JsonXmlHttpEndpoint.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints\n\nimport cats.effect.Effect\nimport cats.syntax.flatMap._\nimport io.circe.generic.auto._\nimport org.http4s._\nimport org.http4s.circe._\nimport org.http4s.dsl.Http4sDsl\n\n// Docs: http://http4s.org/v0.18/entity/\nclass JsonXmlHttpEndpoint[F[_]: Effect] extends Http4sDsl[F] {\n\n  implicit def jsonXmlDecoder: EntityDecoder[F, Person] = jsonOf[F, Person] orElse personXmlDecoder[F]\n\n  val service: HttpService[F] = HttpService {\n    case GET -> Root / ApiVersion / \"media\" =>\n      Ok(\"Send either json or xml via POST method. Eg: \\n{\\n  \\\"name\\\": \\\"gvolpe\\\",\\n  \\\"age\\\": 30\\n}\\n or \\n <person>\\n  <name>gvolpe</name>\\n  <age>30</age>\\n</person>\")\n\n    case req @ POST -> Root / ApiVersion / \"media\" =>\n      req.as[Person].flatMap { person =>\n        Ok(s\"Successfully decoded person: ${person.name}\")\n      }\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/MultipartHttpEndpoint.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints\n\nimport cats.effect.Sync\nimport cats.implicits._\nimport com.github.gvolpe.http4s.server.service.FileService\nimport org.http4s._\nimport org.http4s.dsl.Http4sDsl\nimport org.http4s.multipart.Part\n\nclass MultipartHttpEndpoint[F[_]](fileService: FileService[F])\n                                 (implicit F: Sync[F]) extends Http4sDsl[F] {\n\n  val service: HttpService[F] = HttpService {\n    case GET -> Root / ApiVersion / \"multipart\" =>\n      Ok(\"Send a file (image, sound, etc) via POST Method\")\n\n    case req @ POST -> Root / ApiVersion / \"multipart\" =>\n      req.decodeWith(multipart[F], strict = true) { response =>\n        def filterFileTypes(part: Part[F]): Boolean = {\n          part.headers.exists(_.value.contains(\"filename\"))\n        }\n\n        val stream = response.parts.filter(filterFileTypes).traverse(fileService.store)\n\n        Ok(stream.map(_ => s\"Multipart file parsed successfully > ${response.parts}\"))\n      }\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/TimeoutHttpEndpoint.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints\n\nimport java.util.concurrent.TimeUnit\n\nimport cats.effect.Async\nimport fs2.Scheduler\nimport org.http4s._\nimport org.http4s.dsl.Http4sDsl\n\nimport scala.concurrent.ExecutionContext.Implicits.global\nimport scala.concurrent.duration.FiniteDuration\nimport scala.util.Random\n\nclass TimeoutHttpEndpoint[F[_]](implicit F: Async[F], S: Scheduler) extends Http4sDsl[F] {\n\n  val service: HttpService[F] = HttpService {\n    case GET -> Root / ApiVersion / \"timeout\" =>\n      val randomDuration = FiniteDuration(Random.nextInt(3) * 1000L, TimeUnit.MILLISECONDS)\n      S.effect.delay(Ok(\"delayed response\"), randomDuration)\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/auth/AuthRepository.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints.auth\n\nimport cats.effect.Sync\nimport org.http4s.BasicCredentials\n\ntrait AuthRepository[F[_], A] {\n  def persist(entity: A): F[Unit]\n  def find(entity: A): F[Option[A]]\n}\n\nobject AuthRepository {\n\n  implicit def authUserRepo[F[_]](implicit F: Sync[F]): AuthRepository[F, BasicCredentials] =\n    new AuthRepository[F, BasicCredentials] {\n      private val storage = scala.collection.mutable.Set[BasicCredentials](\n        BasicCredentials(\"gvolpe\", \"123456\")\n      )\n      override def persist(entity: BasicCredentials): F[Unit] = F.delay(storage.add(entity))\n      override def find(entity: BasicCredentials): F[Option[BasicCredentials]] = F.delay(storage.find(_ == entity))\n    }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/auth/BasicAuthHttpEndpoint.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints.auth\n\nimport cats.effect.Sync\nimport com.github.gvolpe.http4s.server.endpoints.ApiVersion\nimport org.http4s._\nimport org.http4s.dsl.Http4sDsl\nimport org.http4s.server.AuthMiddleware\nimport org.http4s.server.middleware.authentication.BasicAuth\n\n// Use this header --> Authorization: Basic Z3ZvbHBlOjEyMzQ1Ng==\nclass BasicAuthHttpEndpoint[F[_]](implicit F: Sync[F], R: AuthRepository[F, BasicCredentials]) extends Http4sDsl[F] {\n\n  private val authedService: AuthedService[BasicCredentials, F] = AuthedService {\n    case GET -> Root as user =>\n      Ok(s\"Access Granted: ${user.username}\")\n  }\n\n  private val authMiddleware: AuthMiddleware[F, BasicCredentials] =\n    BasicAuth[F, BasicCredentials](\"Protected Realm\", R.find)\n\n  val service: HttpService[F] = authMiddleware(authedService)\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/auth/GitHubHttpEndpoint.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints.auth\n\nimport cats.effect.Sync\nimport cats.syntax.flatMap._\nimport cats.syntax.functor._\nimport com.github.gvolpe.http4s.server.endpoints.ApiVersion\nimport com.github.gvolpe.http4s.server.service.GitHubService\nimport org.http4s._\nimport org.http4s.dsl.Http4sDsl\n\nclass GitHubHttpEndpoint[F[_]](gitHubService: GitHubService[F])\n                              (implicit F: Sync[F]) extends Http4sDsl[F] {\n\n  object CodeQuery extends QueryParamDecoderMatcher[String](\"code\")\n  object StateQuery extends QueryParamDecoderMatcher[String](\"state\")\n\n  val service: HttpService[F] = HttpService {\n    case GET -> Root / ApiVersion / \"github\" =>\n      Ok(gitHubService.authorize)\n\n    // OAuth2 Callback URI\n    case GET -> Root / ApiVersion / \"login\" / \"github\" :? CodeQuery(code) :? StateQuery(state) =>\n      Ok(gitHubService.accessToken(code, state).flatMap(gitHubService.userData))\n        .map(_.putHeaders(Header(\"Content-Type\", \"application/json\")))\n  }\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/endpoints/package.scala",
    "content": "package com.github.gvolpe.http4s.server\n\nimport cats.effect.Sync\nimport org.http4s.EntityDecoder\n\nimport scala.xml._\n\npackage object endpoints {\n  val ApiVersion = \"v1\"\n\n  case class Person(name: String, age: Int)\n\n  /**\n    * XML Example for Person:\n    *\n    * <person>\n    *   <name>gvolpe</name>\n    *   <age>30</age>\n    * </person>\n    * */\n  object Person {\n    def fromXml(elem: Elem): Person = {\n      val name = (elem \\\\ \"name\").text\n      val age  = (elem \\\\ \"age\").text\n      Person(name, age.toInt)\n    }\n  }\n\n  def personXmlDecoder[F[_]: Sync]: EntityDecoder[F, Person] =\n    org.http4s.scalaxml.xml[F].map(Person.fromXml)\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/service/FileService.scala",
    "content": "package com.github.gvolpe.http4s.server.service\n\nimport java.io.File\nimport java.nio.file.Paths\n\nimport cats.effect.Effect\nimport com.github.gvolpe.http4s.StreamUtils\nimport fs2.Stream\nimport org.http4s.multipart.Part\n\nclass FileService[F[_]](implicit F: Effect[F], S: StreamUtils[F]) {\n\n  def homeDirectories(depth: Option[Int]): Stream[F, String] =\n    S.env(\"HOME\").flatMap { maybePath =>\n      val ifEmpty = S.error(\"HOME environment variable not found!\")\n      maybePath.fold(ifEmpty)(directories(_, depth.getOrElse(1)))\n    }\n\n  def directories(path: String, depth: Int): Stream[F, String] = {\n\n    def dir(f: File, d: Int): Stream[F, File] = {\n      val dirs = Stream.emits(f.listFiles().toSeq).filter(_.isDirectory).covary[F]\n\n      if (d <= 0) Stream.empty\n      else if (d == 1) dirs\n      else dirs ++ dirs.flatMap(x => dir(x, d - 1))\n    }\n\n    S.evalF(new File(path)).flatMap { file =>\n      dir(file, depth)\n        .map(_.getName)\n        .filter(!_.startsWith(\".\"))\n        .intersperse(\"\\n\")\n    }\n  }\n\n  def store(part: Part[F]): Stream[F, Unit] =\n    for {\n      home      <- S.evalF(sys.env.getOrElse(\"HOME\", \"/tmp\"))\n      filename  <- S.evalF(part.filename.getOrElse(\"sample\"))\n      path      <- S.evalF(Paths.get(s\"$home/$filename\"))\n      _         <- part.body to fs2.io.file.writeAll(path)\n    } yield ()\n\n}\n"
  },
  {
    "path": "src/main/scala/com/github/gvolpe/http4s/server/service/GitHubService.scala",
    "content": "package com.github.gvolpe.http4s.server.service\n\nimport cats.effect.Sync\nimport cats.syntax.functor._\nimport com.github.gvolpe.http4s.server.endpoints.ApiVersion\nimport fs2.Stream\nimport io.circe.generic.auto._\nimport org.http4s.circe._\nimport org.http4s.client.Client\nimport org.http4s.client.dsl.Http4sClientDsl\nimport org.http4s.{Header, Request, Uri}\n\n// See: https://developer.github.com/apps/building-oauth-apps/authorization-options-for-oauth-apps/#web-application-flow\nclass GitHubService[F[_]: Sync](client: Client[F]) extends Http4sClientDsl[F] {\n\n  // NEVER make this data public! This is just a demo!\n  private val ClientId     = \"959ea01cd3065cad274a\"\n  private val ClientSecret = \"53901db46451977e6331432faa2616ba24bc2550\"\n\n  private val RedirectUri  = s\"http://localhost:8080/$ApiVersion/login/github\"\n\n  case class AccessTokenResponse(access_token: String)\n\n  val authorize: Stream[F, Byte] = {\n    val uri = Uri.uri(\"https://github.com\")\n      .withPath(\"/login/oauth/authorize\")\n      .withQueryParam(\"client_id\", ClientId)\n      .withQueryParam(\"redirect_uri\", RedirectUri)\n      .withQueryParam(\"scopes\", \"public_repo\")\n      .withQueryParam(\"state\", \"test_api\")\n\n    client.streaming[Byte](Request[F](uri = uri))(_.body)\n  }\n\n  def accessToken(code: String, state: String): F[String] = {\n    val uri = Uri.uri(\"https://github.com\")\n      .withPath(\"/login/oauth/access_token\")\n      .withQueryParam(\"client_id\", ClientId)\n      .withQueryParam(\"client_secret\", ClientSecret)\n      .withQueryParam(\"code\", code)\n      .withQueryParam(\"redirect_uri\", RedirectUri)\n      .withQueryParam(\"state\", state)\n\n    client.expect[AccessTokenResponse](Request[F](uri = uri))(jsonOf[F, AccessTokenResponse])\n      .map(_.access_token)\n  }\n\n  def userData(accessToken: String): F[String] = {\n    val request = Request[F](uri = Uri.uri(\"https://api.github.com/user\"))\n      .putHeaders(Header(\"Authorization\", s\"token $accessToken\"))\n\n    client.expect[String](request)\n  }\n\n}\n"
  },
  {
    "path": "src/test/scala/com/github/gvolpe/http4s/IOAssertion.scala",
    "content": "package com.github.gvolpe.http4s\n\nimport cats.effect.IO\n\nobject IOAssertion {\n  def apply[A](ioa: IO[A]): A = ioa.unsafeRunSync()\n}\n"
  },
  {
    "path": "src/test/scala/com/github/gvolpe/http4s/server/endpoints/HexNameHttpEndpointSpec.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints\n\nimport cats.effect.IO\nimport com.github.gvolpe.http4s.IOAssertion\nimport org.http4s.server.middleware.GZip\nimport org.http4s.{Header, HttpService, Query, Request, Uri}\nimport org.scalatest.FunSuite\n\n// Docs: http://http4s.org/v0.18/gzip/\nclass HexNameHttpEndpointSpec extends FunSuite {\n\n  private val httpService: HttpService[IO] = GZip(new HexNameHttpEndpoint[IO].service)\n\n  private val CompressedLength  = 74\n  private val NormalLength      = 88\n\n  private val request = Request[IO](uri =\n    Uri(\n      path = s\"/$ApiVersion/hex\",\n      query = Query(\"name\" -> Some(\"Scala is a really cool programming language!\"))\n    )\n  )\n\n  test(\"Compressed Response\") {\n    IOAssertion {\n      val gzipHeader  = Header(\"Accept-Encoding\", \"gzip\")\n      val gzipRequest = request.putHeaders(gzipHeader)\n\n      httpService(gzipRequest).value.flatMap { maybe =>\n        maybe.fold(IO[Unit](fail(\"Empty response\"))) { response =>\n          response.as[String].map(r => assert(r.length == CompressedLength))\n        }\n      }\n    }\n  }\n\n  test(\"Uncompressed Response\") {\n    IOAssertion {\n      httpService(request).value.flatMap { maybe =>\n        maybe.fold(IO[Unit](fail(\"Empty response\"))) { response =>\n          response.as[String].map(r => assert(r.length == NormalLength))\n        }\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "src/test/scala/com/github/gvolpe/http4s/server/endpoints/JsonXmlHttpEndpointSpec.scala",
    "content": "package com.github.gvolpe.http4s.server.endpoints\n\nimport cats.effect.IO\nimport com.github.gvolpe.http4s.IOAssertion\nimport org.http4s.{Header, HttpService, Method, Request, Status, Uri}\nimport org.scalatest.FunSuite\n\nclass JsonXmlHttpEndpointSpec extends FunSuite {\n\n  private val httpService: HttpService[IO] = new JsonXmlHttpEndpoint[IO].service\n\n  private val jsonPerson =\n    \"\"\"\n      |{\n      |  \"name\": \"gvolpe\",\n      |  \"age\": 30\n      |}\n    \"\"\".stripMargin\n\n  private val xmlPerson =\n    \"\"\"\n      |<person>\n      |  <name>gvolpe</name>\n      |  <age>30</age>\n      |</person>\n    \"\"\".stripMargin\n\n  private val request = Request[IO](method = Method.POST, uri = Uri(path = s\"/$ApiVersion/media\"))\n\n  test(\"json is decoded\") {\n    IOAssertion {\n      val bodyRequest = request.withBody[String](jsonPerson)\n\n      bodyRequest.flatMap { req =>\n        httpService(req.putHeaders(Header(\"Content-Type\", \"application/json\"))).value.map { maybe =>\n          maybe.fold(fail(\"Empty response\")) { response =>\n            assert(response.status == Status.Ok)\n          }\n        }\n      }\n    }\n  }\n\n  test(\"xml is decoded\") {\n    IOAssertion {\n      val bodyRequest = request.withBody[String](xmlPerson)\n\n      bodyRequest.flatMap { req =>\n        httpService(req.putHeaders(Header(\"Content-Type\", \"application/xml\"))).value.map { maybe =>\n          maybe.fold(fail(\"Empty response\")) { response =>\n            assert(response.status == Status.Ok)\n          }\n        }\n      }\n    }\n  }\n\n  test(\"decoding fails, no Content Type\") {\n    IOAssertion {\n      val bodyRequest = request.withBody[String](jsonPerson)\n\n      bodyRequest.flatMap { req =>\n        httpService(req).value.attempt.map {\n          case Left(e)  => assert(e.getMessage == \"Malformed message body: Invalid XML\")\n          case Right(_) => fail(\"Got a response when a failure was expected\")\n        }\n      }\n\n      // Using `req.decode` gives you a response, using `req.as` throws an exception\n      // https://gitter.im/http4s/http4s?at=5a964662758c233504cc0fec\n//      bodyRequest.flatMap { req =>\n//        httpService(req).value.map { maybe =>\n//          maybe.fold(fail(\"Empty response\")) { response =>\n//            assert(response.status == Status.BadRequest)\n//          }\n//        }\n//      }\n    }\n  }\n\n}\n"
  }
]