#![cfg(feature = "server")]
use ehttpd::bytes::{Data, Sink, Source};
use ehttpd::http::Response;
use ehttpd::server::Server;
use std::io::{BufWriter, Read, Write};
use std::net::{TcpListener, TcpStream};
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
#[test]
fn request_with_unread_body_is_not_rescheduled() {
const TIMEOUT: Duration = Duration::from_secs(1);
let (request_tx, requests) = mpsc::channel();
let server: Server<65_536> = Server::with_request_response(1, move |request| {
request_tx.send(request.target).expect("failed to report request");
Response::new_200_ok()
});
let source = Source::from(concat! {
"POST /first HTTP/1.1\r\n",
"Content-Length: 6\r\n",
"Connection: keep-alive\r\n",
"\r\n",
"<BODY>",
"GET /second HTTP/1.1\r\n",
"Content-Length: 6\r\n",
"Connection: keep-alive\r\n",
"\r\n",
"<BODY>",
});
server.dispatch(source, Sink::default()).expect("failed to dispatch connection");
let first = requests.recv_timeout(TIMEOUT);
assert_eq!(first, Ok(Data::new_static(b"/first")));
let second = requests.recv_timeout(TIMEOUT);
assert!(second.is_err());
}
#[test]
fn request_with_consumed_body_is_rescheduled() {
const TIMEOUT: Duration = Duration::from_secs(1);
let (request_tx, requests) = mpsc::channel();
let server: Server<65_536> = Server::with_request_response(1, move |request| {
let mut body_buf = *b"<BODY>";
request.stream.read_exact(&mut body_buf).expect("failed to read body");
request_tx.send(request.target).expect("failed to report request");
Response::new_200_ok()
});
let source = Source::from(concat! {
"POST /first HTTP/1.1\r\n",
"Content-Length: 6\r\n",
"Connection: keep-alive\r\n",
"\r\n",
"<BODY>",
"GET /second HTTP/1.1\r\n",
"Content-Length: 6\r\n",
"Connection: keep-alive\r\n",
"\r\n",
"<BODY>",
});
server.dispatch(source, Sink::default()).expect("failed to dispatch connection");
let first = requests.recv_timeout(TIMEOUT);
assert_eq!(first, Ok(Data::new_static(b"/first")));
let second = requests.recv_timeout(TIMEOUT);
assert_eq!(second, Ok(Data::new_static(b"/second")));
}
#[test]
fn bridge_request_response_writes_response() {
const TIMEOUT: Duration = Duration::from_secs(1);
let server: Server<65_536> = Server::with_request_response(1, |_| {
let mut response = Response::new_200_ok();
response.set_body_data("hello");
response
});
let listener = TcpListener::bind(("127.0.0.1", 0)).expect("failed to bind response socket");
let mut output = TcpStream::connect(listener.local_addr().expect("failed to get response address"))
.expect("failed to connect response socket");
output.set_read_timeout(Some(TIMEOUT)).expect("failed to set response timeout");
let (sink, _) = listener.accept().expect("failed to accept response socket");
let source = Source::from(concat! {
"GET / HTTP/1.1\r\n",
"Connection: close\r\n",
"\r\n",
});
server.dispatch(source, Sink::from(BufWriter::new(sink))).expect("failed to dispatch connection");
let mut response = Vec::new();
output.read_to_end(&mut response).expect("failed to read response");
let expected = concat! {
"HTTP/1.1 200 OK\r\n",
"Content-Length: 5\r\n",
"\r\n",
"hello",
};
assert_eq!(response, expected.as_bytes());
}
#[test]
fn bridge_request_response_suppresses_head_body() {
const TIMEOUT: Duration = Duration::from_secs(1);
let server: Server<65_536> = Server::with_request_response(1, |_| {
let mut response = Response::new_200_ok();
response.set_body_data("hello");
response
});
let listener = TcpListener::bind(("127.0.0.1", 0)).expect("failed to bind response socket");
let mut output = TcpStream::connect(listener.local_addr().expect("failed to get response address"))
.expect("failed to connect response socket");
output.set_read_timeout(Some(TIMEOUT)).expect("failed to set response timeout");
let (sink, _) = listener.accept().expect("failed to accept response socket");
let source = Source::from(concat! {
"HEAD / HTTP/1.1\r\n",
"Connection: close\r\n",
"\r\n",
});
server.dispatch(source, Sink::from(BufWriter::new(sink))).expect("failed to dispatch connection");
let mut response = Vec::new();
output.read_to_end(&mut response).expect("failed to read response");
let expected = concat! {
"HTTP/1.1 200 OK\r\n",
"Content-Length: 5\r\n",
"\r\n",
};
assert_eq!(response, expected.as_bytes());
}
#[test]
fn bridge_request_response_honors_response_connection_close() {
const TIMEOUT: Duration = Duration::from_secs(1);
let (request_tx, requests) = mpsc::channel();
let server: Server<65_536> = Server::with_request_response(1, move |request| {
request_tx.send(request.target).expect("failed to report request");
let mut response = Response::new_200_ok();
response.set_connection_close();
response
});
let source = Source::from(concat! {
"GET /first HTTP/1.1\r\n",
"\r\n",
"GET /second HTTP/1.1\r\n",
"\r\n",
});
server.dispatch(source, Sink::default()).expect("failed to dispatch connection");
assert_eq!(requests.recv_timeout(TIMEOUT), Ok(Data::new_static(b"/first")));
assert!(requests.recv_timeout(TIMEOUT).is_err());
}
#[test]
fn incomplete_connection_times_out_after_twenty_seconds() {
const STARTUP_TIMEOUT: Duration = Duration::from_secs(3);
const TIMEOUT_LOWER_BOUND: Duration = Duration::from_secs(19);
const TIMEOUT_UPPER_BOUND: Duration = Duration::from_secs(25);
const CLIENT_TIMEOUT: Duration = Duration::from_secs(30);
let probe = TcpListener::bind(("127.0.0.1", 0)).expect("failed to reserve test address");
let address = probe.local_addr().expect("failed to get test address");
drop(probe);
thread::spawn(move || {
let server: Server<65_536> =
Server::with_request_response(1, |_| panic!("incomplete request must not reach the handler"));
let result = server.accept(address);
panic!("test server stopped accepting connections: {result:?}");
});
thread::sleep(STARTUP_TIMEOUT);
let mut connection = TcpStream::connect(address).expect("failed to connect to server");
connection.set_read_timeout(Some(CLIENT_TIMEOUT)).expect("failed to set client timeout");
connection.write_all(b"GET / HTTP/1.1\r\n").expect("failed to write incomplete request");
let started = Instant::now();
let mut response = Vec::new();
connection.read_to_end(&mut response).expect("server did not close the timed out connection");
let elapsed = started.elapsed();
assert!(response.is_empty());
assert!(elapsed >= TIMEOUT_LOWER_BOUND, "connection closed after {elapsed:?}");
assert!(elapsed <= TIMEOUT_UPPER_BOUND, "connection closed after {elapsed:?}");
}