将任意格式的JSON传入Rust远程过程调用(RPC)服务

本文作者:Timothy

原文链接:《Passing arbitrary JSON into a Rust Remote Procedure Call (RPC) service》(https://medium.com/wasm/passing-arbitrary-json-into-a-rust-remote-procedure-call-rpc-service-54f3b3ec04e0)

本文主要讨论了如何将任意格式的 JSON 传入 Rust 远程过程调用(RPC)服务中的问题,分析了将 JSON 显示映射到 Rust 代码存在的缺陷,以及允许 RPC 应用程序接受、解析任意嵌套的 JSON的解决方案。

在《去中心化计算的未来:通过 RPC 从微服务过渡到 WASM》一文中介绍了 RPC 的使用,这是 WebAssembly (Wasm) 执行生态系统的一部分 。 我们从行程间通讯的角度讨论了 Wasm,证明了实现分布式机器之间无处不在的互操作性的前景,这能让这些计算机通过 RPC 单独执行离散的 Wasm 函数。

与上篇文章有关的工作还在继续。 具体来说,RPC 功能已经过渡到 Rust (作为对 C 的补充)。 这些变化带来了一些有趣的问题和新思维,我想与大家分享一下。

我理解 Rust 需要将任何预期的 JSON 显式映射到 Rust 代码,然后在代码成为生产部署的一部分之前进行编译。 下面的简单示例演示一个 “person” 数据对象是如何(通过 HTTP POST 发送)能被映射到 Rust 结构。

curl --header "Content-Type: application/json" --request POST --data '{ "name": "John Smith", "age": 19 }' localhost:8000/create_person

pub struct Person{
    pub name: String,
    pub age: i32
}

当我着手编写和编译描述输入数据的 Rust 数据结构时,我意识到这种方法存在一些重大的问题。

这种方法“非常复杂”。

了解数据结构

在将数据映射到源代码时,需要事先了解输入的数据结构。 而在一些实际的用例中,在设计阶段我们不是总能知道输入的数据结构。

图源:澳大利亚墨尔本的 Stephen Edmonds

源代码中使用的结构数据类型是平面的。 这意味着,如果我们要解析和遍历嵌套数据,那么就需要在生产源代码中构建和维护多个复杂的数据结构,这些数据结构反映了 JSON 数据。

传入的 JSON 数据可能会发生轻微的更改。 这可能超出你的控制。 这可能取决于数据来自哪里,或者提供数据的软件的业务规则 、 计划是什么。 重点是,如果 JSON 与我们应用程序的源代码中的结构紧密地映射在一起,那么轻微的 JSON 调整可能会成为重大改变,这可能会使我们的应用程序停滞不前; 要求源代码的变化和产品的重新部署。

一个值得一试的解决方案

下面的解决方案允许 RPC 应用程序接受和解析任意嵌套的 JSON。 下面的示例将显示,可以使用最少预期数据来部署应用程序。 这个例子使用了 rocket: : Data struct

我花费了很多时间找到这个解决方案,并成功测试出来。 最终的结果非常简单: 代码量很少,并且总共只有两个依赖项。 因此,我认为值得与 Wasm 社区分享。

你也来试试看

下面的操作指南演示了如何创建一个可以接受、识别和处理任意嵌套的 JSON 对象的 Rust RPC 服务器。系统准备(Ubuntu)

sudo apt-get update
sudo apt-get -y upgrade

Rust安装

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env

Rust配置和整理

rustup override set nightly
rustup update && cargo update

创建RPC项目

cd ~
cargo new rpc
cd rpc

将 rocket 和 serde_json 依赖项添加到 Cargo.toml

[dependencies]
rocket = "0.4.2"
serde_json = "1.0"

创建~/rpc/src/main.rs (http://main.rs/) 文件

#![feature(proc_macro_hygiene, decl_macro)]
use std::str;
use rocket::Data;
use serde_json::{Value};
use rocket::response::content;
#[macro_use] extern crate rocket;
#[post("/add_person", data = "")]
fn add_person(bytes_vec: Data) -> content::Json<&'static str>{
    if bytes_vec.peek_complete() {
        let string_text = str::from_utf8(&bytes_vec.peek()).unwrap();
        let v: Value = serde_json::from_str(string_text).unwrap();
        println!("Person: {:?}", v["person"]["name"]);
    }
    content::Json("{'response':'success'}")
}
fn main() {
    rocket::ignite().mount("/", routes![add_person]).launch();
}

创建应用

cd ~
cd rpc
cargo build --release

启动应用

cd ~
cd ~
cd rpc
./target/release/rpc

使用 HTTP POST 测试

curl --header "Content-Type: application/json" --request POST --data '{"person": { "name": "John Smith", "age": 19 }}' http://localhost:8000/add_person

您可能注意到,我们没有传递平面数据。我们传入了一个嵌套的数据结构。

结果

您会注意到,Rust 代码v [“ person”] [“ name”]能够将数据遍历到正确的点(person 对象的名称键值为 John Smith)。

Person: String("John Smith")

进一步测试

让我们来谈一谈更广泛的应用或数据来源!从这个 JSON 结构中引入或删除了数据的情况。我们来看看如果更改了数据中与该应用程序不直接相关的部分,该应用程序是否仍然可以继续工作。让我们传入这个新的完全不同的数据结构。

{
 "person": {
  "id": 512279332,
  "name": "John Smith",
  "occupation": "Sales Person",
  "department": "Furniture",
  "awards": [{
    "name": "November sales person of the month",
    "date_issued": 20191123
   },
   {
    "name": "December sales person of the month",
    "date_issued": 20191223
   }
  ]
 }
}

可以看到,这和原始的 JSON 完全不同;age已被删除,添加了新的键值对(例如department),并且还添加了一个新的嵌套对象数组,称为awards

调用新数据

curl --header "Content-Type: application/json" --request POST --data '{"person": {"id": 512279332,"name": "John Smith","occupation": "Sales Person","department": "Furniture","awards": {"name": "Sales person of the month","date_issued": 20191123}}}' http://localhost:8000/add_person

新的结果

Rust 代码 v["person"]["name"]任然返回

Person: String(“John Smith”)

可以看到,尽管 JSON 进行了重大更改,但应用程序的原始源代码仍返回期望/预期的结果。

现在让我们缩小并查看应用程序如何查看 JSON 对象 person 的根。我们通过将 Rust 代码从v["person"]["name"]更新到 v["person"]然后向 RPC 服务器发出以下 HTTP POST 调用。

curl --header "Content-Type: application/json" --request POST --data '{
 "person": {
  "id": 512279332,
  "name": "John Smith",
  "occupation": "Sales Person",
  "department": "Furniture",
  "awards": [{
    "name": "November sales person of the month",
    "date_issued": 20191123
   },
   {
    "name": "December sales person of the month",
    "date_issued": 20191223
   }
  ]
 }
}' http://localhost:8000/add_person

结果; 查看整个person对象

可以看到,println 输出实际上会自动识别每种单独的数据类型。

Person: Object({"awards": Array([Object({"date_issued": Number(20191123), "name": String("November sales person of the month")}), Object({"date_issued": Number(20191223), "name": String("December sales person of the month")})]), "department": String("Furniture"), "id": Number(512279332), "name": String("John Smith"), "occupation": String("Sales Person")})

我们可以看到,有一个名为 awards 的键的 Array 对象,其中包含几个嵌套对象等。这是对传入数据的正确解释。

这里要注意的是,这些数据都没有在 Rust 源代码中定义或提前编译。

安全

允许调用代码传入任意字符串的同时,本质上也是发出接受 DDOS 风格攻击的公开邀请。 在源代码中 JSON 和 Rust 结构之间的刻意映射(这可以防止这种情况)是以花费精力和缺乏灵活性为代价的。 我认为上述解决方案是可以解决这两方面的问题的最好方案。 欢迎大家分享观点和想法。

你可能感兴趣的:(将任意格式的JSON传入Rust远程过程调用(RPC)服务)