wmproxy
已用Rust
實現http/https
代理, socks5
代理, 反向代理, 靜態檔案伺服器,四層TCP/UDP轉發,內網穿透,後續將實現websocket
代理等,會將實現過程分享出來,感興趣的可以一起造個輪子
國內: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
序列化(Serialization)
是指將資料結構或物件狀態轉化為可以儲存或傳輸的形式的過程。
在序列化過程中,物件的成員屬性和型別資訊一起被轉換為一個位元組流或可列印字元流,以便於儲存或網路傳輸。
這個位元組流或字元流可以再次被反序列化(Deserialization)
還原為原始物件狀態。
字元流比如JSON
,位元組流比如ProtoBuf
。
在Rust中序列化最常用且支援最廣的為第三方庫serde
,當前在github
上已有8000顆star
。
常用的比如JSON
庫的serde_json
,比如YAML
,TOML
,BSON
等,依靠serde庫之上,對常用的格式已經有了廣泛的的支援。
在程式碼中,Serde
資料模型的序列化部分由特定義 Serializer
,反序列化部分由特徵定義Deserializer
。這些是將每個 Rust 資料結構對映到 29
種可能型別之一的方法。特徵的每個方法Serializer對應於資料模型的一種型別。
支援基礎型別如常用的布林值,整型,浮點型,字串,位元組流
支援的高階型別,如tuple
,struct
,seq
,enum
可以對映成各種內建的資料結構。
假如用現有的資料格式,如json之類的,可以輕鬆的實現。
Cargo.toml
[package]
name = "wmproxy"
version = "0.1.0"
authors = ["wenmeng <[email protected]>"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
# 這僅僅是測試用例,需要用哪個可以選擇新增
serde_json = "1.0"
src/main.rs
使用Serde的自定義匯出:use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 1, y: 2 };
let serialized = serde_json::to_string(&point).unwrap();
println!("serialized = {}", serialized);
let deserialized: Point = serde_json::from_str(&serialized).unwrap();
println!("deserialized = {:?}", deserialized);
}
以下輸出:
$ cargo run
serialized = {"x":1,"y":2}
deserialized = Point { x: 1, y: 2 }
在使用serde中經常可以看到在欄位前加一些屬性引數,這些是約定該欄位序列化或反序列化時將如何處理的,下面我們看以下的例子:
#[serde(default)]
#[serde(default="???")]
,這裡???
將是一個函數名,不能帶引數,可以直接存取,如Vec::new
可以直接存取的函數。fn default_y() -> i32 {
1024
}
#[derive(Serialize, Deserialize, Debug)]
struct Point {
#[serde(default)]
x: i32,
#[serde(default="default_y")]
y: i32,
}
此時我們反序化一個值時,如果沒有x的引數會將x預設設定成0,如果沒有y引數,將會呼叫default_y函數,也就是y會預設為1024。
#[serde(rename = "name")]
#[serde(rename_all = "...")]
可以將所有的名字結構變成全小寫,或者全大寫之類或者駝峰結構等。#[serde(skip)]
#[serde(skip_serializing)]
跳過序列化和#[serde(skip_deserializing)]
跳過反序列化等。#[serde(flatten)]
CommonConfig
,可以極好的精簡設定結構#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HttpConfig {
#[serde(default = "Vec::new")]
pub server: Vec<ServerConfig>,
#[serde(default = "Vec::new")]
pub upstream: Vec<UpstreamConfig>,
#[serde(flatten)]
#[serde(default = "CommonConfig::new")]
pub comm: CommonConfig,
}
#[serde(with = "module")]
Duration
或者原來是一個字串"4k"表示大小,現在需要把他按資料大小轉成數位4096,就需要自定義的序列化過程。serialize_with
和deserialize_with
,該模組需實現$module::serialize
及$module::deserialize
做對應的序列化和反序列化。以下過程是Rust中的資料結構是如何轉化成目標格式的
Rust (結構體列舉)
↓
-- Serialize(序列化) --> 當前結構體中,有對欄位進行協定說明的,加屬性標記
↓
-- 資料的格式(如JSON/BSON/YAML等) --> 根據對應的輸出庫(serde_json/serde_yaml)輸出相應的位元組流
以下以JSON格式是如何轉化成Rust的結構,在JSON中屬於鍵值對且值有特定的資料格式,其中key將解析成資料結構中的欄位名,值value將根據反序列化可以嘗試解析的型別嘗試是否能轉成目標型別。
比如value值為字串,且反序列反時選擇deserialize_str
,將在反序列化的時候會嘗試呼叫
/// 我們將根據該字串的值能否解析成目標型別,如果失敗返回錯誤
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
}
比如value值為數值,且反序列反時選擇deserialize_i64
,將在反序列化的時候會嘗試呼叫
/// 我們將根據該數值的值能否解析成目標型別,如果失敗返回錯誤
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error, {
}
或者以上兩種格式我們都是支援的,比如時間可以支援數位8
或者"8s"
,此時我們需要同時將數位或者字串同時支援轉成Duration::new(8,0)
,那麼此時我們自定義的反序列化函數可以我選擇deserialize_any
,並分別實現visit_i64
及visit_str
以下是通過標準的Display做輸出及FromStr做反序列化,但是此時我們又需要同時支援數位的處理,首先我們先定義模組
pub struct DisplayFromStrOrNumber;
此時該模組需要實現序列化及反序列化。
實現序列化,將用標準的Display做輸出:
impl<T> SerializeAs<T> for DisplayFromStrOrNumber
where
T: Display,
{
fn serialize_as<S>(source: &T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(source)
}
}
實現反序列化,我們將數位統一轉成字串,然後用FromStr做反序列化:
impl<'de, T> DeserializeAs<'de, T> for DisplayFromStrOrNumber
where
T: FromStr,
T::Err: Display,
{
fn deserialize_as<D>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
{
struct Helper<S>(PhantomData<S>);
impl<'de, S> Visitor<'de> for Helper<S>
where
S: FromStr,
<S as FromStr>::Err: Display,
{
type Value = S;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "a string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
value.parse::<Self::Value>().map_err(de::Error::custom)
}
/// 將數位轉成字串從而能呼叫FromStr函數
fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
where
E: Error, {
format!("{}", v).parse::<Self::Value>().map_err(de::Error::custom)
}
}
deserializer.deserialize_any(Helper(PhantomData))
}
}
此時我們已有了標準模組了,我們只能重新實現類的Display
及FromStr
,由於現有的型別如Duration
我們不能重新實現impl Display for Duration
因為介面Display
和型別Duration
均不是我們定義的,如果我們可以重新實現,那麼此有可能其它第三方庫也實現了,那麼我們在參照的時候可能就有多種實現方法,從而無法確定呼叫函數。
那麼此時我們做一層包裹方法
pub struct ConfigDuration(pub Duration);
此時我們只需要重新實現Display
及FromStr
就可以了
impl FromStr for ConfigDuration {
type Err=io::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.len() == 0 {
return Err(io::Error::new(io::ErrorKind::InvalidInput, ""));
}
let d = if s.ends_with("ms") {
let new = s.trim_end_matches("ms");
let s = new.parse::<u64>().map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, ""))?;
Duration::new(0, (s * 1000_000) as u32)
} else if s.ends_with("h") {
let new = s.trim_end_matches("h");
let s = new.parse::<u64>().map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, ""))?;
Duration::new(s * 3600, 0)
} else if s.ends_with("min") {
let new = s.trim_end_matches("min");
let s = new.parse::<u64>().map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, ""))?;
Duration::new(s * 60, 0)
} else if s.ends_with("s") {
let new = s.trim_end_matches("s");
let s = new.parse::<u64>().map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, ""))?;
Duration::new(s, 0)
} else {
let s = s.parse::<u64>().map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, ""))?;
Duration::new(s, 0)
};
Ok(ConfigDuration(d))
}
}
impl Display for ConfigDuration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ms = self.0.subsec_millis();
let s = self.0.as_secs();
if ms > 0 {
f.write_str(&format!("{}ms", ms as u64 + s * 1000))
} else {
if s >= 3600 && s % 3600 == 0 {
f.write_str(&format!("{}h", s / 3600))
} else if s >= 60 && s % 60 == 0 {
f.write_str(&format!("{}min", s / 60))
} else {
f.write_str(&format!("{}s", s))
}
}
}
}
這樣子我們在加上聲名即可以實現自定義的序列化過程了:
pub struct CommonConfig {
#[serde_as(as = "Option<DisplayFromStrOrNumber>")]
pub rate_limit_per: Option<ConfigDuration>,
}
序列化不管在設定還是在傳輸等過程中,都是必不可少的存在,瞭解序列化及反序列化的過程我們將可以更快的找到切入點去實現自己的功能。
點選 [關注],[在看],[點贊] 是對作者最大的支援