Browse Source

Implement ETag for caching

Thomas Dy 7 years ago
parent
commit
9127fbe11e
4 changed files with 51 additions and 11 deletions
  1. 7 1
      rust-server/Cargo.lock
  2. 2 1
      rust-server/Cargo.toml
  3. 41 9
      rust-server/src/handler.rs
  4. 1 0
      rust-server/src/main.rs

+ 7 - 1
rust-server/Cargo.lock

@@ -1,10 +1,11 @@
 [root]
 name = "cloudflare-webui"
-version = "0.1.0"
+version = "0.1.2"
 dependencies = [
  "hyper 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "mime 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha1 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -203,6 +204,11 @@ name = "semver"
 version = "0.1.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
+[[package]]
+name = "sha1"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
 [[package]]
 name = "solicit"
 version = "0.4.4"

+ 2 - 1
rust-server/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "cloudflare-webui"
-version = "0.1.0"
+version = "0.1.2"
 authors = ["Thomas Dy <thatsmydoing@gmail.com>"]
 
 [dependencies]
@@ -8,3 +8,4 @@ hyper = "0.8.0"
 mime = "0.2.0"
 rustc-serialize = "0.3.0"
 url = "0.5.0"
+sha1 = "0.2.0"

+ 41 - 9
rust-server/src/handler.rs

@@ -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();

+ 1 - 0
rust-server/src/main.rs

@@ -2,6 +2,7 @@
 extern crate mime;
 extern crate hyper;
 extern crate rustc_serialize;
+extern crate sha1;
 extern crate url;
 mod config;
 mod handler;