Skip to content

[Bug][internal] HEAD requests fail with :unexpected_streaming_message #8

Description

@gsmlg

Requesting repo: gsmlg-dev/http_fetch on branch main

HTTP.fetch/2 with method: "HEAD" returns an error tuple {:error, :unexpected_streaming_message} instead of a %HTTP.Response{} with the headers.

Root cause (observed)

In lib/http.ex the streaming response handler (handle_streaming_with_headers/3 and related) matches on a fixed set of :http messages from :httpc — {:http_response, ...}, :stream, :stream_end, and :http_error. The catch-all clause throws :unexpected_streaming_message.

A HEAD response is special: :httpc delivers the status line and headers but never sends a body and never sends a :stream_end message (the body is suppressed by the HTTP spec). For the local Go test server at /head, the observed message sequence is just the status line and headers followed by nothing — the handler sits in its receive until either the body message or the timeout fires, then throws :unexpected_streaming_message.

Reproduction

Using the vendored Go test server (priv/test_server/main.go), which has a /head route that returns 200 with a Content-Type: application/json header and no body:

resp =
  "http://127.0.0.1:<port>/head"
  |> HTTP.fetch(method: "HEAD")
  |> HTTP.Promise.await()

# Expected: %HTTP.Response{status: 200, body: "", stream: nil}
# Actual:   {:error, :unexpected_streaming_message}

Impact

  • HTTP.fetch/2 advertises support for all seven HTTP verbs in its docstring, but HEAD is broken.
  • The streaming handler in lib/http.ex is the source of the throw, but the underlying expectation is that :httpc will eventually deliver a :stream or :stream_end, which it does not for HEAD.

Suggested direction (not requesting a specific implementation)

  1. In the streaming handlers in lib/http.ex, detect the HEAD case and treat a quiescent receive after the headers (i.e. no further :http message arrives within a short window) as a successful empty body.
  2. Alternatively, switch HEAD to the non-streaming code path by checking the method before deciding to subscribe to stream messages.

Environment

  • Elixir 1.18+
  • Erlang/OTP 28

Severity

nice-to-have (HEAD is rarely used directly; most users hitting this will work around it by issuing a GET and ignoring the body)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    internal requestInternal request originating from another repo in the gsmlg org

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions