├── README.md ├── async-runtime ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── src │ ├── async_io │ │ ├── executor.rs │ │ ├── mod.rs │ │ ├── reactor.rs │ │ ├── task_queue.rs │ │ └── waker_util.rs │ ├── async_net │ │ ├── client.rs │ │ ├── listener.rs │ │ └── mod.rs │ ├── main.rs │ └── web │ │ ├── mod.rs │ │ ├── node.rs │ │ ├── response.rs │ │ ├── router.rs │ │ └── routes.rs └── static │ ├── _400.html │ ├── _404.html │ ├── favicon.ico │ ├── index.html │ ├── styles.css │ └── todo.html ├── echo-tcp-client ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── echo-tcp-server ├── .gitignore ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── http-sync-server ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── src │ ├── main.rs │ ├── node.rs │ ├── response.rs │ ├── router.rs │ ├── routes.rs │ ├── thread_pool.rs │ └── worker.rs └── static │ ├── favicon.ico │ ├── index.html │ ├── not_found.html │ ├── styles.css │ ├── todo.html │ └── unimplemented.html └── httpserver-async-objects ├── .DS_Store ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── src ├── async_io │ ├── client.rs │ ├── event_handler.rs │ ├── event_loop.rs │ ├── listener.rs │ ├── mod.rs │ └── reactor.rs ├── main.rs ├── node.rs ├── response.rs ├── router.rs └── routes.rs └── static ├── favicon.ico ├── index.html ├── not_found.html ├── styles.css ├── todo.html └── unimplemented.html /README.md: -------------------------------------------------------------------------------- 1 | # async-in-depth-rust-series 2 | 3 | This is the repository for an the [Async in Depth Series](https://www.youtube.com/playlist?list=PLb1VOxJqFzDd05_aDQEm6KVblhee_KStX) I did on my [Youtube Channel](https://youtube.com/c/nyxtom). 4 | The main purpose of this project was to determine how to reason about async programming in general, what non-blocking vs blocking operations consist of, 5 | and a way to build concepts up on one another. 6 | -------------------------------------------------------------------------------- /async-runtime/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /async-runtime/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "cc" 18 | version = "1.0.73" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 21 | 22 | [[package]] 23 | name = "cfg-if" 24 | version = "1.0.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 27 | 28 | [[package]] 29 | name = "colored" 30 | version = "2.0.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 33 | dependencies = [ 34 | "atty", 35 | "lazy_static", 36 | "winapi", 37 | ] 38 | 39 | [[package]] 40 | name = "hermit-abi" 41 | version = "0.1.19" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 44 | dependencies = [ 45 | "libc", 46 | ] 47 | 48 | [[package]] 49 | name = "http-sync-server" 50 | version = "0.1.0" 51 | dependencies = [ 52 | "colored", 53 | "libc", 54 | "polling", 55 | ] 56 | 57 | [[package]] 58 | name = "lazy_static" 59 | version = "1.4.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 62 | 63 | [[package]] 64 | name = "libc" 65 | version = "0.2.126" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 68 | 69 | [[package]] 70 | name = "log" 71 | version = "0.4.17" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 74 | dependencies = [ 75 | "cfg-if", 76 | ] 77 | 78 | [[package]] 79 | name = "polling" 80 | version = "2.2.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" 83 | dependencies = [ 84 | "cfg-if", 85 | "libc", 86 | "log", 87 | "wepoll-ffi", 88 | "winapi", 89 | ] 90 | 91 | [[package]] 92 | name = "wepoll-ffi" 93 | version = "0.1.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 96 | dependencies = [ 97 | "cc", 98 | ] 99 | 100 | [[package]] 101 | name = "winapi" 102 | version = "0.3.9" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 105 | dependencies = [ 106 | "winapi-i686-pc-windows-gnu", 107 | "winapi-x86_64-pc-windows-gnu", 108 | ] 109 | 110 | [[package]] 111 | name = "winapi-i686-pc-windows-gnu" 112 | version = "0.4.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 115 | 116 | [[package]] 117 | name = "winapi-x86_64-pc-windows-gnu" 118 | version = "0.4.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 121 | -------------------------------------------------------------------------------- /async-runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-sync-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | colored = "2.0.0" 10 | libc = "0.2.126" 11 | polling = "2.2.0" 12 | -------------------------------------------------------------------------------- /async-runtime/src/async_io/executor.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | future::Future, 4 | io::Result, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use super::reactor::REACTOR; 9 | use super::task_queue::{Task, TaskQueue}; 10 | use super::waker_util::waker_fn; 11 | use colored::Colorize; 12 | 13 | thread_local! { 14 | pub static EXECUTOR: RefCell = RefCell::new(Executor::new()) 15 | } 16 | 17 | pub fn block_on(f: F) -> Result<()> 18 | where 19 | F: Future + 'static, 20 | { 21 | EXECUTOR.with(|executor| -> Result<()> { 22 | let executor = executor.borrow(); 23 | executor.spawn(f); 24 | executor.run() 25 | }) 26 | } 27 | 28 | pub fn spawn(f: F) 29 | where 30 | F: Future + 'static, 31 | { 32 | EXECUTOR.with(|executor| { 33 | let executor = executor.borrow(); 34 | executor.spawn(f); 35 | }); 36 | } 37 | 38 | pub struct Executor { 39 | pub tasks: RefCell, 40 | } 41 | 42 | impl Executor { 43 | pub fn new() -> Self { 44 | Executor { 45 | tasks: RefCell::new(TaskQueue::new()), 46 | } 47 | } 48 | 49 | pub fn spawn(&self, f: F) 50 | where 51 | F: Future + 'static, 52 | { 53 | self.tasks.borrow_mut().push(Task { 54 | future: RefCell::new(Box::pin(f)), 55 | }); 56 | } 57 | 58 | pub fn run(&self) -> Result<()> { 59 | loop { 60 | // process ready queue 61 | // keep processing as long as we aren't waiting for I/O 62 | loop { 63 | if let Some(task) = { 64 | let mut tasks = self.tasks.borrow_mut(); 65 | tasks.pop() 66 | } { 67 | let waker = { 68 | let sender = self.tasks.borrow().sender(); 69 | let waker_task = task.clone(); 70 | waker_fn(move || { 71 | // executor schedule task again 72 | println!( 73 | "{} {:?} waking up to requeue future polling", 74 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 75 | std::thread::current().id(), 76 | ); 77 | sender.send(waker_task.clone()).unwrap(); 78 | }) 79 | }; 80 | let mut context = Context::from_waker(&waker); 81 | println!( 82 | "{} {:?} received task, polling future...", 83 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 84 | std::thread::current().id(), 85 | ); 86 | match task.future.borrow_mut().as_mut().poll(&mut context) { 87 | Poll::Ready(_) => { 88 | println!( 89 | "{} {:?} poll ready complete on spawned task", 90 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 91 | std::thread::current().id(), 92 | ); 93 | } 94 | Poll::Pending => {} 95 | }; 96 | } 97 | 98 | if self.tasks.borrow().is_empty() { 99 | break; 100 | } 101 | } 102 | 103 | // when all is done wait for I/O 104 | // wait for i/o 105 | // i/o events will requeue associated pending tasks 106 | if !REACTOR.with(|current| current.borrow().waiting_on_events()) { 107 | break Ok(()); 108 | } 109 | 110 | self.wait_for_io()?; 111 | 112 | // IO events trigger wakers, which will generate new tasks 113 | self.tasks.borrow_mut().receive(); 114 | } 115 | } 116 | 117 | fn wait_for_io(&self) -> std::io::Result { 118 | REACTOR.with(|current| -> Result { 119 | println!( 120 | "{} {:?} waiting for I/O", 121 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 122 | std::thread::current().id() 123 | ); 124 | let mut events = Vec::new(); 125 | { 126 | let reactor = current.borrow(); 127 | reactor.wait(&mut events, None)?; 128 | } 129 | 130 | let wakers = { 131 | let mut reactor = current.borrow_mut(); 132 | reactor.wakers(events) 133 | }; 134 | 135 | let len = wakers.len(); 136 | for waker in wakers { 137 | waker.wake(); 138 | } 139 | 140 | Ok(len) 141 | }) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /async-runtime/src/async_io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod executor; 2 | pub mod reactor; 3 | pub mod task_queue; 4 | pub mod waker_util; 5 | -------------------------------------------------------------------------------- /async-runtime/src/async_io/reactor.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::HashMap, 4 | io::Result, 5 | task::{Context, Waker}, 6 | time::Duration, 7 | }; 8 | 9 | use polling::{Event, Poller, Source}; 10 | 11 | thread_local! { 12 | pub static REACTOR: RefCell = RefCell::new(Reactor::new()) 13 | } 14 | 15 | pub struct Reactor { 16 | readable: HashMap>, 17 | writable: HashMap>, 18 | poller: Poller, 19 | } 20 | 21 | impl Reactor { 22 | pub fn new() -> Self { 23 | Reactor { 24 | readable: HashMap::new(), 25 | writable: HashMap::new(), 26 | poller: Poller::new().unwrap(), 27 | } 28 | } 29 | 30 | fn get_interest(&self, key: usize) -> Event { 31 | let readable = self.readable.contains_key(&key); 32 | let writable = self.writable.contains_key(&key); 33 | match (readable, writable) { 34 | (false, false) => Event::none(key), 35 | (true, false) => Event::readable(key), 36 | (false, true) => Event::writable(key), 37 | (true, true) => Event::all(key), 38 | } 39 | } 40 | 41 | pub fn wake_on_readable(&mut self, source: impl Source, cx: &mut Context) { 42 | let key = source.raw() as usize; 43 | self.readable 44 | .entry(key) 45 | .or_default() 46 | .push(cx.waker().clone()); 47 | self.poller.modify(source, self.get_interest(key)).unwrap(); 48 | } 49 | 50 | pub fn wake_on_writable(&mut self, source: impl Source, cx: &mut Context) { 51 | let fd = source.raw(); 52 | 53 | let key = fd as usize; 54 | self.writable 55 | .entry(key) 56 | .or_default() 57 | .push(cx.waker().clone()); 58 | 59 | self.poller.modify(source, self.get_interest(key)).unwrap(); 60 | } 61 | 62 | pub fn remove(&mut self, source: impl Source) { 63 | let key = source.raw() as usize; 64 | self.poller.delete(source).unwrap(); 65 | self.readable.remove(&key); 66 | self.writable.remove(&key); 67 | } 68 | 69 | pub fn add(&self, source: impl Source) { 70 | let key = source.raw() as usize; 71 | self.poller.add(source, self.get_interest(key)).unwrap(); 72 | } 73 | 74 | pub fn wakers(&mut self, events: Vec) -> Vec { 75 | let mut wakers = Vec::new(); 76 | 77 | for ev in events { 78 | if let Some((_, readers)) = self.readable.remove_entry(&ev.key) { 79 | for waker in readers { 80 | wakers.push(waker); 81 | } 82 | } 83 | if let Some((_, writers)) = self.writable.remove_entry(&ev.key) { 84 | for waker in writers { 85 | wakers.push(waker); 86 | } 87 | } 88 | } 89 | 90 | wakers 91 | } 92 | 93 | pub fn wait(&self, events: &mut Vec, timeout: Option) -> Result { 94 | self.poller.wait(events, timeout) 95 | } 96 | 97 | pub fn waiting_on_events(&self) -> bool { 98 | !self.readable.is_empty() || !self.writable.is_empty() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /async-runtime/src/async_io/task_queue.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::future::Future; 3 | use std::pin::Pin; 4 | use std::rc::Rc; 5 | use std::sync::mpsc; 6 | use std::sync::mpsc::{Receiver, Sender}; 7 | 8 | pub type LocalBoxedFuture<'a, T> = Pin + 'a>>; 9 | 10 | pub struct TaskQueue { 11 | sender: Sender>, 12 | receiver: Receiver>, 13 | tasks: Vec>, 14 | } 15 | 16 | pub struct Task { 17 | pub future: RefCell>, 18 | } 19 | 20 | impl TaskQueue { 21 | pub fn new() -> Self { 22 | let (sender, receiver) = mpsc::channel(); 23 | TaskQueue { 24 | sender, 25 | receiver, 26 | tasks: Vec::new(), 27 | } 28 | } 29 | 30 | pub fn sender(&self) -> Sender> { 31 | self.sender.clone() 32 | } 33 | 34 | pub fn pop(&mut self) -> Option> { 35 | self.tasks.pop() 36 | } 37 | 38 | pub fn push(&mut self, runnable: Task) { 39 | self.tasks.push(Rc::new(runnable)); 40 | } 41 | 42 | pub fn receive(&mut self) { 43 | for runnable in self.receiver.try_iter() { 44 | self.tasks.push(runnable); 45 | } 46 | } 47 | 48 | pub fn is_empty(&self) -> bool { 49 | self.tasks.is_empty() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /async-runtime/src/async_io/waker_util.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | mem::ManuallyDrop, 3 | sync::Arc, 4 | task::{RawWaker, RawWakerVTable, Waker}, 5 | }; 6 | 7 | pub fn waker_fn(f: F) -> Waker { 8 | let raw = Arc::into_raw(Arc::new(f)) as *const (); 9 | let vtable = &WakerHelper::::VTABLE; 10 | unsafe { Waker::from_raw(RawWaker::new(raw, vtable)) } 11 | } 12 | 13 | struct WakerHelper(F); 14 | 15 | impl WakerHelper { 16 | const VTABLE: RawWakerVTable = RawWakerVTable::new( 17 | Self::clone_waker, 18 | Self::wake, 19 | Self::wake_by_ref, 20 | Self::drop_waker, 21 | ); 22 | 23 | unsafe fn clone_waker(ptr: *const ()) -> RawWaker { 24 | let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const F)); 25 | std::mem::forget(arc.clone()); 26 | RawWaker::new(ptr, &Self::VTABLE) 27 | } 28 | 29 | unsafe fn wake(ptr: *const ()) { 30 | let arc = Arc::from_raw(ptr as *const F); 31 | (arc)(); 32 | } 33 | 34 | unsafe fn wake_by_ref(ptr: *const ()) { 35 | let arc = ManuallyDrop::new(Arc::from_raw(ptr as *const F)); 36 | (arc)(); 37 | } 38 | 39 | unsafe fn drop_waker(ptr: *const ()) { 40 | drop(Arc::from_raw(ptr as *const F)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /async-runtime/src/async_net/client.rs: -------------------------------------------------------------------------------- 1 | use std::io::{ErrorKind, Read, Result, Write}; 2 | use std::{ 3 | future::Future, 4 | net, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | 9 | use crate::async_io::reactor::REACTOR; 10 | 11 | pub struct TcpClient { 12 | stream: net::TcpStream, 13 | } 14 | 15 | impl TcpClient { 16 | pub fn new(stream: net::TcpStream) -> Self { 17 | TcpClient { stream } 18 | } 19 | 20 | pub fn read<'stream, T: AsMut<[u8]>>( 21 | &'stream mut self, 22 | buf: &'stream mut T, 23 | ) -> ReadFuture<'stream> { 24 | ReadFuture { 25 | stream: &mut self.stream, 26 | buf: buf.as_mut(), 27 | } 28 | } 29 | 30 | pub fn write<'stream, T: AsRef<[u8]>>( 31 | &'stream mut self, 32 | buf: &'stream T, 33 | ) -> WriteFuture<'stream> { 34 | WriteFuture { 35 | stream: &mut self.stream, 36 | buf: buf.as_ref(), 37 | } 38 | } 39 | 40 | pub fn flush(&mut self) { 41 | self.stream.flush().unwrap(); 42 | } 43 | } 44 | 45 | pub struct WriteFuture<'stream> { 46 | stream: &'stream mut net::TcpStream, 47 | buf: &'stream [u8], 48 | } 49 | 50 | impl Future for WriteFuture<'_> { 51 | type Output = Result; 52 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 53 | let state = self.get_mut(); 54 | match state.stream.write(state.buf) { 55 | Ok(n) => Poll::Ready(Ok(n)), 56 | Err(e) if e.kind() == ErrorKind::WouldBlock => { 57 | REACTOR.with(|current| { 58 | current.borrow_mut().wake_on_writable(&*state.stream, cx); 59 | }); 60 | Poll::Pending 61 | } 62 | Err(e) => Poll::Ready(Err(e)), 63 | } 64 | } 65 | } 66 | 67 | pub struct ReadFuture<'stream> { 68 | stream: &'stream mut net::TcpStream, 69 | buf: &'stream mut [u8], 70 | } 71 | 72 | impl Future for ReadFuture<'_> { 73 | type Output = Result; 74 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { 75 | let state = self.get_mut(); 76 | match state.stream.read(&mut state.buf) { 77 | Ok(n) => Poll::Ready(Ok(n)), 78 | Err(e) if e.kind() == ErrorKind::WouldBlock => { 79 | REACTOR.with(|current| { 80 | current.borrow_mut().wake_on_readable(&*state.stream, cx); 81 | }); 82 | Poll::Pending 83 | } 84 | Err(e) => Poll::Ready(Err(e)), 85 | } 86 | } 87 | } 88 | 89 | impl Drop for TcpClient { 90 | fn drop(&mut self) { 91 | REACTOR.with(|current| { 92 | let mut current = current.borrow_mut(); 93 | current.remove(&self.stream); 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /async-runtime/src/async_net/listener.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | use std::io::{self, Result}; 3 | use std::net; 4 | use std::pin::Pin; 5 | use std::task::{Context, Poll}; 6 | 7 | use colored::Colorize; 8 | 9 | use crate::async_io::reactor::REACTOR; 10 | 11 | use super::client::TcpClient; 12 | 13 | pub struct TcpListener { 14 | listener: net::TcpListener, 15 | } 16 | 17 | impl TcpListener { 18 | pub fn bind(addr: &str) -> Result { 19 | let listener = net::TcpListener::bind(addr)?; 20 | listener.set_nonblocking(true)?; 21 | Ok(TcpListener { listener }) 22 | } 23 | 24 | pub fn accept(&self) -> Accept { 25 | REACTOR.with(|current| { 26 | let current = current.borrow(); 27 | current.add(&self.listener); 28 | }); 29 | 30 | Accept { 31 | listener: &self.listener, 32 | } 33 | } 34 | } 35 | 36 | pub struct Accept<'listener> { 37 | listener: &'listener net::TcpListener, 38 | } 39 | 40 | impl Future for Accept<'_> { 41 | type Output = Result<(TcpClient, net::SocketAddr)>; 42 | fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { 43 | match self.listener.accept() { 44 | Ok((stream, addr)) => Poll::Ready(Ok((TcpClient::new(stream), addr))), 45 | Err(e) if e.kind() == io::ErrorKind::WouldBlock => { 46 | println!( 47 | "{} {:?} tcp listener 127.0.0.1:7000 accept() would block, pending future", 48 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 49 | std::thread::current().id() 50 | ); 51 | REACTOR.with(|current| { 52 | let mut current = current.borrow_mut(); 53 | current.wake_on_readable(self.listener, cx); 54 | }); 55 | Poll::Pending 56 | } 57 | Err(e) => Poll::Ready(Err(e)), 58 | } 59 | } 60 | } 61 | 62 | impl Drop for Accept<'_> { 63 | fn drop(&mut self) { 64 | REACTOR.with(|current| { 65 | let mut current = current.borrow_mut(); 66 | current.remove(self.listener); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /async-runtime/src/async_net/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod listener; 3 | -------------------------------------------------------------------------------- /async-runtime/src/main.rs: -------------------------------------------------------------------------------- 1 | mod async_io; 2 | mod async_net; 3 | mod web; 4 | 5 | use crate::async_io::executor; 6 | use crate::async_net::listener::TcpListener; 7 | use crate::web::router::Router; 8 | use crate::web::routes; 9 | use std::io::Result; 10 | 11 | fn main() -> Result<()> { 12 | executor::block_on(async { 13 | let listener = TcpListener::bind("127.0.0.1:7000").unwrap(); 14 | while let Ok((client, addr)) = listener.accept().await { 15 | executor::spawn(async { 16 | let mut router = Router::new(); 17 | routes::configure(&mut router); 18 | router.route_client(client).await.unwrap(); 19 | }); 20 | } 21 | }) 22 | } 23 | -------------------------------------------------------------------------------- /async-runtime/src/web/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod node; 2 | pub mod response; 3 | pub mod router; 4 | pub mod routes; 5 | -------------------------------------------------------------------------------- /async-runtime/src/web/node.rs: -------------------------------------------------------------------------------- 1 | use super::router::HandlerFn; 2 | 3 | pub struct Node { 4 | pub nodes: Vec, 5 | pub key: String, 6 | pub handler: Option, 7 | } 8 | 9 | impl Node { 10 | pub fn new(key: &str) -> Self { 11 | Node { 12 | nodes: Vec::new(), 13 | key: String::from(key), 14 | handler: None, 15 | } 16 | } 17 | 18 | pub fn insert(&mut self, path: &str, f: HandlerFn) { 19 | match path.split_once('/') { 20 | Some((root, "")) => { 21 | self.key = String::from(root); 22 | self.handler = Some(f); 23 | } 24 | Some(("", path)) => self.insert(path, f), 25 | Some((root, path)) => { 26 | let node = self.nodes.iter_mut().find(|m| root == &m.key); 27 | match node { 28 | Some(n) => n.insert(path, f), 29 | None => { 30 | let mut node = Node::new(root); 31 | node.insert(path, f); 32 | self.nodes.push(node); 33 | } 34 | } 35 | } 36 | None => { 37 | let mut node = Node::new(path); 38 | node.handler = Some(f); 39 | self.nodes.push(node); 40 | } 41 | } 42 | } 43 | 44 | pub fn get(&self, path: &str) -> Option<&HandlerFn> { 45 | match path.split_once('/') { 46 | Some((root, "")) => { 47 | if root == &self.key { 48 | self.handler.as_ref() 49 | } else { 50 | None 51 | } 52 | } 53 | Some(("", path)) => self.get(path), 54 | Some((root, path)) => { 55 | let node = self.nodes.iter().find(|m| root == &m.key); 56 | if let Some(node) = node { 57 | node.get(path) 58 | } else { 59 | None 60 | } 61 | } 62 | None => { 63 | let node = self.nodes.iter().find(|m| path == &m.key); 64 | if let Some(node) = node { 65 | node.handler.as_ref() 66 | } else { 67 | None 68 | } 69 | } 70 | } 71 | } 72 | } 73 | /* 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn test_insert_routes() { 80 | let mut root = Node::new(""); 81 | root.insert("/", |_| Ok(())); 82 | root.insert("/foo", |_| Ok(())); 83 | root.insert("/foo/bar", |_| Ok(())); 84 | 85 | println!("{:?}", root); 86 | } 87 | 88 | #[test] 89 | fn test_get_route() { 90 | println!("{:?}", "foo".split_once('/')); 91 | println!("{:?}", "".split_once('/')); 92 | let mut root = Node::new(""); 93 | root.insert("/", |_| Ok(())); 94 | root.insert("/foo/bar", |_| Ok(())); 95 | root.insert("/foo/foo", |_| Ok(())); 96 | root.insert("/users/{id}/profile", |_| Ok(())); 97 | root.insert("/companies/{id}/users/{userid}", |_| Ok(())); 98 | 99 | assert!(root.get("/").is_some()); 100 | assert!(root.get("/foo/bar").is_some()); 101 | assert!(root.get("/foo/foo").is_some()); 102 | assert!(root.get("/fooar").is_none()); 103 | assert!(root.get("/foo/bar/baz").is_none()); 104 | assert!(root.get("/fbar/baz").is_none()); 105 | assert!(root.get("/users/foo/profile").is_some()); 106 | assert!(root.get("/users/bar/profile").is_some()); 107 | assert!(root.get("/users/bar/asdf").is_none()); 108 | assert!(root.get("/companies/1234/asdf").is_none()); 109 | assert!(root.get("/companies/1234/users").is_none()); 110 | assert!(root.get("/companies/1234/users/foo").is_some()); 111 | 112 | println!("{:?}", root); 113 | } 114 | } 115 | */ 116 | -------------------------------------------------------------------------------- /async-runtime/src/web/response.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Result}; 2 | 3 | use colored::Colorize; 4 | 5 | use crate::async_net::client::TcpClient; 6 | 7 | pub struct Response { 8 | client: TcpClient, 9 | } 10 | 11 | pub fn status_code(code: i32) -> i32 { 12 | match code { 13 | 200 | 400 | 404 => code, 14 | _ => 501, 15 | } 16 | } 17 | 18 | pub fn status(code: i32) -> &'static str { 19 | match code { 20 | 200 => "OK", 21 | 400 => "BAD REQUEST", 22 | 404 => "NOT FOUND", 23 | _ => "NOT IMPLEMENTED", 24 | } 25 | } 26 | 27 | impl Response { 28 | pub fn new(client: TcpClient) -> Self { 29 | Response { client } 30 | } 31 | 32 | pub fn parse_mime_type(&self, key: &str) -> &str { 33 | if let Some((_, ext)) = key.rsplit_once(".") { 34 | match ext { 35 | "html" => "text/html", 36 | "css" => "text/css", 37 | "js" => "text/javascript", 38 | "jpg" => "image/jpeg", 39 | "jpeg" => "image/jpeg", 40 | "png" => "image/png", 41 | "ico" => "image/x-icon", 42 | "pdf" => "application/pdf", 43 | _ => "text/plain", 44 | } 45 | } else { 46 | "text/plain" 47 | } 48 | } 49 | 50 | pub fn read_response_file(&self, path: &str) -> Vec { 51 | let file = std::fs::File::open(String::from(path)).unwrap(); 52 | let mut buf = Vec::new(); 53 | let mut reader = std::io::BufReader::new(file); 54 | reader.read_to_end(&mut buf).unwrap(); 55 | buf 56 | } 57 | 58 | pub async fn send_file(&mut self, code: i32, path: &str) -> Result<()> { 59 | let contents = self.read_response_file(path.clone()); 60 | let len = contents.len(); 61 | 62 | let mime_type = self.parse_mime_type(path); 63 | let content = format!( 64 | "HTTP/1.0 {} {} 65 | content-type: {}; charset=UTF-8 66 | content-length: {} 67 | 68 | ", 69 | status_code(code), 70 | status(code), 71 | mime_type, 72 | len 73 | ); 74 | 75 | let bytes = content.as_bytes(); 76 | self.client.write(&bytes).await?; 77 | self.client.write(&contents).await?; 78 | self.client.flush(); 79 | 80 | println!( 81 | "{} {:?} writing response \n{}", 82 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 83 | std::thread::current().id(), 84 | content 85 | ); 86 | 87 | Ok(()) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /async-runtime/src/web/router.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::future::Future; 3 | use std::io::Result; 4 | use std::pin::Pin; 5 | 6 | use colored::Colorize; 7 | 8 | use crate::async_io::task_queue::LocalBoxedFuture; 9 | use crate::async_net::client::TcpClient; 10 | 11 | use super::node::Node; 12 | use super::response::Response; 13 | 14 | #[derive(PartialEq, Eq, Hash)] 15 | pub enum Method { 16 | GET, 17 | } 18 | 19 | pub type HandlerFn = Pin LocalBoxedFuture<'static, Result<()>>>>; 20 | 21 | pub struct Router { 22 | routes: HashMap, 23 | } 24 | 25 | impl Router { 26 | pub fn new() -> Self { 27 | Router { 28 | routes: HashMap::new(), 29 | } 30 | } 31 | 32 | pub fn insert(&mut self, method: Method, path: &str, handler: F) 33 | where 34 | F: Fn(TcpClient) -> Fut + 'static, 35 | Fut: Future> + 'static, 36 | { 37 | let node = self.routes.entry(method).or_insert(Node::new("/")); 38 | node.insert(path, Box::pin(move |client| Box::pin(handler(client)))); 39 | } 40 | 41 | pub async fn route_client(&mut self, mut client: TcpClient) -> Result<()> { 42 | let mut buffer = [0; 1024]; 43 | let n = client.read(&mut buffer).await?; 44 | 45 | // read a single line (if one exists) 46 | let req = String::from_utf8_lossy(&buffer[0..n]); 47 | let mut lines = req.split('\n'); 48 | let line = lines.next().unwrap(); 49 | println!( 50 | "{} {:?} client requested\n{}", 51 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 52 | std::thread::current().id(), 53 | &line 54 | ); 55 | 56 | // consume bytes read from original reader 57 | let parts: Vec<&str> = line.split(" ").collect(); 58 | if parts.len() < 2 { 59 | self.bad_request(client).await 60 | } else { 61 | match (parts[0], parts[1]) { 62 | ("GET", path) => self.handle(Method::GET, path, client).await, 63 | _ => self.not_found(client).await, 64 | } 65 | } 66 | } 67 | 68 | pub async fn handle( 69 | &mut self, 70 | method: Method, 71 | resource: &str, 72 | client: TcpClient, 73 | ) -> Result<()> { 74 | if let Some(node) = self.routes.get_mut(&method) { 75 | if let Some(handler) = node.get(resource) { 76 | return handler(client).await; 77 | } 78 | } 79 | 80 | // default not found 81 | self.bad_request(client).await 82 | } 83 | 84 | pub async fn bad_request(&self, client: TcpClient) -> Result<()> { 85 | let mut res = Response::new(client); 86 | res.send_file(400, "static/_400.html").await 87 | } 88 | 89 | pub async fn not_found(&self, client: TcpClient) -> Result<()> { 90 | let mut res = Response::new(client); 91 | res.send_file(404, "static/_404.html").await 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /async-runtime/src/web/routes.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | response::Response, 3 | router::{Method, Router}, 4 | }; 5 | 6 | pub fn configure(router: &mut Router) { 7 | router.insert(Method::GET, "/", |client| async { 8 | let mut res = Response::new(client); 9 | res.send_file(200, "static/index.html").await 10 | }); 11 | router.insert(Method::GET, "/todo", |client| async { 12 | let mut res = Response::new(client); 13 | res.send_file(200, "static/todo.html").await 14 | }); 15 | router.insert(Method::GET, "/static/styles.css", |client| async { 16 | let mut res = Response::new(client); 17 | res.send_file(200, "static/styles.css").await 18 | }); 19 | router.insert(Method::GET, "/favicon.ico", |client| async { 20 | let mut res = Response::new(client); 21 | res.send_file(200, "static/favicon.ico").await 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /async-runtime/static/_400.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

Bad Request

19 |

Request is not implemented

20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /async-runtime/static/_404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

Not Found

19 |

Content Not Found

20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /async-runtime/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyxtom/async-in-depth-rust-series/de70192cca232c8fa33058543d5f262ad28ee26a/async-runtime/static/favicon.ico -------------------------------------------------------------------------------- /async-runtime/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eu scelerisque felis imperdiet proin fermentum leo. Nunc sed velit dignissim sodales ut. Sagittis vitae et leo duis ut diam quam nulla. Adipiscing tristique risus nec feugiat in fermentum posuere urna nec. Magna eget est lorem ipsum dolor sit. Augue neque gravida in fermentum et sollicitudin ac orci phasellus. Sit amet facilisis magna etiam. Urna condimentum mattis pellentesque id nibh tortor. Arcu non sodales neque sodales. Blandit cursus risus at ultrices. Nunc faucibus a pellentesque sit amet porttitor. Non diam phasellus vestibulum lorem sed. Quisque id diam vel quam elementum pulvinar etiam non. Id faucibus nisl tincidunt eget nullam. Tincidunt eget nullam non nisi est. Sagittis id consectetur purus ut faucibus pulvinar. Non sodales neque sodales ut etiam sit amet nisl. Vivamus arcu felis bibendum ut tristique et egestas quis ipsum. 20 |

21 |

22 | Quis enim lobortis scelerisque fermentum dui faucibus in ornare. Dui ut ornare lectus sit amet est placerat in egestas. Enim ut tellus elementum sagittis vitae et leo. Vitae nunc sed velit dignissim sodales. Viverra adipiscing at in tellus integer. Ac feugiat sed lectus vestibulum mattis ullamcorper velit. Quam pellentesque nec nam aliquam sem et tortor. Nisl condimentum id venenatis a condimentum vitae sapien. Auctor augue mauris augue neque. Diam sollicitudin tempor id eu nisl nunc mi ipsum. Pharetra et ultrices neque ornare aenean euismod elementum nisi quis. Scelerisque fermentum dui faucibus in. 23 |

24 | Elementum tempus egestas sed sed risus pretium quam. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies leo. Ultricies mi quis hendrerit dolor magna eget. Fusce ut placerat orci nulla pellentesque. In est ante in nibh. Nisl pretium fusce id velit ut. Tellus pellentesque eu tincidunt tortor aliquam nulla facilisi. Nam libero justo laoreet sit amet cursus sit amet. Feugiat nisl pretium fusce id velit ut tortor pretium. Ut diam quam nulla porttitor massa. Pellentesque dignissim enim sit amet venenatis. Id volutpat lacus laoreet non. Sit amet aliquam id diam maecenas ultricies mi eget. Mi sit amet mauris commodo. Interdum posuere lorem ipsum dolor sit amet consectetur. Donec adipiscing tristique risus nec feugiat in fermentum posuere. Est pellentesque elit ullamcorper dignissim cras. Erat velit scelerisque in dictum non consectetur. 25 |

26 | Eros in cursus turpis massa tincidunt. Nibh venenatis cras sed felis. Volutpat commodo sed egestas egestas fringilla phasellus. Et magnis dis parturient montes nascetur ridiculus. Natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Ac turpis egestas maecenas pharetra convallis posuere. Mauris augue neque gravida in fermentum et sollicitudin. Ultricies mi quis hendrerit dolor magna eget. Risus commodo viverra maecenas accumsan lacus vel facilisis volutpat. Leo urna molestie at elementum. Senectus et netus et malesuada fames ac turpis egestas. Lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque. 27 |

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /async-runtime/static/styles.css: -------------------------------------------------------------------------------- 1 | /** CSS RESET **/ 2 | html, body, div, span, applet, object, iframe, 3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 4 | a, abbr, acronym, address, big, cite, code, 5 | del, dfn, em, img, ins, kbd, q, s, samp, 6 | small, strike, strong, sub, sup, tt, var, 7 | b, u, i, center, 8 | dl, dt, dd, ol, ul, li, 9 | fieldset, form, label, legend, 10 | table, caption, tbody, tfoot, thead, tr, th, td, 11 | article, aside, canvas, details, embed, 12 | figure, figcaption, footer, header, hgroup, 13 | menu, nav, output, ruby, section, summary, 14 | time, mark, audio, video { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | font-size: 100%; 19 | font: inherit; 20 | vertical-align: baseline; 21 | } 22 | /* HTML5 display-role reset for older browsers */ 23 | article, aside, details, figcaption, figure, 24 | footer, header, hgroup, menu, nav, section { 25 | display: block; 26 | } 27 | body { 28 | line-height: 1.3; 29 | } 30 | ol, ul { 31 | list-style: none; 32 | } 33 | blockquote, q { 34 | quotes: none; 35 | } 36 | blockquote:before, blockquote:after, 37 | q:before, q:after { 38 | content: ''; 39 | content: none; 40 | } 41 | table { 42 | border-collapse: collapse; 43 | border-spacing: 0; 44 | } 45 | 46 | /** Styles **/ 47 | .container { 48 | min-width: 500px; 49 | max-width: 900px; 50 | margin: 20px auto; 51 | } 52 | .title { 53 | font-size: 0.9rem; 54 | font-weight: bold; 55 | } 56 | .nav { 57 | margin: 50px 30px; 58 | display: flex; 59 | flex-direction: row; 60 | } 61 | .nav .title { 62 | margin-right: 50px; 63 | flex: 1; 64 | } 65 | .links { 66 | display: flex; 67 | flex-direction: row; 68 | } 69 | .links li { 70 | font-size: 0.9rem; 71 | } 72 | .links li::after { 73 | padding: 0px 15px; 74 | content: '|'; 75 | display: inline; 76 | } 77 | .links li:last-child::after { 78 | padding: 0px; 79 | content: ''; 80 | display: inline; 81 | } 82 | a { 83 | font-weight: 600; 84 | color: #224466; 85 | text-decoration: none; 86 | transition: all 0.2s ease-in-out; 87 | } 88 | a:hover { 89 | color: #7799aa; 90 | } 91 | 92 | .body { 93 | border-top: 40px solid #f9f9f9; 94 | margin: 0px 20px; 95 | background: #fefefe; 96 | flex-direction: column; 97 | padding: 30px 40px; 98 | box-shadow: 2px 2px 4px 0px #eee, 99 | 4px 4px 12px 4px #eee; 100 | } 101 | .body p { 102 | margin: 20px; 103 | } 104 | -------------------------------------------------------------------------------- /async-runtime/static/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

todo!

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /echo-tcp-client/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "echo-tcp-client" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /echo-tcp-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo-tcp-client" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /echo-tcp-client/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Read, Write}, 3 | net::TcpStream, 4 | }; 5 | 6 | fn main() -> std::io::Result<()> { 7 | // fd = socket 8 | // connect() 9 | let addr = "127.0.0.1:7000"; 10 | let mut client = TcpStream::connect(addr)?; 11 | 12 | let stdin = std::io::stdin(); 13 | let mut input = String::new(); 14 | loop { 15 | input.clear(); 16 | print!("{addr}> "); 17 | std::io::stdout().flush()?; 18 | stdin.read_line(&mut input)?; 19 | 20 | client.write( 21 | "TESTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT".as_bytes(), 22 | )?; 23 | loop { 24 | let mut bytes = [0u8; 32]; 25 | let res = client.read(&mut bytes)?; 26 | if res > 0 { 27 | print!("{}", String::from_utf8_lossy(&bytes[0..res as usize])); 28 | } 29 | if res < bytes.len() { 30 | break; 31 | } 32 | } 33 | println!(""); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /echo-tcp-server/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /echo-tcp-server/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "echo-tcp-server" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /echo-tcp-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "echo-tcp-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | -------------------------------------------------------------------------------- /echo-tcp-server/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io::{Read, Write}, 3 | net::TcpListener, 4 | }; 5 | 6 | fn main() -> std::io::Result<()> { 7 | // fd = socket 8 | // bind(fd, socketaddr *, sizeof(serveraddr *)) 9 | // listen(fd) 10 | let listener = TcpListener::bind("127.0.0.1:7000")?; 11 | println!("\nserver socket bind/listen 127.0.0.1:7000\n"); 12 | 13 | loop { 14 | match listener.accept() { 15 | Ok((mut client_socket, addr)) => { 16 | println!("client {addr} connected to server"); 17 | loop { 18 | let mut bytes = [0u8; 32]; 19 | let res = client_socket.read(&mut bytes)?; 20 | if res > 0 { 21 | client_socket.write(&bytes[0..res as usize])?; 22 | } else { 23 | break; 24 | } 25 | } 26 | } 27 | Err(e) => println!("Could not accept client {:?}", e), 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /http-sync-server/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /http-sync-server/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "cc" 18 | version = "1.0.73" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 21 | 22 | [[package]] 23 | name = "cfg-if" 24 | version = "1.0.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 27 | 28 | [[package]] 29 | name = "colored" 30 | version = "2.0.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 33 | dependencies = [ 34 | "atty", 35 | "lazy_static", 36 | "winapi", 37 | ] 38 | 39 | [[package]] 40 | name = "hermit-abi" 41 | version = "0.1.19" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 44 | dependencies = [ 45 | "libc", 46 | ] 47 | 48 | [[package]] 49 | name = "http-sync-server" 50 | version = "0.1.0" 51 | dependencies = [ 52 | "colored", 53 | "libc", 54 | "polling", 55 | ] 56 | 57 | [[package]] 58 | name = "lazy_static" 59 | version = "1.4.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 62 | 63 | [[package]] 64 | name = "libc" 65 | version = "0.2.126" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 68 | 69 | [[package]] 70 | name = "log" 71 | version = "0.4.17" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 74 | dependencies = [ 75 | "cfg-if", 76 | ] 77 | 78 | [[package]] 79 | name = "polling" 80 | version = "2.2.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" 83 | dependencies = [ 84 | "cfg-if", 85 | "libc", 86 | "log", 87 | "wepoll-ffi", 88 | "winapi", 89 | ] 90 | 91 | [[package]] 92 | name = "wepoll-ffi" 93 | version = "0.1.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 96 | dependencies = [ 97 | "cc", 98 | ] 99 | 100 | [[package]] 101 | name = "winapi" 102 | version = "0.3.9" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 105 | dependencies = [ 106 | "winapi-i686-pc-windows-gnu", 107 | "winapi-x86_64-pc-windows-gnu", 108 | ] 109 | 110 | [[package]] 111 | name = "winapi-i686-pc-windows-gnu" 112 | version = "0.4.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 115 | 116 | [[package]] 117 | name = "winapi-x86_64-pc-windows-gnu" 118 | version = "0.4.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 121 | -------------------------------------------------------------------------------- /http-sync-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-sync-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | colored = "2.0.0" 10 | libc = "0.2.126" 11 | polling = "2.2.0" 12 | -------------------------------------------------------------------------------- /http-sync-server/src/main.rs: -------------------------------------------------------------------------------- 1 | mod node; 2 | mod response; 3 | mod router; 4 | mod routes; 5 | mod thread_pool; 6 | mod worker; 7 | 8 | use crate::thread_pool::ThreadPool; 9 | use colored::*; 10 | use router::Router; 11 | use std::io::{Error, Result}; 12 | use std::net::TcpListener; 13 | use std::sync::Arc; 14 | 15 | fn check_err(num: T) -> Result { 16 | if num < T::default() { 17 | return Err(Error::last_os_error()); 18 | } 19 | Ok(num) 20 | } 21 | 22 | fn fork() -> Result { 23 | check_err(unsafe { libc::fork() }).map(|pid| pid as u32) 24 | } 25 | 26 | fn wait(pid: i32) -> Result { 27 | check_err(unsafe { libc::waitpid(pid, 0 as *mut libc::c_int, 0) }).map(|code| code as u32) 28 | } 29 | 30 | fn main() -> Result<()> { 31 | let port = std::env::var("PORT").unwrap_or(String::from("7000")); 32 | let listener = TcpListener::bind(format!("127.0.0.1:{port}"))?; 33 | println!( 34 | "{} server listening on 127.0.0.1:{}", 35 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 36 | port 37 | ); 38 | 39 | let mut router = Router::new(); 40 | routes::configure(&mut router); 41 | let router = Arc::new(router); 42 | let mut pids = vec![]; 43 | for _ in 0..2 { 44 | let child_pid = fork()?; 45 | if child_pid == 0 { 46 | let pool = ThreadPool::new(4); 47 | for client in listener.incoming() { 48 | if let Ok(client) = client { 49 | let router = Arc::clone(&router); 50 | pool.execute(move || { 51 | println!( 52 | "{} [{:?}] client connected at {}", 53 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 54 | std::thread::current().id(), 55 | client.peer_addr().unwrap() 56 | ); 57 | router.route_client(client).unwrap(); 58 | }); 59 | } 60 | } 61 | break; 62 | } else { 63 | println!( 64 | "{} forking process, new {child_pid}", 65 | format!("[{}]", std::process::id()).truecolor(0, 255, 136) 66 | ); 67 | } 68 | pids.push(child_pid); 69 | } 70 | 71 | for p in pids { 72 | wait(p as i32)?; 73 | println!( 74 | "{} <<< {p} exit()", 75 | format!("[{}]", std::process::id()).truecolor(200, 255, 136) 76 | ); 77 | } 78 | 79 | Ok(()) 80 | } 81 | -------------------------------------------------------------------------------- /http-sync-server/src/node.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Node { 3 | pub nodes: Vec>, 4 | pub key: String, 5 | pub handler: Option, 6 | pub is_wildcard: bool, 7 | } 8 | 9 | impl Node { 10 | pub fn new(key: &str) -> Self { 11 | Node { 12 | nodes: Vec::new(), 13 | key: String::from(key), 14 | handler: None, 15 | is_wildcard: key.starts_with('{') && key.ends_with('}'), 16 | } 17 | } 18 | 19 | pub fn insert(&mut self, path: &str, f: F) { 20 | match path.split_once('/') { 21 | Some((root, "")) => { 22 | self.key = String::from(root); 23 | self.handler = Some(f); 24 | } 25 | Some(("", path)) => self.insert(path, f), 26 | Some((root, path)) => { 27 | let node = self 28 | .nodes 29 | .iter_mut() 30 | .find(|m| root == &m.key || m.is_wildcard); 31 | match node { 32 | Some(n) => n.insert(path, f), 33 | None => { 34 | let mut node = Node::new(root); 35 | node.insert(path, f); 36 | self.nodes.push(node); 37 | } 38 | } 39 | } 40 | None => { 41 | let mut node = Node::new(path); 42 | node.handler = Some(f); 43 | self.nodes.push(node); 44 | } 45 | } 46 | } 47 | 48 | pub fn get(&self, path: &str) -> Option<&F> { 49 | match path.split_once('/') { 50 | Some((root, "")) => { 51 | if root == &self.key || self.is_wildcard { 52 | self.handler.as_ref() 53 | } else { 54 | None 55 | } 56 | } 57 | Some(("", path)) => self.get(path), 58 | Some((root, path)) => { 59 | let node = self.nodes.iter().find(|m| root == &m.key || m.is_wildcard); 60 | if let Some(node) = node { 61 | node.get(path) 62 | } else { 63 | None 64 | } 65 | } 66 | None => { 67 | let node = self.nodes.iter().find(|m| path == &m.key || m.is_wildcard); 68 | if let Some(node) = node { 69 | node.handler.as_ref() 70 | } else { 71 | None 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | use crate::router::HandlerFn; 82 | 83 | #[test] 84 | fn test_insert_routes() { 85 | let mut root: Node = Node::new(""); 86 | root.insert("/", |_| Ok(())); 87 | root.insert("/foo", |_| Ok(())); 88 | root.insert("/foo/bar", |_| Ok(())); 89 | 90 | println!("{:?}", root); 91 | } 92 | 93 | #[test] 94 | fn test_get_route() { 95 | println!("{:?}", "foo".split_once('/')); 96 | println!("{:?}", "".split_once('/')); 97 | let mut root: Node = Node::new(""); 98 | root.insert("/", |_| Ok(())); 99 | root.insert("/foo/bar", |_| Ok(())); 100 | root.insert("/foo/foo", |_| Ok(())); 101 | root.insert("/users/{id}/profile", |_| Ok(())); 102 | root.insert("/companies/{id}/users/{userid}", |_| Ok(())); 103 | 104 | assert!(root.get("/").is_some()); 105 | assert!(root.get("/foo/bar").is_some()); 106 | assert!(root.get("/foo/foo").is_some()); 107 | assert!(root.get("/fooar").is_none()); 108 | assert!(root.get("/foo/bar/baz").is_none()); 109 | assert!(root.get("/fbar/baz").is_none()); 110 | assert!(root.get("/users/foo/profile").is_some()); 111 | assert!(root.get("/users/bar/profile").is_some()); 112 | assert!(root.get("/users/bar/asdf").is_none()); 113 | assert!(root.get("/companies/1234/asdf").is_none()); 114 | assert!(root.get("/companies/1234/users").is_none()); 115 | assert!(root.get("/companies/1234/users/foo").is_some()); 116 | 117 | println!("{:?}", root); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /http-sync-server/src/response.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Display, 3 | fs::File, 4 | io::{BufReader, BufWriter, Read, Result, Write}, 5 | net::TcpStream, 6 | }; 7 | 8 | pub struct Response { 9 | writer: BufWriter, 10 | } 11 | 12 | pub fn status_code(code: i32) -> i32 { 13 | match code { 14 | 200 | 400 | 404 => code, 15 | _ => 501, 16 | } 17 | } 18 | 19 | pub fn status(code: i32) -> &'static str { 20 | match code { 21 | 200 => "OK", 22 | 400 => "BAD REQUEST", 23 | 404 => "NOT FOUND", 24 | _ => "NOT IMPLEMENTED", 25 | } 26 | } 27 | 28 | impl Response { 29 | pub fn new(client: TcpStream) -> Self { 30 | Response { 31 | writer: BufWriter::new(client), 32 | } 33 | } 34 | 35 | pub fn write_status(&mut self, code: i32) -> Result { 36 | self.writer 37 | .write(format!("HTTP/1.0 {} {}\n", code, status(code)).as_bytes()) 38 | } 39 | 40 | pub fn write_header(&mut self, key: &str, val: V) -> Result { 41 | self.writer 42 | .write(format!("\"{}\": {}\n", key, val).as_bytes()) 43 | } 44 | 45 | pub fn write_body(&mut self, val: &[u8]) -> Result { 46 | self.write_header("content-length", val.len())?; 47 | self.writer.write(b"\n")?; 48 | self.writer.write(val) 49 | } 50 | 51 | pub fn parse_mime_type(&self, key: &str) -> &str { 52 | if let Some((_, ext)) = key.rsplit_once(".") { 53 | match ext { 54 | "html" => "text/html", 55 | "css" => "text/css", 56 | "js" => "text/javascript", 57 | "jpg" => "image/jpeg", 58 | "jpeg" => "image/jpeg", 59 | "png" => "image/png", 60 | "ico" => "image/x-icon", 61 | "pdf" => "application/pdf", 62 | _ => "text/plain", 63 | } 64 | } else { 65 | "text/plain" 66 | } 67 | } 68 | 69 | pub fn write_file(&mut self, path: &str) -> Result { 70 | let file = File::open(path)?; 71 | let mut buf = Vec::new(); 72 | let mut reader = BufReader::new(file); 73 | reader.read_to_end(&mut buf)?; 74 | 75 | self.write_header( 76 | "content-type", 77 | format!("{}; charset=UTF-8", self.parse_mime_type(path)), 78 | )?; 79 | self.write_body(&buf) 80 | } 81 | 82 | pub fn flush(&mut self) -> Result<()> { 83 | self.writer.flush() 84 | } 85 | 86 | pub fn sendfile(mut self, code: i32, path: &str) -> Result<()> { 87 | self.write_status(code)?; 88 | self.write_file(path)?; 89 | self.flush() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /http-sync-server/src/router.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | 3 | use crate::{node::Node, response::Response}; 4 | use std::{ 5 | collections::HashMap, 6 | io::{BufRead, BufReader, Result}, 7 | net::TcpStream, 8 | }; 9 | 10 | #[derive(PartialEq, Eq, Hash)] 11 | pub enum Method { 12 | GET, 13 | } 14 | 15 | pub type HandlerFn = fn(Response) -> Result<()>; 16 | 17 | pub struct Router { 18 | routes: HashMap>, 19 | } 20 | 21 | impl Router { 22 | pub fn new() -> Self { 23 | Router { 24 | routes: HashMap::new(), 25 | } 26 | } 27 | 28 | pub fn insert(&mut self, method: Method, path: &str, handler: HandlerFn) { 29 | let node = self.routes.entry(method).or_insert(Node::new("/")); 30 | node.insert(path, handler); 31 | } 32 | 33 | pub fn route_client(&self, client: TcpStream) -> Result<()> { 34 | let mut reader = BufReader::new(&client); 35 | let buf = reader.fill_buf()?; 36 | 37 | // read a single line (if one exists) 38 | let mut line = String::new(); 39 | let mut line_reader = BufReader::new(buf); 40 | let len = line_reader.read_line(&mut line)?; 41 | 42 | // consume bytes read from original reader 43 | reader.consume(len); 44 | if len == 0 { 45 | return self.bad_request(client); 46 | } 47 | 48 | let addr = client.peer_addr()?; 49 | println!( 50 | "{} @{addr} sent", 51 | format!("[{}]", std::process::id()).truecolor(200, 255, 136) 52 | ); 53 | println!("{}", line); 54 | 55 | let parts: Vec<&str> = line.split(" ").collect(); 56 | if parts.len() < 2 { 57 | self.bad_request(client) 58 | } else { 59 | match (parts[0], parts[1]) { 60 | ("GET", path) => self.handle(Method::GET, path, client), 61 | _ => self.bad_request(client), 62 | } 63 | } 64 | } 65 | 66 | pub fn handle(&self, method: Method, resource: &str, client: TcpStream) -> Result<()> { 67 | let res = Response::new(client); 68 | if let Some(node) = self.routes.get(&method) { 69 | if let Some(handler) = node.get(resource) { 70 | return handler(res); 71 | } 72 | } 73 | 74 | // default not found 75 | res.sendfile(404, "static/not_found.html") 76 | } 77 | 78 | pub fn bad_request(&self, client: TcpStream) -> Result<()> { 79 | let res = Response::new(client); 80 | res.sendfile(404, "static/not_found.html") 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /http-sync-server/src/routes.rs: -------------------------------------------------------------------------------- 1 | use crate::router::{Method, Router}; 2 | 3 | pub fn configure(router: &mut Router) { 4 | router.insert(Method::GET, "/", |res| { 5 | res.sendfile(200, "static/index.html") 6 | }); 7 | router.insert(Method::GET, "/todo", |res| { 8 | res.sendfile(200, "static/todo.html") 9 | }); 10 | router.insert(Method::GET, "/static/styles.css", |res| { 11 | res.sendfile(200, "static/styles.css") 12 | }); 13 | router.insert(Method::GET, "/favicon.ico", |res| { 14 | res.sendfile(200, "static/favicon.ico") 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /http-sync-server/src/thread_pool.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc; 2 | use std::sync::Arc; 3 | use std::sync::Mutex; 4 | 5 | use crate::worker::Task; 6 | use crate::worker::Worker; 7 | 8 | pub struct ThreadPool { 9 | workers: Vec, 10 | sender: mpsc::Sender, 11 | } 12 | 13 | impl Drop for ThreadPool { 14 | fn drop(&mut self) { 15 | for _ in &self.workers { 16 | self.sender.send(Task::Exit).unwrap(); 17 | } 18 | for worker in &mut self.workers { 19 | if let Some(handle) = worker.thread.take() { 20 | handle.join().unwrap(); 21 | } 22 | } 23 | } 24 | } 25 | 26 | impl ThreadPool { 27 | pub fn new(size: usize) -> Self { 28 | let mut workers = Vec::with_capacity(size); 29 | 30 | let (tx, rx) = mpsc::channel(); 31 | let rx = Arc::new(Mutex::new(rx)); 32 | 33 | for id in 0..size { 34 | workers.push(Worker::new(id, Arc::clone(&rx))); 35 | } 36 | 37 | ThreadPool { 38 | workers, 39 | sender: tx, 40 | } 41 | } 42 | 43 | pub fn execute(&self, f: F) 44 | where 45 | F: FnOnce() + Send + 'static, 46 | { 47 | let job = Box::new(f); 48 | self.sender.send(Task::New(job)).unwrap(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /http-sync-server/src/worker.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc; 2 | use std::sync::Arc; 3 | use std::sync::Mutex; 4 | use std::thread; 5 | 6 | use colored::Colorize; 7 | 8 | pub struct Worker { 9 | id: usize, 10 | pub thread: Option>, 11 | } 12 | 13 | pub type Job = Box; 14 | 15 | pub enum Task { 16 | New(Job), 17 | Exit, 18 | } 19 | 20 | impl Worker { 21 | pub fn new(id: usize, receiver: Arc>>) -> Self { 22 | let handle = thread::spawn(move || loop { 23 | let task = { 24 | let rx = receiver.lock().unwrap(); 25 | rx.recv().unwrap() 26 | }; 27 | 28 | println!( 29 | "{} {:?} @ worker {} received task", 30 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 31 | std::thread::current().id(), 32 | &id 33 | ); 34 | 35 | match task { 36 | Task::New(job) => job(), 37 | Task::Exit => break, 38 | } 39 | }); 40 | 41 | Worker { 42 | id, 43 | thread: Some(handle), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /http-sync-server/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyxtom/async-in-depth-rust-series/de70192cca232c8fa33058543d5f262ad28ee26a/http-sync-server/static/favicon.ico -------------------------------------------------------------------------------- /http-sync-server/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eu scelerisque felis imperdiet proin fermentum leo. Nunc sed velit dignissim sodales ut. Sagittis vitae et leo duis ut diam quam nulla. Adipiscing tristique risus nec feugiat in fermentum posuere urna nec. Magna eget est lorem ipsum dolor sit. Augue neque gravida in fermentum et sollicitudin ac orci phasellus. Sit amet facilisis magna etiam. Urna condimentum mattis pellentesque id nibh tortor. Arcu non sodales neque sodales. Blandit cursus risus at ultrices. Nunc faucibus a pellentesque sit amet porttitor. Non diam phasellus vestibulum lorem sed. Quisque id diam vel quam elementum pulvinar etiam non. Id faucibus nisl tincidunt eget nullam. Tincidunt eget nullam non nisi est. Sagittis id consectetur purus ut faucibus pulvinar. Non sodales neque sodales ut etiam sit amet nisl. Vivamus arcu felis bibendum ut tristique et egestas quis ipsum. 20 |

21 |

22 | Quis enim lobortis scelerisque fermentum dui faucibus in ornare. Dui ut ornare lectus sit amet est placerat in egestas. Enim ut tellus elementum sagittis vitae et leo. Vitae nunc sed velit dignissim sodales. Viverra adipiscing at in tellus integer. Ac feugiat sed lectus vestibulum mattis ullamcorper velit. Quam pellentesque nec nam aliquam sem et tortor. Nisl condimentum id venenatis a condimentum vitae sapien. Auctor augue mauris augue neque. Diam sollicitudin tempor id eu nisl nunc mi ipsum. Pharetra et ultrices neque ornare aenean euismod elementum nisi quis. Scelerisque fermentum dui faucibus in. 23 |

24 | Elementum tempus egestas sed sed risus pretium quam. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies leo. Ultricies mi quis hendrerit dolor magna eget. Fusce ut placerat orci nulla pellentesque. In est ante in nibh. Nisl pretium fusce id velit ut. Tellus pellentesque eu tincidunt tortor aliquam nulla facilisi. Nam libero justo laoreet sit amet cursus sit amet. Feugiat nisl pretium fusce id velit ut tortor pretium. Ut diam quam nulla porttitor massa. Pellentesque dignissim enim sit amet venenatis. Id volutpat lacus laoreet non. Sit amet aliquam id diam maecenas ultricies mi eget. Mi sit amet mauris commodo. Interdum posuere lorem ipsum dolor sit amet consectetur. Donec adipiscing tristique risus nec feugiat in fermentum posuere. Est pellentesque elit ullamcorper dignissim cras. Erat velit scelerisque in dictum non consectetur. 25 |

26 | Eros in cursus turpis massa tincidunt. Nibh venenatis cras sed felis. Volutpat commodo sed egestas egestas fringilla phasellus. Et magnis dis parturient montes nascetur ridiculus. Natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Ac turpis egestas maecenas pharetra convallis posuere. Mauris augue neque gravida in fermentum et sollicitudin. Ultricies mi quis hendrerit dolor magna eget. Risus commodo viverra maecenas accumsan lacus vel facilisis volutpat. Leo urna molestie at elementum. Senectus et netus et malesuada fames ac turpis egestas. Lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque. 27 |

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /http-sync-server/static/not_found.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

Not Found

19 |

Content Not Found

20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /http-sync-server/static/styles.css: -------------------------------------------------------------------------------- 1 | /** CSS RESET **/ 2 | html, body, div, span, applet, object, iframe, 3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 4 | a, abbr, acronym, address, big, cite, code, 5 | del, dfn, em, img, ins, kbd, q, s, samp, 6 | small, strike, strong, sub, sup, tt, var, 7 | b, u, i, center, 8 | dl, dt, dd, ol, ul, li, 9 | fieldset, form, label, legend, 10 | table, caption, tbody, tfoot, thead, tr, th, td, 11 | article, aside, canvas, details, embed, 12 | figure, figcaption, footer, header, hgroup, 13 | menu, nav, output, ruby, section, summary, 14 | time, mark, audio, video { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | font-size: 100%; 19 | font: inherit; 20 | vertical-align: baseline; 21 | } 22 | /* HTML5 display-role reset for older browsers */ 23 | article, aside, details, figcaption, figure, 24 | footer, header, hgroup, menu, nav, section { 25 | display: block; 26 | } 27 | body { 28 | line-height: 1.3; 29 | } 30 | ol, ul { 31 | list-style: none; 32 | } 33 | blockquote, q { 34 | quotes: none; 35 | } 36 | blockquote:before, blockquote:after, 37 | q:before, q:after { 38 | content: ''; 39 | content: none; 40 | } 41 | table { 42 | border-collapse: collapse; 43 | border-spacing: 0; 44 | } 45 | 46 | /** Styles **/ 47 | .container { 48 | min-width: 500px; 49 | max-width: 900px; 50 | margin: 20px auto; 51 | } 52 | .title { 53 | font-size: 0.9rem; 54 | font-weight: bold; 55 | } 56 | .nav { 57 | margin: 50px 30px; 58 | display: flex; 59 | flex-direction: row; 60 | } 61 | .nav .title { 62 | margin-right: 50px; 63 | flex: 1; 64 | } 65 | .links { 66 | display: flex; 67 | flex-direction: row; 68 | } 69 | .links li { 70 | font-size: 0.9rem; 71 | } 72 | .links li::after { 73 | padding: 0px 15px; 74 | content: '|'; 75 | display: inline; 76 | } 77 | .links li:last-child::after { 78 | padding: 0px; 79 | content: ''; 80 | display: inline; 81 | } 82 | a { 83 | font-weight: 600; 84 | color: #224466; 85 | text-decoration: none; 86 | transition: all 0.2s ease-in-out; 87 | } 88 | a:hover { 89 | color: #7799aa; 90 | } 91 | 92 | .body { 93 | border-top: 40px solid #f9f9f9; 94 | margin: 0px 20px; 95 | background: #fefefe; 96 | flex-direction: column; 97 | padding: 30px 40px; 98 | box-shadow: 2px 2px 4px 0px #eee, 99 | 4px 4px 12px 4px #eee; 100 | } 101 | .body p { 102 | margin: 20px; 103 | } 104 | -------------------------------------------------------------------------------- /http-sync-server/static/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

todo!

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /http-sync-server/static/unimplemented.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

Bad Request

19 |

Request is not implemented

20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /httpserver-async-objects/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyxtom/async-in-depth-rust-series/de70192cca232c8fa33058543d5f262ad28ee26a/httpserver-async-objects/.DS_Store -------------------------------------------------------------------------------- /httpserver-async-objects/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /httpserver-async-objects/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "atty" 7 | version = "0.2.14" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 10 | dependencies = [ 11 | "hermit-abi", 12 | "libc", 13 | "winapi", 14 | ] 15 | 16 | [[package]] 17 | name = "cc" 18 | version = "1.0.73" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 21 | 22 | [[package]] 23 | name = "cfg-if" 24 | version = "1.0.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 27 | 28 | [[package]] 29 | name = "colored" 30 | version = "2.0.0" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" 33 | dependencies = [ 34 | "atty", 35 | "lazy_static", 36 | "winapi", 37 | ] 38 | 39 | [[package]] 40 | name = "hermit-abi" 41 | version = "0.1.19" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 44 | dependencies = [ 45 | "libc", 46 | ] 47 | 48 | [[package]] 49 | name = "http-sync-server" 50 | version = "0.1.0" 51 | dependencies = [ 52 | "colored", 53 | "libc", 54 | "polling", 55 | ] 56 | 57 | [[package]] 58 | name = "lazy_static" 59 | version = "1.4.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 62 | 63 | [[package]] 64 | name = "libc" 65 | version = "0.2.126" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 68 | 69 | [[package]] 70 | name = "log" 71 | version = "0.4.17" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 74 | dependencies = [ 75 | "cfg-if", 76 | ] 77 | 78 | [[package]] 79 | name = "polling" 80 | version = "2.2.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" 83 | dependencies = [ 84 | "cfg-if", 85 | "libc", 86 | "log", 87 | "wepoll-ffi", 88 | "winapi", 89 | ] 90 | 91 | [[package]] 92 | name = "wepoll-ffi" 93 | version = "0.1.2" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 96 | dependencies = [ 97 | "cc", 98 | ] 99 | 100 | [[package]] 101 | name = "winapi" 102 | version = "0.3.9" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 105 | dependencies = [ 106 | "winapi-i686-pc-windows-gnu", 107 | "winapi-x86_64-pc-windows-gnu", 108 | ] 109 | 110 | [[package]] 111 | name = "winapi-i686-pc-windows-gnu" 112 | version = "0.4.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 115 | 116 | [[package]] 117 | name = "winapi-x86_64-pc-windows-gnu" 118 | version = "0.4.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 121 | -------------------------------------------------------------------------------- /httpserver-async-objects/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-sync-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | colored = "2.0.0" 10 | libc = "0.2.126" 11 | polling = "2.2.0" 12 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/async_io/client.rs: -------------------------------------------------------------------------------- 1 | use super::{event_handler::EventHandler, reactor::Reactor}; 2 | use crate::{response::Response, router::Router}; 3 | use colored::Colorize; 4 | use polling::Event; 5 | use std::{ 6 | net::TcpStream, 7 | os::unix::prelude::AsRawFd, 8 | sync::{Arc, Mutex, RwLock}, 9 | thread::JoinHandle, 10 | time::Duration, 11 | }; 12 | 13 | pub struct ClientRequest { 14 | pub client: Option, 15 | pub router: Arc, 16 | pub fd: usize, 17 | pub state: Arc>>, 18 | pub wait_handle: Option>, 19 | pub reactor: Arc>, 20 | } 21 | 22 | pub enum ClientState { 23 | Waiting, 24 | ReadRequest, 25 | WaitingReadFile, 26 | ReadReponseFile(i32, String), 27 | WriteResponse(i32, Vec, String), 28 | WritingResponse, 29 | Close(TcpStream), 30 | Closed, 31 | } 32 | 33 | impl ClientRequest { 34 | pub fn new(client: TcpStream, router: Arc, reactor: Arc>) -> Self { 35 | let fd = client.as_raw_fd() as usize; 36 | ClientRequest { 37 | client: Some(client), 38 | router, 39 | fd, 40 | state: Arc::new(Mutex::new(None)), 41 | wait_handle: None, 42 | reactor, 43 | } 44 | } 45 | 46 | fn log(&self, message: &str) { 47 | println!( 48 | "{} {:?} client {} - {}", 49 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 50 | std::thread::current().id(), 51 | self.name(), 52 | message 53 | ); 54 | } 55 | 56 | fn update_state(&mut self, state: ClientState) { 57 | self.state.lock().unwrap().replace(state); 58 | } 59 | 60 | fn initialize(&mut self) { 61 | if let Some(client) = self.client.as_ref() { 62 | { 63 | let reactor = self.reactor.write().unwrap(); 64 | reactor.add(client, Event::readable(self.fd)); 65 | } 66 | self.update_state(ClientState::Waiting); // Pending 67 | } else { 68 | self.update_state(ClientState::Closed); 69 | } 70 | } 71 | 72 | fn read_request(&mut self) { 73 | if let Some(client) = self.client.as_ref() { 74 | let (code, path) = self.router.read_request(client); 75 | self.update_state(ClientState::ReadReponseFile(code, path)); // Ready 76 | let mut reactor = self.reactor.write().unwrap(); 77 | reactor.schedule(self.fd); 78 | } else { 79 | self.update_state(ClientState::Closed); 80 | } 81 | } 82 | 83 | fn read_response_file(&mut self, code: i32, path: String) { 84 | let router = self.router.clone(); 85 | let fd = self.fd; 86 | let name = self.name(); 87 | self.log(&format!("scheduling file read {}", path)); 88 | let state_arc = self.state.clone(); 89 | let reactor = self.reactor.clone(); 90 | let handle = std::thread::spawn(move || { 91 | let contents = router.read_response_file(path.clone()); 92 | 93 | if "static/index.html" == &path { 94 | println!( 95 | "{} {:?} client {} /index is requested, simulating long file read times...", 96 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 97 | std::thread::current().id(), 98 | name 99 | ); 100 | std::thread::sleep(Duration::from_millis(5000)); 101 | } 102 | 103 | state_arc 104 | .lock() 105 | .unwrap() 106 | .replace(ClientState::WriteResponse(code, contents, path.clone())); 107 | 108 | println!( 109 | "{} {:?} client {} file read done, wakeup! state to -> WriteResponse", 110 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 111 | std::thread::current().id(), 112 | name 113 | ); 114 | 115 | // wake up! (if we are waiting on I/O blocking, wake that up) 116 | // if not, then continue and reschedule this task 117 | reactor.read().unwrap().notify(); 118 | reactor.write().unwrap().schedule(fd); 119 | }); 120 | self.wait_handle = Some(handle); 121 | self.update_state(ClientState::WaitingReadFile); // Pending 122 | } 123 | 124 | fn write_response(&mut self, code: i32, contents: Vec, path: String) { 125 | if let Some(handle) = self.wait_handle.take() { 126 | handle.join().unwrap(); 127 | } 128 | let name = self.name(); 129 | if let Some(client) = self.client.take() { 130 | self.update_state(ClientState::WritingResponse); // Pending 131 | let fd = self.fd; 132 | self.log("scheduling response write thread..."); 133 | let state_arc = self.state.clone(); 134 | let reactor = self.reactor.clone(); 135 | let handle = std::thread::spawn(move || { 136 | let mut response = Response::new(client); 137 | response.send_file_contents(code, &path, contents).unwrap(); 138 | 139 | println!( 140 | "{} {:?} {}, response write done, wakeup! state change -> Close", 141 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 142 | std::thread::current().id(), 143 | name 144 | ); 145 | 146 | // set close state 147 | let client = response.into_inner().unwrap(); 148 | state_arc 149 | .lock() 150 | .unwrap() 151 | .replace(ClientState::Close(client)); 152 | 153 | // wake up! (if we are waiting on I/O blocking, wake that up) 154 | // if not, then continue and reschedule this task 155 | reactor.read().unwrap().notify(); 156 | reactor.write().unwrap().schedule(fd); 157 | }); 158 | self.wait_handle = Some(handle); 159 | } else { 160 | self.update_state(ClientState::Closed); 161 | } 162 | } 163 | 164 | fn close(&mut self, client: TcpStream) { 165 | if let Some(handle) = self.wait_handle.take() { 166 | handle.join().unwrap(); 167 | } 168 | self.log("request done, joining final thread and exiting\n---\n"); 169 | self.update_state(ClientState::Closed); 170 | self.reactor.write().unwrap().remove(self.fd, &client); 171 | } 172 | } 173 | 174 | impl EventHandler for ClientRequest { 175 | fn poll(&mut self) { 176 | let state = self.state.lock().unwrap().take(); 177 | match state { 178 | None => self.initialize(), 179 | Some(ClientState::ReadRequest) => self.read_request(), 180 | Some(ClientState::ReadReponseFile(code, path)) => self.read_response_file(code, path), 181 | Some(ClientState::WriteResponse(code, contents, path)) => { 182 | self.write_response(code, contents, path) 183 | } 184 | Some(ClientState::Close(client)) => self.close(client), 185 | _ => {} 186 | } 187 | } 188 | 189 | fn event(&mut self, event: &polling::Event) { 190 | let state = self.state.lock().unwrap().take(); 191 | match state { 192 | Some(ClientState::Waiting) => { 193 | if event.readable { 194 | let name = self.name(); 195 | println!( 196 | "{} {:?} client received event! updating state to ReadRequest client {}", 197 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 198 | std::thread::current().id(), 199 | name 200 | ); 201 | self.update_state(ClientState::ReadRequest); 202 | let mut reactor = self.reactor.write().unwrap(); 203 | reactor.schedule(self.fd); 204 | } 205 | } 206 | Some(s) => { 207 | self.update_state(s); 208 | } 209 | None => {} 210 | } 211 | } 212 | 213 | fn matches(&self, event: &polling::Event) -> bool { 214 | self.fd == event.key 215 | } 216 | 217 | fn id(&self) -> usize { 218 | self.fd 219 | } 220 | 221 | fn name(&self) -> String { 222 | if let Some(client) = self.client.as_ref() { 223 | format!("TcpStream://{}", client.peer_addr().unwrap().to_string()) 224 | } else { 225 | format!("{}", self.id()) 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/async_io/event_handler.rs: -------------------------------------------------------------------------------- 1 | use polling::Event; 2 | 3 | pub trait EventHandler { 4 | fn id(&self) -> usize; 5 | fn name(&self) -> String; 6 | fn poll(&mut self); 7 | fn event(&mut self, event: &Event); 8 | fn matches(&self, event: &Event) -> bool; 9 | } 10 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/async_io/event_loop.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | io::Result, 4 | pin::Pin, 5 | sync::{Arc, RwLock}, 6 | }; 7 | 8 | use colored::Colorize; 9 | 10 | use super::event_handler::EventHandler; 11 | use super::reactor::Reactor; 12 | 13 | pub struct EventLoop { 14 | pub reactor: Arc>, 15 | pub sources: HashMap>, 16 | } 17 | 18 | impl EventLoop { 19 | pub fn new(reactor: Arc>) -> Self { 20 | EventLoop { 21 | reactor, 22 | sources: HashMap::new(), 23 | } 24 | } 25 | 26 | pub fn run(&mut self) -> Result<()> { 27 | loop { 28 | // process ready queue 29 | // 1. keep processing as long as we aren't waiting for I/O 30 | loop { 31 | if let Some(id) = { 32 | let mut reactor_lock = self.reactor.write().unwrap(); 33 | reactor_lock.tasks.pop() 34 | } { 35 | if let Some(source) = self.sources.get_mut(&id) { 36 | source.poll(); 37 | } 38 | } 39 | 40 | // 2. handle unregister events 41 | self.handle_register(); 42 | 43 | // if there are no more ready events to process, wait for I/O 44 | if self.reactor.read().unwrap().tasks.is_empty() { 45 | break; 46 | } 47 | } 48 | 49 | // 3. handle unregister events 50 | self.handle_unregister(); 51 | 52 | // 4. when all is done wait for I/O 53 | // wait for i/o 54 | // i/o events will requeue associated pending tasks 55 | self.wait_for_io()?; 56 | } 57 | } 58 | 59 | fn wait_for_io(&mut self) -> std::io::Result<()> { 60 | println!( 61 | "{} {:?} waiting for I/O", 62 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 63 | std::thread::current().id() 64 | ); 65 | let mut events = Vec::new(); 66 | { 67 | let cx = self.reactor.read().unwrap(); 68 | cx.poller.wait(&mut events, None)?; 69 | } 70 | for ev in &events { 71 | if let Some(source) = self.sources.get_mut(&ev.key) { 72 | if source.as_ref().matches(ev) { 73 | source.event(ev); 74 | } 75 | } 76 | } 77 | 78 | Ok(()) 79 | } 80 | 81 | fn handle_register(&mut self) { 82 | let mut cx = self.reactor.write().unwrap(); 83 | while let Some((id, source)) = cx.register.pop() { 84 | println!( 85 | "{} {:?} reactor registered source Fd({}) @ {}", 86 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 87 | std::thread::current().id(), 88 | &id, 89 | source.as_ref().name() 90 | ); 91 | self.sources.insert(id, source); 92 | } 93 | } 94 | 95 | fn handle_unregister(&mut self) { 96 | let mut cx = self.reactor.write().unwrap(); 97 | while let Some(id) = cx.unregister.pop() { 98 | println!( 99 | "{} {:?} reactor removed source Fd({})", 100 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 101 | std::thread::current().id(), 102 | &id 103 | ); 104 | self.sources.remove(&id); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/async_io/listener.rs: -------------------------------------------------------------------------------- 1 | use crate::router::Router; 2 | 3 | use super::client::ClientRequest; 4 | use super::{event_handler::EventHandler, reactor::Reactor}; 5 | use polling::Event; 6 | use std::sync::{Arc, RwLock}; 7 | use std::{ 8 | net::{TcpListener, TcpStream}, 9 | os::unix::prelude::AsRawFd, 10 | }; 11 | 12 | pub enum ListenerState { 13 | Waiting, 14 | Accepting(TcpStream), 15 | } 16 | 17 | pub struct AsyncTcpListener { 18 | pub listener: TcpListener, 19 | pub router: Arc, 20 | pub fd: usize, 21 | pub state: Option, 22 | pub reactor: Arc>, 23 | } 24 | 25 | impl AsyncTcpListener { 26 | pub fn new(listener: TcpListener, router: Arc, reactor: Arc>) -> Self { 27 | listener.set_nonblocking(true).unwrap(); 28 | 29 | // add listener to the reactor 30 | let fd = listener.as_raw_fd() as usize; 31 | { 32 | let reactor = reactor.write().unwrap(); 33 | reactor.add(&listener, Event::readable(fd)); 34 | } 35 | 36 | // initialize in a waiting state 37 | AsyncTcpListener { 38 | listener, 39 | router, 40 | fd, 41 | state: Some(ListenerState::Waiting), 42 | reactor, 43 | } 44 | } 45 | } 46 | 47 | impl EventHandler for AsyncTcpListener { 48 | fn poll(&mut self) { 49 | match self.state.take() { 50 | Some(ListenerState::Accepting(client)) => { 51 | // modify interest in read event again 52 | let mut reactor = self.reactor.write().unwrap(); 53 | reactor.modify(&self.listener, Event::readable(self.fd)); 54 | 55 | self.state.replace(ListenerState::Waiting); 56 | // register new client on poller 57 | let client = ClientRequest::new(client, self.router.clone(), self.reactor.clone()); 58 | reactor.register(client.fd, client); 59 | } 60 | _ => {} 61 | } 62 | } 63 | 64 | fn event(&mut self, event: &Event) { 65 | if event.readable { 66 | let (client, addr) = self.listener.accept().unwrap(); 67 | self.state.replace(ListenerState::Accepting(client)); 68 | // reschedule 69 | let mut reactor = self.reactor.write().unwrap(); 70 | reactor.schedule(self.fd); 71 | } 72 | } 73 | 74 | fn matches(&self, event: &Event) -> bool { 75 | event.key == self.fd 76 | } 77 | 78 | fn name(&self) -> String { 79 | format!( 80 | "TcpListener://{}", 81 | self.listener.local_addr().unwrap().to_string() 82 | ) 83 | } 84 | 85 | fn id(&self) -> usize { 86 | self.fd 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/async_io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod event_handler; 3 | pub mod event_loop; 4 | pub mod listener; 5 | pub mod reactor; 6 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/async_io/reactor.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | 3 | use super::event_handler::EventHandler; 4 | use polling::{Event, Poller, Source}; 5 | 6 | pub struct Reactor { 7 | pub tasks: Vec, 8 | pub register: Vec<(usize, Box)>, 9 | pub unregister: Vec, 10 | pub poller: Poller, 11 | } 12 | 13 | impl Reactor { 14 | pub fn new() -> Self { 15 | Reactor { 16 | tasks: Vec::new(), 17 | register: Vec::new(), 18 | unregister: Vec::new(), 19 | poller: Poller::new().unwrap(), 20 | } 21 | } 22 | 23 | pub fn schedule(&mut self, id: usize) { 24 | self.tasks.push(id); 25 | } 26 | 27 | pub fn notify(&self) { 28 | // ensure poller is not blocking on wait 29 | // now that we have something to process 30 | self.poller.notify().unwrap(); 31 | } 32 | 33 | pub fn register(&mut self, id: usize, client: T) 34 | where 35 | T: EventHandler + Send + Sync + 'static, 36 | { 37 | self.schedule(id); 38 | self.register.push((id, Box::new(client))); 39 | } 40 | 41 | pub fn modify(&self, source: impl Source, event: Event) { 42 | self.poller.modify(source, event).unwrap(); 43 | } 44 | 45 | pub fn remove(&mut self, id: usize, source: impl Source) { 46 | self.poller.delete(source).unwrap(); 47 | self.unregister.push(id); 48 | self.schedule(id); 49 | } 50 | 51 | pub fn add(&self, source: impl Source, event: Event) { 52 | self.poller.add(source, event).unwrap(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature(trait_alias)] 2 | 3 | mod async_io; 4 | mod node; 5 | mod response; 6 | mod router; 7 | mod routes; 8 | 9 | use colored::*; 10 | use router::Router; 11 | use std::io::Result; 12 | use std::net::TcpListener; 13 | use std::sync::{Arc, RwLock}; 14 | 15 | use crate::async_io::event_loop::EventLoop; 16 | use crate::async_io::listener::AsyncTcpListener; 17 | use crate::async_io::reactor::Reactor; 18 | 19 | fn main() -> Result<()> { 20 | let port = std::env::var("PORT").unwrap_or(String::from("7000")); 21 | let listener = TcpListener::bind(format!("127.0.0.1:{port}"))?; 22 | 23 | let mut router = Router::new(); 24 | routes::configure(&mut router); 25 | 26 | let router = Arc::new(router); 27 | let reactor = Arc::new(RwLock::new(Reactor::new())); 28 | let listener = AsyncTcpListener::new(listener, router, reactor.clone()); 29 | reactor.write().unwrap().register(listener.fd, listener); 30 | 31 | println!( 32 | "{} server listening on 127.0.0.1:{}", 33 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 34 | port 35 | ); 36 | 37 | let mut event_loop = EventLoop::new(reactor); 38 | event_loop.run() 39 | } 40 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/node.rs: -------------------------------------------------------------------------------- 1 | use crate::router::HandlerFn; 2 | 3 | pub struct Node { 4 | pub nodes: Vec, 5 | pub key: String, 6 | pub handler: Option, 7 | } 8 | 9 | impl Node { 10 | pub fn new(key: &str) -> Self { 11 | Node { 12 | nodes: Vec::new(), 13 | key: String::from(key), 14 | handler: None, 15 | } 16 | } 17 | 18 | pub fn insert(&mut self, path: &str, f: HandlerFn) { 19 | match path.split_once('/') { 20 | Some((root, "")) => { 21 | self.key = String::from(root); 22 | self.handler = Some(f); 23 | } 24 | Some(("", path)) => self.insert(path, f), 25 | Some((root, path)) => { 26 | let node = self.nodes.iter_mut().find(|m| root == &m.key); 27 | match node { 28 | Some(n) => n.insert(path, f), 29 | None => { 30 | let mut node = Node::new(root); 31 | node.insert(path, f); 32 | self.nodes.push(node); 33 | } 34 | } 35 | } 36 | None => { 37 | let mut node = Node::new(path); 38 | node.handler = Some(f); 39 | self.nodes.push(node); 40 | } 41 | } 42 | } 43 | 44 | pub fn get(&self, path: &str) -> Option<&HandlerFn> { 45 | match path.split_once('/') { 46 | Some((root, "")) => { 47 | if root == &self.key { 48 | self.handler.as_ref() 49 | } else { 50 | None 51 | } 52 | } 53 | Some(("", path)) => self.get(path), 54 | Some((root, path)) => { 55 | let node = self.nodes.iter().find(|m| root == &m.key); 56 | if let Some(node) = node { 57 | node.get(path) 58 | } else { 59 | None 60 | } 61 | } 62 | None => { 63 | let node = self.nodes.iter().find(|m| path == &m.key); 64 | if let Some(node) = node { 65 | node.handler.as_ref() 66 | } else { 67 | None 68 | } 69 | } 70 | } 71 | } 72 | } 73 | /* 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn test_insert_routes() { 80 | let mut root = Node::new(""); 81 | root.insert("/", |_| Ok(())); 82 | root.insert("/foo", |_| Ok(())); 83 | root.insert("/foo/bar", |_| Ok(())); 84 | 85 | println!("{:?}", root); 86 | } 87 | 88 | #[test] 89 | fn test_get_route() { 90 | println!("{:?}", "foo".split_once('/')); 91 | println!("{:?}", "".split_once('/')); 92 | let mut root = Node::new(""); 93 | root.insert("/", |_| Ok(())); 94 | root.insert("/foo/bar", |_| Ok(())); 95 | root.insert("/foo/foo", |_| Ok(())); 96 | root.insert("/users/{id}/profile", |_| Ok(())); 97 | root.insert("/companies/{id}/users/{userid}", |_| Ok(())); 98 | 99 | assert!(root.get("/").is_some()); 100 | assert!(root.get("/foo/bar").is_some()); 101 | assert!(root.get("/foo/foo").is_some()); 102 | assert!(root.get("/fooar").is_none()); 103 | assert!(root.get("/foo/bar/baz").is_none()); 104 | assert!(root.get("/fbar/baz").is_none()); 105 | assert!(root.get("/users/foo/profile").is_some()); 106 | assert!(root.get("/users/bar/profile").is_some()); 107 | assert!(root.get("/users/bar/asdf").is_none()); 108 | assert!(root.get("/companies/1234/asdf").is_none()); 109 | assert!(root.get("/companies/1234/users").is_none()); 110 | assert!(root.get("/companies/1234/users/foo").is_some()); 111 | 112 | println!("{:?}", root); 113 | } 114 | } 115 | */ 116 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/response.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::Display, 3 | io::{BufWriter, Result, Write}, 4 | net::TcpStream, 5 | }; 6 | 7 | pub struct Response { 8 | writer: BufWriter, 9 | } 10 | 11 | pub fn status_code(code: i32) -> i32 { 12 | match code { 13 | 200 | 400 | 404 => code, 14 | _ => 501, 15 | } 16 | } 17 | 18 | pub fn status(code: i32) -> &'static str { 19 | match code { 20 | 200 => "OK", 21 | 400 => "BAD REQUEST", 22 | 404 => "NOT FOUND", 23 | _ => "NOT IMPLEMENTED", 24 | } 25 | } 26 | 27 | impl Response { 28 | pub fn new(client: TcpStream) -> Self { 29 | Response { 30 | writer: BufWriter::new(client), 31 | } 32 | } 33 | 34 | pub fn into_inner(self) -> Result { 35 | match self.writer.into_inner() { 36 | Ok(s) => Ok(s), 37 | Err(e) => Err(e.into_error()), 38 | } 39 | } 40 | 41 | pub fn write_status(&mut self, code: i32) -> Result { 42 | let code = status_code(code); 43 | self.writer 44 | .write(format!("HTTP/1.0 {} {}\n", code, status(code)).as_bytes()) 45 | } 46 | 47 | pub fn write_header(&mut self, key: &str, val: V) -> Result { 48 | self.writer 49 | .write(format!("\"{}\": {}\n", key, val).as_bytes()) 50 | } 51 | 52 | pub fn write_body(&mut self, val: &[u8]) -> Result { 53 | self.write_header("content-length", val.len())?; 54 | self.writer.write(b"\n")?; 55 | self.writer.write(val) 56 | } 57 | 58 | pub fn parse_mime_type(&self, key: &str) -> &str { 59 | if let Some((_, ext)) = key.rsplit_once(".") { 60 | match ext { 61 | "html" => "text/html", 62 | "css" => "text/css", 63 | "js" => "text/javascript", 64 | "jpg" => "image/jpeg", 65 | "jpeg" => "image/jpeg", 66 | "png" => "image/png", 67 | "ico" => "image/x-icon", 68 | "pdf" => "application/pdf", 69 | _ => "text/plain", 70 | } 71 | } else { 72 | "text/plain" 73 | } 74 | } 75 | 76 | pub fn send_file_contents(&mut self, code: i32, path: &str, contents: Vec) -> Result<()> { 77 | self.write_status(code)?; 78 | let len = contents.len(); 79 | let mime_type = self.parse_mime_type(path); 80 | self.write_header("content-type", format!("{}; charset=UTF-8", mime_type))?; 81 | self.write_header("content-length", len)?; 82 | self.write_body(&contents)?; 83 | self.flush() 84 | } 85 | 86 | pub fn flush(&mut self) -> Result<()> { 87 | self.writer.flush() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/router.rs: -------------------------------------------------------------------------------- 1 | use colored::Colorize; 2 | 3 | use crate::node::Node; 4 | use std::{ 5 | collections::HashMap, 6 | io::{BufRead, BufReader, Read}, 7 | net::TcpStream, 8 | }; 9 | 10 | #[derive(PartialEq, Eq, Hash)] 11 | pub enum Method { 12 | GET, 13 | } 14 | 15 | pub type HandlerFn = fn() -> (i32, String); 16 | 17 | pub struct Router { 18 | routes: HashMap, 19 | } 20 | 21 | impl Router { 22 | pub fn new() -> Self { 23 | Router { 24 | routes: HashMap::new(), 25 | } 26 | } 27 | 28 | pub fn insert(&mut self, method: Method, path: &str, handler: HandlerFn) { 29 | let node = self.routes.entry(method).or_insert(Node::new("/")); 30 | node.insert(path, handler); 31 | } 32 | 33 | pub fn read_response_file(&self, path: String) -> Vec { 34 | let file = std::fs::File::open(path).unwrap(); 35 | let mut buf = Vec::new(); 36 | let mut reader = std::io::BufReader::new(file); 37 | reader.read_to_end(&mut buf).unwrap(); 38 | buf 39 | } 40 | 41 | pub fn read_request(&self, client: &TcpStream) -> (i32, String) { 42 | let mut reader = BufReader::new(client); 43 | let buf = reader.fill_buf().unwrap(); 44 | 45 | // read a single line (if one exists) 46 | let mut line = String::new(); 47 | let mut line_reader = BufReader::new(buf); 48 | let len = line_reader.read_line(&mut line).unwrap(); 49 | 50 | println!( 51 | "{} {:?} client {} requested: \n{}", 52 | format!("[{}]", std::process::id()).truecolor(0, 255, 136), 53 | std::thread::current().id(), 54 | client.peer_addr().unwrap().to_string(), 55 | &line 56 | ); 57 | 58 | // consume bytes read from original reader 59 | reader.consume(len); 60 | let parts: Vec<&str> = line.split(" ").collect(); 61 | if parts.len() < 2 { 62 | self.bad_request(client) 63 | } else { 64 | match (parts[0], parts[1]) { 65 | ("GET", path) => self.handle(Method::GET, path, client), 66 | _ => (404, String::from("static/not_found.html")), 67 | } 68 | } 69 | } 70 | 71 | pub fn handle(&self, method: Method, resource: &str, client: &TcpStream) -> (i32, String) { 72 | if let Some(node) = self.routes.get(&method) { 73 | if let Some(handler) = node.get(resource) { 74 | return handler(); 75 | } 76 | } 77 | 78 | // default not found 79 | self.bad_request(client) 80 | } 81 | 82 | pub fn bad_request(&self, _client: &TcpStream) -> (i32, String) { 83 | (404, String::from("static/not_found.html")) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /httpserver-async-objects/src/routes.rs: -------------------------------------------------------------------------------- 1 | use crate::router::{Method, Router}; 2 | 3 | pub fn configure(router: &mut Router) { 4 | router.insert(Method::GET, "/", || { 5 | (200, String::from("static/index.html")) 6 | }); 7 | router.insert(Method::GET, "/todo", || { 8 | (200, String::from("static/todo.html")) 9 | }); 10 | router.insert(Method::GET, "/static/styles.css", || { 11 | (200, String::from("static/styles.css")) 12 | }); 13 | router.insert(Method::GET, "/favicon.ico", || { 14 | (200, String::from("static/favicon.ico")) 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /httpserver-async-objects/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyxtom/async-in-depth-rust-series/de70192cca232c8fa33058543d5f262ad28ee26a/httpserver-async-objects/static/favicon.ico -------------------------------------------------------------------------------- /httpserver-async-objects/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Eu scelerisque felis imperdiet proin fermentum leo. Nunc sed velit dignissim sodales ut. Sagittis vitae et leo duis ut diam quam nulla. Adipiscing tristique risus nec feugiat in fermentum posuere urna nec. Magna eget est lorem ipsum dolor sit. Augue neque gravida in fermentum et sollicitudin ac orci phasellus. Sit amet facilisis magna etiam. Urna condimentum mattis pellentesque id nibh tortor. Arcu non sodales neque sodales. Blandit cursus risus at ultrices. Nunc faucibus a pellentesque sit amet porttitor. Non diam phasellus vestibulum lorem sed. Quisque id diam vel quam elementum pulvinar etiam non. Id faucibus nisl tincidunt eget nullam. Tincidunt eget nullam non nisi est. Sagittis id consectetur purus ut faucibus pulvinar. Non sodales neque sodales ut etiam sit amet nisl. Vivamus arcu felis bibendum ut tristique et egestas quis ipsum. 20 |

21 |

22 | Quis enim lobortis scelerisque fermentum dui faucibus in ornare. Dui ut ornare lectus sit amet est placerat in egestas. Enim ut tellus elementum sagittis vitae et leo. Vitae nunc sed velit dignissim sodales. Viverra adipiscing at in tellus integer. Ac feugiat sed lectus vestibulum mattis ullamcorper velit. Quam pellentesque nec nam aliquam sem et tortor. Nisl condimentum id venenatis a condimentum vitae sapien. Auctor augue mauris augue neque. Diam sollicitudin tempor id eu nisl nunc mi ipsum. Pharetra et ultrices neque ornare aenean euismod elementum nisi quis. Scelerisque fermentum dui faucibus in. 23 |

24 | Elementum tempus egestas sed sed risus pretium quam. Dis parturient montes nascetur ridiculus mus mauris vitae ultricies leo. Ultricies mi quis hendrerit dolor magna eget. Fusce ut placerat orci nulla pellentesque. In est ante in nibh. Nisl pretium fusce id velit ut. Tellus pellentesque eu tincidunt tortor aliquam nulla facilisi. Nam libero justo laoreet sit amet cursus sit amet. Feugiat nisl pretium fusce id velit ut tortor pretium. Ut diam quam nulla porttitor massa. Pellentesque dignissim enim sit amet venenatis. Id volutpat lacus laoreet non. Sit amet aliquam id diam maecenas ultricies mi eget. Mi sit amet mauris commodo. Interdum posuere lorem ipsum dolor sit amet consectetur. Donec adipiscing tristique risus nec feugiat in fermentum posuere. Est pellentesque elit ullamcorper dignissim cras. Erat velit scelerisque in dictum non consectetur. 25 |

26 | Eros in cursus turpis massa tincidunt. Nibh venenatis cras sed felis. Volutpat commodo sed egestas egestas fringilla phasellus. Et magnis dis parturient montes nascetur ridiculus. Natoque penatibus et magnis dis parturient montes nascetur ridiculus mus. Ut enim blandit volutpat maecenas volutpat blandit aliquam etiam. Ac turpis egestas maecenas pharetra convallis posuere. Mauris augue neque gravida in fermentum et sollicitudin. Ultricies mi quis hendrerit dolor magna eget. Risus commodo viverra maecenas accumsan lacus vel facilisis volutpat. Leo urna molestie at elementum. Senectus et netus et malesuada fames ac turpis egestas. Lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque. 27 |

28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /httpserver-async-objects/static/not_found.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

Not Found

19 |

Content Not Found

20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /httpserver-async-objects/static/styles.css: -------------------------------------------------------------------------------- 1 | /** CSS RESET **/ 2 | html, body, div, span, applet, object, iframe, 3 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 4 | a, abbr, acronym, address, big, cite, code, 5 | del, dfn, em, img, ins, kbd, q, s, samp, 6 | small, strike, strong, sub, sup, tt, var, 7 | b, u, i, center, 8 | dl, dt, dd, ol, ul, li, 9 | fieldset, form, label, legend, 10 | table, caption, tbody, tfoot, thead, tr, th, td, 11 | article, aside, canvas, details, embed, 12 | figure, figcaption, footer, header, hgroup, 13 | menu, nav, output, ruby, section, summary, 14 | time, mark, audio, video { 15 | margin: 0; 16 | padding: 0; 17 | border: 0; 18 | font-size: 100%; 19 | font: inherit; 20 | vertical-align: baseline; 21 | } 22 | /* HTML5 display-role reset for older browsers */ 23 | article, aside, details, figcaption, figure, 24 | footer, header, hgroup, menu, nav, section { 25 | display: block; 26 | } 27 | body { 28 | line-height: 1.3; 29 | } 30 | ol, ul { 31 | list-style: none; 32 | } 33 | blockquote, q { 34 | quotes: none; 35 | } 36 | blockquote:before, blockquote:after, 37 | q:before, q:after { 38 | content: ''; 39 | content: none; 40 | } 41 | table { 42 | border-collapse: collapse; 43 | border-spacing: 0; 44 | } 45 | 46 | /** Styles **/ 47 | .container { 48 | min-width: 500px; 49 | max-width: 900px; 50 | margin: 20px auto; 51 | } 52 | .title { 53 | font-size: 0.9rem; 54 | font-weight: bold; 55 | } 56 | .nav { 57 | margin: 50px 30px; 58 | display: flex; 59 | flex-direction: row; 60 | } 61 | .nav .title { 62 | margin-right: 50px; 63 | flex: 1; 64 | } 65 | .links { 66 | display: flex; 67 | flex-direction: row; 68 | } 69 | .links li { 70 | font-size: 0.9rem; 71 | } 72 | .links li::after { 73 | padding: 0px 15px; 74 | content: '|'; 75 | display: inline; 76 | } 77 | .links li:last-child::after { 78 | padding: 0px; 79 | content: ''; 80 | display: inline; 81 | } 82 | a { 83 | font-weight: 600; 84 | color: #224466; 85 | text-decoration: none; 86 | transition: all 0.2s ease-in-out; 87 | } 88 | a:hover { 89 | color: #7799aa; 90 | } 91 | 92 | .body { 93 | border-top: 40px solid #f9f9f9; 94 | margin: 0px 20px; 95 | background: #fefefe; 96 | flex-direction: column; 97 | padding: 30px 40px; 98 | box-shadow: 2px 2px 4px 0px #eee, 99 | 4px 4px 12px 4px #eee; 100 | } 101 | .body p { 102 | margin: 20px; 103 | } 104 | -------------------------------------------------------------------------------- /httpserver-async-objects/static/todo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

todo!

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /httpserver-async-objects/static/unimplemented.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scratch Web Server 6 | 7 | 8 | 9 | 16 |
17 |
18 |

Bad Request

19 |

Request is not implemented

20 |
21 | 22 | 23 | 24 | --------------------------------------------------------------------------------