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

feat: HTTP client #127

Merged
merged 46 commits into from
Nov 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
6083c6c
feat(http): wip client
Berrysoft Oct 25, 2023
604e1df
fix: reserve enough space to read
Berrysoft Oct 25, 2023
d8eabec
feat: use hyper instead of handwriting
Berrysoft Oct 26, 2023
68eef00
fix: use hyper body to_bytes
Berrysoft Oct 26, 2023
1db7b7e
fix: use SendWrapper on stream inner
Berrysoft Oct 26, 2023
faf4ed8
fix: use buffer instead of tmp alloc
Berrysoft Oct 26, 2023
038816d
feat: add request builder
Berrysoft Oct 26, 2023
d7d9397
feat: add response wrapper
Berrysoft Oct 26, 2023
c55f17d
feat: add IntoUrl
Berrysoft Oct 26, 2023
94fdd62
feat: convenience methods
Berrysoft Oct 26, 2023
6d95a70
fix: http doc tests
Berrysoft Oct 26, 2023
9747050
test: port some tests from reqwest
Berrysoft Oct 26, 2023
4d09948
fix: add "all" feature to compio-http
Berrysoft Oct 26, 2023
5a32dff
Merge branch 'master' into dev/http-client
Berrysoft Nov 6, 2023
c83dfb6
fix: runtime doesn't live long enough
Berrysoft Nov 6, 2023
5f8363a
feat(http): add rustls support
Berrysoft Nov 6, 2023
c06c251
fix: clippy warnings
Berrysoft Nov 6, 2023
ecc6e35
fix(runtime): use manually drop
Berrysoft Nov 7, 2023
d3d17d7
test(http): add different tls backend test
Berrysoft Nov 7, 2023
3acf0c7
fix(tls): don't write in read
Berrysoft Nov 7, 2023
e367a3c
feat: split http & http-client
Berrysoft Nov 7, 2023
1c574f3
feat(http): add server services
Berrysoft Nov 7, 2023
9972f4d
test: use http server service
Berrysoft Nov 7, 2023
cc7f666
fix: impl executor
Berrysoft Nov 7, 2023
03d8059
feat: client feature for compio-http
Berrysoft Nov 7, 2023
189b1dd
fix: use shorter syntax in Cargo.toml
Berrysoft Nov 8, 2023
d206259
feat(io): add SyncStream wrapper
Berrysoft Nov 10, 2023
fe27a47
feat: add `read_buf` feature for wrappers
Berrysoft Nov 10, 2023
7123027
fix: remove useless commented code
Berrysoft Nov 10, 2023
9d8a82c
fix: new feature core_io_borrowed_buf
Berrysoft Nov 10, 2023
ce9f02c
refactor: rename ClientRef to ClientInner
Berrysoft Nov 11, 2023
14445b7
fix: add docsrs cfg
Berrysoft Nov 11, 2023
e4fccde
refactor(runtime): expose `async_task::Task`
George-Miao Nov 7, 2023
639d292
refactor: ensure Event being oneshot
Berrysoft Nov 9, 2023
11bfe59
feat: init compio-log
ho-229 Nov 4, 2023
e7dfa33
refactor: compio-log
ho-229 Nov 5, 2023
6bae688
feat: add logging for runtime
ho-229 Nov 5, 2023
fe81ca5
refactor(log): rewrite instrument
ho-229 Nov 6, 2023
cd7ccf1
chore(log): remove unused import
ho-229 Nov 6, 2023
793bcd0
fix(log): test
ho-229 Nov 6, 2023
6a993f5
feat(driver): add logging for iour
ho-229 Nov 7, 2023
dc19f59
chore(runtime): change log level to debug
ho-229 Nov 7, 2023
e548032
feat(driver): add logging for iocp
ho-229 Nov 8, 2023
89309ec
feat(driver): add logging for fusion
ho-229 Nov 9, 2023
e60a854
fix: fix merge missing compio-tls
Berrysoft Nov 11, 2023
e63749c
Merge branch 'master' into dev/http-client
Berrysoft Nov 11, 2023
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
13 changes: 13 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ members = [
"compio-io",
"compio-tls",
"compio-log",
"compio-http",
"compio-http-client",
]
resolver = "2"

Expand All @@ -32,20 +34,31 @@ compio-io = { path = "./compio-io", version = "0.1.0" }
compio-net = { path = "./compio-net", version = "0.2.0" }
compio-signal = { path = "./compio-signal", version = "0.1.1" }
compio-dispatcher = { path = "./compio-dispatcher", version = "0.1.0" }
compio-http = { path = "./compio-http", version = "0.1.0" }
compio-log = { path = "./compio-log", version = "0.1.0" }
compio-tls = { path = "./compio-tls", version = "0.1.0" }


cfg-if = "1.0.0"
crossbeam-channel = "0.5.8"
crossbeam-queue = "0.3.8"
futures-channel = "0.3.29"
futures-util = "0.3.29"
libc = "0.2.149"
nix = "0.27.1"
once_cell = "1.18.0"
os_pipe = "1.1.4"
paste = "1.0.14"
send_wrapper = "0.6"
slab = "0.4.9"
socket2 = "0.5.5"
tempfile = "3.8.1"
tokio = "1.33.0"
widestring = "1.0.2"
windows-sys = "0.48.0"

native-tls = "0.2.11"
rustls = "0.21.8"
rustls-native-certs = "0.6.3"

hyper = "0.14"
2 changes: 1 addition & 1 deletion compio-buf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(feature = "allocator_api", feature(allocator_api))]
#![cfg_attr(feature = "read_buf", feature(read_buf))]
#![cfg_attr(feature = "read_buf", feature(read_buf, core_io_borrowed_buf))]
#![cfg_attr(feature = "try_trait_v2", feature(try_trait_v2, try_trait_v2_residual))]
#![warn(missing_docs)]

Expand Down
51 changes: 51 additions & 0 deletions compio-http-client/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
[package]
name = "compio-http-client"
version = "0.1.0"
categories = ["asynchronous", "network-programming"]
keywords = ["async", "net"]
edition = { workspace = true }
authors = { workspace = true }
readme = { workspace = true }
license = { workspace = true }
repository = { workspace = true }

Berrysoft marked this conversation as resolved.
Show resolved Hide resolved
[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg docsrs"]

[dependencies]
compio-buf = { workspace = true, features = ["bytes"] }
compio-runtime = { workspace = true, features = ["time"] }
compio-tls = { workspace = true }
compio-http = { workspace = true, features = ["client"] }

http = "0.2"
hyper = { workspace = true, features = ["client", "http1"] }

base64 = "0.21"
encoding_rs = "0.8"
mime = "0.3"
serde = "1"
serde_json = { version = "1", optional = true }
serde_urlencoded = "0.7"
thiserror = "1"
url = "2"

[dev-dependencies]
compio-net = { workspace = true, features = ["runtime"] }
compio-macros = { workspace = true }
compio-http = { workspace = true, features = ["server"] }

futures-channel = { workspace = true }
hyper = { workspace = true, features = ["server"] }

[features]
default = ["native-tls"]
native-tls = ["compio-http/native-tls"]
rustls = ["compio-http/rustls"]
vendored = ["compio-http/vendored"]
json = ["dep:serde_json"]
all = ["json", "native-tls", "rustls", "compio-http/all"]

read_buf = ["compio-http/read_buf"]
nightly = ["read_buf"]
8 changes: 8 additions & 0 deletions compio-http-client/examples/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use compio_http_client::Client;

#[compio_macros::main]
async fn main() {
let client = Client::new();
let response = client.get("https://www.example.com/").send().await.unwrap();
println!("{}", response.text().await.unwrap());
}
159 changes: 159 additions & 0 deletions compio-http-client/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use std::rc::Rc;

use compio_http::{CompioExecutor, Connector, TlsBackend};
use hyper::{Body, HeaderMap, Method, Uri};

use crate::{IntoUrl, Request, RequestBuilder, Response, Result};

/// An asynchronous `Client` to make Requests with.
#[derive(Debug, Clone)]
pub struct Client {
client: Rc<ClientInner>,
}

impl Client {
/// Create a client with default config.
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
ClientBuilder::new().build()
}

/// Creates a `ClientBuilder` to configure a `Client`.
///
/// This is the same as `ClientBuilder::new()`.
pub fn builder() -> ClientBuilder {
ClientBuilder::new()
}

/// Send a request and wait for a response.
pub async fn execute(&self, request: Request) -> Result<Response> {
let (method, url, headers, body, timeout, version) = request.pieces();
let mut request = hyper::Request::builder()
.method(method)
.uri(
url.as_str()
.parse::<Uri>()
.expect("a parsed Url should always be a valid Uri"),
)
.version(version)
.body(body.unwrap_or_else(Body::empty))?;
*request.headers_mut() = self.client.headers.clone();
crate::util::replace_headers(request.headers_mut(), headers);

let future = self.client.client.request(request);
let res = if let Some(timeout) = timeout {
compio_runtime::time::timeout(timeout, future)
.await
.map_err(|_| crate::Error::Timeout)??
} else {
future.await?
};
Ok(Response::new(res, url))
}

/// Send a request with method and url.
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
RequestBuilder::new(
self.clone(),
url.into_url().map(|url| Request::new(method, url)),
)
}

/// Convenience method to make a `GET` request to a URL.
pub fn get<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::GET, url)
}

/// Convenience method to make a `POST` request to a URL.
pub fn post<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::POST, url)
}

/// Convenience method to make a `PUT` request to a URL.
pub fn put<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PUT, url)
}

/// Convenience method to make a `PATCH` request to a URL.
pub fn patch<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::PATCH, url)
}

/// Convenience method to make a `DELETE` request to a URL.
pub fn delete<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::DELETE, url)
}

/// Convenience method to make a `HEAD` request to a URL.
pub fn head<U: IntoUrl>(&self, url: U) -> RequestBuilder {
self.request(Method::HEAD, url)
}
}

#[derive(Debug)]
struct ClientInner {
client: hyper::Client<Connector, Body>,
headers: HeaderMap,
}

/// A `ClientBuilder` can be used to create a `Client` with custom
/// configuration.
#[derive(Debug)]
#[must_use]
pub struct ClientBuilder {
headers: HeaderMap,
tls: TlsBackend,
}

impl Default for ClientBuilder {
fn default() -> Self {
Self::new()
}
}

impl ClientBuilder {
/// Constructs a new `ClientBuilder`.
pub fn new() -> Self {
Self {
headers: HeaderMap::new(),
tls: TlsBackend::default(),
}
}

/// Returns a `Client` that uses this `ClientBuilder` configuration.
pub fn build(self) -> Client {
let client = hyper::Client::builder()
.executor(CompioExecutor)
.set_host(true)
.build(Connector::new(self.tls));
let client_ref = ClientInner {
client,
headers: self.headers,
};
Client {
client: Rc::new(client_ref),
}
}

/// Set the default headers for every request.
pub fn default_headers(mut self, headers: HeaderMap) -> Self {
for (key, value) in headers.iter() {
self.headers.insert(key, value.clone());
}
self
}

/// Force using the native TLS backend.
#[cfg(feature = "native-tls")]
pub fn use_native_tls(mut self) -> Self {
self.tls = TlsBackend::NativeTls;
self
}

/// Force using the Rustls TLS backend.
#[cfg(feature = "rustls")]
pub fn use_rustls(mut self) -> Self {
self.tls = TlsBackend::Rustls;
self
}
}
36 changes: 36 additions & 0 deletions compio-http-client/src/into_url.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use url::Url;

/// A trait to try to convert some type into a [`Url`].
pub trait IntoUrl {
/// Besides parsing as a valid [`Url`], the [`Url`] must be a valid
/// `http::Uri`, in that it makes sense to use in a network request.
fn into_url(self) -> crate::Result<Url>;
}

impl IntoUrl for Url {
fn into_url(self) -> crate::Result<Url> {
if self.has_host() {
Ok(self)
} else {
Err(crate::Error::BadScheme(self))
}
}
}

impl<'a> IntoUrl for &'a str {
fn into_url(self) -> crate::Result<Url> {
Ok(Url::parse(self)?)
}
}

impl<'a> IntoUrl for &'a String {
fn into_url(self) -> crate::Result<Url> {
(&**self).into_url()
}
}

impl IntoUrl for String {
fn into_url(self) -> crate::Result<Url> {
(&*self).into_url()
}
}
52 changes: 52 additions & 0 deletions compio-http-client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//! A high level HTTP client based on compio.

#![warn(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]

mod client;
pub use client::*;

mod request;
pub use request::*;

mod response;
pub use response::*;

mod into_url;
pub use into_url::*;

mod util;

/// The error type used in `compio-http`.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
/// The request is timeout.
#[error("request timeout")]
Timeout,
/// Bad scheme.
#[error("bad scheme: {0}")]
BadScheme(url::Url),
/// IO error occurs.
#[error("system error: {0}")]
System(#[from] std::io::Error),
/// HTTP related parse error.
#[error("`http` error: {0}")]
Http(#[from] http::Error),
/// Hyper error.
#[error("`hyper` error: {0}")]
Hyper(#[from] hyper::Error),
/// URL parse error.
#[error("url parse error: {0}")]
UrlParse(#[from] url::ParseError),
/// URL encoding error.
#[error("url encode error: {0}")]
UrlEncoded(#[from] serde_urlencoded::ser::Error),
/// JSON serialization error.
#[cfg(feature = "json")]
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
}

/// The result type used in `compio-http`.
pub type Result<T> = std::result::Result<T, Error>;
Loading