diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2016-09-08 18:58:05 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-08 18:58:05 -0500 |
commit | 5a5a76cc5db830d2e622d4e0924837383b64dfa2 (patch) | |
tree | 9f104dbe418b8c42e5767b566700d59ceea84318 /components | |
parent | aa011ea2759683ede76639a1ea889c93c21d1cb8 (diff) | |
parent | faf32a7cfbaf568bbfeb4f2572ea96b6a30d231e (diff) | |
download | servo-5a5a76cc5db830d2e622d4e0924837383b64dfa2.tar.gz servo-5a5a76cc5db830d2e622d4e0924837383b64dfa2.zip |
Auto merge of #13058 - malisas:malisa-responseAPI, r=Manishearth,jdm
Response API
<!-- Please describe your changes on the following line: -->
This PR adds the [dom::Response](https://fetch.spec.whatwg.org/#response-class) implementation and addresses #11896.
The relevant passing tests` expectations have been updated.
In order to allow non-UTF-8-encoded status messages, `net_traits::response::Response`'s `raw_status` field has been changed from type [`Option<RawStatus>`](https://doc.servo.org/hyper/http/struct.RawStatus.html) to type `Option<(u16, Vec<u8>)>`. As a result, a few other files which rely on the `raw_status` field were affected and updated.
TODOs:
- The `body` and `trailer` methods. Relies on implementation of `ReadableStream` and `Promise`s.
- Similarly, replace the dummy constructor `_body: Option<USVString>` argument with `body: ResponseBodyInit`.
- Currently, whenever `r's response's header list` or `r's Headers object` are mentioned, I always modify the `headers_reflector` field (of type dom::Headers, or `r's Headers object`) and not the corresponding hyper::Headers list in the net_traits::Response field. A completely accurate interpretation of the spec might consider making both of these lists the same thing via a reference. [Discussion](https://github.com/whatwg/fetch/issues/358) was [had](https://github.com/servo/servo/pull/12884).
---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `__` with appropriate data: -->
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [ ] These changes fix #__ (github issue number if applicable).
<!-- Either: -->
- [X] There are tests for these changes OR
- [ ] These changes do not require tests because _____
<!-- Pull requests that do not address these steps are welcome, but they will require additional verification as part of the review process. -->
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/13058)
<!-- Reviewable:end -->
Diffstat (limited to 'components')
-rw-r--r-- | components/devtools/Cargo.toml | 1 | ||||
-rw-r--r-- | components/devtools/actors/network_event.rs | 8 | ||||
-rw-r--r-- | components/devtools/lib.rs | 1 | ||||
-rw-r--r-- | components/devtools_traits/lib.rs | 3 | ||||
-rw-r--r-- | components/net/about_loader.rs | 3 | ||||
-rw-r--r-- | components/net/blob_loader.rs | 3 | ||||
-rw-r--r-- | components/net/fetch/methods.rs | 7 | ||||
-rw-r--r-- | components/net/http_loader.rs | 7 | ||||
-rw-r--r-- | components/net_traits/lib.rs | 5 | ||||
-rw-r--r-- | components/net_traits/response.rs | 20 | ||||
-rw-r--r-- | components/script/dom/bindings/trace.rs | 5 | ||||
-rw-r--r-- | components/script/dom/headers.rs | 16 | ||||
-rw-r--r-- | components/script/dom/htmllinkelement.rs | 3 | ||||
-rw-r--r-- | components/script/dom/htmlmediaelement.rs | 3 | ||||
-rw-r--r-- | components/script/dom/htmlscriptelement.rs | 4 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 1 | ||||
-rw-r--r-- | components/script/dom/response.rs | 290 | ||||
-rw-r--r-- | components/script/dom/webidls/Response.webidl | 37 | ||||
-rw-r--r-- | components/script/dom/xmlhttprequest.rs | 9 | ||||
-rw-r--r-- | components/servo/Cargo.lock | 1 |
20 files changed, 385 insertions, 42 deletions
diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index 230c16af6c2..f17488c575d 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -11,6 +11,7 @@ path = "lib.rs" [dependencies] devtools_traits = {path = "../devtools_traits"} +encoding = "0.2" hyper = "0.9.9" hyper_serde = "0.1.4" ipc-channel = "0.5" diff --git a/components/devtools/actors/network_event.rs b/components/devtools/actors/network_event.rs index ba2793af8b7..9281356f7e5 100644 --- a/components/devtools/actors/network_event.rs +++ b/components/devtools/actors/network_event.rs @@ -11,12 +11,15 @@ extern crate hyper; use actor::{Actor, ActorMessageStatus, ActorRegistry}; use devtools_traits::HttpRequest as DevtoolsHttpRequest; use devtools_traits::HttpResponse as DevtoolsHttpResponse; +use encoding::all::UTF_8; +use encoding::types::{DecoderTrap, Encoding}; use hyper::header::Headers; use hyper::header::{ContentType, Cookie}; use hyper::http::RawStatus; use hyper::method::Method; use protocol::JsonPacketStream; use serde_json::Value; +use std::borrow::Cow; use std::collections::BTreeMap; use std::net::TcpStream; use time; @@ -360,7 +363,10 @@ impl NetworkEventActor { pub fn add_response(&mut self, response: DevtoolsHttpResponse) { self.response.headers = response.headers.clone(); - self.response.status = response.status.clone(); + self.response.status = response.status.as_ref().map(|&(s, ref st)| { + let status_text = UTF_8.decode(st, DecoderTrap::Replace).unwrap(); + RawStatus(s, Cow::from(status_text)) + }); self.response.body = response.body.clone(); } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 17e36f95c0b..0ec2a713cde 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -21,6 +21,7 @@ #![deny(unsafe_code)] extern crate devtools_traits; +extern crate encoding; extern crate hyper; extern crate ipc_channel; #[macro_use] diff --git a/components/devtools_traits/lib.rs b/components/devtools_traits/lib.rs index ecaec49995b..4edf19d6f02 100644 --- a/components/devtools_traits/lib.rs +++ b/components/devtools_traits/lib.rs @@ -27,7 +27,6 @@ extern crate time; extern crate url; use hyper::header::Headers; -use hyper::http::RawStatus; use hyper::method::Method; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; @@ -304,7 +303,7 @@ pub struct HttpRequest { #[derive(Debug, PartialEq)] pub struct HttpResponse { pub headers: Option<Headers>, - pub status: Option<RawStatus>, + pub status: Option<(u16, Vec<u8>)>, pub body: Option<Vec<u8>>, pub pipeline_id: PipelineId, } diff --git a/components/net/about_loader.rs b/components/net/about_loader.rs index 29f598f3cdf..26ec34d2e6c 100644 --- a/components/net/about_loader.rs +++ b/components/net/about_loader.rs @@ -4,7 +4,6 @@ use file_loader; use hyper::header::ContentType; -use hyper::http::RawStatus; use hyper::mime::{Mime, SubLevel, TopLevel}; use hyper_serde::Serde; use mime_classifier::MimeClassifier; @@ -38,7 +37,7 @@ pub fn factory(mut load_data: LoadData, Some(Serde(ContentType(Mime(TopLevel::Text, SubLevel::Html, vec![])))), charset: Some("utf-8".to_owned()), headers: None, - status: Some(Serde(RawStatus(200, "OK".into()))), + status: Some((200, b"OK".to_vec())), https_state: HttpsState::None, referrer: None, }; diff --git a/components/net/blob_loader.rs b/components/net/blob_loader.rs index 5cad922802a..1fe15d9a4a5 100644 --- a/components/net/blob_loader.rs +++ b/components/net/blob_loader.rs @@ -5,7 +5,6 @@ use filemanager_thread::{FileManager, UIProvider}; use hyper::header::{DispositionType, ContentDisposition, DispositionParam}; use hyper::header::{Headers, ContentType, ContentLength, Charset}; -use hyper::http::RawStatus; use hyper_serde::Serde; use ipc_channel::ipc; use mime::{Mime, Attr}; @@ -72,7 +71,7 @@ fn load_blob<UI: 'static + UIProvider> charset: charset.map(|c| c.as_str().to_string()), headers: Some(Serde(headers)), // https://w3c.github.io/FileAPI/#TwoHundredOK - status: Some(Serde(RawStatus(200, "OK".into()))), + status: Some((200, b"OK".to_vec())), https_state: HttpsState::None, referrer: None, }; diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index da2dda147fb..b9caa134712 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -973,8 +973,9 @@ fn http_network_fetch(request: Rc<Request>, Ok((res, msg)) => { response.url = Some(url.clone()); response.status = Some(res.response.status); - response.raw_status = Some(res.response.status_raw().clone()); - response.headers = res.response.headers.clone(); + response.raw_status = Some((res.response.status_raw().0, + res.response.status_raw().1.as_bytes().to_vec())); + response.headers = res.response.headers.clone(); let res_body = response.body.clone(); @@ -1001,7 +1002,7 @@ fn http_network_fetch(request: Rc<Request>, send_response_to_devtools( &sender, request_id.unwrap(), meta_headers.map(Serde::into_inner), - meta_status.map(Serde::into_inner), + meta_status, pipeline_id); } } diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 62df61bcd68..a2fd9f87f48 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -629,7 +629,7 @@ pub fn send_request_to_devtools(msg: ChromeToDevtoolsControlMsg, pub fn send_response_to_devtools(devtools_chan: &Sender<DevtoolsControlMsg>, request_id: String, headers: Option<Headers>, - status: Option<RawStatus>, + status: Option<(u16, Vec<u8>)>, pipeline_id: PipelineId) { let response = DevtoolsHttpResponse { headers: headers, status: status, body: None, pipeline_id: pipeline_id }; let net_event_response = NetworkEvent::HttpResponse(response); @@ -1081,7 +1081,8 @@ pub fn load<A, B>(load_data: &LoadData, None => None }); metadata.headers = Some(Serde(adjusted_headers)); - metadata.status = Some(Serde(response.status_raw().clone())); + metadata.status = Some((response.status_raw().0, + response.status_raw().1.as_bytes().to_vec())); metadata.https_state = if doc_url.scheme() == "https" { HttpsState::Modern } else { @@ -1097,7 +1098,7 @@ pub fn load<A, B>(load_data: &LoadData, send_response_to_devtools( &chan, request_id.unwrap(), metadata.headers.clone().map(Serde::into_inner), - metadata.status.clone().map(Serde::into_inner), + metadata.status.clone(), pipeline_id); } } diff --git a/components/net_traits/lib.rs b/components/net_traits/lib.rs index a778d3ce76b..036fb54d940 100644 --- a/components/net_traits/lib.rs +++ b/components/net_traits/lib.rs @@ -560,9 +560,8 @@ pub struct Metadata { /// Headers pub headers: Option<Serde<Headers>>, - #[ignore_heap_size_of = "Defined in hyper"] /// HTTP Status - pub status: Option<Serde<RawStatus>>, + pub status: Option<(u16, Vec<u8>)>, /// Is successful HTTPS connection pub https_state: HttpsState, @@ -580,7 +579,7 @@ impl Metadata { charset: None, headers: None, // https://fetch.spec.whatwg.org/#concept-response-status-message - status: Some(Serde(RawStatus(200, "OK".into()))), + status: Some((200, b"OK".to_vec())), https_state: HttpsState::None, referrer: None, } diff --git a/components/net_traits/response.rs b/components/net_traits/response.rs index 37b0a585780..4305abd4fb6 100644 --- a/components/net_traits/response.rs +++ b/components/net_traits/response.rs @@ -5,7 +5,6 @@ //! The [Response](https://fetch.spec.whatwg.org/#responses) object //! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch) use hyper::header::{AccessControlExposeHeaders, ContentType, Headers}; -use hyper::http::RawStatus; use hyper::status::StatusCode; use hyper_serde::Serde; use std::ascii::AsciiExt; @@ -15,7 +14,7 @@ use url::Url; use {Metadata, NetworkError}; /// [Response type](https://fetch.spec.whatwg.org/#concept-response-type) -#[derive(Clone, PartialEq, Copy, Debug, Deserialize, Serialize)] +#[derive(Clone, PartialEq, Copy, Debug, Deserialize, Serialize, HeapSizeOf)] pub enum ResponseType { Basic, CORS, @@ -26,7 +25,7 @@ pub enum ResponseType { } /// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason) -#[derive(Clone, Copy, Deserialize, Serialize)] +#[derive(Clone, Copy, Deserialize, Serialize, HeapSizeOf)] pub enum TerminationReason { EndUserAbort, Fatal, @@ -35,7 +34,7 @@ pub enum TerminationReason { /// The response body can still be pushed to after fetch /// This provides a way to store unfinished response bodies -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, HeapSizeOf)] pub enum ResponseBody { Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough? Receiving(Vec<u8>), @@ -53,7 +52,7 @@ impl ResponseBody { /// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state) -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, HeapSizeOf)] pub enum CacheState { None, Local, @@ -76,16 +75,19 @@ pub enum ResponseMsg { } /// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec -#[derive(Clone)] +#[derive(Clone, HeapSizeOf)] pub struct Response { pub response_type: ResponseType, pub termination_reason: Option<TerminationReason>, pub url: Option<Url>, pub url_list: RefCell<Vec<Url>>, /// `None` can be considered a StatusCode of `0`. + #[ignore_heap_size_of = "Defined in hyper"] pub status: Option<StatusCode>, - pub raw_status: Option<RawStatus>, + pub raw_status: Option<(u16, Vec<u8>)>, + #[ignore_heap_size_of = "Defined in hyper"] pub headers: Headers, + #[ignore_heap_size_of = "Mutex heap size undefined"] pub body: Arc<Mutex<ResponseBody>>, pub cache_state: CacheState, pub https_state: HttpsState, @@ -104,7 +106,7 @@ impl Response { url: None, url_list: RefCell::new(Vec::new()), status: Some(StatusCode::Ok), - raw_status: Some(RawStatus(200, "OK".into())), + raw_status: Some((200, b"OK".to_vec())), headers: Headers::new(), body: Arc::new(Mutex::new(ResponseBody::Empty)), cache_state: CacheState::None, @@ -239,7 +241,7 @@ impl Response { None => None }); metadata.headers = Some(Serde(self.headers.clone())); - metadata.status = self.raw_status.clone().map(Serde); + metadata.status = self.raw_status.clone(); metadata.https_state = self.https_state; return Ok(metadata); } diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 507e19e4bcb..49a27d3271a 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -50,6 +50,7 @@ use html5ever::tree_builder::QuirksMode; use hyper::header::Headers; use hyper::method::Method; use hyper::mime::Mime; +use hyper::status::StatusCode; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use js::glue::{CallObjectTracer, CallUnbarrieredObjectTracer, CallValueTracer}; use js::jsapi::{GCTraceKindToAscii, Heap, TraceKind, JSObject, JSTracer}; @@ -62,6 +63,7 @@ use net_traits::image::base::{Image, ImageMetadata}; use net_traits::image_cache_thread::{ImageCacheChan, ImageCacheThread}; use net_traits::request::Request; use net_traits::response::HttpsState; +use net_traits::response::{Response, ResponseBody}; use net_traits::storage_thread::StorageType; use net_traits::{Metadata, NetworkError, ResourceThreads}; use offscreen_gl_context::GLLimits; @@ -339,7 +341,10 @@ no_jsmanaged_fields!(SharedRt); no_jsmanaged_fields!(TouchpadPressurePhase); no_jsmanaged_fields!(USVString); no_jsmanaged_fields!(ReferrerPolicy); +no_jsmanaged_fields!(Response); +no_jsmanaged_fields!(ResponseBody); no_jsmanaged_fields!(ResourceThreads); +no_jsmanaged_fields!(StatusCode); no_jsmanaged_fields!(SystemTime); no_jsmanaged_fields!(RelativePos); no_jsmanaged_fields!(OpaqueStyleAndLayoutData); diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 7b45956c039..abfa959c8e6 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -209,7 +209,13 @@ impl Headers { headers_for_request } - pub fn set_guard(&self, new_guard: Guard) { + pub fn for_response(global: GlobalRef) -> Root<Headers> { + let headers_for_response = Headers::new(global); + headers_for_response.guard.set(Guard::Response); + headers_for_response + } + + pub fn set_guard(&self, new_guard: Guard) { self.guard.set(new_guard) } @@ -346,7 +352,7 @@ pub fn is_forbidden_header_name(name: &str) -> bool { // [3] https://tools.ietf.org/html/rfc7230#section-3.2.6 // [4] https://www.rfc-editor.org/errata_search.php?rfc=7230 fn validate_name_and_value(name: ByteString, value: ByteString) - -> Result<(String, Vec<u8>), Error> { + -> Fallible<(String, Vec<u8>)> { let valid_name = try!(validate_name(name)); if !is_field_content(&value) { return Err(Error::Type("Value is not valid".to_string())); @@ -354,7 +360,7 @@ fn validate_name_and_value(name: ByteString, value: ByteString) Ok((valid_name, value.into())) } -fn validate_name(name: ByteString) -> Result<String, Error> { +fn validate_name(name: ByteString) -> Fallible<String> { if !is_field_name(&name) { return Err(Error::Type("Name is not valid".to_string())); } @@ -444,7 +450,7 @@ fn is_field_vchar(x: u8) -> bool { } // https://tools.ietf.org/html/rfc5234#appendix-B.1 -fn is_vchar(x: u8) -> bool { +pub fn is_vchar(x: u8) -> bool { match x { 0x21...0x7E => true, _ => false, @@ -452,7 +458,7 @@ fn is_vchar(x: u8) -> bool { } // http://tools.ietf.org/html/rfc7230#section-3.2.6 -fn is_obs_text(x: u8) -> bool { +pub fn is_obs_text(x: u8) -> bool { match x { 0x80...0xFF => true, _ => false, diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 9264bcb0490..07cd47cbcec 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -22,7 +22,6 @@ use dom::virtualmethods::VirtualMethods; use encoding::EncodingRef; use encoding::all::UTF_8; use hyper::header::ContentType; -use hyper::http::RawStatus; use hyper::mime::{Mime, TopLevel, SubLevel}; use hyper_serde::Serde; use ipc_channel::ipc; @@ -335,7 +334,7 @@ impl AsyncResponseListener for StylesheetContext { document.invalidate_stylesheets(); // FIXME: Revisit once consensus is reached at: https://github.com/whatwg/html/issues/1142 - successful = metadata.status.map_or(false, |Serde(RawStatus(code, _))| code == 200); + successful = metadata.status.map_or(false, |(code, _)| code == 200); } if elem.parser_inserted.get() { diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index d26ac6a044e..c2db2b0b96f 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -24,7 +24,6 @@ use dom::htmlsourceelement::HTMLSourceElement; use dom::mediaerror::MediaError; use dom::node::{window_from_node, document_from_node, Node, UnbindContext}; use dom::virtualmethods::VirtualMethods; -use hyper_serde::Serde; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use net_traits::{AsyncResponseListener, AsyncResponseTarget, Metadata, NetworkError}; @@ -66,7 +65,7 @@ impl AsyncResponseListener for HTMLMediaElementContext { .as_ref() .and_then(|m| m.status .as_ref() - .map(|&Serde(ref s)| s.0 < 200 || s.0 >= 300)) + .map(|&(s, _)| s < 200 || s >= 300)) .unwrap_or(false); if is_failure { // Ensure that the element doesn't receive any further notifications diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 6a6fe7d50b4..a7f5c5e3666 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -26,8 +26,6 @@ use dom::window::ScriptHelpers; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, EncodingRef}; use html5ever::tree_builder::NextParserState; -use hyper::http::RawStatus; -use hyper_serde::Serde; use ipc_channel::ipc; use ipc_channel::router::ROUTER; use js::jsval::UndefinedValue; @@ -159,7 +157,7 @@ impl AsyncResponseListener for ScriptContext { let status_code = self.metadata.as_ref().and_then(|m| { match m.status { - Some(Serde(RawStatus(c, _))) => Some(c), + Some((c, _)) => Some(c), _ => None, } }).unwrap_or(0); diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 07e4d6227be..f38d1e8949c 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -372,6 +372,7 @@ pub mod progressevent; pub mod radionodelist; pub mod range; pub mod request; +pub mod response; pub mod screen; pub mod serviceworker; pub mod serviceworkercontainer; diff --git a/components/script/dom/response.rs b/components/script/dom/response.rs new file mode 100644 index 00000000000..acfc181283a --- /dev/null +++ b/components/script/dom/response.rs @@ -0,0 +1,290 @@ +/* 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/. */ + +use core::cell::Cell; +use dom::bindings::cell::DOMRefCell; +use dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods; +use dom::bindings::codegen::Bindings::ResponseBinding; +use dom::bindings::codegen::Bindings::ResponseBinding::{ResponseMethods, ResponseType as DOMResponseType}; +use dom::bindings::error::{Error, Fallible}; +use dom::bindings::global::GlobalRef; +use dom::bindings::js::{JS, MutNullableHeap, Root}; +use dom::bindings::reflector::{Reflectable, Reflector, reflect_dom_object}; +use dom::bindings::str::{ByteString, USVString}; +use dom::headers::{Headers, Guard}; +use dom::headers::{is_vchar, is_obs_text}; +use hyper::status::StatusCode; +use net_traits::response::{ResponseBody as NetTraitsResponseBody}; +use std::str::FromStr; +use url::Position; +use url::Url; + +#[dom_struct] +pub struct Response { + reflector_: Reflector, + headers_reflector: MutNullableHeap<JS<Headers>>, + mime_type: DOMRefCell<Vec<u8>>, + body_used: Cell<bool>, + /// `None` can be considered a StatusCode of `0`. + #[ignore_heap_size_of = "Defined in hyper"] + status: DOMRefCell<Option<StatusCode>>, + raw_status: DOMRefCell<Option<(u16, Vec<u8>)>>, + response_type: DOMRefCell<DOMResponseType>, + url: DOMRefCell<Option<Url>>, + url_list: DOMRefCell<Vec<Url>>, + // For now use the existing NetTraitsResponseBody enum, until body + // is implemented. + body: DOMRefCell<NetTraitsResponseBody>, +} + +impl Response { + pub fn new_inherited() -> Response { + Response { + reflector_: Reflector::new(), + headers_reflector: Default::default(), + mime_type: DOMRefCell::new("".to_string().into_bytes()), + body_used: Cell::new(false), + status: DOMRefCell::new(Some(StatusCode::Ok)), + raw_status: DOMRefCell::new(Some((200, b"OK".to_vec()))), + response_type: DOMRefCell::new(DOMResponseType::Default), + url: DOMRefCell::new(None), + url_list: DOMRefCell::new(vec![]), + body: DOMRefCell::new(NetTraitsResponseBody::Empty), + } + } + + // https://fetch.spec.whatwg.org/#dom-response + pub fn new(global: GlobalRef) -> Root<Response> { + reflect_dom_object(box Response::new_inherited(), global, ResponseBinding::Wrap) + } + + pub fn Constructor(global: GlobalRef, _body: Option<USVString>, init: &ResponseBinding::ResponseInit) + -> Fallible<Root<Response>> { + // Step 1 + if init.status < 200 || init.status > 599 { + return Err(Error::Range( + format!("init's status member should be in the range 200 to 599, inclusive, but is {}" + , init.status))); + } + + // Step 2 + if !is_valid_status_text(&init.statusText) { + return Err(Error::Type("init's statusText member does not match the reason-phrase token production" + .to_string())); + } + + // Step 3 + let r = Response::new(global); + + // Step 4 + *r.status.borrow_mut() = Some(StatusCode::from_u16(init.status)); + + // Step 5 + *r.raw_status.borrow_mut() = Some((init.status, init.statusText.clone().into())); + + // Step 6 + if let Some(ref headers_member) = init.headers { + // Step 6.1 + // TODO: Figure out how/if we should make r's response's + // header list and r's Headers object the same thing. For + // now just working with r's Headers object. Also, the + // header list should already be empty so this step may be + // unnecessary. + r.Headers().empty_header_list(); + + // Step 6.2 + try!(r.Headers().fill(Some(headers_member.clone()))); + } + + // Step 7 + if let Some(_) = _body { + // Step 7.1 + if is_null_body_status(init.status) { + return Err(Error::Type( + "Body is non-null but init's status member is a null body status".to_string())); + }; + + // Step 7.2 + let content_type: Option<ByteString> = None; + + // Step 7.3 + // TODO: Extract body and implement step 7.3. + + // Step 7.4 + if let Some(content_type_contents) = content_type { + if !r.Headers().Has(ByteString::new(b"Content-Type".to_vec())).unwrap() { + try!(r.Headers().Append(ByteString::new(b"Content-Type".to_vec()), content_type_contents)); + } + }; + } + + // Step 8 + *r.mime_type.borrow_mut() = r.Headers().extract_mime_type(); + + // Step 9 + // TODO: `entry settings object` is not implemented in Servo yet. + + // Step 10 + // TODO: Write this step once Promises are merged in + + // Step 11 + Ok(r) + } + + // https://fetch.spec.whatwg.org/#dom-response-error + pub fn Error(global: GlobalRef) -> Root<Response> { + let r = Response::new(global); + *r.response_type.borrow_mut() = DOMResponseType::Error; + r.Headers().set_guard(Guard::Immutable); + *r.raw_status.borrow_mut() = Some((0, b"".to_vec())); + r + } + + // https://fetch.spec.whatwg.org/#dom-response-redirect + pub fn Redirect(global: GlobalRef, url: USVString, status: u16) -> Fallible<Root<Response>> { + // Step 1 + // TODO: `entry settings object` is not implemented in Servo yet. + let base_url = global.get_url(); + let parsed_url = base_url.join(&url.0); + + // Step 2 + let url = match parsed_url { + Ok(url) => url, + Err(_) => return Err(Error::Type("Url could not be parsed".to_string())), + }; + + // Step 3 + if !is_redirect_status(status) { + return Err(Error::Range("status is not a redirect status".to_string())); + } + + // Step 4 + // see Step 4 continued + let r = Response::new(global); + + // Step 5 + *r.status.borrow_mut() = Some(StatusCode::from_u16(status)); + *r.raw_status.borrow_mut() = Some((status, b"".to_vec())); + + // Step 6 + let url_bytestring = ByteString::from_str(url.as_str()).unwrap_or(ByteString::new(b"".to_vec())); + try!(r.Headers().Set(ByteString::new(b"Location".to_vec()), url_bytestring)); + + // Step 4 continued + // Headers Guard is set to Immutable here to prevent error in Step 6 + r.Headers().set_guard(Guard::Immutable); + + // Step 7 + Ok(r) + } +} + +// https://fetch.spec.whatwg.org/#redirect-status +fn is_redirect_status(status: u16) -> bool { + status == 301 || status == 302 || status == 303 || status == 307 || status == 308 +} + +// https://tools.ietf.org/html/rfc7230#section-3.1.2 +fn is_valid_status_text(status_text: &ByteString) -> bool { + // reason-phrase = *( HTAB / SP / VCHAR / obs-text ) + for byte in status_text.iter() { + if !(*byte == b'\t' || *byte == b' ' || is_vchar(*byte) || is_obs_text(*byte)) { + return false; + } + } + true +} + +// https://fetch.spec.whatwg.org/#null-body-status +fn is_null_body_status(status: u16) -> bool { + status == 101 || status == 204 || status == 205 || status == 304 +} + +impl ResponseMethods for Response { + // https://fetch.spec.whatwg.org/#dom-response-type + fn Type(&self) -> DOMResponseType { + *self.response_type.borrow()//into() + } + + // https://fetch.spec.whatwg.org/#dom-response-url + fn Url(&self) -> USVString { + USVString(String::from((*self.url.borrow()).as_ref().map(|u| serialize_without_fragment(u)).unwrap_or(""))) + } + + // https://fetch.spec.whatwg.org/#dom-response-redirected + fn Redirected(&self) -> bool { + let url_list_len = self.url_list.borrow().len(); + url_list_len > 1 + } + + // https://fetch.spec.whatwg.org/#dom-response-status + fn Status(&self) -> u16 { + match *self.raw_status.borrow() { + Some((s, _)) => s, + None => 0, + } + } + + // https://fetch.spec.whatwg.org/#dom-response-ok + fn Ok(&self) -> bool { + match *self.status.borrow() { + Some(s) => { + let status_num = s.to_u16(); + return status_num >= 200 && status_num <= 299; + } + None => false, + } + } + + // https://fetch.spec.whatwg.org/#dom-response-statustext + fn StatusText(&self) -> ByteString { + match *self.raw_status.borrow() { + Some((_, ref st)) => ByteString::new(st.clone()), + None => ByteString::new(b"OK".to_vec()), + } + } + + // https://fetch.spec.whatwg.org/#dom-response-headers + fn Headers(&self) -> Root<Headers> { + self.headers_reflector.or_init(|| Headers::for_response(self.global().r())) + } + + // https://fetch.spec.whatwg.org/#dom-response-clone + fn Clone(&self) -> Fallible<Root<Response>> { + // Step 1 + // TODO: This step relies on body and stream, which are still unimplemented. + + // Step 2 + let new_response = Response::new(self.global().r()); + new_response.Headers().set_guard(self.Headers().get_guard()); + + // https://fetch.spec.whatwg.org/#concept-response-clone + // Instead of storing a net_traits::Response internally, we + // only store the relevant fields, and only clone them here + *new_response.response_type.borrow_mut() = self.response_type.borrow().clone(); + *new_response.status.borrow_mut() = self.status.borrow().clone(); + *new_response.raw_status.borrow_mut() = self.raw_status.borrow().clone(); + *new_response.url.borrow_mut() = self.url.borrow().clone(); + *new_response.url_list.borrow_mut() = self.url_list.borrow().clone(); + + if *self.body.borrow() != NetTraitsResponseBody::Empty { + *new_response.body.borrow_mut() = self.body.borrow().clone(); + } + + // Step 3 + // TODO: This step relies on promises, which are still unimplemented. + + // Step 4 + Ok(new_response) + } + + // https://fetch.spec.whatwg.org/#dom-body-bodyused + fn BodyUsed(&self) -> bool { + self.body_used.get() + } +} + +fn serialize_without_fragment(url: &Url) -> &str { + &url[..Position::AfterQuery] +} diff --git a/components/script/dom/webidls/Response.webidl b/components/script/dom/webidls/Response.webidl new file mode 100644 index 00000000000..2052f5c6371 --- /dev/null +++ b/components/script/dom/webidls/Response.webidl @@ -0,0 +1,37 @@ +/* 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/. */ + +// https://fetch.spec.whatwg.org/#response-class + +// TODO: pass 'optional ResponseBodyInit? body = null' to constructor in place of USVString + [Constructor(optional USVString? body = null, optional ResponseInit init), + Exposed=(Window,Worker)] +interface Response { + [NewObject] static Response error(); + [NewObject, Throws] static Response redirect(USVString url, optional unsigned short status = 302); + + readonly attribute ResponseType type; + + readonly attribute USVString url; + readonly attribute boolean redirected; + readonly attribute unsigned short status; + readonly attribute boolean ok; + readonly attribute ByteString statusText; + [SameObject] readonly attribute Headers headers; + // readonly attribute ReadableStream? body; + // [SameObject] readonly attribute Promise<Headers> trailer; + + [NewObject, Throws] Response clone(); +}; +Response implements Body; + +dictionary ResponseInit { + unsigned short status = 200; + ByteString statusText = "OK"; + HeadersInit headers; +}; + +enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" }; + +// typedef (BodyInit or ReadableStream) ResponseBodyInit; diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 382e4c69faa..fc82d63a897 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -36,7 +36,6 @@ use encoding::types::{DecoderTrap, EncoderTrap, Encoding, EncodingRef}; use euclid::length::Length; use hyper::header::Headers; use hyper::header::{ContentLength, ContentType}; -use hyper::http::RawStatus; use hyper::method::Method; use hyper::mime::{self, Mime, Attr as MimeAttr, Value as MimeValue}; use hyper_serde::Serde; @@ -91,7 +90,7 @@ struct XHRContext { #[derive(Clone)] pub enum XHRProgress { /// Notify that headers have been received - HeadersReceived(GenerationId, Option<Headers>, Option<RawStatus>), + HeadersReceived(GenerationId, Option<Headers>, Option<(u16, Vec<u8>)>), /// Partial progress (after receiving headers), containing portion of the response Loading(GenerationId, ByteString), /// Loading is done @@ -879,7 +878,7 @@ impl XMLHttpRequest { self.process_partial_response(XHRProgress::HeadersReceived( gen_id, metadata.headers.map(Serde::into_inner), - metadata.status.map(Serde::into_inner))); + metadata.status)); Ok(()) } @@ -943,9 +942,9 @@ impl XMLHttpRequest { // Part of step 13, send() (processing response) // XXXManishearth handle errors, if any (substep 1) // Substep 2 - status.map(|RawStatus(code, reason)| { + status.map(|(code, reason)| { self.status.set(code); - *self.status_text.borrow_mut() = ByteString::new(reason.into_owned().into_bytes()); + *self.status_text.borrow_mut() = ByteString::new(reason); }); headers.as_ref().map(|h| *self.response_headers.borrow_mut() = h.clone()); diff --git a/components/servo/Cargo.lock b/components/servo/Cargo.lock index f402fc54aa3..3d1c5a462e8 100644 --- a/components/servo/Cargo.lock +++ b/components/servo/Cargo.lock @@ -493,6 +493,7 @@ name = "devtools" version = "0.0.1" dependencies = [ "devtools_traits 0.0.1", + "encoding 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", "hyper_serde 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", |