Returning cache headers for network content (#3123)

This commit is contained in:
Tomasz Drwięga 2016-11-03 12:40:53 +01:00 committed by Gav Wood
parent 3a1f3c0a80
commit e9cd2f4d56
9 changed files with 62 additions and 12 deletions

1
Cargo.lock generated
View File

@ -352,6 +352,7 @@ dependencies = [
"serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde_codegen 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
"time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", "zip 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -24,6 +24,7 @@ ethabi = "0.2.2"
linked-hash-map = "0.3" linked-hash-map = "0.3"
parity-dapps-glue = "1.4" parity-dapps-glue = "1.4"
mime = "0.2" mime = "0.2"
time = "0.1.35"
serde_macros = { version = "0.8", optional = true } serde_macros = { version = "0.8", optional = true }
zip = { version = "0.1", default-features = false } zip = { version = "0.1", default-features = false }
ethcore-devtools = { path = "../devtools" } ethcore-devtools = { path = "../devtools" }

View File

@ -32,14 +32,15 @@ use random_filename;
use SyncStatus; use SyncStatus;
use util::{Mutex, H256}; use util::{Mutex, H256};
use util::sha3::sha3; use util::sha3::sha3;
use page::LocalPageEndpoint; use page::{LocalPageEndpoint, PageCache};
use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator}; use handlers::{ContentHandler, ContentFetcherHandler, ContentValidator};
use endpoint::{Endpoint, EndpointPath, Handler}; use endpoint::{Endpoint, EndpointPath, Handler};
use apps::cache::{ContentCache, ContentStatus}; use apps::cache::{ContentCache, ContentStatus};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest, serialize_manifest, Manifest};
use apps::urlhint::{URLHintContract, URLHint, URLHintResult}; use apps::urlhint::{URLHintContract, URLHint, URLHintResult};
const MAX_CACHED_DAPPS: usize = 10; /// Limit of cached dapps/content
const MAX_CACHED_DAPPS: usize = 20;
pub struct ContentFetcher<R: URLHint = URLHintContract> { pub struct ContentFetcher<R: URLHint = URLHintContract> {
dapps_path: PathBuf, dapps_path: PathBuf,
@ -259,6 +260,17 @@ impl ContentValidator for ContentInstaller {
// Create dir // Create dir
try!(fs::create_dir_all(&self.content_path)); try!(fs::create_dir_all(&self.content_path));
// Validate hash
let mut file_reader = io::BufReader::new(try!(fs::File::open(&path)));
let hash = try!(sha3(&mut file_reader));
let id = try!(self.id.as_str().parse().map_err(|_| ValidationError::InvalidContentId));
if id != hash {
return Err(ValidationError::HashMismatch {
expected: id,
got: hash,
});
}
// And prepare path for a file // And prepare path for a file
let filename = path.file_name().expect("We always fetch a file."); let filename = path.file_name().expect("We always fetch a file.");
let mut content_path = self.content_path.clone(); let mut content_path = self.content_path.clone();
@ -270,7 +282,7 @@ impl ContentValidator for ContentInstaller {
try!(fs::copy(&path, &content_path)); try!(fs::copy(&path, &content_path));
Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone()))) Ok((self.id.clone(), LocalPageEndpoint::single_file(content_path, self.mime.clone(), PageCache::Enabled)))
} }
fn done(&self, endpoint: Option<LocalPageEndpoint>) { fn done(&self, endpoint: Option<LocalPageEndpoint>) {
@ -376,7 +388,7 @@ impl ContentValidator for DappInstaller {
try!(manifest_file.write_all(manifest_str.as_bytes())); try!(manifest_file.write_all(manifest_str.as_bytes()));
// Create endpoint // Create endpoint
let app = LocalPageEndpoint::new(target, manifest.clone().into(), self.embeddable_at); let app = LocalPageEndpoint::new(target, manifest.clone().into(), PageCache::Enabled, self.embeddable_at);
// Return modified app manifest // Return modified app manifest
Ok((manifest.id.clone(), app)) Ok((manifest.id.clone(), app))
@ -416,7 +428,7 @@ mod tests {
version: "".into(), version: "".into(),
author: "".into(), author: "".into(),
icon_url: "".into(), icon_url: "".into(),
}, None); }, Default::default(), None);
// when // when
fetcher.set_status("test", ContentStatus::Ready(handler)); fetcher.set_status("test", ContentStatus::Ready(handler));

View File

@ -18,7 +18,7 @@ use std::io;
use std::io::Read; use std::io::Read;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use page::LocalPageEndpoint; use page::{LocalPageEndpoint, PageCache};
use endpoint::{Endpoints, EndpointInfo}; use endpoint::{Endpoints, EndpointInfo};
use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest}; use apps::manifest::{MANIFEST_FILENAME, deserialize_manifest};
@ -102,7 +102,7 @@ pub fn local_endpoints(dapps_path: String, signer_port: Option<u16>) -> Endpoint
for dapp in local_dapps(dapps_path) { for dapp in local_dapps(dapps_path) {
pages.insert( pages.insert(
dapp.id, dapp.id,
Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, signer_port)) Box::new(LocalPageEndpoint::new(dapp.path, dapp.info, PageCache::Disabled, signer_port))
); );
} }
pages pages

View File

@ -44,6 +44,7 @@
#![cfg_attr(feature="nightly", plugin(clippy))] #![cfg_attr(feature="nightly", plugin(clippy))]
extern crate hyper; extern crate hyper;
extern crate time;
extern crate url as url_lib; extern crate url as url_lib;
extern crate unicase; extern crate unicase;
extern crate serde; extern crate serde;

View File

@ -14,7 +14,7 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use page::handler; use page::{handler, PageCache};
use std::sync::Arc; use std::sync::Arc;
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
use parity_dapps::{WebApp, File, Info}; use parity_dapps::{WebApp, File, Info};
@ -80,6 +80,7 @@ impl<T: WebApp> Endpoint for PageEndpoint<T> {
prefix: self.prefix.clone(), prefix: self.prefix.clone(),
path: path, path: path,
file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()), file: handler::ServedFile::new(self.safe_to_embed_at_port.clone()),
cache: PageCache::Disabled,
safe_to_embed_at_port: self.safe_to_embed_at_port.clone(), safe_to_embed_at_port: self.safe_to_embed_at_port.clone(),
}) })
} }

View File

@ -15,6 +15,8 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
use std::io::Write; use std::io::Write;
use time::{self, Duration};
use hyper::header; use hyper::header;
use hyper::server; use hyper::server;
use hyper::uri::RequestUri; use hyper::uri::RequestUri;
@ -69,6 +71,19 @@ impl<T: Dapp> ServedFile<T> {
} }
} }
/// Defines what cache headers should be appended to returned resources.
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum PageCache {
Enabled,
Disabled,
}
impl Default for PageCache {
fn default() -> Self {
PageCache::Disabled
}
}
/// A handler for a single webapp. /// A handler for a single webapp.
/// Resolves correct paths and serves as a plumbing code between /// Resolves correct paths and serves as a plumbing code between
/// hyper server and dapp. /// hyper server and dapp.
@ -83,6 +98,8 @@ pub struct PageHandler<T: Dapp> {
pub path: EndpointPath, pub path: EndpointPath,
/// Flag indicating if the file can be safely embeded (put in iframe). /// Flag indicating if the file can be safely embeded (put in iframe).
pub safe_to_embed_at_port: Option<u16>, pub safe_to_embed_at_port: Option<u16>,
/// Cache settings for this page.
pub cache: PageCache,
} }
impl<T: Dapp> PageHandler<T> { impl<T: Dapp> PageHandler<T> {
@ -129,9 +146,19 @@ impl<T: Dapp> server::Handler<HttpStream> for PageHandler<T> {
ServedFile::File(ref f) => { ServedFile::File(ref f) => {
res.set_status(StatusCode::Ok); res.set_status(StatusCode::Ok);
if let PageCache::Enabled = self.cache {
let mut headers = res.headers_mut();
let validity = Duration::days(365);
headers.set(header::CacheControl(vec![
header::CacheDirective::Public,
header::CacheDirective::MaxAge(validity.num_seconds() as u32),
]));
headers.set(header::Expires(header::HttpDate(time::now() + validity)));
}
match f.content_type().parse() { match f.content_type().parse() {
Ok(mime) => res.headers_mut().set(header::ContentType(mime)), Ok(mime) => res.headers_mut().set(header::ContentType(mime)),
Err(()) => debug!(target: "page_handler", "invalid MIME type: {}", f.content_type()), Err(()) => debug!(target: "dapps", "invalid MIME type: {}", f.content_type()),
} }
// Security headers: // Security headers:
@ -218,6 +245,7 @@ fn should_extract_path_with_appid() {
using_dapps_domains: true, using_dapps_domains: true,
}, },
file: ServedFile::new(None), file: ServedFile::new(None),
cache: Default::default(),
safe_to_embed_at_port: None, safe_to_embed_at_port: None,
}; };

View File

@ -18,7 +18,7 @@ use mime_guess;
use std::io::{Seek, Read, SeekFrom}; use std::io::{Seek, Read, SeekFrom};
use std::fs; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use page::handler; use page::handler::{self, PageCache};
use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler}; use endpoint::{Endpoint, EndpointInfo, EndpointPath, Handler};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -26,24 +26,27 @@ pub struct LocalPageEndpoint {
path: PathBuf, path: PathBuf,
mime: Option<String>, mime: Option<String>,
info: Option<EndpointInfo>, info: Option<EndpointInfo>,
cache: PageCache,
embeddable_at: Option<u16>, embeddable_at: Option<u16>,
} }
impl LocalPageEndpoint { impl LocalPageEndpoint {
pub fn new(path: PathBuf, info: EndpointInfo, embeddable_at: Option<u16>) -> Self { pub fn new(path: PathBuf, info: EndpointInfo, cache: PageCache, embeddable_at: Option<u16>) -> Self {
LocalPageEndpoint { LocalPageEndpoint {
path: path, path: path,
mime: None, mime: None,
info: Some(info), info: Some(info),
cache: cache,
embeddable_at: embeddable_at, embeddable_at: embeddable_at,
} }
} }
pub fn single_file(path: PathBuf, mime: String) -> Self { pub fn single_file(path: PathBuf, mime: String, cache: PageCache) -> Self {
LocalPageEndpoint { LocalPageEndpoint {
path: path, path: path,
mime: Some(mime), mime: Some(mime),
info: None, info: None,
cache: cache,
embeddable_at: None, embeddable_at: None,
} }
} }
@ -66,6 +69,7 @@ impl Endpoint for LocalPageEndpoint {
path: path, path: path,
file: handler::ServedFile::new(None), file: handler::ServedFile::new(None),
safe_to_embed_at_port: self.embeddable_at, safe_to_embed_at_port: self.embeddable_at,
cache: self.cache,
}) })
} else { } else {
Box::new(handler::PageHandler { Box::new(handler::PageHandler {
@ -74,6 +78,7 @@ impl Endpoint for LocalPageEndpoint {
path: path, path: path,
file: handler::ServedFile::new(None), file: handler::ServedFile::new(None),
safe_to_embed_at_port: self.embeddable_at, safe_to_embed_at_port: self.embeddable_at,
cache: self.cache,
}) })
} }
} }

View File

@ -21,4 +21,5 @@ mod handler;
pub use self::local::LocalPageEndpoint; pub use self::local::LocalPageEndpoint;
pub use self::builtin::PageEndpoint; pub use self::builtin::PageEndpoint;
pub use self::handler::PageCache;