{"id":24828189,"url":"https://github.com/spinoco/fs2-http","last_synced_at":"2025-08-19T09:09:19.992Z","repository":{"id":51049207,"uuid":"78711232","full_name":"Spinoco/fs2-http","owner":"Spinoco","description":"Http Server and client using fs2","archived":false,"fork":false,"pushed_at":"2022-02-05T11:46:51.000Z","size":252,"stargazers_count":137,"open_issues_count":15,"forks_count":26,"subscribers_count":11,"default_branch":"series/0.4","last_synced_at":"2025-05-19T19:06:54.537Z","etag":null,"topics":["http-client","http-server","scala","scala-fs2","scodec","server-sent-events","shapeless","websocket-client","websockets"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Spinoco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-01-12T05:18:38.000Z","updated_at":"2025-01-31T21:18:40.000Z","dependencies_parsed_at":"2022-09-09T10:00:37.580Z","dependency_job_id":null,"html_url":"https://github.com/Spinoco/fs2-http","commit_stats":null,"previous_names":[],"tags_count":17,"template":false,"template_full_name":null,"purl":"pkg:github/Spinoco/fs2-http","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spinoco%2Ffs2-http","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spinoco%2Ffs2-http/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spinoco%2Ffs2-http/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spinoco%2Ffs2-http/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Spinoco","download_url":"https://codeload.github.com/Spinoco/fs2-http/tar.gz/refs/heads/series/0.4","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Spinoco%2Ffs2-http/sbom","scorecard":{"id":133047,"data":{"date":"2025-08-04","repo":{"name":"github.com/Spinoco/fs2-http","commit":"8f8ef9a139bb7a96330f14f2994d92b24f5fc8b0"},"scorecard":{"version":"v5.2.1-28-gc1d103a9","commit":"c1d103a9bb9f635ec7260bf9aa0699466fa4be0e"},"score":3.2,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#binary-artifacts"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":1,"reason":"Found 3/24 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#maintained"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'series/0.4'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 9 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/c1d103a9bb9f635ec7260bf9aa0699466fa4be0e/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-16T05:40:57.914Z","repository_id":51049207,"created_at":"2025-08-16T05:40:57.914Z","updated_at":"2025-08-16T05:40:57.914Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271128567,"owners_count":24703876,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-19T02:00:09.176Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http-client","http-server","scala","scala-fs2","scodec","server-sent-events","shapeless","websocket-client","websockets"],"created_at":"2025-01-30T22:51:42.446Z","updated_at":"2025-08-19T09:09:19.971Z","avatar_url":"https://github.com/Spinoco.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 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 =\u003e\n  val request = HttpRequest.get[IO](Uri.https(\"github.com\", \"/Spinoco/fs2-http\"))\n  client.request(request).flatMap { resp =\u003e\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 =\u003e\n  val output =  time.awakeEvery[IO](1.second).map { dur =\u003e println(s\"SENT $dur\"); Frame.Text(s\" ECHO $dur\") }.take(5)\n  output.concurrently(inbound.take(5).map { in =\u003e println((\"RECEIVED \", in)) })\n}\n\nhttp.client[IO]().flatMap { client =\u003e\n  val request = WebSocketRequest.ws(\"echo.websocket.org\", \"/\", \"encoding\" -\u003e \"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) =\u003e ct }.getOrElse(ContentType(MediaType.`application/octet-stream`, None, None))\n      val size = request.headers.collectFirst { case `Content-Length`(sz) =\u003e 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])  =\u003e 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]) =\u003e 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 _ =\u003e ??? }\n  , \"example2\" / as[Int] :/: as[String] map { case int :: s :: HNil =\u003e ??? }\n  , \"example3\" / body.as[Foo] :: choice(Post, Put) map { case foo :: postOrPut :: HNil =\u003e ??? }\n  , \"example4\" / header[`Content-Type`] map { case contentType  =\u003e ??? }\n  , \"example5\" / param[Int](\"count\") :: param[String](\"query\") map { case count :: query :: HNil =\u003e ??? }\n  , \"example6\" / eval(someEffect) map { case result =\u003e ??? }\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\u0026query=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","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspinoco%2Ffs2-http","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspinoco%2Ffs2-http","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspinoco%2Ffs2-http/lists"}