Skip to content

fix: streaming callback returns non-opt StreamingCallbackHttpResponse#96

Merged
lwshang merged 1 commit into
mainfrom
fix/streaming-callback-non-opt-return
Jun 16, 2026
Merged

fix: streaming callback returns non-opt StreamingCallbackHttpResponse#96
lwshang merged 1 commit into
mainfrom
fix/streaming-callback-non-opt-return

Conversation

@lwshang

@lwshang lwshang commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

What

Serving any multi-chunk asset (> MAX_CHUNK_SIZE, 1.9 MB) through the HTTP gateway returned HTTP 503. The fix: http_request_streaming_callback now returns a bare StreamingCallbackHttpResponse instead of opt StreamingCallbackHttpResponse.

Root cause

The HTTP gateway follows the streaming callback via ic-agent, which decodes the reply into a non-optional (StreamingCallbackHttpResponse,). We returned opt ..., and candid cannot coerce wire-type opt T into the expected T (subtype-incomparable), so the gateway failed with AgentError::CandidError ("Candid returned an error") and served 503. The first http_request response decodes fine, so single-chunk assets worked and no existing test exercised the callback decode path.

This means opt was a real bug even though it matches the written HTTP Gateway Protocol spec. The spec text is stale: ic-agent and the SDK reference asset canister's actual wasm both use non-opt (the SDK's hand-written .did says opt but its Rust returns non-opt). A separate docs PR will correct the spec.

Changes

  • Drop Option/opt from the callback return type in three places: the #[query] method, certified-assets.did, and the StreamingStrategy callback func type (define_function!).
  • e2e test (crates/e2e/tests/streaming.rs): generates a ~5 MB (3-chunk) incompressible asset on the fly, deploys it, and asserts it round-trips byte-for-byte through the local gateway. A >2 MB body can only return intact via streaming callbacks, and the gateway validates the certificate before responding — so this also proves certified reassembly.
  • canister-core unit tests: 3-chunk continuation token (the mid-stream case a 2-chunk asset never reaches), non-identity (gzip) multi-chunk streaming, single-chunk assets advertise no streaming strategy, and the callback's unknown-key / unknown-encoding error paths.

Testing

  • cargo test -p canister-core — 104 pass
  • cargo test -p canister candid_interface_compatibility — pass
  • cargo test -p e2e — full suite pass (streaming round-trips through the gateway; previously 503)
  • cargo fmt --check, cargo clippy --workspace --all-targets — clean

🤖 Generated with Claude Code

The HTTP gateway decodes the streaming-callback reply via ic-agent into a
non-optional StreamingCallbackHttpResponse. We returned `opt ...` — which the
written HTTP Gateway Protocol spec specifies, but which the gateway does not
accept: candid cannot coerce wire-type `opt T` into the expected `T`, so the
reply failed to decode and every multi-chunk asset served HTTP 503. Single-chunk
assets skip the callback, so no existing test caught it.

Drop the Option/opt wrapper from the callback return type in the method, the
candid interface, and the StreamingStrategy callback func type.

Add an e2e test that round-trips a ~5 MB (3-chunk) asset through the local
gateway, plus canister-core unit tests covering the multi-chunk streaming paths
(3-chunk continuation token, non-identity encoding, single-chunk no-strategy,
and the callback's unknown-key / unknown-encoding errors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lwshang lwshang marked this pull request as ready for review June 16, 2026 19:24
@lwshang lwshang requested a review from a team as a code owner June 16, 2026 19:24
@lwshang lwshang merged commit 879dcac into main Jun 16, 2026
6 checks passed
@lwshang lwshang deleted the fix/streaming-callback-non-opt-return branch June 16, 2026 19:24
lwshang added a commit to dfinity/developer-docs that referenced this pull request Jun 17, 2026
## Summary

The streaming callback in the HTTP gateway protocol spec must return a
non-opt `StreamingCallbackHttpResponse`. The spec incorrectly wrapped
the return type in `opt`, which no real gateway accepts — candid cannot
coerce wire-type `opt T` into the expected `T`, so serving any
multi-chunk (streamed) asset fails to decode.

Fixed all three occurrences:
- `public/references/http-gateway.did` (downloadable interface)
- `docs/references/http-gateway-protocol-spec.md` — full Canister HTTP
Interface block
- `docs/references/http-gateway-protocol-spec.md` — Response Body
Streaming Interface block

```diff
- callback: func (StreamingToken) -> (opt StreamingCallbackHttpResponse) query;
+ callback: func (StreamingToken) -> (StreamingCallbackHttpResponse) query;
```

Note: only the callback's **return type** changes. The `token : opt
Token` field inside `StreamingCallbackHttpResponse` stays optional — a
null *token* (not a null response) terminates the stream.

## Verification

The real gateway decodes the callback reply into a non-opt
`StreamingCallbackHttpResponse`:

`ic-gateway` → `ic-http-gateway-protocol` (tag `v0.5.1`) → `ic-agent` /
`ic-utils`

- [`ic-http-gateway-protocol` @ v0.5.1 —
`response_handler.rs#L116-L120`](https://github.com/dfinity/ic-http-gateway-protocol/blob/v0.5.1/packages/ic-http-gateway-protocol/src/response/response_handler.rs#L116-L120):
calls `http_request_stream_callback` and matches
`Ok((StreamingCallbackHttpResponse { body, token },))` — the bare
struct, no `Option`.
- [`ic-agent` —
`ic-utils/src/interfaces/http_request.rs`](https://github.com/dfinity/agent-rs/blob/main/ic-utils/src/interfaces/http_request.rs):
`http_request_stream_callback -> SyncCall<Value =
(StreamingCallbackHttpResponse,)>`, and the callback CandidType
`func((ArgToken) -> (StreamingCallbackHttpResponse) query)`.

Detected while adding e2e streaming tests for certified-assets
(dfinity/certified-assets#96). `npm run build` passes.
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