|  | @@ -5,12 +5,13 @@ use std::io::Read;
 | 
	
		
			
				|  |  |  use hyper::{Get, Post};
 | 
	
		
			
				|  |  |  use hyper::Client;
 | 
	
		
			
				|  |  |  use hyper::client;
 | 
	
		
			
				|  |  | -use hyper::header::{ContentType, Headers};
 | 
	
		
			
				|  |  | +use hyper::header::{ContentType, ETag, EntityTag, IfNoneMatch, Headers};
 | 
	
		
			
				|  |  |  use hyper::server::{Handler, Request, Response};
 | 
	
		
			
				|  |  | -use hyper::status::StatusCode::{NotFound, Unauthorized};
 | 
	
		
			
				|  |  | +use hyper::status::StatusCode::{NotFound, NotModified, Unauthorized};
 | 
	
		
			
				|  |  |  use hyper::uri::RequestUri::AbsolutePath;
 | 
	
		
			
				|  |  |  use rustc_serialize::json;
 | 
	
		
			
				|  |  |  use rustc_serialize::json::Json;
 | 
	
		
			
				|  |  | +use sha1::Sha1;
 | 
	
		
			
				|  |  |  use url::form_urlencoded;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  const API_ENDPOINT: &'static str = "https://www.cloudflare.com/api_json.html";
 | 
	
	
		
			
				|  | @@ -18,9 +19,15 @@ const API_ENDPOINT: &'static str = "https://www.cloudflare.com/api_json.html";
 | 
	
		
			
				|  |  |  const INDEX_HTML: &'static str = include_str!("../../index.html");
 | 
	
		
			
				|  |  |  const BUNDLE_JS: &'static str = include_str!("../../assets/bundle.js");
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +struct Etags {
 | 
	
		
			
				|  |  | +    index: EntityTag,
 | 
	
		
			
				|  |  | +    bundle: EntityTag
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  pub struct SiteHandler {
 | 
	
		
			
				|  |  |      cfg: Config,
 | 
	
		
			
				|  |  | -    client: Client
 | 
	
		
			
				|  |  | +    client: Client,
 | 
	
		
			
				|  |  | +    etags: Etags
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl SiteHandler {
 | 
	
	
		
			
				|  | @@ -36,10 +43,21 @@ impl SiteHandler {
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +fn make_etag(source: &str) -> EntityTag {
 | 
	
		
			
				|  |  | +    let mut m = Sha1::new();
 | 
	
		
			
				|  |  | +    m.update(source.as_bytes());
 | 
	
		
			
				|  |  | +    let digest = m.digest().to_string();
 | 
	
		
			
				|  |  | +    EntityTag::new(false, digest)
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  pub fn new(cfg: Config) -> SiteHandler {
 | 
	
		
			
				|  |  |      SiteHandler {
 | 
	
		
			
				|  |  |          cfg: cfg,
 | 
	
		
			
				|  |  | -        client: Client::new()
 | 
	
		
			
				|  |  | +        client: Client::new(),
 | 
	
		
			
				|  |  | +        etags: Etags {
 | 
	
		
			
				|  |  | +            index: make_etag(INDEX_HTML),
 | 
	
		
			
				|  |  | +            bundle: make_etag(BUNDLE_JS)
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -47,9 +65,23 @@ fn get_param<'a>(params: &'a Vec<(String, String)>, key: &str) -> &'a str {
 | 
	
		
			
				|  |  |      params.into_iter().find(|tuple| tuple.0 == key).map(|tuple| tuple.1.as_ref()).unwrap_or("")
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -fn serve(mut res: Response, content: &str, mime: Mime) {
 | 
	
		
			
				|  |  | +fn serve(req: &Request, mut res: Response, content: &str, etag: &EntityTag, mime: Mime) {
 | 
	
		
			
				|  |  | +    let empty_vec = vec!();
 | 
	
		
			
				|  |  | +    let etags = req.headers.get::<IfNoneMatch>();
 | 
	
		
			
				|  |  | +    let etags: &Vec<EntityTag> = match etags {
 | 
	
		
			
				|  |  | +        Some(&IfNoneMatch::Items(ref items)) => items,
 | 
	
		
			
				|  |  | +        _ => &empty_vec
 | 
	
		
			
				|  |  | +    };
 | 
	
		
			
				|  |  | +    let is_cached = etags.iter().find(|&etag_b| etag.weak_eq(etag_b)).is_some();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      res.headers_mut().set(ContentType(mime));
 | 
	
		
			
				|  |  | -    res.send(content.as_bytes()).unwrap();
 | 
	
		
			
				|  |  | +    res.headers_mut().set(ETag(etag.to_owned()));
 | 
	
		
			
				|  |  | +    if is_cached {
 | 
	
		
			
				|  |  | +        *res.status_mut() = NotModified;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +    else {
 | 
	
		
			
				|  |  | +        res.send(content.as_bytes()).unwrap();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  impl Handler for SiteHandler {
 | 
	
	
		
			
				|  | @@ -59,9 +91,9 @@ impl Handler for SiteHandler {
 | 
	
		
			
				|  |  |          match req.uri {
 | 
	
		
			
				|  |  |              AbsolutePath(ref path) => match (&req.method, &path[..]) {
 | 
	
		
			
				|  |  |                  (&Get, "/") =>
 | 
	
		
			
				|  |  | -                    serve(res, INDEX_HTML, mime!(Text/Html; Charset=Utf8)),
 | 
	
		
			
				|  |  | +                    serve(&req, res, INDEX_HTML, &self.etags.index, mime!(Text/Html; Charset=Utf8)),
 | 
	
		
			
				|  |  |                  (&Get, "/assets/bundle.js") =>
 | 
	
		
			
				|  |  | -                    serve(res, BUNDLE_JS, mime!(Application/Javascript; Charset=Utf8)),
 | 
	
		
			
				|  |  | +                    serve(&req, res, BUNDLE_JS, &self.etags.bundle, mime!(Application/Javascript; Charset=Utf8)),
 | 
	
		
			
				|  |  |                  (&Post, "/api") => {
 | 
	
		
			
				|  |  |                      let mut params = form_urlencoded::parse(text.as_bytes());
 | 
	
		
			
				|  |  |                      params.push(("email".to_string(), self.cfg.email.clone()));
 | 
	
	
		
			
				|  | @@ -116,7 +148,7 @@ impl Handler for SiteHandler {
 | 
	
		
			
				|  |  |                      }
 | 
	
		
			
				|  |  |                  },
 | 
	
		
			
				|  |  |                  (&Get, _) =>
 | 
	
		
			
				|  |  | -                    serve(res, INDEX_HTML, mime!(Text/Html; Charset=Utf8)),
 | 
	
		
			
				|  |  | +                    serve(&req, res, INDEX_HTML, &self.etags.index, mime!(Text/Html; Charset=Utf8)),
 | 
	
		
			
				|  |  |                  _ => {
 | 
	
		
			
				|  |  |                      *res.status_mut() = NotFound;
 | 
	
		
			
				|  |  |                      res.send(b"Not Found").unwrap();
 |