From 307acd365a58f12ddec2fa97859aedaf7f015d8e Mon Sep 17 00:00:00 2001 From: williamthome Date: Wed, 21 Oct 2026 12:21:04 -0300 Subject: [PATCH 1/2] Answer conditional GETs and only GET, HEAD and POST on Roadrunner --- docs/content/servers/roadrunner.md | 36 +++++++++++++------ .../src/roadrunner_probe_app.erl | 12 ++++--- .../src/roadrunner_probe_handler.erl | 22 ++++++++---- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/docs/content/servers/roadrunner.md b/docs/content/servers/roadrunner.md index fd39a45..3f6afda 100644 --- a/docs/content/servers/roadrunner.md +++ b/docs/content/servers/roadrunner.md @@ -7,7 +7,7 @@ breadcrumbs: false **Language:** Erlang · [View source on GitHub](https://github.com/MDA2AV/Http11Probe/tree/main/src/Servers/RoadrunnerServer) -[Roadrunner](https://github.com/arizona-framework/roadrunner) is a pure-Erlang HTTP/1.1, HTTP/2, HTTP/3 and WebSocket server from the Arizona framework. This entry runs roadrunner 0.10.2 from Hex as an OTP release: one HTTP/1.1 listener on port 8080 with roadrunner's default options, and a handler that serves `/echo`, `/cookie`, and a catch-all for every other path. HEAD needs no route of its own, since roadrunner answers it like GET without the body. +[Roadrunner](https://github.com/arizona-framework/roadrunner) is a pure-Erlang HTTP/1.1, HTTP/2, HTTP/3 and WebSocket server from the Arizona framework. This entry runs roadrunner 0.10.2 from Hex as an OTP release: one HTTP/1.1 listener on port 8080 with roadrunner's default options plus its `roadrunner_etag` conditional-request middleware, and a handler that serves `/echo`, `/cookie`, and a catch-all for every other path. Each route answers GET, HEAD and POST, and roadrunner's router answers any other method with 405 and an `Allow` header. HEAD needs no route of its own, since roadrunner answers it like GET without the body. ## Dockerfile @@ -73,14 +73,18 @@ ENTRYPOINT ["/app/bin/roadrunner_probe", "foreground"] -export([start/2, stop/1]). -export([init/1]). -%% Starts one HTTP/1.1 listener on port 8080 with roadrunner's defaults. -%% The listener runs under roadrunner's own supervisor; this empty -%% supervisor is only the application's top process. +%% Starts one HTTP/1.1 listener on port 8080 with roadrunner's defaults, +%% plus `roadrunner_etag`, roadrunner's conditional-request middleware: it +%% gives each GET/HEAD 200 a weak ETag and answers a matching +%% If-None-Match with 304. The listener runs under roadrunner's own +%% supervisor; this empty supervisor is only the application's top +%% process. start(_StartType, _StartArgs) -> {ok, Sup} = supervisor:start_link(?MODULE, []), {ok, _} = roadrunner:start_listener(probe, #{ port => 8080, - routes => roadrunner_probe_handler:routes() + routes => roadrunner_probe_handler:routes(), + middlewares => [roadrunner_etag] }), {ok, Sup}. @@ -100,16 +104,26 @@ init([]) -> -export([routes/0]). -export([handle/1]). -%% `/` and every other path share one catch-all handler. HEAD needs no -%% clause of its own: roadrunner answers it like GET without the body. +%% `/` and every other path share one catch-all handler. Every route +%% answers GET, HEAD and POST; roadrunner's router answers any other +%% method with 405 and an `Allow` header. HEAD needs no clause of its +%% own: roadrunner answers it like GET without the body. routes() -> [ - {~"/echo", ?MODULE, echo}, - {~"/cookie", ?MODULE, cookie}, - {~"/", ?MODULE, root}, - {~"/*path", ?MODULE, root} + route(~"/echo", echo), + route(~"/cookie", cookie), + route(~"/", root), + route(~"/*path", root) ]. +route(Path, State) -> + #{ + path => Path, + handler => ?MODULE, + state => State, + methods => [~"GET", ~"HEAD", ~"POST"] + }. + handle(Req) -> handle(roadrunner_req:state(Req), Req). diff --git a/src/Servers/RoadrunnerServer/src/roadrunner_probe_app.erl b/src/Servers/RoadrunnerServer/src/roadrunner_probe_app.erl index 33bb62d..1b807d0 100644 --- a/src/Servers/RoadrunnerServer/src/roadrunner_probe_app.erl +++ b/src/Servers/RoadrunnerServer/src/roadrunner_probe_app.erl @@ -5,14 +5,18 @@ -export([start/2, stop/1]). -export([init/1]). -%% Starts one HTTP/1.1 listener on port 8080 with roadrunner's defaults. -%% The listener runs under roadrunner's own supervisor; this empty -%% supervisor is only the application's top process. +%% Starts one HTTP/1.1 listener on port 8080 with roadrunner's defaults, +%% plus `roadrunner_etag`, roadrunner's conditional-request middleware: it +%% gives each GET/HEAD 200 a weak ETag and answers a matching +%% If-None-Match with 304. The listener runs under roadrunner's own +%% supervisor; this empty supervisor is only the application's top +%% process. start(_StartType, _StartArgs) -> {ok, Sup} = supervisor:start_link(?MODULE, []), {ok, _} = roadrunner:start_listener(probe, #{ port => 8080, - routes => roadrunner_probe_handler:routes() + routes => roadrunner_probe_handler:routes(), + middlewares => [roadrunner_etag] }), {ok, Sup}. diff --git a/src/Servers/RoadrunnerServer/src/roadrunner_probe_handler.erl b/src/Servers/RoadrunnerServer/src/roadrunner_probe_handler.erl index e31ca29..a9ecbd8 100644 --- a/src/Servers/RoadrunnerServer/src/roadrunner_probe_handler.erl +++ b/src/Servers/RoadrunnerServer/src/roadrunner_probe_handler.erl @@ -4,16 +4,26 @@ -export([routes/0]). -export([handle/1]). -%% `/` and every other path share one catch-all handler. HEAD needs no -%% clause of its own: roadrunner answers it like GET without the body. +%% `/` and every other path share one catch-all handler. Every route +%% answers GET, HEAD and POST; roadrunner's router answers any other +%% method with 405 and an `Allow` header. HEAD needs no clause of its +%% own: roadrunner answers it like GET without the body. routes() -> [ - {~"/echo", ?MODULE, echo}, - {~"/cookie", ?MODULE, cookie}, - {~"/", ?MODULE, root}, - {~"/*path", ?MODULE, root} + route(~"/echo", echo), + route(~"/cookie", cookie), + route(~"/", root), + route(~"/*path", root) ]. +route(Path, State) -> + #{ + path => Path, + handler => ?MODULE, + state => State, + methods => [~"GET", ~"HEAD", ~"POST"] + }. + handle(Req) -> handle(roadrunner_req:state(Req), Req). From 7573294cda42b6561b8867d955867d85259af41e Mon Sep 17 00:00:00 2001 From: williamthome Date: Wed, 21 Oct 2026 20:42:01 -0300 Subject: [PATCH 2/2] Update the Roadrunner entry to 0.10.4 --- docs/content/servers/roadrunner.md | 4 ++-- src/Servers/RoadrunnerServer/rebar.config | 2 +- src/Servers/RoadrunnerServer/rebar.lock | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/content/servers/roadrunner.md b/docs/content/servers/roadrunner.md index 3f6afda..08703d0 100644 --- a/docs/content/servers/roadrunner.md +++ b/docs/content/servers/roadrunner.md @@ -7,7 +7,7 @@ breadcrumbs: false **Language:** Erlang · [View source on GitHub](https://github.com/MDA2AV/Http11Probe/tree/main/src/Servers/RoadrunnerServer) -[Roadrunner](https://github.com/arizona-framework/roadrunner) is a pure-Erlang HTTP/1.1, HTTP/2, HTTP/3 and WebSocket server from the Arizona framework. This entry runs roadrunner 0.10.2 from Hex as an OTP release: one HTTP/1.1 listener on port 8080 with roadrunner's default options plus its `roadrunner_etag` conditional-request middleware, and a handler that serves `/echo`, `/cookie`, and a catch-all for every other path. Each route answers GET, HEAD and POST, and roadrunner's router answers any other method with 405 and an `Allow` header. HEAD needs no route of its own, since roadrunner answers it like GET without the body. +[Roadrunner](https://github.com/arizona-framework/roadrunner) is a pure-Erlang HTTP/1.1, HTTP/2, HTTP/3 and WebSocket server from the Arizona framework. This entry runs roadrunner 0.10.4 from Hex as an OTP release: one HTTP/1.1 listener on port 8080 with roadrunner's default options plus its `roadrunner_etag` conditional-request middleware, and a handler that serves `/echo`, `/cookie`, and a catch-all for every other path. Each route answers GET, HEAD and POST, and roadrunner's router answers any other method with 405 and an `Allow` header. HEAD needs no route of its own, since roadrunner answers it like GET without the body. ## Dockerfile @@ -33,7 +33,7 @@ ENTRYPOINT ["/app/bin/roadrunner_probe", "foreground"] {erl_opts, [debug_info]}. {deps, [ - {roadrunner, "0.10.2"} + {roadrunner, "0.10.4"} ]}. {relx, [ diff --git a/src/Servers/RoadrunnerServer/rebar.config b/src/Servers/RoadrunnerServer/rebar.config index a52526e..4e8e9f7 100644 --- a/src/Servers/RoadrunnerServer/rebar.config +++ b/src/Servers/RoadrunnerServer/rebar.config @@ -1,7 +1,7 @@ {erl_opts, [debug_info]}. {deps, [ - {roadrunner, "0.10.2"} + {roadrunner, "0.10.4"} ]}. {relx, [ diff --git a/src/Servers/RoadrunnerServer/rebar.lock b/src/Servers/RoadrunnerServer/rebar.lock index e2acef5..2e7f15a 100644 --- a/src/Servers/RoadrunnerServer/rebar.lock +++ b/src/Servers/RoadrunnerServer/rebar.lock @@ -1,11 +1,11 @@ {"1.2.0", -[{<<"roadrunner">>,{pkg,<<"roadrunner">>,<<"0.10.2">>},0}, +[{<<"roadrunner">>,{pkg,<<"roadrunner">>,<<"0.10.4">>},0}, {<<"telemetry">>,{pkg,<<"telemetry">>,<<"1.4.2">>},1}]}. [ {pkg_hash,[ - {<<"roadrunner">>, <<"9203479DB306FD881A52246BA24BFE515C5C3B89DB1A62DF3CDD3452176FD51E">>}, + {<<"roadrunner">>, <<"34D4465166DE01B1D6D5098AFBCA27C8E8A8F0C05C98659A5963133A1B59EC06">>}, {<<"telemetry">>, <<"A0CB522801DFFB1C49FE6E30561BADFFC7B6D0E180DB1300DF759FAA22062855">>}]}, {pkg_hash_ext,[ - {<<"roadrunner">>, <<"19B1B3DC0F6FF3CCFFBBB0F80E780A49D7BDC0D82EEE66610EA777DE319D1C19">>}, + {<<"roadrunner">>, <<"243E23B07DD844F090D2D76FDB0155C1CA1E8EF93571A21A936203794981EDC1">>}, {<<"telemetry">>, <<"928F6495066506077862C0D1646609EED891A4326BEE3126BA54B60AF61FEBB1">>}]} ].