Skip to content

Commit 9084c13

Browse files
jbolilabudziq
authored andcommitted
Add "Make a partial download with HTTP range headers" example #291 (#309)
Add "Make a partial download with HTTP range headers" example
1 parent 45b7857 commit 9084c13

File tree

2 files changed

+120
-4
lines changed

2 files changed

+120
-4
lines changed

src/intro.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ community. It needs and welcomes help. For details see
9393
| [Extract all links from a webpage HTML][ex-extract-links-webpage] | [![reqwest-badge]][reqwest] [![select-badge]][select] | [![cat-net-badge]][cat-net] |
9494
| [Check webpage for broken links][ex-check-broken-links] | [![reqwest-badge]][reqwest] [![select-badge]][select] [![url-badge]][url] | [![cat-net-badge]][cat-net] |
9595
| [Extract all unique links from a MediaWiki markup][ex-extract-mediawiki-links] | [![reqwest-badge]][reqwest] [![regex-badge]][regex] | [![cat-net-badge]][cat-net] |
96+
| [Make a partial download with HTTP range headers][ex-progress-with-range] | [![reqwest-badge]][reqwest] | [![cat-net-badge]][cat-net] |
9697

9798
## [Application development](app.html)
9899

src/net.md

Lines changed: 119 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
| [Extract all links from a webpage HTML][ex-extract-links-webpage] | [![reqwest-badge]][reqwest] [![select-badge]][select] | [![cat-net-badge]][cat-net] |
2020
| [Check webpage for broken links][ex-check-broken-links] | [![reqwest-badge]][reqwest] [![select-badge]][select] [![url-badge]][url] | [![cat-net-badge]][cat-net] |
2121
| [Extract all unique links from a MediaWiki markup][ex-extract-mediawiki-links] | [![reqwest-badge]][reqwest] [![regex-badge]][regex] | [![cat-net-badge]][cat-net] |
22+
| [Make a partial download with HTTP range headers][ex-progress-with-range] | [![reqwest-badge]][reqwest] | [![cat-net-badge]][cat-net] |
2223

2324
[ex-url-parse]: #ex-url-parse
2425
<a name="ex-url-parse"/>
@@ -837,7 +838,7 @@ After sending data in telnet press `ctrl-]` and type `quit`.
837838
[![reqwest-badge]][reqwest] [![select-badge]][select] [![cat-net-badge]][cat-net]
838839

839840
Use [`reqwest::get`] to perform a HTTP GET request and then use [`Document::from_read`] to parse the response into a HTML document.
840-
We can then retrieve all the links from the document by using [`find`] with the criteria of the [`Name`] being "a".
841+
We can then retrieve all the links from the document by using [`find`] with the criteria of the [`Name`] being "a".
841842
This returns a [`Selection`] that we [`filter_map`] on to retrieve the urls from links that have the "href" [`attr`].
842843

843844
```rust,no_run
@@ -981,7 +982,7 @@ use regex::Regex;
981982
# Regex(regex::Error);
982983
# }
983984
# }
984-
#
985+
#
985986
fn extract_links(content: &str) -> Result<HashSet<Cow<str>>> {
986987
lazy_static! {
987988
static ref WIKI_REGEX: Regex =
@@ -1019,6 +1020,116 @@ fn run() -> Result<()> {
10191020
# quick_main!(run);
10201021
```
10211022

1023+
[ex-progress-with-range]: #ex-progress-with-range
1024+
<a name="ex-progress-with-range"/>
1025+
## Make a partial download with HTTP range headers
1026+
1027+
[![reqwest-badge]][reqwest] [![cat-net-badge]][cat-net]
1028+
1029+
Uses [`reqwest::Client::head`] to get the content-length and validate if the server sets the header
1030+
[`reqwest::header::ContentRange`], required to confirm the support of partial downloads.
1031+
1032+
If supported downloads the content using [`reqwest::get`], setting the [`reqwest::header::Range`]
1033+
to do partial downloads in chunks of 100 bytes, between these writes basic progress messages.
1034+
1035+
Range header, defined in [RFC7233][HTTP Range RFC7233].
1036+
1037+
```rust,no_run
1038+
# #[macro_use]
1039+
# extern crate error_chain;
1040+
extern crate reqwest;
1041+
1042+
use std::io::{Read, Write};
1043+
use std::fs::File;
1044+
1045+
use reqwest::header::{ContentRange, ContentRangeSpec, Range};
1046+
1047+
# error_chain! {
1048+
# foreign_links {
1049+
# Io(std::io::Error);
1050+
# Reqwest(reqwest::Error);
1051+
# }
1052+
# }
1053+
1054+
#[derive(Debug)]
1055+
struct PartialRangeIter {
1056+
start: u64,
1057+
end: u64,
1058+
buffer_size: usize,
1059+
}
1060+
1061+
impl PartialRangeIter {
1062+
pub fn new(content_range: &ContentRangeSpec, buffer_size: usize) -> Result<PartialRangeIter> {
1063+
if buffer_size == 0 {
1064+
Err("invalid buffer_size, give a value greater than zero.")?;
1065+
}
1066+
1067+
match *content_range {
1068+
ContentRangeSpec::Bytes { range: Some(range), .. } => Ok(PartialRangeIter {
1069+
start: range.0,
1070+
end: range.1,
1071+
buffer_size,
1072+
}),
1073+
_ => Err("invalid range specification")?,
1074+
}
1075+
}
1076+
}
1077+
1078+
impl Iterator for PartialRangeIter {
1079+
type Item = Range;
1080+
1081+
fn next(&mut self) -> Option<Self::Item> {
1082+
if self.start > self.end {
1083+
None
1084+
} else {
1085+
let old_start = self.start;
1086+
self.start += std::cmp::min(self.buffer_size as u64, self.end - self.start + 1);
1087+
Some(Range::bytes(old_start, self.start - 1))
1088+
}
1089+
}
1090+
}
1091+
1092+
1093+
fn run() -> Result<()> {
1094+
let client = reqwest::Client::new();
1095+
1096+
// For the purpose of this example is only a small download of 102400 bytes.
1097+
let url = "https://httpbin.org/range/102400?duration=2";
1098+
let response = client.head(url).send()?;
1099+
1100+
let range = response.headers().get::<ContentRange>().ok_or(
1101+
"response doesn't include the expected ranges",
1102+
)?;
1103+
1104+
let mut output_file = File::create("download.bin")?;
1105+
1106+
let mut content_buffer: Vec<u8> = Vec::with_capacity(10000);
1107+
let ranges = PartialRangeIter::new(range, content_buffer.capacity())?;
1108+
1109+
println!("starting download...");
1110+
for range in ranges {
1111+
println!("range {:?}", range);
1112+
1113+
let mut response = client.get(url).header(range).send()?;
1114+
1115+
if !(response.status() == reqwest::StatusCode::Ok ||
1116+
response.status() == reqwest::StatusCode::PartialContent)
1117+
{
1118+
bail!("Unexpected server response: {}", response.status())
1119+
}
1120+
1121+
response.read_to_end(&mut content_buffer)?;
1122+
output_file.write_all(&content_buffer)?;
1123+
content_buffer.clear();
1124+
}
1125+
1126+
println!("finished with success!");
1127+
Ok(())
1128+
}
1129+
#
1130+
# quick_main!(run);
1131+
```
1132+
10221133
{{#include links.md}}
10231134

10241135
<!-- API Reference -->
@@ -1035,7 +1146,7 @@ fn run() -> Result<()> {
10351146
[`Position::BeforePath`]: https://docs.rs/url/*/url/enum.Position.html#variant.BeforePath
10361147
[`Regex::captures_iter`]: https://doc.rust-lang.org/regex/regex/struct.Regex.html#method.captures_iter
10371148
[`RequestBuilder::basic_auth`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.basic_auth
1038-
[`RequestBuilder::body`]: https://docs.rs/reqwest/0.6.2/reqwest/struct.RequestBuilder.html#method.body
1149+
[`RequestBuilder::body`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.body
10391150
[`RequestBuilder::header`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.header
10401151
[`RequestBuilder::json`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.json
10411152
[`RequestBuilder::send`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html#method.send
@@ -1064,9 +1175,12 @@ fn run() -> Result<()> {
10641175
[`parse`]: https://docs.rs/url/1.*/url/struct.Url.html#method.parse
10651176
[`read_to_string`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_to_string
10661177
[`reqwest::Client`]: https://docs.rs/reqwest/*/reqwest/struct.Client.html
1067-
[`reqwest::RequestBuilder`]: https://docs.rs/reqwest/0.6.2/reqwest/struct.RequestBuilder.html
1178+
[`reqwest::RequestBuilder`]: https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html
10681179
[`reqwest::Response`]: https://docs.rs/reqwest/*/reqwest/struct.Response.html
10691180
[`reqwest::get`]: https://docs.rs/reqwest/*/reqwest/fn.get.html
1181+
[`reqwest::Client::head`]: https://docs.rs/reqwest/*/reqwest/struct.Client.html#method.head
1182+
[`reqwest::header::Range`]: https://docs.rs/reqwest/*/reqwest/header/enum.Range.html
1183+
[`reqwest::header::ContentRange`]: https://docs.rs/reqwest/*/reqwest/header/struct.ContentRange.htm
10701184
[`serde::Deserialize`]: https://docs.rs/serde/*/serde/trait.Deserialize.html
10711185
[`serde_json::json!`]: https://docs.rs/serde_json/*/serde_json/macro.json.html
10721186
[`std::iter::Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html
@@ -1080,3 +1194,4 @@ fn run() -> Result<()> {
10801194
[HTTP Basic Auth]: https://tools.ietf.org/html/rfc2617
10811195
[MediaWiki link syntax]: https://www.mediawiki.org/wiki/Help:Links
10821196
[OAuth]: https://oauth.net/getting-started/
1197+
[HTTP Range RFC7233]: https://tools.ietf.org/html/rfc7233#section-3.1

0 commit comments

Comments
 (0)