Rust:axum学习笔记(4) 上传文件

上一篇继续,上传文件是 web开发中的常用功能,本文将演示axum如何实现图片上传(注:其它类型的文件原理相同),一般来说要考虑以下几个因素:

  1. 文件上传的大小限制

  2. 文件上传的类型限制(仅限指定类型:比如图片)

  3. 防止伪装mimetype进行攻击(比如:把.js文件改后缀变成.jpg伪装图片上传,早期有很多这类攻击)

另外,上传图片后,还可以让浏览器重定向到上传后的图片(当然,仅仅只是演示技术实现,实际应用中并非一定要这样)

先展示一个简单的上传文件的表单:

cpp;collapse:true;;gutter:true; // 上传表单 async fn show_upload() -> Html { Html( r#"</p> <pre><code> 上传文件(仅支持图片上传) 上传文件(仅支持图片上传): 上传文件 "#, ) </code></pre> <p>}</p> <pre><code> 上传后,用/save_image来处理图片上传 ;collapse:true;;gutter:true;
// 上传图片
async fn save_image(
ContentLengthLimit(mut multipart): ContentLengthLimit<
Multipart,
{
1024 * 1024 * 20 //20M
},
>,
) -> Result {
if let Some(file) = multipart.next_field().await.unwrap() {
//文件类型
let content_type = file.content_type().unwrap().to_string();

//校验是否为图片(出于安全考虑)
if content_type.starts_with("image/") {
//根据文件类型生成随机文件名(出于安全考虑)
let rnd = (random::() * 1000000000 as f32) as i32;
//提取"/"的index位置
let index = content_type
.find("/")
.map(|i| i)
.unwrap_or(usize::max_value());
//文件扩展名
let mut ext_name = "xxx";
if index != usize::max_value() {
ext_name = &content_type[index + 1..];
}
//最终保存在服务器上的文件名
let save_filename = format!("{}/{}.{}", SAVE_FILE_BASE_PATH, rnd, ext_name);

//文件内容
let data = file.bytes().await.unwrap();

//辅助日志
println!("filename:{},content_type:{}", save_filename, content_type);

//保存上传的文件
tokio::fs::write(&save_filename, &data)
.await
.map_err(|err| err.to_string())?;

//上传成功后,显示上传后的图片
return redirect(format!("/show_image/{}.{}", rnd, ext_name)).await;
}
}

//正常情况,走不到这里来
println!("{}", "没有上传文件或文件格式不对");

//当上传的文件类型不对时,下面的重定向有时候会失败(感觉是axum的bug)
return redirect(format!("/upload")).await;
}

上面的代码,如果上传成功,将自动跳转到/show_image来展示图片:

cpp;collapse:true;;gutter:true; /*<em> * 显示图片 </em>/ async fn show_image(Path(id): Path) -> (HeaderMap, Vec) { let index = id.find(".").map(|i| i).unwrap_or(usize::max_value()); //文件扩展名 let mut ext_name = "xxx"; if index != usize::max_value() { ext_name = &id[index + 1..]; } let content_type = format!("image/{}", ext_name); let mut headers = HeaderMap::new(); headers.insert( HeaderName::from_static("content-type"), HeaderValue::from_str(&content_type).unwrap(), ); let file_name = format!("{}/{}", SAVE_FILE_BASE_PATH, id); (headers, read(&file_name).unwrap()) }</p> <p>/*<em> * 重定向 </em>/ async fn redirect(path: String) -> Result { let mut headers = HeaderMap::new(); //重设LOCATION,跳到新页面 headers.insert( axum::http::header::LOCATION, HeaderValue::from_str(&path).unwrap(), ); //302重定向 Ok((StatusCode::FOUND, headers)) }</p> <pre><code> 最后是路由设置: ;collapse:true;;gutter:true;
#[tokio::main]
async fn main() {
// Set the RUST_LOG, if it hasn’t been explicitly defined
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "example_sse=debug,tower_http=debug")
}
tracing_subscriber::fmt::init();

// our router
let app = Router::new()
.route("/upload", get(show_upload))
.route("/save_image",post(save_image))
.route("/show_image/:id", get(show_image))
.layer(TraceLayer::new_for_http());

let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

// run it with hyper on localhost:3000
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}

运行效果:

  1. 初始上传表单:

Rust:axum学习笔记(4) 上传文件
  1. 文件尺寸太大时

Rust:axum学习笔记(4) 上传文件
3.文件类型不对时

从输出日志上看

2022-01-23T03:56:33.381051Z DEBUG request{method=POST uri=/save_image version=HTTP/1.1}: tower_http::trace::on_request: started processing request
没有上传文件或文件格式不对
2022-01-23T03:56:33.381581Z DEBUG request{method=POST uri=/save_image version=HTTP/1.1}: tower_http::trace::on_response: finished processing request latency=0 ms status=302

已经正确处理,并发生了302重定向,但是浏览器里会报错connection_reset(不知道是不是axum的bug)

Rust:axum学习笔记(4) 上传文件
  1. 成功上传后

Rust:axum学习笔记(4) 上传文件

最后附上完整代码:

cargo.xml

html;collapse:true;;gutter:true; [package] name = "uploadfile" version = "0.1.0" edition = "2021"</p> <p>See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html</p> <p>[dependencies] axum = {version = "0.4.3", features = ["multipart","headers"] } tokio = { version = "1.0", features = ["full"]} rand = "0.7.3" tower-http = { version = "0.2.0", features = ["fs", "trace"] } futures = "0.3" tokio-stream = "0.1" headers = "0.3" tracing = "0.1" tracing-subscriber = { version="0.3", features = ["env-filter"] }</p> <pre><code> main.rs ;collapse:true;;gutter:true;
use axum::{
extract::{ContentLengthLimit, Multipart, Path},
http::header::{HeaderMap, HeaderName, HeaderValue},
http::StatusCode,
response::Html,
routing::{get,post},
Router,
};

use rand::prelude::random;
use std::fs::read;
use std::net::SocketAddr;
use tower_http::trace::TraceLayer;

const SAVE_FILE_BASE_PATH: &str = "/Users/jimmy/Downloads/upload";

// 上传表单
async fn show_upload() -> Html {
Html(
r#"

上传文件(仅支持图片上传)

上传文件(仅支持图片上传):

上传文件

"#,
)
}

// 上传图片
async fn save_image(
ContentLengthLimit(mut multipart): ContentLengthLimit<
Multipart,
{
1024 * 1024 * 20 //20M
},
>,
) -> Result {
if let Some(file) = multipart.next_field().await.unwrap() {
//文件类型
let content_type = file.content_type().unwrap().to_string();

//校验是否为图片(出于安全考虑)
if content_type.starts_with("image/") {
//根据文件类型生成随机文件名(出于安全考虑)
let rnd = (random::() * 1000000000 as f32) as i32;
//提取"/"的index位置
let index = content_type
.find("/")
.map(|i| i)
.unwrap_or(usize::max_value());
//文件扩展名
let mut ext_name = "xxx";
if index != usize::max_value() {
ext_name = &content_type[index + 1..];
}
//最终保存在服务器上的文件名
let save_filename = format!("{}/{}.{}", SAVE_FILE_BASE_PATH, rnd, ext_name);

//文件内容
let data = file.bytes().await.unwrap();

//辅助日志
println!("filename:{},content_type:{}", save_filename, content_type);

//保存上传的文件
tokio::fs::write(&save_filename, &data)
.await
.map_err(|err| err.to_string())?;

//上传成功后,显示上传后的图片
return redirect(format!("/show_image/{}.{}", rnd, ext_name)).await;
}
}

//正常情况,走不到这里来
println!("{}", "没有上传文件或文件格式不对");

//当上传的文件类型不对时,下面的重定向有时候会失败(感觉是axum的bug)
return redirect(format!("/upload")).await;
}

/**
* 显示图片
*/
async fn show_image(Path(id): Path) -> (HeaderMap, Vec) {
let index = id.find(".").map(|i| i).unwrap_or(usize::max_value());
//文件扩展名
let mut ext_name = "xxx";
if index != usize::max_value() {
ext_name = &id[index + 1..];
}
let content_type = format!("image/{}", ext_name);
let mut headers = HeaderMap::new();
headers.insert(
HeaderName::from_static("content-type"),
HeaderValue::from_str(&content_type).unwrap(),
);
let file_name = format!("{}/{}", SAVE_FILE_BASE_PATH, id);
(headers, read(&file_name).unwrap())
}

/**
* 重定向
*/
async fn redirect(path: String) -> Result {
let mut headers = HeaderMap::new();
//重设LOCATION,跳到新页面
headers.insert(
axum::http::header::LOCATION,
HeaderValue::from_str(&path).unwrap(),
);
//302重定向
Ok((StatusCode::FOUND, headers))
}

#[tokio::main]
async fn main() {
// Set the RUST_LOG, if it hasn’t been explicitly defined
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "example_sse=debug,tower_http=debug")
}
tracing_subscriber::fmt::init();

// our router
let app = Router::new()
.route("/upload", get(show_upload))
.route("/save_image",post(save_image))
.route("/show_image/:id", get(show_image))
.layer(TraceLayer::new_for_http());

let addr = SocketAddr::from(([127, 0, 0, 1], 3000));

// run it with hyper on localhost:3000
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}

Original: https://www.cnblogs.com/yjmyzz/p/axum_tutorial_4_upload_file.html
Author: 菩提树下的杨过
Title: Rust:axum学习笔记(4) 上传文件

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/546623/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • skia 图形矩阵转换

    SkiaSharp 中的矩阵转换 下载示例 利用多样的转换矩阵深入了解 SkiaSharp 转换 应用于该对象的所有转换 SKCanvas 都在结构的单个实例中合并 SKMatri…

    技术杂谈 2023年5月31日
    0113
  • lamba: lamba变量申明

    1,下面编译运行没问题 2,如果想在lamba中引用外围变量x,是无法做到的,如下会报错,类型不匹配了 error C2440: “初始化”: 无法从&#8…

    技术杂谈 2023年5月31日
    067
  • wasm调试 webAssembly介绍大全

    https://segmentfault.com/a/1190000040867861 最近在研究 WebAssembly,也写了几篇全面介绍的文章: 本文是学习 WebAssem…

    技术杂谈 2023年5月31日
    076
  • 比CMD更强大的命令行WMIC

    先决条件:a. 启动Windows Management Instrumentation服务,开放TCP135端口。b. 本地安全策略的”网络访问: 本地帐户的共享和安…

    技术杂谈 2023年6月1日
    076
  • vue 版本查看

    如何查看vue版本号? 方法1、全局查看vue版本号 npm info vue方法2、局部(当前项目)查vue版本号 npm list vue version方法3、此外还可以通过…

    技术杂谈 2023年7月11日
    095
  • login方法访问不到解决过程

    背景:由于项目登录模块之前使用传统的字符验证码,干扰又太严重,经常会有输入十次以上才能蒙对的情况。于是提出让改为滑动验证码(斗鱼,B站等等)。如图所示: 原有的: 要改的: 这个实…

    技术杂谈 2023年7月24日
    081
  • 20个数据库常见面试题讲解

    事务四大特性(ACID)原子性、一致性、隔离性、持久性? 事务的并发?事务隔离级别,每个级别会引发什么问题,MySQL默认是哪个级别? MySQL常见的三种存储引擎(InnoDB、…

    技术杂谈 2023年7月24日
    068
  • 友情链接

    posted @2022-02-12 22:04 cjwen6 阅读(13 ) 评论() 编辑 Original: https://www.cnblogs.com/cjwen6/p…

    技术杂谈 2023年7月23日
    073
  • 杂七杂八

    &#xA0;<span class="hljs-keyword">var head = <span class="hljs-…

    技术杂谈 2023年5月31日
    092
  • Jenkins pipline

    pipeline { agent any options { durabilityHint ‘PERFORMANCE_OPTIMIZED’ timeout(time:5, unit…

    技术杂谈 2023年5月31日
    092
  • JavaSE-对象所有字段判空

    /** * 判断该对象是否所有属性为空 * 返回ture表示所有属性为null,返回false表示不是所有属性都是null */ public static boolean isA…

    技术杂谈 2023年6月21日
    081
  • PyQt5 控件交互与焦点控制

    ################################ PyQt5中文网 – PyQt5全套视频教程 # https://www.PyQt5.cn/ # 主讲: 村长 #…

    技术杂谈 2023年5月31日
    084
  • 5 个方便好用的 Python 自动化脚本,拿来就用

    前言 相比大家都听过自动化生产线、自动化办公等词汇,在没有人工干预的情况下,机器可以自己完成各项任务,这大大提升了工作 效率。 编程世界里有各种各样的自动化脚本,来完成不同的任务。…

    技术杂谈 2023年6月21日
    082
  • 性能测试案例全过程方案六———购物流程(重要!!!)

    代码改变世界 Cnblogs Dashboard Login 2022-01-15 22:19 清风软件测试开发 阅读(9 ) 评论() 编辑 性能测试案例全过程方案六 模拟多用户…

    技术杂谈 2023年5月31日
    0114
  • 经典45个git使用技巧与场合,专治不会合代码。

    前言 git对于大家应该都不太陌生,熟练使用git已经成为程序员的一项基本技能,尽管在工作中有诸如 Sourcetree这样牛X的客户端工具,使得合并代码变的很方便。但找工作面试和…

    技术杂谈 2023年7月11日
    0102
  • CGContext图形上下文详解

    CGContextSaveGState函数的作用是将当前图形状态推入堆栈。之后,您对图形状态所做的修改会影响随后的描画操作,但不影响存储在堆栈中的拷贝。在修改完成后,您可以通过CG…

    技术杂谈 2023年5月30日
    090
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球