最近学了一点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!()
},
}
}
评论