use crate::server::ServerCore;
use std::io::{BufRead, BufReader, Read, Write};
use std::net::{SocketAddr, TcpListener, TcpStream};
use std::panic::AssertUnwindSafe;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::{Duration, Instant};
pub const MAX_BODY: usize = 1 << 20;
pub const MAX_HEADERS: usize = 64;
pub const MAX_LINE: usize = 16 * 1024;
pub const READ_TIMEOUT: Duration = Duration::from_secs(30);
pub const WRITE_TIMEOUT: Duration = Duration::from_secs(10);
pub const REQUEST_DEADLINE: Duration = Duration::from_secs(60);
pub const MAX_CONNECTIONS: usize = 256;
pub const HEALTH_ROUTE: &str = "/health";
pub const SSE_ANTI_BUFFERING: &str =
"Cache-Control: no-cache, no-transform\r\nX-Accel-Buffering: no\r\n";
pub struct BoundServer {
pub(crate) listener: TcpListener,
pub(crate) core: Arc<ServerCore>,
pub(crate) route: Arc<String>,
pub(crate) allow_origins: Arc<Vec<String>>,
pub(crate) read_timeout: Duration,
pub(crate) max_connections: usize,
pub(crate) live_connections: Arc<AtomicUsize>,
}
impl BoundServer {
pub fn local_addr(&self) -> std::io::Result<SocketAddr> {
self.listener.local_addr()
}
pub fn serve(self) -> std::io::Result<()> {
for stream in self.listener.incoming() {
let Ok(stream) = stream else {
continue;
};
let _ = stream.set_read_timeout(Some(self.read_timeout));
let _ = stream.set_write_timeout(Some(WRITE_TIMEOUT));
let _ = stream.set_nodelay(true);
let prev = self.live_connections.fetch_add(1, Ordering::Relaxed);
if prev >= self.max_connections {
self.live_connections.fetch_sub(1, Ordering::Relaxed);
let _ = respond(
&mut &stream,
"503 Service Unavailable",
"",
"application/json",
br#"{"jsonrpc":"2.0","id":null,"error":{"code":-32000,"message":"trop de connexions"}}"#,
false,
);
continue;
}
let core = Arc::clone(&self.core);
let route = Arc::clone(&self.route);
let allow_origins = Arc::clone(&self.allow_origins);
let live = Arc::clone(&self.live_connections);
std::thread::spawn(move || {
struct LiveGuard(Arc<AtomicUsize>);
impl Drop for LiveGuard {
fn drop(&mut self) {
self.0.fetch_sub(1, Ordering::Relaxed);
}
}
let _guard = LiveGuard(live);
let _ = handle_connection(stream, &core, &route, &allow_origins);
});
}
Ok(())
}
}
enum LineFail {
TooLong,
Slow,
Closed,
}
fn read_line_capped(
reader: &mut BufReader<TcpStream>,
out: &mut String,
scratch: &mut Vec<u8>,
cap: usize,
deadline: Instant,
) -> Result<(), LineFail> {
out.clear();
scratch.clear();
loop {
if Instant::now() >= deadline {
return Err(LineFail::Slow);
}
let available = match reader.fill_buf() {
Ok(bytes) => bytes,
Err(error)
if matches!(
error.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
) =>
{
return Err(LineFail::Slow);
}
Err(_) => return Err(LineFail::Closed),
};
if available.is_empty() {
return Err(LineFail::Closed);
}
if let Some(pos) = available.iter().position(|&byte| byte == b'\n') {
let chunk = &available[..=pos];
if scratch.len() + chunk.len() > cap {
return Err(LineFail::TooLong);
}
scratch.extend_from_slice(chunk);
reader.consume(pos + 1);
return match std::str::from_utf8(scratch) {
Ok(text) => {
out.push_str(text);
Ok(())
}
Err(_) => Err(LineFail::Closed), };
}
if scratch.len() + available.len() > cap {
return Err(LineFail::TooLong);
}
scratch.extend_from_slice(available);
let filled = available.len();
reader.consume(filled);
}
}
fn handle_connection(
stream: TcpStream,
core: &ServerCore,
route: &str,
allow_origins: &[String],
) -> std::io::Result<()> {
let mut reader = BufReader::with_capacity(4 * 1024, stream.try_clone()?);
let mut stream = stream;
let mut line = String::new();
let mut scratch = Vec::new();
loop {
let deadline = Instant::now() + REQUEST_DEADLINE;
match read_line_capped(&mut reader, &mut line, &mut scratch, MAX_LINE, deadline) {
Ok(()) => {}
Err(LineFail::TooLong) => {
respond(
&mut stream,
"431 Request Header Fields Too Large",
"",
"application/json",
br#"{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"ligne trop longue"}}"#,
false,
)?;
return Ok(());
}
Err(_) => return Ok(()),
}
let mut parts = line.split_whitespace();
let verb = parts.next().unwrap_or("");
let full_path = parts.next().unwrap_or("");
let version = parts.next().unwrap_or("");
if verb.is_empty() || full_path.is_empty() || version.is_empty() || parts.next().is_some() {
respond_error(
&mut stream,
"400 Bad Request",
"ligne de requête invalide",
"",
)?;
return Ok(());
}
let http10 = version.eq_ignore_ascii_case("HTTP/1.0");
if !http10 && !version.eq_ignore_ascii_case("HTTP/1.1") {
respond_error(
&mut stream,
"505 HTTP Version Not Supported",
"version HTTP non supportée",
"",
)?;
return Ok(());
}
let path = full_path.split('?').next().unwrap_or(full_path);
let is_post = verb == "POST";
let is_get = verb == "GET";
let route_matched = path == route;
let health_probe = path == HEALTH_ROUTE;
let mut content_length: Option<usize> = None;
let mut saw_content_length = false;
let mut malformed_length = false;
let mut duplicate_length = false;
let mut transfer_encoding = false;
let mut origin_seen = false;
let mut origin_duplicate = false;
let mut origin_allowed = false;
let mut host_seen = false;
let mut host_duplicate = false;
let mut accept_json = false;
let mut accept_sse = false;
let mut expect_continue = false;
let mut close = http10; let mut header_count = 0usize;
loop {
match read_line_capped(&mut reader, &mut line, &mut scratch, MAX_LINE, deadline) {
Ok(()) => {}
Err(LineFail::TooLong) => {
respond(
&mut stream,
"431 Request Header Fields Too Large",
"",
"application/json",
r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"ligne trop longue"}}"#.as_bytes(),
false,
)?;
return Ok(());
}
Err(_) => return Ok(()),
}
let header = line.trim_end();
if header.is_empty() {
break;
}
if header.starts_with(' ') || header.starts_with('\t') {
respond_error(
&mut stream,
"400 Bad Request",
"ligne d'en-tête obsolète (obs-fold)",
"",
)?;
return Ok(());
}
header_count += 1;
if header_count > MAX_HEADERS {
respond(
&mut stream,
"431 Request Header Fields Too Large",
"",
"application/json",
r#"{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"trop d'en-têtes"}}"#.as_bytes(),
false,
)?;
return Ok(());
}
if let Some((name, value)) = header.split_once(':') {
let name = name.trim();
let value = value.trim();
if name.eq_ignore_ascii_case("content-length") {
if saw_content_length {
duplicate_length = true;
} else {
saw_content_length = true;
if !value.is_empty() && value.bytes().all(|b| b.is_ascii_digit()) {
content_length = value.parse().ok();
if content_length.is_none() {
malformed_length = true;
}
} else {
malformed_length = true;
}
}
} else if name.eq_ignore_ascii_case("transfer-encoding") {
transfer_encoding = true; } else if name.eq_ignore_ascii_case("origin") {
if origin_seen {
origin_duplicate = true;
} else {
origin_seen = true;
origin_allowed =
allow_origins.iter().any(|o| o.eq_ignore_ascii_case(value));
}
} else if name.eq_ignore_ascii_case("host") {
if host_seen {
host_duplicate = true;
} else {
host_seen = true;
}
} else if name.eq_ignore_ascii_case("accept") {
accept_json |= contains_ignore_case(value, "application/json");
accept_sse |= contains_ignore_case(value, "text/event-stream");
} else if name.eq_ignore_ascii_case("connection") {
let mut has_close = false;
let mut has_keep_alive = false;
for token in value.split(',') {
let token = token.trim();
if token.eq_ignore_ascii_case("close") {
has_close = true;
} else if token.eq_ignore_ascii_case("keep-alive") {
has_keep_alive = true;
}
}
if has_close {
close = true;
}
if http10 {
if has_keep_alive && !has_close {
close = false;
} else if !has_keep_alive {
close = true;
}
}
} else if name.eq_ignore_ascii_case("expect")
&& contains_ignore_case(value, "100-continue")
{
expect_continue = true;
}
}
}
if origin_duplicate {
respond_error(
&mut stream,
"400 Bad Request",
"en-tete Origin duplique",
"",
)?;
return Ok(());
}
if origin_seen && !origin_allowed {
respond_error(&mut stream, "403 Forbidden", "origin refusée", "")?;
return Ok(());
}
if !http10 {
if !host_seen {
respond_error(&mut stream, "400 Bad Request", "en-tête Host requis", "")?;
return Ok(());
}
if host_duplicate {
respond_error(&mut stream, "400 Bad Request", "en-tête Host dupliqué", "")?;
return Ok(());
}
}
if health_probe {
if is_get {
respond(&mut stream, "200 OK", "", "text/plain", b"ok", !close)?;
if close {
break;
}
continue;
}
respond_error(
&mut stream,
"405 Method Not Allowed",
"GET uniquement",
"Allow: GET\r\n",
)?;
return Ok(());
}
if !route_matched {
respond_error(&mut stream, "404 Not Found", "route inconnue", "")?;
return Ok(());
}
if !is_post {
respond_error(
&mut stream,
"405 Method Not Allowed",
"POST uniquement",
"Allow: POST\r\n",
)?;
return Ok(());
}
if transfer_encoding || duplicate_length {
respond_error(
&mut stream,
"400 Bad Request",
"encodage de requête ambigu",
"",
)?;
return Ok(());
}
let len = match content_length {
Some(len) => len,
None if malformed_length => {
respond_error(
&mut stream,
"400 Bad Request",
"Content-Length invalide",
"",
)?;
return Ok(());
}
None => {
respond_error(
&mut stream,
"411 Length Required",
"Content-Length requis",
"",
)?;
return Ok(());
}
};
if len > MAX_BODY {
respond_error(
&mut stream,
"413 Payload Too Large",
"corps trop volumineux",
"",
)?;
return Ok(());
}
if expect_continue && len > 0 {
stream.write_all(b"HTTP/1.1 100 Continue\r\n\r\n")?;
stream.flush()?;
}
let mut body = vec![0u8; len];
if read_exact_deadline(&mut reader, &mut body, deadline).is_err() {
let _ = respond_error(
&mut stream,
"400 Bad Request",
"corps tronque ou trop lent",
"",
);
return Ok(());
}
let dispatch_result = std::panic::catch_unwind(AssertUnwindSafe(|| core.dispatch(&body)));
let reply = match dispatch_result {
Ok(Some(reply)) => reply,
Ok(None) => {
respond(
&mut stream,
"202 Accepted",
"",
"application/json",
b"",
!close,
)?;
if close {
break;
}
continue;
}
Err(_) => {
let payload = serde_json::json!({
"jsonrpc": "2.0",
"id": null,
"error": { "code": -32603, "message": "erreur interne" }
});
respond(
&mut stream,
"200 OK",
"",
"application/json",
payload.to_string().as_bytes(),
false,
)?;
return Ok(());
}
};
if accept_sse && !accept_json {
let mut event = Vec::with_capacity(reply.len() + 8);
event.extend_from_slice(b"data: ");
event.extend_from_slice(&reply);
event.extend_from_slice(b"\n\n");
respond(
&mut stream,
"200 OK",
SSE_ANTI_BUFFERING,
"text/event-stream",
&event,
false,
)?;
break; }
respond(
&mut stream,
"200 OK",
"",
"application/json",
&reply,
!close,
)?;
if close {
break;
}
}
Ok(())
}
fn respond_error(
stream: &mut TcpStream,
status: &str,
message: &str,
extra_headers: &str,
) -> std::io::Result<()> {
let payload: &[u8] = &error_payload(message);
respond(
stream,
status,
extra_headers,
"application/json",
payload,
false,
)?;
let _ = stream.shutdown(std::net::Shutdown::Write);
Ok(())
}
fn error_payload(message: &str) -> Vec<u8> {
serde_json::json!({
"jsonrpc": "2.0",
"id": null,
"error": { "code": -32600, "message": message }
})
.to_string()
.into_bytes()
}
fn read_exact_deadline(
reader: &mut BufReader<TcpStream>,
buf: &mut [u8],
deadline: Instant,
) -> std::io::Result<()> {
let mut filled = 0usize;
while filled < buf.len() {
if Instant::now() >= deadline {
return Err(std::io::Error::new(
std::io::ErrorKind::TimedOut,
"deadline dépassée",
));
}
let n = reader.read(&mut buf[filled..])?;
if n == 0 {
return Err(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"corps tronqué",
));
}
filled += n;
}
Ok(())
}
fn respond(
stream: &mut impl Write,
status: &str,
extra_headers: &str,
content_type: &str,
body: &[u8],
keep_alive: bool,
) -> std::io::Result<()> {
let connection = if keep_alive { "keep-alive" } else { "close" };
let mut buf = Vec::with_capacity(
96 + status.len() + extra_headers.len() + content_type.len() + body.len(),
);
write!(
buf,
"HTTP/1.1 {status}\r\n{extra_headers}Content-Type: {content_type}\r\nContent-Length: {}\r\nConnection: {connection}\r\n\r\n",
body.len()
)?;
buf.extend_from_slice(body);
stream.write_all(&buf)?;
stream.flush()
}
fn contains_ignore_case(haystack: &str, needle: &str) -> bool {
let (h, n) = (haystack.as_bytes(), needle.as_bytes());
!n.is_empty() && h.windows(n.len()).any(|w| w.eq_ignore_ascii_case(n))
}