{"id":28184150,"url":"https://github.com/robur-coop/httpcats","last_synced_at":"2025-10-07T04:56:10.727Z","repository":{"id":197971733,"uuid":"699777592","full_name":"robur-coop/httpcats","owner":"robur-coop","description":"A simple http client/server (http/1.1 \u0026 h2) for OCaml 5","archived":false,"fork":false,"pushed_at":"2025-09-18T16:18:29.000Z","size":731,"stargazers_count":39,"open_issues_count":3,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-04T18:51:05.126Z","etag":null,"topics":["http","http-client","http-server","ocaml","parallel","protocol"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robur-coop.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2023-10-03T10:17:10.000Z","updated_at":"2025-09-18T16:18:31.000Z","dependencies_parsed_at":"2024-02-29T19:28:29.080Z","dependency_job_id":"807b4c4a-058d-4d28-aa59-7c4154f5d593","html_url":"https://github.com/robur-coop/httpcats","commit_stats":null,"previous_names":["robur-coop/httpcats"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/robur-coop/httpcats","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robur-coop%2Fhttpcats","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robur-coop%2Fhttpcats/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robur-coop%2Fhttpcats/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robur-coop%2Fhttpcats/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robur-coop","download_url":"https://codeload.github.com/robur-coop/httpcats/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robur-coop%2Fhttpcats/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278722766,"owners_count":26034461,"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-10-07T02:00:06.786Z","response_time":59,"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","http-client","http-server","ocaml","parallel","protocol"],"created_at":"2025-05-16T05:12:18.834Z","updated_at":"2025-10-07T04:56:10.721Z","avatar_url":"https://github.com/robur-coop.png","language":"OCaml","funding_links":[],"categories":[],"sub_categories":[],"readme":"# A simple HTTP client/server (HTTP/1.1 \u0026 h2) with [Miou][miou]\n\n`httpcats` (HTTP + cats because [miou][miou]) is an implementation of an HTTP\nclient and server (HTTP/1.1 \u0026 h2) in pure OCaml. This implementation is based on\nthe [miou][miou] scheduler, [ocaml-dns][ocaml-dns] (for domain name resolution),\n[happy-eyeballs][happy-eyeballs] (to manage connections), [ocaml-tls][ocaml-tls]\n(for TLS protocol) \u0026 [mirage-crypto][mirage-crypto] (for cryptography),\n[ca-certs][ca-certs] to obtain system certificates and [h1][h1] and [h2][h2] to\nimplement HTTP protocols. In all, `httpcats` requires 58 packages (including\n`dune` \u0026 `ocamlfind`) for a single installation.\n\n**U**: That's a lot of packages!\n\nThat's what's needed to end up with a pure OCaml http client. `curl`, for\nexample, has 13 dependencies and also contains implementations such as ftp or\nsmtp that are not related to an http client. A comparison would therefore be\ndifficult, you just have to choose your poison (OCaml or C?).\n\n**U**: However, there are other implementations of HTTP client \u0026 server in\nOCaml. Why implement it yet again?\n\nThese implementations don't use [miou], however. What's more, since\n[http-lwt-client], we're opposed to the (ultimately complex) feature of being\nable to choose the TLS implementation (although we understand the constraints\nsome users may have in wanting to use OpenSSL) and prefer to offer an HTTP\nclient that uses strictly [ocaml-tls][ocaml-tls]. Finally, we also want to have\ncontrol over domain resolution, rather than having to use the system's resolver.\n\n\u003chr /\u003e\n\n\u003ctag id=\"fn1\"\u003e**1**\u003c/tag\u003e: Here, we point the finger at the software components\n[Conduit][conduit] and [Gluten][gluten], which perform dynamic and/or static\ndispatching of TLS layer implementations that we do not find suitable.\n\n**U**: So how does `httpcats` work?\n\nYou need to initialize the random number generator required by `mirage-crypto`\nand `ocaml-tls` and make your request like this:\n```ocaml\nlet fn _meta _req _resp () = function\n  | Some str -\u003e print_string str\n  | None -\u003e ()\n\nlet () = Miou_unix.run @@ fun () -\u003e\n  let rng = Mirage_crypto_rng_miou_unix.(initialize (module Pfortuna)) in\n  ignore (Httpcats.request ~fn ~uri:\"https://robur.coop/\" ());\n  Mirage_crypto_rng_miou_unix.kill rng\n```\n\nIt's quite... simple. You can, of course, make `POST` requests, consume the\nresponse body in a more complex way (store it in a buffer, for example), process\nthe received response and lots of other things like:\n- forcing the use of a version of the HTTP protocol\n- define your own TLS configuration\n- accept certain certificates (such as self-signed ones)\n- follow or not follow redirects\n- resolve domain names via `happy-eyeballs` \u0026 `ocaml-dns`\n\n**U**: What about the server?\n\nYou can also have an HTTP/1.1 and h2 server (with TLS and a certificate you can\nhandle with [x509][x509]). As an example, here's a simple HTTP/1.1 server:\n```ocaml\nlet text = \"Hello World!\"\n\nlet[@warning \"-8\"] handler _ (`V1 reqd : [ `V1 of H1.Reqd.t | `V2 of H2.Reqd.t ]) =\n  let open H1 in\n  let request = Reqd.request reqd in\n  match request.Request.target with\n  | \"\" | \"/\" | \"/index.html\" -\u003e\n      let headers =\n        Headers.of_list\n          [\n            (\"content-type\", \"text/plain; charset=utf-8\")\n          ; (\"content-length\", string_of_int (String.length text))\n          ]\n      in\n      let resp = Response.create ~headers `OK in\n      let body = Reqd.request_body reqd in\n      Body.Reader.close body;\n      Reqd.respond_with_string reqd resp text\n  | _ -\u003e\n      let headers = Headers.of_list [ (\"content-length\", \"0\") ] in\n      let resp = Response.create ~headers `Not_found in\n      Reqd.respond_with_string reqd resp \"\"\n\nlet server sockaddr = Httpcats.Server.clear ~handler sockaddr\n\nlet () =\n  let sockaddr = Unix.(ADDR_INET (inet_addr_loopback, 8080)) in\n  Miou_unix.run @@ fun () -\u003e\n  let domains = Miou.Domain.available () in\n  let prm = Miou.async @@ fun () -\u003e server sockaddr in\n  if domains \u003e 0\n  then Miou.parallel server (List.init domains (Fun.const sockaddr))\n       |\u003e List.iter (function Ok () -\u003e () | Error exn -\u003e raise exn);\n  Miou.await_exn prm\n```\n\nAgain, it's pretty straightforward. This server takes the opportunity to use all\nyour cores thanks to [miou][miou]. You can also run the program with a specific\nnumber of domains:\n```sh\n$ ocamlfind opt -linkpkg -package digestif.c,httpcats server.ml\n$ MIOU_DOMAINS=2 ./a.out\n```\n\n## Benchmarks\n\nSome contributors to the OCaml community wanted to benchmark different HTTP\nimplementations in OCaml. You can find more details [here][discuss-benchmark].\nAs for `httpcats`, a benchmark was developed and proposed\n[here][FrameworkBenchmarks].\n\nThis benchmark tool has the advantage of being fairly reproducible. Here are\nthe results between `httpun+eio` and `httpcats` (`h1+miou`) (on AMD Ryzen 9\n7950X 16-Core):\n\n### `httpcats` (or `h1` + `miou`)\n\n| clients | threads | latencyAvg | latencyMax | latencyStdev | totalRequests |\n|---------|---------|------------|------------|--------------|---------------|\n| 16      | 16      | 47.43us    | 2.27ms     | 38.48us      | 5303700       |\n| 32      | 32      | 71.73us    | 1.04ms     | 47.58us      | 7016729       |\n| 64      | 32      | 140.29us   | 5.72ms     | 121.50us     | 7658146       |\n| 128     | 32      | 279.73us   | 11.35ms    | 287.92us     | 7977306       |\n| 256     | 32      | 519.02us   | 16.89ms    | 330.20us     | 7816435       |\n| 512     | 32      | 1.06ms     | 37.42ms    | 534.14us     | 7409781       |\n\n### `httpun` \u0026 `eio`\n\n| clients | threads | latencyAvg | latencyMax | latencyStdev | totalRequests |\n|---------|---------|------------|------------|--------------|---------------|\n| 16      | 16      | 1.19ms     | 17.12ms    | 2.09ms       | 2966727       |\n| 32      | 32      | 0.91ms     | 17.49ms    | 1.65ms       | 5366296       |\n| 64      | 32      | 1.08ms     | 17.30ms    | 1.82ms       | 5919733       |\n| 128     | 32      | 1.16ms     | 18.62ms    | 1.76ms       | 6187300       |\n| 256     | 32      | 1.41ms     | 26.61ms    | 1.96ms       | 6604454       |\n| 512     | 32      | 1.84ms     | 32.37ms    | 2.23ms       | 6798222       |\n\n### Interpretations\n\nAs we can see, `httpcats` performs **better** than `eio` (with `httpun`) in\nterms of latency and the number of requests it can handle per second.\n\nTo be precise, `h1` and `httpun` are both forks of `httpaf` and the code is\nvery similar. If we had to explain a difference between these two benchmarks,\nit would **not** be due to `h1` or `httpun`.\n\nCoHTTP is not included in this benchmark because it is more a comparison\nbetween schedulers than implementations of the HTTP/1.1 protocol. In this case,\n`h1` and `httpun`, due to their similarities with `httpaf`, normally perform\nbetter than CoHTTP — you can see [the conference][httpaf-conf] about `httpaf`\nor the official [repository][httpaf]. These implementations also allow for\nsupport of the [`h2`][h2] protocol (which is not currently possible with\nCoHTTP).\n\nThe real difference lies between `miou` and `eio` and their task management\npolicies. For more details, please refer to [the Miou documentation][miou-doc]:\noverall, Miou offers more _poll points_ than Eio, which provides more\nopportunities to manage more clients. This is one of Miou's stated objectives:\nto be a scheduler designed for this type of service.\n\n\u003chr /\u003e\n\n\u003ctag id=\"fn2\"\u003e**2**\u003c/tag\u003e: In the discussion thread presented above, there is\nalso mention of `httpaf+lwt`, which performs even better than `httpcats`. It is\nspecified that the use of domains in this benchmark is **not** safe.\n\n\u003ctag id=\"fn3\"\u003e**3**\u003c/tag\u003e: It should be noted that `eio` uses\n[`io_uring`][io_uring] while Miou uses `select(3P)`. It is possible to improve\nMiou to use `epoll(7)` or `io_uring` (and make sure that `httpcats` uses this\nimplementation) but, as it stands, `select()` is sufficient.\n\n[miou]: https://github.com/robur-coop/miou\n[ocaml-dns]: https://github.com/mirage/ocaml-dns\n[happy-eyeballs]: https://github.com/robur-coop/happy-eyeballs\n[ocaml-tls]: https://github.com/mirleft/ocaml-tls\n[ca-certs]: https://github.com/mirage/ca-certs\n[h1]: https://github.com/robur-coop/ocaml-h1\n[h2]: https://github.com/anmonteiro/ocaml-h2\n[http-lwt-client]: https://github.com/robur-coop/http-lwt-client\n[x509]: https://github.com/mirleft/ocaml-x509\n[io_uring]: https://github.com/ocaml-multicore/ocaml-uring\n[nginx-benchmark]: https://openbenchmarking.org/test/pts/nginx\u0026eval=f9e860ca197d88a133e3ae0496e96fa3c79e33fe#metrics\n[cohttp]: https://github.com/mirage/ocaml-cohttp\n[rewrk]: https://github.com/lnx-search/rewrk\n[mirage-crypto]: https://github.com/mirage/mirage-crypto\n[miou-doc]: https://docs.osau.re/miou/\n[httpaf-conf]: https://watch.ocaml.org/w/b2KP5hMngXkyVU3z7yNLpG\n[httpaf]: https://github.com/inhabitedtype/httpaf\n[discuss-benchmark]: https://discuss.ocaml.org/t/lwt-multi-processing-much-more-performant-than-eio-multi-core/16395\n[FrameWorkBenchmarks]: https://github.com/TechEmpower/FrameworkBenchmarks/pull/10009\n[conduit]: https://github.com/mirage/ocaml-conduit\n[gluten]: https://github.com/anmonteiro/gluten\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobur-coop%2Fhttpcats","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobur-coop%2Fhttpcats","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobur-coop%2Fhttpcats/lists"}