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
版权:文章转载自网络,如有侵权,请联系删除!
资讯推荐
更多