servo/components/net_traits/response.rs
Javed Nissar 7596c36959 Move ResourceFetchTiming into Arc
The purpose of this commit is to ensure that the Response object has
access to Timing updates as previously the Response object simply
stored a ResourceFetchTiming struct so updates on ResourceFetchTiming
that were not explicitly done on the Response would not be passed down.
The references to ServoArc are added because Response uses
servo_arc::Arc rather than std::sync::Arc as is used elsewhere. So,
we've switched those other places to servo_arc::Arc instead of switching
Response to std::sync::Arc.
2019-09-18 11:54:28 -04:00

359 lines
13 KiB
Rust

/* 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 https://mozilla.org/MPL/2.0/. */
//! The [Response](https://fetch.spec.whatwg.org/#responses) object
//! resulting from a [fetch operation](https://fetch.spec.whatwg.org/#concept-fetch)
use crate::{FetchMetadata, FilteredMetadata, Metadata, NetworkError, ReferrerPolicy};
use crate::{ResourceFetchTiming, ResourceTimingType};
use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt};
use http::{HeaderMap, StatusCode};
use hyper_serde::Serde;
use servo_arc::Arc;
use servo_url::ServoUrl;
use std::sync::atomic::AtomicBool;
use std::sync::Mutex;
/// [Response type](https://fetch.spec.whatwg.org/#concept-response-type)
#[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
pub enum ResponseType {
Basic,
Cors,
Default,
Error(NetworkError),
Opaque,
OpaqueRedirect,
}
/// [Response termination reason](https://fetch.spec.whatwg.org/#concept-response-termination-reason)
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum TerminationReason {
EndUserAbort,
Fatal,
Timeout,
}
/// The response body can still be pushed to after fetch
/// This provides a way to store unfinished response bodies
#[derive(Clone, Debug, MallocSizeOf, PartialEq)]
pub enum ResponseBody {
Empty, // XXXManishearth is this necessary, or is Done(vec![]) enough?
Receiving(Vec<u8>),
Done(Vec<u8>),
}
impl ResponseBody {
pub fn is_done(&self) -> bool {
match *self {
ResponseBody::Done(..) => true,
ResponseBody::Empty | ResponseBody::Receiving(..) => false,
}
}
}
/// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state)
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum CacheState {
None,
Local,
Validated,
Partial,
}
/// [Https state](https://fetch.spec.whatwg.org/#concept-response-https-state)
#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum HttpsState {
None,
Deprecated,
Modern,
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct ResponseInit {
pub url: ServoUrl,
#[serde(
deserialize_with = "::hyper_serde::deserialize",
serialize_with = "::hyper_serde::serialize"
)]
#[ignore_malloc_size_of = "Defined in hyper"]
pub headers: HeaderMap,
pub status_code: u16,
pub referrer: Option<ServoUrl>,
pub location_url: Option<Result<ServoUrl, String>>,
}
/// A [Response](https://fetch.spec.whatwg.org/#concept-response) as defined by the Fetch spec
#[derive(Clone, Debug, MallocSizeOf)]
pub struct Response {
pub response_type: ResponseType,
pub termination_reason: Option<TerminationReason>,
url: Option<ServoUrl>,
pub url_list: Vec<ServoUrl>,
/// `None` can be considered a StatusCode of `0`.
#[ignore_malloc_size_of = "Defined in hyper"]
pub status: Option<(StatusCode, String)>,
pub raw_status: Option<(u16, Vec<u8>)>,
#[ignore_malloc_size_of = "Defined in hyper"]
pub headers: HeaderMap,
#[ignore_malloc_size_of = "Mutex heap size undefined"]
pub body: Arc<Mutex<ResponseBody>>,
pub cache_state: CacheState,
pub https_state: HttpsState,
pub referrer: Option<ServoUrl>,
pub referrer_policy: Option<ReferrerPolicy>,
/// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list)
pub cors_exposed_header_name_list: Vec<String>,
/// [Location URL](https://fetch.spec.whatwg.org/#concept-response-location-url)
pub location_url: Option<Result<ServoUrl, String>>,
/// [Internal response](https://fetch.spec.whatwg.org/#concept-internal-response), only used if the Response
/// is a filtered response
pub internal_response: Option<Box<Response>>,
/// whether or not to try to return the internal_response when asked for actual_response
pub return_internal: bool,
/// https://fetch.spec.whatwg.org/#concept-response-aborted
#[ignore_malloc_size_of = "AtomicBool heap size undefined"]
pub aborted: Arc<AtomicBool>,
/// track network metrics
#[ignore_malloc_size_of = "Mutex heap size undefined"]
pub resource_timing: Arc<Mutex<ResourceFetchTiming>>,
}
impl Response {
pub fn new(url: ServoUrl, resource_timing: ResourceFetchTiming) -> Response {
Response {
response_type: ResponseType::Default,
termination_reason: None,
url: Some(url),
url_list: vec![],
status: Some((StatusCode::OK, "".to_string())),
raw_status: Some((200, b"".to_vec())),
headers: HeaderMap::new(),
body: Arc::new(Mutex::new(ResponseBody::Empty)),
cache_state: CacheState::None,
https_state: HttpsState::None,
referrer: None,
referrer_policy: None,
cors_exposed_header_name_list: vec![],
location_url: None,
internal_response: None,
return_internal: true,
aborted: Arc::new(AtomicBool::new(false)),
resource_timing: Arc::new(Mutex::new(resource_timing)),
}
}
pub fn from_init(init: ResponseInit, resource_timing_type: ResourceTimingType) -> Response {
let mut res = Response::new(init.url, ResourceFetchTiming::new(resource_timing_type));
res.location_url = init.location_url;
res.headers = init.headers;
res.referrer = init.referrer;
res.status = StatusCode::from_u16(init.status_code)
.map(|s| (s, s.to_string()))
.ok();
res
}
pub fn network_error(e: NetworkError) -> Response {
Response {
response_type: ResponseType::Error(e),
termination_reason: None,
url: None,
url_list: vec![],
status: None,
raw_status: None,
headers: HeaderMap::new(),
body: Arc::new(Mutex::new(ResponseBody::Empty)),
cache_state: CacheState::None,
https_state: HttpsState::None,
referrer: None,
referrer_policy: None,
cors_exposed_header_name_list: vec![],
location_url: None,
internal_response: None,
return_internal: true,
aborted: Arc::new(AtomicBool::new(false)),
resource_timing: Arc::new(Mutex::new(ResourceFetchTiming::new(
ResourceTimingType::Error,
))),
}
}
pub fn url(&self) -> Option<&ServoUrl> {
self.url.as_ref()
}
pub fn is_network_error(&self) -> bool {
match self.response_type {
ResponseType::Error(..) => true,
_ => false,
}
}
pub fn get_network_error(&self) -> Option<&NetworkError> {
match self.response_type {
ResponseType::Error(ref e) => Some(e),
_ => None,
}
}
pub fn actual_response(&self) -> &Response {
if self.return_internal && self.internal_response.is_some() {
&**self.internal_response.as_ref().unwrap()
} else {
self
}
}
pub fn actual_response_mut(&mut self) -> &mut Response {
if self.return_internal && self.internal_response.is_some() {
&mut **self.internal_response.as_mut().unwrap()
} else {
self
}
}
pub fn to_actual(self) -> Response {
if self.return_internal && self.internal_response.is_some() {
*self.internal_response.unwrap()
} else {
self
}
}
pub fn get_resource_timing(&self) -> Arc<Mutex<ResourceFetchTiming>> {
Arc::clone(&self.resource_timing)
}
/// Convert to a filtered response, of type `filter_type`.
/// Do not use with type Error or Default
#[cfg_attr(rustfmt, rustfmt_skip)]
pub fn to_filtered(self, filter_type: ResponseType) -> Response {
match filter_type {
ResponseType::Default |
ResponseType::Error(..) => panic!(),
_ => (),
}
let old_response = self.to_actual();
if let ResponseType::Error(e) = old_response.response_type {
return Response::network_error(e);
}
let old_headers = old_response.headers.clone();
let mut response = old_response.clone();
response.internal_response = Some(Box::new(old_response));
response.response_type = filter_type;
match response.response_type {
ResponseType::Default |
ResponseType::Error(..) => unreachable!(),
ResponseType::Basic => {
let headers = old_headers.iter().filter(|(name, _)| {
match &*name.as_str().to_ascii_lowercase() {
"set-cookie" | "set-cookie2" => false,
_ => true
}
}).map(|(n, v)| (n.clone(), v.clone())).collect();
response.headers = headers;
},
ResponseType::Cors => {
let headers = old_headers.iter().filter(|(name, _)| {
match &*name.as_str().to_ascii_lowercase() {
"cache-control" | "content-language" | "content-type" |
"expires" | "last-modified" | "pragma" => true,
"set-cookie" | "set-cookie2" => false,
header => {
let access = old_headers.typed_get::<AccessControlExposeHeaders>();
let result = access
.and_then(|v| v.iter().find(|h| *header == h.as_str().to_ascii_lowercase()));
result.is_some()
}
}
}).map(|(n, v)| (n.clone(), v.clone())).collect();
response.headers = headers;
},
ResponseType::Opaque => {
response.url_list = vec![];
response.url = None;
response.headers = HeaderMap::new();
response.status = None;
response.body = Arc::new(Mutex::new(ResponseBody::Empty));
response.cache_state = CacheState::None;
},
ResponseType::OpaqueRedirect => {
response.headers = HeaderMap::new();
response.status = None;
response.body = Arc::new(Mutex::new(ResponseBody::Empty));
response.cache_state = CacheState::None;
},
}
response
}
pub fn metadata(&self) -> Result<FetchMetadata, NetworkError> {
fn init_metadata(response: &Response, url: &ServoUrl) -> Metadata {
let mut metadata = Metadata::default(url.clone());
metadata.set_content_type(
response
.headers
.typed_get::<ContentType>()
.map(|v| v.into())
.as_ref(),
);
metadata.location_url = response.location_url.clone();
metadata.headers = Some(Serde(response.headers.clone()));
metadata.status = response.raw_status.clone();
metadata.https_state = response.https_state;
metadata.referrer = response.referrer.clone();
metadata.referrer_policy = response.referrer_policy.clone();
metadata
};
if let Some(error) = self.get_network_error() {
return Err(error.clone());
}
let metadata = self.url.as_ref().map(|url| init_metadata(self, url));
if let Some(ref response) = self.internal_response {
match response.url {
Some(ref url) => {
let unsafe_metadata = init_metadata(response, url);
match self.response_type {
ResponseType::Basic => Ok(FetchMetadata::Filtered {
filtered: FilteredMetadata::Basic(metadata.unwrap()),
unsafe_: unsafe_metadata,
}),
ResponseType::Cors => Ok(FetchMetadata::Filtered {
filtered: FilteredMetadata::Cors(metadata.unwrap()),
unsafe_: unsafe_metadata,
}),
ResponseType::Default => unreachable!(),
ResponseType::Error(ref network_err) => Err(network_err.clone()),
ResponseType::Opaque => Ok(FetchMetadata::Filtered {
filtered: FilteredMetadata::Opaque,
unsafe_: unsafe_metadata,
}),
ResponseType::OpaqueRedirect => Ok(FetchMetadata::Filtered {
filtered: FilteredMetadata::OpaqueRedirect,
unsafe_: unsafe_metadata,
}),
}
},
None => Err(NetworkError::Internal(
"No url found in unsafe response".to_owned(),
)),
}
} else {
assert_eq!(self.response_type, ResponseType::Default);
Ok(FetchMetadata::Unfiltered(metadata.unwrap()))
}
}
}