{"id":13476598,"url":"https://github.com/anmonteiro/ocaml-h2","last_synced_at":"2025-04-05T13:08:22.412Z","repository":{"id":34366263,"uuid":"168848022","full_name":"anmonteiro/ocaml-h2","owner":"anmonteiro","description":"An HTTP/2 implementation written in pure OCaml","archived":false,"fork":false,"pushed_at":"2024-09-04T18:31:07.000Z","size":2738,"stargazers_count":314,"open_issues_count":14,"forks_count":32,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-03-27T22:34:16.070Z","etag":null,"topics":["http","http2","ocaml"],"latest_commit_sha":null,"homepage":"","language":"OCaml","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/anmonteiro.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2019-02-02T15:52:06.000Z","updated_at":"2025-02-09T10:54:58.000Z","dependencies_parsed_at":"2023-10-11T04:48:11.031Z","dependency_job_id":"670919b2-babd-4c0f-9d0b-21b2334890af","html_url":"https://github.com/anmonteiro/ocaml-h2","commit_stats":{"total_commits":282,"total_committers":13,"mean_commits":"21.692307692307693","dds":0.07092198581560283,"last_synced_commit":"49c0591ce90e54187625919a460b694c8f3d003b"},"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anmonteiro%2Focaml-h2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anmonteiro%2Focaml-h2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anmonteiro%2Focaml-h2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/anmonteiro%2Focaml-h2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/anmonteiro","download_url":"https://codeload.github.com/anmonteiro/ocaml-h2/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247339158,"owners_count":20923014,"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","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","http2","ocaml"],"created_at":"2024-07-31T16:01:32.414Z","updated_at":"2025-04-05T13:08:22.389Z","avatar_url":"https://github.com/anmonteiro.png","language":"OCaml","funding_links":[],"categories":["OCaml","\u003ca name=\"OCaml\"\u003e\u003c/a\u003eOCaml"],"sub_categories":[],"readme":"# h2\n\nh2 is an implementation of the\n[HTTP/2](https://tools.ietf.org/html/rfc7540) specification entirely in OCaml.\nIt is based on the concepts in\n[http/af](https://github.com/inhabitedtype/httpaf), and therefore uses the\n[Angstrom][angstrom] and [Faraday][faraday] libraries to implement the parsing\nand serialization layers of the HTTP/2 standard. It also preserves the same API\nas http/af wherever possible.\n\n[angstrom]: https://github.com/inhabitedtype/angstrom\n[faraday]: https://github.com/inhabitedtype/faraday\n\n## Installation\n\nInstall the library and its dependencies via [OPAM][opam]:\n\n[opam]: http://opam.ocaml.org/\n\n```bash\nopam install h2\n```\n\n## Usage\n\n### Resources\n\nFirst of all, the generated documentation lives\n[here](https://ocaml.org/p/h2/latest/doc/index.html). It is recommended to browse it\nand get to know the API exposed by `H2`.\n\nThere are also some examples in the [`examples`](./examples) folder. Most\nnotably, the [ALPN example](./examples/alpn) provides an implementation of a\ncommon real-world use case:\n\nIt sets up a server that listens on 2 ports:\n\n1. __port 8080__: redirects all incoming traffic to `https://localhost:9443`\n2. __port 9443__: negotiates which protocol to use over the TLS\n   [Application-Layer Protocol Negotiation\n   (ALPN)](https://tools.ietf.org/html/rfc7301) extension. It supports 2\n   protocols (in order of preference): `h2` and `http/1.1`.\n\n    If `h2` is negotiated, the example sets up a connection handler using\n    `h2-lwt-unix`. Otherwise the connection handler will serve HTTP/1.1 traffic\n    using [httpun](https://github.com/anmonteiro/httpun).\n\nThe ALPN example also provides a unikernel implementation with the same\nfunctionality that runs on [MirageOS](https://mirage.io).\n\n### A server example\n\nWe present an annotated example below that responds to any `GET` request and\nreturns a response body containing the target of the request.\n\n```ocaml\nopen H2\n\n(* This is our request handler. H2 will invoke this function whenever the\n * client send a request to our server. *)\nlet request_handler _client_address reqd =\n  (* `reqd` is a \"request descriptor\". Conceptually, it's just a reference to\n   * the request that the client sends, which allows us to do two things:\n   *\n   * 1. Get more information about the request that we're handling. In our\n   *    case, we're inspecting the method and the target of the request, but we\n   *    could also look at the request headers.\n   *\n   * 2. A request descriptor is also what allows us to respond to this\n   *    particular request by passing it into one of the response functions\n   *    that we will look at below. *)\n  let { Request.meth; target; _ } = Reqd.request reqd in\n  match meth with\n  | `GET -\u003e\n    let response_body =\n      Printf.sprintf \"You made a request to the following resource: %s\\n\" target\n    in\n    (* Specify the length of the response body. Two notes to make here:\n     *\n     * 1. Specifying the content length of a response is optional since HTTP/2\n     *    is a binary protocol based on frames which carry information about\n     *    whether a frame is the last for a given stream.\n     *\n     * 2. In HTTP/2, all header names are required to be lowercase. We use\n     *    `content-length` instead of what might be commonly seen in HTTP/1.X\n     *    (`Content-Length`). *)\n    let headers =\n      Headers.of_list\n        [ \"content-length\", string_of_int (String.length response_body) ]\n    in\n    (* Respond immediately with the response body we constructed above,\n     * finishing the request/response exchange (and the unerlying HTTP/2\n     * stream).\n     *\n     * The other functions in the `Reqd` module that allow sending a response\n     * to the client are `Reqd.respond_with_bigstring`, that only differs from\n     * `Reqd.respond_with_string` in that the response body should be a\n     * bigarray, and `Reqd.respond_with_streaming` (see\n     * http://anmonteiro.com/ocaml-h2/h2/H2/Reqd/index.html#val-respond_with_streaming)\n     * which starts streaming a response body which can be written to\n     * asynchronously to the client. *)\n    Reqd.respond_with_string reqd (Response.create ~headers `OK) response_body\n  | meth -\u003e\n    let response_body =\n      Printf.sprintf\n        \"This server does not respond to %s methods.\\n\"\n        (Method.to_string meth)\n    in\n    Reqd.respond_with_string\n      reqd\n      (* We don't include any headers in this case. The HTTP/2 framing layer\n       * knows that these will be last frames in the exchange. *)\n      (Response.create `Method_not_allowed)\n      response_body\n\n(* This is our error handler. Everytime H2 sees a malformed request or an\n * exception on a specific stream, it will invoke this function to send a\n * response back to the misbehaving client. Because there might not be a\n * request for the stream (handing malformed requests to the application is\n * strongly discouraged), there is also no request descriptor like we saw in\n * the request handler above. In this case, one of the arguments to this\n * function is a function that will start the response. It has the following\n * signature:\n *\n *   val start_response : H2.headers.t -\u003e [`write] H2.Body.t\n *\n * This is also where we first encounter the concept of a `Body` (which were\n * briefly mentioned above) that can be written to (potentially\n * asynchronously). *)\nlet error_handler _client_address ?request:_ _error start_response =\n  (* We start the error response by calling the `start_response` function. We\n   * get back a response body. *)\n  let response_body = start_response Headers.empty in\n  (* Once we get the response body, we can immediately start writing to it. In\n   * this case, it might be sufficient to say that there was an error. *)\n  Body.Writer.write_string\n    response_body\n    \"There was an error handling your request.\\n\";\n  (* Finally, we close the streaming response body to signal to the underlying\n   * HTTP/2 framing layer that we have finished sending the response. *)\n  Body.Writer.close response_body\n\nlet () =\n  (* We're going to be using the `H2_lwt_unix` module from the `h2-lwt-unix`\n   * library to create a server that communicates over the underlying operating\n   * system socket abstraction. The first step is to create a connection\n   * handler that will accept incoming connections and let our request and\n   * error handlers handle the request / response exchanges in those\n   * connections. *)\n  let connection_handler =\n    H2_lwt_unix.Server.create_connection_handler\n      ?config:None\n      ~request_handler\n      ~error_handler\n  in\n  (* We'll be listening for requests on the loopback interface (localhost), on\n   * port 8080. *)\n  let listen_address = Unix.(ADDR_INET (inet_addr_loopback, 8080)) in\n  (* The final step is to start a server that will set up all the low-level\n   * networking communication for us, and let it run forever. *)\n  let _server =\n    Lwt_io.establish_server_with_client_socket listen_address connection_handler\n  in\n  let forever, _ = Lwt.wait () in\n  Lwt_main.run forever\n```\n\n### A client example\n\nThe following annotated client example performs a `GET` request to `example.com`\nand prints the response body as it arrives.\n\n```ocaml\nopen H2\nmodule Client = H2_lwt_unix.Client\n\n(* This is our response handler. H2 will invoke this function whenever the\n * server has responded to our request. The `notify_response_received` argument\n * is explained further down. *)\nlet response_handler notify_response_received response response_body =\n  (* `response` contains information about the response that we received. We're\n   * looking at the status to know whether our request produced a successful\n   * response, but we could also get the response headers, for example. *)\n  match response.Response.status with\n  | `OK -\u003e\n    (* If we got a successful response, we're going to read the response body\n     * as it arrives, and print its fragments as we receive them. *)\n    let rec read_response () =\n      (* Scheduling a read of the response body registers two functions with\n       * H2:\n       *\n       * 1. `on_read`: this function will be called upon the receipt of a\n       *    response body chunk (in HTTP/2 speak, a DATA frame). Our handling\n       *    of these chunks is explained inline below.\n       *\n       * 2. `on_eof`: this function will be called once the entire response\n       *    body has arrived. In our case, this is where we fulfill the promise\n       *    that we're done handling the response.\n       *)\n      Body.Reader.schedule_read\n        response_body\n        ~on_read:(fun bigstring ~off ~len -\u003e\n          (* Once a response body chunk is handed to us (as a bigarray, and an\n           * offset and length into that bigarray), we'll copy it into a string\n           * and print it to stdout. *)\n          let response_fragment = Bytes.create len in\n          Bigstringaf.blit_to_bytes\n            bigstring\n            ~src_off:off\n            response_fragment\n            ~dst_off:0\n            ~len;\n          print_string (Bytes.to_string response_fragment);\n          (* We need to make sure that we register another read of the response\n           * body after we're done handling a fragment, as it will not be\n           * registered for us. This is where our recursive function comes in\n           * handy. *)\n          read_response ())\n        ~on_eof:(fun () -\u003e\n          (* Signal to the caller of the HTTP/2 request that we are now done\n           * handling the response, and the program can continue. *)\n          Lwt.wakeup_later notify_response_received ())\n    in\n    read_response ()\n  | _ -\u003e\n    (* We didn't get a successful status in the response. Just print what we\n     * received to stderr and bail early. *)\n    Format.eprintf \"Unsuccessful response: %a\\n%!\" Response.pp_hum response;\n    exit 1\n\nlet error_handler _error =\n  (* There was an error handling the request. In this simple example, we don't\n   * try too hard to understand it. Just print to stderr and exit with an\n   * unsuccessful status code. *)\n  Format.eprintf \"Unsuccessful request!\\n%!\";\n  exit 1\n\nopen Lwt.Infix\n\nlet () =\n  let host = \"www.example.com\" in\n  Lwt_main.run\n    ( (* We start by resolving the address of the host we want to connect to. *)\n      Lwt_unix.getaddrinfo host \"443\" [ Unix.(AI_FAMILY PF_INET) ]\n    \u003e\u003e= fun addresses -\u003e\n      (* Once the address for the host we want to contact has been resolved, we\n       * need to create the socket through which the communication with the\n       * remote host is going to happen. *)\n      let socket = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in\n      (* Then, we connect to the socket we just created, on the address we have\n       * previously obtained through name resolution. *)\n      Lwt_unix.connect socket (List.hd addresses).Unix.ai_addr \u003e\u003e= fun () -\u003e\n      let request =\n        Request.create\n          `GET\n          \"/\"\n          (* a scheme pseudo-header is required in HTTP/2 requests, otherwise\n           * the request will be considered malformed. In our case, we're\n           * making a request over HTTPS, so we specify \"https\" *)\n          ~scheme:\"https\"\n          ~headers:\n            (* The `:authority` pseudo-header is a blurry line in the HTTP/2\n             * specificiation. It's not strictly required but most\n             * implementations treat a request with a missing `:authority`\n             * pseudo-header as malformed. That is the case for example.com, so\n             * we include it. *)\n            Headers.(add_list empty [ \":authority\", host ])\n      in\n      (* The H2 API relies on callbacks to allow for a single, stable core to\n       * be used with different I/O runtimes. Because we're using Lwt in this\n       * example, we'll create an Lwt task that is going to help us transform\n       * the callback-calling style of H2 into an Lwt promise whenever we're\n       * done handling the response.\n       *\n       * If you're not familiar with Lwt or its `Lwt.wait` function, it's\n       * recommended you read at least the following bit before moving on:\n       * http://ocsigen.org/lwt/4.1.0/api/Lwt#VALwait. *)\n      let response_received, notify_response_received = Lwt.wait () in\n      (* Partially apply the `response_handler` function that we defined above\n       * to produce one that matches H2's expected signature. After this line,\n       * `response_handler` now has the following signature:\n       *\n       *   val response_handler: Response.t -\u003e [ `read ] Body.t -\u003e unit\n       *)\n      let response_handler = response_handler notify_response_received in\n      (* HTTP/2 itself does not define that the protocol must be used with TLS.\n       * In practice, though, TLS is widely used in the Internet today (and\n       * that's a good thing!) and no serious deployments use plaintext HTTP/2.\n       * The following is a good read on why this is the case:\n       * https://http2-explained.haxx.se/content/en/part8.html#844-its-use-of-tls-makes-it-slower\n       *\n       * For us, this means that we need to make our request over TLS. H2, and\n       * more specifically `h2-lwt-unix`, provide a `TLS` module for both the\n       * client and the server implementations that rely on an optional\n       * dependency to ocaml-tls.\n       *\n       * We start by creating a connection handler. The `create_connection`\n       * function takes two arguments: a connection-level error handler (you\n       * can read more about the difference between connection-level and\n       * stream-level in H2 and HTTP/2 in general here:\n       * https://anmonteiro.com/ocaml-h2/h2/H2/Client_connection/index.html#val-create)\n       * and the file descriptor that we created above. *)\n      Client.TLS.create_connection_with_default ~error_handler socket\n      \u003e\u003e= fun connection -\u003e\n      (* Once the connection has been created, we can initiate our request. For\n       * that, we call the `request` function, which will send the request that\n       * we created to the server, and direct its response to either the\n       * response handler - in case of a successful request / response exchange\n       * - or the (stream-level) error handler, in case our request was\n       * malformed. *)\n      let request_body =\n        Client.TLS.request connection request ~error_handler ~response_handler\n      in\n      (* The `request` function returns a request body that we can write to,\n       * but in our case just the headers are sufficient. We close the request\n       * body immediately to signal to the underlying HTTP/2 framing layer that\n       * we're done sending our request. *)\n      Body.Writer.close request_body;\n      (* Our call to `Lwt_main.run` above will wait until this promise is\n       * filled  before exiting the program. *)\n      response_received )\n```\n\n## Conformance\n\nOne of h2's goals is to be 100% compliant with the HTTP/2 specification.\nThere are currently 3 mechanisms in place to verify such conformance:\n\n1. Unit tests using the HPACK stories in the\n   [http2jp/hpack-test-case](https://github.com/http2jp/hpack-test-case)\n   repository\n2. Unit tests using the test cases provided by the\n   [http2jp/http2-frame-test-case](https://github.com/http2jp/http2-frame-test-case)\n   repository.\n3. Automated test runs (in CI) using the\n   [h2spec](https://github.com/summerwind/h2spec) conformance testing tool for\n   HTTP/2 implementations.\n   - These test all the `Reqd.respond_with_*` functions for conformance against\n     the specification.\n\n## Performance\n\nh2 aims to be a high-performance, memory-efficient, scalable, and easily\nportable (with respect to different I/O runtimes) implementation. To achieve\nthat, it takes advantage of the unbuffered parsing interface in Angstrom using\noff-heap buffers wherever possible, for both parsing and serialization.\n\nBelow is a plot of H2's latency profile at a sustained rate of 17000 requests\nper second over 30 seconds, benchmarked using the\n[vegeta](https://github.com/tsenart/vegeta) load testing tool.\n\n![ocaml-h2](./vegeta-plot.png)\n\n## Development\n\nThis source distribution provides a number of packages and examples. The\ndirectory structure is as follows:\n\n- [`examples/`](./examples): contains example applications using the various\n  I/O runtimes provided in this source distribution.\n- [`hpack/`](./hpack): contains the implementation of\n  [HPACK](https://tools.ietf.org/html/rfc7541), the Header Compression\n  specification for HTTP/2.\n- [`lib/`](./lib): contains the core implementation of this library, including\n  HTTP/2 frame parsing, serialization and state machine implementations.\n- [`lib_test/`](./lib_test): contains various unit tests for modules in the\n  core h2 package.\n-  [`lwt/`](./lwt): contains an implementation of a Lwt runtime for h2\n  functorized over the specific input / output channel abstraction such that it\n  can work in either UNIX-like systems or MirageOS.\n- [`lwt-unix/`](./lwt-unix): contains an Lwt runtime adapter for h2 that\n  communicates over UNIX file descriptors.\n- [`mirage/`](./mirage): contains a Mirage runtime adapter for h2 that\n  allows using h2 to write unikernels that serve traffic over HTTP/2.\n- [`spec/`](./spec): contains example implementations of servers using h2\n  that respond with the different provided APIs to be used for conformance\n  testing with the [h2spec](https://github.com/summerwind/h2spec) tool.\n\n### Cloning the repository\n\n```shell\n# Use --recurse-submodules to get the test git submodules\n$ git clone git@github.com:anmonteiro/ocaml-h2.git --recurse-submodules\n```\n\n### Using OPAM\n\nTo install development dependencies, pin the package from the root of the\nrepository:\n\n```bash\n$ opam pin add -n hpack .\n$ opam pin add -n h2 .\n$ opam install --deps-only h2\n```\n\nAfter this, you may install a development version of the library using the\ninstall command as usual.\n\nTests can be run via dune:\n\n```bash\ndune runtest\n```\n\n## License\n\nh2 is distributed under the 3-Clause BSD License, see [LICENSE](./LICENSE).\n\nThis source distribution includes work based on\n[http/af](https://github.com/inhabitedtype/httpaf). http/af's license file is\nincluded in [httpaf.LICENSE](./httpaf.LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanmonteiro%2Focaml-h2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fanmonteiro%2Focaml-h2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fanmonteiro%2Focaml-h2/lists"}