Skip to content

fix(http): preserve content-type on body-bearing httpsig replies - #11

Open
xylophonez wants to merge 1 commit into
candidate/fast-arweave-scheduler-1from
fix/http-reply-content-type
Open

xylophonez wants to merge 1 commit into
candidate/fast-arweave-scheduler-1from
fix/http-reply-content-type

Conversation

@xylophonez

Copy link
Copy Markdown
Owner

Problem

The httpsig@1.0 branch of hb_http:encode_reply/4 returns only the headers of the encoded message (hb_maps:without([<<"body">>], EncMessage, Opts)), discarding the base headers computed at the top of the function that carry the reply's codec and media-type metadata. A successful reply with a non-empty body can therefore leave the node with a content-length but no content-type.

This was observed against a deployed peer: a 200 compute result selected through the legacy /compute/<field> interface arrived with AO result metadata and a correct content length, but no Content-Type. Without the header, clients (including browsers) cannot reliably choose JSON decoding for scalar results, and downstream re-encoders may serialize the entire AO envelope instead of forwarding the selected wire body. dev_process:compute/3 returns state/result messages without declaring a media type, so nothing on the path to the wire ever labels the body.

Repro

  • Captured wire evidence: a successful response with a non-empty compute body, matching content-length, and no content-type header.
  • In-module: encode_reply(200, #{}, #{ <<"body">> => <<"5514001">> }, Opts) previously returned a header map with no content-type.

Fix

Enforce at the reply boundary that any non-empty body is labeled with a media type, via a new ensure_content_type/4 applied in the httpsig@1.0 branch:

  • Headers produced by the codec always take precedence: an explicit content-type set by the device, or generated by the encoder (e.g. multipart/form-data bodies), is forwarded untouched.
  • When absent, the codec's default content-type (already computed as part of BaseHdrs) is added.
  • When the codec declares no default (as httpsig@1.0 does not), the fallback is application/octet-stream.
  • Bodyless replies are returned unmodified, so they do not advertise a media type for an entity that does not exist.

Relationship to 47e5014 ("fix: limit codec response headers")

That commit, on a newer branch, removed the generic (binary-codec) branch's behavior of copying all top-level scalar message fields into response headers, limiting those replies to the base headers (codec-device and content-type). This change is aligned with it in spirit and does not reintroduce what it removed: it adds only the single content-type header, only on body-bearing httpsig@1.0 replies that lack one, and it does not touch the generic branch that 47e5014 rewrote, so the two compose cleanly on a rebase.

Tests

  • New: hb_http:reply_content_type_preserved_test asserts that (1) a device-declared content-type is preserved on the wire, (2) a body-bearing reply without one receives the fallback media type, and (3) a bodyless reply gains no content-type.
  • Full hb_http eunit suite passes: rebar3 eunit --module=hb_http — all 19 tests, including the HTTP round-trip tests (simple_ao_resolve_*, nested_signed_bundle_over_http_test, ans104_wasm_test, index_test, parallel_request_test).

The httpsig@1.0 branch of encode_reply/4 returned only the headers of the
encoded message, discarding the base headers that carry the reply's media
type metadata. A successful reply with a non-empty body (for example, a
compute result selected through the legacy /compute/<field> interface)
could therefore reach the wire with a content-length but no content-type,
leaving peers and browsers to guess at the body's serialization.

Ensure at the reply boundary that any non-empty body is labeled: keep the
codec's own headers when they already declare a media type (for example,
multipart bodies), otherwise add the codec's default content-type from the
base headers, falling back to application/octet-stream for codecs that do
not define one. Bodyless replies are left untouched so they do not
advertise a media type for an entity that does not exist.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant