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

2023-05-16

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();
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Rust:axum学习笔记(4) 上传文件 的相关文章

  • Atcoder AGC005 题解

    A STring 用类似括号匹配的方法搞一下即可 span class token macro property span class token directive keyword include span span class toke
  • CentOS-7安装桌面环境

    CentOS 7安装桌面环境 CentOS 7安装桌面环境 CentOS 7安装Server with GUI 设置为开机从桌面环境启动 yum y group install 39 Server with GUI 39 systemctl
  • [软件注册]Sublime 3 激活/注册码(key)

    偶然发现了一种sublime激活方式 使用的sublime3 1 1版本 亲试有效 Step1 配置 host文件 推荐使用 switchhost软件 可以快速变更host span class hljs number 127 0 span
  • 测试git能否连接github

    welcome to my blog 使用以下命令进行测试 ssh T git 64 github com 出现报错 ssh dispatch run fatal Connection to 13 250 177 223 port 22 S
  • vtk中实现3D模型(读取文件)

    xff08 xff09 VTK 坐标系统及空间变换 窗口 视图分割 mb5fed73533dfa9的技术博客 51CTO博客 VTK学习 xff08 三 xff09 VTK读取序列图像 灰信网 xff08 软件开发博客聚合 xff09 读取
  • centos中安装Python2.7

    转载于 xff1a 秋水逸冰 CentOS 6 8安装Python2 7 13 查看当前系统中的 Python 版本 python version 返回 Python 2 6 6 为正常 检查 CentOS 版本 cat etc redha
  • 安装tar.gz文件(无configure文件)

    如何安装tar gz文件 xff08 以webstorm为例 xff09 1 获取root权限并输入密码 xff1a su root 2 进入有该文件的目录下 xff08 以我的为例 xff0c 具体看你的文件在哪里 xff09 cd 下载
  • 游戏服务端框架之业务线程模型

    请求消息绑定线程策略的选择 在上一篇文章中 我们看到 消息是直接在网络框架的io线程中处理的 这样做有一个非常严重的缺陷 如果业务处理比较耗时 那么io线程接受消息的速度就会下降 严重影响io的吞吐量 典型的 我们应该另起线程池 专门用于异
  • 在WSL中使用GPU:WSL2 + Ubuntu 18.04 + CUDA + Gnome图形界面环境配置

    目录 引言1 确认Windows 10版本2 在Windows上安装WSL23 在Windows上安装CUDA on WSL驱动4 在WSL2中安装CUDA Toolkit3 测试CUDA是否能在WSL2中运作4 安装Gnome图形界面其他
  • Centos 开启路由转发实现全网互通

    只需在RouterSrv网关服务器上开启路由转发功能即可 root 64 RouterSrv vi etc sysctl conf net ipv4 ip forward 61 1 添加此行即可 root 64 localhost sysc
  • 虚拟机中配置外网环境

    文章目录 在虚拟机中配置外网环境 在虚拟机中配置外网环境 主机为 win10 xff0c 虚拟机中为 ubuntu 系统 xff0c 采用clash 1 xff0c 设置 Allow Lan xff0c 允许局域网访问 2 xff0c 虚拟
  • mysql 操作数据库(备份与恢复)

    一 直接把创建数据库的语句放到sql 文件中 xff1a php 写法 xff1a lt php mysql port 61 get mysql port cmd 61 US MYSQL BIN 34 mysql exe port 61 3
  • Go调用Python by go-python3

    确保python版本为3 7 conda create go python span class token assign left variable python span span class token operator 61 spa
  • linux下搭建maven私服

    maven私服我相信很多公司都有 xff0c 私服的好处有以下几点 xff1a 1 节省下载资源开销 jar包 xff08 不一定是jar xff0c 也可以是其他资源 xff09 都存在私服 xff0c 可以不必每次下载都去远程仓库去下载
  • git 安装包 最新 下载 快速 国内 镜像 地址

    下载git时 xff0c 先进官网看 https git scm com download win 然后发现几kb的网速 xff0c 这是要让我下一年么 xff0c 找了找网上有没有其他的镜像 xff0c 发现阿里有一个镜像 xff0c 下
  • docker笔记(四、docker部署beego打包后的二进制文件)

    在beego工程里 xff0c 使用go build可以将该工程打包成一个二进制文件 xff0c 那么这个二进制文件在docker里面该怎么部署呢 xff1f 先写一个简单的图片上传的demo xff0c 名字叫docker test 在工
  • LINUX服务器最简洁的HTTPS免费证书配置方法

    注意 xff1a 该方法已在多台服务器配置了免费的https证书 xff0c 无论是更新还是第一次配置都运行成功 xff1b 由于是免费版 xff0c 每个证书都只有三个月的有效期 xff0c 也无法保证安全和稳定性 xff0c 所以只建议
  • 【性能测试】获取性能系统指标之示例Python代码

    usr bin env python coding utf 8 import sys import datetime import time import psutil from ctypes import 34 34 34 性能测试示例P
  • select I/O 多路复用实现服务器聊天室功能

    基本概念 IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取 xff0c 它就通知该进程 IO多路复用适用如下场合 xff1a xff08 1 xff09 当客户处理多个描述字时 xff08 一般是交互式输入和网络套接口 x
  • iOS-使用Masnory实现UITableViewCell自适应高度

    在iOS开发当中 xff0c 如果涉及到UITableViewCell的一些复杂UI的绘制时难免会碰到这么一个难题 xff1a UITableViewCell的高度如何设置 xff01 的确 xff0c 我们就拿一个简单的例子来说 xff1

随机推荐

  • ubuntu中共享文件夹看不到

    博主的ubuntu安装VMwaretools后共享文件夹设置完发现在 mnt hgfs总看不到 经过多次摸索后终于可以了 首先要使用root用户登陆ubuntu 然后再安装VMwaretools 在设置共享文件夹 然后解决挂在的问题 1 设
  • keystore was tampered with,or password was incorrect解决办法

    利用keytool导入证书 xff0c 命令如下 keytool import alias HZZSQKJdianshang file HZZSQKJdianshang cer keystore trust jks storepass st
  • Convert Picture or Video to ascii

    一个利用ascii拼成的谷歌街景地图 xff01 http tllabs io asciistreetview 看上去效果真不错 xff01 除此之外 xff0c linux下面也有类似的ascii艺术 xff0c 比如 aview asc
  • texlive2015-6安装

    http www cnblogs com snake553 p 4831588 html CentOS6 5 下安装 texlive2015 并设置 ctex 中文套装 0 卸载旧版本的 texlive 0 1 卸载 texlive2007
  • ubuntu20.04 安装显卡驱动后开机卡住,无法进入登陆界面问题解决

    ubuntu20 04 安装显卡驱动后开机卡住 xff0c 无法进入登陆界面问题解决 进入recovery mode 禁用nvidia drm systemctl isolate multi user target modprobe r n
  • Linux环境变量失效,命令不可用

    背景 linux在修改完环境变量 etc profile后保存文件后 xff0c 发现大多数命令不可用 xff0c 只有少数如 xff1a cd pwd可以使用 xff1b 原因分析 1 etc profile文件中有无效字符或变量 xff
  • Ubuntu20.04安装MySQL5.7-实测3种方法(保姆级教程)

    最近生产系统系统需要使用MySQL5 7版本的数据库 xff0c 而Ubuntu20 04默认是8 0的版本 xff0c 折腾了一段时间后 xff0c 测试了3中方法 xff0c 在实际应用环境中测试成功 xff0c 因此发布出来给大家参考
  • 数据仓库的源数据类型

    数据仓库中集成了企业几乎所有的可以获取到的数据以用于数据分析和决策支持 xff0c 当然也包括了我在网站分析的数据来源一文中所提到的所有数据 这些进入到数据仓库中的数据无外乎三种类型 xff1a 结构化数据 半结构化数据 和非结构化数据 x
  • soj 1075 拓扑排序队列实现

    就是soj 拓扑排序的模板题吧 然后我中午把用队列实现的拓扑排序的方法看了下 晚上就打算来练一下这种纯模板 对于这实现的方法 xff0c 我的理解就是存下每个节点的入度以及它指向的其他节点 xff0c 由于指向多少个这个不太能确定所以用一个
  • poker

    include lt stdbool h gt include lt stdio h gt include lt stdlib h gt define NUM RANKS 13 define NUM SUTTS 4 define NUM C
  • vue实现筛选动态配置

    lt el form class 61 34 cellStyleForm 34 model 61 34 queryParams 34 ref 61 34 queryForm 34 v show 61 34 showSearch 34 inl
  • C. Good String Codeforces Round #560 (Div. 3)

    C Good String time limit per test 1 second memory limit per test 256 megabytes input standard input output standard outp
  • v4l2接口解析和摄像头数据采集

    V4L2接口解析 操作步骤 应用程序通过V4L2接口采集视频数据步骤 打开视频设备文件 xff0c 通过视频采集的参数初始化 xff0c 通过V4L2接口设置视频图像属性 申请若干视频采集的帧缓存区 xff0c 并将这些帧缓冲区从内核空间映
  • 阿里云cdn缓存命中查看

    我们在使用阿里云想知道CDN缓存命中 命中Hit X Cache HIT TCP MEM HIT dirn 9 435942540 上面的情况是命中的情况 不命中的情况miss X Cache MISS TCP MISS dirn 2 2
  • ffmpeg解码RTSP/TCP视频流H.264(QT界面显示视频画面)

    源码下载地址 http download csdn net detail liukang325 9489952 我用的ffmpeg版本为 ffmpeg 2 1 8 tar bz2 版本低了恐怕有些头文件和API找不到 在Linux下解压后编
  • vue的script代码

  • C++扑克牌类的设计

    C 43 43 扑克牌 1 设计扑克类Card xff1a 主要属性有花色和点数 xff1b 主要方法有初始化 输出 比较大小等 2 设计一副扑克牌类Cards xff1a 主要属性包括扑克对象数组 xff1b 主要方法有初始化 洗牌 输出
  • 用Linux系统做路由器

    一 网络结构如下 Internet nbsp nbsp nbsp nbsp nbsp 路由器A Lan 192 168 1 1 nbsp nbsp nbsp nbsp Linux路由器 eth0接路由器A IP 192 168 1 2 网关
  • rust 学习笔记

    按照菜鸟教程 xff1a Rust 教程 菜鸟教程 安装成功了 xff0c 可以跑通第一个程序 中间有一些插曲 xff0c 比如报了 error linker 96 link exe 96 not found 这个错误 输入 xff1a r
  • Rust:axum学习笔记(4) 上传文件

    Rust axum学习笔记 4 上传文件 上传文件是 web开发中的常用功能 xff0c 本文将演示axum如何实现图片上传 xff08 注 xff1a 其它类型的文件原理相同 xff09 xff0c 一般来说要考虑以下几个因素 xff1a