Skip to content

feat(middleware)!: replace unary HTTP request evaluation with a streaming hook #3307

Description

@pimlock

User Story

As a supervisor middleware author, I want one streaming HTTP request hook with headers-only, whole-body, and incremental inspection, so that I can handle bounded requests and large uploads through a contract consistent with HTTP response middleware.

Problem Statement

SupervisorMiddleware.EvaluateHttpRequest is unary. It receives one buffered HttpRequestEvaluation and returns one HttpRequestResult; request and replacement bodies have a 4 MiB platform limit. Even a stage that needs only headers enters the buffered request path.

The response contract introduced in #3073 and implemented by #3074 uses the hook-specific bidirectional HttpResponsePreReturn.Evaluate service, with preflight, body units, trailers, and session termination. Keeping the unary request hook would stabilize two different processing models for closely related extension operations.

Parent: #2565. This is an intentional breaking change before 0.1.0. Remove the unary request hook and replace it with the streaming contract; preserving the old RPC or providing a unary compatibility adapter is not a requirement.

Impact / Why This Matters

Middleware authors cannot inspect request headers without whole-body buffering or incrementally process bodies larger than the inspection limit. Operators must accept buffering latency and capacity limits, bypass inspection where fail-open permits it, or use another proxy. Bypassing does not provide the requested inspection, and another proxy duplicates policy and credential-boundary integration.

Delaying replacement until after 0.1.0 would leave extension authors with a compatibility migration during the stable-contract period. Making the change now lets request and response middleware share lifecycle conventions while preserving their different enforcement boundaries.

Proposed Design

Replace EvaluateHttpRequest with a hook-specific bidirectional service for HTTP_REQUEST/PRE_CREDENTIALS, following the HttpResponsePreReturn.Evaluate pattern. HttpRequestPreCredentials.Evaluate is the proposed name. Retain the existing registration, policy attachment, host selection, and ordering workflow.

Each selected stage receives a preflight containing request context, admitted target, safe headers, configuration, effective payload limits, and permitted body modes. It can skip, inspect, or explicitly block the request. Inspection supports headers-only processing, a bounded whole-body value, or normalized byte units. Whole-body inspection remains available through this stream for simple middleware implementations.

Align with the finalized response contract from #3073/#3074:

  • One ordered stream per request and stage, with stage-local body sequences and exactly one ordered result for each preflight, body, and trailer event.
  • Explicit pass-through, replacement, block, and skip-remaining actions. Skip and fail-open end only the affected stage; later stages continue with the current validated input.
  • A final result-bearing body unit marked end_of_stream, defined empty-body behavior, trailers for normally completed body inspection, and one best-effort shared MiddlewareSessionEnd notification without a result.
  • Stage-local body-mode eligibility and limits. Whole-body input and replacement must fit the effective limit; streaming bounds each input and replacement without treating that limit as a total upload cap.
  • Unit-local streaming, with each result accounting for its entire input. Cross-unit retention, rolling-window inspection, and an author SDK are follow-up work.

Define request-specific semantics explicitly. Headers-only middleware must not itself force body accumulation. A whole-body decision must finish before any request bytes reach upstream. Incremental processing can release approved bytes, so a later block stops further forwarding but cannot undo upstream disclosure or side effects. Specify downstream error behavior separately from upstream commitment, including early upstream responses, cancellation, and connection reuse. An explicit block applies regardless of on_error; malformed results and transport failures follow on_error. Never replay a partially forwarded request to recover a failed stage.

Preserve policy admission and post-transformation checks before later stages or upstream delivery. Derive permitted modes from the complete request path: body-aware GraphQL/JSON-RPC/MCP policy, credential body rewrites, or signing may still require buffering. Unsupported combinations must have explicit behavior and must not silently weaken enforcement. Keep credential injection after inspection and retain direction-specific header/trailer protections and supervisor-owned framing.

Acceptance Criteria

  • The public unary RPC and request-only messages/adapters are removed. Built-ins, remote services, examples, fixtures, and affected generated bindings use the new request stream. Shared response/WebSocket types remain valid.
  • The protobuf contract documents preflight actions, body modes, ordered results, replacements including empty bytes, trailers, terminal events, invalid transitions, and diagnostic limits consistently with the response hook.
  • Eligible headers-only and streaming requests can exceed the old whole-body limit without whole-request buffering; whole-body mode remains bounded and withholds all upstream request bytes until approval.
  • Mixed-mode and mixed-limit chains preserve policy order, transformations, skip-remaining, explicit denial, and fail-open/fail-closed behavior without replay or loss of input.
  • Request commitment, partial forwarding, early upstream responses, downstream errors, cancellation, disconnects, and policy reload have documented and tested outcomes.
  • Backpressure, concurrent-stream admission, per-result timeouts, whole-body accumulation deadlines, and cleanup bound resource use, including for slow or unknown-length uploads.
  • Tests cover bodyless and empty requests, Content-Length and chunked bodies, trailers, Expect: 100-continue, size-changing transformations, over-capacity input/replacements, invalid results, and failures before and after upstream forwarding.
  • Tests preserve post-transformation body-aware policy enforcement, credential non-disclosure, protected-header validation, and correct upstream framing. OCSF events omit bodies, credentials, query secrets, and free-form middleware reasons.
  • External middleware e2e coverage demonstrates the new hook alongside response middleware, including whole-body and streaming use cases.
  • Architecture, RFC 0009's contract description, published middleware/config documentation, and relevant skills describe the replacement. The 0.1.0 upgrade guide in docs: author the 0.1.0 upgrade guide #3063 includes a concrete unary-to-stream migration and coordinated supervisor/service upgrade requirements, with no silent unary fallback.

Alternatives Considered

Keep unary and streaming APIs with a compatibility adapter. This is the approach proposed in #2431, but it retains a contract we intend to remove before 0.1.0. Whole-body mode provides the bounded inspection workflow through the replacement API.

Wrap the buffered request in a stream without incremental processing. This changes transport cardinality but retains the buffering and upload-size limitations.

Reuse the response RPC or introduce a universal inspection RPC. Requests have different metadata, mutation authority, policy checks, and upstream commitment semantics. Share conventions and internal machinery where useful while keeping the public hooks distinct.

Agent Investigation

Checklist

  • I've reviewed existing issues and the architecture docs
  • This is a design proposal, not a "please build this" request

Activity

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Relationships

None yet

Development

No branches or pull requests

Issue actions