Skip to content

Instantly share code, notes, and snippets.

@nerdalert
Last active April 27, 2026 22:24
Show Gist options
  • Select an option

  • Save nerdalert/48aa05a0a125da4465bb9c21f04c59bf to your computer and use it in GitHub Desktop.

Select an option

Save nerdalert/48aa05a0a125da4465bb9c21f04c59bf to your computer and use it in GitHub Desktop.

Manual Validation of Praxis JSON-RPC 2.0 foundation for method-based routing

Validation

  ### 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

Raw Output:


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

Output Summary:

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment