HTTP session hooks: streaming request and response middleware
The PR replaces the v1 HTTP middleware hooks with two bidirectional streaming RPCs. Middleware can now inspect bodies of any size, and decide about connections OpenShell cannot parse as HTTP.
- Streaming API
EvaluateHttpRequestSessionandEvaluateHttpResponseSession, one stream per HTTP message and stage. A preflight picks continue, reject, or inspect (BUFFERED or STREAM). - Opt in by capabilitySame
HTTP_REQUEST/HTTP_RESPONSEbindings. A service that requiresopenshell.supervisor-middleware.http-sessiongets the new RPCs. No new operations or versioned enums. - Always fail-closed
on_error: fail_openis rejected for session hook services. - v1 deprecated
EvaluateHttpRequestandHttpResponsePreReturnkeep working and are removed in 0.2.0.
In this deck
- Before and after
- Shape of the new API
- How a service selects the hook version
- Migrating a v1 service
- Next iterations
- Preflight and body eligibility rules
- Chain ordering and when the head commits
- STREAM queues, stalls, and deadlines
- Failure semantics per phase
- Code map for the diff
Same place in the proxy, different conversation with the service
The middleware still sits between network-policy admission and credential injection for requests, and before delivery for responses. What changes is how the supervisor talks to it, and which traffic it can see.
v to toggle
What changes
| v1 hooks | Session hooks | |
|---|---|---|
| Body size | ≤ 4 MiB, buffered | BUFFERED ≤ 4 MiB or unbounded STREAM |
| Request vs response | Two unrelated contracts | One event/result pair for both |
| Who chooses body handling | Request: always full body. Response: mode at preflight | Service, at preflight, from modes OpenShell permits |
tls: skip, h2c, raw TCP | Not shown to middleware | Request service allows or refuses the connection |
| Failures | on_error decides | Always fail-closed |
| Version selection | Default | Required capability in Describe |
Two RPCs, one event stream, one result stream
Requests and responses share HttpEvent and HttpResult. The first event is always a preflight, and its result decides which of the remaining events the service will see.
proto/supervisor_middleware.proto
service SupervisorMiddleware { rpc Describe(...) returns (MiddlewareManifest); rpc ValidateConfig(...) returns (ValidateConfigResponse); rpc EvaluateHttpRequest(HttpRequestEvaluation) returns (HttpRequestResult); // deprecated rpc EvaluateHttpRequestSession(stream HttpEvent) returns (stream HttpResult); rpc EvaluateHttpResponseSession(stream HttpEvent) returns (stream HttpResult); rpc EvaluateWebSocketSession(...) // unchanged } service HttpResponsePreReturn { ... } // deprecated
message HttpEvent { oneof event { preflight = 1; begin = 2; buffered_body = 3; input_chunk = 4; input_end = 5; session_end = 6; } }
message HttpResult { oneof result { preflight_result = 1; buffered_result = 2; output_start = 3; output_chunk = 4; finish = 5; reject = 6; } }
message HttpPreflight { oneof subject { request | response | uninspectable } context, middleware_name, config repeated HttpBodyMode permitted_body_modes; // BUFFERED, STREAM repeated HttpBodyModeUnavailable unavailable_body_modes; // mode + reason HttpBodyLimits limits; // chunk, buffer, queues, idle optional uint64 declared_body_bytes; }
Exchange explorer · pick what the service decides at preflight
A required capability selects the hook version
The bindings stay HTTP_REQUEST and HTTP_RESPONSE. Gateways and supervisors have rejected unmet required_capabilities at Describe since v0.1.0, so an older peer refuses a session hook service instead of calling it through the v1 RPCs.
Describe → MiddlewareManifest
bindings { operation: HTTP_REQUEST phase: PRE_CREDENTIALS }
bindings { operation: HTTP_RESPONSE phase: PRE_RETURN }
bindings { operation: WEBSOCKET_MESSAGE ... } // unaffected
extension {
supported_capabilities: "openshell.supervisor-middleware.http-session"
required_capabilities: "openshell.supervisor-middleware.http-session"
}
Rust helper
extension: Some(http_session_middleware_metadata( MANIFEST_NAME, openshell_core::VERSION, )), // openshell_core::extension_protocol
- The capability applies to every HTTP binding of the service. To serve both versions during a rollout, run two registrations.
- A binding may set
max_payload_bytes: 0. It then gets no body modes and can only continue or reject.
What happens for each manifest and peer
EvaluateHttp*Session.No RPC, message, or enum value carries a version number. buf breaking against v0.1.2 reports no breaking change.
Moving a service from v1 to session hooks
Both hook versions run side by side until 0.2.0. The new docs page docs/extensibility/supervisor-middleware/http-session-hooks.mdx carries these steps for users.
Steps for a service owner
- Require the capabilityAdd
openshell.supervisor-middleware.http-sessionto bothsupported_capabilitiesandrequired_capabilities. Keep the bindings. - Implement the session RPCsImplement
EvaluateHttpRequestSession/EvaluateHttpResponseSessionfor the bindings you declare; drop the v1 RPCs. - Make policy entries fail-closedChange
on_error: fail_opentofail_closed. The gateway rejectsfail_openfor session hook services. - Separate selectors from v1 entriesEndpoint selectors must not overlap v1 HTTP hook entries for the same direction, including built-in
openshell/regex. - Roll out gateway firstUpgrade the gateway, then deploy the service. Recreate sandboxes whose supervisors predate this PR.
v1 → session hooks
| v1 | Session hooks | |
|---|---|---|
EvaluateHttpRequest | → | inspect: buffered |
Decision DENY | → | reject |
HEADERS_ONLY | → | continue_without_body + header mutations |
WHOLE_BODY_BYTES | → | inspect: buffered |
STREAM_BYTES | → | inspect: stream |
block_delivery | → | reject |
on_error: fail_open | → | not allowed |
New gateway policy checks
- Overlapping selectors that mix hook versions for one direction are rejected. If a mixed chain still occurs at runtime, traffic is blocked with
middleware_hook_versions_mixed. - Session hook request middleware may attach to
tls: skipendpoints; supervisors from before this PR reject such a policy. - Stored policies with
fail_openstill load; supervisors run their HTTP traffic fail-closed once they reach the service.
deprecated.