@@ -26,6 +26,38 @@ pub type OhttpJsonrpseeLayer = OhttpLayer<fn(Full<Bytes>) -> HttpBody>;
2626/// Pass this to `OhttpLayer::new` when constructing the layer.
2727pub const OHTTP_JSONRPSEE_BODY_BUILDER : fn ( Full < Bytes > ) -> HttpBody = HttpBody :: new;
2828
29+ /// The production HTTP middleware stack, applied verbatim by both transports:
30+ /// the HTTP path (`start_server`) and the HTTPS path (`tls::start_tls_server`).
31+ ///
32+ /// This is a macro rather than a helper function because the value is a deeply
33+ /// nested `ServiceBuilder<Stack<Stack<...>>>` whose type cannot be spelled in a
34+ /// signature. Defining the chain once keeps the two transports from drifting.
35+ ///
36+ /// Defined above `pub mod tls;` on purpose: `macro_rules!` scoping is textual, so moving the
37+ /// definition below the module would make it invisible to `tls.rs`. The macro also resolves
38+ /// `ServiceBuilder`, the layer types, and `HttpBody` at each call site (not at the definition),
39+ /// so every caller must have them in scope — adding a layer here adds an import obligation at
40+ /// each call site.
41+ ///
42+ /// Layer order (tower makes the last-added layer innermost):
43+ /// - `HealthLayer` sits outermost so `GET /health` is answered before any other middleware runs.
44+ /// - `OhttpLayer` must sit OUTSIDE `CompressionLayer` so compression applies to the inner JSON-RPC
45+ /// response (the client's inner `Accept-Encoding` travels through BHTTP into jsonrpsee) rather
46+ /// than to the OHTTP ciphertext envelope. `MapRequestBodyLayer`/`MapResponseBodyLayer` keep
47+ /// `HttpBody` on both sides of OHTTP to satisfy its symmetric-body bound; `HttpBody::new` is a
48+ /// zero-cost wrapper, so non-OHTTP requests still stream through unbuffered.
49+ macro_rules! prover_http_middleware {
50+ ( $cors_layer: expr, $ohttp_layer: expr $( , ) ?) => {
51+ ServiceBuilder :: new( )
52+ . layer( HealthLayer )
53+ . option_layer( $cors_layer)
54+ . layer( MapRequestBodyLayer :: new( HttpBody :: new) )
55+ . option_layer( $ohttp_layer)
56+ . layer( MapResponseBodyLayer :: new( HttpBody :: new) )
57+ . layer( CompressionLayer :: new( ) )
58+ } ;
59+ }
60+
2961pub mod config;
3062pub mod cors;
3163pub mod errors;
@@ -67,31 +99,8 @@ pub async fn start_server(
6799 . build ( ) ;
68100 let server = ServerBuilder :: default ( )
69101 . set_config ( server_config)
70- // `OhttpLayer` must sit OUTSIDE `CompressionLayer` so compression
71- // applies to the inner JSON-RPC response (the client's inner
72- // `Accept-Encoding` travels through BHTTP into jsonrpsee) rather than
73- // to the OHTTP ciphertext envelope. Because tower's `ServiceBuilder`
74- // makes the last-added layer innermost, `CompressionLayer` is added
75- // last here.
76- //
77- // `MapRequestBodyLayer` wraps hyper's `Request<Incoming>` into
78- // `Request<HttpBody>` before `OhttpLayer` sees it — `OhttpLayer`'s
79- // symmetric-body bound requires `B = HttpBody` on both sides.
80- // `MapResponseBodyLayer` converts `CompressionBody<HttpBody>` back to
81- // `HttpBody` on the response path so `OhttpLayer` receives the body
82- // type it expects. `HttpBody::new` is a zero-cost wrapper, so
83- // non-OHTTP requests still stream through unbuffered.
84- . set_http_middleware (
85- // `HealthLayer` sits outermost so `GET /health` is answered
86- // before any other middleware runs.
87- ServiceBuilder :: new ( )
88- . layer ( HealthLayer )
89- . option_layer ( cors_layer)
90- . layer ( MapRequestBodyLayer :: new ( HttpBody :: new) )
91- . option_layer ( ohttp_layer)
92- . layer ( MapResponseBodyLayer :: new ( HttpBody :: new) )
93- . layer ( CompressionLayer :: new ( ) ) ,
94- )
102+ // See `prover_http_middleware!` for the full layer-order rationale.
103+ . set_http_middleware ( prover_http_middleware ! ( cors_layer, ohttp_layer) )
95104 . build ( & addr)
96105 . await
97106 . context ( format ! ( "Failed to bind JSON-RPC server to {addr}" ) ) ?;
0 commit comments