xrpc

Crates.ioxrpc
lib.rsxrpc
version0.1.40
created_at2025-10-24 13:24:26.316336+00
updated_at2025-12-08 04:03:54.152264+00
descriptionTransport-agnostic RPC framework / 传输层无关的RPC框架
homepagehttps://github.com/i18n-site/rust/tree/dev/xrpc
repositoryhttps://github.com/i18n-site/rust.git
max_upload_size
id1898418
size112,968
i18n.site (i18nsite)

documentation

README

English | 中文


xrpc: A Transport-Agnostic RPC Framework

A framework for building transport-agnostic RPC services. It provides a set of traits and structures to abstract the underlying communication protocol, allowing developers to focus on business logic.

Table of Contents

Design Philosophy

The core idea behind xrpc is the separation of concerns. An RPC call is broken down into distinct stages:

  1. Request Reception & Parsing: The transport layer (e.g., gRPC, HTTP) receives a request and parses it into arguments.
  2. Business Logic Execution: The core logic of the RPC method is executed.
  3. Response Generation: The result of the business logic is converted back into a transport-specific response.

xrpc provides traits to standardize the second stage, while offering hooks and adapters for the first and third. This makes the core application logic independent of the transport layer. The call! macro automates logging, timing, and error handling, further simplifying the developer's task.

Core Concepts

The primary components of xrpc are exposed in src/lib.rs.

  • trait Func: Defines the basic signature of an RPC function, associating Args and a Result type.
  • trait Call / trait AsyncCall: Implement these traits for your synchronous or asynchronous RPC functions. You define the inner method, and the call method (provided by the call! macro) wraps it with logging, error handling, and metrics.
  • struct ReqArgs: A wrapper that provides the inner method with the request context (including headers) and the parsed arguments.
  • trait Req: Represents an incoming request, providing a method ext to access request-scoped extension data.
  • trait Ext: A trait for extracting and initializing request-scoped data, such as user sessions or database connections, in a lazy-loaded fashion.
  • enum Result<T>: A specialized result type for inner methods. It can be:
    • Ok(T): The operation was successful.
    • Err(anyhow::Error): A generic error occurred. The framework will automatically convert this to a standard 500-level error response.
    • Response(Response): The method needs to return a specific error response (e.g., a 404 Not Found).
  • struct Response: Represents a direct error response with a status code and body.

Usage Example

Since the project does not contain a tests/ directory, here is a conceptual example of how to define and use an RPC service with xrpc and volo-grpc.

First, define your service and its methods in a .proto file:

syntax = "proto3";

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}

service Greeter {
    rpc SayHello(HelloRequest) returns (HelloResponse);
}

Next, implement the AsyncCall trait for your SayHello RPC:

use xrpc::{AsyncCall, Func, ReqArgs, Result};
use your_generated_types::{HelloRequest, HelloResponse}; // Assuming types generated by prost/pilota

pub struct SayHello;

impl Func for SayHello {
    type Args = HelloRequest;
    type Result = HelloResponse;

    fn name() -> &'static str {
        "SayHello"
    }
}

impl AsyncCall for SayHello {
    async fn inner<H: Map, E: Ext>(req_args: ReqArgs<H, E, Self::Args>) -> Result<Self::Result> {
        let message = format!("Hello, {}!", req_args.args.name);
        Result::Ok(HelloResponse { message })
    }
}

Finally, integrate it into your volo-grpc service implementation:

use volo_grpc::Request;
use xrpc::volo::grpc::{split, IntoResponse};
use xrpc::Call; // Use Call or AsyncCall

// Assuming a volo service struct
pub struct MyService;

#[volo::async_trait]
impl Greeter for MyService {
    async fn say_hello(&self, req: Request<HelloRequest>) -> Result<Response<HelloResponse>, Status> {
        // 1. Split the volo request into xrpc parts
        let (req, args) = split(req);
        
        // 2. Execute the xrpc call
        let result = SayHello::call::<MyLogger, _, _>((&req, args)).await;

        // 3. Convert the xrpc result back into a volo response
        result.into_response()
    }
}

Directory Structure

/
├── Cargo.toml       # Package manifest
├── AGENTS.md        # Agent instructions
├── src/             # Source code
│   ├── lib.rs       # Main library file, exports public APIs
│   ├── call.rs      # Defines the core `Call` and `AsyncCall` traits
│   ├── response.rs  # Defines the `Response` struct for direct error returns
│   ├── result.rs    # Defines the `Result` enum for RPC outcomes
│   └── volo/        # Adapters for the Volo framework
│       ├── mod.rs
│       ├── grpc.rs  # gRPC-specific helpers
│       └── http.rs  # HTTP-specific helpers
└── readme/          # README files
    ├── en.md        # English README
    └── zh.md        # Chinese README

A Brief History of RPC

Remote Procedure Calls (RPC) are a cornerstone of distributed systems. The concept originated in the 1970s, with one of the first major implementations being at Xerox PARC. The idea was simple yet powerful: make a function call on a remote machine look and feel like a local one.

The 1980s popularized the term, largely thanks to Sun Microsystems and their implementation used in the Network File System (NFS). This era was dominated by technologies like SunRPC (later ONC RPC) and DCE/RPC.

The rise of the web in the 2000s brought RPC to HTTP, with protocols like XML-RPC and SOAP (Simple Object Access Protocol). These used text-based formats (XML) for communication, which offered interoperability at the cost of performance.

Modern RPC, driven by the microservices boom, has shifted back towards high-performance binary protocols. Frameworks like Google's gRPC (which uses Protocol Buffers) and Apache Thrift dominate the landscape. They offer features essential for modern distributed applications, such as efficient serialization, streaming, and language independence, continuing the evolution of that initial idea from the 1970s.


About

This project is an open-source component of i18n.site ⋅ Internationalization Solution.


xrpc:传输层无关的RPC框架

一个用于构建传输层无关RPC服务的框架。它提供一系列特性和结构来抽象底层通信协议,使开发者能专注于业务逻辑。

目录

设计思路

xrpc 的核心思想是关注点分离。一个RPC调用被分解为几个不同阶段:

  1. 请求接收与解析:传输层(如gRPC、HTTP)接收请求并将其解析为参数。
  2. 业务逻辑执行:执行RPC方法的核心逻辑。
  3. 响应生成:将业务逻辑的结果转换回传输层特定的响应格式。

xrpc 提供 traits 来标准化第二阶段,同时为第一和第三阶段提供钩子和适配器。这使得核心应用逻辑独立于传输层。call! 宏自动处理日志、计时和错误处理,进一步简化了开发者的任务。

核心概念

xrpc 的主要组件在 src/lib.rs 中导出。

  • trait Func:定义RPC函数的基本签名,关联 ArgsResult 类型。
  • trait Call / trait AsyncCall:为同步或异步RPC函数实现这些 trait。你只需定义 inner 方法,而 call 方法(由 call! 宏提供)会用日志、错误处理和度量来包装它。
  • struct ReqArgs:一个包装器,为 inner 方法提供请求上下文(包括头信息)和已解析的参数。
  • trait Req:代表传入的请求,提供 ext 方法以访问请求范围的扩展数据。
  • trait Ext:用于以懒加载方式提取和初始化请求范围数据的 trait,例如用户会话或数据库连接。
  • enum Result<T>:为 inner 方法设计的专用结果类型。它可以是:
    • Ok(T):操作成功。
    • Err(anyhow::Error):发生通用错误。框架会自动将其转换为标准的500级别错误响应。
    • Response(Response):方法需要返回一个特定的错误响应(例如,404 Not Found)。
  • struct Response:代表一个带有状态码和消息体的直接错误响应。

使用演示

由于项目不包含 tests/ 目录,这里是一个如何使用 xrpcvolo-grpc 定义和使用RPC服务的概念性示例。

首先,在 .proto 文件中定义你的服务及其方法:

syntax = "proto3";

message HelloRequest {
    string name = 1;
}

message HelloResponse {
    string message = 1;
}

service Greeter {
    rpc SayHello(HelloRequest) returns (HelloResponse);
}

接下来,为 SayHello RPC实现 AsyncCall trait:

use xrpc::{AsyncCall, Func, ReqArgs, Result};
use your_generated_types::{HelloRequest, HelloResponse}; // 假设是由 prost/pilota 生成的类型

pub struct SayHello;

impl Func for SayHello {
    type Args = HelloRequest;
    type Result = HelloResponse;

    fn name() -> &'static str {
        "SayHello"
    }
}

impl AsyncCall for SayHello {
    async fn inner<H: Map, E: Ext>(req_args: ReqArgs<H, E, Self::Args>) -> Result<Self::Result> {
        let message = format!("Hello, {}!", req_args.args.name);
        Result::Ok(HelloResponse { message })
    }
}

最后,将其集成到你的 volo-grpc 服务实现中:

use volo_grpc::Request;
use xrpc::volo::grpc::{split, IntoResponse};
use xrpc::Call; // 根据情况使用 Call 或 AsyncCall

// 假设的服务结构体
pub struct MyService;

#[volo::async_trait]
impl Greeter for MyService {
    async fn say_hello(&self, req: Request<HelloRequest>) -> Result<Response<HelloResponse>, Status> {
        // 1. 将 volo 请求拆分为 xrpc 部件
        let (req, args) = split(req);
        
        // 2. 执行 xrpc 调用
        let result = SayHello::call::<MyLogger, _, _>((&req, args)).await;

        // 3. 将 xrpc 结果转换回 volo 响应
        result.into_response()
    }
}

目录结构

/
├── Cargo.toml       # 包配置
├── AGENTS.md        # Agent 指令
├── src/             # 源代码
│   ├── lib.rs       # 主库文件,导出公共API
│   ├── call.rs      # 定义核心的 `Call` 和 `AsyncCall` traits
│   ├── response.rs  # 定义用于直接错误返回的 `Response` 结构体
│   ├── result.rs    # 定义用于RPC结果的 `Result` 枚举
│   └── volo/        # Volo 框架的适配器
│       ├── mod.rs
│       ├── grpc.rs  # gRPC 相关辅助函数
│       └── http.rs  # HTTP 相关辅助函数
└── readme/          # README 文件
    ├── en.md        # 英文 README
    └── zh.md        # 中文 README

RPC简史

远程过程调用(RPC)是分布式系统的基石。其概念起源于1970年代,最早的重要实现之一是在施乐帕洛阿尔托研究中心(Xerox PARC)。这个想法简单而强大:让远程机器上的函数调用看起来和感觉上都像本地调用一样。

1980年代,这个术语开始普及,主要归功于Sun Microsystems及其在网络文件系统(NFS)中的实现。那个时代由SunRPC(后来的ONC RPC)和DCE/RPC等技术主导。

2000年代互联网的兴起将RPC带入了HTTP,催生了XML-RPC和SOAP(简单对象访问协议)等协议。它们使用基于文本的格式(XML)进行通信,以性能为代价换取了互操作性。

在微服务浪潮的推动下,现代RPC已回归到高性能的二进制协议。像Google的gRPC(使用Protocol Buffers)和Apache Thrift等框架占据了主导地位。它们为现代分布式应用提供了必不可少的特性,如高效序列化、流处理和语言无关性,延续了1970年代最初那个想法的演进之路。


关于

本项目为 i18n.site ⋅ 国际化解决方案 的开源组件。

Commit count: 68

cargo fmt