Skip to content

Commit 0830034

Browse files
authored
Merge pull request #1238 from seanmonstar/reqwest
improve reqwest backend
2 parents 07fd5bf + 28f37ab commit 0830034

File tree

8 files changed

+294
-87
lines changed

8 files changed

+294
-87
lines changed

Cargo.lock

Lines changed: 12 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/download/Cargo.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ license = "MIT/Apache-2.0"
1111
default = ["curl-backend"]
1212

1313
curl-backend = ["curl"]
14-
reqwest-backend = ["reqwest", "env_proxy"]
14+
reqwest-backend = ["reqwest", "env_proxy", "lazy_static"]
1515

1616
[dependencies]
1717
error-chain = "0.10"
1818
url = "1.2"
1919
curl = { version = "0.4.6", optional = true }
2020
env_proxy = { version = "0.1.1", optional = true }
21-
reqwest = { version = "0.7.2", optional = true }
21+
lazy_static = { version = "0.1", optional = true }
22+
reqwest = { version = "0.7.3", optional = true }
2223

2324
[dev-dependencies]
25+
futures = "0.1"
26+
hyper = "0.11"
2427
tempdir = "0.3.4"

src/download/src/lib.rs

Lines changed: 56 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ extern crate url;
66

77
#[cfg(feature = "reqwest-backend")]
88
extern crate reqwest;
9+
#[cfg(feature = "reqwest-backend")]
10+
#[macro_use]
11+
extern crate lazy_static;
912

1013
use url::Url;
1114
use std::path::Path;
@@ -237,56 +240,24 @@ pub mod reqwest_be {
237240
extern crate env_proxy;
238241

239242
use std::io;
243+
use std::time::Duration;
240244
use errors::*;
241245
use url::Url;
242246
use super::Event;
243-
use reqwest::{header, Client, Proxy};
247+
use reqwest::{header, Client, Proxy, Response};
244248

245249
pub fn download(url: &Url,
246250
resume_from: u64,
247251
callback: &Fn(Event) -> Result<()>)
248252
-> Result<()> {
249253

250254
// Short-circuit reqwest for the "file:" URL scheme
251-
if try!(download_from_file_url(url, callback)) {
255+
if download_from_file_url(url, resume_from, callback)? {
252256
return Ok(());
253257
}
254258

255-
let maybe_proxy = env_proxy::for_url(url)
256-
.map(|(addr, port)| {
257-
String::from(format!("{}:{}", addr, port))
258-
});
259-
260-
let client = match url.scheme() {
261-
"https" => match maybe_proxy {
262-
None => Client::new()?,
263-
Some(host_port) => {
264-
Client::builder()?
265-
.proxy(Proxy::https(&host_port)?)
266-
.build()?
267-
}
268-
},
269-
"http" => match maybe_proxy {
270-
None => Client::new()?,
271-
Some(host_port) => {
272-
Client::builder()?
273-
.proxy(Proxy::http(&host_port)?)
274-
.build()?
275-
}
276-
},
277-
_ => return Err(format!("unsupported URL scheme: '{}'", url.scheme()).into())
278-
};
279-
280-
let mut res = if resume_from > 0 {
281-
client
282-
.get(url.clone())?
283-
.header(header::Range::Bytes(
284-
vec![header::ByteRangeSpec::AllFrom(resume_from)]
285-
))
286-
.send()
287-
} else {
288-
client.get(url.clone())?.send()
289-
}.chain_err(|| "failed to make network request")?;
259+
let mut res = request(url, resume_from)
260+
.chain_err(|| "failed to make network request")?;
290261

291262
if !res.status().is_success() {
292263
let code: u16 = res.status().into();
@@ -296,23 +267,65 @@ pub mod reqwest_be {
296267
let buffer_size = 0x10000;
297268
let mut buffer = vec![0u8; buffer_size];
298269

299-
if let Some(len) = res.headers().get::<header::ContentLength>().cloned() {
300-
try!(callback(Event::DownloadContentLengthReceived(len.0)));
270+
if let Some(len) = res.headers().get::<header::ContentLength>() {
271+
callback(Event::DownloadContentLengthReceived(len.0 + resume_from))?;
301272
}
302273

303274
loop {
304-
let bytes_read = try!(io::Read::read(&mut res, &mut buffer)
305-
.chain_err(|| "error reading from socket"));
275+
let bytes_read = io::Read::read(&mut res, &mut buffer)
276+
.chain_err(|| "error reading from socket")?;
306277

307278
if bytes_read != 0 {
308-
try!(callback(Event::DownloadDataReceived(&buffer[0..bytes_read])));
279+
callback(Event::DownloadDataReceived(&buffer[0..bytes_read]))?;
309280
} else {
310281
return Ok(());
311282
}
312283
}
313284
}
314285

286+
lazy_static! {
287+
static ref CLIENT: Client = {
288+
let catcher = || {
289+
Client::builder()?
290+
.gzip(false)
291+
.proxy(Proxy::custom(env_proxy))
292+
.timeout(Duration::from_secs(30))
293+
.build()
294+
};
295+
296+
// woah, an unwrap?!
297+
// It's OK. This is the same as what is happening in curl.
298+
//
299+
// The curl::Easy::new() internally assert!s that the initialized
300+
// Easy is not null. Inside reqwest, the errors here would be from
301+
// the TLS library returning a null pointer as well.
302+
catcher().unwrap()
303+
};
304+
}
305+
306+
fn env_proxy(url: &Url) -> Option<Url> {
307+
env_proxy::for_url(url).and_then(|(host, port)| {
308+
//TODO: update env_proxy to return full string, not just (host,port)
309+
//Ideally: fn for_str(s: &str) -> Option<String>
310+
let proxy_url = format!("http://{}:{}", host, port);
311+
proxy_url.parse().ok()
312+
})
313+
}
314+
315+
fn request(url: &Url, resume_from: u64) -> ::reqwest::Result<Response> {
316+
let mut req = CLIENT.get(url.clone())?;
317+
318+
if resume_from != 0 {
319+
req.header(header::Range::Bytes(
320+
vec![header::ByteRangeSpec::AllFrom(resume_from)]
321+
));
322+
}
323+
324+
req.send()
325+
}
326+
315327
fn download_from_file_url(url: &Url,
328+
resume_from: u64,
316329
callback: &Fn(Event) -> Result<()>)
317330
-> Result<bool> {
318331

@@ -333,6 +346,7 @@ pub mod reqwest_be {
333346

334347
let ref mut f = try!(fs::File::open(src)
335348
.chain_err(|| "unable to open downloaded file"));
349+
io::Seek::seek(f, io::SeekFrom::Start(resume_from))?;
336350

337351
let ref mut buffer = vec![0u8; 0x10000];
338352
loop {

src/download/tests/download-curl-resume.rs

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,16 @@
11
#![cfg(feature = "curl-backend")]
22

33
extern crate download;
4-
extern crate tempdir;
54
extern crate url;
65

7-
use std::sync::{Arc, Mutex};
8-
use std::fs::{self, File};
9-
use std::io::{self, Read};
10-
use std::path::Path;
6+
use std::sync::Mutex;
117

12-
use tempdir::TempDir;
138
use url::Url;
149

1510
use download::*;
1611

17-
fn tmp_dir() -> TempDir {
18-
TempDir::new("rustup-download-test-").expect("creating tempdir for test")
19-
}
20-
21-
fn file_contents(path: &Path) -> String {
22-
let mut result = String::new();
23-
File::open(&path).unwrap().read_to_string(&mut result).expect("reading test result file");
24-
result
25-
}
26-
27-
28-
pub fn write_file(path: &Path, contents: &str) {
29-
let mut file = fs::OpenOptions::new()
30-
.write(true)
31-
.truncate(true)
32-
.create(true)
33-
.open(path)
34-
.expect("writing test data");
35-
36-
io::Write::write_all(&mut file, contents.as_bytes()).expect("writing test data");
37-
38-
file.sync_data().expect("writing test data");
39-
}
12+
mod support;
13+
use support::{tmp_dir, write_file, file_contents, serve_file};
4014

4115
#[test]
4216
fn partially_downloaded_file_gets_resumed_from_byte_offset() {
@@ -62,36 +36,47 @@ fn partially_downloaded_file_gets_resumed_from_byte_offset() {
6236
#[test]
6337
fn callback_gets_all_data_as_if_the_download_happened_all_at_once() {
6438
let tmpdir = tmp_dir();
65-
66-
let from_path = tmpdir.path().join("download-source");
67-
write_file(&from_path, "xxx45");
68-
6939
let target_path = tmpdir.path().join("downloaded");
7040
write_file(&target_path, "123");
7141

72-
let from_url = Url::from_file_path(&from_path).unwrap();
42+
let (addr, _shutdown) = serve_file(b"xxx45".to_vec());
43+
44+
let from_url = format!("http://{}", addr).parse().unwrap();
7345

74-
let received_in_callback = Arc::new(Mutex::new(Vec::new()));
46+
let callback_partial = Mutex::new(false);
47+
let callback_len = Mutex::new(None);
48+
let received_in_callback = Mutex::new(Vec::new());
7549

7650
download_to_path_with_backend(Backend::Curl,
7751
&from_url,
7852
&target_path,
7953
true,
8054
Some(&|msg| {
8155
match msg {
56+
Event::ResumingPartialDownload => {
57+
let mut flag = callback_partial.lock().unwrap();
58+
assert!(!*flag);
59+
*flag = true;
60+
},
61+
Event::DownloadContentLengthReceived(len) => {
62+
let mut flag = callback_len.lock().unwrap();
63+
assert!(flag.is_none());
64+
*flag = Some(len);
65+
}
8266
Event::DownloadDataReceived(data) => {
8367
for b in data.iter() {
8468
received_in_callback.lock().unwrap().push(b.clone());
8569
}
8670
}
87-
_ => {}
8871
}
8972

9073

9174
Ok(())
9275
}))
9376
.expect("Test download failed");
9477

78+
assert!(*callback_partial.lock().unwrap());
79+
assert_eq!(*callback_len.lock().unwrap(), Some(5));
9580
let ref observed_bytes = *received_in_callback.lock().unwrap();
9681
assert_eq!(observed_bytes, &vec![b'1', b'2', b'3', b'4', b'5']);
9782
assert_eq!(file_contents(&target_path), "12345");

0 commit comments

Comments
 (0)