use std::borrow::Cow;
use std::cell::{Cell, RefCell};
use std::convert::TryFrom;
use std::env;
use std::error::Error;
use std::fmt::Write as _;
use std::net::TcpStream;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use std::task::{Context as AsyncContext, Poll};
use std::time::Duration;
use async_io::Async;
use futures_lite::{future, io, prelude::*};
use http_types::{Method, Request, Url};
use async_rustls::{client::TlsStream, TlsConnector};
use rustls::OwnedTrustAnchor;
use rustybuzz::Face as LayoutFace;
use softbuffer::{Context, Surface};
use ttf_parser::Face;
use winit::dpi::LogicalSize;
use winit::event::{Event, KeyboardInput, VirtualKeyCode, WindowEvent};
use winit::event_loop::{EventLoop, EventLoopBuilder};
use winit::window::{Window, WindowBuilder};
use winit_block_on::{prelude::*, Signal};
type BoxError = Box<dyn Error + 'static>;
type Result<T> = std::result::Result<T, BoxError>;
const WIDTH: u32 = 800;
const HEIGHT: u32 = 600;
const DRAW_BUFFER_SIZE: usize = WIDTH as usize * HEIGHT as usize;
const DEFAULT_URLS: &[&str] = &[
"https://www.rust-lang.org/",
"https://www.google.com/",
"http://neverssl.com",
"https://serde.rs",
];
struct Rerender;
struct AppState {
http_states: Vec<HttpState>,
}
struct HttpState {
url: String,
status: Cow<'static, str>,
progress: u8,
was_changed: bool,
error: bool,
}
impl HttpState {
fn new(url: String) -> Self {
Self {
url,
status: "Waiting to start...".into(),
progress: 0,
was_changed: false,
error: false,
}
}
fn add_progress(&mut self, new_status: &'static str, new_progress: u8) {
self.status = new_status.into();
self.progress += new_progress;
if self.progress > 100 {
self.progress = 100;
}
self.was_changed = true;
}
}
async fn get_http(url: &str, state: &RefCell<AppState>, index: usize) -> Result<Vec<u8>> {
let update = move |new_status: &'static str, new_progress: u8| {
state.borrow_mut().http_states[index].add_progress(new_status, new_progress);
};
let url = Url::parse(url)?;
let use_tls = url.scheme() == "https";
let socket_list = {
let domain = url
.domain()
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "No domain in URL"))?;
let port = match (url.port(), use_tls) {
(Some(port), _) => port,
(None, true) => 443,
(None, false) => 80,
};
let url_to_lookup = format!("{}:{}", domain, port);
blocking::unblock(move || {
std::net::ToSocketAddrs::to_socket_addrs(&url_to_lookup)
.map(|sock| sock.collect::<Vec<_>>())
})
.await?
};
update("Connecting to server...", 10);
let stream = {
let mut error = None;
let mut socks = socket_list.into_iter();
let stream = loop {
let sock = socks.next().ok_or_else(|| {
error.unwrap_or_else(|| io::Error::new(io::ErrorKind::Other, "No more sockets"))
})?;
match Async::<TcpStream>::connect(sock).await {
Ok(stream) => break stream,
Err(e) => {
error = Some(e);
continue;
}
}
};
stream
};
update("Performing TLS handshake...", 10);
let stream = {
if use_tls {
let mut root_cert_store = rustls::RootCertStore::empty();
root_cert_store.add_server_trust_anchors(webpki_roots::TLS_SERVER_ROOTS.0.iter().map(
|ta| {
OwnedTrustAnchor::from_subject_spki_name_constraints(
ta.subject,
ta.spki,
ta.name_constraints,
)
},
));
let config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(root_cert_store)
.with_no_client_auth();
let connector = TlsConnector::from(Arc::new(config));
let domain = url
.domain()
.and_then(|domain| rustls::ServerName::try_from(domain).ok())
.ok_or_else(|| BoxError::from("No domain in URL"))?;
let stream = connector.connect(domain, stream).await?;
Connection::Tls(stream)
} else {
Connection::NoTls(stream)
}
};
update("Sending HTTP request...", 15);
let req = Request::new(Method::Get, url);
let mut response = async_h1::client::connect(stream, req).await?;
update("Reading response...", 35);
future::yield_now().await;
let bytes = response.body_bytes().await?;
update("Parsing response...", 30);
Ok(bytes)
}
async fn network(state: &RefCell<AppState>) -> Result<()> {
let ongoing_op_count = Cell::new(0);
let ex = async_executor::LocalExecutor::new();
state.borrow().http_states.iter().enumerate().for_each({
let ongoing_op_count = &ongoing_op_count;
let ex = &ex;
move |(i, http)| {
ongoing_op_count.set(ongoing_op_count.get() + 1);
let url = http.url.to_string();
let fut = async move {
let data = match get_http(&url, state, i).await {
Ok(data) => data,
Err(err) => {
let mut state = state.borrow_mut();
let http = &mut state.http_states[i];
http.error = true;
http.progress = 100;
http.status = Cow::Owned(format!("Error: {err}"));
return;
}
};
future::yield_now().await;
let bytes = data.len();
let mut analysis = format!("# of Bytes: {bytes}");
if let Ok(data) = String::from_utf8(data) {
let num_opening_braces = data.chars().filter(|&c| c == '<').count();
write!(
&mut analysis,
" | # of Opening Braces: {num_opening_braces}"
)
.ok();
}
ongoing_op_count.set(ongoing_op_count.get() - 1);
let mut state = state.borrow_mut();
let http = &mut state.http_states[i];
http.progress = 100;
http.status = Cow::Owned(analysis);
};
ex.spawn(fut).detach();
}
});
ex.run({
let ongoing_op_count = &ongoing_op_count;
async move {
let mut timer = async_io::Timer::interval(Duration::from_millis(100));
while ongoing_op_count.get() > 0 {
timer.next().await;
}
}
})
.await;
Ok(())
}
struct Draw {
face: Face<'static>,
unicode_buffer: Option<rustybuzz::UnicodeBuffer>,
surface: Surface,
draw_buffer: Box<[u32; DRAW_BUFFER_SIZE]>,
}
impl Draw {
fn new(evl: &EventLoop<Signal<Rerender>>, window: &Window) -> Result<Self> {
let face = Face::from_slice(include_bytes!("../assets/Roboto-Regular.ttf"), 0)?;
let buffer = rustybuzz::UnicodeBuffer::new();
let context = unsafe { Context::new(&**evl)? };
let surface = unsafe { Surface::new(&context, window)? };
Ok(Self {
face,
unicode_buffer: Some(buffer),
surface,
draw_buffer: Box::new([0; DRAW_BUFFER_SIZE]),
})
}
fn render(&mut self, state: &AppState) -> Result<()> {
self.draw_buffer.fill(0xFFFFFF);
let x = 20;
let mut y = 20;
let mut all_done = true;
for http in &state.http_states {
self.draw_text(&http.url, x, y)?;
self.draw_text(&http.status, x + 20, y + 20)?;
self.draw_progress_bar(http.progress, y + 40, http.error)?;
y += 100;
if http.progress != 100 {
all_done = false;
}
}
if all_done {
self.draw_text("Press [R] to restart", 120, y)?;
}
self.surface
.set_buffer(&*self.draw_buffer, WIDTH as u16, HEIGHT as u16);
Ok(())
}
fn draw_text(&mut self, text: &str, x: u32, y: u32) -> Result<()> {
let mut unicode_buffer = self
.unicode_buffer
.take()
.unwrap_or_else(rustybuzz::UnicodeBuffer::new);
unicode_buffer.clear();
unicode_buffer.push_str(text);
let font = rusttype::Font::Ref(Arc::new(self.face.clone()));
let glyph_buffer = rustybuzz::shape(
&LayoutFace::from_face(self.face.clone())
.ok_or_else(|| BoxError::from("Failed to create layout face"))?,
&[],
unicode_buffer,
);
let scale = rusttype::Scale::uniform(16.0);
glyph_buffer
.glyph_infos()
.iter()
.copied()
.zip(glyph_buffer.glyph_positions().iter().copied())
.try_fold((x, y), |(mut x, mut y), (info, position)| {
let glyph = font.glyph(rusttype::GlyphId(info.glyph_id as _));
let glyph = glyph.scaled(scale).positioned(rusttype::Point {
x: x as f32,
y: y as f32,
});
if let Some(bbx) = glyph.pixel_bounding_box() {
glyph.draw(|dx, dy, pixel| {
let (mut dx, mut dy) = (dx as i32, dy as i32);
dx += bbx.min.x;
dy += bbx.min.y;
let start_index = dx as usize + (dy as usize * WIDTH as usize);
let buffer_start = &mut self.draw_buffer[start_index];
let buffer =
bytemuck::cast_slice_mut::<u32, u8>(std::slice::from_mut(buffer_start));
for i in 0..4 {
buffer[i] = ((1.0 - pixel) * 255.0) as u8;
}
});
}
x += position.x_advance as u32 / 100;
y += position.y_advance as u32 / 100;
Result::Ok((x, y))
})?;
self.unicode_buffer = Some(glyph_buffer.clear());
Ok(())
}
fn draw_progress_bar(&mut self, progress: u8, y: u32, error: bool) -> Result<()> {
use tiny_skia::Color;
let mut pixmap = tiny_skia::PixmapMut::from_bytes(
bytemuck::cast_slice_mut::<u32, u8>(&mut *self.draw_buffer),
WIDTH,
HEIGHT,
)
.unwrap();
let mut builder = None;
let mut draw_rect = move |x, y, width: f32, height: f32, color| {
let path = {
let mut path = builder
.take()
.unwrap_or_else(tiny_skia_path::PathBuilder::new);
path.move_to(x, y);
path.line_to(x + width, y);
path.line_to(x + width, y + height);
path.line_to(x, y + height);
path.close();
path.finish().unwrap()
};
let paint = {
let mut paint = tiny_skia::Paint::default();
paint.set_color(color);
paint
};
pixmap
.fill_path(
&path,
&paint,
tiny_skia::FillRule::EvenOdd,
tiny_skia::Transform::default(),
None,
)
.ok_or_else(|| BoxError::from("Failed to paint progress bar"))?;
Result::Ok(())
};
draw_rect(
20.0,
y as _,
(WIDTH - 40) as _,
24.0,
Color::from_rgba8(0, 0, 0, 0xFF),
)?;
let mut width = (WIDTH - 50) as f32;
draw_rect(
25.0,
(y + 4) as _,
width,
16.0,
Color::from_rgba8(0x77, 0x77, 0x77, 0xFF),
)?;
width *= (progress as f32) / 100.0;
draw_rect(
25.0,
(y + 4) as _,
width,
16.0,
if error {
Color::from_rgba8(0xFF, 0, 0, 0xFF)
} else {
Color::from_rgba8(0, 0xFF, 0, 0xFF)
},
)
.ok();
Ok(())
}
}
fn main() {
let evl = EventLoopBuilder::new_block_on().build();
let window = Rc::new(
WindowBuilder::new()
.with_inner_size(LogicalSize::new(WIDTH, HEIGHT))
.with_min_inner_size(LogicalSize::new(WIDTH, HEIGHT))
.with_max_inner_size(LogicalSize::new(WIDTH, HEIGHT))
.with_title("Networking Example")
.build(&evl)
.expect("Failed to create window"),
);
let mut draw = Draw::new(&evl, &window).expect("Failed to create draw context");
let state = Rc::new(RefCell::new(AppState {
http_states: {
let mut states = env::args()
.skip(1)
.map(|url| HttpState::new(url))
.collect::<Vec<_>>();
if states.is_empty() {
states.extend(
DEFAULT_URLS
.iter()
.map(|url| HttpState::new(url.to_string())),
);
}
states
},
}));
let reset = Rc::new(event_listener::Event::new());
let mut fut = Box::pin({
let state = state.clone();
let reset = reset.clone();
async move {
loop {
if let Err(e) = network(&state).await {
eprintln!("Error: {}", e);
break;
}
reset.listen().await;
state.borrow_mut().http_states.iter_mut().for_each(|http| {
*http = HttpState::new(http.url.clone());
});
}
}
});
evl.block_on(
{
let window = window.clone();
move |event, _, control_flow| {
control_flow.set_wait();
match event {
Event::WindowEvent { event, window_id } if window_id == window.id() => {
match event {
WindowEvent::CloseRequested => control_flow.set_exit(),
WindowEvent::KeyboardInput {
input:
KeyboardInput {
virtual_keycode: Some(VirtualKeyCode::R),
..
},
..
} => {
reset.notify(1);
}
_ => {}
}
}
Event::RedrawRequested(window_id) if window_id == window.id() => {
draw.render(&state.borrow()).expect("Failed to render");
}
Event::UserEvent(Rerender) => {
window.request_redraw();
}
_ => {}
}
}
},
future::poll_fn(move |cx| {
let _ = fut.as_mut().poll(cx);
window.request_redraw();
Poll::Pending
}),
)
}
enum Connection {
NoTls(Async<TcpStream>),
Tls(TlsStream<Async<TcpStream>>),
}
impl AsyncRead for Connection {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut AsyncContext<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
match self.get_mut() {
Connection::NoTls(stream) => Pin::new(stream).poll_read(cx, buf),
Connection::Tls(stream) => Pin::new(stream).poll_read(cx, buf),
}
}
fn poll_read_vectored(
self: Pin<&mut Self>,
cx: &mut AsyncContext<'_>,
bufs: &mut [std::io::IoSliceMut<'_>],
) -> Poll<io::Result<usize>> {
match self.get_mut() {
Connection::NoTls(stream) => Pin::new(stream).poll_read_vectored(cx, bufs),
Connection::Tls(stream) => Pin::new(stream).poll_read_vectored(cx, bufs),
}
}
}
impl AsyncWrite for Connection {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut AsyncContext<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
match self.get_mut() {
Connection::NoTls(stream) => Pin::new(stream).poll_write(cx, buf),
Connection::Tls(stream) => Pin::new(stream).poll_write(cx, buf),
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut AsyncContext<'_>) -> Poll<io::Result<()>> {
match self.get_mut() {
Connection::NoTls(stream) => Pin::new(stream).poll_flush(cx),
Connection::Tls(stream) => Pin::new(stream).poll_flush(cx),
}
}
fn poll_close(self: Pin<&mut Self>, cx: &mut AsyncContext<'_>) -> Poll<io::Result<()>> {
match self.get_mut() {
Connection::NoTls(stream) => Pin::new(stream).poll_close(cx),
Connection::Tls(stream) => Pin::new(stream).poll_close(cx),
}
}
fn poll_write_vectored(
self: Pin<&mut Self>,
cx: &mut AsyncContext<'_>,
bufs: &[std::io::IoSlice<'_>],
) -> Poll<io::Result<usize>> {
match self.get_mut() {
Connection::NoTls(stream) => Pin::new(stream).poll_write_vectored(cx, bufs),
Connection::Tls(stream) => Pin::new(stream).poll_write_vectored(cx, bufs),
}
}
}