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