最近学了一点rust,就尝试写了个随机图片api,但没有用301/302重定向,而是直接用http响应体实现

缺点:可能不利于浏览器缓存,可更改代码中http请求头Cache-Control: max-age=0设置浏览器的缓存时间。

其中还遇到一个坑,windows的换行符和linux不兼容?windows系统下换行符在代码转义字符只能表示为"\r\n",写成"\n"会报错,而在linux下换行符转义只能表示为"\n",写成"\r\n"会识别不到。

main.rs源代码

use tokio::net::TcpListener;
use tokio::net::TcpStream;
use tokio::io::AsyncReadExt;
use tokio::io::AsyncWriteExt;
use std::sync::Arc;
use tokio::sync::RwLock;
use tokio::fs::{read,read_to_string};
use rand::Rng;
use std::env;


#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
async fn main() {

    //读取配置文件
    let config_path = "config.txt";
    let config = Arc::new(tokio::sync::RwLock::new(Vec::new()));//有原子引用计数,又有读写锁的数组
    read_config(config_path, config.clone()).await;
    
    let args: Vec<String> = env::args().collect();
    let address;
    if args.len() < 2 {
        println!("WARN:未指定监听地址\n");
        address = "0.0.0.0:12520";
    } else {
        address = &args[1];
    }

    // 监听本地端口,等待 TCP 连接的建立
    let listener = match TcpListener::bind(address).await {
        Ok(ok) => ok,
        Err(e) => {
        println!("ERROR:监听地址失败:{}",e);
        panic!()
        },
    };
    
    println!("INFO:监听地址:http://{}\n",address);

    let config = Arc::new(config.read().await.clone());//去掉锁
    
    loop {
        //接受请求,然后把请求扔到线程池处理
        match listener.accept().await {
            Ok((stream, _addr)) => {
                let config = Arc::clone(&config);
                tokio::spawn(async move {
                    handle_request(stream, config).await;
                });
            }
            Err(e) => {
                println!("WARN:无法处理请求:{}\n", e);
            }
        }
    }
}

async fn handle_request(mut request: TcpStream, config: Arc<Vec<String>>) {
    let mut request_data = [0u8; 1024];
    //读取请求的数据
    if let Err(e) = request.read(&mut request_data[..]).await {
        println!("WARN:无效请求:{}\n", e);
        //直接结束这个异步函数的运行
        return;
    };

    let get_path = b"GET / HTTP/1.1\r\n";   //http路径

    let mut response: Vec<u8>;  //存储http响应体的变量
    //判断http路径
    if request_data.starts_with(get_path) {
        let random_number = rand::thread_rng().gen_range(0..config.len() as usize);
        //根据生成的随机数随机选择图片文件路径
        let file_path = &config[random_number];
        //获取到文件后缀名
        let file_suffix: Vec<&str> = file_path.split('.').collect();
        let file_suffix = file_suffix[file_suffix.len() - 1];
        //读取图片文件内容并构造http响应体
        match read(file_path).await {
            Ok(content) =>{
                response = format!(
                    "HTTP/1.1 200 OK\r\n\
                    Content-Type: image/{}\r\n\
                    Content-Length: {}\r\n\
                    Cache-Control: max-age=0\r\n\
                    Connection: keep-alive\r\n\r\n",
                    file_suffix,
                    content.len()
                ).into_bytes();
                //最后写入图片文件内容
                response.extend_from_slice(&content);
                // 获取请求的 IP 地址并打印日志
                let peer_addr = match request.peer_addr() {
                    Ok(addr) => addr.ip().to_string(),
                    Err(e) => {
                        println!("WARN:无法获取请求的IP地址:{}\n", e);
                        return;
                    }
                };
                println!("INFO: request-IP:{} response:{}",peer_addr,file_path)
            },
            Err(e) => {
                response = "HTTP/1.1 500 Internal Server Error\r\n\r\n".to_string().into_bytes();
                println!("ERROR:读取文件失败:{}",e);
            },
        };
    } else {
        response = "HTTP/1.1 404 NOT FOUND\r\n\r\n".to_string().into_bytes();
    }

    //将回复内容写入连接缓存中
    if let Err(e) = request.write_all(&response).await {
        println!("ERROR:写入连接缓存错误:{}\n", e);
        //直接结束这个异步函数的运行
        return;
    }

    //使用 flush 将缓存中的内容发送到客户端
    if let Err(e) = request.flush().await {
        println!("ERROR:响应请求错误:{}\n", e);
        return;
    }
}

async fn read_config(config_path: &str, config: Arc<RwLock<Vec<String>>>) {
    //保存路径配置项到数组
    match read_to_string(config_path).await {
        Ok(content) =>{
            let lines = content.lines();
            let mut config_clone = config.write().await;
            for i in lines {
                config_clone.push(i.to_string());
            }
        },
        Err(e) => {
            println!("ERROR:读取config.txt配置文件失败:{}",e);
            panic!()
        },
    }
}