3分鐘瞭解如何將任意格式的 JSON 傳入Rust RPC服務

在 一文中介紹了 RPC 的使用,這是 WebAssembly (Wasm) 執行生態系統的一部分 。 我們從行程間通訊的角度討論了 Wasm,證明了實現分佈式機器之間無處不在的互操作性的前景,這能讓這些計算機通過 RPC 單獨執行離散的 Wasm 函數。

與上篇文章有關的工作還在繼續。 具體來說,RPC 功能已經過渡到 Rust (作為對 C + + 的補充)。 這些變化帶來了一些有趣的問題和新思維,我想與大家分享一下。

我理解 Rust 需要將任何預期的 JSON 顯式映射到 Rust 代碼,然後在代碼成為生產部署的一部分之前進行編譯。 下面的簡單示例演示一個 “person” 數據對象是如何(通過 HTTP POST 發送)能被映射到 Rust 結構。

<code>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
}/<code>

當我著手編寫和編譯描述輸入數據的 Rust 數據結構時,我意識到這種方法存在一些重大的問題。

這種方法“非常複雜”。


3分鐘瞭解如何將任意格式的 JSON 傳入Rust RPC服務


瞭解數據結構

在將數據映射到源代碼時,需要事先了解輸入的數據結構。 而在一些實際的用例中,在設計階段我們不是總能知道輸入的數據結構。


3分鐘瞭解如何將任意格式的 JSON 傳入Rust RPC服務

圖源:澳大利亞墨爾本的 Stephen Edmonds


源代碼中使用的結構數據類型是平面的。 這意味著,如果我們要解析和遍歷嵌套數據,那麼就需要在生產源代碼中構建和維護多個複雜的數據結構,這些數據結構反映了 JSON 數據。

傳入的 JSON 數據可能會發生輕微的更改。 這可能超出你的控制。 這可能取決於數據來自哪裡,或者提供數據的軟件的業務規則 、 計劃是什麼。 重點是,如果 JSON 與我們應用程序的源代碼中的結構緊密地映射在一起,那麼輕微的 JSON 調整可能會成為重大改變,這可能會使我們的應用程序停滯不前; 要求源代碼的變化和產品的重新部署。

一個值得一試的解決方案

下面的解決方案允許 RPC 應用程序接受和解析任意嵌套的 JSON。 下面的示例將顯示,可以使用最少預期數據來部署應用程序。 這個例子使用了 rocket: : Data struct。

我花費了很多時間找到這個解決方案,併成功測試出來。

最終的結果非常簡單: 代碼量很少,並且總共只有兩個依賴項。 我認為值得與 Wasm 社區分享。

你也來試試看

下面的操作指南演示瞭如何創建一個可以接受、識別和處理任意嵌套的 JSON 對象的 Rust RPC 服務器。 系統準備(Ubuntu)

<code>sudo apt-get update
sudo apt-get -y upgrade/<code>

Rust 安裝

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

Rust 配置和整理

<code>rustup override set nightly
rustup update && cargo update/<code>

創建 RPC 項目

<code>cd ~
cargo new rpc
cd rpc/<code>

將 rocket 和 serde_json 依賴項添加到 Cargo.toml

<code>[dependencies]
rocket = "0.4.2"
serde_json = "1.0"/<code>

創建 ~/rpc/src/main.rs (main.rs/) 文件

<code>#![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 = "<bytes>")]
fn add_person(bytes_vec: Data) -> content::Json{
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();
}/<bytes>/<code>

創建應用

<code>cd ~
cd rpc
cargo build --release/<code>

啟動應用

<code>cd ~
cd ~
cd rpc
./target/release/rpc/<code>

使用 HTTP POST 測試

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

這裡,我們沒有傳遞平面數據。相反,我們傳入了一個嵌套的數據結構。

結果

Rust 代碼v [“ person”] [“ name”]能夠將數據遍歷到正確的點(person 對象的名稱鍵值為 John Smith)。

<code>Person: String("John Smith")/<code>

進一步測試

讓我們來談一談更廣泛的應用或數據來源!從這個 JSON 結構中引入或刪除了數據的情況。我們來看看如果更改了數據中與該應用程序不直接相關的部分,該應用程序是否仍然可以繼續工作。 讓我們傳入這個新的完全不同的數據結構。

<code>{
"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
}
]
}
}/<code>

可以看到,這和原始的 JSON 完全不同;age已被刪除,添加了新的鍵值對(例如department),並且還添加了一個新的嵌套對象數組,稱為awards。

調用新數據

<code>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/<code>

新的結果

Rust 代碼 v["person"]["name"] 任意返回

<code>Person: String(“John Smith”)/<code>

可以看到,儘管 JSON 進行了重大更改,但應用程序的原始源代碼仍返回期望/預期的結果。

現在讓我們縮小並查看應用程序如何查看 JSON 對象 person 的根。我們通過將 Rust 代碼從v["person"]["name"] 更新到 v["person"] 然後向 RPC 服務器發出以下 HTTP POST 調用。

<code>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/<code>

結果: 查看整個person對象

println 輸出實際上會自動識別每種單獨的數據類型。

<code>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")})/<code>

我們可以看到,有一個名為 awards 的鍵的 Array 對象,其中包含幾個嵌套對象等。這是對傳入數據的正確解釋。

這裡要注意的是,這些數據都沒有在 Rust 源代碼中定義或提前編譯。

安全

允許調用代碼傳入任意字符串的同時,本質上也是發出接受 DDOS 風格攻擊的公開邀請。 在源代碼中 JSON 和 Rust 結構之間的刻意映射(這可以防止這種情況)是以花費精力和缺乏靈活性為代價的。

我認為上述解決方案是可以解決這兩方面的問題的最好方案。

歡迎大家分享觀點和想法。

作者:Timothy McCallum

鏈接:https://juejin.im/post/5e2180dce51d4577794c09ca


分享到:


相關文章: