client: Add HttpClientExt
This commit is contained in:
parent
e94e2e7b2b
commit
c7742085a8
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 {}
|
||||
|
@ -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)?)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user