diff options
Diffstat (limited to 'components')
-rw-r--r-- | components/net/Cargo.toml | 2 | ||||
-rw-r--r-- | components/net/connector.rs | 106 | ||||
-rw-r--r-- | components/net/decoder.rs | 483 | ||||
-rw-r--r-- | components/net/http_loader.rs | 61 | ||||
-rw-r--r-- | components/net/lib.rs | 1 |
5 files changed, 505 insertions, 148 deletions
diff --git a/components/net/Cargo.toml b/components/net/Cargo.toml index 8840d09a2e5..943e693da1d 100644 --- a/components/net/Cargo.toml +++ b/components/net/Cargo.toml @@ -22,6 +22,7 @@ crossbeam-channel = "0.3" devtools_traits = {path = "../devtools_traits"} embedder_traits = { path = "../embedder_traits" } flate2 = "1" +futures = "0.1" headers-core = "0.0.1" headers-ext = "0.0.3" http = "0.1" @@ -31,6 +32,7 @@ hyper-openssl = "0.7" immeta = "0.4" ipc-channel = "0.11" lazy_static = "1" +libflate = "0.1" log = "0.4" malloc_size_of = { path = "../malloc_size_of" } malloc_size_of_derive = "0.1" diff --git a/components/net/connector.rs b/components/net/connector.rs index c93f82df5e2..1306c2d5609 100644 --- a/components/net/connector.rs +++ b/components/net/connector.rs @@ -3,9 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::hosts::replace_host; -use crate::http_loader::Decoder; -use flate2::read::GzDecoder; -use hyper::body::Payload; use hyper::client::connect::{Connect, Destination}; use hyper::client::HttpConnector as HyperHttpConnector; use hyper::rt::Future; @@ -13,9 +10,7 @@ use hyper::{Body, Client}; use hyper_openssl::HttpsConnector; use openssl::ssl::{SslConnector, SslConnectorBuilder, SslMethod, SslOptions}; use openssl::x509; -use std::io::{Cursor, Read}; use tokio::prelude::future::Executor; -use tokio::prelude::{Async, Stream}; pub const BUF_SIZE: usize = 32768; @@ -47,105 +42,6 @@ impl Connect for HttpConnector { } pub type Connector = HttpsConnector<HttpConnector>; -pub struct WrappedBody { - pub body: Body, - pub decoder: Decoder, -} - -impl WrappedBody { - pub fn new(body: Body) -> Self { - Self::new_with_decoder(body, Decoder::Plain) - } - - pub fn new_with_decoder(body: Body, decoder: Decoder) -> Self { - WrappedBody { body, decoder } - } -} - -impl Payload for WrappedBody { - type Data = <Body as Payload>::Data; - type Error = <Body as Payload>::Error; - fn poll_data(&mut self) -> Result<Async<Option<Self::Data>>, Self::Error> { - self.body.poll_data() - } -} - -impl Stream for WrappedBody { - type Item = <Body as Stream>::Item; - type Error = <Body as Stream>::Error; - fn poll(&mut self) -> Result<Async<Option<Self::Item>>, Self::Error> { - self.body.poll().map(|res| { - res.map(|maybe_chunk| { - if let Some(chunk) = maybe_chunk { - match self.decoder { - Decoder::Plain => Some(chunk), - Decoder::Gzip(Some(ref mut decoder)) => { - let mut buf = vec![0; BUF_SIZE]; - decoder.get_mut().get_mut().extend(chunk.as_ref()); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Gzip(None) => { - let mut buf = vec![0; BUF_SIZE]; - let mut decoder = GzDecoder::new(Cursor::new(chunk.into_bytes())); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - self.decoder = Decoder::Gzip(Some(decoder)); - Some(buf.into()) - }, - Decoder::Deflate(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - decoder.get_mut().get_mut().extend(chunk.as_ref()); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Brotli(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - decoder.get_mut().get_mut().extend(chunk.as_ref()); - let len = decoder.read(&mut buf).ok()?; - buf.truncate(len); - Some(buf.into()) - }, - } - } else { - // Hyper is done downloading but we still have uncompressed data - match self.decoder { - Decoder::Gzip(Some(ref mut decoder)) => { - let mut buf = vec![0; BUF_SIZE]; - let len = decoder.read(&mut buf).ok()?; - if len == 0 { - return None; - } - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Deflate(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - let len = decoder.read(&mut buf).ok()?; - if len == 0 { - return None; - } - buf.truncate(len); - Some(buf.into()) - }, - Decoder::Brotli(ref mut decoder) => { - let mut buf = vec![0; BUF_SIZE]; - let len = decoder.read(&mut buf).ok()?; - if len == 0 { - return None; - } - buf.truncate(len); - Some(buf.into()) - }, - _ => None, - } - } - }) - }) - } -} pub fn create_ssl_connector_builder(certs: &str) -> SslConnectorBuilder { // certs include multiple certificates. We could add all of them at once, @@ -189,7 +85,7 @@ pub fn create_ssl_connector_builder(certs: &str) -> SslConnectorBuilder { pub fn create_http_client<E>( ssl_connector_builder: SslConnectorBuilder, executor: E, -) -> Client<Connector, WrappedBody> +) -> Client<Connector, Body> where E: Executor<Box<dyn Future<Error = (), Item = ()> + Send + 'static>> + Sync + Send + 'static, { diff --git a/components/net/decoder.rs b/components/net/decoder.rs new file mode 100644 index 00000000000..bad25eb10cd --- /dev/null +++ b/components/net/decoder.rs @@ -0,0 +1,483 @@ +/* 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/. */ + +//! Adapted from an implementation in reqwest. + +/*! +A potentially non-blocking response decoder. + +The decoder wraps a stream of chunks and produces a new stream of decompressed chunks. +The decompressed chunks aren't guaranteed to align to the compressed ones. + +If the response is plaintext then no additional work is carried out. +Chunks are just passed along. + +If the response is gzip, then the chunks are decompressed into a buffer. +Slices of that buffer are emitted as new chunks. + +This module consists of a few main types: + +- `ReadableChunks` is a `Read`-like wrapper around a stream +- `Decoder` is a layer over `ReadableChunks` that applies the right decompression + +The following types directly support the gzip compression case: + +- `Pending` is a non-blocking constructor for a `Decoder` in case the body needs to be checked for EOF +- `Peeked` is a buffer that keeps a few bytes available so `libflate`s `read_exact` calls won't fail +*/ + +use crate::connector::BUF_SIZE; +use brotli::Decompressor; +use bytes::{Buf, BufMut, BytesMut}; +use flate2::read::DeflateDecoder; +use futures::{Async, Future, Poll, Stream}; +use hyper::header::{HeaderValue, CONTENT_ENCODING, TRANSFER_ENCODING}; +use hyper::{self, Body, Chunk, Response}; +use libflate::non_blocking::gzip; +use std::cmp; +use std::fmt; +use std::io::{self, Read}; +use std::mem; + +pub enum Error { + Io(io::Error), + Hyper(hyper::error::Error), +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Error { + Error::Io(err) + } +} + +impl From<hyper::error::Error> for Error { + fn from(err: hyper::error::Error) -> Error { + Error::Hyper(err) + } +} + +const INIT_BUFFER_SIZE: usize = 8192; + +/// A response decompressor over a non-blocking stream of chunks. +/// +/// The inner decoder may be constructed asynchronously. +pub struct Decoder { + inner: Inner, +} + +#[derive(PartialEq)] +enum DecoderType { + Gzip, + Brotli, + Deflate, +} + +enum Inner { + /// A `PlainText` decoder just returns the response content as is. + PlainText(Body), + /// A `Gzip` decoder will uncompress the gzipped response content before returning it. + Gzip(Gzip), + /// A `Delfate` decoder will uncompress the inflated response content before returning it. + Deflate(Deflate), + /// A `Brotli` decoder will uncompress the brotli-encoded response content before returning it. + Brotli(Brotli), + /// A decoder that doesn't have a value yet. + Pending(Pending), +} + +/// A future attempt to poll the response body for EOF so we know whether to use gzip or not. +struct Pending { + body: ReadableChunks<Body>, + type_: DecoderType, +} + +/// A gzip decoder that reads from a `libflate::gzip::Decoder` into a `BytesMut` and emits the results +/// as a `Chunk`. +struct Gzip { + inner: Box<gzip::Decoder<Peeked<ReadableChunks<Body>>>>, + buf: BytesMut, +} + +impl fmt::Debug for Decoder { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Decoder").finish() + } +} + +impl Decoder { + /// A plain text decoder. + /// + /// This decoder will emit the underlying chunks as-is. + #[inline] + fn plain_text(body: Body) -> Decoder { + Decoder { + inner: Inner::PlainText(body), + } + } + + /// A pending decoder. + /// + /// This decoder will buffer and decompress chunks that are encoded in the expected format. + #[inline] + fn pending(body: Body, type_: DecoderType) -> Decoder { + Decoder { + inner: Inner::Pending(Pending { + body: ReadableChunks::new(body), + type_: type_, + }), + } + } + + /// Constructs a Decoder from a hyper request. + /// + /// A decoder is just a wrapper around the hyper request that knows + /// how to decode the content body of the request. + /// + /// Uses the correct variant by inspecting the Content-Encoding header. + pub fn detect(response: Response<Body>) -> Response<Decoder> { + let values = response + .headers() + .get_all(CONTENT_ENCODING) + .iter() + .chain(response.headers().get_all(TRANSFER_ENCODING).iter()); + let decoder = values.fold(None, |acc, enc| { + acc.or_else(|| { + if enc == HeaderValue::from_static("gzip") { + Some(DecoderType::Gzip) + } else if enc == HeaderValue::from_static("br") { + Some(DecoderType::Brotli) + } else if enc == HeaderValue::from_static("deflate") { + Some(DecoderType::Deflate) + } else { + None + } + }) + }); + match decoder { + Some(type_) => response.map(|r| Decoder::pending(r, type_)), + None => response.map(Decoder::plain_text), + } + } +} + +impl Stream for Decoder { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { + // Do a read or poll for a pending decoder value. + let new_value = match self.inner { + Inner::Pending(ref mut future) => match future.poll() { + Ok(Async::Ready(inner)) => inner, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e.into()), + }, + Inner::PlainText(ref mut body) => return body.poll().map_err(|e| e.into()), + Inner::Gzip(ref mut decoder) => return decoder.poll(), + Inner::Brotli(ref mut decoder) => return decoder.poll(), + Inner::Deflate(ref mut decoder) => return decoder.poll(), + }; + + self.inner = new_value; + self.poll() + } +} + +impl Future for Pending { + type Item = Inner; + type Error = hyper::error::Error; + + fn poll(&mut self) -> Poll<Self::Item, Self::Error> { + let body_state = match self.body.poll_stream() { + Ok(Async::Ready(state)) => state, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => return Err(e), + }; + + let body = mem::replace(&mut self.body, ReadableChunks::new(Body::empty())); + // libflate does a read_exact([0; 2]), so its impossible to tell + // if the stream was empty, or truly had an UnexpectedEof. + // Therefore, we need to check for EOF first. + match body_state { + StreamState::Eof => Ok(Async::Ready(Inner::PlainText(Body::empty()))), + StreamState::HasMore => Ok(Async::Ready(match self.type_ { + DecoderType::Gzip => Inner::Gzip(Gzip::new(body)), + DecoderType::Brotli => Inner::Brotli(Brotli::new(body)), + DecoderType::Deflate => Inner::Deflate(Deflate::new(body)), + })), + } + } +} + +impl Gzip { + fn new(stream: ReadableChunks<Body>) -> Self { + Gzip { + buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), + inner: Box::new(gzip::Decoder::new(Peeked::new(stream))), + } + } +} + +#[allow(unsafe_code)] +fn poll_with_read(reader: &mut Read, buf: &mut BytesMut) -> Poll<Option<Chunk>, Error> { + if buf.remaining_mut() == 0 { + buf.reserve(INIT_BUFFER_SIZE); + } + + // The buffer contains uninitialised memory so getting a readable slice is unsafe. + // We trust the reader not to read from the memory given. + // + // To be safe, this memory could be zeroed before passing to the reader. + // Otherwise we might need to deal with the case where the reader panics. + let read = { + let mut buf = unsafe { buf.bytes_mut() }; + reader.read(&mut buf) + }; + + match read { + Ok(read) if read == 0 => Ok(Async::Ready(None)), + Ok(read) => { + unsafe { buf.advance_mut(read) }; + let chunk = Chunk::from(buf.split_to(read).freeze()); + + Ok(Async::Ready(Some(chunk))) + }, + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => Ok(Async::NotReady), + Err(e) => Err(e.into()), + } +} + +impl Stream for Gzip { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { + poll_with_read(&mut self.inner, &mut self.buf) + } +} + +/// A brotli decoder that reads from a `brotli::Decompressor` into a `BytesMut` and emits the results +/// as a `Chunk`. +struct Brotli { + inner: Box<Decompressor<Peeked<ReadableChunks<Body>>>>, + buf: BytesMut, +} + +impl Brotli { + fn new(stream: ReadableChunks<Body>) -> Self { + Self { + buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), + inner: Box::new(Decompressor::new(Peeked::new(stream), BUF_SIZE)), + } + } +} + +impl Stream for Brotli { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { + poll_with_read(&mut self.inner, &mut self.buf) + } +} + +/// A deflate decoder that reads from a `deflate::Decoder` into a `BytesMut` and emits the results +/// as a `Chunk`. +struct Deflate { + inner: Box<DeflateDecoder<Peeked<ReadableChunks<Body>>>>, + buf: BytesMut, +} + +impl Deflate { + fn new(stream: ReadableChunks<Body>) -> Self { + Self { + buf: BytesMut::with_capacity(INIT_BUFFER_SIZE), + inner: Box::new(DeflateDecoder::new(Peeked::new(stream))), + } + } +} + +impl Stream for Deflate { + type Item = Chunk; + type Error = Error; + + fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> { + poll_with_read(&mut self.inner, &mut self.buf) + } +} + +/// A `Read`able wrapper over a stream of chunks. +pub struct ReadableChunks<S> { + state: ReadState, + stream: S, +} + +enum ReadState { + /// A chunk is ready to be read from. + Ready(Chunk), + /// The next chunk isn't ready yet. + NotReady, + /// The stream has finished. + Eof, +} + +enum StreamState { + /// More bytes can be read from the stream. + HasMore, + /// No more bytes can be read from the stream. + Eof, +} + +/// A buffering reader that ensures `Read`s return at least a few bytes. +struct Peeked<R> { + state: PeekedState, + peeked_buf: [u8; 10], + pos: usize, + inner: R, +} + +enum PeekedState { + /// The internal buffer hasn't filled yet. + NotReady, + /// The internal buffer can be read. + Ready(usize), +} + +impl<R> Peeked<R> { + #[inline] + fn new(inner: R) -> Self { + Peeked { + state: PeekedState::NotReady, + peeked_buf: [0; 10], + inner: inner, + pos: 0, + } + } + + #[inline] + fn ready(&mut self) { + self.state = PeekedState::Ready(self.pos); + self.pos = 0; + } + + #[inline] + fn not_ready(&mut self) { + self.state = PeekedState::NotReady; + self.pos = 0; + } +} + +impl<R: Read> Read for Peeked<R> { + #[inline] + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + loop { + match self.state { + PeekedState::Ready(peeked_buf_len) => { + let len = cmp::min(buf.len(), peeked_buf_len - self.pos); + let start = self.pos; + let end = self.pos + len; + + buf[..len].copy_from_slice(&self.peeked_buf[start..end]); + self.pos += len; + if self.pos == peeked_buf_len { + self.not_ready(); + } + + return Ok(len); + }, + PeekedState::NotReady => { + let read = self.inner.read(&mut self.peeked_buf[self.pos..]); + + match read { + Ok(0) => self.ready(), + Ok(read) => { + self.pos += read; + if self.pos == self.peeked_buf.len() { + self.ready(); + } + }, + Err(e) => return Err(e), + } + }, + }; + } + } +} + +impl<S> ReadableChunks<S> { + #[inline] + fn new(stream: S) -> Self { + ReadableChunks { + state: ReadState::NotReady, + stream: stream, + } + } +} + +impl<S> fmt::Debug for ReadableChunks<S> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("ReadableChunks").finish() + } +} + +impl<S> Read for ReadableChunks<S> +where + S: Stream<Item = Chunk, Error = hyper::error::Error>, +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { + loop { + let ret; + match self.state { + ReadState::Ready(ref mut chunk) => { + let len = cmp::min(buf.len(), chunk.remaining()); + + buf[..len].copy_from_slice(&chunk[..len]); + chunk.advance(len); + if chunk.is_empty() { + ret = len; + } else { + return Ok(len); + } + }, + ReadState::NotReady => match self.poll_stream() { + Ok(Async::Ready(StreamState::HasMore)) => continue, + Ok(Async::Ready(StreamState::Eof)) => return Ok(0), + Ok(Async::NotReady) => return Err(io::ErrorKind::WouldBlock.into()), + Err(e) => { + return Err(io::Error::new(io::ErrorKind::Other, e)); + }, + }, + ReadState::Eof => return Ok(0), + } + self.state = ReadState::NotReady; + return Ok(ret); + } + } +} + +impl<S> ReadableChunks<S> +where + S: Stream<Item = Chunk, Error = hyper::error::Error>, +{ + /// Poll the readiness of the inner reader. + /// + /// This function will update the internal state and return a simplified + /// version of the `ReadState`. + fn poll_stream(&mut self) -> Poll<StreamState, hyper::error::Error> { + match self.stream.poll() { + Ok(Async::Ready(Some(chunk))) => { + self.state = ReadState::Ready(chunk); + + Ok(Async::Ready(StreamState::HasMore)) + }, + Ok(Async::Ready(None)) => { + self.state = ReadState::Eof; + + Ok(Async::Ready(StreamState::Eof)) + }, + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(e) => Err(e), + } + } +} diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 1290d7754b3..db8c607ef1a 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -2,9 +2,10 @@ * 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/. */ -use crate::connector::{create_http_client, Connector, WrappedBody, BUF_SIZE}; +use crate::connector::{create_http_client, Connector}; use crate::cookie; use crate::cookie_storage::CookieStorage; +use crate::decoder::Decoder; use crate::fetch::cors_cache::CorsCache; use crate::fetch::methods::{ is_cors_safelisted_method, is_cors_safelisted_request_header, main_fetch, @@ -13,14 +14,11 @@ use crate::fetch::methods::{Data, DoneChannel, FetchContext, Target}; use crate::hsts::HstsList; use crate::http_cache::HttpCache; use crate::resource_thread::AuthCache; -use brotli::Decompressor; -use bytes::Bytes; use crossbeam_channel::{unbounded, Sender}; use devtools_traits::{ ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest, }; use devtools_traits::{HttpResponse as DevtoolsHttpResponse, NetworkEvent}; -use flate2::read::{DeflateDecoder, GzDecoder}; use headers_core::HeaderMapExt; use headers_ext::{AccessControlAllowCredentials, AccessControlAllowHeaders}; use headers_ext::{ @@ -49,7 +47,6 @@ use openssl::ssl::SslConnectorBuilder; use servo_url::{ImmutableOrigin, ServoUrl}; use std::collections::{HashMap, HashSet}; use std::error::Error; -use std::io::Cursor; use std::iter::FromIterator; use std::mem; use std::ops::Deref; @@ -71,7 +68,7 @@ pub struct HttpState { pub http_cache: RwLock<HttpCache>, pub auth_cache: RwLock<AuthCache>, pub history_states: RwLock<HashMap<HistoryStateId, Vec<u8>>>, - pub client: Client<Connector, WrappedBody>, + pub client: Client<Connector, Body>, } impl HttpState { @@ -266,31 +263,6 @@ fn set_cookies_from_headers( } } -impl Decoder { - fn from_http_response(response: &HyperResponse<Body>) -> Decoder { - if let Some(encoding) = response.headers().typed_get::<ContentEncoding>() { - if encoding.contains("gzip") { - Decoder::Gzip(None) - } else if encoding.contains("deflate") { - Decoder::Deflate(DeflateDecoder::new(Cursor::new(Bytes::new()))) - } else if encoding.contains("br") { - Decoder::Brotli(Decompressor::new(Cursor::new(Bytes::new()), BUF_SIZE)) - } else { - Decoder::Plain - } - } else { - Decoder::Plain - } - } -} - -pub enum Decoder { - Gzip(Option<GzDecoder<Cursor<Bytes>>>), - Deflate(DeflateDecoder<Cursor<Bytes>>), - Brotli(Decompressor<Cursor<Bytes>>), - Plain, -} - fn prepare_devtools_request( request_id: String, url: ServoUrl, @@ -367,7 +339,7 @@ fn auth_from_cache( } fn obtain_response( - client: &Client<Connector, WrappedBody>, + client: &Client<Connector, Body>, url: &ServoUrl, method: &Method, request_headers: &HeaderMap, @@ -379,10 +351,7 @@ fn obtain_response( is_xhr: bool, ) -> Box< dyn Future< - Item = ( - HyperResponse<WrappedBody>, - Option<ChromeToDevtoolsControlMsg>, - ), + Item = (HyperResponse<Decoder>, Option<ChromeToDevtoolsControlMsg>), Error = NetworkError, >, > { @@ -423,7 +392,7 @@ fn obtain_response( .replace("{", "%7B") .replace("}", "%7D"), ) - .body(WrappedBody::new(request_body.clone().into())); + .body(request_body.clone().into()); let mut request = match request { Ok(request) => request, @@ -474,11 +443,7 @@ fn obtain_response( debug!("Not notifying devtools (no request_id)"); None }; - let decoder = Decoder::from_http_response(&res); - Ok(( - res.map(move |r| WrappedBody::new_with_decoder(r, decoder)), - msg, - )) + Ok((Decoder::detect(res), msg)) }) .map_err(move |e| NetworkError::from_hyper_error(&e)), ) @@ -1265,6 +1230,7 @@ fn http_network_fetch( } *res_body.lock().unwrap() = ResponseBody::Receiving(vec![]); + let res_body2 = res_body.clone(); if let Some(ref sender) = devtools_sender { if let Some(m) = msg { @@ -1285,6 +1251,7 @@ fn http_network_fetch( } let done_sender2 = done_sender.clone(); + let done_sender3 = done_sender.clone(); HANDLE.lock().unwrap().spawn( res.into_body() .map_err(|_| ()) @@ -1311,7 +1278,15 @@ fn http_network_fetch( let _ = done_sender2.send(Data::Done); future::ok(()) }) - .map_err(|_| ()), + .map_err(move |_| { + let mut body = res_body2.lock().unwrap(); + let completed_body = match *body { + ResponseBody::Receiving(ref mut body) => mem::replace(body, vec![]), + _ => vec![], + }; + *body = ResponseBody::Done(completed_body); + let _ = done_sender3.send(Data::Done); + }), ); // TODO these substeps aren't possible yet diff --git a/components/net/lib.rs b/components/net/lib.rs index d5943fa380c..fb3c6fff78b 100644 --- a/components/net/lib.rs +++ b/components/net/lib.rs @@ -21,6 +21,7 @@ pub mod connector; pub mod cookie; pub mod cookie_storage; mod data_loader; +mod decoder; pub mod filemanager_thread; mod hosts; pub mod hsts; |