knafeh 1.1.0

QUIC-based RPC library with Python bindings
Documentation
//! Example: Knafeh RPC server in Rust.
//!
//! Run with:
//!     cargo run --no-default-features --example rust_server
//!
//! TLS certificates are generated at startup via `rcgen`.

use async_trait::async_trait;
use knafeh::codec::JsonCodec;
use knafeh::error::{KnafehError, RpcStatusCode};
use knafeh::rpc::message::{RpcRequest, RpcResponse};
use knafeh::rpc::service::{MethodDescriptor, MethodKind, Service};
use knafeh::rpc::stream::{RpcStreamRequest, RpcStreamResponse};
use knafeh::server::Server;
use knafeh::transport::tls::TlsConfig;

struct GreeterService;

#[async_trait]
impl Service for GreeterService {
    fn name(&self) -> &str {
        "greeter"
    }

    fn methods(&self) -> Vec<MethodDescriptor> {
        vec![
            MethodDescriptor {
                name: "say_hello".to_string(),
                kind: MethodKind::Unary,
            },
            MethodDescriptor {
                name: "say_goodbye".to_string(),
                kind: MethodKind::Unary,
            },
        ]
    }

    async fn call_unary(
        &self,
        method: &str,
        request: RpcRequest,
    ) -> Result<RpcResponse, KnafehError> {
        let body: serde_json::Value = serde_json::from_slice(&request.body)?;
        let name = body.get("name").and_then(|v| v.as_str()).unwrap_or("World");

        let response = match method {
            "say_hello" => {
                serde_json::json!({ "message": format!("Hello, {name}!") })
            }
            "say_goodbye" => {
                serde_json::json!({ "message": format!("Goodbye, {name}!") })
            }
            _ => {
                return Err(KnafehError::Service {
                    code: RpcStatusCode::NotFound,
                    message: format!("unknown method: {method}"),
                });
            }
        };

        Ok(RpcResponse::ok(serde_json::to_vec(&response).unwrap()))
    }

    async fn call_server_stream(
        &self,
        _method: &str,
        _request: RpcRequest,
    ) -> Result<RpcStreamResponse, KnafehError> {
        Err(KnafehError::Service {
            code: RpcStatusCode::Unimplemented,
            message: "not implemented".to_string(),
        })
    }

    async fn call_client_stream(
        &self,
        _method: &str,
        _stream: RpcStreamRequest,
    ) -> Result<RpcResponse, KnafehError> {
        Err(KnafehError::Service {
            code: RpcStatusCode::Unimplemented,
            message: "not implemented".to_string(),
        })
    }

    async fn call_bidi_stream(
        &self,
        _method: &str,
        _stream: RpcStreamRequest,
    ) -> Result<RpcStreamResponse, KnafehError> {
        Err(KnafehError::Service {
            code: RpcStatusCode::Unimplemented,
            message: "not implemented".to_string(),
        })
    }
}

/// Generate ephemeral self-signed TLS cert + key for the example.
fn generate_tls_config() -> (TlsConfig, tempfile::TempDir) {
    let ck = rcgen::generate_simple_self_signed(vec!["localhost".to_string()])
        .expect("failed to generate TLS cert");
    let dir = tempfile::tempdir().expect("failed to create temp dir");
    let cert_path = dir.path().join("cert.pem");
    let key_path = dir.path().join("key.pem");
    std::fs::write(&cert_path, ck.cert.pem()).unwrap();
    std::fs::write(&key_path, ck.key_pair.serialize_pem()).unwrap();
    (TlsConfig::server(&cert_path, &key_path), dir)
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt::init();

    let (tls, _cert_dir) = generate_tls_config();

    let server = Server::builder()
        .bind_str("0.0.0.0:4433")?
        .tls(tls)
        .codec(JsonCodec::new())
        .add_service(GreeterService)
        .build()?;

    println!("Starting Knafeh RPC server on 0.0.0.0:4433...");
    server.serve().await?;

    Ok(())
}