{"action":"created","comment":{"url":"https://api.github.com/repos/paritytech/parity/pulls/comments/174220116","pull_request_review_id":103527208,"id":174220116,"diff_hunk":"@@ -14,347 +14,554 @@\n // You should have received a copy of the GNU General Public License\n // along with Parity.  If not, see <http://www.gnu.org/licenses/>.\n \n-//! Fetching\n-\n-use std::{io, fmt, time};\n+use futures::future::{self, Loop};\n+use futures::sync::{mpsc, oneshot};\n+use futures::{self, Future, Async, Sink, Stream};\n+use futures_timer::FutureExt;\n+use hyper::header::{UserAgent, Location, ContentLength, ContentType};\n+use hyper::mime::Mime;\n+use hyper::{self, Request, Method, StatusCode};\n+use hyper_rustls;\n+use parking_lot::{Condvar, Mutex};\n use std::sync::Arc;\n-use std::sync::atomic::{self, AtomicBool};\n-\n-use futures::{self, Future};\n-use futures_cpupool::{CpuPool, CpuFuture};\n-use parking_lot::RwLock;\n-use reqwest;\n-use reqwest::mime::Mime;\n-\n-type BoxFuture<A, B> = Box<Future<Item = A, Error = B> + Send>;\n-\n-/// Fetch abort control\n-#[derive(Default, Debug, Clone)]\n-pub struct Abort(Arc<AtomicBool>);\n+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};\n+use std::thread;\n+use std::time::Duration;\n+use std::{cmp, io, fmt, mem};\n+use tokio_core::reactor;\n+use url::{self, Url};\n+\n+const MAX_SIZE: usize = 64 * 1024 * 1024;\n+const MAX_SECS: u64 = 5;\n+const MAX_REDR: usize = 5;\n+\n+/// A handle to abort requests.\n+///\n+/// Requests are either aborted based on reaching thresholds such as\n+/// maximum response size, timeouts or too many redirects, or else\n+/// they can be aborted explicitly by the calling code.\n+#[derive(Clone, Debug)]\n+pub struct Abort {\n+\tabort: Arc<AtomicBool>,\n+\tsize: usize,\n+\ttime: Duration,\n+\tredir: usize,\n+}\n \n-impl Abort {\n-\t/// Returns `true` if request is aborted.\n-\tpub fn is_aborted(&self) -> bool {\n-\t\tself.0.load(atomic::Ordering::SeqCst)\n+impl Default for Abort {\n+\tfn default() -> Abort {\n+\t\tAbort {\n+\t\t\tabort: Arc::new(AtomicBool::new(false)),\n+\t\t\tsize: MAX_SIZE,\n+\t\t\ttime: Duration::from_secs(MAX_SECS),\n+\t\t\tredir: MAX_REDR,\n+\t\t}\n \t}\n }\n \n impl From<Arc<AtomicBool>> for Abort {\n-\tfn from(a: Arc<AtomicBool>) -> Self {\n-\t\tAbort(a)\n+\tfn from(a: Arc<AtomicBool>) -> Abort {\n+\t\tAbort {\n+\t\t\tabort: a,\n+\t\t\tsize: MAX_SIZE,\n+\t\t\ttime: Duration::from_secs(MAX_SECS),\n+\t\t\tredir: MAX_REDR,\n+\t\t}\n \t}\n }\n \n-/// Fetch\n-pub trait Fetch: Clone + Send + Sync + 'static {\n-\t/// Result type\n-\ttype Result: Future<Item=Response, Error=Error> + Send + 'static;\n+impl Abort {\n+\t/// True if `abort` has been invoked.\n+\tpub fn is_aborted(&self) -> bool {\n+\t\tself.abort.load(Ordering::SeqCst)\n+\t}\n \n-\t/// Creates new Fetch object.\n-\tfn new() -> Result<Self, Error> where Self: Sized;\n+\t/// The maximum response body size.\n+\tpub fn max_size(&self) -> usize {\n+\t\tself.size\n+\t}\n \n-\t/// Spawn the future in context of this `Fetch` thread pool.\n-\t/// Implementation is optional.\n-\tfn process<F, I, E>(&self, f: F) -> BoxFuture<I, E> where\n-\t\tF: Future<Item=I, Error=E> + Send + 'static,\n-\t\tI: Send + 'static,\n-\t\tE: Send + 'static,\n-\t{\n-\t\tBox::new(f)\n+\t/// The maximum total time, including redirects.\n+\tpub fn max_duration(&self) -> Duration {\n+\t\tself.time\n \t}\n \n-\t/// Spawn the future in context of this `Fetch` thread pool as \"fire and forget\", i.e. dropping this future without\n-\t/// canceling the underlying future.\n-\t/// Implementation is optional.\n-\tfn process_and_forget<F, I, E>(&self, _: F) where\n-\t\tF: Future<Item=I, Error=E> + Send + 'static,\n-\t\tI: Send + 'static,\n-\t\tE: Send + 'static,\n-\t{\n-\t\tpanic!(\"Attempting to process and forget future on unsupported Fetch.\");\n+\t/// The maximum number of redirects to allow.\n+\tpub fn max_redirects(&self) -> usize {\n+\t\tself.redir\n \t}\n \n-\t/// Fetch URL and get a future for the result.\n-\t/// Supports aborting the request in the middle of execution.\n-\tfn fetch_with_abort(&self, url: &str, abort: Abort) -> Self::Result;\n+\t/// Mark as aborted.\n+\tpub fn abort(&self) {\n+\t\tself.abort.store(true, Ordering::SeqCst)\n+\t}\n \n-\t/// Fetch URL and get a future for the result.\n-\tfn fetch(&self, url: &str) -> Self::Result {\n-\t\tself.fetch_with_abort(url, Default::default())\n+\t/// Set the maximum reponse body size.\n+\tpub fn with_max_size(self, n: usize) -> Abort {\n+\t\tAbort { size: n, .. self }\n \t}\n \n-\t/// Fetch URL and get the result synchronously.\n-\tfn fetch_sync(&self, url: &str) -> Result<Response, Error> {\n-\t\tself.fetch(url).wait()\n+\t/// Set the maximum duration (including redirects).\n+\tpub fn with_max_duration(self, d: Duration) -> Abort {\n+\t\tAbort { time: d, .. self }\n \t}\n \n-\t/// Closes this client\n-\tfn close(self) where Self: Sized {}\n+\t/// Set the maximum number of redirects to follow.\n+\tpub fn with_max_redirects(self, n: usize) -> Abort {\n+\t\tAbort { redir: n, .. self }\n+\t}\n }\n \n-const CLIENT_TIMEOUT_SECONDS: u64 = 5;\n+/// Types which retrieve content from some URL.\n+pub trait Fetch: Clone + Send + Sync + 'static {\n+\t/// The result future.\n+\ttype Result: Future<Item=Response, Error=Error> + Send + 'static;\n \n-/// Fetch client\n+\t/// Get content from some URL.\n+\tfn fetch(&self, url: &str, abort: Abort) -> Self::Result;\n+}\n+\n+type TxResponse = oneshot::Sender<Result<Response, Error>>;\n+type StartupCond = Arc<(Mutex<Result<(), io::Error>>, Condvar)>;\n+type ChanItem = Option<(Url, Abort, TxResponse)>;\n+\n+/// An implementation of `Fetch` using a `hyper` client.\n+// Due to the `Send` bound of `Fetch` we spawn a background thread for\n+// actual request/response processing as `hyper::Client` itself does\n+// not implement `Send` currently.\n+#[derive(Debug)]\n pub struct Client {\n-\tclient: RwLock<(time::Instant, Arc<reqwest::Client>)>,\n-\tpool: CpuPool,\n-\tlimit: Option<usize>,\n+\tcore: mpsc::Sender<ChanItem>,\n+\trefs: Arc<AtomicUsize>,\n }\n \n+// When cloning a client we increment the internal reference counter.\n impl Clone for Client {\n-\tfn clone(&self) -> Self {\n-\t\tlet (ref time, ref client) = *self.client.read();\n+\tfn clone(&self) -> Client {\n+\t\tself.refs.fetch_add(1, Ordering::SeqCst);\n \t\tClient {\n-\t\t\tclient: RwLock::new((time.clone(), client.clone())),\n-\t\t\tpool: self.pool.clone(),\n-\t\t\tlimit: self.limit.clone(),\n+\t\t\tcore: self.core.clone(),\n+\t\t\trefs: self.refs.clone(),\n \t\t}\n \t}\n }\n \n-impl Client {\n-\tfn new_client() -> Result<Arc<reqwest::Client>, Error> {\n-\t\tlet mut client = reqwest::ClientBuilder::new();\n-\t\tclient.redirect(reqwest::RedirectPolicy::limited(5));\n-\t\tOk(Arc::new(client.build()?))\n+// When dropping a client, we decrement the reference counter.\n+// Once it reaches 0 we terminate the background thread.\n+impl Drop for Client {\n+\tfn drop(&mut self) {\n+\t\tif self.refs.fetch_sub(1, Ordering::SeqCst) == 1 {\n+\t\t\t// ignore send error as it means the background thread is gone already\n+\t\t\tlet _ = self.core.clone().send(None).wait();\n+\t\t}\n \t}\n+}\n+\n+impl Client {\n+\t/// Create a new fetch client.\n+\tpub fn new() -> Result<Self, Error> {\n+\t\tlet startup_done = Arc::new((Mutex::new(Ok(())), Condvar::new()));\n+\t\tlet (tx_proto, rx_proto) = mpsc::channel(64);\n+\n+\t\tClient::background_thread(startup_done.clone(), rx_proto)?;\n+\n+\t\tlet mut guard = startup_done.0.lock();\n+\t\tlet startup_result = startup_done.1.wait_for(&mut guard, Duration::from_secs(3));\n+\n+\t\tif startup_result.timed_out() {\n+\t\t\terror!(target: \"fetch\", \"timeout starting background thread\");\n+\t\t\treturn Err(Error::BackgroundThreadDead)\n+\t\t}\n+\t\tif let Err(e) = mem::replace(&mut *guard, Ok(())) {\n+\t\t\terror!(target: \"fetch\", \"error starting background thread: {}\", e);\n+\t\t\treturn Err(e.into())\n+\t\t}\n \n-\tfn with_limit(limit: Option<usize>) -> Result<Self, Error> {\n \t\tOk(Client {\n-\t\t\tclient: RwLock::new((time::Instant::now(), Self::new_client()?)),\n-\t\t\tpool: CpuPool::new(4),\n-\t\t\tlimit: limit,\n+\t\t\tcore: tx_proto,\n+\t\t\trefs: Arc::new(AtomicUsize::new(1)),\n \t\t})\n \t}\n \n-\t/// Sets a limit on the maximum download size.\n-\tpub fn set_limit(&mut self, limit: Option<usize>) {\n-\t\tself.limit = limit\n+\tfn background_thread(start: StartupCond, rx_proto: mpsc::Receiver<ChanItem>) -> io::Result<thread::JoinHandle<()>> {\n+\t\tthread::Builder::new().name(\"fetch\".into()).spawn(move || {\n+\t\t\tlet mut core = match reactor::Core::new() {\n+\t\t\t\tOk(c)  => c,\n+\t\t\t\tErr(e) => {\n+\t\t\t\t\t*start.0.lock() = Err(e);\n+\t\t\t\t\tstart.1.notify_one();\n+\t\t\t\t\treturn ()\n+\t\t\t\t}\n+\t\t\t};\n+\t\t\tlet handle = core.handle();\n+\t\t\tlet hyper = hyper::Client::configure()\n+\t\t\t\t.connector(hyper_rustls::HttpsConnector::new(4, &core.handle()))\n+\t\t\t\t.build(&core.handle());\n+\n+\t\t\tlet future = rx_proto.take_while(|item| Ok(item.is_some()))\n+\t\t\t\t.map(|item| item.expect(\"`take_while` is only passing on channel items != None\"))\n+\t\t\t\t.for_each(|(url, abort, sender)|\n+\t\t\t{\n+\t\t\t\ttrace!(target: \"fetch\", \"new request to {}\", url);\n+\t\t\t\tif abort.is_aborted() {\n+\t\t\t\t\treturn future::ok(sender.send(Err(Error::Aborted)).unwrap_or(()))\n+\t\t\t\t}\n+\t\t\t\tlet ini = (hyper.clone(), url, abort, 0);\n+\t\t\t\tlet fut = future::loop_fn(ini, |(client, url, abort, redirects)| {\n+\t\t\t\t\tlet url2 = url.clone();\n+\t\t\t\t\tlet abort2 = abort.clone();\n+\t\t\t\t\tclient.request(get(&url))\n+\t\t\t\t\t\t.map(move |resp| Response::new(url2, resp, abort2))\n+\t\t\t\t\t\t.from_err()\n+\t\t\t\t\t\t.and_then(move |resp| {\n+\t\t\t\t\t\t\tif abort.is_aborted() {\n+\t\t\t\t\t\t\t\tdebug!(target: \"fetch\", \"fetch of {} aborted\", url);\n+\t\t\t\t\t\t\t\treturn Err(Error::Aborted)\n+\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t\tif let Some(next_url) = redirect_location(url, &resp) {\n+\t\t\t\t\t\t\t\tif redirects >= abort.max_redirects() {\n+\t\t\t\t\t\t\t\t\treturn Err(Error::TooManyRedirects)\n+\t\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t\t\tOk(Loop::Continue((client, next_url, abort, redirects + 1)))\n+\t\t\t\t\t\t\t} else {\n+\t\t\t\t\t\t\t\tlet content_len = resp.headers.get::<ContentLength>().cloned();\n+\t\t\t\t\t\t\t\tif content_len.map(|n| *n > abort.max_size() as u64).unwrap_or(false) {\n+\t\t\t\t\t\t\t\t\treturn Err(Error::SizeLimit)\n+\t\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t\t\tOk(Loop::Break(resp))\n+\t\t\t\t\t\t\t}\n+\t\t\t\t\t\t})\n+\t\t\t\t\t})\n+\t\t\t\t\t.then(|result| {\n+\t\t\t\t\t\tfuture::ok(sender.send(result).unwrap_or(()))\n+\t\t\t\t\t});\n+\t\t\t\thandle.spawn(fut);\n+\t\t\t\ttrace!(target: \"fetch\", \"waiting for next request ...\");\n+\t\t\t\tfuture::ok(())\n+\t\t\t});\n+\n+\t\t\tstart.1.notify_one();\n+\n+\t\t\tdebug!(target: \"fetch\", \"processing requests ...\");\n+\t\t\tif let Err(()) = core.run(future) {\n+\t\t\t\terror!(target: \"fetch\", \"error while executing future\")\n+\t\t\t}\n+\t\t\tdebug!(target: \"fetch\", \"fetch background thread finished\")\n+\t\t})\n+\t}\n+}\n+\n+impl Fetch for Client {\n+\ttype Result = Box<Future<Item=Response, Error=Error> + Send>;\n+\n+\tfn fetch(&self, url: &str, abort: Abort) -> Self::Result {\n+\t\tdebug!(target: \"fetch\", \"fetching: {:?}\", url);\n+\t\tif abort.is_aborted() {\n+\t\t\treturn Box::new(future::err(Error::Aborted))\n+\t\t}\n+\t\tlet url: Url = match url.parse() {\n+\t\t\tOk(u) => u,\n+\t\t\tErr(e) => return Box::new(future::err(e.into()))\n+\t\t};\n+\t\tlet (tx_res, rx_res) = oneshot::channel();\n+\t\tlet maxdur = abort.max_duration();\n+\t\tlet sender = self.core.clone();\n+\t\tlet future = sender.send(Some((url.clone(), abort, tx_res)))\n+\t\t\t.map_err(|e| {\n+\t\t\t\terror!(target: \"fetch\", \"failed to schedule request: {}\", e);\n+\t\t\t\tError::BackgroundThreadDead\n+\t\t\t})\n+\t\t\t.and_then(|_| rx_res.map_err(|oneshot::Canceled| Error::BackgroundThreadDead))\n+\t\t\t.and_then(future::result)\n+\t\t\t.timeout(maxdur)\n+\t\t\t.map_err(|err| {\n+\t\t\t\tif let Error::Io(ref e) = err {\n+\t\t\t\t\tif let io::ErrorKind::TimedOut = e.kind() {\n+\t\t\t\t\t\treturn Error::Timeout\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\terr.into()\n+\t\t\t});\n+\t\tBox::new(future)\n \t}\n+}\n \n-\tfn client(&self) -> Result<Arc<reqwest::Client>, Error> {\n-\t\t{\n-\t\t\tlet (ref time, ref client) = *self.client.read();\n-\t\t\tif time.elapsed() < time::Duration::from_secs(CLIENT_TIMEOUT_SECONDS) {\n-\t\t\t\treturn Ok(client.clone());\n+// Extract redirect location from response.\n+fn redirect_location(u: Url, r: &Response) -> Option<Url> {\n+\tuse hyper::StatusCode::*;\n+\tmatch r.status() {\n+\t\tMovedPermanently\n+\t\t| PermanentRedirect\n+\t\t| TemporaryRedirect\n+\t\t| Found\n+\t\t| SeeOther => {\n+\t\t\tif let Some(loc) = r.headers.get::<Location>() {\n+\t\t\t\tu.join(loc).ok()\n+\t\t\t} else {\n+\t\t\t\tNone\n \t\t\t}\n \t\t}\n+\t\t_ => None\n+\t}\n+}\n+\n+// Build a simple GET request for the given Url.\n+fn get(u: &Url) -> hyper::Request {\n+\tlet uri = u.as_ref().parse().expect(\"Every valid URL is aso a URI.\");\n+\tlet mut rq = Request::new(Method::Get, uri);\n+\trq.headers_mut().set(UserAgent::new(\"Parity Fetch Neo\"));\n+\trq\n+}\n+\n+/// An HTTP response.\n+#[derive(Debug)]\n+pub struct Response {\n+\turl: Url,\n+\tstatus: StatusCode,\n+\theaders: hyper::Headers,\n+\tbody: hyper::Body,\n+\tabort: Abort,\n+\tnread: usize,\n+}\n+\n+impl Response {\n+\t/// Create a new response, wrapping a hyper response.\n+\tpub fn new(u: Url, r: hyper::Response, a: Abort) -> Response {\n+\t\tResponse {\n+\t\t\turl: u,\n+\t\t\tstatus: r.status(),\n+\t\t\theaders: r.headers().clone(),\n+\t\t\tbody: r.body(),\n+\t\t\tabort: a,\n+\t\t\tnread: 0,\n+\t\t}\n+\t}\n \n-\t\tlet client = Self::new_client()?;\n-\t\t*self.client.write() = (time::Instant::now(), client.clone());\n-\t\tOk(client)\n+\t/// The response status.\n+\tpub fn status(&self) -> StatusCode {\n+\t\tself.status\n \t}\n \n-\t/// Returns a handle to underlying CpuPool of this client.\n-\tpub fn pool(&self) -> CpuPool {\n-\t\tself.pool.clone()\n+\t/// Status code == OK (200)?\n+\tpub fn is_success(&self) -> bool {\n+\t\tself.status() == StatusCode::Ok\n \t}\n-}\n \n-impl Fetch for Client {\n-\ttype Result = CpuFuture<Response, Error>;\n-\n-\tfn new() -> Result<Self, Error> {\n-\t\t// Max 64MB will be downloaded.\n-\t\tSelf::with_limit(Some(64 * 1024 * 1024))\n-\t}\n-\n-\tfn process<F, I, E>(&self, f: F) -> BoxFuture<I, E> where\n-\t\tF: Future<Item=I, Error=E> + Send + 'static,\n-\t\tI: Send + 'static,\n-\t\tE: Send + 'static,\n-\t{\n-\t\tBox::new(self.pool.spawn(f))\n-\t}\n-\n-\tfn process_and_forget<F, I, E>(&self, f: F) where\n-\t\tF: Future<Item=I, Error=E> + Send + 'static,\n-\t\tI: Send + 'static,\n-\t\tE: Send + 'static,\n-\t{\n-\t\tself.pool.spawn(f).forget()\n-\t}\n-\n-\tfn fetch_with_abort(&self, url: &str, abort: Abort) -> Self::Result {\n-\t\tdebug!(target: \"fetch\", \"Fetching from: {:?}\", url);\n-\n-\t\tmatch self.client() {\n-\t\t\tOk(client) => {\n-\t\t\t\tself.pool.spawn(FetchTask {\n-\t\t\t\t\turl: url.into(),\n-\t\t\t\t\tclient: client,\n-\t\t\t\t\tlimit: self.limit,\n-\t\t\t\t\tabort: abort,\n-\t\t\t\t})\n-\t\t\t},\n-\t\t\tErr(err) => {\n-\t\t\t\tself.pool.spawn(futures::future::err(err))\n-\t\t\t},\n+\t/// Is the content-type text/html?\n+\tpub fn is_html(&self) -> bool {\n+\t\tif let Some(ref mime) = self.content_type() {\n+\t\t\tmime.type_() == \"text\" && mime.subtype() == \"html\"\n+\t\t} else {\n+\t\t\tfalse\n \t\t}\n \t}\n-}\n \n-struct FetchTask {\n-\turl: String,\n-\tclient: Arc<reqwest::Client>,\n-\tlimit: Option<usize>,\n-\tabort: Abort,\n+\t/// The conten-type header value.\n+\tpub fn content_type(&self) -> Option<Mime> {\n+\t\tself.headers.get::<ContentType>().map(|ct| ct.0.clone())\n+\t}\n }\n \n-impl Future for FetchTask {\n-\t// TODO [ToDr] timeouts handling?\n-\ttype Item = Response;\n+impl Stream for Response {\n+\ttype Item = hyper::Chunk;\n \ttype Error = Error;\n \n-\tfn poll(&mut self) -> futures::Poll<Self::Item, Self::Error> {\n+\tfn poll(&mut self) -> futures::Poll<Option<Self::Item>, Self::Error> {\n \t\tif self.abort.is_aborted() {\n-\t\t\ttrace!(target: \"fetch\", \"Fetch of {:?} aborted.\", self.url);\n-\t\t\treturn Err(Error::Aborted);\n+\t\t\tdebug!(target: \"fetch\", \"fetch of {} aborted\", self.url);\n+\t\t\treturn Err(Error::Aborted)\n \t\t}\n+\t\tmatch try_ready!(self.body.poll()) {\n+\t\t\tNone    => Ok(Async::Ready(None)),\n+\t\t\tSome(c) => {\n+\t\t\t\tif self.nread + c.len() > self.abort.max_size() {\n+\t\t\t\t\tdebug!(target: \"fetch\", \"size limit {:?} for {} exceeded\", self.abort.max_size(), self.url);\n+\t\t\t\t\treturn Err(Error::SizeLimit)\n+\t\t\t\t}\n+\t\t\t\tself.nread += c.len();\n+\t\t\t\tOk(Async::Ready(Some(c)))\n+\t\t\t}\n+\t\t}\n+\t}\n+}\n \n-\t\ttrace!(target: \"fetch\", \"Starting fetch task: {:?}\", self.url);\n-\t\tlet result = self.client.get(&self.url)\n-\t\t\t\t\t\t  .header(reqwest::header::UserAgent::new(\"Parity Fetch\"))\n-\t\t\t\t\t\t  .send()?;\n+/// `BodyReader` serves as an adapter from async to sync I/O.\n+///\n+/// It implements `io::Read` by repedately waiting for the next `Chunk`\n+/// of hyper's response `Body` which blocks the current thread.\n+pub struct BodyReader {\n+\tchunk:  hyper::Chunk,\n+\tbody:   Option<hyper::Body>,\n+\toffset: usize,\n+}\n \n-\t\tOk(futures::Async::Ready(Response {\n-\t\t\tinner: ResponseInner::Response(result),\n-\t\t\tabort: self.abort.clone(),\n-\t\t\tlimit: self.limit,\n-\t\t\tread: 0,\n-\t\t}))\n+impl BodyReader {\n+\t/// Create a new body reader for the given response.\n+\tpub fn new(r: Response) -> BodyReader {\n+\t\tBodyReader {\n+\t\t\tbody: Some(r.body),\n+\t\t\tchunk: Default::default(),\n+\t\t\toffset: 0,\n+\t\t}\n \t}\n }\n \n-/// Fetch Error\n+impl io::Read for BodyReader {\n+\tfn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {\n+\t\tlet mut m = 0;\n+\t\twhile self.body.is_some() {\n+\t\t\t// Can we still read from the current chunk?\n+\t\t\tif self.offset < self.chunk.len() {\n+\t\t\t\tlet k = cmp::min(self.chunk.len() - self.offset, buf.len() - m);","path":"util/fetch/src/client.rs","position":554,"original_position":554,"commit_id":"6e49a0948d441e805d5e375dc3b2a606fcf6a454","original_commit_id":"a3ef61cd4e8ad5d6cc4746549d2c5f379a3835cd","user":{"login":"tomusdrw","id":581548,"avatar_url":"https://avatars1.githubusercontent.com/u/581548?v=4","gravatar_id":"","url":"https://api.github.com/users/tomusdrw","html_url":"https://github.com/tomusdrw","followers_url":"https://api.github.com/users/tomusdrw/followers","following_url":"https://api.github.com/users/tomusdrw/following{/other_user}","gists_url":"https://api.github.com/users/tomusdrw/gists{/gist_id}","starred_url":"https://api.github.com/users/tomusdrw/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tomusdrw/subscriptions","organizations_url":"https://api.github.com/users/tomusdrw/orgs","repos_url":"https://api.github.com/users/tomusdrw/repos","events_url":"https://api.github.com/users/tomusdrw/events{/privacy}","received_events_url":"https://api.github.com/users/tomusdrw/received_events","type":"User","site_admin":false},"body":"Might be good to use a bit more descriptive names for `m` and `k`","created_at":"2018-03-13T17:32:54Z","updated_at":"2018-03-13T17:39:24Z","html_url":"https://github.com/paritytech/parity/pull/8099#discussion_r174220116","pull_request_url":"https://api.github.com/repos/paritytech/parity/pulls/8099","author_association":"CONTRIBUTOR","_links":{"self":{"href":"https://api.github.com/repos/paritytech/parity/pulls/comments/174220116"},"html":{"href":"https://github.com/paritytech/parity/pull/8099#discussion_r174220116"},"pull_request":{"href":"https://api.github.com/repos/paritytech/parity/pulls/8099"}}},"pull_request":{"url":"https://api.github.com/repos/paritytech/parity/pulls/8099","id":174632383,"html_url":"https://github.com/paritytech/parity/pull/8099","diff_url":"https://github.com/paritytech/parity/pull/8099.diff","patch_url":"https://github.com/paritytech/parity/pull/8099.patch","issue_url":"https://api.github.com/repos/paritytech/parity/issues/8099","number":8099,"state":"open","locked":false,"title":"Replace reqwest with hyper","user":{"login":"twittner","id":132165,"avatar_url":"https://avatars0.githubusercontent.com/u/132165?v=4","gravatar_id":"","url":"https://api.github.com/users/twittner","html_url":"https://github.com/twittner","followers_url":"https://api.github.com/users/twittner/followers","following_url":"https://api.github.com/users/twittner/following{/other_user}","gists_url":"https://api.github.com/users/twittner/gists{/gist_id}","starred_url":"https://api.github.com/users/twittner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/twittner/subscriptions","organizations_url":"https://api.github.com/users/twittner/orgs","repos_url":"https://api.github.com/users/twittner/repos","events_url":"https://api.github.com/users/twittner/events{/privacy}","received_events_url":"https://api.github.com/users/twittner/received_events","type":"User","site_admin":false},"body":"The approach taken is to spawn a background thread running a tokio `Core` and hyper `Client`, processing requests sent over a `mpsc` channel and send back responses over `oneshot` channels. Hyper client itself does not implement `Send` so this way we can keep the `Send` bound of the `Fetch` trait itself and make this a local change.\r\n\r\nTo accommodate existing synchronous uses of fetch responses an adapter `BodyReader` is provided which waits for response body `Chunk`s to arrive. \r\n\r\nResolves #7829.","created_at":"2018-03-13T11:37:13Z","updated_at":"2018-03-13T17:39:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"ee1ea7ef322e90833cc27242f2c75e71346df4f2","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/paritytech/parity/pulls/8099/commits","review_comments_url":"https://api.github.com/repos/paritytech/parity/pulls/8099/comments","review_comment_url":"https://api.github.com/repos/paritytech/parity/pulls/comments{/number}","comments_url":"https://api.github.com/repos/paritytech/parity/issues/8099/comments","statuses_url":"https://api.github.com/repos/paritytech/parity/statuses/6e49a0948d441e805d5e375dc3b2a606fcf6a454","head":{"label":"paritytech:replace_reqwest_with_hyper","ref":"replace_reqwest_with_hyper","sha":"6e49a0948d441e805d5e375dc3b2a606fcf6a454","user":{"login":"paritytech","id":14176906,"avatar_url":"https://avatars2.githubusercontent.com/u/14176906?v=4","gravatar_id":"","url":"https://api.github.com/users/paritytech","html_url":"https://github.com/paritytech","followers_url":"https://api.github.com/users/paritytech/followers","following_url":"https://api.github.com/users/paritytech/following{/other_user}","gists_url":"https://api.github.com/users/paritytech/gists{/gist_id}","starred_url":"https://api.github.com/users/paritytech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paritytech/subscriptions","organizations_url":"https://api.github.com/users/paritytech/orgs","repos_url":"https://api.github.com/users/paritytech/repos","events_url":"https://api.github.com/users/paritytech/events{/privacy}","received_events_url":"https://api.github.com/users/paritytech/received_events","type":"Organization","site_admin":false},"repo":{"id":46714939,"name":"parity","full_name":"paritytech/parity","owner":{"login":"paritytech","id":14176906,"avatar_url":"https://avatars2.githubusercontent.com/u/14176906?v=4","gravatar_id":"","url":"https://api.github.com/users/paritytech","html_url":"https://github.com/paritytech","followers_url":"https://api.github.com/users/paritytech/followers","following_url":"https://api.github.com/users/paritytech/following{/other_user}","gists_url":"https://api.github.com/users/paritytech/gists{/gist_id}","starred_url":"https://api.github.com/users/paritytech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paritytech/subscriptions","organizations_url":"https://api.github.com/users/paritytech/orgs","repos_url":"https://api.github.com/users/paritytech/repos","events_url":"https://api.github.com/users/paritytech/events{/privacy}","received_events_url":"https://api.github.com/users/paritytech/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/paritytech/parity","description":"Fast, light, robust Ethereum implementation.","fork":false,"url":"https://api.github.com/repos/paritytech/parity","forks_url":"https://api.github.com/repos/paritytech/parity/forks","keys_url":"https://api.github.com/repos/paritytech/parity/keys{/key_id}","collaborators_url":"https://api.github.com/repos/paritytech/parity/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/paritytech/parity/teams","hooks_url":"https://api.github.com/repos/paritytech/parity/hooks","issue_events_url":"https://api.github.com/repos/paritytech/parity/issues/events{/number}","events_url":"https://api.github.com/repos/paritytech/parity/events","assignees_url":"https://api.github.com/repos/paritytech/parity/assignees{/user}","branches_url":"https://api.github.com/repos/paritytech/parity/branches{/branch}","tags_url":"https://api.github.com/repos/paritytech/parity/tags","blobs_url":"https://api.github.com/repos/paritytech/parity/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/paritytech/parity/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/paritytech/parity/git/refs{/sha}","trees_url":"https://api.github.com/repos/paritytech/parity/git/trees{/sha}","statuses_url":"https://api.github.com/repos/paritytech/parity/statuses/{sha}","languages_url":"https://api.github.com/repos/paritytech/parity/languages","stargazers_url":"https://api.github.com/repos/paritytech/parity/stargazers","contributors_url":"https://api.github.com/repos/paritytech/parity/contributors","subscribers_url":"https://api.github.com/repos/paritytech/parity/subscribers","subscription_url":"https://api.github.com/repos/paritytech/parity/subscription","commits_url":"https://api.github.com/repos/paritytech/parity/commits{/sha}","git_commits_url":"https://api.github.com/repos/paritytech/parity/git/commits{/sha}","comments_url":"https://api.github.com/repos/paritytech/parity/comments{/number}","issue_comment_url":"https://api.github.com/repos/paritytech/parity/issues/comments{/number}","contents_url":"https://api.github.com/repos/paritytech/parity/contents/{+path}","compare_url":"https://api.github.com/repos/paritytech/parity/compare/{base}...{head}","merges_url":"https://api.github.com/repos/paritytech/parity/merges","archive_url":"https://api.github.com/repos/paritytech/parity/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/paritytech/parity/downloads","issues_url":"https://api.github.com/repos/paritytech/parity/issues{/number}","pulls_url":"https://api.github.com/repos/paritytech/parity/pulls{/number}","milestones_url":"https://api.github.com/repos/paritytech/parity/milestones{/number}","notifications_url":"https://api.github.com/repos/paritytech/parity/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/paritytech/parity/labels{/name}","releases_url":"https://api.github.com/repos/paritytech/parity/releases{/id}","deployments_url":"https://api.github.com/repos/paritytech/parity/deployments","created_at":"2015-11-23T11:07:32Z","updated_at":"2018-03-13T14:24:04Z","pushed_at":"2018-03-13T17:29:42Z","git_url":"git://github.com/paritytech/parity.git","ssh_url":"git@github.com:paritytech/parity.git","clone_url":"https://github.com/paritytech/parity.git","svn_url":"https://github.com/paritytech/parity","homepage":"https://parity.io","size":47802,"stargazers_count":3804,"watchers_count":3804,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":734,"mirror_url":null,"archived":false,"open_issues_count":385,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":734,"open_issues":385,"watchers":3804,"default_branch":"master"}},"base":{"label":"paritytech:master","ref":"master","sha":"7e948a088f61c6a989d9c3de4ef0423b56700c65","user":{"login":"paritytech","id":14176906,"avatar_url":"https://avatars2.githubusercontent.com/u/14176906?v=4","gravatar_id":"","url":"https://api.github.com/users/paritytech","html_url":"https://github.com/paritytech","followers_url":"https://api.github.com/users/paritytech/followers","following_url":"https://api.github.com/users/paritytech/following{/other_user}","gists_url":"https://api.github.com/users/paritytech/gists{/gist_id}","starred_url":"https://api.github.com/users/paritytech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paritytech/subscriptions","organizations_url":"https://api.github.com/users/paritytech/orgs","repos_url":"https://api.github.com/users/paritytech/repos","events_url":"https://api.github.com/users/paritytech/events{/privacy}","received_events_url":"https://api.github.com/users/paritytech/received_events","type":"Organization","site_admin":false},"repo":{"id":46714939,"name":"parity","full_name":"paritytech/parity","owner":{"login":"paritytech","id":14176906,"avatar_url":"https://avatars2.githubusercontent.com/u/14176906?v=4","gravatar_id":"","url":"https://api.github.com/users/paritytech","html_url":"https://github.com/paritytech","followers_url":"https://api.github.com/users/paritytech/followers","following_url":"https://api.github.com/users/paritytech/following{/other_user}","gists_url":"https://api.github.com/users/paritytech/gists{/gist_id}","starred_url":"https://api.github.com/users/paritytech/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/paritytech/subscriptions","organizations_url":"https://api.github.com/users/paritytech/orgs","repos_url":"https://api.github.com/users/paritytech/repos","events_url":"https://api.github.com/users/paritytech/events{/privacy}","received_events_url":"https://api.github.com/users/paritytech/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/paritytech/parity","description":"Fast, light, robust Ethereum implementation.","fork":false,"url":"https://api.github.com/repos/paritytech/parity","forks_url":"https://api.github.com/repos/paritytech/parity/forks","keys_url":"https://api.github.com/repos/paritytech/parity/keys{/key_id}","collaborators_url":"https://api.github.com/repos/paritytech/parity/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/paritytech/parity/teams","hooks_url":"https://api.github.com/repos/paritytech/parity/hooks","issue_events_url":"https://api.github.com/repos/paritytech/parity/issues/events{/number}","events_url":"https://api.github.com/repos/paritytech/parity/events","assignees_url":"https://api.github.com/repos/paritytech/parity/assignees{/user}","branches_url":"https://api.github.com/repos/paritytech/parity/branches{/branch}","tags_url":"https://api.github.com/repos/paritytech/parity/tags","blobs_url":"https://api.github.com/repos/paritytech/parity/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/paritytech/parity/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/paritytech/parity/git/refs{/sha}","trees_url":"https://api.github.com/repos/paritytech/parity/git/trees{/sha}","statuses_url":"https://api.github.com/repos/paritytech/parity/statuses/{sha}","languages_url":"https://api.github.com/repos/paritytech/parity/languages","stargazers_url":"https://api.github.com/repos/paritytech/parity/stargazers","contributors_url":"https://api.github.com/repos/paritytech/parity/contributors","subscribers_url":"https://api.github.com/repos/paritytech/parity/subscribers","subscription_url":"https://api.github.com/repos/paritytech/parity/subscription","commits_url":"https://api.github.com/repos/paritytech/parity/commits{/sha}","git_commits_url":"https://api.github.com/repos/paritytech/parity/git/commits{/sha}","comments_url":"https://api.github.com/repos/paritytech/parity/comments{/number}","issue_comment_url":"https://api.github.com/repos/paritytech/parity/issues/comments{/number}","contents_url":"https://api.github.com/repos/paritytech/parity/contents/{+path}","compare_url":"https://api.github.com/repos/paritytech/parity/compare/{base}...{head}","merges_url":"https://api.github.com/repos/paritytech/parity/merges","archive_url":"https://api.github.com/repos/paritytech/parity/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/paritytech/parity/downloads","issues_url":"https://api.github.com/repos/paritytech/parity/issues{/number}","pulls_url":"https://api.github.com/repos/paritytech/parity/pulls{/number}","milestones_url":"https://api.github.com/repos/paritytech/parity/milestones{/number}","notifications_url":"https://api.github.com/repos/paritytech/parity/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/paritytech/parity/labels{/name}","releases_url":"https://api.github.com/repos/paritytech/parity/releases{/id}","deployments_url":"https://api.github.com/repos/paritytech/parity/deployments","created_at":"2015-11-23T11:07:32Z","updated_at":"2018-03-13T14:24:04Z","pushed_at":"2018-03-13T17:29:42Z","git_url":"git://github.com/paritytech/parity.git","ssh_url":"git@github.com:paritytech/parity.git","clone_url":"https://github.com/paritytech/parity.git","svn_url":"https://github.com/paritytech/parity","homepage":"https://parity.io","size":47802,"stargazers_count":3804,"watchers_count":3804,"language":"Rust","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":734,"mirror_url":null,"archived":false,"open_issues_count":385,"license":{"key":"other","name":"Other","spdx_id":null,"url":null},"forks":734,"open_issues":385,"watchers":3804,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/paritytech/parity/pulls/8099"},"html":{"href":"https://github.com/paritytech/parity/pull/8099"},"issue":{"href":"https://api.github.com/repos/paritytech/parity/issues/8099"},"comments":{"href":"https://api.github.com/repos/paritytech/parity/issues/8099/comments"},"review_comments":{"href":"https://api.github.com/repos/paritytech/parity/pulls/8099/comments"},"review_comment":{"href":"https://api.github.com/repos/paritytech/parity/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/paritytech/parity/pulls/8099/commits"},"statuses":{"href":"https://api.github.com/repos/paritytech/parity/statuses/6e49a0948d441e805d5e375dc3b2a606fcf6a454"}},"author_association":"NONE"}}
{"actor":{"display_login":"tomusdrw"}}