#![cfg(feature = "std")]
use std::{
io::{Read, Write},
net::{SocketAddr, TcpStream},
sync::{
Arc,
atomic::{AtomicUsize, Ordering},
},
thread::{self, JoinHandle},
time::{Duration, Instant},
};
use http_net::{
ClientConn, ClientEvent, Limits, Request, RequestHead, Response,
blocking::{self, Connection, Server, ServerConfig, ShutdownHandle},
};
struct Running {
addr: SocketAddr,
stop: ShutdownHandle,
thread: Option<JoinHandle<std::io::Result<()>>>,
}
impl Drop for Running {
fn drop(&mut self) {
self.stop.shutdown();
if let Some(t) = self.thread.take() {
t.join().expect("server thread").expect("serve");
}
}
}
fn start(server: Server, handler: fn(&RequestHead<'_>, &[u8]) -> Response) -> Running {
let addr = server.local_addr();
let stop = server.shutdown_handle();
let thread = Some(thread::spawn(move || server.serve(handler)));
Running { addr, stop, thread }
}
fn echo(req: &RequestHead<'_>, body: &[u8]) -> Response {
match req.path() {
"/echo" => Response::new(200)
.header("content-type", "application/octet-stream")
.body(body.to_vec()),
"/stream" => Response::new(200).chunked(),
path => Response::text(200, &format!("{} {path}", req.method_str())),
}
}
fn connect(addr: SocketAddr) -> TcpStream {
let s = TcpStream::connect(addr).expect("connect");
s.set_read_timeout(Some(Duration::from_secs(10)))
.expect("timeout");
s
}
fn read_response(stream: &mut TcpStream, conn: &mut ClientConn) -> (u16, Vec<u8>, bool) {
let mut status = 0;
let mut body = Vec::new();
let mut close = false;
loop {
match conn.poll().expect("response") {
ClientEvent::Response(r) => {
status = r.status();
close = !r.keep_alive();
}
ClientEvent::Body(b) => body.extend_from_slice(b),
ClientEvent::ResponseEnd => return (status, body, close),
ClientEvent::NeedInput => {
let n = stream.read(conn.read_buf()).expect("read");
if n == 0 {
conn.feed_eof();
} else {
conn.commit(n);
}
}
other => panic!("unexpected {other:?}"),
}
}
}
#[test]
fn test_tier1_server_and_get() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
for path in ["/", "/a/b?c=d", "/%7Euser"] {
let resp = blocking::get(&format!("http://{}{path}", running.addr)).unwrap();
assert_eq!(resp.status(), 200);
let expected = format!("GET {}", path.split('?').next().unwrap());
assert_eq!(resp.text(), Some(expected.as_str()));
assert!(resp.header("date").is_some_and(|d| d.ends_with(b" GMT")));
assert_eq!(
resp.header("content-type"),
Some(&b"text/plain; charset=utf-8"[..])
);
}
}
#[test]
fn test_keep_alive_and_pipelining_on_one_socket() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let mut stream = connect(running.addr);
let mut conn = ClientConn::new();
for i in 0..5 {
let mut out = Vec::new();
conn.send_request(
&Request::get(&format!("/seq/{i}")).header("host", "x"),
&mut out,
)
.unwrap();
stream.write_all(&out).unwrap();
let (status, body, close) = read_response(&mut stream, &mut conn);
assert_eq!((status, close), (200, false));
assert_eq!(body, format!("GET /seq/{i}").as_bytes());
assert!(matches!(conn.poll().unwrap(), ClientEvent::Idle));
}
stream
.write_all(
b"GET /p1 HTTP/1.1\r\nHost: x\r\n\r\nGET /p2 HTTP/1.1\r\nHost: x\r\n\r\n\
GET /p3 HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n",
)
.unwrap();
let mut raw = Vec::new();
stream.read_to_end(&mut raw).unwrap();
let text = String::from_utf8(raw).unwrap();
let bodies: Vec<&str> = text
.split("HTTP/1.1 200 OK\r\n")
.skip(1)
.map(|r| r.rsplit("\r\n\r\n").next().unwrap())
.collect();
assert_eq!(bodies, ["GET /p1", "GET /p2", "GET /p3"]);
}
#[test]
fn test_chunked_upload_echo_and_chunked_download() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let mut stream = connect(running.addr);
let mut conn = ClientConn::new();
let mut out = Vec::new();
conn.send_request(
&Request::post("/echo").header("host", "x").chunked(),
&mut out,
)
.unwrap();
let payload: Vec<u8> = (0..200_000u32).map(|i| (i % 251) as u8).collect();
for piece in payload.chunks(7_919) {
conn.send_body(piece, &mut out).unwrap();
}
conn.end_body(&mut out).unwrap();
stream.write_all(&out).unwrap();
let (status, body, _) = read_response(&mut stream, &mut conn);
assert_eq!(status, 200);
assert_eq!(body, payload);
}
#[test]
fn test_streamed_response_through_connection() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let server = thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
let mut conn = Connection::new(stream, Limits::new());
while let Some(req) = conn.next_request().unwrap() {
let lines: usize = req.head().query().unwrap_or("3").parse().unwrap();
conn.respond(&Response::new(200).chunked()).unwrap();
for i in 0..lines {
conn.send_body(format!("line {i}\n").as_bytes()).unwrap();
}
conn.end_body().unwrap();
}
conn.close();
});
let resp = blocking::get(&format!("http://{addr}/log?4")).unwrap();
assert_eq!(resp.text(), Some("line 0\nline 1\nline 2\nline 3\n"));
assert_eq!(resp.header("transfer-encoding"), Some(&b"chunked"[..]));
server.join().unwrap();
}
#[test]
fn test_bad_requests_are_answered_and_closed() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
for (raw, status) in [
(&b"GET / HTTP/1.1\r\n\r\n"[..], &b"HTTP/1.1 400 "[..]),
(b"POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 1\r\nTransfer-Encoding: chunked\r\n\r\n", b"HTTP/1.1 400 "),
(b"POST / HTTP/1.1\r\nHost: x\r\nContent-Length: 2000000\r\n\r\n", b"HTTP/1.1 413 "),
(b"GET / HTTP/2.0\r\nHost: x\r\n\r\n", b"HTTP/1.1 505 "),
(b"POST / HTTP/1.1\r\nHost: x\r\nTransfer-Encoding: gzip, chunked\r\n\r\n", b"HTTP/1.1 501 "),
] {
let mut stream = connect(running.addr);
stream.write_all(raw).unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
assert!(reply.starts_with(status), "{}", String::from_utf8_lossy(&reply));
assert!(reply.windows(17).any(|w| w == b"connection: close"));
}
let mut stream = connect(running.addr);
let mut long = b"GET /".to_vec();
long.extend(std::iter::repeat_n(b'a', 9000));
long.extend_from_slice(b" HTTP/1.1\r\nHost: x\r\n\r\n");
stream.write_all(&long).unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
assert!(reply.starts_with(b"HTTP/1.1 414 "));
}
#[test]
fn test_http10_client_gets_close() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let mut stream = connect(running.addr);
stream.write_all(b"GET /old HTTP/1.0\r\n\r\n").unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
assert!(reply.starts_with(b"HTTP/1.1 200 OK\r\n"));
assert!(reply.ends_with(b"GET /old"));
}
#[test]
fn test_connection_cap_answers_503() {
let server = Server::bind("127.0.0.1:0").unwrap().with_config(
ServerConfig::new()
.with_max_connections(1)
.with_read_timeout(Some(Duration::from_secs(5))),
);
let running = start(server, echo);
let mut first = connect(running.addr);
first
.write_all(b"GET /hold HTTP/1.1\r\nHost: x\r\n\r\n")
.unwrap();
let mut conn = ClientConn::new();
conn.send_request(&Request::get("/hold").header("host", "x"), &mut Vec::new())
.unwrap();
let (status, _, _) = read_response(&mut first, &mut conn);
assert_eq!(status, 200);
let mut second = connect(running.addr);
let mut reply = Vec::new();
second.read_to_end(&mut reply).unwrap();
assert!(
reply.starts_with(b"HTTP/1.1 503 "),
"{}",
String::from_utf8_lossy(&reply)
);
drop(first);
}
#[test]
fn test_upgrade_hands_over_the_socket() {
let upgrades = Arc::new(AtomicUsize::new(0));
let seen = Arc::clone(&upgrades);
let server = Server::bind("127.0.0.1:0")
.unwrap()
.on_upgrade(move |mut up| {
let _ = seen.fetch_add(1, Ordering::SeqCst);
assert_eq!(up.target, "/rpc");
up.stream.write_all(&up.leftover).unwrap();
let mut buf = [0u8; 64];
loop {
match up.stream.read(&mut buf) {
Ok(0) | Err(_) => break,
Ok(n) => up.stream.write_all(&buf[..n]).unwrap(),
}
}
});
let running = start(server, |req, _| {
if req.is_upgrade() && req.header("upgrade") == Some(b"echo") {
Response::switching_protocols("echo")
} else {
Response::new(426)
}
});
let mut stream = connect(running.addr);
let mut conn = ClientConn::new();
let mut out = Vec::new();
conn.send_request(
&Request::get("/rpc").header("host", "x").upgrade("echo"),
&mut out,
)
.unwrap();
out.extend_from_slice(b"early");
stream.write_all(&out).unwrap();
loop {
match conn.poll().unwrap() {
ClientEvent::Response(r) => assert_eq!(r.status(), 101),
ClientEvent::Upgraded => break,
ClientEvent::NeedInput => {
let n = stream.read(conn.read_buf()).unwrap();
assert!(n > 0);
conn.commit(n);
}
other => panic!("unexpected {other:?}"),
}
}
let mut got = conn.remaining_input().to_vec();
stream.write_all(b" and late").unwrap();
let want = b"early and late";
let mut buf = [0u8; 64];
while got.len() < want.len() {
let n = stream.read(&mut buf).unwrap();
assert!(n > 0);
got.extend_from_slice(&buf[..n]);
}
assert_eq!(&got, want);
drop(stream);
drop(running);
assert_eq!(upgrades.load(Ordering::SeqCst), 1);
}
#[test]
fn test_client_errors() {
let addr = std::net::TcpListener::bind("127.0.0.1:0")
.unwrap()
.local_addr()
.unwrap();
let err = blocking::Client::new()
.with_timeout(Duration::from_secs(2))
.get(&format!("http://{addr}/"))
.unwrap_err();
assert!(matches!(err, blocking::Error::Io(_)));
let running = start(Server::bind("127.0.0.1:0").unwrap(), |_, _| {
Response::new(200).body(vec![b'x'; 4096])
});
let err = blocking::Client::new()
.with_limits(Limits::new().with_max_body(1024))
.get(&format!("http://{}/", running.addr))
.unwrap_err();
assert!(matches!(
err,
blocking::Error::Http(http_net::HttpError::BodyTooLarge)
));
}
#[test]
fn test_shutdown_with_idle_server_returns() {
let server = Server::bind("127.0.0.1:0").unwrap();
let stop = server.shutdown_handle();
let t = thread::spawn(move || server.serve(echo));
stop.shutdown();
t.join().unwrap().unwrap();
}
fn trickle(mut stream: TcpStream, piece: &'static [u8], every: Duration, total: Duration) {
let start = Instant::now();
while start.elapsed() < total {
if stream.write_all(piece).is_err() {
return;
}
thread::sleep(every);
}
}
#[test]
fn test_slow_request_is_cut_off_by_the_request_timeout() {
let server = Server::bind("127.0.0.1:0").unwrap().with_config(
ServerConfig::new()
.with_read_timeout(Some(Duration::from_secs(5)))
.with_request_timeout(Some(Duration::from_secs(1))),
);
let running = start(server, echo);
let mut stream = connect(running.addr);
stream.write_all(b"GET / HTTP/1.1\r\nHost: x\r\n").unwrap();
let writer = stream.try_clone().unwrap();
let t = thread::spawn(move || {
trickle(
writer,
b"X: 1\r\n",
Duration::from_millis(150),
Duration::from_secs(6),
);
});
let started = Instant::now();
let mut sink = Vec::new();
let _ = stream.read_to_end(&mut sink);
let took = started.elapsed();
assert!(
took < Duration::from_secs(4),
"connection held for {took:?}"
);
assert!(
sink.starts_with(b"HTTP/1.1 408 "),
"{}",
String::from_utf8_lossy(&sink)
);
t.join().unwrap();
}
#[test]
fn test_slow_body_is_cut_off_by_the_request_timeout() {
let server = Server::bind("127.0.0.1:0").unwrap().with_config(
ServerConfig::new()
.with_read_timeout(Some(Duration::from_secs(5)))
.with_request_timeout(Some(Duration::from_secs(1))),
);
let running = start(server, echo);
let mut stream = connect(running.addr);
stream
.write_all(b"POST /echo HTTP/1.1\r\nHost: x\r\nContent-Length: 100000\r\n\r\n")
.unwrap();
let writer = stream.try_clone().unwrap();
let t = thread::spawn(move || {
trickle(
writer,
b"x",
Duration::from_millis(150),
Duration::from_secs(6),
);
});
let started = Instant::now();
let mut sink = Vec::new();
let _ = stream.read_to_end(&mut sink);
let took = started.elapsed();
assert!(
took < Duration::from_secs(4),
"connection held for {took:?}"
);
t.join().unwrap();
}
#[test]
fn test_request_timeout_does_not_count_idle_keep_alive_time() {
let server = Server::bind("127.0.0.1:0").unwrap().with_config(
ServerConfig::new()
.with_read_timeout(Some(Duration::from_secs(5)))
.with_request_timeout(Some(Duration::from_millis(700))),
);
let running = start(server, echo);
let mut stream = connect(running.addr);
let mut conn = ClientConn::new();
for i in 0..2 {
thread::sleep(Duration::from_millis(900));
let mut out = Vec::new();
conn.send_request(
&Request::get(&format!("/{i}")).header("host", "x"),
&mut out,
)
.unwrap();
stream.write_all(&out).unwrap();
let (status, _, close) = read_response(&mut stream, &mut conn);
assert_eq!((status, close), (200, false));
assert!(matches!(conn.poll().unwrap(), ClientEvent::Idle));
}
}
#[test]
fn test_busy_refusal_never_stalls_the_accept_loop() {
let server = Server::bind("127.0.0.1:0").unwrap().with_config(
ServerConfig::new()
.with_max_connections(1)
.with_read_timeout(Some(Duration::from_secs(10))),
);
let running = start(server, echo);
let mut first = connect(running.addr);
first
.write_all(b"GET / HTTP/1.1\r\nHost: x\r\n\r\n")
.unwrap();
let mut conn = ClientConn::new();
conn.send_request(&Request::get("/").header("host", "x"), &mut Vec::new())
.unwrap();
assert_eq!(read_response(&mut first, &mut conn).0, 200);
let slow = connect(running.addr);
let t = thread::spawn(move || {
trickle(
slow,
b"x",
Duration::from_millis(50),
Duration::from_secs(3),
);
});
thread::sleep(Duration::from_millis(200));
let started = Instant::now();
let mut third = connect(running.addr);
let mut reply = Vec::new();
let _ = third.read_to_end(&mut reply);
let took = started.elapsed();
assert!(
reply.starts_with(b"HTTP/1.1 503 "),
"{}",
String::from_utf8_lossy(&reply)
);
assert!(
took < Duration::from_millis(1500),
"accept loop stalled for {took:?}"
);
t.join().unwrap();
drop(first);
}
#[test]
fn test_lingering_close_has_a_total_deadline() {
let server = Server::bind("127.0.0.1:0").unwrap().with_config(
ServerConfig::new()
.with_max_connections(1)
.with_read_timeout(Some(Duration::from_secs(10))),
);
let running = start(server, echo);
let mut stream = connect(running.addr);
stream
.write_all(b"GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
.unwrap();
let writer = stream.try_clone().unwrap();
let t = thread::spawn(move || {
trickle(
writer,
b"x",
Duration::from_millis(50),
Duration::from_secs(5),
);
});
let mut reply = Vec::new();
let _ = stream.read_to_end(&mut reply);
assert!(reply.starts_with(b"HTTP/1.1 200 "));
thread::sleep(Duration::from_millis(2500));
let mut next = connect(running.addr);
next.write_all(b"GET /n HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
.unwrap();
let mut reply = Vec::new();
let _ = next.read_to_end(&mut reply);
assert!(
reply.starts_with(b"HTTP/1.1 200 "),
"{}",
String::from_utf8_lossy(&reply)
);
t.join().unwrap();
}
#[test]
fn test_expect_continue_gets_100_before_the_body() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let mut stream = connect(running.addr);
stream
.set_read_timeout(Some(Duration::from_secs(3)))
.unwrap();
stream
.write_all(
b"POST /echo HTTP/1.1\r\nHost: x\r\nExpect: 100-continue\r\n\
Content-Length: 5\r\nConnection: close\r\n\r\n",
)
.unwrap();
let mut interim = [0u8; 25];
stream
.read_exact(&mut interim)
.expect("100 Continue before the body");
assert_eq!(&interim, b"HTTP/1.1 100 Continue\r\n\r\n");
stream.write_all(b"hello").unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
assert!(reply.starts_with(b"HTTP/1.1 200 OK\r\n"));
assert!(reply.ends_with(b"\r\n\r\nhello"));
}
#[test]
fn test_expect_continue_over_the_limit_gets_413_not_100() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let mut stream = connect(running.addr);
stream
.write_all(
b"POST /echo HTTP/1.1\r\nHost: x\r\nExpect: 100-continue\r\n\
Content-Length: 5000000\r\n\r\n",
)
.unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
assert!(
reply.starts_with(b"HTTP/1.1 413 "),
"{}",
String::from_utf8_lossy(&reply)
);
}
#[test]
fn test_handler_misuse_is_answered_with_500() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), |req, _| {
match req.path() {
"/stream" => Response::new(200).chunked(),
"/length" => Response::new(200).content_length(10),
_ => Response::new(200).header("content-length", "1"),
}
});
for path in ["/stream", "/length", "/reserved"] {
let mut stream = connect(running.addr);
stream
.write_all(format!("GET {path} HTTP/1.1\r\nHost: x\r\n\r\n").as_bytes())
.unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
assert!(
reply.starts_with(b"HTTP/1.1 500 "),
"{path}: {}",
String::from_utf8_lossy(&reply)
);
assert!(reply.windows(17).any(|w| w == b"connection: close"));
}
let mut stream = connect(running.addr);
let mut conn = ClientConn::new();
for _ in 0..2 {
let mut out = Vec::new();
conn.send_request(
&Request::new("HEAD", "/length").header("host", "x"),
&mut out,
)
.unwrap();
stream.write_all(&out).unwrap();
let (status, body, close) = read_response(&mut stream, &mut conn);
assert_eq!((status, body.len(), close), (200, 0, false));
assert!(matches!(conn.poll().unwrap(), ClientEvent::Idle));
}
}
#[test]
fn test_upgraded_socket_does_not_inherit_the_request_deadline() {
let server = Server::bind("127.0.0.1:0")
.unwrap()
.with_config(
ServerConfig::new()
.with_read_timeout(Some(Duration::from_secs(10)))
.with_request_timeout(Some(Duration::from_secs(1))),
)
.on_upgrade(|mut up| {
let mut buf = [0u8; 16];
if let Ok(n) = up.stream.read(&mut buf) {
let _ = up.stream.write_all(&buf[..n]);
}
});
let running = start(server, |req, _| {
if req.is_upgrade() {
Response::switching_protocols("echo")
} else {
Response::new(426)
}
});
let mut stream = connect(running.addr);
stream
.write_all(b"GET /ws HTTP/1.1\r\nHost: x\r\n")
.unwrap();
thread::sleep(Duration::from_millis(150));
stream
.write_all(b"Connection: upgrade\r\nUpgrade: echo\r\n\r\n")
.unwrap();
let mut head = Vec::new();
let mut byte = [0u8; 1];
while !head.ends_with(b"\r\n\r\n") {
stream.read_exact(&mut byte).unwrap();
head.push(byte[0]);
}
assert!(head.starts_with(b"HTTP/1.1 101 "));
thread::sleep(Duration::from_millis(1500));
stream.write_all(b"ping").unwrap();
let mut echo = [0u8; 4];
stream.read_exact(&mut echo).unwrap();
assert_eq!(&echo, b"ping");
}
fn is_imf_fixdate(v: &[u8]) -> bool {
const DAYS: [&[u8]; 7] = [b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat", b"Sun"];
const MONTHS: [&[u8]; 12] = [
b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov",
b"Dec",
];
let digits = |r: std::ops::Range<usize>| v[r].iter().all(u8::is_ascii_digit);
v.len() == 29
&& DAYS.contains(&&v[0..3])
&& &v[3..5] == b", "
&& digits(5..7)
&& v[7] == b' '
&& MONTHS.contains(&&v[8..11])
&& v[11] == b' '
&& digits(12..16)
&& v[16] == b' '
&& digits(17..19)
&& v[19] == b':'
&& digits(20..22)
&& v[22] == b':'
&& digits(23..25)
&& &v[25..] == b" GMT"
}
#[test]
fn test_every_response_date_is_an_imf_fixdate() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let resp = blocking::get(&format!("http://{}/", running.addr)).unwrap();
let date = resp.header("date").expect("date");
assert!(is_imf_fixdate(date), "{}", String::from_utf8_lossy(date));
let mut stream = connect(running.addr);
stream.write_all(b"GET / HTTP/1.1\r\n\r\n").unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
let text = String::from_utf8_lossy(&reply);
assert!(text.starts_with("HTTP/1.1 400 "), "{text}");
let date = text
.lines()
.find_map(|l| l.strip_prefix("date: "))
.expect("date on the error answer");
assert!(is_imf_fixdate(date.as_bytes()), "{date}");
assert!(is_imf_fixdate(blocking::now_date().as_bytes()));
}
#[test]
fn test_connection_response_deadline_slow_reader_times_out() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let stop_reading = Arc::new(std::sync::atomic::AtomicBool::new(false));
let reader = {
let stop_reading = Arc::clone(&stop_reading);
thread::spawn(move || {
let mut s = connect(addr);
s.write_all(b"GET /big HTTP/1.1\r\nHost: x\r\n\r\n")
.unwrap();
let mut byte = [0u8; 1];
while !stop_reading.load(Ordering::SeqCst) {
if s.read(&mut byte).unwrap_or(0) == 0 {
break;
}
thread::sleep(Duration::from_millis(100));
}
})
};
let (stream, _) = listener.accept().unwrap();
stream
.set_write_timeout(Some(Duration::from_secs(10)))
.unwrap();
let mut conn = Connection::new(stream, Limits::new())
.with_response_deadline(Some(Duration::from_millis(500)));
assert!(conn.next_request().unwrap().is_some());
let started = Instant::now();
conn.respond(&Response::new(200).chunked()).unwrap();
let chunk = vec![b'x'; 1 << 20];
let mut outcome = Ok(());
for _ in 0..1024 {
outcome = conn.send_body(&chunk);
if outcome.is_err() {
break;
}
}
let waited = started.elapsed();
stop_reading.store(true, Ordering::SeqCst);
drop(conn);
reader.join().unwrap();
assert!(
matches!(&outcome, Err(blocking::Error::Io(e)) if e.kind() == std::io::ErrorKind::TimedOut),
"{outcome:?}"
);
assert!(
waited >= Duration::from_millis(400) && waited < Duration::from_secs(3),
"the response was cut after {waited:?}"
);
}
#[test]
fn test_server_response_deadline_slow_reader_is_cut_off() {
let server = Server::bind("127.0.0.1:0").unwrap().with_config(
ServerConfig::new()
.with_read_timeout(Some(Duration::from_secs(1)))
.with_write_timeout(Some(Duration::from_secs(10)))
.with_response_deadline(Some(Duration::from_millis(500))),
);
let running = start(server, |_, _| Response::new(200).body(vec![b'x'; 64 << 20]));
let mut stream = connect(running.addr);
stream
.write_all(b"GET /big HTTP/1.1\r\nHost: x\r\n\r\n")
.unwrap();
let stop_reading = Arc::new(std::sync::atomic::AtomicBool::new(false));
let reader = {
let stop_reading = Arc::clone(&stop_reading);
thread::spawn(move || {
let mut byte = [0u8; 1];
while !stop_reading.load(Ordering::SeqCst) {
if stream.read(&mut byte).unwrap_or(0) == 0 {
break;
}
thread::sleep(Duration::from_millis(100));
}
})
};
thread::sleep(Duration::from_millis(200));
let started = Instant::now();
drop(running);
let waited = started.elapsed();
stop_reading.store(true, Ordering::SeqCst);
reader.join().unwrap();
assert!(
waited < Duration::from_secs(5),
"the connection was held for {waited:?}"
);
}
#[test]
fn test_max_requests_per_connection_third_response_closes() {
let server = Server::bind("127.0.0.1:0")
.unwrap()
.with_config(ServerConfig::new().with_max_requests_per_connection(Some(3)));
let running = start(server, echo);
let mut stream = connect(running.addr);
let one = b"GET /n HTTP/1.1\r\nHost: x\r\n\r\n";
stream.write_all(&one.repeat(4)).unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
let text = String::from_utf8_lossy(&reply);
assert_eq!(text.matches("HTTP/1.1 200 OK").count(), 3, "{text}");
assert_eq!(text.matches("connection: close").count(), 1, "{text}");
let third = text.rfind("HTTP/1.1 200 OK").unwrap();
assert!(text[third..].contains("connection: close"), "{text}");
}
#[test]
fn test_unknown_expectation_gets_417_without_reading_the_body() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let mut stream = connect(running.addr);
stream
.set_read_timeout(Some(Duration::from_secs(3)))
.unwrap();
stream
.write_all(
b"POST /echo HTTP/1.1\r\nHost: x\r\nExpect: x-unknown\r\nContent-Length: 5\r\n\r\n",
)
.unwrap();
let mut reply = Vec::new();
stream.read_to_end(&mut reply).unwrap();
let text = String::from_utf8_lossy(&reply);
assert!(
text.starts_with("HTTP/1.1 417 Expectation Failed\r\n"),
"{text}"
);
assert!(!text.contains("100 Continue"), "{text}");
assert!(text.contains("connection: close"), "{text}");
}
#[test]
fn test_connection_send_informational_writes_103_first() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let client = thread::spawn(move || {
let mut s = connect(addr);
s.write_all(b"GET / HTTP/1.1\r\nHost: x\r\nConnection: close\r\n\r\n")
.unwrap();
let mut reply = Vec::new();
s.read_to_end(&mut reply).unwrap();
reply
});
let (stream, _) = listener.accept().unwrap();
let mut conn = Connection::new(stream, Limits::new());
assert!(conn.next_request().unwrap().is_some());
conn.send_informational(&Response::new(103).header("link", "</a.css>; rel=preload"))
.unwrap();
conn.respond(&Response::text(200, "page")).unwrap();
conn.close();
let reply = client.join().unwrap();
let text = String::from_utf8_lossy(&reply);
assert!(
text.starts_with(
"HTTP/1.1 103 Early Hints\r\nlink: </a.css>; rel=preload\r\n\r\nHTTP/1.1 200 OK\r\n"
),
"{text}"
);
assert!(text.ends_with("\r\n\r\npage"), "{text}");
}
#[test]
fn test_client_request_post_with_a_body_reaches_the_handler() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), echo);
let request = Request::post("/")
.header("content-type", "application/octet-stream")
.body(&b"payload"[..]);
let resp = blocking::Client::new()
.request(&format!("http://{}/echo", running.addr), request)
.unwrap();
assert_eq!(resp.status(), 200);
assert_eq!(resp.body(), b"payload");
let resp = blocking::Client::new()
.request(
&format!("http://{}/x?y", running.addr),
Request::delete("/"),
)
.unwrap();
assert_eq!(resp.text(), Some("DELETE /x"));
}
#[test]
fn test_post_sends_the_body_and_content_type() {
let running = start(Server::bind("127.0.0.1:0").unwrap(), |req, body| {
let ct = req.header_str("content-type").unwrap_or("");
Response::text(200, &format!("{ct} {}", body.len()))
});
let resp = blocking::post(
&format!("http://{}/notes", running.addr),
"text/plain",
b"hello",
)
.unwrap();
assert_eq!(resp.text(), Some("text/plain 5"));
}
#[test]
fn test_client_deadline_silent_server_times_out() {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let holder = thread::spawn(move || {
let (stream, _) = listener.accept().unwrap();
thread::sleep(Duration::from_secs(3));
drop(stream);
});
let started = Instant::now();
let err = blocking::Client::new()
.with_timeout(Duration::from_secs(10))
.with_deadline(Some(Duration::from_millis(500)))
.get(&format!("http://{addr}/"))
.unwrap_err();
let waited = started.elapsed();
assert!(
matches!(&err, blocking::Error::Io(e) if e.kind() == std::io::ErrorKind::TimedOut),
"{err}"
);
assert!(waited < Duration::from_secs(2), "waited {waited:?}");
holder.join().unwrap();
}