qiniu_http_client/client/retrier/
error.rs1use super::{
2 super::{Idempotent, ResponseErrorKind},
3 RequestRetrier, RequestRetrierOptions, RetryDecision, RetryResult,
4};
5use qiniu_http::{RequestParts as HttpRequestParts, ResponseErrorKind as HttpResponseErrorKind};
6
7#[derive(Copy, Clone, Debug, Default)]
9pub struct ErrorRetrier;
10
11impl RequestRetrier for ErrorRetrier {
12 fn retry(&self, request: &mut HttpRequestParts, opts: RequestRetrierOptions) -> RetryResult {
13 return match opts.response_error().kind() {
14 ResponseErrorKind::HttpError(http_err_kind) => match http_err_kind {
15 HttpResponseErrorKind::ProtocolError => RetryDecision::RetryRequest,
16 HttpResponseErrorKind::InvalidUrl => RetryDecision::TryNextServer,
17 HttpResponseErrorKind::ConnectError => RetryDecision::TryNextServer,
18 HttpResponseErrorKind::ProxyError => RetryDecision::RetryRequest,
19 HttpResponseErrorKind::DnsServerError => RetryDecision::RetryRequest,
20 HttpResponseErrorKind::UnknownHostError => RetryDecision::TryNextServer,
21 HttpResponseErrorKind::SendError => RetryDecision::RetryRequest,
22 HttpResponseErrorKind::ReceiveError | HttpResponseErrorKind::UnknownError => {
23 if is_idempotent(request, opts.idempotent()) {
24 RetryDecision::RetryRequest
25 } else {
26 RetryDecision::DontRetry
27 }
28 }
29 HttpResponseErrorKind::LocalIoError => RetryDecision::DontRetry,
30 HttpResponseErrorKind::TimeoutError => RetryDecision::RetryRequest,
31 HttpResponseErrorKind::ServerCertError => RetryDecision::TryAlternativeEndpoints,
32 HttpResponseErrorKind::ClientCertError => RetryDecision::DontRetry,
33 HttpResponseErrorKind::TooManyRedirect => RetryDecision::DontRetry,
34 HttpResponseErrorKind::CallbackError => RetryDecision::DontRetry,
35 _ => RetryDecision::RetryRequest,
36 },
37 ResponseErrorKind::UnexpectedStatusCode(_) => RetryDecision::DontRetry,
38 ResponseErrorKind::StatusCodeError(status_code) => match status_code.as_u16() {
39 0..=399 => panic!("Should not arrive here"),
40 400..=499 | 501 | 579 | 608 | 612 | 614 | 616 | 618 | 630 | 631 | 632 | 640 | 701 => {
41 RetryDecision::DontRetry
42 }
43 509 | 573 => RetryDecision::Throttled,
44 _ => RetryDecision::TryNextServer,
45 },
46 ResponseErrorKind::ParseResponseError | ResponseErrorKind::UnexpectedEof => {
47 if is_idempotent(request, opts.idempotent()) {
48 RetryDecision::RetryRequest
49 } else {
50 RetryDecision::DontRetry
51 }
52 }
53 ResponseErrorKind::MaliciousResponse => RetryDecision::RetryRequest,
54 ResponseErrorKind::NoTry | ResponseErrorKind::SystemCallError => RetryDecision::DontRetry,
55 }
56 .into();
57
58 fn is_idempotent(request: &HttpRequestParts, idempotent: Idempotent) -> bool {
59 match idempotent {
60 Idempotent::Always => true,
61 Idempotent::Default => request.method().is_safe(),
62 Idempotent::Never => false,
63 }
64 }
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::{
71 super::super::{super::RetriedStatsInfo, ResponseError},
72 *,
73 };
74 use qiniu_http::{Method as HttpMethod, Request as HttpRequest, Uri as HttpUri};
75 use std::{convert::TryFrom, error::Error, result::Result};
76
77 #[test]
78 fn test_error_retrier_idempotent() -> Result<(), Box<dyn Error>> {
79 let uri = HttpUri::try_from("http://localhost/abc")?;
80
81 let retrier = ErrorRetrier;
82 let (mut parts, _) = HttpRequest::builder()
83 .url(uri.to_owned())
84 .method(HttpMethod::GET)
85 .body(())
86 .build()
87 .into_parts_and_body();
88 let result = retrier.retry(
89 &mut parts,
90 RequestRetrierOptions::builder(
91 &ResponseError::new_with_msg(HttpResponseErrorKind::ReceiveError.into(), "Test Error"),
92 &RetriedStatsInfo::default(),
93 )
94 .build(),
95 );
96 assert_eq!(result.decision(), RetryDecision::RetryRequest);
97
98 let result = retrier.retry(
99 &mut parts,
100 RequestRetrierOptions::builder(
101 &ResponseError::new_with_msg(HttpResponseErrorKind::ReceiveError.into(), "Test Error"),
102 &RetriedStatsInfo::default(),
103 )
104 .idempotent(Idempotent::Never)
105 .build(),
106 );
107 assert_eq!(result.decision(), RetryDecision::DontRetry);
108
109 let (mut parts, _) = HttpRequest::builder()
110 .url(uri)
111 .method(HttpMethod::POST)
112 .body(())
113 .build()
114 .into_parts_and_body();
115 let result = retrier.retry(
116 &mut parts,
117 RequestRetrierOptions::builder(
118 &ResponseError::new_with_msg(HttpResponseErrorKind::ReceiveError.into(), "Test Error"),
119 &RetriedStatsInfo::default(),
120 )
121 .build(),
122 );
123 assert_eq!(result.decision(), RetryDecision::DontRetry);
124
125 let result = retrier.retry(
126 &mut parts,
127 RequestRetrierOptions::builder(
128 &ResponseError::new_with_msg(HttpResponseErrorKind::ReceiveError.into(), "Test Error"),
129 &RetriedStatsInfo::default(),
130 )
131 .idempotent(Idempotent::Always)
132 .build(),
133 );
134 assert_eq!(result.decision(), RetryDecision::RetryRequest);
135
136 let result = retrier.retry(
137 &mut parts,
138 RequestRetrierOptions::builder(
139 &ResponseError::new_with_msg(HttpResponseErrorKind::InvalidUrl.into(), "Test Error"),
140 &RetriedStatsInfo::default(),
141 )
142 .idempotent(Idempotent::Always)
143 .build(),
144 );
145 assert_eq!(result.decision(), RetryDecision::TryNextServer);
146
147 Ok(())
148 }
149
150 #[test]
151 fn test_error_retrier_retries() -> Result<(), Box<dyn Error>> {
152 let uri = HttpUri::try_from("http://localhost/abc")?;
153
154 let retrier = ErrorRetrier;
155 let mut retried = RetriedStatsInfo::default();
156 retried.increase_current_endpoint();
157 retried.increase_current_endpoint();
158
159 let (mut parts, _) = HttpRequest::builder()
160 .url(uri)
161 .method(HttpMethod::GET)
162 .body(())
163 .build()
164 .into_parts_and_body();
165 let result = retrier.retry(
166 &mut parts,
167 RequestRetrierOptions::builder(
168 &ResponseError::new_with_msg(HttpResponseErrorKind::ReceiveError.into(), "Test Error"),
169 &retried,
170 )
171 .build(),
172 );
173 assert_eq!(result.decision(), RetryDecision::RetryRequest);
174
175 retried.switch_endpoint();
176
177 let result = retrier.retry(
178 &mut parts,
179 RequestRetrierOptions::builder(
180 &ResponseError::new_with_msg(HttpResponseErrorKind::ReceiveError.into(), "Test Error"),
181 &retried,
182 )
183 .build(),
184 );
185 assert_eq!(result.decision(), RetryDecision::RetryRequest);
186
187 Ok(())
188 }
189}