最近学了一点rust,就尝试写了个随机图片api,但没有用301/302重定向,而是直接用http响应体实现
缺点:可能不利于浏览器缓存,可更改代码中http请求头Cache-Control: max-age=0设置浏览器的缓存时间。
其中还遇到一个坑,windows的换行符和linux不兼容?windows系统下换行符在代码转义字符只能表示为”\r\n”,写成”\n”会报错,而在linux下换行符转义只能表示为”\n”,写成”\r\n”会识别不到。
main.rs源代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 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!() }, } }