Repository: ostera/httpkit Branch: master Commit: ecfe4297bb3d Files: 47 Total size: 60.6 KB Directory structure: gitextract_lfbuuy6e/ ├── .gitattributes ├── .gitignore ├── 3rdparty/ │ ├── dune-project │ ├── httpaf-lwt-unix/ │ │ ├── buffer.ml │ │ ├── buffer.mli │ │ ├── dune │ │ ├── httpaf_lwt_unix.ml │ │ ├── httpaf_lwt_unix.mli │ │ ├── ssl_io.ml │ │ └── tls_io.ml │ └── httpaf-lwt-unix.opam ├── README.md ├── bench/ │ ├── .gitignore │ ├── Gemfile │ ├── README.md │ ├── echo.go │ ├── echo.js │ ├── echo.py │ └── echo.rb ├── dune-project ├── esy.json ├── examples/ │ ├── dune │ ├── echo_server_http.re │ ├── echo_server_http2.re │ ├── request_http.re │ └── request_http2.re ├── httpkit-lwt-unix-h2.opam ├── httpkit-lwt-unix-httpaf.opam ├── httpkit.opam ├── lwt-unix-h2/ │ ├── client_http.re │ ├── client_https.re │ ├── client_request.re │ ├── client_response.re │ ├── dune │ ├── httpkit_lwt_unix_h2.re │ ├── server_http.re │ └── server_request.re ├── lwt-unix-httpaf/ │ ├── client_http.re │ ├── client_https.re │ ├── client_request.re │ ├── client_response.re │ ├── dune │ ├── httpkit_lwt_unix_httpaf.re │ ├── server_http.re │ └── server_request.re └── src/ ├── dune └── httpkit.re ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ bench/* linguist-vendored *.re linguist-language=Reason *.rei linguist-language=Reason *.ml linguist-language=OCaml *.mli linguist-language=OCaml ================================================ FILE: .gitignore ================================================ _esy _build _public .merlin /lua /lua_modules *.install ================================================ FILE: 3rdparty/dune-project ================================================ (lang dune 1.9) (name httpaf-lwt-unix) ================================================ FILE: 3rdparty/httpaf-lwt-unix/buffer.ml ================================================ open Lwt.Infix (* Based on the Buffer module in httpaf_async.ml. *) type t = { buffer : Lwt_bytes.t ; mutable off : int ; mutable len : int } let create size = let buffer = Lwt_bytes.create size in { buffer; off = 0; len = 0 } let compress t = if t.len = 0 then begin t.off <- 0; t.len <- 0; end else if t.off > 0 then begin Lwt_bytes.blit t.buffer t.off t.buffer 0 t.len; t.off <- 0; end let get t ~f = let n = f t.buffer ~off:t.off ~len:t.len in t.off <- t.off + n; t.len <- t.len - n; if t.len = 0 then t.off <- 0; n let put t ~f = compress t; f t.buffer ~off:(t.off + t.len) ~len:(Lwt_bytes.length t.buffer - t.len) >>= fun n -> t.len <- t.len + n; Lwt.return n ================================================ FILE: 3rdparty/httpaf-lwt-unix/buffer.mli ================================================ type t val create : int -> t val get : t -> f:(Lwt_bytes.t -> off:int -> len:int -> int) -> int val put : t -> f:(Lwt_bytes.t -> off:int -> len:int -> int Lwt.t) -> int Lwt.t ================================================ FILE: 3rdparty/httpaf-lwt-unix/dune ================================================ (library (name httpaf_lwt_unix) (public_name httpaf-lwt-unix) (libraries faraday-lwt-unix httpaf lwt.unix ssl lwt_ssl) (modules buffer httpaf_lwt_unix tls_io ssl_io) (flags (:standard -safe-string))) ================================================ FILE: 3rdparty/httpaf-lwt-unix/httpaf_lwt_unix.ml ================================================ (*---------------------------------------------------------------------------- Copyright (c) 2018 Inhabited Type LLC. Copyright (c) 2018 Anton Bachin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the author nor the names of his contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ----------------------------------------------------------------------------*) open Lwt.Infix let read fd buffer = Lwt.catch (fun () -> Buffer.put buffer ~f:(fun bigstring ~off ~len -> Lwt_bytes.read fd bigstring off len)) (function | Unix.Unix_error (Unix.EBADF, _, _) as exn -> Lwt.fail exn | exn -> Lwt.async (fun () -> Lwt_unix.close fd); Lwt.fail exn) >>= fun bytes_read -> if bytes_read = 0 then Lwt.return `Eof else Lwt.return (`Ok bytes_read) let shutdown socket command = try Lwt_unix.shutdown socket command with Unix.Unix_error (Unix.ENOTCONN, _, _) -> () module Config = Httpaf.Config module Server = struct module Server_connection = Httpaf.Server_connection let start_read_write_loops ?(readf=read) ?(writev=Faraday_lwt_unix.writev_of_fd) ~config ~socket connection = let read_buffer = Buffer.create config.Config.read_buffer_size in let read_loop_exited, notify_read_loop_exited = Lwt.wait () in let rec read_loop () = let rec read_loop_step () = match Server_connection.next_read_operation connection with | `Read -> readf socket read_buffer >>= begin function | `Eof -> Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> Server_connection.read_eof connection bigstring ~off ~len) |> ignore; read_loop_step () | `Ok _ -> Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> Server_connection.read connection bigstring ~off ~len) |> ignore; read_loop_step () end | `Yield -> Server_connection.yield_reader connection read_loop; Lwt.return_unit | `Close -> Lwt.wakeup_later notify_read_loop_exited (); if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin shutdown socket Unix.SHUTDOWN_RECEIVE end; Lwt.return_unit in Lwt.async (fun () -> Lwt.catch read_loop_step (fun exn -> Server_connection.report_exn connection exn; Lwt.return_unit)) in let writev = writev socket in let write_loop_exited, notify_write_loop_exited = Lwt.wait () in let rec write_loop () = let rec write_loop_step () = match Server_connection.next_write_operation connection with | `Write io_vectors -> writev io_vectors >>= fun result -> Server_connection.report_write_result connection result; write_loop_step () | `Yield -> Server_connection.yield_writer connection write_loop; Lwt.return_unit | `Close _ -> Lwt.wakeup_later notify_write_loop_exited (); if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin shutdown socket Unix.SHUTDOWN_SEND end; Lwt.return_unit in Lwt.async (fun () -> Lwt.catch write_loop_step (fun exn -> Server_connection.report_exn connection exn; Lwt.return_unit)) in read_loop (); write_loop (); Lwt.join [read_loop_exited; write_loop_exited] >>= fun () -> if Lwt_unix.state socket <> Lwt_unix.Closed then Lwt.catch (fun () -> Lwt_unix.close socket) (fun _exn -> Lwt.return_unit) else Lwt.return_unit let create_connection_handler ?(config=Config.default) ~request_handler ~error_handler = fun client_addr socket -> let connection = Server_connection.create ~config ~error_handler:(error_handler client_addr) (request_handler client_addr) in start_read_write_loops ~config ~socket connection module TLS = struct let create_connection_handler ?server ?certfile ?keyfile ?(config=Config.default) ~request_handler ~error_handler = let make_tls_server = Tls_io.make_server ?server ?certfile ?keyfile in fun client_addr socket -> let connection = Server_connection.create ~config ~error_handler:(error_handler client_addr) (request_handler client_addr) in make_tls_server socket >>= fun tls_server -> let readf = Tls_io.readf tls_server in let writev = Tls_io.writev tls_server in start_read_write_loops ~config ~readf ~writev ~socket connection >>= Lwt.return end module SSL = struct let create_connection_handler ?server ?certfile ?keyfile ?(config=Config.default) ~request_handler ~error_handler = let make_ssl_server = Ssl_io.make_server ?server ?certfile ?keyfile in fun client_addr socket -> let connection = Server_connection.create ~config ~error_handler:(error_handler client_addr) (request_handler client_addr) in make_ssl_server socket >>= fun tls_server -> let readf = Ssl_io.readf tls_server in let writev = Ssl_io.writev tls_server in start_read_write_loops ~config ~readf ~writev ~socket connection >>= Lwt.return end end module Client = struct module Client_connection = Httpaf.Client_connection let start_read_write_loops ?(readf=read) ?(writev=Faraday_lwt_unix.writev_of_fd) ~config ~socket connection = let read_buffer = Buffer.create config.Config.read_buffer_size in let read_loop_exited, notify_read_loop_exited = Lwt.wait () in let read_loop () = let rec read_loop_step () = match Client_connection.next_read_operation connection with | `Read -> readf socket read_buffer >>= begin function | `Eof -> Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> Client_connection.read_eof connection bigstring ~off ~len) |> ignore; read_loop_step () | `Ok _ -> Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> Client_connection.read connection bigstring ~off ~len) |> ignore; read_loop_step () end | `Close -> Lwt.wakeup_later notify_read_loop_exited (); if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin shutdown socket Unix.SHUTDOWN_RECEIVE end; Lwt.return_unit in Lwt.async (fun () -> Lwt.catch read_loop_step (fun exn -> Client_connection.report_exn connection exn; Lwt.return_unit)) in let writev = writev socket in let write_loop_exited, notify_write_loop_exited = Lwt.wait () in let rec write_loop () = let rec write_loop_step () = match Client_connection.next_write_operation connection with | `Write io_vectors -> writev io_vectors >>= fun result -> Client_connection.report_write_result connection result; write_loop_step () | `Yield -> Client_connection.yield_writer connection write_loop; Lwt.return_unit | `Close _ -> Lwt.wakeup_later notify_write_loop_exited (); Lwt.return_unit in Lwt.async (fun () -> Lwt.catch write_loop_step (fun exn -> Client_connection.report_exn connection exn; Lwt.return_unit)) in read_loop (); write_loop (); Lwt.async (fun () -> Lwt.join [read_loop_exited; write_loop_exited] >>= fun () -> if Lwt_unix.state socket <> Lwt_unix.Closed then Lwt.catch (fun () -> Lwt_unix.close socket) (fun _exn -> Lwt.return_unit) else Lwt.return_unit) let request ?(config=Config.default) socket request ~error_handler ~response_handler = let request_body, connection = Client_connection.request ~config request ~error_handler ~response_handler in start_read_write_loops ~config ~socket connection; request_body module TLS = struct let request ?client ?(config=Config.default) socket request ~error_handler ~response_handler = let request_body, connection = Client_connection.request ~config request ~error_handler ~response_handler in Lwt.async(fun () -> Tls_io.make_client ?client socket >|= fun tls_client -> let readf = Tls_io.readf tls_client in let writev = Tls_io.writev tls_client in start_read_write_loops ~config ~readf ~writev ~socket connection); request_body end module SSL = struct let request ?client ?(config=Config.default) socket request ~error_handler ~response_handler = let request_body, connection = Client_connection.request ~config request ~error_handler ~response_handler in Lwt.async(fun () -> Ssl_io.make_client ?client socket >|= fun tls_client -> let readf = Ssl_io.readf tls_client in let writev = Ssl_io.writev tls_client in start_read_write_loops ~config ~readf ~writev ~socket connection); request_body end end ================================================ FILE: 3rdparty/httpaf-lwt-unix/httpaf_lwt_unix.mli ================================================ open Httpaf (* The function that results from [create_connection_handler] should be passed to [Lwt_io.establish_server_with_client_socket]. For an example, see [examples/lwt_echo_server.ml]. *) module Server : sig val create_connection_handler : ?config : Config.t -> request_handler : (Unix.sockaddr -> Server_connection.request_handler) -> error_handler : (Unix.sockaddr -> Server_connection.error_handler) -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t module TLS : sig val create_connection_handler : ?server : Tls_io.server -> ?certfile : string -> ?keyfile : string -> ?config : Config.t -> request_handler : (Unix.sockaddr -> Server_connection.request_handler) -> error_handler : (Unix.sockaddr -> Server_connection.error_handler) -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t end module SSL : sig val create_connection_handler : ?server : Ssl_io.server -> ?certfile : string -> ?keyfile : string -> ?config : Config.t -> request_handler : (Unix.sockaddr -> Server_connection.request_handler) -> error_handler : (Unix.sockaddr -> Server_connection.error_handler) -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t end end (* For an example, see [examples/lwt_get.ml]. *) module Client : sig val request : ?config : Config.t -> Lwt_unix.file_descr -> Request.t -> error_handler : Client_connection.error_handler -> response_handler : Client_connection.response_handler -> [`write] Body.t module TLS : sig val request : ?client : Tls_io.client -> ?config : Config.t -> Lwt_unix.file_descr -> Request.t -> error_handler : Client_connection.error_handler -> response_handler : Client_connection.response_handler -> [`write] Body.t end module SSL : sig val request : ?client : Ssl_io.client -> ?config : Config.t -> Lwt_unix.file_descr -> Request.t -> error_handler : Client_connection.error_handler -> response_handler : Client_connection.response_handler -> [`write] Body.t end end ================================================ FILE: 3rdparty/httpaf-lwt-unix/ssl_io.ml ================================================ open Lwt.Infix let () = Ssl.init () let readf socket = fun _fd buffer -> Lwt.catch (fun () -> Buffer.put buffer ~f:(fun bigstring ~off ~len -> Lwt_unix.blocking (Lwt_ssl.get_fd socket) >>= fun _ -> Lwt_ssl.read_bytes socket bigstring off len)) (function | Unix.Unix_error (Unix.EBADF, _, _) as exn -> Lwt.fail exn | exn -> Lwt.async (fun () -> Lwt_ssl.ssl_shutdown socket >>= fun () -> Lwt_ssl.close socket); Lwt.fail exn) >>= fun bytes_read -> if bytes_read = 0 then Lwt.return `Eof else Lwt.return (`Ok bytes_read) let writev socket _fd = fun iovecs -> Lwt.catch (fun () -> Lwt_list.fold_left_s (fun acc {Faraday.buffer; off; len} -> Lwt_ssl.write_bytes socket buffer off len >|= fun written -> acc + written) 0 iovecs >|= fun n -> `Ok n) (function | Unix.Unix_error (Unix.EBADF, "check_descriptor", _) -> Lwt.return `Closed | exn -> Lwt.fail exn) type client = Lwt_ssl.socket type server = Lwt_ssl.socket let make_client ?client socket = match client with | Some client -> Lwt.return client | None -> let client_ctx = Ssl.create_context Ssl.SSLv23 Ssl.Client_context in Ssl.disable_protocols client_ctx [Ssl.SSLv23]; Ssl.honor_cipher_order client_ctx; Lwt_ssl.ssl_connect socket client_ctx let make_server ?server ?certfile ?keyfile socket = match server, certfile, keyfile with | Some server, _, _ -> Lwt.return server | None, Some cert, Some priv_key -> let server_ctx = Ssl.create_context Ssl.SSLv23 Ssl.Server_context in Ssl.disable_protocols server_ctx [Ssl.SSLv23]; Ssl.use_certificate server_ctx cert priv_key; Lwt_ssl.ssl_accept socket server_ctx | _ -> Lwt.fail (Invalid_argument "Certfile and Keyfile required when server isn't provided") ================================================ FILE: 3rdparty/httpaf-lwt-unix/tls_io.ml ================================================ let readf _tls = fun _fd _buffer -> Lwt.fail_with "Tls not available" let writev _tls _fd = fun _iovecs -> Lwt.fail_with "Tls not available" type client = [ `Tls_not_available ] type server = [ `Tls_not_available ] let[@ocaml.warning "-21"] make_client ?client:_ = failwith "TLS not available"; fun _socket -> Lwt.return `Tls_not_available let[@ocaml.warning "-21"] make_server ?server:_ ?certfile:_ ?keyfile:_ = failwith "TLS not available"; fun _socket -> Lwt.fail_with "TLS not available" ================================================ FILE: 3rdparty/httpaf-lwt-unix.opam ================================================ ================================================ FILE: README.md ================================================ # ⚡️HttpKit — high-level, high-performance HTTP1.1/2 clients/servers in Reason > NOTE: under heavy reconstruction. Latest stable version was [`660d1c8`](https://github.com/ostera/httpkit/tree/660d1c8b7438d207be2717495d8590a529bf5a1f) HttpKit is a high-level library for building and consuming web servers over HTTP, HTTPS, and HTTP2. It serves as a thin layer over `h2` and `http/af`, and when it can it allows you to seamlessly transition from one to the other. 0. [Roadmap](#roadmap) 1. [Getting Started](#getting-started) 1. [Running the Examples](#running-the-examples) ## Roadmap | Feature | HTTP/1.1 | HTTPS/1.1 | HTTP/2 | HTTPS/2 | |------------------|----------|-----------|--------|---------| | Listen as Server | Yes | No | Yes | No | | Send Request | Yes | Yes | No | No | | Server Push | - | - | No | No | | | | | | | ## Getting Started #### Usage `httpkit` can be used both to build servers and to make requests as a client. Documentation is still a work-in-progress, but there's examples in the `examples` section that can give you a better idea of how to use the libraries. In short: For making a request: ```reason open Lwt_result.Infix; module Httpkit = Httpkit_lwt_unix_httpaf; let req = Httpkit.Request.create( ~headers=[("User-Agent", "Reason HttpKit")], `GET, Uri.of_string("http://api.github.com/repos/ostera/httpkit"), ); /* Send over HTTP */ req |> Httpkit.Client.Http.send >>= Httpkit.Client.Response.body |> Lwt_main.run /* Send over HTTPS */ req |> Httpkit.Client.Https.send >>= Httpkit.Client.Response.body |> Lwt_main.run ``` For making a server: ```reason module Httpkit = Httpkit_lwt_unix_httpaf; let port = 8080; let on_start = (~hoststring) => Logs.app(m => m("Running on %s", hoststring)); let handler: Httpkit.Server.handler = (req, reply, kill_server) => { let method = req |> Httpkit.Request.meth |> H2.Method.to_string; let path = req |> Httpkit.Request.path; Logs.app(m => m("%s %s", method, path)); reply(200, "hi"); kill_server(); }; /* Start server over HTTP */ Httpkit.Server.Http.listen(~port, ~address=`Any, ~handler, ~on_start) |> Lwt_main.run; /* Start server over HTTPS */ Httpkit.Server.Http.listen(~port, ~address=`Any, ~handler, ~on_start) |> Lwt_main.run; ``` #### Installing with esy You can install by dropping the following dependencies in your `package.json`: ```json { "dependencies": { "@opam/httpkit": "*", "@opam/httpkit-lwt-unix-httpaf": "*", "@opam/logs": "*", "@opam/fmt": "*", // ... }, "resolutions": { "@opam/httpkit": "ostera/httpkit:httpkit.opam#f738417", "@opam/httpkit-lwt-unix-httpaf": "ostera/httpkit:httpkit-lwt-unix-httpaf.opam#f738417", } } ``` > NOTE: For `httpkit` make sure you're using the latest commit hash! ## Running the Examples All of the examples are runnable as binaries after compilation, so you can either run `esy build` and find them within `./_esy/default/build/default/examples/*.exe` or you can ask dune to run them for you: ```sh ostera/httpkit λ esy dune exec ./examples/Request.exe ``` ================================================ FILE: bench/.gitignore ================================================ echo ================================================ FILE: bench/Gemfile ================================================ source "https://rubygems.org" gem "rack" ================================================ FILE: bench/README.md ================================================ # Benchmarking HttpKit ⚡️ In order to ensure that `httpkit` stays fast, here you'll find a number of small servers written in other languages to benchmark against. They should all do the same thing: 1. Log down the time, method and path 2. Reply with the path They will all be called with the same command: ```sh wrk2 \ --threads=12 \ --connections=400 \ --duration=30s \ --rate 30K https://localhost:8080/bench-it-chewie! ``` ## Results | Lang | Lib | KB/s | RPS | Total Req | |---------|---------------------|----------|-------|-----------| | OCaml | httpkit+http/af+lwt | 414.17KB | 10874 | 326230 | | OCaml | http/af+lwt | 414.76KB | 10890 | 326792 | | Ruby | rack | 149.70KB | 806 | 24349 | | Node.js | stdlib http | 591.80KB | 5179 | 155767 | | Golang | stdlib http | 0.95MB | 13314 | 399390 | | Python | BaseHTTPServer | 65.50KB | 519 | 15704 | ## Details ### OCaml/httpkit+httpaf+lwt ```sh ostera/httpkit λ wrk2 --threads=12 --connections=400 --duration=30s --rate 30K http://localhost:9999/what Running 30s test @ http://localhost:9999/what 12 threads and 400 connections Thread calibration: mean lat.: 3092.528ms, rate sampling interval: 11714ms Thread calibration: mean lat.: 3241.606ms, rate sampling interval: 12107ms Thread calibration: mean lat.: 3183.128ms, rate sampling interval: 11763ms Thread calibration: mean lat.: 3235.211ms, rate sampling interval: 12009ms Thread calibration: mean lat.: 3109.703ms, rate sampling interval: 11780ms Thread calibration: mean lat.: 3214.701ms, rate sampling interval: 12017ms Thread calibration: mean lat.: 3174.243ms, rate sampling interval: 11862ms Thread calibration: mean lat.: 3067.300ms, rate sampling interval: 11575ms Thread calibration: mean lat.: 3066.558ms, rate sampling interval: 11755ms Thread calibration: mean lat.: 3101.464ms, rate sampling interval: 11722ms Thread calibration: mean lat.: 3084.410ms, rate sampling interval: 11829ms Thread calibration: mean lat.: 3075.844ms, rate sampling interval: 11444ms Thread Stats Avg Stdev Max +/- Stdev Latency 12.87s 3.62s 20.76s 58.95% Req/Sec 0.92k 18.58 0.95k 58.33% 326230 requests in 30.00s, 12.13MB read Socket errors: connect 0, read 40, write 3, timeout 4 Requests/sec: 10874.67 Transfer/sec: 414.17KB ``` ### OCaml/httpaf+lwt ```sh ostera/httpkit λ wrk2 --threads=12 --connections=400 --duration=30s --rate 30K http://localhost:9999/what Running 30s test @ http://localhost:9999/what 12 threads and 400 connections Thread calibration: mean lat.: 3103.933ms, rate sampling interval: 11304ms Thread calibration: mean lat.: 3074.874ms, rate sampling interval: 11141ms Thread calibration: mean lat.: 3190.094ms, rate sampling interval: 11370ms Thread calibration: mean lat.: 2974.283ms, rate sampling interval: 10543ms Thread calibration: mean lat.: 2987.974ms, rate sampling interval: 11091ms Thread calibration: mean lat.: 2895.381ms, rate sampling interval: 10919ms Thread calibration: mean lat.: 2908.596ms, rate sampling interval: 10870ms Thread calibration: mean lat.: 3164.646ms, rate sampling interval: 11526ms Thread calibration: mean lat.: 3000.368ms, rate sampling interval: 11182ms Thread calibration: mean lat.: 3051.481ms, rate sampling interval: 11386ms Thread calibration: mean lat.: 3005.150ms, rate sampling interval: 11165ms Thread calibration: mean lat.: 2956.432ms, rate sampling interval: 10960ms Thread Stats Avg Stdev Max +/- Stdev Latency 12.55s 3.75s 21.20s 59.01% Req/Sec 0.91k 15.95 0.95k 75.00% 326792 requests in 30.01s, 12.15MB read Socket errors: connect 0, read 97, write 4, timeout 11 Requests/sec: 10890.16 Transfer/sec: 414.76KB ``` ### Ruby/Rack ```sh ostera/httpkit λ wrk2 --threads=12 --connections=400 --duration=30s --rate 30K http://localhost:8080/bench-it-chewie! Running 30s test @ http://localhost:8080/bench-it-chewie! 12 threads and 400 connections Thread calibration: mean lat.: 4611.970ms, rate sampling interval: 16515ms Thread calibration: mean lat.: 4602.015ms, rate sampling interval: 16154ms Thread calibration: mean lat.: 4654.559ms, rate sampling interval: 16613ms Thread calibration: mean lat.: 4516.088ms, rate sampling interval: 16179ms Thread calibration: mean lat.: 4601.379ms, rate sampling interval: 16269ms Thread calibration: mean lat.: 4900.919ms, rate sampling interval: 16449ms Thread calibration: mean lat.: 4552.994ms, rate sampling interval: 16523ms Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms Thread calibration: mean lat.: 9223372036854776.000ms, rate sampling interval: 10ms Thread calibration: mean lat.: 4669.880ms, rate sampling interval: 16556ms Thread Stats Avg Stdev Max +/- Stdev Latency 17.89s 5.12s 27.25s 57.47% Req/Sec 0.11 3.31 108.00 99.89% 24349 requests in 30.18s, 4.41MB read Socket errors: connect 0, read 0, write 0, timeout 4276 Requests/sec: 806.78 Transfer/sec: 149.70KB ``` ### Node/Http ```sh ostera/httpkit λ wrk2 --threads=12 --connections=400 --duration=30s --rate 30K http://localhost:8080/bench-it-chewie! Running 30s test @ http://localhost:8080/bench-it-chewie! 12 threads and 400 connections Thread calibration: mean lat.: 3976.372ms, rate sampling interval: 13983ms Thread calibration: mean lat.: 4418.043ms, rate sampling interval: 15417ms Thread calibration: mean lat.: 4419.606ms, rate sampling interval: 15417ms Thread calibration: mean lat.: 4413.743ms, rate sampling interval: 15409ms Thread calibration: mean lat.: 4421.132ms, rate sampling interval: 15417ms Thread calibration: mean lat.: 4418.656ms, rate sampling interval: 15417ms Thread calibration: mean lat.: 4422.277ms, rate sampling interval: 15417ms Thread calibration: mean lat.: 4420.156ms, rate sampling interval: 15409ms Thread calibration: mean lat.: 4420.646ms, rate sampling interval: 15409ms Thread calibration: mean lat.: 3972.669ms, rate sampling interval: 13885ms Thread calibration: mean lat.: 4421.550ms, rate sampling interval: 15417ms Thread calibration: mean lat.: 3966.959ms, rate sampling interval: 13877ms Thread Stats Avg Stdev Max +/- Stdev Latency 16.14s 4.73s 24.72s 56.45% Req/Sec 456.33 0.75 458.00 100.00% 155767 requests in 30.07s, 17.38MB read Socket errors: connect 0, read 1, write 0, timeout 0 Requests/sec: 5179.50 Transfer/sec: 591.80KB ``` ### Golang/Http The golang standard library `http` module seems flexible enough for us to build an incredibly fast echo server! Closer to 3 times faster than node's, and around 2000 requests per second more than `httpkit`. If the superior type-safety offered by `httpkit` is not what you're looking for, have a look at this: ```sh ostera/httpkit λ wrk2 --threads=12 --connections=400 --duration=30s --rate 30K http://localhost:8080/bench-it-chewie! Running 30s test @ http://localhost:8080/bench-it-chewie! 12 threads and 400 connections Thread calibration: mean lat.: 2694.872ms, rate sampling interval: 9592ms Thread calibration: mean lat.: 2674.729ms, rate sampling interval: 9551ms Thread calibration: mean lat.: 2644.030ms, rate sampling interval: 9437ms Thread calibration: mean lat.: 2680.816ms, rate sampling interval: 9568ms Thread calibration: mean lat.: 2667.644ms, rate sampling interval: 9502ms Thread calibration: mean lat.: 2691.502ms, rate sampling interval: 9560ms Thread calibration: mean lat.: 2490.139ms, rate sampling interval: 8781ms Thread calibration: mean lat.: 2652.057ms, rate sampling interval: 9461ms Thread calibration: mean lat.: 2665.217ms, rate sampling interval: 9519ms Thread calibration: mean lat.: 2687.348ms, rate sampling interval: 9584ms Thread calibration: mean lat.: 2697.834ms, rate sampling interval: 9560ms Thread calibration: mean lat.: 2684.353ms, rate sampling interval: 9519ms Thread Stats Avg Stdev Max +/- Stdev Latency 11.02s 3.20s 17.10s 58.01% Req/Sec 1.09k 23.03 1.12k 62.50% 399390 requests in 30.00s, 28.57MB read Requests/sec: 13314.83 Transfer/sec: 0.95MB ``` ### Python/BaseHTTPServer ```sh ostera/httpkit λ wrk2 --threads=12 --connections=400 --duration=30s --rate 30K http://localhost:8080/bench-it-chewie! Running 30s test @ http://localhost:8080/bench-it-chewie! 12 threads and 400 connections Thread calibration: mean lat.: 4190.046ms, rate sampling interval: 14639ms Thread calibration: mean lat.: 4269.527ms, rate sampling interval: 14483ms Thread calibration: mean lat.: 4213.688ms, rate sampling interval: 15704ms Thread calibration: mean lat.: 3632.081ms, rate sampling interval: 14434ms Thread calibration: mean lat.: 3932.717ms, rate sampling interval: 13344ms Thread calibration: mean lat.: 4980.591ms, rate sampling interval: 15450ms Thread calibration: mean lat.: 3027.887ms, rate sampling interval: 12361ms Thread calibration: mean lat.: 4010.051ms, rate sampling interval: 12828ms Thread calibration: mean lat.: 4807.472ms, rate sampling interval: 18104ms Thread calibration: mean lat.: 4543.476ms, rate sampling interval: 14524ms Thread calibration: mean lat.: 4214.574ms, rate sampling interval: 14008ms Thread calibration: mean lat.: 4466.553ms, rate sampling interval: 16506ms Thread Stats Avg Stdev Max +/- Stdev Latency 10.99s 2.92s 20.97s 63.93% Req/Sec 6.50 1.89 10.00 91.67% 15704 requests in 30.20s, 1.93MB read Socket errors: connect 0, read 1533, write 57, timeout 4489 Requests/sec: 519.94 Transfer/sec: 65.50KB ``` ### Lua/http ### Rust/hyper+tokio ================================================ FILE: bench/echo.go ================================================ package main import ( "bytes" "log" "net/http" ) const DefaultPort = "8080" func EchoHandler(writer http.ResponseWriter, request *http.Request) { stamp := request.Method + " " + request.URL.Path log.Println(stamp) buf := bytes.NewBufferString(request.URL.Path) request.Write(buf) } func main() { log.Println("Listening on port " + DefaultPort) http.HandleFunc("/", EchoHandler) http.ListenAndServe(":"+DefaultPort, nil) } ================================================ FILE: bench/echo.js ================================================ const httpServer = require('http'); httpServer.createServer((req, res) => { console.log(new Date(), req.method, req.url); res.end(req.url); }) .listen(8080, () => console.log("Server started..."));; ================================================ FILE: bench/echo.py ================================================ from BaseHTTPServer import BaseHTTPRequestHandler,HTTPServer PORT_NUMBER = 8080 class handler(BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-Length',len(self.path)) self.end_headers() self.wfile.write(self.path) return server = HTTPServer(('', PORT_NUMBER), handler) print 'Listening on port ' , PORT_NUMBER server.serve_forever() ================================================ FILE: bench/echo.rb ================================================ require 'rack' app = Proc.new do |env| req = Rack::Request.new(env) meth = req.request_method path = req.path now = Time.now puts "#{now} - #{meth} #{path}" ['200', {'Content-Type' => 'text/html'}, [path]] end Rack::Handler::WEBrick.run app ================================================ FILE: dune-project ================================================ (lang dune 1.5) (name httpkit) (using fmt 1.0) ================================================ FILE: esy.json ================================================ { "dependencies": { "@esy-ocaml/reason": "3.4.0", "@opam/dune": "*", "@opam/fmt": "*", "@opam/h2": "0.2.0", "@opam/h2-lwt": "0.2.0", "@opam/h2-lwt-unix": "0.2.0", "@opam/httpaf": "*", "@opam/logs": "*", "@opam/lwt": "4.2.1", "@opam/lwt_ssl": "1.1.2", "@opam/merlin": "*", "@opam/odoc": "*", "@opam/uri": "*", "@opam/yojson": "*", "ocaml": "~4.7.0", "refmterr": "*" }, "resolutions": { "@opam/conf-libev": "esy-packages/libev:package.json#86d244e", "@opam/conf-autoconf": "esy-packages/esy-autoconf:package.json#71a8836", "@opam/conf-openssl": { "source": "no-source:", "override": { "dependencies": { "@opam/conf-pkg-config": "*", "@esy-packages/esy-openssl": "esy-packages/esy-openssl#f6107d6", "@opam/conf-autoconf": "*" } } }, "@opam/httpaf": "anmonteiro/httpaf:httpaf.opam#6d2c80e3a16ecf85d74df76005ab68136457e111", "@opam/ssl": "anmonteiro/ocaml-ssl:ssl.opam#b965d15" } } ================================================ FILE: examples/dune ================================================ (executable (name request_http) (modules request_http) (ocamlopt_flags -O3) (libraries httpkit-lwt-unix-httpaf httpkit logs.fmt fmt.tty)) (executable (name request_http2) (modules request_http2) (ocamlopt_flags -O3) (libraries httpkit-lwt-unix-h2 httpkit logs.fmt fmt.tty)) (executable (name echo_server_http) (modules echo_server_http) (ocamlopt_flags -O3) (libraries httpkit-lwt-unix-httpaf httpkit logs.fmt fmt.tty)) (executable (name echo_server_http2) (modules echo_server_http2) (ocamlopt_flags -O3) (libraries httpkit-lwt-unix-h2 httpkit logs.fmt fmt.tty)) ================================================ FILE: examples/echo_server_http.re ================================================ /** Handle sigpipe internally */ Sys.(set_signal(sigpipe, Signal_ignore)); /** Setup loggers */ Fmt_tty.setup_std_outputs(); Logs.set_level(Some(Logs.Debug)); Logs.set_reporter(Logs_fmt.reporter()); module Httpkit = Httpkit_lwt_unix_httpaf; let port = 8080; let on_start = (~hoststring) => Logs.app(m => m("Running on %s", hoststring)); let handler: Httpkit.Server.handler = (req, reply, close) => { let method = req |> Httpkit.Request.meth |> H2.Method.to_string; let path = req |> Httpkit.Request.path; Logs.app(m => m("%s %s", method, path)); reply(200, "hi"); close(); }; Httpkit.Server.Http.listen(~port, ~address=`Any, ~handler, ~on_start) |> Lwt_main.run; ================================================ FILE: examples/echo_server_http2.re ================================================ /** Handle sigpipe internally */ Sys.(set_signal(sigpipe, Signal_ignore)); /** Setup loggers */ Fmt_tty.setup_std_outputs(); Logs.set_level(Some(Logs.Debug)); Logs.set_reporter(Logs_fmt.reporter()); module Httpkit = Httpkit_lwt_unix_h2; let port = 8080; let on_start = (~hoststring) => Logs.app(m => m("Running on %s", hoststring)); let handler: Httpkit.Server.handler = (req, reply, close) => { let method = req |> Httpkit.Request.meth |> H2.Method.to_string; let path = req |> Httpkit.Request.path; Logs.app(m => m("%s %s", method, path)); reply(200, "hi"); close(); }; Httpkit.Server.Http.listen(~port, ~address=`Any, ~handler, ~on_start) |> Lwt_main.run; ================================================ FILE: examples/request_http.re ================================================ open Lwt_result.Infix; /** Handle sigpipe internally */ Sys.(set_signal(sigpipe, Signal_ignore)); /** Setup loggers */ Fmt_tty.setup_std_outputs(); Logs.set_level(Some(Logs.Debug)); Logs.set_reporter(Logs_fmt.reporter()); module Httpkit = Httpkit_lwt_unix_httpaf; /** Sample HTTPS Request using No Authentication :tm: */ let https_url = Sys.argv[1]; Logs.app(m => m("Requesting: %s", https_url)); switch ( Httpkit.Request.create( ~headers=[ ("User-Agent", "Reason HttpKit"), ("Accept", "*/*"), ], `GET, https_url |> Uri.of_string, ) |> Httpkit.Client.Https.send >>= Httpkit.Client.Response.body |> Lwt_main.run ) { | exception e => Logs.err(m => m("%s", Printexc.to_string(e))) | Ok(body) => Logs.app(m => m("Response: %s", body)) | Error(`Connection_error(`Invalid_response_body_length(req))) => let str = Buffer.create(1024); let fmt = Format.formatter_of_buffer(str); Httpaf.Response.pp_hum(fmt, req); Logs.err(m => m( "Connection Error (Invalid response body length): %s", str |> Buffer.to_bytes |> Bytes.to_string, ) ); | Error(`Connection_error(`Malformed_response(str))) => Logs.err(m => m("Connection Error (Malformed response): %s", str)) | Error(`Connection_error(`Exn(ex))) => Logs.err(m => m("Connection Error (Exception): %s", ex |> Printexc.to_string) ) }; ================================================ FILE: examples/request_http2.re ================================================ open Lwt_result.Infix; /** Handle sigpipe internally */ Sys.(set_signal(sigpipe, Signal_ignore)); /** Setup loggers */ Fmt_tty.setup_std_outputs(); Logs.set_level(Some(Logs.Debug)); Logs.set_reporter(Logs_fmt.reporter()); module Httpkit = Httpkit_lwt_unix_h2; /** Sample HTTPS Request using No Authentication :tm: */ let https_url = Sys.argv[1]; Logs.app(m => m("Requesting: %s", https_url)); switch ( Httpkit.Request.create(`GET, https_url |> Uri.of_string) |> Httpkit.Client.Https.send >>= Httpkit.Client.Response.body |> Lwt_main.run ) { | exception e => Logs.err(m => m("%s", Printexc.to_string(e))) | Ok(body) => Logs.app(m => m("Response: %s", body)) | Error(`Connection_error(`Invalid_response_body_length(req))) => let str = Buffer.create(1024); let fmt = Format.formatter_of_buffer(str); H2.Response.pp_hum(fmt, req); Logs.err(m => m( "Connection Error (Invalid response body length): %s", str |> Buffer.to_bytes |> Bytes.to_string, ) ); | Error(`Connection_error(`Malformed_response(str))) => Logs.err(m => m("Connection Error (Malformed response): %s", str)) | Error(`Connection_error(`Exn(ex))) => Logs.err(m => m("Connection Error (Exception): %s", ex |> Printexc.to_string) ) | Error(`Connection_error(`Protocol_error)) => Logs.err(m => m("Connection Error (Protocol Error)")) }; ================================================ FILE: httpkit-lwt-unix-h2.opam ================================================ opam-version: "2.0" name: "httpkit-lwt-unix-h2" version: "0.13" synopsis: "High-level, High-performance HTTP(S) Clients/Servers with Lwt" maintainer: "Leandro Ostera " authors: "Leandro Ostera " license: "MIT" homepage: "https//github.com/ostera/httpkit" bug-reports: "https//github.com/ostera/httpkit/issues" depends: [ "httpkit" "h2" "h2-lwt" "h2-lwt-unix" "lwt" "lwt_ssl" "ssl" "tls" "dune" {build} "reason" {build} ] build: ["dune" "build" "-p" name] install: ["dune" "install" name "--prefix" prefix "--root" "."] ================================================ FILE: httpkit-lwt-unix-httpaf.opam ================================================ opam-version: "2.0" name: "httpkit-lwt-unix-httpaf" version: "0.13" synopsis: "High-level, High-performance HTTP(S) Clients/Servers with Lwt" maintainer: "Leandro Ostera " authors: "Leandro Ostera " license: "MIT" homepage: "https//github.com/ostera/httpkit" bug-reports: "https//github.com/ostera/httpkit/issues" depends: [ "httpkit" "httpaf" "httpaf-lwt-unix" "lwt" "ssl" "tls" "dune" {build} "reason" {build} ] build: ["dune" "build" "-p" name] install: ["dune" "install" name "--prefix" prefix "--root" "."] ================================================ FILE: httpkit.opam ================================================ opam-version: "2.0" name: "httpkit" version: "0.13" synopsis: "High-level, High-performance HTTP(S) Clients/Servers" maintainer: "Leandro Ostera " authors: "Leandro Ostera " license: "MIT" homepage: "https//github.com/ostera/httpkit" bug-reports: "https//github.com/ostera/httpkit/issues" depends: [ "logs" "uri" "httpaf" "h2" "dune" {build} "reason" {build} ] build: ["dune" "build" "-p" name] install: ["dune" "install" name "--prefix" prefix "--root" "."] ================================================ FILE: lwt-unix-h2/client_http.re ================================================ open Lwt.Infix; let send: (~config: H2.Config.t=?, Httpkit.Request.t) => Lwt_result.t( (H2.Response.t, H2.Body.t([ | `read])), [> | `Connection_error(H2.Client_connection.error)], ) = (~config=H2.Config.default, req) => { let uri = Httpkit.Request.uri(req); let response_handler = (notify_response_received, response, response_body) => { Logs.debug(m => m("Handling response...")); Lwt.wakeup_later( notify_response_received, (response, response_body) |> Lwt_result.return, ); }; let error_handler = (notify_response_received, error) => { Logs.debug(m => m("Handling errors...")); Lwt.wakeup_later( notify_response_received, `Connection_error(error) |> Lwt_result.fail, ); }; let host = Uri.host_with_default(uri); let port = switch (Uri.port(uri)) { | None => "443" | Some(number) => string_of_int(number) }; Lwt_unix.getaddrinfo(host, port, [Unix.(AI_FAMILY(PF_INET))]) >>= ( addresses => { Logs.debug(m => m("Got address...")); let socket_addr = List.hd(addresses).Unix.ai_addr; let socket = Lwt_unix.socket(Unix.PF_INET, Unix.SOCK_STREAM, 0); Lwt_unix.connect(socket, socket_addr) >>= ( () => { Logs.debug(m => m("Opened socket...")); let (response_received, notify_response_received) = Lwt.wait(); let response_handler = response_handler(notify_response_received); let error_handler = error_handler(notify_response_received); let write_body = request_body => { Logs.debug(m => m("Writing body...")); switch (Httpkit.Request.body(req)) { | None => () | Some(str) => H2.Body.write_string(request_body, str) }; H2.Body.close_writer(request_body); Logs.debug(m => m("Closed writer...")); response_received >>= (x => x); }; let request = Client_request.of_httpkit_request(req); H2_lwt_unix.Client.create_connection( ~config, ~error_handler, socket, ) >>= ( connection => { H2_lwt_unix.Client.request( connection, request, ~error_handler, ~response_handler, ) |> write_body; } ); } ); } ); }; ================================================ FILE: lwt-unix-h2/client_https.re ================================================ open Lwt.Infix; Ssl_threads.init(); Ssl.init(); let default_ssl_context = Ssl.create_context(Ssl.SSLv23, Ssl.Client_context); Ssl.disable_protocols(default_ssl_context, [Ssl.SSLv23]); Ssl.set_context_alpn_protos(default_ssl_context, ["h2"]); Ssl.honor_cipher_order(default_ssl_context); let send: (~config: H2.Config.t=?, Httpkit.Request.t) => Lwt_result.t( (H2.Response.t, H2.Body.t([ | `read])), [> | `Connection_error(H2.Client_connection.error)], ) = (~config=H2.Config.default, req) => { let uri = Httpkit.Request.uri(req); let response_handler = (notify_response_received, response, response_body) => { Logs.debug(m => m("Handling response...")); Lwt.wakeup_later( notify_response_received, (response, response_body) |> Lwt_result.return, ); }; let error_handler = (notify_response_received, error) => { Logs.debug(m => m("Handling errors...")); Lwt.wakeup_later( notify_response_received, `Connection_error(error) |> Lwt_result.fail, ); }; let host = Uri.host_with_default(uri); let port = switch (Uri.port(uri)) { | None => "443" | Some(number) => string_of_int(number) }; Lwt_unix.getaddrinfo(host, port, [Unix.(AI_FAMILY(PF_INET))]) >>= ( addresses => { let socket_addr = List.hd(addresses).Unix.ai_addr; let socket = Lwt_unix.socket(Unix.PF_INET, Unix.SOCK_STREAM, 0); Lwt_unix.connect(socket, socket_addr) >>= ( () => { Lwt_ssl.ssl_connect(socket, default_ssl_context) >>= ( ssl_client => { let (response_received, notify_response_received) = Lwt.wait(); let response_handler = response_handler(notify_response_received); let error_handler = error_handler(notify_response_received); let write_body = request_body => { switch (Httpkit.Request.body(req)) { | None => () | Some(str) => H2.Body.write_string(request_body, str) }; H2.Body.flush( request_body, () => { H2.Body.close_writer(request_body); Logs.debug(m => m("Closed body writer...")); }, ); response_received >>= (x => x); }; let request = Client_request.of_httpkit_request(req); Logs.debug(m => { let buffer = Buffer.create(1024); let fmt = Format.formatter_of_buffer(buffer); H2.Request.pp_hum(fmt, request); m("%s", buffer |> Buffer.contents); }); let handle_ssl_connection = connection => H2_lwt_unix.Client.SSL.request( connection, request, ~error_handler, ~response_handler, ) |> write_body; let connect = () => H2_lwt_unix.Client.SSL.create_connection( ~client=ssl_client, ~config, ~error_handler, socket, ); connect() >>= handle_ssl_connection; } ); } ); } ); }; ================================================ FILE: lwt-unix-h2/client_request.re ================================================ let of_httpkit_request = req => { open Httpkit; let host = req |> Request.host; let scheme = req |> Request.scheme; let meth = req |> Request.meth; let path = req |> Request.path; let headers = [ (":authority", host), (":method", meth |> Method.to_string), (":path", path), (":scheme", scheme), ] @ (req |> Request.headers) |> H2.Headers.of_list; H2.Request.create(~headers, ~scheme, meth, path); }; let to_httpkit_request = (~body, ~uri, req) => { Httpkit.Request.create( ~headers=req.H2.Request.headers |> H2.Headers.to_list, ~body= switch (body) { | None => "" | Some(b) => b }, req.meth, uri, ); }; ================================================ FILE: lwt-unix-h2/client_response.re ================================================ let body: ((H2.Response.t, H2.Body.t([ | `read]))) => Lwt.t(result(string, 'b)) = ((_response, body)) => { open Lwt.Infix; let buffer = Buffer.create(1024); Logs.debug(m => m("Buffering response...")); let (next, wakeup) = Lwt.wait(); Lwt.async(() => { let rec read_response = () => H2.Body.schedule_read( body, ~on_eof= () => Lwt.wakeup_later(wakeup, Ok(Buffer.contents(buffer))), ~on_read= (response_fragment, ~off, ~len) => { let response_fragment_string = Bytes.create(len); Lwt_bytes.blit_to_bytes( response_fragment, off, response_fragment_string, 0, len, ); Buffer.add_bytes(buffer, response_fragment_string); read_response(); }, ); read_response() |> Lwt.return; }) |> ignore; next >>= Lwt_result.lift; }; ================================================ FILE: lwt-unix-h2/dune ================================================ (library (name httpkit_lwt_unix_h2) (public_name httpkit-lwt-unix-h2) (libraries httpkit h2 h2-lwt h2-lwt-unix lwt lwt.unix uri logs logs.lwt fpath bigstringaf)) ================================================ FILE: lwt-unix-h2/httpkit_lwt_unix_h2.re ================================================ module Method = Httpkit.Method; module Status = Httpkit.Status; module Request = Httpkit.Request; module Server = { include Httpkit.Server; module Http = Server_http; }; module Client = { module Https = Client_https; module Http = Client_http; module Response = Client_response; }; ================================================ FILE: lwt-unix-h2/server_http.re ================================================ open Lwt.Infix; let make_request_handler: ( ~uri: Uri.t, ~handler: Httpkit.Server.handler, ~closer: unit => unit, Unix.sockaddr ) => H2.Server_connection.request_handler = (~uri, ~handler, ~closer, _client, reqd) => { let req = reqd |> H2.Reqd.request; Logs.debug(m => m("Handling request...")); let respond = (~headers=?, status, content) => { let headers = ( switch (headers) { | None => [] | Some(hs) => hs } ) @ [("content-length", content |> String.length |> string_of_int)] |> H2.Headers.of_list; let res = H2.Response.create(status |> H2.Status.of_code, ~headers); H2.Reqd.respond_with_string(reqd, res, content); }; Server_request.read_body(reqd) >|= ( body => { let uri = Uri.with_path(uri, req.target); let req = Client_request.to_httpkit_request(~uri, ~body, req); let () = handler(req, respond, closer); (); } ) |> ignore; }; let error_handler = (_client, ~request as _=?, err, _get) => { Logs.err(m => m( "Something went wrong! %s", switch (err) { | `Bad_gateway => "Bad gateway" | `Bad_request => "Bad request" | `Internal_server_error => "Internal_server_error" | `Exn(exn) => Printexc.to_string(exn) }, ) ); (); }; let listen: ( ~address: [ | `Loopback | `Any | `Of_string(string)]=?, ~port: int, ~on_start: (~hoststring: string) => unit, ~handler: Httpkit.Server.handler ) => Lwt.t(unit) = (~address=`Any, ~port, ~on_start, ~handler) => { let host = switch (address) { | `Loopback => "127.0.0.1" | `Any => "0.0.0.0" | `Of_string(str) => str }; let uri = Uri.make(~scheme="http", ~host, ~port, ()); let (forever, awaker) = Lwt.wait(); let closer = () => Lwt.wakeup_later(awaker, ()); let address = switch (address) { | `Loopback => Unix.inet_addr_loopback | `Any => Unix.inet_addr_any | `Of_string(str) => Unix.inet_addr_of_string(str) }; let listening_address = Unix.(ADDR_INET(address, port)); let connection_handler = H2_lwt_unix.Server.create_connection_handler( ~config=H2.Config.default, ~request_handler=make_request_handler(~uri, ~handler, ~closer), ~error_handler, ); Lwt_io.establish_server_with_client_socket( listening_address, connection_handler, ) >|= (_ => on_start(~hoststring=Uri.to_string(uri))) |> ignore; forever; }; ================================================ FILE: lwt-unix-h2/server_request.re ================================================ let read_body = reqd => { let (next, awake) = Lwt.wait(); Lwt.async(() => { let body = reqd |> H2.Reqd.request_body; let body_str = ref(""); let on_eof = () => Lwt.wakeup_later(awake, Some(body_str^)); let rec on_read = (request_data, ~off, ~len) => { let read = Bigstringaf.substring(~off, ~len, request_data); body_str := body_str^ ++ read; H2.Body.schedule_read(body, ~on_read, ~on_eof); }; H2.Body.schedule_read(body, ~on_read, ~on_eof); Lwt.return_unit; }); next; }; ================================================ FILE: lwt-unix-httpaf/client_http.re ================================================ open Lwt.Infix; let send: (~config: Httpaf.Config.t=?, Httpkit.Request.t) => Lwt_result.t( (Httpaf.Response.t, Httpaf.Body.t([ | `read])), [> | `Connection_error(Httpaf.Client_connection.error)], ) = (~config=Httpaf.Config.default, req) => { let uri = Httpkit.Request.uri(req); let response_handler = (notify_response_received, response, response_body) => { Logs.debug(m => m("Handling response...")); Lwt.wakeup_later( notify_response_received, (response, response_body) |> Lwt_result.return, ); }; let error_handler = (notify_response_received, error) => { Logs.debug(m => m("Handling errors...")); Lwt.wakeup_later( notify_response_received, `Connection_error(error) |> Lwt_result.fail, ); }; let host = Uri.host_with_default(uri); let port = switch (Uri.port(uri)) { | None => "80" | Some(number) => string_of_int(number) }; Logs.debug(m => m("Getting address for %s:%s", host, port)); Lwt_unix.getaddrinfo(host, port, [Unix.(AI_FAMILY(PF_INET))]) >>= ( addresses => { let socket_addr = List.hd(addresses).Unix.ai_addr; let socket = Lwt_unix.socket(Unix.PF_INET, Unix.SOCK_STREAM, 0); Logs.debug(m => m("Opening socket to %s:%s", host, port)); Lwt_unix.connect(socket, socket_addr) >>= ( () => { let (response_received, notify_response_received) = Lwt.wait(); let response_handler = response_handler(notify_response_received); let error_handler = error_handler(notify_response_received); let write_body = request_body => { switch (Httpkit.Request.body(req)) { | None => () | Some(str) => Httpaf.Body.write_string(request_body, str) }; Httpaf.Body.close_writer(request_body); Logs.debug(m => m("Request sent. Awaiting for response...")); response_received >>= (x => x); }; let request = Client_request.of_httpkit_request(req); Logs.debug(m => m("Sending request: \n\n%s", req |> Httpkit.Request.to_string) ); Httpaf_lwt_unix.Client.request( ~config, ~error_handler, ~response_handler, socket, request, ) |> write_body; } ); } ); }; ================================================ FILE: lwt-unix-httpaf/client_https.re ================================================ open Lwt.Infix; type client_security = [ | `No_authentication ]; let send: ( ~config: Httpaf.Config.t=?, ~client: client_security=?, Httpkit.Request.t ) => Lwt_result.t( (Httpaf.Response.t, Httpaf.Body.t([ | `read])), [> | `Connection_error(Httpaf.Client_connection.error)], ) = (~config=Httpaf.Config.default, ~client as _=`No_authentication, req) => { let uri = Httpkit.Request.uri(req); let response_handler = (notify_response_received, response, response_body) => { Logs.debug(m => m("Handling response...")); Lwt.wakeup_later( notify_response_received, (response, response_body) |> Lwt_result.return, ); }; let error_handler = (notify_response_received, error) => { Logs.debug(m => m("Handling errors...")); Lwt.wakeup_later( notify_response_received, `Connection_error(error) |> Lwt_result.fail, ); }; let host = Uri.host_with_default(uri); let port = switch (Uri.port(uri)) { | None => "443" | Some(number) => string_of_int(number) }; Lwt_unix.getaddrinfo(host, port, [Unix.(AI_FAMILY(PF_INET))]) >>= ( addresses => { Logs.debug(m => m("Got address...")); let socket_addr = List.hd(addresses).Unix.ai_addr; let socket = Lwt_unix.socket(Unix.PF_INET, Unix.SOCK_STREAM, 0); Lwt_unix.connect(socket, socket_addr) >>= ( () => { Logs.debug(m => m("Opened socket...")); let (response_received, notify_response_received) = Lwt.wait(); let response_handler = response_handler(notify_response_received); let error_handler = error_handler(notify_response_received); let write_body = request_body => { Logs.debug(m => m("Writing body...")); switch (Httpkit.Request.body(req)) { | None => () | Some(str) => Httpaf.Body.write_string(request_body, str) }; Httpaf.Body.close_writer(request_body); response_received >>= (x => x); }; let request = Client_request.of_httpkit_request(req); Httpaf_lwt_unix.Client.SSL.request( ~config, ~error_handler, ~response_handler, socket, request ) |> write_body } ); } ); }; ================================================ FILE: lwt-unix-httpaf/client_request.re ================================================ let of_httpkit_request = req => { Httpkit.( Httpaf.Request.create( ~headers=req |> Request.headers |> Httpaf.Headers.of_list, req |> Request.meth, req |> Request.path, ) ); }; let to_httpkit_request = (~body, ~uri, req) => { Httpkit.Request.create( ~headers=req.Httpaf.Request.headers |> Httpaf.Headers.to_list, ~body= switch (body) { | None => "" | Some(b) => b }, req.meth, uri, ); }; ================================================ FILE: lwt-unix-httpaf/client_response.re ================================================ let body: ((Httpaf.Response.t, Httpaf.Body.t([ | `read]))) => Lwt_result.t(string, 'b) = ((_response, body)) => { open Lwt.Infix; let buffer = Buffer.create(2048); Logs.debug(m => m("Prepared buffer for response body...")); let (next, wakeup) = Lwt.wait(); Lwt.async(() => { let rec read_response = () => Httpaf.Body.schedule_read( body, ~on_eof= () => Lwt.wakeup_later(wakeup, Ok(Buffer.contents(buffer))), ~on_read= (response_fragment, ~off, ~len) => { let response_fragment_string = Bytes.create(len); Lwt_bytes.blit_to_bytes( response_fragment, off, response_fragment_string, 0, len, ); Buffer.add_bytes(buffer, response_fragment_string); read_response(); }, ); read_response() |> Lwt.return; }) |> ignore; next >>= Lwt_result.lift; }; ================================================ FILE: lwt-unix-httpaf/dune ================================================ (library (name httpkit_lwt_unix_httpaf) (public_name httpkit-lwt-unix-httpaf) (libraries httpkit httpaf httpaf-lwt-unix lwt lwt.unix uri logs logs.lwt bigstringaf)) ================================================ FILE: lwt-unix-httpaf/httpkit_lwt_unix_httpaf.re ================================================ module Method = Httpkit.Method; module Status = Httpkit.Status; module Request = Httpkit.Request; module Server = { include Httpkit.Server; module Http = Server_http; }; module Client = { module Https = Client_https; module Http = Client_http; module Response = Client_response; }; ================================================ FILE: lwt-unix-httpaf/server_http.re ================================================ open Lwt.Infix; let make_request_handler: ( ~uri: Uri.t, ~handler: Httpkit.Server.handler, ~closer: unit => unit, Unix.sockaddr ) => Httpaf.Server_connection.request_handler = (~uri, ~handler, ~closer, _client, reqd) => { let req = reqd |> Httpaf.Reqd.request; Logs.debug(m => m("Handling request...")); let respond = (~headers=?, status, content) => { let headers = ( switch (headers) { | None => [] | Some(hs) => hs } ) @ [("content-length", content |> String.length |> string_of_int)] |> Httpaf.Headers.of_list; let res = Httpaf.Response.create(status |> Httpaf.Status.of_code, ~headers); Httpaf.Reqd.respond_with_string(reqd, res, content); }; Server_request.read_body(reqd) >|= ( body => { let uri = Uri.with_path(uri, req.target); let req = Client_request.to_httpkit_request(~uri, ~body, req); let () = handler(req, respond, closer); (); } ) |> ignore; }; let error_handler = (_client, ~request as _=?, err, _get) => { Logs.err(m => m( "Something went wrong! %s", switch (err) { | `Bad_gateway => "Bad gateway" | `Bad_request => "Bad request" | `Internal_server_error => "Internal_server_error" | `Exn(exn) => Printexc.to_string(exn) }, ) ); (); }; let listen: ( ~address: [ | `Loopback | `Any | `Of_string(string)]=?, ~port: int, ~on_start: (~hoststring: string) => unit, ~handler: Httpkit.Server.handler ) => Lwt.t(unit) = (~address=`Any, ~port, ~on_start, ~handler) => { let host = switch (address) { | `Loopback => "127.0.0.1" | `Any => "0.0.0.0" | `Of_string(str) => str }; let uri = Uri.make(~scheme="http", ~host, ~port, ()); let (forever, awaker) = Lwt.wait(); let closer = () => Lwt.wakeup_later(awaker, ()); let address = switch (address) { | `Loopback => Unix.inet_addr_loopback | `Any => Unix.inet_addr_any | `Of_string(str) => Unix.inet_addr_of_string(str) }; let listening_address = Unix.(ADDR_INET(address, port)); let connection_handler = Httpaf_lwt_unix.Server.create_connection_handler( ~config=Httpaf.Config.default, ~request_handler=make_request_handler(~uri, ~handler, ~closer), ~error_handler, ); Lwt_io.establish_server_with_client_socket( listening_address, connection_handler, ) >|= (_ => on_start(~hoststring=Uri.to_string(uri))) |> ignore; forever; }; ================================================ FILE: lwt-unix-httpaf/server_request.re ================================================ let read_body = reqd => { let (next, awake) = Lwt.wait(); Lwt.async(() => { let body = reqd |> Httpaf.Reqd.request_body; let body_str = ref(""); let on_eof = () => Lwt.wakeup_later(awake, Some(body_str^)); let rec on_read = (request_data, ~off, ~len) => { let read = Bigstringaf.substring(~off, ~len, request_data); body_str := body_str^ ++ read; Httpaf.Body.schedule_read(body, ~on_read, ~on_eof); }; Httpaf.Body.schedule_read(body, ~on_read, ~on_eof); Lwt.return_unit; }); next; }; ================================================ FILE: src/dune ================================================ (library (name httpkit) (public_name httpkit) (libraries httpaf h2 uri logs)) ================================================ FILE: src/httpkit.re ================================================ module Method = H2.Method; module Status = H2.Status; module Request = { type t = { meth: Method.t, uri: Uri.t, body: option(string), headers: list((string, string)), }; let body = t => t.body; let content_length = t => switch (t.body) { | None => 0 | Some(body) => String.length(body) }; let meth = t => t.meth; let uri = t => t.uri; let path = t => t.uri |> Uri.path_and_query; let headers = t => t.headers; let host = t => t.uri |> Uri.host_with_default; let scheme = t => switch (t |> uri |> Uri.scheme) { | None => "" | Some(s) => s }; let create = (~headers=[], ~body="", meth, uri) => { let host = Uri.host_with_default(uri); let content_length = body |> String.length |> string_of_int; let headers = [("host", host), ("content-length", content_length)] @ (headers |> List.map(((k, v)) => (k |> String.lowercase_ascii, v))); let body = switch (body) { | "" => None | _ => Some(body) }; {meth, uri, headers, body}; }; let to_string = req => { (req.meth |> H2.Method.to_string) ++ " " ++ (req.uri |> Uri.path) ++ "\n" ++ (req.headers |> H2.Headers.of_list |> H2.Headers.to_string) ++ ( switch (req.body) { | None => "" | Some(s) => "\n\n" ++ s } ); }; }; module Server = { type replier = (~headers: list((string, string))=?, int, string) => unit; type closer = unit => unit; type handler = (Request.t, replier, closer) => unit; };