From 3caebfce2b511b6bc0a430a6367fb9bb7c52b446 Mon Sep 17 00:00:00 2001 From: Malcolm Sparks Date: Fri, 17 Apr 2026 15:55:48 +0100 Subject: [PATCH 1/2] fix(http/response): Don't write body on HEAD request RFC 9110 8.6 contains the following paragraph: "A server MAY send a Content-Length header field in a response to a HEAD request (Section 9.3.2); a server MUST NOT send Content-Length in such a response unless its field value equals the decimal number of octets that would have been sent in the content of a response if the same request had used the GET method." When writing a nil body and closing the output stream, Helidon recomputes the length of the body bytes and overwrites any content-length header provided. In the case of a HEAD request, where a content-length header may be provided but where there is no body, this causes the content-length to be reset to 0. This commit adds a special set-head-response! function that avoids writing the body, and calls send() on the server-response instead, as required by Helidon, see https://helidon.io/docs/v4/se/webserver/webserver#anchor-sending-response --- src/s_exp/hirundo/http/response.clj | 6 ++++++ src/s_exp/hirundo/http/routing.clj | 6 ++++-- test/s_exp/hirundo_test.clj | 5 ++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/s_exp/hirundo/http/response.clj b/src/s_exp/hirundo/http/response.clj index 4735d26..a81f5aa 100644 --- a/src/s_exp/hirundo/http/response.clj +++ b/src/s_exp/hirundo/http/response.clj @@ -34,3 +34,9 @@ (set-headers! server-response headers) (set-status! server-response status) (write-body! server-response response body)) + +(defn set-head-response! + [^ServerResponse server-response {:as response :keys [headers status]}] + (set-headers! server-response headers) + (set-status! server-response status) + (.send server-response)) diff --git a/src/s_exp/hirundo/http/routing.clj b/src/s_exp/hirundo/http/routing.clj index 4ea13a2..66496c8 100644 --- a/src/s_exp/hirundo/http/routing.clj +++ b/src/s_exp/hirundo/http/routing.clj @@ -19,10 +19,12 @@ (into-array Handler [(reify Handler (handle [_ server-request server-response] - (let [response (handler (request/ring-request server-request server-response))] + (let [response (handler (request/ring-request server-request server-response)) + head? (= (.method (.prologue server-request)) io.helidon.http.Method/HEAD)] (cond->> response (map? response) - (response/set-response! server-response)))))])))))) + ((if head? response/set-head-response! response/set-response!) + server-response)))))])))))) (defmethod options/set-server-option! :http-handler [^WebServerConfig$Builder builder _ handler options] diff --git a/test/s_exp/hirundo_test.clj b/test/s_exp/hirundo_test.clj index a26159c..b19df66 100644 --- a/test/s_exp/hirundo_test.clj +++ b/test/s_exp/hirundo_test.clj @@ -85,7 +85,10 @@ (is (-> (client/get *endpoint*) :body (= "yes")))) (with-server {:http-handler (fn [req] {:body (java.io.ByteArrayInputStream. (.getBytes "yes"))})} - (is (-> (client/get *endpoint*) :body (= "yes"))))) + (is (-> (client/get *endpoint*) :body (= "yes")))) + + (with-server {:http-handler (fn [req] {:body (java.io.ByteArrayInputStream. (.getBytes "yes"))})} + (is (-> (client/head *endpoint*) :body nil?)))) (deftest resp-map-decoding (with-server {:http-handler (fn [req] From fed1203c10bba77b1b58ee5a73500f52f743d1df Mon Sep 17 00:00:00 2001 From: Malcolm Sparks Date: Fri, 17 Apr 2026 17:49:53 +0100 Subject: [PATCH 2/2] fix(http/response): Preserve Content-Length on 304 Not Modified --- src/s_exp/hirundo/http/response.clj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/s_exp/hirundo/http/response.clj b/src/s_exp/hirundo/http/response.clj index a81f5aa..e9d3824 100644 --- a/src/s_exp/hirundo/http/response.clj +++ b/src/s_exp/hirundo/http/response.clj @@ -33,10 +33,12 @@ [^ServerResponse server-response {:as response :keys [body headers status]}] (set-headers! server-response headers) (set-status! server-response status) - (write-body! server-response response body)) + (if body + (write-body! server-response response body) + (.send server-response))) (defn set-head-response! - [^ServerResponse server-response {:as response :keys [headers status]}] + [^ServerResponse server-response {:keys [headers status]}] (set-headers! server-response headers) (set-status! server-response status) (.send server-response))