use std::io::{self, Read, Write};
use std::net::TcpStream;
use std::sync::Mutex;
use std::time::{Duration, Instant};
use crate::error::Result;
use crate::session::Session;
pub(crate) const READ_BUF: usize = 16 * 1024;
pub(crate) const IO_TIMEOUT: Duration = Duration::from_secs(30);
pub(crate) const MIN_PROGRESS: usize = 256;
pub(crate) fn apply_timeouts(stream: &TcpStream) {
let _ = stream.set_read_timeout(Some(IO_TIMEOUT));
let _ = stream.set_write_timeout(Some(IO_TIMEOUT));
}
pub(crate) const ACCEPT_BACKOFF: Duration = Duration::from_millis(50);
const ACCEPT_LOG_EVERY: Duration = Duration::from_secs(5);
pub(crate) fn note_accept_error(label: &str, e: &io::Error) -> bool {
let exhausted = matches!(e.raw_os_error(), Some(24) | Some(23));
static THROTTLE: Mutex<Option<(Instant, u64)>> = Mutex::new(None);
if let Ok(mut guard) = THROTTLE.lock() {
let now = Instant::now();
let report = match *guard {
Some((last, suppressed)) if now.duration_since(last) < ACCEPT_LOG_EVERY => {
*guard = Some((last, suppressed + 1));
None
}
other => {
*guard = Some((now, 0));
Some(other.map_or(0, |(_, suppressed)| suppressed))
}
};
if let Some(suppressed) = report {
if suppressed > 0 {
eprintln!("httpsd: {label}: {e} (+{suppressed} suppressed)");
} else {
eprintln!("httpsd: {label}: {e}");
}
}
}
exhausted
}
pub(crate) fn serve_blocking<S: Read + Write>(stream: &mut S, session: &mut Session) -> Result<()> {
serve_blocking_prefed(stream, session, &[])
}
pub(crate) fn serve_blocking_prefed<S: Read + Write>(
stream: &mut S,
session: &mut Session,
initial: &[u8],
) -> Result<()> {
let mut buf = [0u8; READ_BUF];
let mut pending = initial;
let mut window_deadline = Instant::now() + IO_TIMEOUT;
let mut window_bytes: usize = 0;
loop {
let received = if !pending.is_empty() {
let r = session.received(pending);
window_bytes = window_bytes.saturating_add(pending.len());
pending = &[];
r
} else {
let n = stream.read(&mut buf)?;
if n == 0 {
break; }
window_bytes = window_bytes.saturating_add(n);
session.received(&buf[..n])
};
if window_bytes >= MIN_PROGRESS {
window_deadline = Instant::now() + IO_TIMEOUT;
window_bytes = 0;
} else if Instant::now() >= window_deadline {
break; }
loop {
match session.to_send() {
Ok(out) if !out.is_empty() => {
stream.write_all(&out)?;
stream.flush()?;
if !session.has_output() {
break;
}
}
_ => break,
}
}
received?;
if session.wants_close() {
break;
}
}
Ok(())
}