Skip to content

Commit b3f828c

Browse files
committed
improve timeout error handling & relative uri support
1 parent ce69208 commit b3f828c

File tree

3 files changed

+114
-60
lines changed

3 files changed

+114
-60
lines changed

Cargo.lock

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

src/stream.rs

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ pub enum Stream {
2020
}
2121

2222
impl Stream {
23+
/// Opens a TCP connection to a remote host with a connection timeout (if specified).
24+
#[deprecated(
25+
since = "0.12.0",
26+
note = "Stream::new(uri, connect_timeout) was replaced with Stream::connect(uri, connect_timeout)"
27+
)]
28+
pub fn new(uri: &Uri, connect_timeout: Option<Duration>) -> Result<Stream, Error> {
29+
Stream::connect(uri, connect_timeout)
30+
}
31+
2332
/// Opens a TCP connection to a remote host with a connection timeout (if specified).
2433
pub fn connect(uri: &Uri, connect_timeout: Option<Duration>) -> Result<Stream, Error> {
2534
let host = uri.host().unwrap_or("");
@@ -168,30 +177,18 @@ where
168177
receiver: &Receiver<Vec<u8>>,
169178
deadline: Instant,
170179
) -> Result<(), Error> {
171-
let mut result = Ok(());
172-
173180
execute_with_deadline(deadline, |remaining_time| {
174-
let mut is_complete = false;
175-
176181
let data_read = match receiver.recv_timeout(remaining_time) {
177182
Ok(data) => data,
178-
Err(e) => {
179-
if e == RecvTimeoutError::Timeout {
180-
result = Err(Error::Timeout);
181-
}
182-
return true;
183-
}
183+
Err(e) => match e {
184+
RecvTimeoutError::Timeout => return Err(Error::Timeout),
185+
RecvTimeoutError::Disconnected => return Ok(true),
186+
},
184187
};
185188

186-
if let Err(e) = self.write_all(&data_read).map_err(|e| Error::IO(e)) {
187-
result = Err(e);
188-
is_complete = true;
189-
}
190-
191-
is_complete
192-
});
193-
194-
result
189+
self.write_all(&data_read).map_err(|e| Error::IO(e))?;
190+
Ok(false)
191+
})
195192
}
196193
}
197194

@@ -236,19 +233,27 @@ where
236233
/// Key information about function `func`:
237234
/// - is provided with information about remaining time
238235
/// - must ensure that its execution will not take more time than specified in `remaining_time`
239-
/// - needs to return `true` when the operation is complete
240-
pub fn execute_with_deadline<F>(deadline: Instant, mut func: F)
236+
/// - needs to return `Some(true)` when the operation is complete, and `Some(false)` - when operation is in progress
237+
pub fn execute_with_deadline<F>(deadline: Instant, mut func: F) -> Result<(), Error>
241238
where
242-
F: FnMut(Duration) -> bool,
239+
F: FnMut(Duration) -> Result<bool, Error>,
243240
{
244241
loop {
245242
let now = Instant::now();
246243
let remaining_time = deadline - now;
247244

248-
if deadline < now || func(remaining_time) == true {
249-
break;
245+
if deadline < now {
246+
return Err(Error::Timeout);
247+
}
248+
249+
match func(remaining_time) {
250+
Ok(true) => break,
251+
Ok(false) => continue,
252+
Err(e) => return Err(e),
250253
}
251254
}
255+
256+
Ok(())
252257
}
253258

254259
/// Reads the head of HTTP response from `reader`.
@@ -475,17 +480,18 @@ mod tests {
475480
let star_time = Instant::now();
476481
let deadline = star_time + TIMEOUT;
477482

478-
execute_with_deadline(deadline, |_| {
483+
let timeout_err = execute_with_deadline(deadline, |_| {
479484
let sleep_time = Duration::from_millis(500);
480485
thread::sleep(sleep_time);
481486

482-
false
487+
Ok(false)
483488
});
484489

485490
let end_time = Instant::now();
486491
let total_time = end_time.duration_since(star_time).as_secs();
487492

488493
assert_eq!(total_time, TIMEOUT.as_secs());
494+
assert!(timeout_err.is_err());
489495
}
490496
{
491497
let star_time = Instant::now();
@@ -495,8 +501,9 @@ mod tests {
495501
let sleep_time = Duration::from_secs(1);
496502
thread::sleep(sleep_time);
497503

498-
true
499-
});
504+
Ok(true)
505+
})
506+
.unwrap();
500507

501508
let end_time = Instant::now();
502509
let total_time = end_time.duration_since(star_time).as_secs();

src/uri.rs

Lines changed: 75 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -255,23 +255,10 @@ impl<'a> Uri<'a> {
255255
let mut resource = self.resource().to_string();
256256

257257
resource = match &relative_uri.get(..1) {
258-
Some("#") => Uri::add_part(&resource, relative_uri, "#"),
259-
Some("?") => Uri::add_part(&self.path().unwrap_or("/"), relative_uri, "?"),
260-
Some("/") => Uri::add_part(&resource, relative_uri, "/"),
261-
Some(_) | None => {
262-
let part_idx = resource.rfind("/");
263-
264-
match part_idx {
265-
Some(idx) => resource[..idx].to_string() + relative_uri,
266-
None => {
267-
if resource.starts_with("/") {
268-
resource.to_string() + relative_uri
269-
} else {
270-
resource.to_string() + "/" + relative_uri
271-
}
272-
}
273-
}
274-
}
258+
Some("#") => Uri::add_part_start(&resource, relative_uri, "#"),
259+
Some("?") => Uri::add_part_start(&self.path().unwrap_or("/"), relative_uri, "?"),
260+
Some("/") => Uri::add_part_start(&resource, relative_uri, "/"),
261+
Some(_) | None => Uri::add_part_end(&resource, relative_uri, "/"),
275262
};
276263

277264
*relative_uri = if let Some(p) = self.path {
@@ -283,15 +270,43 @@ impl<'a> Uri<'a> {
283270
Uri::try_from(relative_uri.as_str())
284271
}
285272

286-
/// Adds a part to a base at the position definied by a separator.
287-
/// If the separator is not found, concatenates 2 strings together.
288-
fn add_part(base: &str, part: &str, separator: &str) -> String {
289-
let part_idx = base.find(separator);
273+
/// Adds a part at the beggining of the base.
274+
/// Finds the first occurance of a separator in a base and the first occurance of a separator in a part.
275+
/// Joins all chars before the separator from the base, separator and all chars after the separator from the part.
276+
fn add_part_start(base: &str, part: &str, separator: &str) -> String {
277+
let base_idx = base.find(separator);
278+
Uri::add_part(base, part, separator, base_idx)
279+
}
280+
281+
/// Adds a part at the end of the base.
282+
/// Finds the last occurance of a separator in a base and the first occurance of a separator in a part.
283+
/// Joins all chars before the separator from the base, separator and all chars after the separator from the part.
284+
fn add_part_end(base: &str, part: &str, separator: &str) -> String {
285+
let base_idx = base.rfind(separator);
286+
Uri::add_part(base, part, separator, base_idx)
287+
}
288+
289+
/// Adds a part to the base with separator in between.
290+
/// Base index defines where part should be added.
291+
fn add_part(base: &str, part: &str, separator: &str, base_idx: Option<usize>) -> String {
292+
let mut output = String::new();
293+
let part_idx = part.find(separator);
294+
295+
if let Some(idx) = base_idx {
296+
output += &base[..idx];
297+
} else {
298+
output += base;
299+
}
300+
301+
output += separator;
290302

291-
match part_idx {
292-
Some(idx) => base[..idx].to_string() + part,
293-
None => base.to_string() + part,
303+
if let Some(idx) = part_idx {
304+
output += &part[idx + 1..];
305+
} else {
306+
output += part;
294307
}
308+
309+
output
295310
}
296311
}
297312

@@ -569,7 +584,7 @@ mod tests {
569584
"#fragment",
570585
"other-path",
571586
"#paragraph",
572-
"/foo/bar/buz",
587+
"./foo/bar/buz",
573588
"?users#1551",
574589
];
575590

@@ -832,11 +847,43 @@ mod tests {
832847

833848
#[test]
834849
fn uri_add_part() {
835-
const BASES: [&str; 2] = ["/bar/baz?query", "/bar/baz?query#some-fragment"];
836-
const RESULT: &str = "/bar/baz?query#another-fragment";
850+
const BASES: [&str; 2] = ["/bar/baz/fizz?query", "/bar/baz?query#some-fragment"];
851+
const RESULT: [&str; 2] = [
852+
"/bar/baz/fizz?query#another-fragment",
853+
"/bar/baz?query#some-fragment#another-fragment",
854+
];
855+
856+
for i in 0..BASES.len() {
857+
assert_eq!(
858+
Uri::add_part(BASES[i], "#another-fragment", "#", Some(BASES[i].len())),
859+
RESULT[i]
860+
);
861+
}
862+
}
863+
864+
#[test]
865+
fn uri_add_part_start() {
866+
const BASES: [&str; 2] = ["/bar/baz/fizz?query", "/bar/baz?query#some-fragment"];
867+
const RESULT: [&str; 2] = [
868+
"/bar/baz/fizz?query#another-fragment",
869+
"/bar/baz?query#another-fragment",
870+
];
871+
872+
for i in 0..BASES.len() {
873+
assert_eq!(
874+
Uri::add_part_start(BASES[i], "#another-fragment", "#"),
875+
RESULT[i]
876+
);
877+
}
878+
}
879+
880+
#[test]
881+
fn uri_add_part_end() {
882+
const BASES: [&str; 2] = ["/bar/baz/fizz?query", "/bar/baz?query#some-fragment"];
883+
const RESULT: [&str; 2] = ["/bar/baz/another", "/bar/another"];
837884

838885
for i in 0..BASES.len() {
839-
assert_eq!(Uri::add_part(BASES[i], "#another-fragment", "#"), RESULT);
886+
assert_eq!(Uri::add_part_end(BASES[i], "./another", "/"), RESULT[i]);
840887
}
841888
}
842889

0 commit comments

Comments
 (0)