diff --git a/examples/http-get-message.rs b/examples/http-get-message.rs index 3596b33bd5d..2727b10e6d8 100644 --- a/examples/http-get-message.rs +++ b/examples/http-get-message.rs @@ -9,15 +9,13 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let client = Client::new(env::var("DISCORD_TOKEN")?); - let channel_id = Id::new(381_926_291_785_383_946); + let channel_id = Id::new(745811002771374151); - future::join_all((1u8..=10).map(|x| { - client - .create_message(channel_id) - .content(&format!("Ping #{x}")) - .into_future() - })) - .await; + client + .create_message(channel_id) + .content(&format!("Ping #{{x}}")) + .expect("content not a valid length") + .await.unwrap(); let me = client.current_user().await?.model().await?; println!("Current user: {}#{}", me.name, me.discriminator); diff --git a/twilight-http-ratelimiting/Cargo.toml b/twilight-http-ratelimiting/Cargo.toml index 7c1e3448eae..d71d53a21b9 100644 --- a/twilight-http-ratelimiting/Cargo.toml +++ b/twilight-http-ratelimiting/Cargo.toml @@ -15,12 +15,12 @@ version = "0.15.1" [dependencies] futures-util = { version = "0.3", default-features = false } -tokio = { version = "1", default-features = false, features = ["rt", "sync", "time"] } +http = { version = "0.2", default-features = false } +tokio = { version = "1.26", default-features = false, features = ["rt", "sync", "time"] } tracing = { default-features = false, features = ["std", "attributes"], version = "0.1.23" } -[dev-dependencies] +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] criterion = { default-features = false, version = "0.4" } -http = { version = "0.2", default-features = false } static_assertions = { default-features = false, version = "1.1.0" } tokio = { default-features = false, features = ["macros", "rt-multi-thread"], version = "1.0" } diff --git a/twilight-http-ratelimiting/src/request.rs b/twilight-http-ratelimiting/src/request.rs index e09414dbf01..404b95cc109 100644 --- a/twilight-http-ratelimiting/src/request.rs +++ b/twilight-http-ratelimiting/src/request.rs @@ -43,6 +43,16 @@ impl Method { Method::Put => "PUT", } } + + pub const fn to_http(self) -> ::http::Method { + match self { + Method::Delete => ::http::Method::DELETE, + Method::Get => ::http::Method::GET, + Method::Patch => ::http::Method::PATCH, + Method::Post => ::http::Method::POST, + Method::Put => ::http::Method::PUT, + } + } } /// Error returned when a [`Path`] could not be parsed from a string. diff --git a/twilight-http/Cargo.toml b/twilight-http/Cargo.toml index f6b7e93e43c..36450115f0a 100644 --- a/twilight-http/Cargo.toml +++ b/twilight-http/Cargo.toml @@ -14,10 +14,8 @@ rust-version.workspace = true version = "0.15.2" [dependencies] -hyper = { default-features = false, features = ["client", "http1", "http2", "runtime"], version = "0.14" } -hyper-rustls = { default-features = false, optional = true, features = ["http1", "http2"], version = "0.24" } -hyper-tls = { default-features = false, optional = true, version = "0.5" } -hyper-trust-dns = { default-features = false, optional = true, version = "0.5" } +bytes = "1.4.0" +http = { version = "0.2.9", default-features = false } percent-encoding = { default-features = false, version = "2" } rand = { default-features = false, features = ["std_rng", "std"], version = "0.8" } serde = { default-features = false, features = ["derive"], version = "1" } @@ -32,15 +30,27 @@ twilight-validate = { default-features = false, path = "../twilight-validate", v brotli = { default-features = false, features = ["std"], optional = true, version = "3.0.0" } simd-json = { default-features = false, features = ["serde_impl", "swar-number-parsing"], optional = true, version = ">=0.4, <0.11" } +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +hyper = { default-features = false, features = ["client", "http1", "http2", "runtime"], version = "0.14" } +hyper-rustls = { default-features = false, optional = true, features = ["http1", "http2"], version = "0.24" } +hyper-tls = { default-features = false, optional = true, version = "0.5" } +hyper-trust-dns = { default-features = false, optional = true, version = "0.5" } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +reqwest = { default-features = false, features = [] } +#reqwest = { git = "https://github.com/flisky/reqwest", default-features = false, features = [] } +getrandom = { version = "0.2", features = ["js"] } + [features] -default = ["decompression", "rustls-native-roots"] +default = ["rustls-native-roots"] +# default = ["decompression", "rustls-native-roots"] decompression = ["dep:brotli"] native = ["dep:hyper-tls"] rustls-native-roots = ["dep:hyper-rustls", "hyper-rustls?/native-tokio"] rustls-webpki-roots = ["dep:hyper-rustls", "hyper-rustls?/webpki-tokio"] trust-dns = ["dep:hyper-trust-dns"] -[dev-dependencies] +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] serde_test = { default-features = false, version = "1" } static_assertions = { default-features = false, version = "1.1.0" } twilight-util = { default-features = false, features = ["builder"], path = "../twilight-util", version = "0.15.2" } diff --git a/twilight-http/src/client/builder.rs b/twilight-http/src/client/builder.rs index b74d7617be8..a1faa7718b6 100644 --- a/twilight-http/src/client/builder.rs +++ b/twilight-http/src/client/builder.rs @@ -1,6 +1,6 @@ use super::Token; -use crate::{client::connector, Client}; -use hyper::header::HeaderMap; +use crate::{Client, http::HttpClient}; +use http::header::HeaderMap; use std::{ sync::{atomic::AtomicBool, Arc}, time::Duration, @@ -30,9 +30,7 @@ impl ClientBuilder { /// Build the [`Client`]. pub fn build(self) -> Client { - let connector = connector::create(); - - let http = hyper::Client::builder().build(connector); + let http = HttpClient::new(); let token_invalidated = if self.remember_invalid_token { Some(Arc::new(AtomicBool::new(false))) @@ -155,7 +153,8 @@ impl Default for ClientBuilder { default_allowed_mentions: None, default_headers: None, proxy: None, - ratelimiter: Some(Box::new(InMemoryRatelimiter::default())), + ratelimiter: None, + //ratelimiter: Some(Box::new(InMemoryRatelimiter::default())), remember_invalid_token: true, timeout: Duration::from_secs(10), token: None, diff --git a/twilight-http/src/client/mod.rs b/twilight-http/src/client/mod.rs index 5397d2de8bf..ddfc3526289 100644 --- a/twilight-http/src/client/mod.rs +++ b/twilight-http/src/client/mod.rs @@ -1,11 +1,9 @@ mod builder; -mod connector; mod interaction; pub use self::{builder::ClientBuilder, interaction::InteractionClient}; use crate::{ - client::connector::Connector, error::{Error, ErrorType}, request::{ channel::{ @@ -79,13 +77,9 @@ use crate::{ Method, Request, }, response::ResponseFuture, - API_VERSION, -}; -use hyper::{ - client::Client as HyperClient, - header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT}, - Body, + API_VERSION, http::{RawRequestBuilder, HttpClient}, }; +use http::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT}; use std::{ fmt::{Debug, Formatter, Result as FmtResult}, ops::Deref, @@ -223,7 +217,7 @@ impl Deref for Token { pub struct Client { pub(crate) default_allowed_mentions: Option, default_headers: Option, - http: HyperClient, + http: HttpClient, proxy: Option>, ratelimiter: Option>, timeout: Duration, @@ -2578,7 +2572,7 @@ impl Client { let url = format!("{protocol}://{host}/api/v{API_VERSION}/{path}"); tracing::debug!(?url); - let mut builder = hyper::Request::builder().method(method.name()).uri(&url); + let mut builder = RawRequestBuilder::new().method(method.to_http()).uri(&url)?; if use_authorization_token { if let Some(token) = self.token.as_deref() { @@ -2612,7 +2606,7 @@ impl Client { #[cfg(feature = "decompression")] headers.insert( - hyper::header::ACCEPT_ENCODING, + http::header::ACCEPT_ENCODING, HeaderValue::from_static("br"), ); @@ -2634,18 +2628,23 @@ impl Client { } let try_req = if let Some(form) = form { - builder.body(Body::from(form.build())) + builder.body(form.build()) } else if let Some(bytes) = body { - builder.body(Body::from(bytes)) + builder.body(bytes) } else { - builder.body(Body::empty()) + builder.body(Vec::new()) }; - let inner = self.http.request(try_req.map_err(|source| Error { + let inner = self.http.request(try_req.build().map_err(|source| Error { kind: ErrorType::BuildingRequest, - source: Some(Box::new(source)), + source: None, })?); + // let inner = self.http.request(try_req.build().map_err(|source| Error { + // kind: ErrorType::BuildingRequest, + // source: Some(Box::new(source)), + // })?); + // For requests that don't use an authorization token we don't need to // remember whether the token is invalid. This may be for requests such // as webhooks and interactions. @@ -2658,7 +2657,7 @@ impl Client { ResponseFuture::ratelimit(invalid_token, inner, self.timeout, tx_future) } else { - ResponseFuture::new(Box::pin(time::timeout(self.timeout, inner)), invalid_token) + ResponseFuture::new(Box::pin(inner), invalid_token) }) } } diff --git a/twilight-http/src/error.rs b/twilight-http/src/error.rs index 08a35f5bbbf..5fa518f0505 100644 --- a/twilight-http/src/error.rs +++ b/twilight-http/src/error.rs @@ -1,5 +1,4 @@ -use crate::{api_error::ApiError, json::JsonError, response::StatusCode}; -use hyper::{Body, Response}; +use crate::{api_error::ApiError, json::JsonError, response::StatusCode, http::RawResponse}; use std::{ error::Error as StdError, fmt::{Debug, Display, Formatter, Result as FmtResult}, @@ -125,7 +124,7 @@ pub enum ErrorType { /// /// This may occur during Discord API stability incidents. ServiceUnavailable { - response: Response, + response: RawResponse, }, /// Token in use has become revoked or is otherwise invalid. /// diff --git a/twilight-http/src/http/hyper/client.rs b/twilight-http/src/http/hyper/client.rs new file mode 100644 index 00000000000..2b58cb4b8ef --- /dev/null +++ b/twilight-http/src/http/hyper/client.rs @@ -0,0 +1,23 @@ +use super::{response::RawResponseFuture, request::RawRequest, connector}; + +#[derive(Debug)] +pub struct HttpClient { + pub(crate) hyper: hyper::Client +} + +impl HttpClient { + pub fn new() -> Self { + let connector = connector::create(); + + let hyper = hyper::Client::builder().build(connector); + + HttpClient { hyper } + } + + pub fn request(&self, req: RawRequest) -> RawResponseFuture { + let inner = self.hyper.request(req.hyper); + RawResponseFuture { + inner + } + } +} diff --git a/twilight-http/src/client/connector.rs b/twilight-http/src/http/hyper/connector.rs similarity index 100% rename from twilight-http/src/client/connector.rs rename to twilight-http/src/http/hyper/connector.rs diff --git a/twilight-http/src/http/hyper/mod.rs b/twilight-http/src/http/hyper/mod.rs new file mode 100644 index 00000000000..5172fb8cb1a --- /dev/null +++ b/twilight-http/src/http/hyper/mod.rs @@ -0,0 +1,9 @@ +mod connector; +mod response; +mod request; +mod client; + +pub use connector::{create, Connector}; +pub use response::{RawResponseFuture, RawResponse}; +pub use request::RawRequest; +pub use client::HttpClient; diff --git a/twilight-http/src/http/hyper/request.rs b/twilight-http/src/http/hyper/request.rs new file mode 100644 index 00000000000..f76fd2e748e --- /dev/null +++ b/twilight-http/src/http/hyper/request.rs @@ -0,0 +1,8 @@ +use http::{Method, Uri, HeaderMap, HeaderValue}; +use hyper::Body; + +use crate::{Error, error::ErrorType}; + +pub struct RawRequest { + pub(crate) hyper: hyper::Request +} diff --git a/twilight-http/src/http/hyper/response.rs b/twilight-http/src/http/hyper/response.rs new file mode 100644 index 00000000000..4d51e23885d --- /dev/null +++ b/twilight-http/src/http/hyper/response.rs @@ -0,0 +1,49 @@ +use std::{future::Future, pin::Pin}; + +use http::{HeaderValue, header::HeaderMap}; +use hyper::Body; + +use crate::response::{BytesFuture, StatusCode}; + +#[derive(Debug)] +pub struct RawResponse { + pub inner: hyper::Response, +} + +impl RawResponse { + fn new(inner: hyper::Response) -> Self { + RawResponse { inner } + } + + pub fn headers(&self) -> &HeaderMap { + self.inner.headers() + } + + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.inner.headers_mut() + } + + pub fn bytes(self, compressed: bool) -> BytesFuture { + BytesFuture::from_hyper(self.inner.into_body(), compressed) + } + + pub fn status(&self) -> StatusCode { + // Convert the `hyper` status code into its raw form in order to return + // our own. + let raw = self.inner.status().as_u16(); + + StatusCode::new(raw) + } +} + +pub struct RawResponseFuture { + pub inner: hyper::client::ResponseFuture, +} + +impl Future for RawResponseFuture { + type Output = hyper::Result; + + fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll { + Pin::new(&mut self.inner).poll(cx).map_ok(RawResponse::new) + } +} diff --git a/twilight-http/src/http/mod.rs b/twilight-http/src/http/mod.rs new file mode 100644 index 00000000000..3e7a339cef3 --- /dev/null +++ b/twilight-http/src/http/mod.rs @@ -0,0 +1,117 @@ +use crate::{Error, error::ErrorType}; + +#[cfg(not(target_arch = "wasm32"))] +mod hyper; +#[cfg(not(target_arch = "wasm32"))] +pub use self::hyper::{HttpClient, RawRequest, RawResponse, RawResponseFuture}; + +#[cfg(target_arch = "wasm32")] +mod reqwest; +#[cfg(target_arch = "wasm32")] +pub use self::reqwest::{HttpClient, RawRequest, RawResponseFuture, RawResponse}; + +use http::{Method, HeaderMap, HeaderValue, Uri}; + +pub struct RawRequestBuilder { + method: Method, + uri: Uri, + headers: HeaderMap, + body: Vec, +} + +impl RawRequestBuilder { + pub fn new() -> Self { + RawRequestBuilder { + method: Method::GET, + uri: Uri::default(), + headers: HeaderMap::default(), + body: Vec::new(), + } + } + + pub fn method(mut self, method: Method) -> Self { + self.method = method; + + self + } + + pub fn uri(mut self, uri: &str) -> Result { + let parsed = Uri::try_from(uri).map_err(|source| { + Error { + kind: ErrorType::BuildingRequest, + source: Some(Box::new(source)), + } + })?; + + self.uri = parsed; + + Ok(self) + } + + pub fn headers_mut(&mut self) -> Option<&mut HeaderMap> { + Some(&mut self.headers) + } + + pub fn body(mut self, body: Vec) -> Self { + self.body = body; + + self + } + + #[cfg(not(target_arch = "wasm32"))] + pub(super) fn build(self) -> Result { + let mut builder = ::hyper::Request::builder().method(self.method).uri(self.uri); + if let Some(headers) = builder.headers_mut() { + *headers = self.headers; + } + let hyper = builder.body(::hyper::Body::from(self.body)).map_err(|source| { + Error { + kind: ErrorType::BuildingRequest, + source: Some(Box::new(source)), + } + })?; + Ok(RawRequest { hyper }) + } + + #[cfg(target_arch = "wasm32")] + pub(super) fn build(self) -> Result { + let url = ::reqwest::Url::try_from(self.uri.to_string().as_str()).unwrap(); + let mut req = ::reqwest::Request::new(self.method, url); + *req.headers_mut() = self.headers; + *req.body_mut() = Some(self.body.into()); + + Ok(reqwest::RawRequest { req }) + } + + // #[cfg(target_arch = "wasm32")] + // pub fn build(self) -> Result { + // use ::worker::{ + // js_sys, + // wasm_bindgen::{JsCast, JsValue}, + // wasm_bindgen_futures::JsFuture, + // Headers, Request, RequestInit, Response, + // }; + + // let mut init = RequestInit::new(); + // let body = std::str::from_utf8(&self.body).map_err(|source| Error { + // kind: ErrorType::BuildingRequest, + // source: Some(Box::new(source)), + // })?; + // init.body = Some(JsValue::from_str(body)); + + // let headers = Headers::from(&self.headers); + // init.headers = headers; + + // let method = ::worker::Method::from(self.method.to_string()); + // init.method = method; + + // let request = + // Request::new_with_init(&self.uri.to_string(), &init).map_err(|_source| Error { + // kind: ErrorType::BuildingRequest, + // source: None, + // })?; + + // Ok(RawRequest { request }) + // } + +} diff --git a/twilight-http/src/http/reqwest/mod.rs b/twilight-http/src/http/reqwest/mod.rs new file mode 100644 index 00000000000..fd4b663192f --- /dev/null +++ b/twilight-http/src/http/reqwest/mod.rs @@ -0,0 +1,74 @@ +use std::{pin::Pin, future::Future, task::{Context, Poll}}; + +use http::{HeaderMap, HeaderValue}; + +use crate::{response::{BytesFuture, StatusCode}, Error, error::ErrorType}; + +#[derive(Debug)] +pub struct HttpClient { + pub(crate) reqwest: reqwest::Client, +} + +impl HttpClient { + pub fn new() -> Self { + HttpClient { reqwest: reqwest::Client::new() } + } + + pub fn request(&self, req: RawRequest) -> RawResponseFuture { + let inner = Box::pin(self.reqwest.execute(req.req)); + RawResponseFuture { inner } + } +} + +pub struct RawRequest { + pub(crate) req: ::reqwest::Request, +} + +pub struct RawResponseFuture { + inner: Pin>>>, +} + +impl Future for RawResponseFuture { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + Pin::new(&mut self.inner) + .poll(cx) + .map_ok(RawResponse::new) + .map_err(|source| Error { + kind: ErrorType::RequestError, + source: Some(Box::new(source)), + }) + } +} + +#[derive(Debug)] +pub struct RawResponse { + pub inner: reqwest::Response, +} + +impl RawResponse { + fn new(inner: reqwest::Response) -> Self { + RawResponse { inner } + } + + pub fn headers(&self) -> &HeaderMap { + self.inner.headers() + } + + pub fn headers_mut(&mut self) -> &mut HeaderMap { + self.inner.headers_mut() + } + + pub fn bytes(self, compressed: bool) -> BytesFuture { + BytesFuture::from_reqwest(self.inner) + } + + pub fn status(&self) -> StatusCode { + // Convert the `hyper` status code into its raw form in order to return + // our own. + let raw = self.inner.status().as_u16(); + + StatusCode::new(raw) + } +} diff --git a/twilight-http/src/lib.rs b/twilight-http/src/lib.rs index 7562599b991..652cef909b9 100644 --- a/twilight-http/src/lib.rs +++ b/twilight-http/src/lib.rs @@ -13,6 +13,7 @@ pub mod request; pub mod response; pub mod routing; +mod http; mod json; /// Discord API version used by this crate. diff --git a/twilight-http/src/request/base.rs b/twilight-http/src/request/base.rs index b68fe99c993..020fb096cfa 100644 --- a/twilight-http/src/request/base.rs +++ b/twilight-http/src/request/base.rs @@ -3,7 +3,7 @@ use crate::{ error::Error, routing::{Path, Route}, }; -use hyper::header::{HeaderMap, HeaderName, HeaderValue}; +use http::header::{HeaderMap, HeaderName, HeaderValue}; use serde::Serialize; /// Builder to create a customized request. diff --git a/twilight-http/src/request/guild/ban/create_ban.rs b/twilight-http/src/request/guild/ban/create_ban.rs index 0809fa15571..f010813195b 100644 --- a/twilight-http/src/request/guild/ban/create_ban.rs +++ b/twilight-http/src/request/guild/ban/create_ban.rs @@ -139,7 +139,7 @@ mod tests { client::Client, request::{AuditLogReason, TryIntoRequest, REASON_HEADER_NAME}, }; - use hyper::header::HeaderValue; + use http::header::HeaderValue; use std::error::Error; use twilight_http_ratelimiting::Method; use twilight_model::id::{ diff --git a/twilight-http/src/request/mod.rs b/twilight-http/src/request/mod.rs index acc8e091429..6bebb8058c3 100644 --- a/twilight-http/src/request/mod.rs +++ b/twilight-http/src/request/mod.rs @@ -73,7 +73,7 @@ pub use self::{ pub use twilight_http_ratelimiting::request::Method; use crate::error::{Error, ErrorType}; -use hyper::header::{HeaderName, HeaderValue}; +use http::header::{HeaderName, HeaderValue}; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use serde::Serialize; use std::iter; diff --git a/twilight-http/src/response/future.rs b/twilight-http/src/response/future.rs index a8a24bf9734..5b1bd71b045 100644 --- a/twilight-http/src/response/future.rs +++ b/twilight-http/src/response/future.rs @@ -1,9 +1,9 @@ use super::{Response, StatusCode}; use crate::{ api_error::ApiError, - error::{Error, ErrorType}, + error::{Error, ErrorType}, http::RawResponseFuture, }; -use hyper::{client::ResponseFuture as HyperResponseFuture, StatusCode as HyperStatusCode}; +//use hyper::{client::ResponseFuture as HyperResponseFuture, StatusCode as HyperStatusCode}; use std::{ future::Future, marker::PhantomData, @@ -28,8 +28,8 @@ enum InnerPoll { } struct Chunking { - future: Pin, Error>> + Send + Sync + 'static>>, - status: HyperStatusCode, + future: Pin, Error>> + 'static>>, + status: StatusCode, } impl Chunking { @@ -54,7 +54,7 @@ impl Chunking { kind: ErrorType::Response { body: bytes, error, - status: StatusCode::new(self.status.as_u16()), + status: StatusCode::new(self.status.get()), }, source: None, })) @@ -72,34 +72,40 @@ impl Failed { } struct InFlight { - future: Pin>>, + future: Pin>, invalid_token: Option>, tx: Option, } impl InFlight { fn poll(mut self, cx: &mut Context<'_>) -> InnerPoll { - let resp = match Pin::new(&mut self.future).poll(cx) { - Poll::Ready(Ok(Ok(resp))) => resp, - Poll::Ready(Ok(Err(source))) => { + let resp = match Future::poll(Pin::new(&mut self.future), cx) { + Poll::Ready(Ok(resp)) => resp, + Poll::Ready(Err(_source)) => { return InnerPoll::Ready(Err(Error { kind: ErrorType::RequestError, - source: Some(Box::new(source)), - })) - } - Poll::Ready(Err(source)) => { - return InnerPoll::Ready(Err(Error { - kind: ErrorType::RequestTimedOut, - source: Some(Box::new(source)), + source: None, })) } + // Poll::Ready(Ok(Err(source))) => { + // return InnerPoll::Ready(Err(Error { + // kind: ErrorType::RequestError, + // source: Some(Box::new(source)), + // })) + // } + // Poll::Ready(Err(source)) => { + // return InnerPoll::Ready(Err(Error { + // kind: ErrorType::RequestTimedOut, + // source: Some(Box::new(source)), + // })) + // } Poll::Pending => return InnerPoll::Pending(ResponseFutureStage::InFlight(self)), }; // If the API sent back an Unauthorized response, then the client's // configured token is permanently invalid and future requests must be // ignored to avoid API bans. - if resp.status() == HyperStatusCode::UNAUTHORIZED { + if resp.status().get() == 401 /*UNAUTHORIZED*/ { if let Some(invalid_token) = self.invalid_token { invalid_token.store(true, Ordering::Relaxed); } @@ -130,16 +136,16 @@ impl InFlight { let mut resp = resp; // Inaccurate since end-users can only access the decompressed body. #[cfg(feature = "decompression")] - resp.headers_mut().remove(hyper::header::CONTENT_LENGTH); + resp.headers_mut().remove(http::header::CONTENT_LENGTH); return InnerPoll::Ready(Ok(Response::new(resp))); } - match status { - HyperStatusCode::TOO_MANY_REQUESTS => { + match status.get() { + 429 => { tracing::warn!("429 response: {resp:?}"); } - HyperStatusCode::SERVICE_UNAVAILABLE => { + 503 => { return InnerPoll::Ready(Err(Error { kind: ErrorType::ServiceUnavailable { response: resp }, source: None, @@ -154,8 +160,12 @@ impl InFlight { .await .map_err(|source| Error { kind: ErrorType::ChunkingResponse, - source: Some(Box::new(source)), + source: None, }) + // .map_err(|source| Error { + // kind: ErrorType::ChunkingResponse, + // source: Some(Box::new(source)), + // }) }; InnerPoll::Advance(ResponseFutureStage::Chunking(Chunking { @@ -167,9 +177,9 @@ impl InFlight { struct RatelimitQueue { invalid_token: Option>, - response_future: HyperResponseFuture, + response_future: RawResponseFuture, timeout: Duration, - pre_flight_check: Option bool + Send + 'static>>, + pre_flight_check: Option bool + 'static>>, wait_for_sender: WaitForTicketFuture, } @@ -196,7 +206,7 @@ impl RatelimitQueue { } InnerPoll::Advance(ResponseFutureStage::InFlight(InFlight { - future: Box::pin(time::timeout(self.timeout, self.response_future)), + future: Box::pin(self.response_future), invalid_token: self.invalid_token, tx: Some(tx), })) @@ -260,7 +270,7 @@ pub struct ResponseFuture { impl ResponseFuture { pub(crate) const fn new( - future: Pin>>, + future: Pin>, invalid_token: Option>, ) -> Self { Self { @@ -328,7 +338,7 @@ impl ResponseFuture { /// ``` pub fn set_pre_flight( &mut self, - pre_flight: Box bool + Send + 'static>, + pre_flight: Box bool + 'static>, ) -> bool { if let ResponseFutureStage::RatelimitQueue(queue) = &mut self.stage { queue.pre_flight_check = Some(pre_flight); @@ -348,7 +358,7 @@ impl ResponseFuture { pub(crate) fn ratelimit( invalid_token: Option>, - response_future: HyperResponseFuture, + response_future: RawResponseFuture, timeout: Duration, wait_for_sender: WaitForTicketFuture, ) -> Self { diff --git a/twilight-http/src/response/mod.rs b/twilight-http/src/response/mod.rs index 1632071843c..0552522bb0a 100644 --- a/twilight-http/src/response/mod.rs +++ b/twilight-http/src/response/mod.rs @@ -50,14 +50,13 @@ pub(crate) mod future; mod status_code; +use crate::http::RawResponse; + pub use self::{future::ResponseFuture, status_code::StatusCode}; use self::marker::ListBody; -use hyper::{ - body::{self, Bytes}, - header::{HeaderValue, Iter as HeaderMapIter}, - Body, Response as HyperResponse, -}; +use bytes::Bytes; +use http::header::{HeaderValue, Iter as HeaderMapIter}; use serde::de::DeserializeOwned; use std::{ error::Error, @@ -174,12 +173,13 @@ pub enum DeserializeBodyErrorType { /// ``` #[derive(Debug)] pub struct Response { - inner: HyperResponse, + //inner: HyperResponse, + inner: RawResponse, phantom: PhantomData, } impl Response { - pub(crate) const fn new(inner: HyperResponse) -> Self { + pub(crate) const fn new(inner: RawResponse) -> Self { Self { inner, phantom: PhantomData, @@ -195,11 +195,7 @@ impl Response { /// Status code of the response. #[must_use = "retrieving the status code has no use on its own"] pub fn status(&self) -> StatusCode { - // Convert the `hyper` status code into its raw form in order to return - // our own. - let raw = self.inner.status().as_u16(); - - StatusCode::new(raw) + self.inner.status() } /// Consume the response and accumulate the chunked body into bytes. @@ -233,34 +229,18 @@ impl Response { /// /// [`text`]: Self::text pub fn bytes(self) -> BytesFuture { + // todo(erk): move this to RawResponse::bytes #[cfg(feature = "decompression")] let compressed = self .inner .headers() - .get(hyper::header::CONTENT_ENCODING) + .get(http::header::CONTENT_ENCODING) .is_some(); - let body = self.inner.into_body(); - - let fut = async move { - { - #[cfg(feature = "decompression")] - if compressed { - return decompress(body).await; - } - - body::to_bytes(body) - .await - .map_err(|source| DeserializeBodyError { - kind: DeserializeBodyErrorType::Chunking, - source: Some(Box::new(source)), - }) - } - }; + #[cfg(not(feature = "decompression"))] + let compressed = false; - BytesFuture { - inner: Box::pin(fut), - } + self.inner.bytes(compressed) } /// Consume the response and accumulate the body into a string. @@ -437,7 +417,49 @@ impl<'a> Iterator for HeaderIter<'a> { #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct BytesFuture { inner: - Pin> + Send + Sync + 'static>>, + Pin> + 'static>>, +} + +impl BytesFuture { + #[cfg(not(target_arch = "wasm32"))] + pub(crate) fn from_hyper(body: hyper::Body, compressed: bool) -> Self { + let fut = async move { + { + #[cfg(feature = "decompression")] + if compressed { + return decompress(body).await; + } + + hyper::body::to_bytes(body) + .await + .map_err(|source| DeserializeBodyError { + kind: DeserializeBodyErrorType::Chunking, + source: Some(Box::new(source)), + }) + } + }; + + BytesFuture { + inner: Box::pin(fut), + } + } + + #[cfg(target_arch = "wasm32")] + pub(crate) fn from_reqwest(response: reqwest::Response) -> Self { + let fut = async move { + response + .bytes() + .await + .map_err(|source| DeserializeBodyError { + kind: DeserializeBodyErrorType::Chunking, + source: Some(Box::new(source)), + }) + }; + + BytesFuture { + inner: Box::pin(fut), + } + } } impl Future for BytesFuture { @@ -574,13 +596,13 @@ impl Future for TextFuture { } } -#[cfg(feature = "decompression")] -async fn decompress(body: Body) -> Result { +#[cfg(all(feature = "decompression", feature = "hyper"))] +async fn decompress(body: hyper::Body) -> Result { use brotli::Decompressor; use hyper::body::Buf; use std::io::Read; - let aggregate = body::aggregate(body) + let aggregate = hyper::body::aggregate(body) .await .map_err(|source| DeserializeBodyError { kind: DeserializeBodyErrorType::Chunking, diff --git a/twilight-util/Cargo.toml b/twilight-util/Cargo.toml index 455b16c9840..341d2c11397 100644 --- a/twilight-util/Cargo.toml +++ b/twilight-util/Cargo.toml @@ -16,7 +16,7 @@ version = "0.15.2" twilight-model = { default-features = false, optional = true, path = "../twilight-model", version = "0.15.2" } twilight-validate = { default-features = false, optional = true, path = "../twilight-validate", version = "0.15.1" } -[dev-dependencies] +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] chrono = { default-features = false, features = ["std"], version = "0.4" } static_assertions = { default-features = false, version = "1" } time = { default-features = false, features = ["formatting"], version = "0.3" }