Skip to content

http: sticky echo headers + Fly.io quickstart helpers#10

Merged
rustyconover merged 1 commit into
mainfrom
sticky-sessions-echo-headers
May 18, 2026
Merged

http: sticky echo headers + Fly.io quickstart helpers#10
rustyconover merged 1 commit into
mainfrom
sticky-sessions-echo-headers

Conversation

@rustyconover
Copy link
Copy Markdown
Collaborator

Summary

  • PR 2 of 3 in the sticky-sessions series. Stacked on http: opt-in sticky sessions (HTTP-only, header transport, DELETE endpoint) #9 (sticky core); base is sticky-sessions-core so it'll cleanly rebase onto main once http: opt-in sticky sessions (HTTP-only, header transport, DELETE endpoint) #9 merges.
  • New server config sticky_echo_headers={name: value} on make_wsgi_app tells the client to echo arbitrary headers on every subsequent request in the session. Used for client-driven routing on platforms like Fly.io.
  • New vgi_rpc/http/fly.py (~25 lines): auto_server_id() + fly_sticky_echo_headers() — Fly.io quickstart helpers. Two-line setup, zero LB config.
  • Capability header VGI-Sticky-Echo-Headers advertises the configured names; HttpServerCapabilities.sticky_echo_headers: tuple[str, ...] surfaces them.
  • New canonical conformance test TestSticky::test_echo_header_round_trip (capability-gated on VGI-Sticky-Echo-Headers so deployments without echo support skip cleanly).

Wire contract

Server side: when sticky_echo_headers={"fly-force-instance-id": \"abc\"} is configured, every session-opening response carries VGI-Echo-fly-force-instance-id: abc. Emitted once only — subsequent responses MUST NOT re-emit. Capability header VGI-Sticky-Echo-Headers: fly-force-instance-id advertises the contract.

Client side: with_session_token() view's _SessionTrackingClient captures every VGI-Echo-<name> response header (case-insensitive, prefix-stripped) and replays the inner header on every subsequent request in the same block. Captured map exposed via sess.current_echo_headers(); cleared on VGI-Session-Close: true.

Caller-supplied request headers take precedence: _merge_headers uses setdefault, so passing an explicit per-call header overrides the captured echo header for that one request.

Fly quickstart

from vgi_rpc import RpcServer
from vgi_rpc.http import make_wsgi_app
from vgi_rpc.http.fly import auto_server_id, fly_sticky_echo_headers

server = RpcServer(MyService, MyServiceImpl(), server_id=auto_server_id())
app = make_wsgi_app(
    server,
    enable_sticky=True,
    sticky_echo_headers=fly_sticky_echo_headers(),
)

Off Fly both helpers return None so the same code is a no-op — no conditional branches.

Test plan

  • uv run ruff format .
  • uv run ruff check . → clean
  • uv run mypy vgi_rpc/ → clean
  • uv run ty check vgi_rpc/ → clean (pre-existing pyarrow note only)
  • uv run pytest --timeout=503316 passed, 0 failed in ~82s
  • All 10 canonical TestSticky tests pass (was 9 in PR1; +1 echo round-trip)
  • All 26 Python-only sticky tests pass (was 16; +10 for echo + Fly helper)
  • Stateless conformance + non-sticky wire path still byte-identical

What's next

  • PR 3: graceful drain (RpcServer.drain() + SIGTERM handler + drain_grace_seconds + access-log session_id/session_action fields). Independent of this PR.

🤖 Generated with Claude Code

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@rustyconover rustyconover changed the base branch from sticky-sessions-core to main May 18, 2026 03:06
Server-side ``sticky_echo_headers={name: value}`` config tells the client
to replay arbitrary headers on every subsequent request in the session.
Emitted once-only as ``VGI-Echo-<name>: <value>`` on session-opening
responses; client captures + replays automatically inside the
``with_session_token()`` view. Used for client-driven routing on
platforms like Fly.io — ``vgi_rpc.http.fly.fly_sticky_echo_headers()``
returns ``{"fly-force-instance-id": $FLY_MACHINE_ID}`` so fly-proxy
routes directly to the owning Machine with zero LB config.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rustyconover rustyconover force-pushed the sticky-sessions-echo-headers branch from ec11d3f to 726173c Compare May 18, 2026 03:09
@rustyconover rustyconover merged commit 38cf9d8 into main May 18, 2026
2 of 4 checks passed
rustyconover added a commit that referenced this pull request May 18, 2026
API Reference entries for surfaces introduced in #9/#10/#11:

* http.md gains ``serve_http`` (was missing), ``DrainHandle``,
  ``drain_handle``, and the ``vgi_rpc.http.fly`` quickstart helpers.
* core.md "Errors" gains a "Typed marker errors" subsection covering
  ``MethodNotImplementedError`` (pre-existing miss), ``SessionLostError``,
  and ``ServerDrainingError`` — the three classes carrying
  ``error_kind`` for wire-side pattern matching.

mkdocs --strict builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
rustyconover added a commit that referenced this pull request May 18, 2026
Feature release. Adds opt-in sticky sessions for the HTTP transport
(PRs #9, #10, #11). Non-sticky wire path byte-identical to 0.16.1;
existing callers see no behaviour change.

* ctx.open_session / ctx.close_session runtime API on CallContext
* with conn.with_session_token() as sess: client view
* VGI-Session header transport with AEAD-sealed token, principal-bound AAD
* DELETE /vgi/__session__ idempotent teardown endpoint
* sticky_echo_headers — server-issued headers the client replays
  (Fly.io quickstart helpers in vgi_rpc.http.fly)
* drain_handle(app) operator API + serve_http SIGTERM graceful drain
* Access-log session_id + session_action fields
* Typed errors SessionLostError + ServerDrainingError
* Canonical TestSticky cross-language conformance group (11 tests,
  capability-gated)
* Full spec at docs/sticky-sessions-spec.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants