mirror of
https://github.com/actix/actix-web.git
synced 2024-11-25 19:11:10 +00:00
153 lines
4.9 KiB
Rust
153 lines
4.9 KiB
Rust
use std::{io, time::Duration};
|
|
|
|
use actix_http::{error::Error, HttpService, Response};
|
|
use actix_server::Server;
|
|
use tokio::io::AsyncWriteExt;
|
|
|
|
#[actix_rt::test]
|
|
async fn h2_ping_pong() -> io::Result<()> {
|
|
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
|
|
|
let lst = std::net::TcpListener::bind("127.0.0.1:0")?;
|
|
|
|
let addr = lst.local_addr().unwrap();
|
|
|
|
let join = std::thread::spawn(move || {
|
|
actix_rt::System::new().block_on(async move {
|
|
let srv = Server::build()
|
|
.disable_signals()
|
|
.workers(1)
|
|
.listen("h2_ping_pong", lst, || {
|
|
HttpService::build()
|
|
.keep_alive(Duration::from_secs(3))
|
|
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
|
.tcp()
|
|
})?
|
|
.run();
|
|
|
|
tx.send(srv.handle()).unwrap();
|
|
|
|
srv.await
|
|
})
|
|
});
|
|
|
|
let handle = rx.recv().unwrap();
|
|
|
|
let (sync_tx, rx) = std::sync::mpsc::sync_channel(1);
|
|
|
|
// use a separate thread for h2 client so it can be blocked.
|
|
std::thread::spawn(move || {
|
|
tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.unwrap()
|
|
.block_on(async move {
|
|
let stream = tokio::net::TcpStream::connect(addr).await.unwrap();
|
|
|
|
let (mut tx, conn) = h2::client::handshake(stream).await.unwrap();
|
|
|
|
tokio::spawn(async move { conn.await.unwrap() });
|
|
|
|
let (res, _) = tx.send_request(::http::Request::new(()), true).unwrap();
|
|
let res = res.await.unwrap();
|
|
|
|
assert_eq!(res.status().as_u16(), 200);
|
|
|
|
sync_tx.send(()).unwrap();
|
|
|
|
// intentionally block the client thread so it can not answer ping pong.
|
|
std::thread::sleep(std::time::Duration::from_secs(1000));
|
|
})
|
|
});
|
|
|
|
rx.recv().unwrap();
|
|
|
|
let now = std::time::Instant::now();
|
|
|
|
// stop server gracefully. this step would take up to 30 seconds.
|
|
handle.stop(true).await;
|
|
|
|
// join server thread. only when connection are all gone this step would finish.
|
|
join.join().unwrap()?;
|
|
|
|
// check the time used for join server thread so it's known that the server shutdown
|
|
// is from keep alive and not server graceful shutdown timeout.
|
|
assert!(now.elapsed() < std::time::Duration::from_secs(30));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[actix_rt::test]
|
|
async fn h2_handshake_timeout() -> io::Result<()> {
|
|
let (tx, rx) = std::sync::mpsc::sync_channel(1);
|
|
|
|
let lst = std::net::TcpListener::bind("127.0.0.1:0")?;
|
|
|
|
let addr = lst.local_addr().unwrap();
|
|
|
|
let join = std::thread::spawn(move || {
|
|
actix_rt::System::new().block_on(async move {
|
|
let srv = Server::build()
|
|
.disable_signals()
|
|
.workers(1)
|
|
.listen("h2_ping_pong", lst, || {
|
|
HttpService::build()
|
|
.keep_alive(Duration::from_secs(30))
|
|
// set first request timeout to 5 seconds.
|
|
// this is the timeout used for http2 handshake.
|
|
.client_request_timeout(Duration::from_secs(5))
|
|
.h2(|_| async { Ok::<_, Error>(Response::ok()) })
|
|
.tcp()
|
|
})?
|
|
.run();
|
|
|
|
tx.send(srv.handle()).unwrap();
|
|
|
|
srv.await
|
|
})
|
|
});
|
|
|
|
let handle = rx.recv().unwrap();
|
|
|
|
let (sync_tx, rx) = std::sync::mpsc::sync_channel(1);
|
|
|
|
// use a separate thread for tcp client so it can be blocked.
|
|
std::thread::spawn(move || {
|
|
tokio::runtime::Builder::new_current_thread()
|
|
.enable_all()
|
|
.build()
|
|
.unwrap()
|
|
.block_on(async move {
|
|
let mut stream = tokio::net::TcpStream::connect(addr).await.unwrap();
|
|
|
|
// do not send the last new line intentionally.
|
|
// This should hang the server handshake
|
|
let malicious_buf = b"PRI * HTTP/2.0\r\n\r\nSM\r\n";
|
|
stream.write_all(malicious_buf).await.unwrap();
|
|
stream.flush().await.unwrap();
|
|
|
|
sync_tx.send(()).unwrap();
|
|
|
|
// intentionally block the client thread so it sit idle and not do handshake.
|
|
std::thread::sleep(std::time::Duration::from_secs(1000));
|
|
|
|
drop(stream)
|
|
})
|
|
});
|
|
|
|
rx.recv().unwrap();
|
|
|
|
let now = std::time::Instant::now();
|
|
|
|
// stop server gracefully. this step would take up to 30 seconds.
|
|
handle.stop(true).await;
|
|
|
|
// join server thread. only when connection are all gone this step would finish.
|
|
join.join().unwrap()?;
|
|
|
|
// check the time used for join server thread so it's known that the server shutdown
|
|
// is from handshake timeout and not server graceful shutdown timeout.
|
|
assert!(now.elapsed() < std::time::Duration::from_secs(30));
|
|
|
|
Ok(())
|
|
}
|