Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstract HTTP and add support for wasm through Reqwest #2229

Draft
wants to merge 8 commits into
base: next
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 6 additions & 8 deletions examples/http-get-message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions twilight-http-ratelimiting/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
laralove143 marked this conversation as resolved.
Show resolved Hide resolved
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" }

Expand Down
21 changes: 16 additions & 5 deletions twilight-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand All @@ -32,6 +30,19 @@ 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.10" }

[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 = [] }
getrandom = { version = "0.2", features = ["js"] }

#worker = "0.0.13"
#worker-sys = "0.0.7"

[features]
default = ["decompression", "rustls-native-roots"]
decompression = ["dep:brotli"]
Expand All @@ -40,7 +51,7 @@ 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" }
Expand Down
8 changes: 3 additions & 5 deletions twilight-http/src/client/builder.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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)))
Expand Down
31 changes: 15 additions & 16 deletions twilight-http/src/client/mod.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -223,7 +217,7 @@ impl Deref for Token {
pub struct Client {
pub(crate) default_allowed_mentions: Option<AllowedMentions>,
default_headers: Option<HeaderMap>,
http: HyperClient<Connector>,
http: HttpClient,
proxy: Option<Box<str>>,
ratelimiter: Option<Box<dyn Ratelimiter>>,
timeout: Duration,
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -2612,7 +2606,7 @@ impl Client {

#[cfg(feature = "decompression")]
headers.insert(
hyper::header::ACCEPT_ENCODING,
http::header::ACCEPT_ENCODING,
HeaderValue::from_static("br"),
);

Expand All @@ -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.
Expand Down
5 changes: 2 additions & 3 deletions twilight-http/src/error.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -125,7 +124,7 @@ pub enum ErrorType {
///
/// This may occur during Discord API stability incidents.
ServiceUnavailable {
response: Response<Body>,
response: RawResponse,
},
/// Token in use has become revoked or is otherwise invalid.
///
Expand Down
9 changes: 9 additions & 0 deletions twilight-http/src/http/hyper/#mod.rs#
Original file line number Diff line number Diff line change
@@ -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, RawRequestBuilder};
pub use client::HttpClient;
23 changes: 23 additions & 0 deletions twilight-http/src/http/hyper/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use super::{response::RawResponseFuture, request::RawRequest, connector};

#[derive(Debug)]
pub struct HttpClient {
pub(crate) hyper: hyper::Client<connector::Connector>
}

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
}
}
}
9 changes: 9 additions & 0 deletions twilight-http/src/http/hyper/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
8 changes: 8 additions & 0 deletions twilight-http/src/http/hyper/request.rs
Original file line number Diff line number Diff line change
@@ -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<Body>
}
49 changes: 49 additions & 0 deletions twilight-http/src/http/hyper/response.rs
Original file line number Diff line number Diff line change
@@ -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<Body>,
}

impl RawResponse {
fn new(inner: hyper::Response<Body>) -> Self {
RawResponse { inner }
}

pub fn headers(&self) -> &HeaderMap<HeaderValue> {
self.inner.headers()
}

pub fn headers_mut(&mut self) -> &mut HeaderMap<HeaderValue> {
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<RawResponse>;

fn poll(mut self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
Pin::new(&mut self.inner).poll(cx).map_ok(RawResponse::new)
}
}
89 changes: 89 additions & 0 deletions twilight-http/src/http/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::{Error, error::ErrorType};

#[cfg(not(target_arch = "wasm32"))]
mod hyper;
#[cfg(not(target_arch = "wasm32"))]
pub use super::hyper::{HttpClient, RawRequest, RawResponse, RawResponseFuture};

#[cfg(target_arch = "wasm32")]
mod reqwest;
#[cfg(target_arch = "wasm32")]
pub use self::reqwest::{HttpClient, RawRequest, RawResponseFuture, RawResponse};

// mod worker;

// pub use self::worker::{HttpClient, RawRequestBuilder, RawRequest, RawResponseFuture, RawResponse};

use http::{Method, HeaderMap, HeaderValue, Uri};

pub struct RawRequestBuilder {
method: Method,
uri: Uri,
headers: HeaderMap<HeaderValue>,
body: Vec<u8>,
}

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<Self, Error> {
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<HeaderValue>> {
Some(&mut self.headers)
}

pub fn body(mut self, body: Vec<u8>) -> Self {
self.body = body;

self
}

#[cfg(not(target_arch = "wasm32"))]
pub(super) fn build(self) -> Result<tmp::RawRequest, Error> {
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(tmp::RawRequest { hyper })
}

#[cfg(target_arch = "wasm32")]
pub(super) fn build(self) -> Result<reqwest::RawRequest, Error> {
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 })
}
}
Loading