首页 > 系统相关 >25从零开始用Rust编写nginx,序列化之serde是如何工作的

25从零开始用Rust编写nginx,序列化之serde是如何工作的

时间:2024-01-25 16:31:41浏览次数:32  
标签:25 serde emsp let str new 序列化

wmproxy

wmproxy已用Rust实现http/https代理, socks5代理, 反向代理, 静态文件服务器,四层TCP/UDP转发,内网穿透,后续将实现websocket代理等,会将实现过程分享出来,感兴趣的可以一起造个轮子

项目地址

国内: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

序列化

  序列化(Serialization)是指将数据结构或对象状态转化为可以存储或传输的形式的过程。

  在序列化过程中,对象的成员属性和类型信息一起被转换为一个字节流或可打印字符流,以便于存储或网络传输。

  这个字节流或字符流可以再次被反序列化(Deserialization)还原为原始对象状态。

  字符流比如JSON,字节流比如ProtoBuf

Rust中的序列化

  在Rust中序列化最常用且支持最广的为第三方库serde,当前在github上已有8000颗star

  常用的比如JSON库的serde_json,比如YAMLTOMLBSON等,依靠serde库之上,对常用的格式已经有了广泛的的支持。

  在代码中,Serde数据模型的序列化部分由特定义 Serializer,反序列化部分由特征定义Deserializer。这些是将每个 Rust 数据结构映射到 29 种可能类型之一的方法。特征的每个方法Serializer对应于数据模型的一种类型。

  支持基础类型如常用的布尔值,整型,浮点型,字符串,字节流

  支持的高级类型,如tuplestructseqenum可以映射成各种内置的数据结构。

如何使用serde

假如用现有的数据格式,如json之类的,可以轻松的实现。

  1. 配置Cargo.toml
[package]
name = "wmproxy"
version = "0.1.0"
authors = ["wenmeng <[email protected]>"]

[dependencies]
serde = { version = "1.0", features = ["derive"] }

# 这仅仅是测试用例,需要用哪个可以选择添加
serde_json = "1.0"
  1. 现在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中经常可以看到在字段前加一些属性参数,这些是约定该字段序列化或反序列化时将如何处理的,下面我们看以下的例子:

  • #[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_withdeserialize_with,该模块需实现$module::serialize$module::deserialize做对应的序列化和反序列化。

serde的工作原理

序列化

以下过程是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_i64visit_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))
    }
}

&emsp;&emsp;此时我们已有了标准模块了,我们只能重新实现类的DisplayFromStr,由于现有的类型如Duration我们不能重新实现impl Display for Duration因为接口Display和类型Duration均不是我们定义的,如果我们可以重新实现,那么此有可能其它第三方库也实现了,那么我们在引用的时候可能就有多种实现方法,从而无法确定调用函数。

&emsp;&emsp;那么此时我们做一层包裹方法

pub struct ConfigDuration(pub Duration);

此时我们只需要重新实现DisplayFromStr就可以了

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>,
}

结语

序列化不管在配置还是在传输等过程中,都是必不可少的存在,了解序列化及反序列化的过程我们将可以更快的找到切入点去实现自己的功能。

点击 [关注][在看][点赞] 是对作者最大的支持

标签:25,serde,emsp,let,str,new,序列化
From: https://blog.51cto.com/u_16321542/9415608

相关文章

  • 1.25
    不知不觉已经回家12天了 and 啥也没干 是时候重新计划一下了!!!距离开学还有正好一个月30天 今天腊月15 估计腊月二十九之前要除去几天 2526272829 五天+2天与朋友亲戚之类的聚会 7年后 正月1234515+1天走亲戚之类的活动730-14=16±2 也就是......
  • 20240125打卡——《构建之法》读书笔记第1~4章
    第一章概论在这一章中,作者为我们介绍了一些关于软件工程的基本知识。①软件=程序+软件工程:正是因为对软件开发活动(构建管理、源代码管理、软件设计、软件测试、项目管理)相关的内容的完成,才能完成把整个程序转化成为一个可用的软件的过程。扩展的推论:软件企业=软件+商业模式......
  • 1.25学习进度
    1.rdd的数据是过程数据rdd之间进行相互迭代计算,当执行开启后,新rdd的产生,代表老rdd的消失rdd的数据是过程数据,只在处理的过程中存在,一旦处理完成,就不见了这样可以最大化的利用资源2.rdd的缓存sparkt提供了缓存api,可以让我们通过调用api,将指定的rdd数据保留在内存或者硬盘上缓存特点......
  • 如何手工制作绿色免安装单文件同花顺免费版Windows客户端 2024-01-25
    如何手工制作绿色免安装单文件同花顺免费版Windows客户端  2024-01-25第1步、下载同花顺免费版http://download.10jqka.com.cn/第2步、安装同花顺免费版第3步、移动同花顺免费版软件到文件夹 D:\Prog\同花顺第4步、新建批处理脚本文件 D:\Prog\同花顺\一键打包\一键打......
  • 2024-1-25HTTP协议 - 请求报文
    目录HTTP协议-请求报文HTTP协议-请求报文HTTP协议:规定了浏览器发送及服务器返回内容的格式请求报文:浏览器按照HTTP协议要求的格式,发送给服务器的内容总结:1.浏览器发送给服务器的内容叫做请求报文2.请求报文的组成3.查看请求报文......
  • 2024-1-25axios错误处理
    目录axios错误处理axios错误处理该错误是当时在POST案例出现的,当提交过一次用户后再次提交出现了报错。场景:再次注册相同的账号,会遇到错误信息处理:用更直观的方式,给普通用户展示错误信息错误处理固定格式语法:在then方法的后面,通过点语法调用catch方法,传入回调函数error并定......
  • 2024-1-25常见请求方法和数据提交
    目录常见请求方法和数据提交常见请求方法axios请求配置这里提供一个案例来学习addEventListener方法总结常见请求方法和数据提交常见请求方法什么是请求方法请求方法:对服务器资源,要执行的操作请求方法操作GET获取数据POST提交数据PUT修改数据(全部)DELETE......
  • day25 代码随想录算法训练营 216. 组合总和 III
    题目:216.组合总和III我的感悟:还是按照之前的套路来。多了一个参数path_sum应该是有两处剪枝,1处横线剪枝,1处纵向剪枝?或者说1处求和剪枝?1处范围剪枝?【疑问】理解难点:不剪枝的已经模的差不多了,剪枝的再看看 自己听了一遍写的:[未剪枝]classSolution:defcombina......
  • Jackson+Feign反序列化问题排查
    概述本文记录在使用SpringCloud微服务开发时遇到的一个反序列化问题,RPC/HTTP框架使用的是Feign,JSON序列化反序列化工具是Jackson。问题测试环境的ELK告警日志如下:-[43f42bf7]500ServerErrorforHTTPPOST"/api/open/dialog/nextQuestion"feign.codec.DecodeException:......
  • POJ 2531(DFS)
    POJ2531题目大意,一共N个网络节点,每个节点与其他节点通信需要消耗流量,把这n个节点分为AB两个集合,使得A集合与B集合之间的通讯流量的总和最大。输入,N+N行N个的数据,输出最大的流量(N<=20)3050305004030400思路:假设最开始所有的都在B集合,通过dfs搜索,将数量从1-......