sysinfo_web/
web.rs

1
2use iron::{Iron, IronResult, Listening, status};
3use iron::error::HttpResult;
4use iron::response::Response;
5#[cfg(feature = "gzip")]
6use iron::response::WriteBody;
7use iron::request::Request;
8use iron::middleware::Handler;
9use iron::mime::Mime;
10
11use sysinfo::{System, SystemExt};
12
13#[cfg(feature = "gzip")]
14use flate2::Compression;
15#[cfg(feature = "gzip")]
16use flate2::write::GzEncoder;
17
18use std::sync::{Arc, Mutex, RwLock};
19use std::thread;
20use std::time::{Duration, SystemTime};
21#[cfg(feature = "gzip")]
22use std::io::{self, Write};
23
24use SysinfoExt;
25use serde_json;
26
27
28const INDEX_HTML: &'static [u8] = include_bytes!("index.html");
29const FAVICON: &'static [u8] = include_bytes!("../resources/favicon.ico");
30const REFRESH_DELAY: u64 = 60 * 10; // 10 minutes
31
32/// Simple wrapper to get gzip compressed output on string types.
33#[cfg(feature = "gzip")]
34struct GzipContent(Box<WriteBody>);
35
36#[cfg(feature = "gzip")]
37impl WriteBody for GzipContent {
38    fn write_body(&mut self, w: &mut Write) -> io::Result<()> {
39        let mut w = GzEncoder::new(w, Compression::default());
40        self.0.write_body(&mut w)?;
41        w.finish().map(|_| ())
42    }
43}
44
45struct SysinfoIronHandler(Arc<DataHandler>);
46
47struct DataHandler {
48    system: RwLock<System>,
49    last_connection: Mutex<SystemTime>,
50    json_output: RwLock<String>,
51}
52
53impl DataHandler {
54    fn can_update_system_info(&self) -> bool {
55        SystemTime::now().duration_since(*self.last_connection.lock().unwrap())
56                         .unwrap()
57                         .as_secs() < REFRESH_DELAY
58    }
59
60    fn update_last_connection(&self) {
61        *self.last_connection.lock().unwrap() = SystemTime::now();
62    }
63}
64
65#[cfg(feature = "gzip")]
66macro_rules! return_gzip_or_not {
67    ($req:expr, $content:expr, $typ:expr) => {{
68        let mut use_gzip = false;
69
70        if let Some(raw_accept_encoding) = $req.headers.get_raw("accept-encoding") {
71            for accept_encoding in raw_accept_encoding {
72                match ::std::str::from_utf8(accept_encoding).map(|s| s.to_lowercase()) {
73                    Ok(ref s) if s.contains("gzip") => {
74                        use_gzip = true;
75                        break;
76                    }
77                    _ => continue,
78                }
79            }
80        }
81        if !use_gzip {
82            Ok(Response::with((status::Ok, $typ.parse::<Mime>().unwrap(), $content)))
83        } else {
84            use iron::headers::{ContentType, ContentEncoding, Encoding};
85            let mut res = Response::new();
86            res.status = Some(status::Ok);
87            res.body = Some(Box::new(GzipContent(Box::new($content))));
88            res.headers.set(ContentType($typ.parse::<Mime>().unwrap()));
89            res.headers.set(ContentEncoding(vec![Encoding::Gzip]));
90            Ok(res)
91        }
92    }}
93}
94
95#[cfg(not(feature = "gzip"))]
96macro_rules! return_gzip_or_not {
97    ($req:expr, $content:expr, $typ:expr) => {{
98        Ok(Response::with((status::Ok, $typ.parse::<Mime>().unwrap(), $content)))
99    }}
100}
101
102impl Handler for SysinfoIronHandler {
103    fn handle(&self, req: &mut Request) -> IronResult<Response> {
104        match match req.url.path().last() {
105            Some(path) => {
106                if *path == "" {
107                    1
108                } else if *path == "favicon.ico" {
109                    2
110                } else {
111                    3
112                }
113            }
114            None => 0,
115        } {
116            1 => return_gzip_or_not!(req, INDEX_HTML, "text/html"),
117            2 => return_gzip_or_not!(req, FAVICON, "image/x-icon"),
118            3 => {
119                self.0.update_last_connection();
120                return_gzip_or_not!(req,
121                                    self.0.json_output.read().unwrap().clone(),
122                                    "application/json")
123            }
124            _ => Ok(Response::with((status::NotFound, "Not found"))),
125        }
126    }
127}
128
129pub fn start_web_server(sock_addr: Option<String>) -> HttpResult<Listening> {
130    let data_handler = Arc::new(DataHandler {
131        system: RwLock::new(System::new()),
132        last_connection: Mutex::new(SystemTime::now()),
133        json_output: RwLock::new(String::from("[]")),
134    });
135    let data_handler_clone = data_handler.clone();
136    thread::spawn(move || {
137        let mut sleeping = false;
138        loop {
139            if data_handler_clone.can_update_system_info() {
140                {
141                    let mut system = data_handler_clone.system.write().unwrap();
142                    system.refresh_all();
143                    // refresh it twice to provide accurate information after wake up
144                    if sleeping {
145                        system.refresh_all();
146                        sleeping = false;
147                    }
148                    let sysinfo = SysinfoExt::new(&system);
149                    let mut json_output = data_handler_clone.json_output.write().unwrap();
150                    json_output.clear();
151                    use std::fmt::Write;
152                    json_output.write_str(&serde_json::to_string(&sysinfo)
153                                          .unwrap_or(String::from("[]"))).unwrap();
154                }
155                thread::sleep(Duration::new(5, 0));
156            } else {
157                // If we don't need to refresh the system information, we can sleep a lot less.
158                thread::sleep(Duration::from_millis(500));
159                sleeping = true;
160            }
161        }
162    });
163    let mut iron = Iron::new(SysinfoIronHandler(data_handler));
164    iron.threads = 4;
165    iron.http(sock_addr.unwrap_or("localhost:3000".to_owned()))
166}