diff --git a/ylong_http/src/body/mime/mimetype.rs b/ylong_http/src/body/mime/mimetype.rs index 2fbebdc580e1089e0073b55288539e5702a917e6..d9abacb54ccedfee5b913b7bc555739e976a3e24 100644 --- a/ylong_http/src/body/mime/mimetype.rs +++ b/ylong_http/src/body/mime/mimetype.rs @@ -27,7 +27,7 @@ use crate::error::{ErrorKind, HttpError}; /// between: /// /// ```type/subtype``` -/// +/// /// It is case-insensitive but are traditionally written in lowercase, such as: /// ```application/octet-stream```. /// diff --git a/ylong_http/src/response/status.rs b/ylong_http/src/response/status.rs index c5ad299ab5711f0fdf4a0c499a2cdecc5bd2d3ae..d079556232b8ce9a63d8a51da4bf1530cddefa67 100644 --- a/ylong_http/src/response/status.rs +++ b/ylong_http/src/response/status.rs @@ -284,183 +284,148 @@ impl Display for StatusCode { status_list!( /// [`100 Continue`]: https://tools.ietf.org/html/rfc7231#section-6.2.1 (100, CONTINUE, "Continue"), - /// [`101 Switching Protocols`]: https://tools.ietf.org/html/rfc7231#section-6.2.2 (101, SWITCHING_PROTOCOLS, "Switching Protocols"), - /// [`102 Processing`]: https://tools.ietf.org/html/rfc2518 (102, PROCESSING, "Processing"), - /// [`200 OK`]: https://tools.ietf.org/html/rfc7231#section-6.3.1 (200, OK, "OK"), - /// [`201 Created`]: https://tools.ietf.org/html/rfc7231#section-6.3.2 (201, CREATED, "Created"), - /// [`202 Accepted`]: https://tools.ietf.org/html/rfc7231#section-6.3.3 (202, ACCEPTED, "Accepted"), - /// [`203 Non-Authoritative Information`]: https://tools.ietf.org/html/rfc7231#section-6.3.4 - (203, NON_AUTHORITATIVE_INFORMATION, "Non Authoritative Information"), - + ( + 203, + NON_AUTHORITATIVE_INFORMATION, + "Non Authoritative Information" + ), /// [`204 No Content`]: https://tools.ietf.org/html/rfc7231#section-6.3.5 (204, NO_CONTENT, "No Content"), - /// [`205 Reset Content`]: https://tools.ietf.org/html/rfc7231#section-6.3.6 (205, RESET_CONTENT, "Reset Content"), - /// [`206 Partial Content`]: https://tools.ietf.org/html/rfc7233#section-4.1 (206, PARTIAL_CONTENT, "Partial Content"), - /// [`207 Multi-Status`]: https://tools.ietf.org/html/rfc4918 (207, MULTI_STATUS, "Multi-Status"), - /// [`208 Already Reported`]: https://tools.ietf.org/html/rfc5842 (208, ALREADY_REPORTED, "Already Reported"), - /// [`226 IM Used`]: https://tools.ietf.org/html/rfc3229 (226, IM_USED, "IM Used"), - /// [`300 Multiple Choices`]: https://tools.ietf.org/html/rfc7231#section-6.4.1 (300, MULTIPLE_CHOICES, "Multiple Choices"), - /// [`301 Moved Permanently`]: https://tools.ietf.org/html/rfc7231#section-6.4.2 (301, MOVED_PERMANENTLY, "Moved Permanently"), - /// [`302 Found`]: https://tools.ietf.org/html/rfc7231#section-6.4.3 (302, FOUND, "Found"), - /// [`303 See Other`]: https://tools.ietf.org/html/rfc7231#section-6.4.4 (303, SEE_OTHER, "See Other"), - /// [`304 Not Modified`]: https://tools.ietf.org/html/rfc7232#section-4.1 (304, NOT_MODIFIED, "Not Modified"), - /// [`305 Use Proxy`]: https://tools.ietf.org/html/rfc7231#section-6.4.5 (305, USE_PROXY, "Use Proxy"), - /// [`307 Temporary Redirect`]: https://tools.ietf.org/html/rfc7231#section-6.4.7 (307, TEMPORARY_REDIRECT, "Temporary Redirect"), - /// [`308 Permanent Redirect`]: https://tools.ietf.org/html/rfc7238 (308, PERMANENT_REDIRECT, "Permanent Redirect"), - /// [`400 Bad Request`]: https://tools.ietf.org/html/rfc7231#section-6.5.1 (400, BAD_REQUEST, "Bad Request"), - /// [`401 Unauthorized`]: https://tools.ietf.org/html/rfc7235#section-3.1 (401, UNAUTHORIZED, "Unauthorized"), - /// [`402 Payment Required`]: https://tools.ietf.org/html/rfc7231#section-6.5.2 (402, PAYMENT_REQUIRED, "Payment Required"), - /// [`403 Forbidden`]: https://tools.ietf.org/html/rfc7231#section-6.5.3 (403, FORBIDDEN, "Forbidden"), - /// [`404 Not Found`]: https://tools.ietf.org/html/rfc7231#section-6.5.4 (404, NOT_FOUND, "Not Found"), - /// [`405 Method Not Allowed`]: https://tools.ietf.org/html/rfc7231#section-6.5.5 (405, METHOD_NOT_ALLOWED, "Method Not Allowed"), - /// [`406 Not Acceptable`]: https://tools.ietf.org/html/rfc7231#section-6.5.6 (406, NOT_ACCEPTABLE, "Not Acceptable"), - /// [`407 Proxy Authentication Required`]: https://tools.ietf.org/html/rfc7235#section-3.2 - (407, PROXY_AUTHENTICATION_REQUIRED, "Proxy Authentication Required"), - + ( + 407, + PROXY_AUTHENTICATION_REQUIRED, + "Proxy Authentication Required" + ), /// [`408 Request Timeout`]: https://tools.ietf.org/html/rfc7231#section-6.5.7 (408, REQUEST_TIMEOUT, "Request Timeout"), - /// [`409 Conflict`]: https://tools.ietf.org/html/rfc7231#section-6.5.8 (409, CONFLICT, "Conflict"), - /// [`410 Gone`]: https://tools.ietf.org/html/rfc7231#section-6.5.9 (410, GONE, "Gone"), - /// [`411 Length Required`]: https://tools.ietf.org/html/rfc7231#section-6.5.10 (411, LENGTH_REQUIRED, "Length Required"), - /// [`412 Precondition Failed`]: https://tools.ietf.org/html/rfc7232#section-4.2 (412, PRECONDITION_FAILED, "Precondition Failed"), - /// [`413 Payload Too Large`]: https://tools.ietf.org/html/rfc7231#section-6.5.11 (413, PAYLOAD_TOO_LARGE, "Payload Too Large"), - /// [`414 URI Too Long`]: https://tools.ietf.org/html/rfc7231#section-6.5.12 (414, URI_TOO_LONG, "URI Too Long"), - /// [`415 Unsupported Media Type`]: https://tools.ietf.org/html/rfc7231#section-6.5.13 (415, UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"), - /// [`416 Range Not Satisfiable`]: https://tools.ietf.org/html/rfc7233#section-4.4 (416, RANGE_NOT_SATISFIABLE, "Range Not Satisfiable"), - /// [`417 Expectation Failed`]: https://tools.ietf.org/html/rfc7231#section-6.5.14 (417, EXPECTATION_FAILED, "Expectation Failed"), - /// [`418 I'm a teapot`]: https://tools.ietf.org/html/rfc2324 (418, IM_A_TEAPOT, "I'm a teapot"), - /// [`421 Misdirected Request`]: http://tools.ietf.org/html/rfc7540#section-9.1.2 (421, MISDIRECTED_REQUEST, "Misdirected Request"), - /// [`422 Unprocessable Entity`]: https://tools.ietf.org/html/rfc4918 (422, UNPROCESSABLE_ENTITY, "Unprocessable Entity"), - /// [`423 Locked`]: https://tools.ietf.org/html/rfc4918 (423, LOCKED, "Locked"), - /// [`424 Failed Dependency`]: https://tools.ietf.org/html/rfc4918 (424, FAILED_DEPENDENCY, "Failed Dependency"), - /// [`426 Upgrade Required`]: https://tools.ietf.org/html/rfc7231#section-6.5.15 (426, UPGRADE_REQUIRED, "Upgrade Required"), - /// [`428 Precondition Required`]: https://tools.ietf.org/html/rfc6585 (428, PRECONDITION_REQUIRED, "Precondition Required"), - /// [`429 Too Many Requests`]: https://tools.ietf.org/html/rfc6585 (429, TOO_MANY_REQUESTS, "Too Many Requests"), - /// [`431 Request Header Fields Too Large`]: https://tools.ietf.org/html/rfc6585 - (431, REQUEST_HEADER_FIELDS_TOO_LARGE, "Request Header Fields Too Large"), - + ( + 431, + REQUEST_HEADER_FIELDS_TOO_LARGE, + "Request Header Fields Too Large" + ), /// [`451 Unavailable For Legal Reasons`]: http://tools.ietf.org/html/rfc7725 - (451, UNAVAILABLE_FOR_LEGAL_REASONS, "Unavailable For Legal Reasons"), - + ( + 451, + UNAVAILABLE_FOR_LEGAL_REASONS, + "Unavailable For Legal Reasons" + ), /// [`500 Internal Server Error`]: https://tools.ietf.org/html/rfc7231#section-6.6.1 (500, INTERNAL_SERVER_ERROR, "Internal Server Error"), - /// [`501 Not Implemented`]: https://tools.ietf.org/html/rfc7231#section-6.6.2 (501, NOT_IMPLEMENTED, "Not Implemented"), - /// [`502 Bad Gateway`]: https://tools.ietf.org/html/rfc7231#section-6.6.3 (502, BAD_GATEWAY, "Bad Gateway"), - /// [`503 Service Unavailable`]: https://tools.ietf.org/html/rfc7231#section-6.6.4 (503, SERVICE_UNAVAILABLE, "Service Unavailable"), - /// [`504 Gateway Timeout`]: https://tools.ietf.org/html/rfc7231#section-6.6.5 (504, GATEWAY_TIMEOUT, "Gateway Timeout"), - /// [`505 HTTP Version Not Supported`]: https://tools.ietf.org/html/rfc7231#section-6.6.6 - (505, HTTP_VERSION_NOT_SUPPORTED, "HTTP Version Not Supported"), - + ( + 505, + HTTP_VERSION_NOT_SUPPORTED, + "HTTP Version Not Supported" + ), /// [`506 Variant Also Negotiates`]: https://tools.ietf.org/html/rfc2295 (506, VARIANT_ALSO_NEGOTIATES, "Variant Also Negotiates"), - /// [`507 Insufficient Storage`]: https://tools.ietf.org/html/rfc4918 (507, INSUFFICIENT_STORAGE, "Insufficient Storage"), - /// [`508 Loop Detected`]: https://tools.ietf.org/html/rfc5842 (508, LOOP_DETECTED, "Loop Detected"), - /// [`510 Not Extended`]: https://tools.ietf.org/html/rfc2774 (510, NOT_EXTENDED, "Not Extended"), - /// [`511 Network Authentication Required`]: https://tools.ietf.org/html/rfc6585 - (511, NETWORK_AUTHENTICATION_REQUIRED, "Network Authentication Required"), + ( + 511, + NETWORK_AUTHENTICATION_REQUIRED, + "Network Authentication Required" + ), ); #[cfg(test)] diff --git a/ylong_http_client/src/async_impl/client.rs b/ylong_http_client/src/async_impl/client.rs index 12ba3800a220e4c222d124cad97c0ee1d6cb0cde..1db1f73d49a25282fcad3b49da865520156f1a81 100644 --- a/ylong_http_client/src/async_impl/client.rs +++ b/ylong_http_client/src/async_impl/client.rs @@ -161,7 +161,18 @@ impl Client { let mut request = RequestArc::new(request); let mut retries = self.config.retry.times().unwrap_or(0); loop { - let response = self.send_request(request.clone()).await; + let response = if let Some(timeout) = self.config.total_timeout.inner() { + TimeoutFuture::new( + self.send_request(request.clone()), + timeout, + |response, timeout| { + response.body_mut().set_total_sleep(timeout); + }, + ) + .await + } else { + self.send_request(request.clone()).await + }; if let Err(ref err) = response { if retries > 0 && request.ref_mut().body_mut().reuse().await.is_ok() { self.interceptors.intercept_retry(err)?; @@ -172,6 +183,24 @@ impl Client { return response; } } + + /// Enables a total timeout. + /// + /// The timeout is applied from when the request starts connection util the + /// response body has finished, and only affects subsequent tasks. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::Client; + /// use ylong_http_client::{HttpClientError, Timeout}; + /// + /// let mut client = Client::new(); + /// client.total_timeout(Timeout::none()); + /// ``` + pub fn total_timeout(&mut self, timeout: Timeout) { + self.config.total_timeout = timeout; + } } impl Client { @@ -220,7 +249,14 @@ impl Client { interceptor: Arc::clone(&self.interceptors), }; if let Some(timeout) = self.config.request_timeout.inner() { - TimeoutFuture::new(conn::request(conn, message), timeout).await + TimeoutFuture::new( + conn::request(conn, message), + timeout, + |response, timeout| { + response.body_mut().set_request_sleep(timeout); + }, + ) + .await } else { conn::request(conn, message).await } @@ -340,7 +376,7 @@ impl ClientBuilder { self } - /// Enables a request timeout. + /// Enables a total timeout. /// /// The timeout is applied from when the request starts connection util the /// response body has finished. @@ -351,6 +387,24 @@ impl ClientBuilder { /// use ylong_http_client::async_impl::ClientBuilder; /// use ylong_http_client::Timeout; /// + /// let builder = ClientBuilder::new().total_timeout(Timeout::none()); + /// ``` + pub fn total_timeout(mut self, timeout: Timeout) -> Self { + self.client.total_timeout = timeout; + self + } + + /// Enables a request timeout. + /// + /// The timeout is applied from when the request is sent util the + /// response body has finished. + /// + /// # Examples + /// + /// ``` + /// use ylong_http_client::async_impl::ClientBuilder; + /// use ylong_http_client::Timeout; + /// /// let builder = ClientBuilder::new().request_timeout(Timeout::none()); /// ``` pub fn request_timeout(mut self, timeout: Timeout) -> Self { diff --git a/ylong_http_client/src/async_impl/http_body.rs b/ylong_http_client/src/async_impl/http_body.rs index bef1026dd96347af45c3ad344b7aea5faa9a070d..9ad5fe944c4749bfb297ce6be17c89195edcbb80 100644 --- a/ylong_http_client/src/async_impl/http_body.rs +++ b/ylong_http_client/src/async_impl/http_body.rs @@ -65,7 +65,8 @@ const TRAILER_SIZE: usize = 1024; /// ``` pub struct HttpBody { kind: Kind, - timeout: Option>>, + request_timeout: Option>>, + total_timeout: Option>>, } type BoxStreamData = Box; @@ -94,12 +95,17 @@ impl HttpBody { }; Ok(Self { kind, - timeout: None, + request_timeout: None, + total_timeout: None, }) } - pub(crate) fn set_sleep(&mut self, sleep: Option>>) { - self.timeout = sleep; + pub(crate) fn set_request_sleep(&mut self, sleep: Option>>) { + self.request_timeout = sleep; + } + + pub(crate) fn set_total_sleep(&mut self, sleep: Option>>) { + self.total_timeout = sleep; } } @@ -115,7 +121,13 @@ impl Body for HttpBody { return Poll::Ready(Ok(0)); } - if let Some(delay) = self.timeout.as_mut() { + if let Some(delay) = self.request_timeout.as_mut() { + if let Poll::Ready(()) = Pin::new(delay).poll(cx) { + return Poll::Ready(err_from_io!(Timeout, std::io::ErrorKind::TimedOut.into())); + } + } + + if let Some(delay) = self.total_timeout.as_mut() { if let Poll::Ready(()) = Pin::new(delay).poll(cx) { return Poll::Ready(err_from_io!(Timeout, std::io::ErrorKind::TimedOut.into())); } @@ -135,7 +147,13 @@ impl Body for HttpBody { cx: &mut Context<'_>, ) -> Poll, Self::Error>> { // Get trailer data from io - if let Some(delay) = self.timeout.as_mut() { + if let Some(delay) = self.request_timeout.as_mut() { + if let Poll::Ready(()) = Pin::new(delay).poll(cx) { + return Poll::Ready(err_from_msg!(Timeout, "Request timeout")); + } + } + + if let Some(delay) = self.total_timeout.as_mut() { if let Poll::Ready(()) = Pin::new(delay).poll(cx) { return Poll::Ready(err_from_msg!(Timeout, "Request timeout")); } diff --git a/ylong_http_client/src/async_impl/timeout.rs b/ylong_http_client/src/async_impl/timeout.rs index e13a72198cab28dcf4b0b8dc4ece72251239e814..3f3ba11f24c44634346b4555735fbd559e89d383 100644 --- a/ylong_http_client/src/async_impl/timeout.rs +++ b/ylong_http_client/src/async_impl/timeout.rs @@ -20,23 +20,29 @@ use super::Response; use crate::error::HttpClientError; use crate::runtime::{sleep, Sleep}; -pub(crate) struct TimeoutFuture { +pub(crate) struct TimeoutFuture { pub(crate) timeout: Option>>, pub(crate) future: T, + pub(crate) set_timeout: Option, } -impl TimeoutFuture>> { - pub(crate) fn new(future: T, timeout: Duration) -> Self { +impl TimeoutFuture>, F> +where + F: FnOnce(&mut Response, Option>>), +{ + pub(crate) fn new(future: T, timeout: Duration, set_timeout: F) -> Self { Self { timeout: Some(Box::pin(sleep(timeout))), future: Box::pin(future), + set_timeout: Some(set_timeout), } } } -impl Future for TimeoutFuture +impl Future for TimeoutFuture where T: Future> + Unpin, + F: FnOnce(&mut Response, Option>>) + Unpin, { type Output = Result; @@ -50,7 +56,9 @@ where } match Pin::new(&mut this.future).poll(cx) { Poll::Ready(Ok(mut response)) => { - response.body_mut().set_sleep(this.timeout.take()); + if let Some(set_timeout) = this.set_timeout.take() { + set_timeout(&mut response, this.timeout.take()); + } Poll::Ready(Ok(response)) } Poll::Ready(Err(e)) => Poll::Ready(Err(e)), @@ -62,7 +70,6 @@ where #[cfg(all(test, feature = "ylong_base"))] mod ut_timeout { use std::sync::Arc; - use ylong_http::response::status::StatusCode; use ylong_http::response::{Response, ResponsePart}; use ylong_http::version::Version; @@ -72,6 +79,9 @@ mod ut_timeout { use crate::util::interceptor::IdleInterceptor; use crate::util::normalizer::BodyLength; use crate::HttpClientError; + use std::pin::Pin; + + use crate::runtime::Sleep; /// UT test cases for `TimeoutFuture`. /// @@ -108,11 +118,21 @@ mod ut_timeout { let time_future1 = TimeoutFuture { timeout: None, future: future1, + set_timeout: Some( + |response: &mut super::Response, timeout: Option>>| { + response.body_mut().set_total_sleep(timeout); + }, + ), }; let time_future2 = TimeoutFuture { timeout: None, future: future2, + set_timeout: Some( + |response: &mut super::Response, timeout: Option>>| { + response.body_mut().set_request_sleep(timeout); + }, + ), }; assert!(ylong_runtime::block_on(time_future1).is_ok()); diff --git a/ylong_http_client/src/util/config/client.rs b/ylong_http_client/src/util/config/client.rs index a275b5dff395c70489b360087c1c5aee43e01cd1..da545f26c650342d0b8790132fb4f7b503f8b321 100644 --- a/ylong_http_client/src/util/config/client.rs +++ b/ylong_http_client/src/util/config/client.rs @@ -21,6 +21,7 @@ pub(crate) struct ClientConfig { pub(crate) retry: Retry, pub(crate) connect_timeout: Timeout, pub(crate) request_timeout: Timeout, + pub(crate) total_timeout: Timeout, } impl ClientConfig { @@ -31,6 +32,7 @@ impl ClientConfig { retry: Retry::none(), connect_timeout: Timeout::none(), request_timeout: Timeout::none(), + total_timeout: Timeout::none(), } } }