Compare commits
No commits in common. "7a392e9c6c18b6e904efc7bf21b59efa069e8608" and "210e24415edb7439fc4d7e0094673c25fe8f4a5d" have entirely different histories.
7a392e9c6c
...
210e24415e
2 changed files with 47 additions and 85 deletions
21
src/http.rs
21
src/http.rs
|
@ -13,7 +13,7 @@ pub enum RequestRange {
|
||||||
Suffix(usize),
|
Suffix(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Method {
|
pub enum Method {
|
||||||
Get,
|
Get,
|
||||||
Head,
|
Head,
|
||||||
|
@ -35,7 +35,7 @@ pub enum Status {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Content {
|
pub struct Content {
|
||||||
mime_type: &'static str,
|
content_type: &'static str,
|
||||||
range: Option<(usize, usize, usize)>,
|
range: Option<(usize, usize, usize)>,
|
||||||
bytes: Vec<u8>,
|
bytes: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
@ -93,14 +93,14 @@ impl Response {
|
||||||
let mut buffer = format!(
|
let mut buffer = format!(
|
||||||
"{}\r\nContent-Type: {}\r\nAccept-Ranges: bytes\r\nContent-Length: {}\r\n",
|
"{}\r\nContent-Type: {}\r\nAccept-Ranges: bytes\r\nContent-Length: {}\r\n",
|
||||||
self.status.header(),
|
self.status.header(),
|
||||||
content.mime_type,
|
content.content_type,
|
||||||
content.bytes.len(),
|
content.bytes.len(),
|
||||||
)
|
)
|
||||||
.into_bytes();
|
.into_bytes();
|
||||||
if let Some((start, end, size)) = content.range {
|
if let Some((start, end, size)) = content.range {
|
||||||
buffer.extend_from_slice(
|
buffer.extend_from_slice(
|
||||||
format!("Content-Range: bytes {start}-{end}/{size}\r\n").as_bytes(),
|
format!("Content-Range: bytes {}-{}/{}\r\n", start, end, size).as_bytes(),
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
buffer.extend_from_slice(b"\r\n");
|
buffer.extend_from_slice(b"\r\n");
|
||||||
if !head_only {
|
if !head_only {
|
||||||
|
@ -168,7 +168,7 @@ impl Content {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
mime_type: content_type,
|
content_type,
|
||||||
range: None,
|
range: None,
|
||||||
bytes,
|
bytes,
|
||||||
}
|
}
|
||||||
|
@ -209,15 +209,6 @@ impl Method {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Method {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Method::Get => write!(f, "GET"),
|
|
||||||
Method::Head => write!(f, "HEAD"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RequestRange {
|
impl RequestRange {
|
||||||
fn parse(source: &str) -> Option<Self> {
|
fn parse(source: &str) -> Option<Self> {
|
||||||
let source = source.strip_prefix("bytes=")?;
|
let source = source.strip_prefix("bytes=")?;
|
||||||
|
|
111
src/main.rs
111
src/main.rs
|
@ -27,42 +27,40 @@ fn main() {
|
||||||
for stream in listener.incoming() {
|
for stream in listener.incoming() {
|
||||||
match stream {
|
match stream {
|
||||||
Ok(stream) => threads.push(thread::spawn(|| handle_connection(stream))),
|
Ok(stream) => threads.push(thread::spawn(|| handle_connection(stream))),
|
||||||
Err(err) => println!("Error with incoming stream: {err}"),
|
Err(err) => println!("Error with incoming stream: {}", err),
|
||||||
}
|
}
|
||||||
threads.retain(|j| !j.is_finished());
|
threads = threads.into_iter().filter(|j| !j.is_finished()).collect();
|
||||||
println!("{} connections open", threads.len());
|
println!("{} threads open", threads.len());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_connection(mut stream: TcpStream) {
|
fn handle_connection(mut stream: TcpStream) {
|
||||||
const MAX_REQUEST_SIZE: usize = 1024 * 4;
|
let Ok(peer_addr) = stream.peer_addr() else {
|
||||||
let Ok(client_ip) = stream.peer_addr() else {
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
println!("[{client_ip}] new connection");
|
println!("#### new connection from {peer_addr}");
|
||||||
|
|
||||||
let mut buffer = Vec::with_capacity(2048);
|
let mut buffer = Vec::with_capacity(2048);
|
||||||
loop {
|
loop {
|
||||||
let mut b = vec![0; 512];
|
let mut b = vec![0; 512];
|
||||||
let Ok(size) = stream.read(&mut b) else {
|
let Ok(size) = stream.read(&mut b) else {
|
||||||
println!("[{client_ip}] connection broken");
|
println!("failed to read ");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if size == 0 {
|
if size == 0 {
|
||||||
println!("[{client_ip}] connection closed by client");
|
println!("nothing read");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
b.truncate(size);
|
b.truncate(size);
|
||||||
buffer.extend_from_slice(&b);
|
buffer.extend_from_slice(&b);
|
||||||
|
|
||||||
if buffer.len() > MAX_REQUEST_SIZE {
|
if buffer.len() > 4096 {
|
||||||
println!("[{client_ip}] request over {MAX_REQUEST_SIZE} bytes, closing connection");
|
println!("request too long");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if buffer.ends_with(b"\r\n\r\n") {
|
if buffer.ends_with(b"\r\n\r\n") {
|
||||||
let request = String::from_utf8_lossy(&buffer).to_string();
|
let request = String::from_utf8_lossy(&buffer).to_string();
|
||||||
|
// println!("Received {} bytes from {}", buffer.len(), peer_addr);
|
||||||
println!("[{client_ip}] received {} bytes", buffer.len());
|
|
||||||
// println!(
|
// println!(
|
||||||
// "=======\n{}=======\n\n",
|
// "=======\n{}=======\n\n",
|
||||||
// request
|
// request
|
||||||
|
@ -71,51 +69,48 @@ fn handle_connection(mut stream: TcpStream) {
|
||||||
// .replace("\\r\\n", "\n")
|
// .replace("\\r\\n", "\n")
|
||||||
// .replace("\\n", "\n")
|
// .replace("\\n", "\n")
|
||||||
// );
|
// );
|
||||||
if handle_request(&request, &mut stream) {
|
if handle_request(request, &mut stream) {
|
||||||
println!("[{client_ip}] closing connection");
|
println!("closing connection");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// println!("keeping connection");
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_request(request: &str, stream: &mut TcpStream) -> bool {
|
fn handle_request(request: String, stream: &mut TcpStream) -> bool {
|
||||||
let Ok(client_ip) = stream.peer_addr() else {
|
let request = Request::parse(&request);
|
||||||
return true;
|
|
||||||
};
|
|
||||||
let request = Request::parse(request);
|
|
||||||
let response;
|
let response;
|
||||||
let mut end_connection = true;
|
let mut end_connection = true;
|
||||||
|
|
||||||
if let Some(request) = request {
|
if let Some(request) = request {
|
||||||
println!("[{client_ip}] {} {}", request.method, request.path);
|
|
||||||
let head_only = request.method == Method::Head;
|
let head_only = request.method == Method::Head;
|
||||||
let path = request.path.clone();
|
let path = request.path.clone();
|
||||||
response = get_file(&request)
|
response = get_file(request)
|
||||||
.map(|(content, end_of_file)| {
|
.map(|(content, end_of_file)| {
|
||||||
end_connection = end_of_file;
|
end_connection = end_of_file;
|
||||||
println!("[{client_ip}] sending file content");
|
|
||||||
Response::new(Status::Ok).with_content(content)
|
Response::new(Status::Ok).with_content(content)
|
||||||
})
|
})
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
println!("[{client_ip}] file not found");
|
|
||||||
Response::new(Status::NotFound)
|
Response::new(Status::NotFound)
|
||||||
.with_content(Content::text(format!("404 NOT FOUND - '{path}'")))
|
.with_content(Content::text(format!("FILE NOT FOUND - '{}'", path)))
|
||||||
})
|
})
|
||||||
.format(head_only);
|
.format(head_only);
|
||||||
} else {
|
} else {
|
||||||
println!("[{client_ip}] bad request");
|
|
||||||
response = Response::new(Status::BadRequest).format(false);
|
response = Response::new(Status::BadRequest).format(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if stream.write_all(&response).is_err() || stream.flush().is_err() {
|
stream
|
||||||
println!("[{client_ip}] failed to send response");
|
.write_all(&response)
|
||||||
}
|
.unwrap_or_else(|_| println!("failed to respond"));
|
||||||
|
stream
|
||||||
|
.flush()
|
||||||
|
.unwrap_or_else(|_| println!("failed to flush"));
|
||||||
end_connection
|
end_connection
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_file(request: &Request) -> Option<(Content, bool)> {
|
fn get_file(request: Request) -> Option<(Content, bool)> {
|
||||||
const MAX_SIZE: usize = 1024 * 1024 * 8;
|
const MAX_SIZE: usize = 1024 * 1024 * 8;
|
||||||
|
|
||||||
let current_dir = env::current_dir().unwrap();
|
let current_dir = env::current_dir().unwrap();
|
||||||
|
@ -177,37 +172,31 @@ fn generate_index(relative_path: &str, path: &Path) -> Option<Content> {
|
||||||
.ok()?
|
.ok()?
|
||||||
.flatten()
|
.flatten()
|
||||||
.filter_map(|d| {
|
.filter_map(|d| {
|
||||||
let size = if d.file_type().ok()?.is_dir() {
|
let is_dir = d.file_type().ok()?.is_dir();
|
||||||
None
|
d.file_name().to_str().map(|s| (s.to_owned(), is_dir))
|
||||||
} else {
|
|
||||||
Some(d.metadata().ok()?.len())
|
|
||||||
};
|
|
||||||
|
|
||||||
d.file_name().to_str().map(|s| (s.to_owned(), size))
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
dirs.sort_by(|(name_a, size_a), (name_b, size_b)| {
|
dirs.sort_by(|(name_a, dir_a), (name_b, dir_b)| dir_b.cmp(dir_a).then(name_a.cmp(name_b)));
|
||||||
size_a
|
|
||||||
.is_some()
|
|
||||||
.cmp(&size_b.is_some())
|
|
||||||
.then(name_a.cmp(name_b))
|
|
||||||
});
|
|
||||||
let list = dirs
|
let list = dirs
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(name, size)| {
|
.filter_map(|(name, is_dir)| {
|
||||||
let formatted_size = size.map(format_size).unwrap_or_default();
|
let mut s = format!(
|
||||||
format!(
|
" <li><a href=\"{}\">{}",
|
||||||
"<tr><td><a href=\"{href}\">{name}{trailing_slash}</a></td><td>{formatted_size}</td></tr>\n",
|
PathBuf::from(relative_path).join(&name).display(),
|
||||||
href = PathBuf::from(relative_path).join(&name).display(),
|
name
|
||||||
trailing_slash = if size.is_some() { "" } else { "/" },
|
);
|
||||||
)
|
if is_dir {
|
||||||
|
s.push('/');
|
||||||
|
}
|
||||||
|
s.push_str("</a></li>\n");
|
||||||
|
Some(s)
|
||||||
})
|
})
|
||||||
.fold(String::new(), |mut content, entry| {
|
.fold(String::new(), |mut content, entry| {
|
||||||
content.push_str(&entry);
|
content.push_str(&entry);
|
||||||
content
|
content
|
||||||
});
|
});
|
||||||
let parent = if relative_path != "/" {
|
let parent = if relative_path != "/" {
|
||||||
"<tr><td><a href=\"..\">../</a></td><td></td></tr>"
|
r#" <li><a href="..">../</a></li>"#
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
|
@ -216,31 +205,13 @@ fn generate_index(relative_path: &str, path: &Path) -> Option<Content> {
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>Index of {relative_path}</title>
|
<title>Index of {relative_path}</title>
|
||||||
<style>
|
|
||||||
html {{ color-scheme: dark; }}
|
|
||||||
tr:nth-child(odd) {{ background-color: #333; }}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h3>Index of {relative_path}</h3>
|
<h3>Index of {relative_path}</h3>
|
||||||
<table>
|
<ul>
|
||||||
{parent}
|
{parent}{list} </ul>
|
||||||
{list}
|
|
||||||
</table>
|
|
||||||
</body>
|
</body>
|
||||||
</html>"#,
|
</html>"#,
|
||||||
);
|
);
|
||||||
Some(Content::html(page))
|
Some(Content::html(page))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn format_size(bytes: u64) -> String {
|
|
||||||
if bytes < 1024 {
|
|
||||||
format!("{bytes} B")
|
|
||||||
} else if bytes < 1024 * 1024 {
|
|
||||||
format!("{:.1} KiB", bytes as f64 / 1024.0)
|
|
||||||
} else if bytes < 1024 * 1024 * 1024 {
|
|
||||||
format!("{:.1} MiB", bytes as f64 / (1024.0 * 1024.0))
|
|
||||||
} else {
|
|
||||||
format!("{:.1} GiB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue