2
0
mirror of https://github.com/xvxx/phd synced 2024-11-14 18:12:54 +00:00
phd/src/server.rs

133 lines
3.7 KiB
Rust
Raw Normal View History

2019-12-28 04:19:09 +00:00
use crate::{Request, Result};
2019-12-27 23:42:57 +00:00
use gophermap::{GopherMenu, ItemType};
2019-12-27 22:37:46 +00:00
use std::{
2019-12-27 04:59:34 +00:00
fs,
2019-12-27 22:37:46 +00:00
io::prelude::*,
io::{BufReader, Read, Write},
2019-12-27 23:42:57 +00:00
net::{TcpListener, TcpStream},
2019-12-27 04:59:34 +00:00
};
2019-12-27 22:37:46 +00:00
use threadpool::ThreadPool;
2019-12-27 04:59:34 +00:00
2019-12-27 23:42:57 +00:00
const MAX_WORKERS: usize = 10;
2019-12-27 05:48:29 +00:00
const MAX_PEEK_SIZE: usize = 1024;
2019-12-28 04:09:41 +00:00
const TCP_BUF_SIZE: usize = 1024;
2019-12-27 05:48:29 +00:00
2019-12-28 04:09:41 +00:00
/// Starts a Gopher server at the specified host, port, and root directory.
pub fn start(host: &str, port: u16, root: &str) -> Result<()> {
2019-12-27 23:42:57 +00:00
let addr = format!("{}:{}", host, port);
let listener = TcpListener::bind(&addr)?;
2019-12-28 04:09:41 +00:00
let full_root_path = fs::canonicalize(&root)?.to_string_lossy().to_string();
2019-12-27 23:42:57 +00:00
let pool = ThreadPool::new(MAX_WORKERS);
2019-12-28 04:09:41 +00:00
println!("-> Listening on {} at {}", addr, full_root_path);
2019-12-27 23:42:57 +00:00
for stream in listener.incoming() {
let stream = stream?;
println!("-> Connection from: {}", stream.peer_addr()?);
2019-12-28 04:09:41 +00:00
let req = Request::from(host, port, root)?;
2019-12-27 23:42:57 +00:00
pool.execute(move || {
2019-12-28 04:09:41 +00:00
if let Err(e) = accept(stream, req) {
eprintln!("-! {}", e);
2019-12-27 23:42:57 +00:00
}
});
}
Ok(())
}
2019-12-28 04:09:41 +00:00
/// Reads from the client and responds.
fn accept(stream: TcpStream, mut req: Request) -> Result<()> {
let reader = BufReader::new(&stream);
let mut lines = reader.lines();
if let Some(Ok(line)) = lines.next() {
2019-12-28 04:28:08 +00:00
println!("-> Client sent: {:?}", line);
2019-12-28 04:09:41 +00:00
req.selector = line;
write_response(&stream, req)?;
2019-12-27 22:37:46 +00:00
}
2019-12-28 04:09:41 +00:00
Ok(())
}
2019-12-27 22:37:46 +00:00
2019-12-28 04:09:41 +00:00
/// Writes a response to a client based on a Request.
fn write_response<'a, W>(w: &'a W, req: Request) -> Result<()>
where
&'a W: Write,
{
let md = fs::metadata(&req.file_path())?;
if md.is_file() {
2019-12-28 04:27:02 +00:00
write_file(w, req)
2019-12-28 04:09:41 +00:00
} else if md.is_dir() {
write_dir(w, req)
} else {
Ok(())
2019-12-27 20:58:05 +00:00
}
2019-12-28 04:09:41 +00:00
}
2019-12-27 20:58:05 +00:00
2019-12-28 04:09:41 +00:00
/// Send a directory listing (menu) to the client based on a Request.
fn write_dir<'a, W>(w: &'a W, req: Request) -> Result<()>
where
&'a W: Write,
{
let mut dir = fs::read_dir(&req.file_path())?;
let mut menu = GopherMenu::with_write(w);
let rel_path = req.relative_file_path();
while let Some(Ok(entry)) = dir.next() {
let mut path = rel_path.clone();
2019-12-27 23:42:57 +00:00
if !path.ends_with('/') {
path.push('/');
}
2019-12-28 04:09:41 +00:00
let file_name = entry.file_name();
path.push_str(&file_name.to_string_lossy());
menu.write_entry(
file_type(&entry),
&file_name.to_string_lossy(),
&path,
&req.host,
req.port,
)?;
2019-12-27 05:56:56 +00:00
}
2019-12-28 04:09:41 +00:00
menu.end()?;
Ok(())
2019-12-28 03:19:09 +00:00
}
2019-12-27 05:56:56 +00:00
2019-12-28 04:27:02 +00:00
/// Send a file to the client based on a Request.
fn write_file<'a, W>(mut w: &'a W, req: Request) -> Result<()>
2019-12-28 03:19:09 +00:00
where
&'a W: Write,
{
2019-12-28 04:09:41 +00:00
let path = req.file_path();
2019-12-28 03:19:09 +00:00
let md = fs::metadata(&path)?;
let mut f = fs::File::open(&path)?;
2019-12-28 04:09:41 +00:00
let mut buf = [0; TCP_BUF_SIZE];
2019-12-28 03:19:09 +00:00
let mut bytes = md.len();
while bytes > 0 {
let n = f.read(&mut buf[..])?;
bytes -= n as u64;
w.write_all(&buf[..n])?;
2019-12-27 04:59:34 +00:00
}
2019-12-28 03:19:09 +00:00
Ok(())
2019-12-27 04:59:34 +00:00
}
2019-12-27 05:48:29 +00:00
2019-12-27 23:42:57 +00:00
/// Determine the gopher type for a DirEntry on disk.
fn file_type(dir: &fs::DirEntry) -> ItemType {
2019-12-28 03:19:09 +00:00
let metadata = match dir.metadata() {
Err(_) => return ItemType::Error,
Ok(md) => md,
};
2019-12-27 06:01:28 +00:00
2019-12-27 23:42:57 +00:00
if metadata.is_file() {
if let Ok(file) = fs::File::open(&dir.path()) {
let mut buffer: Vec<u8> = vec![];
let _ = file.take(MAX_PEEK_SIZE as u64).read_to_end(&mut buffer);
if content_inspector::inspect(&buffer).is_binary() {
ItemType::Binary
2019-12-27 05:48:29 +00:00
} else {
2019-12-27 23:42:57 +00:00
ItemType::File
2019-12-27 05:48:29 +00:00
}
} else {
2019-12-27 23:42:57 +00:00
ItemType::Binary
2019-12-27 05:48:29 +00:00
}
2019-12-27 23:42:57 +00:00
} else if metadata.is_dir() {
ItemType::Directory
2019-12-27 05:48:29 +00:00
} else {
2019-12-27 23:42:57 +00:00
ItemType::Error
2019-12-27 05:48:29 +00:00
}
}