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 struct BoundServer {
pub(crate) listener: TcpListener,
pub(crate) core: Arc<ServerCore>,
pub(crate) route: String,
pub(crate) allow_origins: 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 = self.route.clone();
let allow_origins = self.allow_origins.clone();
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,
cap: usize,
deadline: Instant,
) -> Result<(), LineFail> {
out.clear();
let mut bytes = Vec::new();
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 bytes.len() + chunk.len() > cap {
return Err(LineFail::TooLong);
}
bytes.extend_from_slice(chunk);
reader.consume(pos + 1);
return match String::from_utf8(bytes) {
Ok(text) => {
*out = text;
Ok(())
}
Err(_) => Err(LineFail::Closed), };
}
if bytes.len() + available.len() > cap {
return Err(LineFail::TooLong);
}
bytes.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::new(stream.try_clone()?);
let mut stream = stream;
loop {
let deadline = Instant::now() + REQUEST_DEADLINE;
let mut request_line = String::new();
match read_line_capped(&mut reader, &mut request_line, 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 = request_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 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: Option<String> = None;
let mut origin_duplicate = false;
let mut host: Option<String> = None;
let mut host_duplicate = false;
let mut accept = String::new();
let mut expect_continue = false;
let mut close = http10; let mut header_count = 0usize;
loop {
let mut header = String::new();
match read_line_capped(&mut reader, &mut header, 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 = header.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().to_ascii_lowercase();
let value = value.trim();
match name.as_str() {
"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;
}
}
}
"transfer-encoding" => transfer_encoding = true, "origin" => {
if origin.is_some() {
origin_duplicate = true;
} else {
origin = Some(value.to_string());
}
}
"host" => {
if host.is_some() {
host_duplicate = true;
} else {
host = Some(value.to_string());
}
}
"accept" => {
let v = value.to_ascii_lowercase();
if accept.is_empty() {
accept = v;
} else {
accept.push_str(", ");
accept.push_str(&v);
}
}
"connection" => {
let lower = value.to_ascii_lowercase();
let tokens: Vec<&str> = lower.split(',').map(str::trim).collect();
if tokens.contains(&"close") {
close = true;
}
if http10 {
if tokens.contains(&"keep-alive") && !tokens.contains(&"close") {
close = false;
} else if !tokens.contains(&"keep-alive") {
close = true;
}
}
}
"expect" if value.to_ascii_lowercase().contains("100-continue") => {
expect_continue = true;
}
_ => {}
}
}
}
if origin_duplicate {
respond_error(
&mut stream,
"400 Bad Request",
"en-tete Origin duplique",
"",
)?;
return Ok(());
}
if let Some(origin) = &origin {
let allowed = allow_origins.iter().any(|o| o.eq_ignore_ascii_case(origin));
if !allowed {
respond_error(&mut stream, "403 Forbidden", "origin refusée", "")?;
return Ok(());
}
}
if !http10 {
if host.is_none() {
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 path != route {
respond_error(&mut stream, "404 Not Found", "route inconnue", "")?;
return Ok(());
}
if verb != "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(());
}
};
let sse_only = accept.contains("text/event-stream") && !accept.contains("application/json");
if sse_only {
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",
"",
"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 head = format!(
"HTTP/1.1 {status}\r\n{extra_headers}Content-Type: {content_type}\r\nContent-Length: {}\r\nConnection: {connection}\r\n\r\n",
body.len()
);
stream.write_all(head.as_bytes())?;
stream.write_all(body)?;
stream.flush()
}