use fandhe_backend_core::{Handler, Server};
use fandhe_backend_http::request::RequestHead;
use fandhe_backend_http::response::Response;
use fandhe_backend_routes::Router;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use tokio::time::timeout;
async fn read_response(stream: &mut TcpStream) -> String {
let mut out = Vec::new();
let mut buf = [0u8; 4096];
loop {
let n = stream.read(&mut buf).await.expect("read response");
if n == 0 {
break;
}
out.extend_from_slice(&buf[..n]);
}
String::from_utf8(out).expect("response must be valid utf-8")
}
async fn spawn_server(server: Server) -> std::net::SocketAddr {
let bound = server.bind("127.0.0.1:0").await.unwrap();
let addr = bound.local_addr().unwrap();
tokio::spawn(async move {
let _ = bound.run().await;
});
addr
}
#[tokio::test]
async fn async_handler_awaits_sleep_before_responding() {
let router = Router::new().route_async("GET", "/slow", |_head, _body| async {
tokio::time::sleep(Duration::from_millis(50)).await;
Response::new(200, b"slow-ok".to_vec())
});
let addr = spawn_server(Server::new().handler(router)).await;
let mut stream = TcpStream::connect(addr).await.unwrap();
stream
.write_all(b"GET /slow HTTP/1.1\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let text = timeout(Duration::from_secs(2), read_response(&mut stream))
.await
.expect("応答がタイムアウトしないこと");
assert!(text.starts_with("HTTP/1.1 200"));
assert!(text.ends_with("slow-ok"));
}
#[tokio::test]
async fn slow_async_handler_does_not_block_other_connections() {
let router = Router::new()
.route_async("GET", "/slow", |_head, _body| async {
tokio::time::sleep(Duration::from_secs(2)).await;
Response::new(200, b"slow-ok".to_vec())
})
.route_async("GET", "/fast", |_head, _body| async {
Response::new(200, b"fast-ok".to_vec())
});
let addr = spawn_server(Server::new().handler(router)).await;
let mut slow_stream = TcpStream::connect(addr).await.unwrap();
slow_stream
.write_all(b"GET /slow HTTP/1.1\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let mut fast_stream = TcpStream::connect(addr).await.unwrap();
fast_stream
.write_all(b"GET /fast HTTP/1.1\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let fast_text = timeout(Duration::from_secs(1), read_response(&mut fast_stream))
.await
.expect(
"/fast は /slow の sleep をブロックせず短時間で応答するはず\
(async ハンドラが並行実行されていない場合はここでタイムアウトする)",
);
assert!(fast_text.starts_with("HTTP/1.1 200"));
assert!(fast_text.ends_with("fast-ok"));
let slow_text = timeout(Duration::from_secs(5), read_response(&mut slow_stream))
.await
.expect("/slow も最終的には応答するはず");
assert!(slow_text.starts_with("HTTP/1.1 200"));
}
struct PanicHandler {
calls: Arc<AtomicUsize>,
}
impl Handler for PanicHandler {
fn handle(&self, _head: &RequestHead, _body: &[u8]) -> fandhe_backend_routes::HandlerFuture {
self.calls.fetch_add(1, Ordering::SeqCst);
Box::pin(async {
panic!("intentional panic from async handler body");
#[allow(unreachable_code)]
Response::empty(200)
})
}
}
#[tokio::test]
async fn panicking_handler_does_not_crash_server_for_subsequent_connections() {
let calls = Arc::new(AtomicUsize::new(0));
let handler = PanicHandler {
calls: calls.clone(),
};
let addr = spawn_server(Server::new().handler(handler)).await;
let mut stream1 = TcpStream::connect(addr).await.unwrap();
stream1
.write_all(b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let mut buf = [0u8; 64];
let _ = timeout(Duration::from_secs(2), stream1.read(&mut buf)).await;
let mut stream2 = TcpStream::connect(addr).await.unwrap();
stream2
.write_all(b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let _ = timeout(Duration::from_secs(2), stream2.read(&mut buf)).await;
assert_eq!(
calls.load(Ordering::SeqCst),
2,
"panic 後もサーバは後続接続の Handler::handle を呼び出し続けるはず\
(1 接続の panic が他接続の処理能力を損なわない)"
);
}
#[tokio::test]
async fn sync_router_handler_still_works_after_async_migration() {
let router = Router::new().route("GET", "/sync", |_head, _body| {
Response::new(200, b"sync-ok".to_vec())
});
let addr = spawn_server(Server::new().handler(router)).await;
let mut stream = TcpStream::connect(addr).await.unwrap();
stream
.write_all(b"GET /sync HTTP/1.1\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let text = timeout(Duration::from_secs(2), read_response(&mut stream))
.await
.expect("同期ハンドラの応答がタイムアウトしないこと");
assert!(text.starts_with("HTTP/1.1 200"));
assert!(text.ends_with("sync-ok"));
}
#[tokio::test]
async fn sync_direct_handler_impl_still_works_after_async_migration() {
struct FixedHandler;
impl Handler for FixedHandler {
fn handle(
&self,
_head: &RequestHead,
_body: &[u8],
) -> fandhe_backend_routes::HandlerFuture {
Box::pin(std::future::ready(Response::new(200, b"fixed-ok".to_vec())))
}
}
let addr = spawn_server(Server::new().handler(FixedHandler)).await;
let mut stream = TcpStream::connect(addr).await.unwrap();
stream
.write_all(b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
.await
.unwrap();
let text = timeout(Duration::from_secs(2), read_response(&mut stream))
.await
.expect("直接実装 Handler の応答がタイムアウトしないこと");
assert!(text.starts_with("HTTP/1.1 200"));
assert!(text.ends_with("fixed-ok"));
}