mirror of https://github.com/fafhrd91/actix-web
				
				
				
			
		
			
				
	
	
		
			154 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
			
		
		
	
	
			154 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(())
 | |
| }
 |