OpenHuman JSON-RPC Dispatch Flow: From HTTP Request to Domain Handler

OpenHuman processes JSON-RPC requests through a layered Axum handler that validates parameters against static schemas before dispatching to registered domain-specific async functions.

The OpenHuman framework exposes its functionality via JSON-RPC 2.0 over HTTP, routing requests through a type-safe dispatch pipeline implemented in Rust. This architecture separates HTTP handling from business logic by using a static controller registry to validate and route calls to domain handlers located throughout the codebase.

HTTP Entry Point and Axum Routing

OpenHuman receives JSON-RPC calls on the POST /rpc endpoint defined in src/core/jsonrpc.rs. The Axum web framework binds this route to the rpc_handler function, which acts as the primary ingress point for all remote procedure calls.

When a request arrives, rpc_handler extracts the JSON-RPC envelope containing the id, method, and params fields. It then immediately delegates to the internal dispatch machinery while preparing to encode the final response as a JSON-RPC 2.0 object.

The Session-Aware Dispatch Pipeline

The dispatch flow proceeds through two distinct layers to handle cross-cutting concerns:

  1. invoke_method – This wrapper function in src/core/jsonrpc.rs adds session-management logic, including automatic clearing of 401 Unauthorized tokens. It sanitizes the execution context before passing control to the core dispatcher.

  2. invoke_method_inner – Also defined in src/core/jsonrpc.rs, this function performs the actual method resolution and invocation. It first queries the static controller registry via all::schema_for_rpc_method to locate the appropriate handler for the requested method name.

If the registry contains a matching schema, invoke_method_inner converts the parameters to an object, validates them against the schema, and invokes the method through all::try_invoke_registered_rpc. If no handler exists in the registry, the call falls back to the legacy shim crate::core::dispatch::dispatch in src/rpc/dispatch.rs, which currently returns None to indicate method not found.

Static Controller Registry and Schema Validation

The static controller registry in src/core/all.rs serves as the authoritative source of truth for all available RPC methods. During application startup, all_registered_controllers() aggregates schemas from every domain module and registers the concrete handler functions.

Each domain (such as openhuman::cron or openhuman::credentials) provides an rpc.rs file implementing the business-logic function and registers it with the central registry. For example, in src/openhuman/cron/rpc.rs, a handler might look like:

pub async fn schedule_job(params: Map<String, Value>) -> Result<Value, String> {
    // Domain logic delegates to crate::openhuman::cron::ops::schedule(...)
}

The registration process maps method names (like openhuman.cron_schedule_job) to their corresponding async functions and input schemas, enabling compile-time validation of the RPC interface.

Domain Handler Execution

Once the registry resolves a method, the corresponding async function executes within the Tokio runtime. The handler typically follows a three-step pattern:

  • Parameter extraction – Parsing the validated serde_json::Map<String, Value> into domain-specific structs
  • Business logic delegation – Calling functions in the domain's ops.rs module (e.g., src/openhuman/cron/ops.rs) to perform database updates or external API calls
  • Result serialization – Returning a serde_json::Value that becomes the result field in the JSON-RPC response

After the domain handler completes, the result propagates back up through invoke_method to rpc_handler, which wraps successful returns in an RpcSuccess object or errors in an RpcFailure before encoding the HTTP response.

Code Examples

Making an HTTP JSON-RPC Request

You can invoke the dispatch flow directly via curl:

curl -s http://127.0.0.1:7788/rpc \
  -H "Content-Type: application/json" \
  -d '{
        "jsonrpc":"2.0",
        "id":1,
        "method":"openhuman.cron_schedule_job",
        "params":{"cron":"0 * * * *","name":"hourly_cleanup"}
      }'

This request enters through rpc_handler in src/core/jsonrpc.rs, validates against the cron schema in the registry, and executes the schedule_job function in src/openhuman/cron/rpc.rs.

Calling the Dispatch Flow from Rust

For internal calls or testing, bypass HTTP and invoke the dispatcher directly:

use openhuman_core::core::jsonrpc::{invoke_method, default_state};

#[tokio::main]
async fn main() {
    let state = default_state();
    let method = "openhuman.cron_schedule_job";
    let params = serde_json::json!({
        "cron": "0 * * * *",
        "name": "hourly_cleanup"
    });

    match invoke_method(state, method, params).await {
        Ok(value) => println!("Result: {}", value),
        Err(err) => eprintln!("RPC error: {}", err),
    }
}

This uses the same validation and registry lookup as HTTP requests but skips the network layer.

Adding a New Domain Method

To extend the dispatch flow with new functionality:

  1. Create src/openhuman/<domain>/rpc.rs with an async function accepting Map<String, Value> and returning Result<Value, String>
  2. Define the input schema in src/openhuman/<domain>/schemas.rs
  3. Register the method in src/core/all.rs via register_controller to populate the static registry at startup
  4. Implement core logic in src/openhuman/<domain>/ops.rs

Once registered, the method becomes available via /rpc without additional routing changes.

Summary

  • HTTP handling occurs in rpc_handler within src/core/jsonrpc.rs, which parses the JSON-RPC envelope.
  • invoke_method manages sessions and wraps invoke_method_inner, the core dispatcher.
  • Static controller registry (src/core/all.rs) validates parameters via schemas and resolves method names to handler functions.
  • Domain handlers (e.g., src/openhuman/cron/rpc.rs) execute business logic and return serialized results.
  • Legacy fallback routes to src/rpc/dispatch.rs when methods are missing from the registry.
  • Response encoding wraps results in JSON-RPC 2.0 response objects before returning to the client.

Frequently Asked Questions

How does OpenHuman validate JSON-RPC parameters?

OpenHuman validates parameters through its static controller registry in src/core/all.rs. When invoke_method_inner processes a request, it retrieves the schema for the requested method via all::schema_for_rpc_method and validates the input parameters against this schema before invocation. This ensures type safety and required field checking before the domain handler executes.

What happens if a method is not found in the controller registry?

If all::schema_for_rpc_method returns no matching entry, the dispatch flow falls back to the legacy compatibility shim crate::core::dispatch::dispatch located in src/rpc/dispatch.rs. Currently, this fallback returns None, which results in a method-not-found error response being generated by the rpc_handler.

Where is the HTTP router configured in OpenHuman?

The HTTP router is constructed in the build_core_http_router function within src/core/jsonrpc.rs. This function sets up the Axum routes, including binding the POST /rpc endpoint to the rpc_handler function, and applies any middleware layers before the router begins accepting connections.

How do I register a new JSON-RPC method in OpenHuman?

To register a new method, implement the handler function in your domain's rpc.rs file, define its schema in schemas.rs, and add a registration entry in src/core/all.rs using register_controller. This entry populates the static registry during startup, making the method available through the standard dispatch flow without modifying the HTTP routing logic.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →