You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
phd/src/server.rs

133 lines
3.7 KiB
Rust

4 years ago
use crate::{Request, Result};
use gophermap::{GopherMenu, ItemType};
use std::{
fs,
io::prelude::*,
io::{BufReader, Read, Write},
net::{TcpListener, TcpStream},
};
use threadpool::ThreadPool;
const MAX_WORKERS: usize = 10;
4 years ago
const MAX_PEEK_SIZE: usize = 1024;
4 years ago
const TCP_BUF_SIZE: usize = 1024;
4 years ago
4 years ago
/// Starts a Gopher server at the specified host, port, and root directory.
pub fn start(host: &str, port: u16, root: &str) -> Result<()> {
let addr = format!("{}:{}", host, port);
let listener = TcpListener::bind(&addr)?;
4 years ago
let full_root_path = fs::canonicalize(&root)?.to_string_lossy().to_string();
let pool = ThreadPool::new(MAX_WORKERS);
4 years ago
println!("-> Listening on {} at {}", addr, full_root_path);
for stream in listener.incoming() {
let stream = stream?;
println!("-> Connection from: {}", stream.peer_addr()?);
4 years ago
let req = Request::from(host, port, root)?;
pool.execute(move || {
4 years ago
if let Err(e) = accept(stream, req) {
eprintln!("-! {}", e);
}
});
}
Ok(())
}
4 years ago
/// 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() {
4 years ago
println!("-> Client sent: {:?}", line);
4 years ago
req.selector = line;
write_response(&stream, req)?;
}
4 years ago
Ok(())
}
4 years ago
/// 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() {
write_file(w, req)
4 years ago
} else if md.is_dir() {
write_dir(w, req)
} else {
Ok(())
}
4 years ago
}
4 years ago
/// 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();
if !path.ends_with('/') {
path.push('/');
}
4 years ago
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,
)?;
4 years ago
}
4 years ago
menu.end()?;
Ok(())
4 years ago
}
4 years ago
/// Send a file to the client based on a Request.
fn write_file<'a, W>(mut w: &'a W, req: Request) -> Result<()>
4 years ago
where
&'a W: Write,
{
4 years ago
let path = req.file_path();
4 years ago
let md = fs::metadata(&path)?;
let mut f = fs::File::open(&path)?;
4 years ago
let mut buf = [0; TCP_BUF_SIZE];
4 years ago
let mut bytes = md.len();
while bytes > 0 {
let n = f.read(&mut buf[..])?;
bytes -= n as u64;
w.write_all(&buf[..n])?;
}
4 years ago
Ok(())
}
4 years ago
/// Determine the gopher type for a DirEntry on disk.
fn file_type(dir: &fs::DirEntry) -> ItemType {
4 years ago
let metadata = match dir.metadata() {
Err(_) => return ItemType::Error,
Ok(md) => md,
};
4 years ago
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
4 years ago
} else {
ItemType::File
4 years ago
}
} else {
ItemType::Binary
4 years ago
}
} else if metadata.is_dir() {
ItemType::Directory
4 years ago
} else {
ItemType::Error
4 years ago
}
}