char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
688e96e
main
1use std:: collections:: BTreeMap ; 2use std:: io::{ Read , Write }; 3use std:: net:: TcpListener ; 4use std:: sync::{ 5Arc , Mutex , 6 atomic::{ AtomicBool , Ordering }, 7}; 8use std:: time:: Duration ; 9 10# [ derive ( Clone )] 11pub struct Response { 12pub status : u16 , 13pub headers : String , 14pub body : Vec < u8 >, 15} 16 17type ResponseHook =Box < dyn FnOnce () +Send >; 18 19pub struct Http { 20pub url : url:: Url , 21pub routes : Arc < Mutex < BTreeMap < String , Response >>>, 22pub requests : Arc < Mutex < Vec < String >>>, 23# [ allow ( dead_code )] 24pub hook : Arc < Mutex < Option < ResponseHook >>>, 25stop : Arc < AtomicBool >, 26worker : Option < std:: thread:: JoinHandle <()>>, 27} 28 29impl Http { 30pub fn new () ->Self { 31let listener =TcpListener :: bind ( "127.0.0.1:0" ). unwrap (); 32 listener. set_nonblocking ( true ). unwrap (); 33let url = url:: Url :: parse ( & format! ( "http://{}/" , listener. local_addr (). unwrap ())). unwrap (); 34let routes =Arc :: new ( Mutex :: new ( BTreeMap ::< String , Response >:: new ())); 35let requests =Arc :: new ( Mutex :: new ( Vec :: new ())); 36let hook =Arc :: new ( Mutex :: new ( None ::< ResponseHook >)); 37let stop =Arc :: new ( AtomicBool :: new ( false )); 38let state =( routes. clone (), requests. clone (), hook. clone (), stop. clone ()); 39let worker = std:: thread:: spawn ( move ||{ 40while !state. 3 . load ( Ordering :: Relaxed ) { 41let mut stream =match listener. accept () { 42Ok (( stream, _)) => stream, 43Err ( error) if error. kind () == std:: io:: ErrorKind :: WouldBlock =>{ 44 std:: thread:: sleep ( Duration :: from_millis ( 1 )); 45continue ; 46} 47Err ( error) =>panic! ( "{error}" ), 48}; 49 stream 50. set_read_timeout ( Some ( Duration :: from_secs ( 3 ))) 51. unwrap (); 52let mut request =Vec :: new (); 53let mut buf =[ 0 ; 1024 ]; 54while !request. windows ( 4 ). any ( |bytes| bytes ==b"\r\n\r\n" ) { 55match stream. read ( & mut buf) { 56Ok ( 0 ) |Err ( _) =>break , 57Ok ( len) => request. extend_from_slice ( & buf[ ..len]), 58} 59} 60let request =String :: from_utf8_lossy ( & request); 61let Some ( path) = request 62. lines () 63. next () 64. and_then ( |line| line. split_whitespace (). nth ( 1 )) 65else { 66continue ; 67}; 68 state. 1 . lock (). unwrap (). push ( path. to_owned ()); 69let response = state 70. 0 71. lock () 72. unwrap () 73. get ( path) 74. cloned () 75. unwrap_or ( Response { 76status : 404 , 77headers : String :: new (), 78body : Vec :: new (), 79}); 80if let Some ( hook) = state. 2 . lock (). unwrap (). take () { 81hook (); 82} 83let headers =format! ( 84"HTTP/1.1 {} Test\r\n{}Content-Length: {}\r\nConnection: close\r\n\r\n" , 85 response. status , 86 response. headers , 87 response. body . len () 88); 89let _ = stream 90. write_all ( headers. as_bytes ()) 91. and_then ( |_| stream. write_all ( & response. body )); 92} 93}); 94Self { 95 url, 96 routes, 97 requests, 98 hook, 99 stop, 100worker : Some ( worker), 101} 102} 103 104pub fn name ( & self , path : & str ) ->String { 105self . url . join ( path). unwrap (). to_string () 106} 107 108pub fn source ( & self , path : & str , source : & str ) { 109self . routes . lock (). unwrap (). insert ( 110format! ( "/{path}" ), 111Response { 112status : 200 , 113headers : "Content-Type: application/javascript\r\n" . into (), 114body : source. as_bytes (). to_vec (), 115}, 116); 117} 118 119pub fn redirect ( & self , path : & str , location : & str ) { 120self . routes . lock (). unwrap (). insert ( 121format! ( "/{path}" ), 122Response { 123status : 302 , 124headers : format! ( "Location: {location}\r\n" ), 125body : Vec :: new (), 126}, 127); 128} 129} 130 131impl Drop for Http { 132fn drop ( & mut self ) { 133self . stop . store ( true , Ordering :: Relaxed ); 134self . worker . take (). unwrap (). join (). unwrap (); 135} 136}