makepad_hub/
httpserver.rs

1use std::net::{TcpListener, TcpStream, SocketAddr, Shutdown};
2use std::sync::{mpsc, Arc, Mutex};
3use std::io::prelude::*;
4use std::io::BufReader;
5use std::str;
6use std::time::Duration;
7use std::collections::HashMap;
8use serde::{Serialize, Deserialize};
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
11pub enum HttpServerConfig {
12    Offline,
13    Localhost(u16),
14    Network(u16),
15    InterfaceV4((u16, [u8; 4]))
16}
17
18#[derive(Default)]
19pub struct HttpServerShared {
20    pub terminate: bool,
21    pub watcher_id: u64,
22    pub watch_pending: Vec<(u64, mpsc::Sender<String>)>,
23    pub files_read: Vec<String>,
24}
25
26#[derive(Default)]
27pub struct HttpServer {
28    pub listen_thread: Option<std::thread::JoinHandle<()>>,
29    pub listen_address: Option<SocketAddr>,
30    pub shared: Arc<Mutex<HttpServerShared>>,
31}
32
33impl HttpServer {
34    pub fn start_http_server(config: &HttpServerConfig, workspaces_arc: Arc<Mutex<HashMap<String, String>>>) -> Option<HttpServer> {
35        
36        let listen_address = match config {
37            HttpServerConfig::Offline => return None,
38            HttpServerConfig::Localhost(port) => SocketAddr::from(([127, 0, 0, 1], *port)),
39            HttpServerConfig::Network(port) => SocketAddr::from(([0, 0, 0, 0], *port)),
40            HttpServerConfig::InterfaceV4((port, ip)) => SocketAddr::from((*ip, *port)),
41        };
42        
43        let listener = if let Ok(listener) = TcpListener::bind(listen_address.clone()) {listener} else {println!("Cannot bind http server port"); return None};
44        let workspaces = Arc::clone(&workspaces_arc);
45        let shared = Arc::new(Mutex::new(HttpServerShared::default()));
46        
47        let listen_thread = {
48            let shared = Arc::clone(&shared);
49            std::thread::spawn(move || {
50                for tcp_stream in listener.incoming() {
51                    if let Ok(shared) = shared.lock() {
52                        if shared.terminate {
53                            return
54                        }
55                    }
56                    let mut tcp_stream = tcp_stream.expect("Incoming stream failure");
57                    let (tx_write, rx_write) = mpsc::channel::<String>();
58                    let mut reader = BufReader::new(tcp_stream.try_clone().expect("Cannot clone tcp stream"));
59                    let workspaces = Arc::clone(&workspaces);
60                    let shared = Arc::clone(&shared);
61                    let _read_thread = std::thread::spawn(move || {
62                        
63                        let mut line = String::new();
64                        reader.read_line(&mut line).expect("http read line fail");
65                        if !line.starts_with("GET /") || line.len() < 10 {
66                            let _ = tcp_stream.shutdown(Shutdown::Both);
67                            return
68                        }
69                        
70                        let line = &line[5..];
71                        let space = line.find(' ').expect("http space fail");
72                        let mut url = line[0..space].to_string();
73                        if url.ends_with("/"){
74                            url.push_str("index.html");
75                        }
76                        let url_lc = url.clone();
77                        url_lc.to_lowercase();
78                        if url_lc.ends_with("/key.ron") || url.find("..").is_some() || url.starts_with("/") {
79                            let _ = tcp_stream.shutdown(Shutdown::Both);
80                            return
81                        }
82                        if url_lc.starts_with("$watch") { // its a watcher wait for the finish
83                            let mut watcher_id = 0;
84                            if let Ok(mut shared) = shared.lock() {
85                                shared.watcher_id += 1;
86                                watcher_id = shared.watcher_id;
87                                shared.watch_pending.push((watcher_id, tx_write));
88                            };
89                            match rx_write.recv_timeout(Duration::from_secs(30)) {
90                                Ok(msg) => { // let the watcher know
91                                    write_bytes_to_tcp_stream_no_error(&mut tcp_stream, msg.as_bytes());
92                                    let _ = tcp_stream.shutdown(Shutdown::Both);
93                                },
94                                Err(_) => { // close gracefully
95                                    write_bytes_to_tcp_stream_no_error(&mut tcp_stream, "HTTP/1.1 201 Retry\r\n\r\n".as_bytes());
96                                    let _ = tcp_stream.shutdown(Shutdown::Both);
97                                }
98                            }
99
100                            if let Ok(mut shared) = shared.lock() {
101                                for i in 0..shared.watch_pending.len() {
102                                    let (id, _) = &shared.watch_pending[i];
103                                    if *id == watcher_id {
104                                        shared.watch_pending.remove(i);
105                                        break
106                                    }
107                                }
108                            };
109                            return
110                        }
111
112                        if url.ends_with("favicon.ico"){
113                            let header =  "HTTP/1.1 200 OK\r\nContent-Type: image/x-icon\r\nTransfer-encoding: identity\r\nContent-Length: 0\r\n: close\r\n\r\n";
114                            write_bytes_to_tcp_stream_no_error(&mut tcp_stream, header.as_bytes());
115                            let _ = tcp_stream.shutdown(Shutdown::Both);
116                            return
117                        }
118
119                        let file_path = if let Some(file_pos) = url.find('/') {
120                            let (workspace, rest) = url.split_at(file_pos);
121                            let (_, rest) = rest.split_at(1);
122                            if let Ok(workspaces) = workspaces.lock() {
123                                if let Some(abs_path) = workspaces.get(workspace) {
124                                    Some(format!("{}/{}", abs_path, rest))
125                                }
126                                else {None}
127                            }
128                            else {None}
129                        }
130                        else {None};
131                        
132                        if file_path.is_none() {
133                            let _ = tcp_stream.shutdown(Shutdown::Both);
134                            return
135                        }
136                        let file_path = file_path.unwrap();
137                        let file_path = if file_path.ends_with("/"){
138                            format!("{}/{}", file_path, "index.html")
139                        }
140                        else{
141                            file_path
142                        };
143
144                        if let Ok(mut shared) = shared.lock() {
145                            if shared.files_read.iter().find( | v | **v == url).is_none() {
146                                shared.files_read.push(url.to_string());
147                            }
148                        };
149                        
150                        if let Ok(data) = std::fs::read(&file_path) {
151                            let mime_type = if url.ends_with(".html") {"text/html"}
152                            else if url.ends_with(".wasm") {"application/wasm"}
153                            else if url.ends_with(".js") {"text/javascript"}
154                            else {"application/octet-stream"};
155                            
156                            // write the header
157                            let header = format!(
158                                "HTTP/1.1 200 OK\r\nContent-Type: {}\r\nContent-encoding: identity\r\nTransfer-encoding: identity\r\nContent-Length: {}\r\nConnection: close\r\n\r\n",
159                                mime_type,
160                                data.len()
161                            );
162                            write_bytes_to_tcp_stream_no_error(&mut tcp_stream, header.as_bytes());
163                            write_bytes_to_tcp_stream_no_error(&mut tcp_stream, &data);
164                            let _ = tcp_stream.shutdown(Shutdown::Both);
165                        }
166                        else { // 404
167                            write_bytes_to_tcp_stream_no_error(&mut tcp_stream, "HTTP/1.1 404 NotFound\r\n".as_bytes());
168                            let _ = tcp_stream.shutdown(Shutdown::Both);
169                        }
170                    });
171                }
172            })
173        };
174        Some(HttpServer {
175            listen_thread: Some(listen_thread),
176            listen_address: Some(listen_address.clone()),
177            shared: shared,
178        })
179    }
180    
181    pub fn send_json_message(&mut self, json_msg: &str) {
182        if let Ok(shared) = self.shared.lock() {
183            for (_, tx) in &shared.watch_pending {
184                let msg = format!(
185                    "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-encoding: identity\r\nTransfer-encoding: identity\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}",
186                    json_msg.len(),
187                    json_msg
188                );
189                let _ = tx.send(msg);
190            }
191        }
192    }
193    
194    pub fn send_file_change(&mut self, path: &str) {
195        if let Ok(shared) = self.shared.lock() {
196            if shared.files_read.iter().find( | v | **v == path).is_none() {
197                return
198            }
199        }
200        self.send_json_message(&format!("{{\"type\":\"file_change\",\"path\":\"{}\"}}", path));
201    }
202    
203    pub fn send_build_start(&mut self) {
204        //self.send_json_message(&format!("{{\"type\":\"build_start\"}}"));
205    }
206    
207    pub fn terminate(&mut self) {
208        if let Ok(mut shared) = self.shared.lock() {
209            shared.terminate = true;
210            for (_, tx) in &shared.watch_pending {
211                let _ = tx.send("HTTP/1.1 201 Retry\r\n\r\n".to_string());
212            }
213        }
214        if let Some(listen_address) = self.listen_address {
215            self.listen_address = None;
216            if let Ok(_) = TcpStream::connect(listen_address) {
217                self.listen_thread.take().expect("cant take listen thread").join().expect("cant join listen thread");
218            }
219        }
220    }
221}
222
223fn write_bytes_to_tcp_stream_no_error(tcp_stream: &mut TcpStream, bytes: &[u8]) {
224    let bytes_total = bytes.len();
225    let mut bytes_left = bytes_total;
226    while bytes_left > 0 {
227        let buf = &bytes[(bytes_total - bytes_left)..bytes_total];
228        if let Ok(bytes_written) = tcp_stream.write(buf) {
229            if bytes_written == 0 {
230                return
231            }
232            bytes_left -= bytes_written;
233        }
234        else {
235            return
236        }
237    }
238}