Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,239 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn headers_return_before_body_and_download_ignores_buffered_total_limit() {
|
||||
let (opened, permit) = std::sync::mpsc::channel();
|
||||
let (port, server) = peer(move |listener| {
|
||||
let mut socket = accept(&listener);
|
||||
headers(&mut socket);
|
||||
socket
|
||||
.get_mut()
|
||||
.write_all(
|
||||
b"HTTP/1.1 200 OK\r\nContent-Length: 100003\r\nX-Test: one\r\nX-Test: two\r\n\r\n",
|
||||
)
|
||||
.unwrap();
|
||||
permit.recv_timeout(Duration::from_secs(2)).unwrap();
|
||||
socket.get_mut().write_all(&vec![b'x'; 100003]).unwrap();
|
||||
assert_eq!(socket.read(&mut [0]).unwrap(), 0);
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(
|
||||
scope,
|
||||
port,
|
||||
&Limits {
|
||||
body_bytes: 1,
|
||||
..Default::default()
|
||||
},
|
||||
&PoolLimits {
|
||||
chunk_bytes: 7,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let response = client.open(message(), event).await.unwrap();
|
||||
assert_eq!(
|
||||
response
|
||||
.headers
|
||||
.iter()
|
||||
.filter(|header| header.name == "x-test")
|
||||
.count(),
|
||||
2
|
||||
);
|
||||
opened.send(()).unwrap();
|
||||
let body = collect(&response.body, 7).await;
|
||||
assert_eq!(body, vec![b'x'; 100003]);
|
||||
assert!(response.body.next().await.unwrap().is_none());
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unread_body_holds_slot_and_idle_clock_starts_after_eof() {
|
||||
let (port, server) = peer(|listener| {
|
||||
let mut socket = accept(&listener);
|
||||
for _ in 0..2 {
|
||||
headers(&mut socket);
|
||||
socket
|
||||
.get_mut()
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nabcdef")
|
||||
.unwrap();
|
||||
}
|
||||
let start = std::time::Instant::now();
|
||||
assert_eq!(socket.read(&mut [0]).unwrap(), 0);
|
||||
assert!(start.elapsed() < Duration::from_secs(1));
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(
|
||||
scope,
|
||||
port,
|
||||
&Limits {
|
||||
connections: 1,
|
||||
timeout_ms: 80,
|
||||
..Default::default()
|
||||
},
|
||||
&PoolLimits {
|
||||
idle_ms: 20,
|
||||
chunk_bytes: 2,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let response = client.open(message(), event).await.unwrap();
|
||||
assert!(
|
||||
client
|
||||
.open(message(), event)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.contains("timed out")
|
||||
);
|
||||
assert_eq!(collect(&response.body, 2).await, b"abcdef");
|
||||
assert_eq!(
|
||||
client.request(message()).await.unwrap().body.as_ref(),
|
||||
b"abcdef"
|
||||
);
|
||||
tokio::time::sleep(Duration::from_millis(80)).await;
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn early_close_of_buffered_frame_closes_http1_and_next_request_reconnects() {
|
||||
let (port, server) = peer(|listener| {
|
||||
let mut first = accept(&listener);
|
||||
headers(&mut first);
|
||||
first
|
||||
.get_mut()
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 6\r\n\r\nabcdef")
|
||||
.unwrap();
|
||||
assert_eq!(first.read(&mut [0]).unwrap(), 0);
|
||||
let mut second = accept(&listener);
|
||||
headers(&mut second);
|
||||
second
|
||||
.get_mut()
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok")
|
||||
.unwrap();
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(
|
||||
scope,
|
||||
port,
|
||||
&Limits {
|
||||
connections: 1,
|
||||
..Default::default()
|
||||
},
|
||||
&PoolLimits::default(),
|
||||
);
|
||||
let response = client.open(message(), event).await.unwrap();
|
||||
let alias = response.body.clone();
|
||||
tokio::time::sleep(Duration::from_millis(30)).await;
|
||||
response.body.close();
|
||||
assert!(alias.next().await.unwrap().is_none());
|
||||
assert_eq!(
|
||||
client.request(message()).await.unwrap().body.as_ref(),
|
||||
b"ok"
|
||||
);
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_timeout_and_protocol_failure_emit_one_error_then_eof() {
|
||||
for body in [
|
||||
b"".as_slice(),
|
||||
b"3\r\nabc\r\n0\r\nX-End: bad\r\n\r\n",
|
||||
b"invalid\r\n",
|
||||
] {
|
||||
let (port, server) = peer(move |listener| {
|
||||
let mut socket = accept(&listener);
|
||||
headers(&mut socket);
|
||||
socket
|
||||
.get_mut()
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n")
|
||||
.unwrap();
|
||||
socket.get_mut().write_all(body).unwrap();
|
||||
let _ = socket.read(&mut [0]);
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(
|
||||
scope,
|
||||
port,
|
||||
&Limits::default(),
|
||||
&PoolLimits {
|
||||
read_ms: 40,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let response = client.open(message(), event).await.unwrap();
|
||||
let mut failures = 0;
|
||||
while let Some(item) = response.body.next().await.unwrap() {
|
||||
failures += usize::from(item.is_err());
|
||||
}
|
||||
assert_eq!(failures, 1);
|
||||
assert!(response.body.next().await.unwrap().is_none());
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_consumer_closes_body_and_pool_close_wakes_body_and_slot_waiters() {
|
||||
for cancel in [true, false] {
|
||||
let (port, server) = peer(|listener| {
|
||||
let mut socket = accept(&listener);
|
||||
headers(&mut socket);
|
||||
socket
|
||||
.get_mut()
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 20\r\n\r\n")
|
||||
.unwrap();
|
||||
assert_eq!(socket.read(&mut [0]).unwrap(), 0);
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(
|
||||
scope,
|
||||
port,
|
||||
&Limits {
|
||||
connections: 1,
|
||||
..Default::default()
|
||||
},
|
||||
&PoolLimits::default(),
|
||||
);
|
||||
let response = client.open(message(), event).await.unwrap();
|
||||
let alias = response.body.clone();
|
||||
if cancel {
|
||||
let consume = async {
|
||||
let _owner = alias.close_on_drop();
|
||||
alias.next().await
|
||||
};
|
||||
assert!(
|
||||
tokio::time::timeout(Duration::from_millis(20), consume)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(response.body.next().await.unwrap().is_none());
|
||||
} else {
|
||||
let (item, waiting, ()) =
|
||||
tokio::join!(response.body.next(), client.open(message(), event), async {
|
||||
tokio::time::sleep(Duration::from_millis(20)).await;
|
||||
client.close().await.unwrap();
|
||||
});
|
||||
assert!(item.unwrap().unwrap().unwrap_err().contains("closed"));
|
||||
assert!(waiting.unwrap_err().contains("closed"));
|
||||
assert!(response.body.next().await.unwrap().is_none());
|
||||
}
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//! 独立回环协议对端验证客户端按需拉取与连接所有权。
|
||||
|
||||
use super::*;
|
||||
use bytes::Bytes;
|
||||
use dever_runtime::{
|
||||
async_stream::AsyncStream,
|
||||
http::{HttpClient, PoolLimits, StreamRequest},
|
||||
io::Error,
|
||||
tls,
|
||||
};
|
||||
use std::net::TcpListener;
|
||||
|
||||
mod download;
|
||||
mod protocol;
|
||||
mod upload;
|
||||
|
||||
type Event = Result<Bytes, Error>;
|
||||
|
||||
fn event(value: Event) -> Event {
|
||||
value
|
||||
}
|
||||
|
||||
fn message() -> Request {
|
||||
Request {
|
||||
method: "POST".into(),
|
||||
target: "/stream".into(),
|
||||
headers: vec![],
|
||||
body: bytes::Bytes::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn client(scope: &Scope<'_>, port: u16, limits: &Limits, pool: &PoolLimits) -> HttpClient {
|
||||
http::client(
|
||||
&format!("http://127.0.0.1:{port}"),
|
||||
&tls::system(),
|
||||
limits,
|
||||
pool,
|
||||
scope,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn peer(work: impl FnOnce(TcpListener) + Send + 'static) -> (u16, std::thread::JoinHandle<()>) {
|
||||
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
(port, std::thread::spawn(move || work(listener)))
|
||||
}
|
||||
|
||||
fn accept(listener: &TcpListener) -> BufReader<TcpStream> {
|
||||
listener.set_nonblocking(true).unwrap();
|
||||
let start = std::time::Instant::now();
|
||||
let socket = loop {
|
||||
match listener.accept() {
|
||||
Ok((socket, _)) => break socket,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::WouldBlock => {
|
||||
assert!(
|
||||
start.elapsed() < Duration::from_secs(5),
|
||||
"peer accept timed out"
|
||||
);
|
||||
std::thread::sleep(Duration::from_millis(2));
|
||||
}
|
||||
Err(error) => panic!("{error}"),
|
||||
}
|
||||
};
|
||||
socket
|
||||
.set_read_timeout(Some(Duration::from_secs(5)))
|
||||
.unwrap();
|
||||
socket
|
||||
.set_write_timeout(Some(Duration::from_secs(5)))
|
||||
.unwrap();
|
||||
BufReader::new(socket)
|
||||
}
|
||||
|
||||
fn headers(socket: &mut BufReader<TcpStream>) -> String {
|
||||
let mut headers = String::new();
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
assert!(socket.read_line(&mut line).unwrap() > 0);
|
||||
if line == "\r\n" {
|
||||
return headers;
|
||||
}
|
||||
headers.push_str(&line);
|
||||
}
|
||||
}
|
||||
|
||||
async fn collect(body: &AsyncStream<Event>, chunk: usize) -> Vec<u8> {
|
||||
let _guard = body.close_on_drop();
|
||||
let mut bytes = vec![];
|
||||
while let Some(event) = body.next().await.unwrap() {
|
||||
let part = event.unwrap();
|
||||
assert!(!part.is_empty() && part.len() <= chunk);
|
||||
bytes.extend_from_slice(&part);
|
||||
}
|
||||
bytes
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn http1_http2_tls_streams_roundtrip_and_buffered_limits_remain_enforced() {
|
||||
for http2 in [false, true] {
|
||||
for secure in [false, true] {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let port = listener.port().unwrap();
|
||||
let limits = Limits {
|
||||
connections: 1,
|
||||
http2: http2.then(Default::default),
|
||||
..Default::default()
|
||||
};
|
||||
let incoming = listener.clone();
|
||||
let protocol = limits.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
if secure {
|
||||
let tls = tls::server(
|
||||
super::super::tls::CERTIFICATE,
|
||||
super::super::tls::KEY,
|
||||
)
|
||||
.unwrap();
|
||||
http::serve_tls::<_, _, false>(
|
||||
&incoming, &protocol, &child, &tls, route,
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
http::serve::<_, _, false>(&incoming, &protocol, &child, route)
|
||||
.await
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
let origin = format!(
|
||||
"{}://localhost:{port}",
|
||||
if secure { "https" } else { "http" }
|
||||
);
|
||||
let trust = tls::client(super::super::tls::ROOT).unwrap();
|
||||
let client = http::client(
|
||||
&origin,
|
||||
&trust,
|
||||
&Limits {
|
||||
body_bytes: 1,
|
||||
..limits
|
||||
},
|
||||
&PoolLimits {
|
||||
chunk_bytes: 3,
|
||||
..Default::default()
|
||||
},
|
||||
scope,
|
||||
)
|
||||
.unwrap();
|
||||
let body = AsyncStream::values([
|
||||
Ok(Bytes::from_static(b"abc")),
|
||||
Ok(Bytes::from_static(b"def")),
|
||||
]);
|
||||
let request = StreamRequest {
|
||||
method: "POST".into(),
|
||||
target: "/echo".into(),
|
||||
headers: vec![],
|
||||
body,
|
||||
};
|
||||
let response = client.open_stream(request, event, event).await.unwrap();
|
||||
assert_eq!(collect(&response.body, 3).await, b"abcdef");
|
||||
let mut request = message();
|
||||
request.body = b"ab".to_vec().into();
|
||||
assert!(
|
||||
client
|
||||
.open(request.clone(), event)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.contains("byte limit")
|
||||
);
|
||||
assert!(
|
||||
client
|
||||
.request(request)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.contains("byte limit")
|
||||
);
|
||||
client.close().await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn http2_closing_one_response_preserves_neighbor_and_releases_only_its_slot() {
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let listener = net::listen("127.0.0.1", 0, scope).await.unwrap();
|
||||
let port = listener.port().unwrap();
|
||||
let limits = Limits {
|
||||
connections: 1,
|
||||
http2: Some(http::Http2Limits {
|
||||
streams: 2,
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
let incoming = listener.clone();
|
||||
let protocol = limits.clone();
|
||||
let server = scope
|
||||
.spawn(move |child| async move {
|
||||
http::serve_live(
|
||||
&incoming,
|
||||
&protocol,
|
||||
&http::LiveLimits::default(),
|
||||
&child,
|
||||
|request: Request, reply: http::HttpReply, _| async move {
|
||||
if request.target == "/ordinary" {
|
||||
return reply
|
||||
.respond(Response {
|
||||
status: 200,
|
||||
headers: vec![],
|
||||
body: b"ok".to_vec().into(),
|
||||
})
|
||||
.await
|
||||
.map_err(|error| error.to_string());
|
||||
}
|
||||
reply
|
||||
.start(200, vec![])
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
reply
|
||||
.write(b"first")
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
tokio::time::sleep(Duration::from_millis(150)).await;
|
||||
reply
|
||||
.write(b"last")
|
||||
.await
|
||||
.map_err(|error| error.to_string())
|
||||
},
|
||||
)
|
||||
.await
|
||||
})
|
||||
.unwrap();
|
||||
let client = client(
|
||||
scope,
|
||||
port as u16,
|
||||
&limits,
|
||||
&PoolLimits {
|
||||
idle_ms: 20,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let first = client.open(message(), event).await.unwrap();
|
||||
let second = client.open(message(), event).await.unwrap();
|
||||
assert_eq!(
|
||||
first.body.next().await.unwrap().unwrap().unwrap(),
|
||||
b"first"[..]
|
||||
);
|
||||
assert_eq!(
|
||||
second.body.next().await.unwrap().unwrap().unwrap(),
|
||||
b"first"[..]
|
||||
);
|
||||
first.body.close();
|
||||
let mut ordinary = message();
|
||||
ordinary.target = "/ordinary".into();
|
||||
assert_eq!(client.request(ordinary).await.unwrap().body.as_ref(), b"ok");
|
||||
assert_eq!(collect(&second.body, 65536).await, b"last");
|
||||
client.close().await.unwrap();
|
||||
listener.close().unwrap();
|
||||
server.wait().await.unwrap().unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
use super::*;
|
||||
|
||||
fn chunk(socket: &mut BufReader<TcpStream>) -> Vec<u8> {
|
||||
let mut line = String::new();
|
||||
assert!(socket.read_line(&mut line).unwrap() > 0);
|
||||
let count = usize::from_str_radix(line.trim(), 16).unwrap();
|
||||
let mut body = vec![0; count];
|
||||
socket.read_exact(&mut body).unwrap();
|
||||
let mut ending = [0; 2];
|
||||
socket.read_exact(&mut ending).unwrap();
|
||||
assert_eq!(&ending, b"\r\n");
|
||||
body
|
||||
}
|
||||
|
||||
fn request(body: AsyncStream<Event>) -> StreamRequest<Event> {
|
||||
StreamRequest {
|
||||
method: "POST".into(),
|
||||
target: "/stream".into(),
|
||||
headers: vec![],
|
||||
body,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplex_upload_sends_first_chunk_before_source_eof_and_returns_response_early() {
|
||||
let (port, server) = peer(|listener| {
|
||||
let mut socket = accept(&listener);
|
||||
let head = headers(&mut socket).to_ascii_lowercase();
|
||||
assert!(head.contains("transfer-encoding: chunked"));
|
||||
assert!(!head.contains("content-length:"));
|
||||
assert_eq!(chunk(&mut socket), b"abc");
|
||||
socket
|
||||
.get_mut()
|
||||
.write_all(b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n3\r\nabc\r\n")
|
||||
.unwrap();
|
||||
assert_eq!(chunk(&mut socket), b"def");
|
||||
assert!(chunk(&mut socket).is_empty());
|
||||
socket
|
||||
.get_mut()
|
||||
.write_all(b"3\r\ndef\r\n0\r\n\r\n")
|
||||
.unwrap();
|
||||
assert_eq!(socket.read(&mut [0]).unwrap(), 0);
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(
|
||||
scope,
|
||||
port,
|
||||
&Limits {
|
||||
body_bytes: 1,
|
||||
..Default::default()
|
||||
},
|
||||
&PoolLimits {
|
||||
chunk_bytes: 3,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let proceed = Arc::new(tokio::sync::Notify::new());
|
||||
let source = proceed.clone();
|
||||
let body = AsyncStream::new(futures_util::stream::unfold(0, move |index| {
|
||||
let source = source.clone();
|
||||
async move {
|
||||
match index {
|
||||
0 => Some((Ok(Ok(Bytes::from_static(b"abc"))), 1)),
|
||||
1 => {
|
||||
source.notified().await;
|
||||
Some((Ok(Ok(Bytes::from_static(b"def"))), 2))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}));
|
||||
let alias = body.clone();
|
||||
let response = client
|
||||
.open_stream(request(body), event, event)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.body.next().await.unwrap().unwrap().unwrap(),
|
||||
b"abc"[..]
|
||||
);
|
||||
proceed.notify_one();
|
||||
assert_eq!(collect(&response.body, 3).await, b"def");
|
||||
assert!(alias.next().await.unwrap().is_none());
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn upload_rejects_oversized_chunks_source_errors_and_stalled_reads() {
|
||||
for mode in 0..3 {
|
||||
let (port, server) = peer(|listener| {
|
||||
let mut socket = accept(&listener);
|
||||
let mut request = Vec::new();
|
||||
socket.read_to_end(&mut request).unwrap();
|
||||
assert!(!request.windows(8).any(|window| window == b"oversize"));
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(
|
||||
scope,
|
||||
port,
|
||||
&Limits {
|
||||
timeout_ms: 1000,
|
||||
..Default::default()
|
||||
},
|
||||
&PoolLimits {
|
||||
chunk_bytes: 3,
|
||||
read_ms: 40,
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
let body = match mode {
|
||||
0 => AsyncStream::values([Ok(Bytes::from_static(b"oversize"))]),
|
||||
1 => AsyncStream::values([Err("source failed".into())]),
|
||||
_ => AsyncStream::new(futures_util::stream::pending()),
|
||||
};
|
||||
let alias = body.clone();
|
||||
assert!(
|
||||
client
|
||||
.open_stream(request(body), event, event)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
assert!(alias.next().await.unwrap().is_none());
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancelling_request_before_headers_closes_upload_source_and_socket() {
|
||||
let (port, server) = peer(|listener| {
|
||||
let mut socket = accept(&listener);
|
||||
let mut bytes = vec![];
|
||||
socket.read_to_end(&mut bytes).unwrap();
|
||||
});
|
||||
runtime()
|
||||
.run(async |scope| {
|
||||
let client = client(scope, port, &Limits::default(), &PoolLimits::default());
|
||||
let body = AsyncStream::<Event>::new(futures_util::stream::pending());
|
||||
assert!(
|
||||
tokio::time::timeout(
|
||||
Duration::from_millis(40),
|
||||
client.open_stream(request(body.clone()), event, event)
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
tokio::time::timeout(Duration::from_secs(1), body.next())
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert!(body.next().await.unwrap().is_none());
|
||||
client.close().await.unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
server.join().unwrap();
|
||||
}
|
||||
Reference in new issue
Block a user