天空游戏网 手游攻略 手游评测 rust上手:用rust写一个thumbor服务器

rust上手:用rust写一个thumbor服务器

时间:2023-01-03 16:38:00 来源:网络 浏览:12

thumbor

在这边文章里面,我们会实现一个类似于python里著名的图片服务器thumbor的thumbor,今天我们来实现其中最核心的功能,对图片进行动态转换。

一、接口的设计

我们需要的是将图片进行的一系列有序操作进行编码存放进入url中,之后也能解码出它原本的样子。这样的有序操作,可以用有序列表(数组)来表示,每个操作就是一个enum,像这样:

// 用数组来储存对图片进行的一系列有序操作
struct ImageSpec {
specs: Vec<Spec>
}
// 操作所支持的类型
enum Spec {
Resize(Resize),
Crop(Crop),
...
}
// 具体类型的数据描述
struct Resize {
width: u32,
height: u32,
}

如何把我们的操作流程进行传输呢?这里我们实现的方式是用prost将操作转化为字节流,然后在用base64编码,这样就可以在url里面传输,服务器接受操作指令过后,先用base64解码,然后用prost解析出一些列有序的操作指令再依次处理。

二、protobuf的定义和编译

先创建项目,然后再添加依赖

>cargo new thumbor && cd thumbor
>cargo add axum anyhow base64 bytes image lazy_static lru percent-encoding photon-rs prost reqwest serde tokio tower tower-http tracing tracing-subscriber prost-build

最后Cargo.toml文件像这样:

[dependencies]anyhow = "1.0.64" # 错误处理axum = "0.5.15" # web服务器base64 = "0.13.0" # base64 编码解码bytes = "1.2.1" # 处理字节流image = "0.24.3" # 处理图片lazy_static = "1.4.0" # 方便初始化静态变量lru = "0.7.8" # lru 缓存percent-encoding = "2.1.0" #url 编码解码photon-rs = "0.3.1" # 图片效果处理prost = "0.11.0" # protobuf 处理reqwest = "0.11.11" # http clientserde = {version = "1.0.144", features = ["derive"]} # 序列化tokio = {version = "1.21.0", features = ["full"]} # 异步处理tower = {version = "0.4.13", features = ["util", "timeout", "load-shed", "limit"]}tower-http ={version = "0.3.4", features = ["add-extension", "compression-full"]}tracing = "0.1.36" # 日志和追踪tracing-subscriber = "0.3.15" #日志和追踪[build-dependencies]prost-build = "0.8" # 编译proto文件的依赖,cargo b 会自动运行build.rs文件

在项目的根目录下创建abi.proto文件,写入我们支持的图片处理服务用到的数据结构。

syntax = "proto3";package abi; // 这个名字会被用作编译结果:abi.rs// 一个ImageSpec是一个有序的数组message ImageSpec { repeated Spec specs =1;}// resize参考photo_re::tansform所支持的操作,后面类似message Resize {    uint32 width = 1;    uint32 height = 2;// 这里的Type和后面一些enum参考photo_rs所支持图片操作    enum ResizeType {        NORMAL = 0;        SEAM_CARVE = 1;    }        ResizeType rtype = 3;    enum SampleFilter {        UNDEFINED = 0;        NEAREST = 1;        TRIANGLE = 2;        CATMULL_ROM = 3;        GAUSSIAN = 4;        LANCZOS3 = 5;    }    SampleFilter filter = 4;}message Crop {    uint32 x1 = 1;    uint32 y1 = 2;    uint32 x2 = 3;    uint32 y2 = 4;}  message Fliph {}message Flipv {}// 对比度message Contrast {    float contrast = 1;}// 滤镜message Filter {    enum Filter {        UNSPECIFIED = 0;        OCEANIC = 1;        ISLANDS = 2;        MARINE = 3;    }    Filter filter = 1;}  // 水印message WaterMark {    uint32 x= 1;    uint32 y= 2;}message Spec {    oneof data {        Resize resize = 1;        Crop crop = 2;        Flipv flipv = 3;        Fliph fliph = 4;        Contrast contrast = 5;        Filter filter = 6;        WaterMark watermark = 7;    }}

然后我们在项目根目录新建一个build.rs文件,用来编译我们的abi.proto,build.rs文件内容如下:

fn main() {
prost_build::Config::new()
.out_dir("src/pb") //输出目录
.compile_protos(&["abi.proto"],&["."])
.unwrap();
}

运行cargo b后会在src/pb目录下生成abi.rs文件。接下来我们创建src/pb/mod.rs,一个目录下的所有代码,可以通过mod.rs声明。在这个文件中,我们引入abi.rs,并定义一些辅助函数。记得在main.rs引入mod pb模块,将文件引入树内。

mod abi;
pub use abi::*;



use base64::{decode_config, encode_config, URL_SAFE_NO_PAD};
use photon_rs::transform::SamplingFilter;
use prost::Message;


impl ImageSpec {
pub fn new(specs: Vec<Spec>) -> Self {
Self { specs }
}
}



// 这里实现ImageSpec和String的相互转化
// 实现ImageSpec -> String 的trait
impl From<&ImageSpec> for String {
fn from(spec: &ImageSpec) -> Self {
let data = spec.encode_to_vec();
encode_config(data, URL_SAFE_NO_PAD)
}
}

// 实现str -> ImageSpec 的trait
impl TryFrom<&str> for ImageSpec {
type Error = anyhow::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
let data = decode_config(value, URL_SAFE_NO_PAD)?;
Ok(Self::decode(&data[..])?)
}
}


// photo_rs相应的方法需要字符串
impl filter::Filter {
pub fn to_str(&self) -> Option<&'static str> {
match self {
filter::Filter::Unspecified => None,
filter::Filter::Oceanic => Some("oceanic"),
filter::Filter::Islands => Some("islands"),
filter::Filter::Marine => Some("marine"),
}
}
}

// 这里把我们自己的SampleFilter转成photo_rs的SamplingFilter
impl From<resize::SampleFilter> for SamplingFilter {
fn from(v: resize::SampleFilter) -> Self {
match v {
resize::SampleFilter::Undefined => SamplingFilter::Nearest,
resize::SampleFilter::Nearest => SamplingFilter::Nearest,
resize::SampleFilter::Triangle => SamplingFilter::Triangle,
resize::SampleFilter::CatmullRom => SamplingFilter::CatmullRom,
resize::SampleFilter::Gaussian => SamplingFilter::Gaussian,
resize::SampleFilter::Lanczos3 => SamplingFilter::Lanczos3,
}
}
}

// 为我们的Spec定义几个使用的函数,方便创建Spec
impl Spec {
pub fn new_resize_seam_carve(width: u32, height: u32) -> Self {
Self {
data: Some(spec::Data::Resize(Resize {
width,
height,
rtype: resize::ResizeType::SeamCarve as i32,
filter: resize::SampleFilter::Undefined as i32,
})),
}
}

pub fn new_resize(width: u32, height: u32,filter: resize::SampleFilter) -> Self {
Self {
data: Some(spec::Data::Resize(Resize {
width,
height,
rtype: resize::ResizeType::Normal as i32,
filter: filter as i32,
})),
}
}

pub fn new_filter(filter: filter::Filter) -> Self {
Self {
data: Some(spec::Data::Filter(Filter {
filter: filter as i32,
})),
}
}

pub fn new_watermark(x: u32, y: u32) -> Self {
Self {
data: Some(spec::Data::Watermark(WaterMark { x, y })),
}
}
}



#[cfg(test)]
mod tests {
use std::{borrow::Borrow, convert::TryInto};
use super::{Spec, resize::SampleFilter, filter, ImageSpec};
// 测试功能的正确性,编码后解码能否解码成原来的样子
#[test]
fn encoded_vec_could_be_decoded() {
let spec1 = Spec::new_resize(600, 600,SampleFilter::CatmullRom);
let spec2 = Spec::new_filter(filter::Filter::Marine);
let image_spec = ImageSpec::new(vec![spec1, spec2]);
let s: String = image_spec.borrow().into();
assert_eq!(image_spec,s.as_str().try_into().unwrap());
}
}

运行cargo t测试一下我们的实现功能的正确性:

> cargo t
Compiling thumbor v0.1.0 (G:\Projects\Rust\thumbor)
Finished test [unoptimized + debuginfo] target(s) in 1.99s
Running unittests src\main.rs (target\debug\deps\thumbor-7d2bb94d80a8d984.exe)

running 1 test
test pb::tests::encoded_vec_could_be_decoded ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

好啦,大功告成,我们已经实现了把一系列有序的操作放入url进行传输,并在接受后能够正确的解析。下一节我们再引入http服务器,麻烦点个关注方便看后续,谢谢阅读,拜拜。

标题:rust上手:用rust写一个thumbor服务器
链接:https://www.skyyx.com/news/sypc/7404.html
版权:文章转载自网络,如有侵权,请联系删除!
资讯推荐
更多
可以插原神人物的游戏手游

嗨,各位游戏玩家们!今天我要为大家介绍一款备受瞩目的游戏——《原神》手游版!作为备受瞩目的单机游戏,《原神》

2023-01-03
lpl各战队主场城市

大家好,我是游戏博主小明,今天要给大家带来的是关于LPL各战队主场城市的攻略。作为一名游戏爱好者,相信大家对

2023-01-03
绯红之境兑换码最新2021 礼包兑换码大全

绯红之境兑换码最新2021 礼包兑换码大全[多图],绯红之境兑换码怎么领取?绯红之境兑换码有哪些?绯红之境在今日

2023-01-03
妄想山海怎么加好友 加好友方法大全

妄想山海怎么加好友 加好友方法大全[多图],妄想山海添加好友功能在哪里?妄想山海添加好友的方法是什么?好友添

2023-01-03