Rust:axum学习笔记(4) 上传文件
接上一篇继续,上传文件是 web开发中的常用功能,本文将演示axum如何实现图片上传(注:其它类型的文件原理相同),一般来说要考虑以下几个因素:
1. 文件上传的大小限制
2. 文件上传的类型限制(仅限指定类型:比如图片)
3. 防止伪装mimetype进行攻击(比如:把.js文件改后缀变成.jpg伪装图片上传,早期有很多这类攻击)
另外,上传图片后,还可以让浏览器重定向到上传后的图片(当然,仅仅只是演示技术实现,实际应用中并非一定要这样)
先展示一个简单的上传文件的表单:
// 上传表单
async fn show_upload() -> Html<&'static str> {
Html(
r#"
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>上传文件(仅支持图片上传)</title>
</head>
<body>
<form action="/save_image" method="post" enctype="multipart/form-data">
<label>
上传文件(仅支持图片上传):
<input type="file" name="file">
</label>
<button type="submit">上传文件</button>
</form>
</body>
</html>
"#,
)
}
上传后,用/save_image来处理图片上传
// 上传图片
async fn save_image(
ContentLengthLimit(mut multipart): ContentLengthLimit<
Multipart,
{
1024 * 1024 * 20 //20M
},
>,
) -> Result<(StatusCode, HeaderMap), String> {
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::<f32>() * 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来展示图片:
/**
* 显示图片
*/
async fn show_image(Path(id): Path<String>) -> (HeaderMap, Vec<u8>) {
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<(StatusCode, HeaderMap), String> {
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();
}
运行效果:
1. 初始上传表单:

2. 文件尺寸太大时

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)

4. 成功上传后

最后附上完整代码:
cargo.xml
[package]
name = "uploadfile"
version = "0.1.0"
edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [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"] }
main.rs
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<&'static str> {
Html(
r#"
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>上传文件(仅支持图片上传)</title>
</head>
<body>
<form action="/save_image" method="post" enctype="multipart/form-data">
<label>
上传文件(仅支持图片上传):
<input type="file" name="file">
</label>
<button type="submit">上传文件</button>
</form>
</body>
</html>
"#,
)
}
// 上传图片
async fn save_image(
ContentLengthLimit(mut multipart): ContentLengthLimit<
Multipart,
{
1024 * 1024 * 20 //20M
},
>,
) -> Result<(StatusCode, HeaderMap), String> {
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::<f32>() * 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<String>) -> (HeaderMap, Vec<u8>) {
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<(StatusCode, HeaderMap), String> {
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();
}
Rust:axum学习笔记(4) 上传文件的更多相关文章
- ASP代码审计学习笔记 -3.上传漏洞
1.ASP上传过程抓包分析: POST /4.asp HTTP/1.1 Host: 192.168.1.102 User-Agent: Mozilla/5.0 (Windows NT 10.0; WO ...
- Struts学习总结-04 上传文件
1. upload.jsp <%@ page language="java" import="java.util.*" pageEncoding=&quo ...
- Struts学习总结-02 上传文件
Struts 2框架提供了内置支持处理文件上传使用基于HTML表单的文件上传.上传一个文件时,它通常会被存储在一个临时目录中,他们应该由Action类进行处理或移动到一个永久的目录,以确保数据不丢失. ...
- 学习笔记-git 上传
0.git add * (如果你需要修改源码需要在 1 之前使用,然后再回到 1) 1.git commit -m '提交文字描述' 2.git push -u origin master (上传到主 ...
- C#上传文件
QQ:1187362408 欢迎技术交流和学习 关于C#上传文件(产品开发): TODO: 1.文件大小不足500M(web.config配置直接处理) 2,文件大小超过500M(ASP.NET分段读 ...
- Python3+Selenium3+webdriver学习笔记9(发送富文本信息及上传文件处理)
#!/usr/bin/env python# -*- coding:utf-8 -*-'''Selenium3+webdriver学习笔记9(发送富文本信息及上传文件处理)'''from seleni ...
- Nodejs学习笔记(八)--- Node.js + Express 实现上传文件功能(felixge/node-formidable)
目录 前言 formidable简介 创建项目并安装formidable 实现上传功能 运行结果 部分疑惑解析 写在之后 前言 前面讲了一个构建网站的示例,这次在此基础上再说说web的常规功能---- ...
- Web 在线文件管理器学习笔记与总结(19)上传文件
dir.func.php 中添加方法: /* 上传文件 */ function uploadFile($fileInfo,$path,$allowExt = array('jpg','jpeg','p ...
- 《Play for Java》学习笔记(六)文件上传file upload
一. Play中标准方法 使用表单form和multipart/form-data的content-type类型. 1.Form @form(action = routes.Application.u ...
- PHP学习笔记——上传文件到服务端的文件夹下
环境 开发包:appserv-win32-2.5.10 服务器:Apache2.2 数据库:phpMyAdmin 语言:php5,java 平台:windows 10 需求 编写一个PHP脚本页面,可 ...
随机推荐
- 解决 Dify 部署中 Podman WSL 容器文件权限问题
解决 Dify 部署中 Podman WSL 容器文件权限问题 在使用 Podman 进行 Dify 部署时,遇到了一个关键问题:启动服务时出现 initdb: error: could not ch ...
- 一文速通Python并行计算:10 Python多进程编程-进程之间的数据共享-基于共享内存和数据管理器
一文速通 Python 并行计算:10 Python 多进程编程-进程之间的数据共享-基于共享内存和数据管理器 摘要: Python 多进程通信中,共享内存通过 Value 和 Array 实现高效数 ...
- 蓝桥杯2019java b组
给定一个数列 1 1 1 3 5 9 17--,这个数列第四项开始等于前三项的和,让你求出第20190324项的最后四位数. package BlueCup; public class Main { ...
- Spring Boot MyBatis使用type-aliases-package自定义类别名
摘要:介绍MyBatis 中 type-aliases-package 属性的作用.在Spring Boot项目中,使用属性type-aliases-package为MyBatis引用的实体类自定义别 ...
- 洛谷 P3396 哈希冲突
洛谷 P3396 哈希冲突 Problem P3396 哈希冲突 Solution 本文摘自基础数据结构学习笔记 先想两种极端的做法 对于每个询问,暴力计算\(k,k+p,k+2\times p\do ...
- 数栈产品预告丨您的指标管理平台——EasyIndex即将上线
一.写在前面 2016年,数栈开始正式投入研发,发展至今,已经拥有了:实时开发.离线开发.算法开发这些开发平台:数据资产.数据质量这些资产平台:以及数据服务.智能标签这些服务平台,这些不同类型的 ...
- ChunJun支持异构数据源DDL转换与自动执行 丨DTMO 02期回顾(内含课程回放+课件)
导读: 4月26日晚,ChunJun项目核心成员.袋鼠云数栈大数据引擎开发专家渡劫为大家带来分享<ChunJun支持异构数据源DDL转换与自动执行>,我们将直播精华部分做了整理,带大家再次 ...
- veRL代码阅读-2.Ray
看VeRL代码之前发现代码里主要使用了ray框架来进行调度和通信. 所以先对ray进行初步学习, 后续有空闲时间再细看下Ray的代码. 框架原理 构成 架构图如下, ray里主要分为系统层面的laye ...
- 基于混合检索与RRF融合的智能问答系统核心技术解析
引言 在当今信息爆炸的时代,如何快速.精准地从海量知识中定位用户所需信息,成为智能问答系统面临的核心挑战.GC-QA-RAG系统通过创新的向量检索技术和混合检索机制,实现了高效的知识点定位能力.本文将 ...
- C++ deque容器操作 总结
------------------------------------ deque容器 双口容器 -----基本操作: 插入 push_back() push_front() insert() fr ...