Manual Validation of Praxis JSON-RPC 2.0 foundation for method-based routing
### 1. Build Praxis with JSON-RPC Filter
```bash
cargo build --release -p praxis
2. Use Simple netcat Backend
# Terminal 1 - Simple netcat server
while true; do echo -e "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n" | nc -l -p 9001; done
3. Create Test Configuration
cat > test_config.yaml << 'EOF'
listeners:
- name: default
address: "0.0.0.0:8080"
filter_chains: [main]
filter_chains:
- name: main
filters:
- filter: json_rpc
max_body_bytes: 1048576
batch_policy: reject
on_invalid: continue
- filter: router
routes:
- path_prefix: "/"
cluster: "backend"
- filter: load_balancer
clusters:
- name: "backend"
endpoints:
- "127.0.0.1:9001"
EOF
4. Start Praxis Proxy WITH TRACE LOGGING
# Terminal 2 - Don't forget RUST_LOG=trace
RUST_LOG=trace ./target/release/praxis --config test_config.yaml
5. Test and Observe Logs
# Terminal 3 - Test JSON-RPC request
curl -X POST http://localhost:8080/api \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/call","id":"req-123"}'
# Check Terminal 2 for logs showing:
# extracted JSON-RPC envelope metadata method=Some("tools/call") kind=Request
# injecting promoted headers count=3
# x-json-rpc-method: tools/call
Alternative: JSON-RPC Filter Only Logging
# Less verbose - only JSON-RPC filter logs:
RUST_LOG=praxis_filter::builtins::http::payload_processing::json_rpc=trace ./target/release/praxis --config test_config.yaml
2026-04-27T22:22:12.119180Z DEBUG pingora_core::services::listening: new event!
2026-04-27T22:22:12.119273Z DEBUG pingora_proxy: Successfully get a new request
2026-04-27T22:22:12.119281Z TRACE pingora_proxy: Request header: Parts { method: POST, uri: /api, version: HTTP/1.1, headers: {"host": "localhost:8080", "user-agent": "curl/8.5.0", "accept": "*/*", "content-type": "application/json", "content-length": "54"} }
2026-04-27T22:22:12.119325Z DEBUG praxis_protocol::http::pingora::handler::request_filter: pre-reading request body for StreamBuffer inspection
2026-04-27T22:22:12.119338Z DEBUG pingora_core::protocols::http::v1::body: BodyReader body_state: Partial(0, 54), read data from IO: 54
2026-04-27T22:22:12.119345Z TRACE praxis_filter::pipeline::http: on_request_body filter="json_rpc"
2026-04-27T22:22:12.119370Z TRACE praxis_filter::builtins::http::payload_processing::json_rpc: extracted JSON-RPC envelope metadata method=Some("tools/call") kind=Request
2026-04-27T22:22:12.119379Z DEBUG praxis_filter::pipeline::http_utils: filter released body filter="json_rpc"
2026-04-27T22:22:12.119384Z DEBUG praxis_protocol::http::pingora::handler::request_filter::stream_buffer: StreamBuffer released during pre-read
2026-04-27T22:22:12.119394Z TRACE praxis_filter::pipeline::http: on_request_body filter="json_rpc"
2026-04-27T22:22:12.119398Z DEBUG praxis_protocol::http::pingora::handler::request_filter::stream_buffer: storing pre-read body for forwarding by request_body_filter
2026-04-27T22:22:12.119402Z DEBUG praxis_protocol::http::pingora::handler::request_filter: injecting promoted headers count=3
2026-04-27T22:22:12.119422Z TRACE praxis_filter::pipeline::http: on_request filter="json_rpc"
2026-04-27T22:22:12.119427Z TRACE praxis_filter::pipeline::http: on_request filter="router"
2026-04-27T22:22:12.119436Z TRACE praxis_filter::builtins::http::traffic_management::router: matching route path=/api host="localhost:8080"
2026-04-27T22:22:12.119443Z DEBUG praxis_filter::builtins::http::traffic_management::router: route matched path=/api cluster=backend
2026-04-27T22:22:12.119452Z TRACE praxis_filter::pipeline::http: on_request filter="load_balancer"
2026-04-27T22:22:12.119457Z DEBUG praxis_filter::builtins::http::traffic_management::load_balancer: upstream selected cluster=backend upstream=127.0.0.1:9001
2026-04-27T22:22:12.119478Z DEBUG pingora_core::connectors: No reusable connection found for addr: 127.0.0.1:9001, scheme: HTTP
2026-04-27T22:22:12.119564Z TRACE mio::poll: registering event source with poller: token=Token(129855268065664), interests=READABLE | WRITABLE
2026-04-27T22:22:12.119666Z DEBUG pingora_core::connectors::l4: connected to new server: 127.0.0.1:9001
2026-04-27T22:22:12.119741Z DEBUG praxis_protocol::http::pingora::handler::via: adding request Via header via=1.1 praxis
2026-04-27T22:22:12.119754Z DEBUG pingora_proxy::proxy_h1: Sending header to upstream RequestHeader { base: Parts { method: POST, uri: /api, version: HTTP/1.1, headers: {"host": "localhost:8080", "user-agent": "curl/8.5.0", "accept": "*/*", "content-type": "application/json", "content-length": "54", "x-json-rpc-method": "tools/call", "x-json-rpc-id": "req-123", "x-json-rpc-kind": "request", "via": "1.1 praxis"} }, header_name_map: Some({"host": CaseHeaderName(b"Host"), "user-agent": CaseHeaderName(b"User-Agent"), "accept": CaseHeaderName(b"Accept"), "content-type": CaseHeaderName(b"Content-Type"), "content-length": CaseHeaderName(b"Content-Length"), "x-json-rpc-method": CaseHeaderName(b"x-json-rpc-method"), "x-json-rpc-id": CaseHeaderName(b"x-json-rpc-id"), "x-json-rpc-kind": CaseHeaderName(b"x-json-rpc-kind"), "via": CaseHeaderName(b"via")}), raw_path_fallback: [], send_end_stream: true }
2026-04-27T22:22:12.119786Z TRACE pingora_core::protocols::http::v1::client: Writing request header: b"POST /api HTTP/1.1\r\nHost: localhost:8080\r\nUser-Agent: curl/8.5.0\r\nAccept: */*\r\nContent-Type: application/json\r\nContent-Length: 54\r\nx-json-rpc-method: tools/call\r\nx-json-rpc-id: req-123\r\nx-json-rpc-kind: request\r\nvia: 1.1 praxis\r\n\r\n"
2026-04-27T22:22:12.119828Z TRACE praxis_protocol::http::pingora::handler::request_body_filter: forwarding pre-read body chunks from StreamBuffer mode
2026-04-27T22:22:12.119832Z DEBUG pingora_proxy::proxy_h1: Read 54 bytes body from downstream
2026-04-27T22:22:12.119851Z TRACE pingora_core::protocols::http::v1::body: Writing Body, size: 54
2026-04-27T22:22:12.119863Z DEBUG pingora_proxy::proxy_h1: Write 54 bytes body to upstream
2026-04-27T22:22:12.119866Z DEBUG pingora_proxy::proxy_h1: finish sending body to upstream
2026-04-27T22:22:12.119994Z DEBUG pingora_core::protocols::http::v1::client: Response header: ResponseHeader { base: Parts { status: 200, version: HTTP/1.1, headers: {"content-type": "text/plain", "content-length": "20"} }, header_name_map: Some({"content-type": CaseHeaderName(b"Content-Type"), "content-length": CaseHeaderName(b"Content-Length")}), reason_phrase: None }
2026-04-27T22:22:12.120010Z TRACE pingora_core::protocols::http::v1::client: Raw Response header: "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 20\r\n\r\n"
2026-04-27T22:22:12.120016Z DEBUG pingora_core::protocols::http::v1::body: BodyReader body_state: Partial(0, 20), read data from IO: 20
2026-04-27T22:22:12.120020Z DEBUG pingora_core::protocols::http::v1::client: Response body: 20 bytes, end: true
2026-04-27T22:22:12.120023Z TRACE pingora_core::protocols::http::v1::client: Response body: Some(b"Backend received OK\n"), upgraded: false
2026-04-27T22:22:12.120030Z DEBUG pingora_proxy::proxy_h1: upstream event: Some(Header(ResponseHeader { base: Parts { status: 200, version: HTTP/1.1, headers: {"content-type": "text/plain", "content-length": "20"} }, header_name_map: Some({"content-type": CaseHeaderName(b"Content-Type"), "content-length": CaseHeaderName(b"Content-Length")}), reason_phrase: None }, false))
2026-04-27T22:22:12.120037Z DEBUG pingora_proxy::proxy_h1: upstream event now: Some(Body(Some(b"Backend received OK\n"), true))
2026-04-27T22:22:12.120040Z DEBUG pingora_proxy::proxy_h1: upstream event now: None
2026-04-27T22:22:12.120053Z TRACE praxis_filter::pipeline::http: on_response filter="load_balancer"
2026-04-27T22:22:12.120075Z TRACE praxis_filter::builtins::http::traffic_management::load_balancer: releasing in-flight counter
2026-04-27T22:22:12.120078Z TRACE praxis_filter::pipeline::http: on_response filter="router"
2026-04-27T22:22:12.120081Z TRACE praxis_filter::pipeline::http: on_response filter="json_rpc"
2026-04-27T22:22:12.120087Z DEBUG praxis_protocol::http::pingora::handler::via: adding response Via header via=1.1 praxis
2026-04-27T22:22:12.120114Z TRACE pingora_core::protocols::http::v1::body: Writing Body, size: 20
2026-04-27T22:22:12.120145Z TRACE pingora_core::protocols::http::v1::server: finish body (response body writer), upgraded: false
2026-04-27T22:22:12.120150Z TRACE pingora_core::protocols::http::v1::server: finish body (response body writer), upgraded: false
2026-04-27T22:22:12.120153Z DEBUG pingora_proxy::proxy_h1: finished sending body to downstream
2026-04-27T22:22:12.120163Z DEBUG pingora_core::connectors: Try to keepalive client session
2026-04-27T22:22:12.120185Z TRACE pingora_core::protocols::http::v1::server: finish body (response body writer), upgraded: false
2026-04-27T22:22:12.120255Z DEBUG pingora_core::protocols::http::v1::server: Client prematurely closed connection with 0 byte sent
2026-04-27T22:22:12.120261Z DEBUG pingora_core::protocols::l4::stream: Dropping socket Some(BufStream { inner: BufReader { reader: BufWriter { writer: RawStreamWrapper { stream: Tcp(TcpStream { addr: 127.0.0.1:8080, peer: 127.0.0.1:55154, fd: 14 }), rx_ts: None, enable_rx_ts: false, reusable_cmsg_space: [] }, buffer: 0/1460, written: 0 }, buffer: 0/65536 } })
2026-04-27T22:22:12.120271Z TRACE mio::poll: deregistering event source from poller
1. JSON-RPC Envelope Extracted
TRACE praxis_filter::builtins::http::payload_processing::json_rpc:
extracted JSON-RPC envelope metadata method=Some("tools/call") kind=Request
2. Headers Promoted
DEBUG praxis_protocol::http::pingora::handler::request_filter:
injecting promoted headers count=3
3. Headers Sent to Backend
In the raw HTTP request to the backend:
x-json-rpc-method: tools/call
x-json-rpc-id: req-123
x-json-rpc-kind: request
✅ Complete Workflow Validation:
1. ✅ Request received: POST /api with JSON-RPC body
2. ✅ Body buffered: StreamBuffer mode captured 54 bytes
3. ✅ JSON-RPC parsed: Filter extracted method and kind
4. ✅ Headers injected: 3 JSON-RPC headers added
5. ✅ Routing worked: Router matched path, load balancer selected backend
6. ✅ Backend received: All headers forwarded including JSON-RPC metadata
Manual Testing Results:
The logs prove the JSON-RPC filter successfully:
- Parses JSON-RPC 2.0 envelopes from request bodies
- Extracts method, id, and kind metadata
- Promotes metadata to headers for routing integration
- Integrates seamlessly with router and load balancer filters