use tokio::io::{AsyncReadExt as _, AsyncWriteExt as _};
use tokio::net::TcpStream;
const PEEK_BYTES: usize = 2048;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Kind {
Upgrade,
Page,
Other,
}
pub(super) async fn classify(stream: &TcpStream) -> Kind {
let mut head = [0_u8; PEEK_BYTES];
let Ok(read) = stream.peek(&mut head).await else {
return Kind::Other;
};
let Ok(text) = core::str::from_utf8(head.get(..read).unwrap_or_default()) else {
return Kind::Other;
};
let mut lines = text.lines();
let Some(request) = lines.next() else {
return Kind::Other;
};
if !request.starts_with("GET ") {
return Kind::Other;
}
let mut upgrades = false;
let mut connection_upgrade = false;
let mut has_key = false;
for line in lines {
if line.is_empty() {
break;
}
let Some((name, value)) = line.split_once(':') else {
continue;
};
let value = value.trim();
if name.eq_ignore_ascii_case("upgrade") && value.eq_ignore_ascii_case("websocket") {
upgrades = true;
} else if name.eq_ignore_ascii_case("connection") {
connection_upgrade = value
.split(&[' ', ','][..])
.any(|token| token.eq_ignore_ascii_case("upgrade"));
} else if name.eq_ignore_ascii_case("sec-websocket-key") && !value.is_empty() {
has_key = true;
}
}
if upgrades && connection_upgrade && has_key {
Kind::Upgrade
} else {
Kind::Page
}
}
pub(super) async fn serve_page(mut stream: TcpStream, body: &str) {
let response = format!(
"HTTP/1.1 200 OK\r\n\
Content-Type: text/html; charset=utf-8\r\n\
Content-Length: {}\r\n\
Cache-Control: no-store\r\n\
Connection: close\r\n\
\r\n\
{body}",
body.len()
);
let _ = stream.write_all(response.as_bytes()).await;
let _ = stream.flush().await;
let mut drain = [0_u8; PEEK_BYTES];
let _ = stream.read(&mut drain).await;
}