aboutsummaryrefslogtreecommitdiffstats
path: root/components/net/resource_task.rs
diff options
context:
space:
mode:
Diffstat (limited to 'components/net/resource_task.rs')
-rw-r--r--components/net/resource_task.rs267
1 files changed, 267 insertions, 0 deletions
diff --git a/components/net/resource_task.rs b/components/net/resource_task.rs
new file mode 100644
index 00000000000..bdc1c3f2339
--- /dev/null
+++ b/components/net/resource_task.rs
@@ -0,0 +1,267 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! A task that takes a URL and streams back the binary data.
+
+use file_loader;
+use http_loader;
+use data_loader;
+
+use std::comm::{channel, Receiver, Sender};
+use std::task::TaskBuilder;
+use std::os;
+use http::headers::content_type::MediaType;
+use ResponseHeaderCollection = http::headers::response::HeaderCollection;
+use RequestHeaderCollection = http::headers::request::HeaderCollection;
+use http::method::{Method, Get};
+use url::Url;
+
+use StatusOk = http::status::Ok;
+use http::status::Status;
+
+
+pub enum ControlMsg {
+ /// Request the data associated with a particular URL
+ Load(LoadData, Sender<LoadResponse>),
+ Exit
+}
+
+#[deriving(Clone)]
+pub struct LoadData {
+ pub url: Url,
+ pub method: Method,
+ pub headers: RequestHeaderCollection,
+ pub data: Option<Vec<u8>>,
+ pub cors: Option<ResourceCORSData>
+}
+
+impl LoadData {
+ pub fn new(url: Url) -> LoadData {
+ LoadData {
+ url: url,
+ method: Get,
+ headers: RequestHeaderCollection::new(),
+ data: None,
+ cors: None
+ }
+ }
+}
+
+#[deriving(Clone)]
+pub struct ResourceCORSData {
+ /// CORS Preflight flag
+ pub preflight: bool,
+ /// Origin of CORS Request
+ pub origin: Url
+}
+
+/// Metadata about a loaded resource, such as is obtained from HTTP headers.
+pub struct Metadata {
+ /// Final URL after redirects.
+ pub final_url: Url,
+
+ /// MIME type / subtype.
+ pub content_type: Option<(String, String)>,
+
+ /// Character set.
+ pub charset: Option<String>,
+
+ /// Headers
+ pub headers: Option<ResponseHeaderCollection>,
+
+ /// HTTP Status
+ pub status: Status
+}
+
+impl Metadata {
+ /// Metadata with defaults for everything optional.
+ pub fn default(url: Url) -> Metadata {
+ Metadata {
+ final_url: url,
+ content_type: None,
+ charset: None,
+ headers: None,
+ status: StatusOk // http://fetch.spec.whatwg.org/#concept-response-status-message
+ }
+ }
+
+ /// Extract the parts of a MediaType that we care about.
+ pub fn set_content_type(&mut self, content_type: &Option<MediaType>) {
+ match *content_type {
+ None => (),
+ Some(MediaType { type_: ref type_,
+ subtype: ref subtype,
+ parameters: ref parameters }) => {
+ self.content_type = Some((type_.clone(), subtype.clone()));
+ for &(ref k, ref v) in parameters.iter() {
+ if "charset" == k.as_slice() {
+ self.charset = Some(v.clone());
+ }
+ }
+ }
+ }
+ }
+}
+
+/// Message sent in response to `Load`. Contains metadata, and a port
+/// for receiving the data.
+///
+/// Even if loading fails immediately, we send one of these and the
+/// progress_port will provide the error.
+pub struct LoadResponse {
+ /// Metadata, such as from HTTP headers.
+ pub metadata: Metadata,
+ /// Port for reading data.
+ pub progress_port: Receiver<ProgressMsg>,
+}
+
+/// Messages sent in response to a `Load` message
+#[deriving(PartialEq,Show)]
+pub enum ProgressMsg {
+ /// Binary data - there may be multiple of these
+ Payload(Vec<u8>),
+ /// Indicates loading is complete, either successfully or not
+ Done(Result<(), String>)
+}
+
+/// For use by loaders in responding to a Load message.
+pub fn start_sending(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Sender<ProgressMsg> {
+ start_sending_opt(start_chan, metadata).ok().unwrap()
+}
+
+/// For use by loaders in responding to a Load message.
+pub fn start_sending_opt(start_chan: Sender<LoadResponse>, metadata: Metadata) -> Result<Sender<ProgressMsg>, ()> {
+ let (progress_chan, progress_port) = channel();
+ let result = start_chan.send_opt(LoadResponse {
+ metadata: metadata,
+ progress_port: progress_port,
+ });
+ match result {
+ Ok(_) => Ok(progress_chan),
+ Err(_) => Err(())
+ }
+}
+
+/// Convenience function for synchronously loading a whole resource.
+pub fn load_whole_resource(resource_task: &ResourceTask, url: Url)
+ -> Result<(Metadata, Vec<u8>), String> {
+ let (start_chan, start_port) = channel();
+ resource_task.send(Load(LoadData::new(url), start_chan));
+ let response = start_port.recv();
+
+ let mut buf = vec!();
+ loop {
+ match response.progress_port.recv() {
+ Payload(data) => buf.push_all(data.as_slice()),
+ Done(Ok(())) => return Ok((response.metadata, buf)),
+ Done(Err(e)) => return Err(e)
+ }
+ }
+}
+
+/// Handle to a resource task
+pub type ResourceTask = Sender<ControlMsg>;
+
+pub type LoaderTask = proc(load_data: LoadData, Sender<LoadResponse>);
+
+/**
+Creates a task to load a specific resource
+
+The ResourceManager delegates loading to a different type of loader task for
+each URL scheme
+*/
+type LoaderTaskFactory = extern "Rust" fn() -> LoaderTask;
+
+/// Create a ResourceTask
+pub fn new_resource_task() -> ResourceTask {
+ let (setup_chan, setup_port) = channel();
+ let builder = TaskBuilder::new().named("ResourceManager");
+ builder.spawn(proc() {
+ ResourceManager::new(setup_port).start();
+ });
+ setup_chan
+}
+
+struct ResourceManager {
+ from_client: Receiver<ControlMsg>,
+}
+
+
+impl ResourceManager {
+ fn new(from_client: Receiver<ControlMsg>) -> ResourceManager {
+ ResourceManager {
+ from_client : from_client,
+ }
+ }
+}
+
+
+impl ResourceManager {
+ fn start(&self) {
+ loop {
+ match self.from_client.recv() {
+ Load(load_data, start_chan) => {
+ self.load(load_data, start_chan)
+ }
+ Exit => {
+ break
+ }
+ }
+ }
+ }
+
+ fn load(&self, mut load_data: LoadData, start_chan: Sender<LoadResponse>) {
+ let loader = match load_data.url.scheme.as_slice() {
+ "file" => file_loader::factory(),
+ "http" | "https" => http_loader::factory(),
+ "data" => data_loader::factory(),
+ "about" => {
+ match load_data.url.non_relative_scheme_data().unwrap() {
+ "crash" => fail!("Loading the about:crash URL."),
+ "failure" => {
+ // FIXME: Find a way to load this without relying on the `../src` directory.
+ let mut path = os::self_exe_path().expect("can't get exe path");
+ path.pop();
+ path.push_many(["src", "test", "html", "failure.html"]);
+ load_data.url = Url::from_file_path(&path).unwrap();
+ file_loader::factory()
+ }
+ _ => {
+ start_sending(start_chan, Metadata::default(load_data.url))
+ .send(Done(Err("Unknown about: URL.".to_string())));
+ return
+ }
+ }
+ },
+ _ => {
+ debug!("resource_task: no loader for scheme {:s}", load_data.url.scheme);
+ start_sending(start_chan, Metadata::default(load_data.url))
+ .send(Done(Err("no loader for scheme".to_string())));
+ return
+ }
+ };
+ debug!("resource_task: loading url: {:s}", load_data.url.serialize());
+ loader(load_data, start_chan);
+ }
+}
+
+#[test]
+fn test_exit() {
+ let resource_task = new_resource_task();
+ resource_task.send(Exit);
+}
+
+#[test]
+fn test_bad_scheme() {
+ let resource_task = new_resource_task();
+ let (start_chan, start) = channel();
+ let url = Url::parse("bogus://whatever").unwrap();
+ resource_task.send(Load(LoadData::new(url), start_chan));
+ let response = start.recv();
+ match response.progress_port.recv() {
+ Done(result) => { assert!(result.is_err()) }
+ _ => fail!("bleh")
+ }
+ resource_task.send(Exit);
+}