Skip to content

add OCaml httpun using eio #9751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions frameworks/OCaml/httpun/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# httpun Benchmarking Test

### Test Type Implementation Source Code

* [JSON](httpun_unix.ml)
* [PLAINTEXT](httpun_unix.ml)

## Important Libraries
The tests were run with:
* [HTTP library](https://github.com/anmonteiro/httpun)
* [Concurrency library](https://github.com/ocaml-multicore/eio)

## Test URLs
### JSON

http://localhost:8080/json

### PLAINTEXT

http://localhost:8080/plaintext
26 changes: 26 additions & 0 deletions frameworks/OCaml/httpun/benchmark_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"framework": "httpun",
"tests": [
{
"default": {
"json_url": "/json",
"plaintext_url": "/plaintext",
"port": 8080,
"approach": "Realistic",
"classification": "Platform",
"database": "None",
"framework": "None",
"language": "OCaml",
"flavor": "None",
"orm": "None",
"platform": "httpun",
"webserver": "None",
"os": "Linux",
"database_os": "Linux",
"display_name": "httpun",
"notes": "",
"versus": "None"
}
}
]
}
3 changes: 3 additions & 0 deletions frameworks/OCaml/httpun/dune
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
(executable
(name httpun_eio)
(libraries httpun httpun-eio eio eio_main eio_linux yojson unix))
1 change: 1 addition & 0 deletions frameworks/OCaml/httpun/dune-project
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(lang dune 3.9)
22 changes: 22 additions & 0 deletions frameworks/OCaml/httpun/httpun.dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# -*- mode: dockerfile -*-

# https://github.com/rbjorklin/techempower-ocaml-image
# Use pre-built image with all dependencies for faster test times
FROM rbjorklin/techempower-ocaml-image:5.3.0-4debebb5

# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html
# https://linux.die.net/man/1/ocamlrun
# https://blog.janestreet.com/memory-allocator-showdown/
ENV OCAMLRUNPARAM="a=2,o=240"

COPY . /app

WORKDIR /app

RUN \
eval $(opam env) && \
dune build --release httpun_eio.exe

EXPOSE 8080

ENTRYPOINT ["_build/default/httpun_eio.exe"]
142 changes: 142 additions & 0 deletions frameworks/OCaml/httpun/httpun_eio.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
open Httpun
open Httpun_eio
open Eio.Std

(* Heavily inspired by the original httpaf implementation in this repo with eio additions from:
* https://github.com/ocaml-multicore/eio/blob/8f7f82d2c12076af8e9b8b365c58ebadaa963b8c/examples/net/server.ml
* https://github.com/ocaml-multicore/eio/blob/8f7f82d2c12076af8e9b8b365c58ebadaa963b8c/examples/net/main.ml
* https://github.com/anmonteiro/httpun/blob/37fdcd8fd09dc851acbf224c4ec1cb8681942f04/examples/lib/httpun_examples.ml#L115-L125
* https://github.com/anmonteiro/httpun/blob/37fdcd8fd09dc851acbf224c4ec1cb8681942f04/examples/eio/eio_connect_server.ml
*)

(* Dates *)

let get_date () = Unix.(gettimeofday () |> gmtime)

let dow = function
| 0 -> "Sun"
| 1 -> "Mon"
| 2 -> "Tue"
| 3 -> "Wed"
| 4 -> "Thu"
| 5 -> "Fri"
| _ -> "Sat"

let month = function
| 0 -> "Jan"
| 1 -> "Feb"
| 2 -> "Mar"
| 3 -> "Apr"
| 4 -> "May"
| 5 -> "Jun"
| 6 -> "Jul"
| 7 -> "Aug"
| 8 -> "Sep"
| 9 -> "Oct"
| 10 -> "Nov"
| _ -> "Dec"

let date () =
let d = get_date () in
(* Wed, 17 Apr 2013 12:00:00 GMT *)
Format.sprintf "%s, %02d %s %4d %02d:%02d:%02d GMT" (dow d.tm_wday) d.tm_mday
(month d.tm_mon) (1900 + d.tm_year) d.tm_hour d.tm_min d.tm_sec

let memo_date = ref @@ date ()

let refresh_date () =
let f _ =
memo_date := date ();
ignore @@ Unix.alarm 1
in
(ignore @@ Sys.(signal sigalrm (Signal_handle f)));
f ()

(* HTTP *)

let request_handler (_ : Eio.Net.Sockaddr.stream) { Gluten.reqd; _ } =
let req = Reqd.request reqd in
match req.target with
| "/json" ->
let obj = `Assoc [ ("message", `String "Hello, World!") ] in
let payload = Yojson.to_string obj in
let headers =
Headers.of_rev_list
[
("content-length", string_of_int @@ String.length payload);
("content-type", "application/json");
("server", "httpun");
("date", !memo_date);
]
in
let rsp = Response.create ~headers `OK in
Reqd.respond_with_string reqd rsp payload
| "/plaintext" ->
let payload = "Hello, World!" in
let headers =
Headers.of_rev_list
[
("content-length", string_of_int @@ String.length payload);
("content-type", "text/plain");
("server", "httpun");
("date", !memo_date);
]
in
let rsp = Response.create ~headers `OK in
Reqd.respond_with_string reqd rsp payload
| _ ->
let moo = "m00." in
let headers =
Headers.of_list
[ ("content-length", string_of_int @@ String.length moo) ]
in
let rsp = Response.create ~headers `OK in
Reqd.respond_with_string reqd rsp moo

let error_handler (_ : Eio.Net.Sockaddr.stream) ?request:_ error start_response
=
let response_body = start_response Headers.empty in
(match error with
| `Exn exn ->
Body.Writer.write_string response_body (Printexc.to_string exn);
Body.Writer.write_string response_body "\n"
| #Status.standard as error ->
Body.Writer.write_string response_body
(Status.default_reason_phrase error));
Body.Writer.close response_body

let () =
let domain_count = Stdlib.Domain.recommended_domain_count () in
Printf.eprintf "Detected %d cores\n" domain_count;
let ulimit_n =
Unix.open_process_in "ulimit -n" |> input_line |> int_of_string
in
Printf.eprintf "Detected %d max open files\n" ulimit_n;
let somaxconn =
Stdlib.open_in "/proc/sys/net/core/somaxconn"
|> Stdlib.input_line |> Stdlib.int_of_string
in
Printf.eprintf "Detected %d somaxconn\n" somaxconn;
refresh_date ();
let backlog = Stdlib.min ulimit_n somaxconn in

Eio_main.run @@ fun env ->
Switch.run @@ fun sw ->
(* https://github.com/ocaml-multicore/eio/tree/main?tab=readme-ov-file#executor-pool *)
let dm = Eio.Stdenv.domain_mgr env in

let addr = `Tcp (Eio.Net.Ipaddr.V4.any, 8080) in
let listening_socket =
Eio.Net.listen ~sw env#net addr ~backlog ~reuse_addr:true
in

let connection_handler flow addr =
Server.create_connection_handler ~request_handler ~error_handler ~sw addr
flow
in
let run listening_socket =
Eio.Net.run_server ~additional_domains:(dm, domain_count) listening_socket
~max_connections:backlog connection_handler
~on_error:(traceln "Error handling connection: %a" Fmt.exn)
in
run listening_socket
Loading