use std::cell::RefCell;
use std::future::Future;
use std::io::{Read, Write};
use std::net::TcpStream;
use std::pin::pin;
use std::rc::Rc;
use std::task::{Context, Poll, Waker};
use dope_futures::{AsyncRead, AsyncWrite};
use dope_tls::future::TlsConnector;
struct BlockingIo {
sock: Rc<RefCell<TcpStream>>,
}
impl AsyncRead for BlockingIo {
async fn read(&mut self, mut buf: Vec<u8>) -> (std::io::Result<usize>, Vec<u8>) {
let cap = buf.capacity().max(buf.len()).max(1);
buf.resize(cap, 0);
let res = self.sock.borrow_mut().read(&mut buf);
match res {
Ok(n) => {
buf.truncate(n);
(Ok(n), buf)
}
Err(e) => (Err(e), buf),
}
}
}
impl AsyncWrite for BlockingIo {
async fn write(&mut self, buf: Vec<u8>) -> (std::io::Result<usize>, Vec<u8>) {
let res = self.sock.borrow_mut().write_all(&buf).map(|_| buf.len());
(res, buf)
}
}
fn block_on<F: Future>(f: F) -> F::Output {
let mut f = pin!(f);
let waker = Waker::noop();
let mut cx = Context::from_waker(waker);
loop {
if let Poll::Ready(v) = f.as_mut().poll(&mut cx) {
return v;
}
}
}
#[test]
#[ignore]
fn live_https_get_example_com() {
const HOST: &str = "example.com";
const PORT: u16 = 443;
let sock = TcpStream::connect((HOST, PORT)).expect("tcp connect");
sock.set_nodelay(true).ok();
let io = BlockingIo {
sock: Rc::new(RefCell::new(sock)),
};
let connector = TlsConnector::from_webpki_roots()
.with_alpn(vec![b"http/1.1".to_vec()]);
let mut tls = block_on(async { connector.connect_hostname(HOST, io).await })
.expect("tls handshake");
if let Some(picked) = tls.selected_alpn() {
assert_eq!(picked, b"http/1.1", "server picked unsupported ALPN");
}
let req = format!(
"GET / HTTP/1.1\r\nHost: {HOST}\r\nUser-Agent: shin-test\r\nConnection: close\r\nAccept: */*\r\n\r\n"
);
let (res, _) = block_on(tls.write(req.into_bytes()));
res.expect("tls write");
let mut response = Vec::new();
loop {
let (res, buf) = block_on(tls.read(vec![0u8; 8192]));
match res {
Ok(0) => break,
Ok(n) => response.extend_from_slice(&buf[..n]),
Err(e) => panic!("tls read: {e:?}"),
}
if response.len() > 256 * 1024 {
break;
}
}
let head_end = response.windows(4).position(|w| w == b"\r\n\r\n").expect(
"found end of headers in response",
);
let head = std::str::from_utf8(&response[..head_end]).expect("ascii head");
let status_line = head.lines().next().expect("status line");
println!("status: {status_line}");
assert!(
status_line.starts_with("HTTP/1.1 "),
"unexpected status: {status_line}",
);
let code: u16 = status_line
.split_whitespace()
.nth(1)
.and_then(|s| s.parse().ok())
.expect("parse status code");
assert!(
(200..400).contains(&code),
"expected 2xx/3xx, got {code}: {status_line}",
);
block_on(async { tls.shutdown().await }).expect("tls shutdown");
}