qiniu_http_client/client/retrier/
error.rs

1use super::{
2    super::{Idempotent, ResponseErrorKind},
3    RequestRetrier, RequestRetrierOptions, RetryDecision, RetryResult,
4};
5use qiniu_http::{RequestParts as HttpRequestParts, ResponseErrorKind as HttpResponseErrorKind};
6
7/// 根据七牛 API 返回的状态码作出重试决定
8#[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}