Skip to content

Commit 5f1e72a

Browse files
rbjorklindinosaure
authored andcommitted
add OCaml httpun using eio
1 parent b749f1f commit 5f1e72a

File tree

6 files changed

+214
-0
lines changed

6 files changed

+214
-0
lines changed

frameworks/OCaml/httpun/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# httpun Benchmarking Test
2+
3+
### Test Type Implementation Source Code
4+
5+
* [JSON](httpun_unix.ml)
6+
* [PLAINTEXT](httpun_unix.ml)
7+
8+
## Important Libraries
9+
The tests were run with:
10+
* [HTTP library](https://github.com/anmonteiro/httpun)
11+
* [Concurrency library](https://github.com/ocaml-multicore/eio)
12+
13+
## Test URLs
14+
### JSON
15+
16+
http://localhost:8080/json
17+
18+
### PLAINTEXT
19+
20+
http://localhost:8080/plaintext
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"framework": "httpun",
3+
"tests": [
4+
{
5+
"default": {
6+
"json_url": "/json",
7+
"plaintext_url": "/plaintext",
8+
"port": 8080,
9+
"approach": "Realistic",
10+
"classification": "Platform",
11+
"database": "None",
12+
"framework": "None",
13+
"language": "OCaml",
14+
"flavor": "None",
15+
"orm": "None",
16+
"platform": "httpun",
17+
"webserver": "None",
18+
"os": "Linux",
19+
"database_os": "Linux",
20+
"display_name": "httpun",
21+
"notes": "",
22+
"versus": "None"
23+
}
24+
}
25+
]
26+
}

frameworks/OCaml/httpun/dune

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
(executable
2+
(name httpun_eio)
3+
(libraries httpun httpun-eio eio eio_main eio_linux yojson unix))

frameworks/OCaml/httpun/dune-project

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(lang dune 3.9)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# -*- mode: dockerfile -*-
2+
3+
# https://github.com/rbjorklin/techempower-ocaml-image
4+
# Use pre-built image with all dependencies for faster test times
5+
FROM rbjorklin/techempower-ocaml-image:5.3.0-4debebb5
6+
7+
# https://caml.inria.fr/pub/docs/manual-ocaml/libref/Gc.html
8+
# https://linux.die.net/man/1/ocamlrun
9+
# https://blog.janestreet.com/memory-allocator-showdown/
10+
ENV OCAMLRUNPARAM="a=2,o=240"
11+
12+
COPY . /app
13+
14+
WORKDIR /app
15+
16+
RUN \
17+
eval $(opam env) && \
18+
dune build --release httpun_eio.exe
19+
20+
EXPOSE 8080
21+
22+
ENTRYPOINT ["_build/default/httpun_eio.exe"]

frameworks/OCaml/httpun/httpun_eio.ml

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
open Httpun
2+
open Httpun_eio
3+
open Eio.Std
4+
5+
(* Heavily inspired by the original httpaf implementation in this repo with eio additions from:
6+
* https://github.com/ocaml-multicore/eio/blob/8f7f82d2c12076af8e9b8b365c58ebadaa963b8c/examples/net/server.ml
7+
* https://github.com/ocaml-multicore/eio/blob/8f7f82d2c12076af8e9b8b365c58ebadaa963b8c/examples/net/main.ml
8+
* https://github.com/anmonteiro/httpun/blob/37fdcd8fd09dc851acbf224c4ec1cb8681942f04/examples/lib/httpun_examples.ml#L115-L125
9+
* https://github.com/anmonteiro/httpun/blob/37fdcd8fd09dc851acbf224c4ec1cb8681942f04/examples/eio/eio_connect_server.ml
10+
*)
11+
12+
(* Dates *)
13+
14+
let get_date () = Unix.(gettimeofday () |> gmtime)
15+
16+
let dow = function
17+
| 0 -> "Sun"
18+
| 1 -> "Mon"
19+
| 2 -> "Tue"
20+
| 3 -> "Wed"
21+
| 4 -> "Thu"
22+
| 5 -> "Fri"
23+
| _ -> "Sat"
24+
25+
let month = function
26+
| 0 -> "Jan"
27+
| 1 -> "Feb"
28+
| 2 -> "Mar"
29+
| 3 -> "Apr"
30+
| 4 -> "May"
31+
| 5 -> "Jun"
32+
| 6 -> "Jul"
33+
| 7 -> "Aug"
34+
| 8 -> "Sep"
35+
| 9 -> "Oct"
36+
| 10 -> "Nov"
37+
| _ -> "Dec"
38+
39+
let date () =
40+
let d = get_date () in
41+
(* Wed, 17 Apr 2013 12:00:00 GMT *)
42+
Format.sprintf "%s, %02d %s %4d %02d:%02d:%02d GMT" (dow d.tm_wday) d.tm_mday
43+
(month d.tm_mon) (1900 + d.tm_year) d.tm_hour d.tm_min d.tm_sec
44+
45+
let memo_date = ref @@ date ()
46+
47+
let refresh_date () =
48+
let f _ =
49+
memo_date := date ();
50+
ignore @@ Unix.alarm 1
51+
in
52+
(ignore @@ Sys.(signal sigalrm (Signal_handle f)));
53+
f ()
54+
55+
(* HTTP *)
56+
57+
let request_handler (_ : Eio.Net.Sockaddr.stream) { Gluten.reqd; _ } =
58+
let req = Reqd.request reqd in
59+
match req.target with
60+
| "/json" ->
61+
let obj = `Assoc [ ("message", `String "Hello, World!") ] in
62+
let payload = Yojson.to_string obj in
63+
let headers =
64+
Headers.of_rev_list
65+
[
66+
("content-length", string_of_int @@ String.length payload);
67+
("content-type", "application/json");
68+
("server", "httpun");
69+
("date", !memo_date);
70+
]
71+
in
72+
let rsp = Response.create ~headers `OK in
73+
Reqd.respond_with_string reqd rsp payload
74+
| "/plaintext" ->
75+
let payload = "Hello, World!" in
76+
let headers =
77+
Headers.of_rev_list
78+
[
79+
("content-length", string_of_int @@ String.length payload);
80+
("content-type", "text/plain");
81+
("server", "httpun");
82+
("date", !memo_date);
83+
]
84+
in
85+
let rsp = Response.create ~headers `OK in
86+
Reqd.respond_with_string reqd rsp payload
87+
| _ ->
88+
let moo = "m00." in
89+
let headers =
90+
Headers.of_list
91+
[ ("content-length", string_of_int @@ String.length moo) ]
92+
in
93+
let rsp = Response.create ~headers `OK in
94+
Reqd.respond_with_string reqd rsp moo
95+
96+
let error_handler (_ : Eio.Net.Sockaddr.stream) ?request:_ error start_response
97+
=
98+
let response_body = start_response Headers.empty in
99+
(match error with
100+
| `Exn exn ->
101+
Body.Writer.write_string response_body (Printexc.to_string exn);
102+
Body.Writer.write_string response_body "\n"
103+
| #Status.standard as error ->
104+
Body.Writer.write_string response_body
105+
(Status.default_reason_phrase error));
106+
Body.Writer.close response_body
107+
108+
let () =
109+
let domain_count = Stdlib.Domain.recommended_domain_count () in
110+
Printf.eprintf "Detected %d cores\n" domain_count;
111+
let ulimit_n =
112+
Unix.open_process_in "ulimit -n" |> input_line |> int_of_string
113+
in
114+
Printf.eprintf "Detected %d max open files\n" ulimit_n;
115+
let somaxconn =
116+
Stdlib.open_in "/proc/sys/net/core/somaxconn"
117+
|> Stdlib.input_line |> Stdlib.int_of_string
118+
in
119+
Printf.eprintf "Detected %d somaxconn\n" somaxconn;
120+
refresh_date ();
121+
let backlog = Stdlib.min ulimit_n somaxconn in
122+
123+
Eio_main.run @@ fun env ->
124+
Switch.run @@ fun sw ->
125+
(* https://github.com/ocaml-multicore/eio/tree/main?tab=readme-ov-file#executor-pool *)
126+
let dm = Eio.Stdenv.domain_mgr env in
127+
128+
let addr = `Tcp (Eio.Net.Ipaddr.V4.any, 8080) in
129+
let listening_socket =
130+
Eio.Net.listen ~sw env#net addr ~backlog ~reuse_addr:true
131+
in
132+
133+
let connection_handler flow addr =
134+
Server.create_connection_handler ~request_handler ~error_handler ~sw addr
135+
flow
136+
in
137+
let run listening_socket =
138+
Eio.Net.run_server ~additional_domains:(dm, domain_count) listening_socket
139+
~max_connections:backlog connection_handler
140+
~on_error:(traceln "Error handling connection: %a" Fmt.exn)
141+
in
142+
run listening_socket

0 commit comments

Comments
 (0)