use std::future::Future;
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use crate::Query;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Request {
pub query: Query,
pub peer: SocketAddr,
}
pub trait Handler: Send + Sync + 'static {
fn handle(&self, request: Request) -> impl Future<Output = Vec<u8>> + Send;
}
impl<F, Fut> Handler for F
where
F: Fn(Request) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Vec<u8>> + Send,
{
fn handle(&self, request: Request) -> impl Future<Output = Vec<u8>> + Send {
self(request)
}
}
#[derive(Debug, Clone)]
pub struct ServerConfig {
pub max_request_line: usize,
pub timeout: Duration,
}
impl Default for ServerConfig {
fn default() -> Self {
Self {
max_request_line: 512,
timeout: Duration::from_secs(30),
}
}
}
pub async fn serve(
listener: TcpListener,
handler: impl Handler,
config: ServerConfig,
shutdown: impl Future<Output = ()>,
) -> std::io::Result<()> {
let handler = Arc::new(handler);
let config = Arc::new(config);
tokio::pin!(shutdown);
loop {
tokio::select! {
_ = &mut shutdown => break,
accepted = listener.accept() => match accepted {
Ok((stream, peer)) => {
let handler = handler.clone();
let config = config.clone();
tokio::spawn(async move {
if let Err(error) = handle_connection(stream, peer, handler, &config).await {
log::debug!("finger: connection from {peer} failed: {error}");
}
});
}
Err(error) => log::warn!("finger: accept failed: {error}"),
},
}
}
Ok(())
}
async fn handle_connection(
mut stream: TcpStream,
peer: SocketAddr,
handler: Arc<impl Handler>,
config: &ServerConfig,
) -> std::io::Result<()> {
let mut line = Vec::with_capacity(32);
let mut byte = [0u8; 1];
loop {
let count = tokio::time::timeout(config.timeout, stream.read(&mut byte))
.await
.map_err(|_| std::io::Error::new(std::io::ErrorKind::TimedOut, "query read"))??;
if count == 0 || byte[0] == b'\n' {
break;
}
line.push(byte[0]);
if line.len() >= config.max_request_line {
return stream.shutdown().await;
}
}
let request = Request {
query: parse_query(&line),
peer,
};
let body = handler.handle(request).await;
stream.write_all(&body).await?;
stream.shutdown().await
}
pub fn parse_query(line: &[u8]) -> Query {
let text = String::from_utf8_lossy(line);
let text = text.trim_end_matches(['\r', '\n']);
let (verbose, rest) = match text.strip_prefix("/W") {
Some(rest) => (true, rest.trim_start()),
None => (false, text),
};
let user = rest.trim();
Query {
user: (!user.is_empty()).then(|| user.to_string()),
verbose,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_bare_username_is_a_plain_query() {
let q = parse_query(b"alice\r\n");
assert_eq!(q.user.as_deref(), Some("alice"));
assert!(!q.verbose);
}
#[test]
fn an_empty_line_is_a_listing_request() {
assert_eq!(parse_query(b"\r\n").user, None);
assert_eq!(parse_query(b"").user, None);
}
#[test]
fn the_verbose_switch_is_recognised_with_and_without_a_user() {
let q = parse_query(b"/W alice\r\n");
assert_eq!(q.user.as_deref(), Some("alice"));
assert!(q.verbose);
let q = parse_query(b"/W\r\n");
assert_eq!(q.user, None);
assert!(q.verbose);
}
#[test]
fn the_wire_form_and_the_parser_agree() {
for query in [
Query::user("alice"),
Query::user("bob").verbose(),
Query::default(),
Query::default().verbose(),
] {
assert_eq!(parse_query(query.wire().as_bytes()), query);
}
}
#[test]
fn a_bare_lf_is_accepted_as_well_as_crlf() {
assert_eq!(parse_query(b"alice\n").user.as_deref(), Some("alice"));
}
#[test]
fn surrounding_whitespace_does_not_become_part_of_the_name() {
assert_eq!(parse_query(b" alice \r\n").user.as_deref(), Some("alice"));
}
}