client: Add HttpClientExt

This commit is contained in:
Jonas Platte 2021-04-27 01:52:59 +02:00
parent e94e2e7b2b
commit c7742085a8
No known key found for this signature in database
GPG Key ID: CC154DE0E30B7C67
5 changed files with 145 additions and 62 deletions

View File

@ -1,6 +1,5 @@
use std::{convert::TryFrom, env, process::exit};
use http::Uri;
use ruma::{
api::client::r0::{alias::get_alias, membership::join_room_by_id, message::send_message_event},
events::{room::message::MessageEventContent, AnyMessageEventContent},
@ -10,7 +9,7 @@ use ruma::{
type MatrixClient = ruma_client::Client<ruma_client::http_client::HyperNativeTls>;
async fn hello_world(
homeserver_url: Uri,
homeserver_url: String,
username: &str,
password: &str,
room_alias: &RoomAliasId,
@ -18,10 +17,10 @@ async fn hello_world(
let client = MatrixClient::new(homeserver_url, None);
client.log_in(username, password, None, Some("ruma-example-client")).await?;
let room_id = client.request(get_alias::Request::new(room_alias)).await?.room_id;
client.request(join_room_by_id::Request::new(&room_id)).await?;
let room_id = client.send_request(get_alias::Request::new(room_alias)).await?.room_id;
client.send_request(join_room_by_id::Request::new(&room_id)).await?;
client
.request(send_message_event::Request::new(
.send_request(send_message_event::Request::new(
&room_id,
"1",
&AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain("Hello World!")),
@ -45,11 +44,5 @@ async fn main() -> anyhow::Result<()> {
}
};
hello_world(
homeserver_url.parse()?,
&username,
&password,
&RoomAliasId::try_from(room.as_str())?,
)
.await
hello_world(homeserver_url, &username, &password, &RoomAliasId::try_from(room.as_str())?).await
}

View File

@ -1,7 +1,6 @@
use std::{env, process::exit, time::Duration};
use assign::assign;
use http::Uri;
use ruma::{
api::client::r0::{filter::FilterDefinition, sync::sync_events},
events::{
@ -14,14 +13,18 @@ use tokio_stream::StreamExt as _;
type MatrixClient = ruma_client::Client<ruma_client::http_client::HyperNativeTls>;
async fn log_messages(homeserver_url: Uri, username: &str, password: &str) -> anyhow::Result<()> {
async fn log_messages(
homeserver_url: String,
username: &str,
password: &str,
) -> anyhow::Result<()> {
let client = MatrixClient::new(homeserver_url, None);
client.log_in(username, password, None, None).await?;
let filter = FilterDefinition::ignore_all().into();
let initial_sync_response = client
.request(assign!(sync_events::Request::new(), {
.send_request(assign!(sync_events::Request::new(), {
filter: Some(&filter),
}))
.await?;
@ -76,6 +79,5 @@ async fn main() -> anyhow::Result<()> {
}
};
let server = homeserver_url.parse()?;
log_messages(server, &username, &password).await
log_messages(homeserver_url, &username, &password).await
}

View File

@ -27,7 +27,7 @@ impl<C: HttpClient> Client<C> {
initial_device_display_name: Option<&str>,
) -> Result<login::Response, Error<C::Error, ruma_client_api::Error>> {
let response = self
.request(assign!(
.send_request(assign!(
login::Request::new(
LoginInfo::Password { identifier: UserIdentifier::MatrixId(user), password }
), {
@ -50,7 +50,7 @@ impl<C: HttpClient> Client<C> {
&self,
) -> Result<register::Response, Error<C::Error, ruma_client_api::r0::uiaa::UiaaResponse>> {
let response = self
.request(assign!(register::Request::new(), { kind: RegistrationKind::Guest }))
.send_request(assign!(register::Request::new(), { kind: RegistrationKind::Guest }))
.await?;
*self.0.access_token.lock().unwrap() = response.access_token.clone();
@ -71,7 +71,7 @@ impl<C: HttpClient> Client<C> {
password: &str,
) -> Result<register::Response, Error<C::Error, ruma_client_api::r0::uiaa::UiaaResponse>> {
let response = self
.request(assign!(register::Request::new(), { username, password: Some(password) }))
.send_request(assign!(register::Request::new(), { username, password: Some(password) }))
.await?;
*self.0.access_token.lock().unwrap() = response.access_token.clone();
@ -116,7 +116,7 @@ impl<C: HttpClient> Client<C> {
try_stream! {
loop {
let response = self
.request(assign!(sync_events::Request::new(), {
.send_request(assign!(sync_events::Request::new(), {
filter,
since: Some(&since),
set_presence,

View File

@ -1,8 +1,13 @@
//! This module contains an abstraction for HTTP clients as well as friendly-named re-exports of
//! client types that implement this trait.
use std::{collections::BTreeMap, future::Future, pin::Pin};
use async_trait::async_trait;
use bytes::BufMut;
use ruma_api::{OutgoingRequest, SendAccessToken};
use crate::ResponseResult;
#[cfg(feature = "hyper")]
mod hyper;
@ -16,9 +21,9 @@ pub use self::hyper::HyperRustls;
/// An HTTP client that can be used to send requests to a Matrix homeserver.
#[async_trait]
pub trait HttpClient {
pub trait HttpClient: Sync {
/// The type to use for `try_into_http_request`.
type RequestBody: Default + BufMut;
type RequestBody: Default + BufMut + Send;
/// The type to use for `try_from_http_response`.
type ResponseBody: AsRef<[u8]>;
@ -38,3 +43,46 @@ pub trait DefaultConstructibleHttpClient: HttpClient {
/// Creates a new HTTP client with default configuration.
fn default() -> Self;
}
/// Convenience functionality on top of `HttpClient`.
///
/// If you want to build your own matrix client type instead of using `ruma_client::Client`, this
/// trait should make that relatively easy.
pub trait HttpClientExt: HttpClient {
/// Send a strongly-typed matrix request to get back a strongly-typed response.
// TODO: `R: 'a` bound should not be needed
fn send_request<'a, R: OutgoingRequest + 'a>(
&'a self,
homeserver_url: &str,
access_token: SendAccessToken<'_>,
request: R,
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>> {
Box::pin(crate::send_request_with_url_params(
self,
homeserver_url,
access_token,
None,
request,
))
}
/// Send a strongly-typed matrix request to get back a strongly-typed response.
fn send_request_with_url_params<'a, R: OutgoingRequest + 'a>(
&'a self,
homeserver_url: &str,
access_token: SendAccessToken<'_>,
extra_params: BTreeMap<String, String>,
request: R,
) -> Pin<Box<dyn Future<Output = ResponseResult<Self, R>> + 'a>> {
Box::pin(crate::send_request_with_url_params(
self,
homeserver_url,
access_token,
Some(extra_params),
request,
))
}
}
#[async_trait]
impl<T: HttpClient> HttpClientExt for T {}

View File

@ -75,12 +75,13 @@
use std::{
collections::BTreeMap,
future::Future,
sync::{Arc, Mutex},
};
use assign::assign;
use http::uri::Uri;
use ruma_api::{AuthScheme, OutgoingRequest, SendAccessToken};
use ruma_api::{OutgoingRequest, SendAccessToken};
use ruma_serde::urlencoded;
// "Undo" rename from `Cargo.toml` that only serves to make `hyper-rustls` available as a Cargo
@ -95,9 +96,15 @@ pub mod http_client;
pub use self::{
error::Error,
http_client::{DefaultConstructibleHttpClient, HttpClient},
http_client::{DefaultConstructibleHttpClient, HttpClient, HttpClientExt},
};
/// The result of sending the request `R` with the http client `C`.
pub type ResponseResult<C, R> = Result<
<R as OutgoingRequest>::IncomingResponse,
Error<<C as HttpClient>::Error, <R as OutgoingRequest>::EndpointError>,
>;
/// A client for the Matrix client-server API.
#[derive(Clone, Debug)]
pub struct Client<C>(Arc<ClientData<C>>);
@ -106,7 +113,7 @@ pub struct Client<C>(Arc<ClientData<C>>);
#[derive(Debug)]
struct ClientData<C> {
/// The URL of the homeserver to connect to.
homeserver_url: Uri,
homeserver_url: String,
/// The underlying HTTP client.
http_client: C,
@ -121,7 +128,7 @@ impl<C> Client<C> {
/// This allows the user to configure the details of HTTP as desired.
pub fn with_http_client(
http_client: C,
homeserver_url: Uri,
homeserver_url: String,
access_token: Option<String>,
) -> Self {
Self(Arc::new(ClientData {
@ -141,7 +148,7 @@ impl<C> Client<C> {
impl<C: DefaultConstructibleHttpClient> Client<C> {
/// Creates a new client based on a default-constructed hyper HTTP client.
pub fn new(homeserver_url: Uri, access_token: Option<String>) -> Self {
pub fn new(homeserver_url: String, access_token: Option<String>) -> Self {
Self(Arc::new(ClientData {
homeserver_url,
http_client: DefaultConstructibleHttpClient::default(),
@ -152,48 +159,81 @@ impl<C: DefaultConstructibleHttpClient> Client<C> {
impl<C: HttpClient> Client<C> {
/// Makes a request to a Matrix API endpoint.
pub async fn request<Request: OutgoingRequest>(
pub async fn send_request<R: OutgoingRequest>(
&self,
request: Request,
) -> Result<Request::IncomingResponse, Error<C::Error, Request::EndpointError>> {
self.request_with_url_params(request, None).await
request: R,
) -> Result<R::IncomingResponse, Error<C::Error, R::EndpointError>> {
let access_token = self.access_token();
let send_access_token = match access_token.as_deref() {
Some(at) => SendAccessToken::IfRequired(at),
None => SendAccessToken::None,
};
send_request_with_url_params(
&self.0.http_client,
&self.0.homeserver_url,
send_access_token,
None,
request,
)
.await
}
/// Makes a request to a Matrix API endpoint including additional URL parameters.
pub async fn request_with_url_params<Request: OutgoingRequest>(
pub async fn send_request_with_url_params<R: OutgoingRequest>(
&self,
request: Request,
extra_params: Option<BTreeMap<String, String>>,
) -> Result<Request::IncomingResponse, Error<C::Error, Request::EndpointError>> {
let client = self.0.clone();
let mut http_request = {
let lock;
let access_token = if Request::METADATA.authentication == AuthScheme::AccessToken {
lock = client.access_token.lock().unwrap();
if let Some(access_token) = &*lock {
SendAccessToken::IfRequired(access_token.as_str())
} else {
return Err(Error::AuthenticationRequired);
}
} else {
SendAccessToken::None
extra_params: BTreeMap<String, String>,
request: R,
) -> Result<R::IncomingResponse, Error<C::Error, R::EndpointError>> {
let access_token = self.access_token();
let send_access_token = match access_token.as_deref() {
Some(at) => SendAccessToken::IfRequired(at),
None => SendAccessToken::None,
};
send_request_with_url_params(
&self.0.http_client,
&self.0.homeserver_url,
send_access_token,
Some(extra_params),
request,
)
.await
}
}
fn send_request_with_url_params<'a, C, Request>(
http_client: &'a C,
homeserver_url: &str,
send_access_token: SendAccessToken<'_>,
extra_params: Option<BTreeMap<String, String>>,
request: Request,
) -> impl Future<Output = Result<Request::IncomingResponse, Error<C::Error, Request::EndpointError>>>
+ Send
+ 'a
where
C: HttpClient + ?Sized,
Request: OutgoingRequest,
{
let res = request.try_into_http_request(homeserver_url, send_access_token);
async move {
let mut http_request = res?;
if let Some(extra_params) = extra_params {
let extra_params = urlencoded::to_string(extra_params).unwrap();
let uri = http_request.uri_mut();
let new_path_and_query = match uri.query() {
Some(params) => format!("{}?{}&{}", uri.path(), params, extra_params),
None => format!("{}?{}", uri.path(), extra_params),
};
request.try_into_http_request(&client.homeserver_url.to_string(), access_token)?
};
let extra_params = urlencoded::to_string(extra_params).unwrap();
let uri = http_request.uri_mut();
let new_path_and_query = match uri.query() {
Some(params) => format!("{}?{}&{}", uri.path(), params, extra_params),
None => format!("{}?{}", uri.path(), extra_params),
};
*uri = Uri::from_parts(assign!(uri.clone().into_parts(), {
path_and_query: Some(new_path_and_query.parse()?),
}))?;
*uri = Uri::from_parts(assign!(uri.clone().into_parts(), {
path_and_query: Some(new_path_and_query.parse()?),
}))?;
}
let http_response =
client.http_client.send_http_request(http_request).await.map_err(Error::Response)?;
http_client.send_http_request(http_request).await.map_err(Error::Response)?;
Ok(ruma_api::IncomingResponse::try_from_http_response(http_response)?)
}
}