Skip to content

Commit 2bd8305

Browse files
authored
Merge pull request #5 from MadLittleMods/rei/tweaks
Add support for `matrix-federation://` scheme to be compatible with new versions of Synapse
2 parents 834a6f1 + f7b186c commit 2bd8305

File tree

4 files changed

+78
-66
lines changed

4 files changed

+78
-66
lines changed

Diff for: README.md

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
# Matrix Hyper Federation Client
22

3-
A hyper client for connecting over Matrix federation.
3+
A hyper client for handling internal Synapse routing of outbound federation traffic.
44

5+
- `matrix-federation://`: Used in Synapse >= [1.87.0rc1][synapse-1.87.0rc1-changelog]
6+
(2023-06-27)
7+
- `matrix://`: Used in Synapse < [1.87.0rc1][synapse-1.87.0rc1-changelog] (2023-06-27)
8+
9+
[synapse-1.87.0rc1-changelog]: https://github.com/element-hq/synapse/blob/develop/docs/changelogs/CHANGES-2023.md#synapse-1870rc1-2023-06-27
510

611
## Example
712

@@ -10,9 +15,10 @@ use ed25519_dalek::SigningKey;
1015
use matrix_hyper_federation_client::SigningFederationClient;
1116
1217
async fn run(secret_key: SigningKey) -> Result<(), anyhow::Error> {
13-
let client = SigningFederationClient::new("local_server", "ed25519:sg5Sa", secret_key).await?;
18+
let client = SigningFederationClient::new("local_server", "ed25519:sg5Sa", secret_key)?;
1419
15-
let resp = client.get("matrix://matrix.org/_matrix/federation/v1/version".parse()?).await?;
20+
let resp = client.get("matrix-federation://matrix.org/_matrix/federation/v1/version".parse()?).await?;
21+
// let resp = client.get("matrix://matrix.org/_matrix/federation/v1/version".parse()?).await?;
1622
1723
assert_eq!(resp.status(), 200);
1824

Diff for: src/client.rs

+26-21
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ use signed_json::{Canonical, Signed};
1919

2020
use crate::server_resolver::{handle_delegated_server, MatrixConnector};
2121

22-
/// A [`hyper::Client`] that routes `matrix://` URIs correctly, but does not
23-
/// sign the requests.
22+
/// A [`hyper::Client`] that routes `matrix://` (Synapse <1.87.0rc1 (2023-06-27)) and
23+
/// `matrix-federation://` (Synapse >=1.87.0rc1 (2023-06-27)) URIs correctly, but does
24+
/// not sign the requests.
2425
///
2526
/// Either use [`SigningFederationClient`] if you want requests to be automatically
2627
/// signed, or [`sign_and_build_json_request`] to sign the requests.
@@ -34,26 +35,26 @@ impl FederationClient {
3435
FederationClient { client }
3536
}
3637

38+
/// Helper function to build a [`FederationClient`].
39+
pub fn new_with_default_resolver() -> Result<FederationClient, Error> {
40+
let connector = MatrixConnector::with_default_resolver()?;
41+
42+
Ok(FederationClient {
43+
client: Client::builder().build(connector),
44+
})
45+
}
46+
3747
pub async fn request(&self, mut req: Request<Body>) -> Result<Response<Body>, Error> {
3848
req = handle_delegated_server(&self.client, req).await?;
3949

4050
Ok(self.client.request(req).await?)
4151
}
4252
}
4353

44-
/// Helper function to build a [`FederationClient`].
45-
pub async fn new_federation_client() -> Result<FederationClient, Error> {
46-
let connector = MatrixConnector::with_default_resolver().await?;
47-
48-
Ok(FederationClient {
49-
client: Client::builder().build(connector),
50-
})
51-
}
52-
53-
/// A HTTP client that correctly resolves `matrix://` URIs and signs the
54+
/// A HTTP client that correctly resolves `matrix://` and `matrix-federation://` URIs and signs the
5455
/// requests.
5556
///
56-
/// This will fail for requests to a `matrix://` URI that have a non-JSON body.
57+
/// This will fail for requests to a `matrix://` or `matrix-federation://` URI that have a non-JSON body.
5758
///
5859
/// **Note**: Using this is less efficient than using a [`Client`] with a
5960
/// [`MatrixConnector`] and manually signing the requests, as the implementation
@@ -69,12 +70,12 @@ pub struct SigningFederationClient<C = MatrixConnector> {
6970

7071
impl SigningFederationClient<MatrixConnector> {
7172
/// Create a new client with the default resolver.
72-
pub async fn new(
73+
pub fn new(
7374
server_name: impl ToString,
7475
key_id: impl ToString,
7576
secret_key: SigningKey,
7677
) -> Result<Self, Error> {
77-
let connector = MatrixConnector::with_default_resolver().await?;
78+
let connector = MatrixConnector::with_default_resolver()?;
7879

7980
Ok(SigningFederationClient {
8081
client: Client::builder().build(connector),
@@ -88,8 +89,8 @@ impl SigningFederationClient<MatrixConnector> {
8889
impl<C> SigningFederationClient<C> {
8990
/// Create a new [`SigningFederationClient`] using the given [`Client`].
9091
///
91-
/// Note, the connector used by the [`Client`] must support `matrix://`
92-
/// URIs.
92+
/// Note, the connector used by the [`Client`] must support `matrix://` and
93+
/// `matrix-federation://` URIs.
9394
pub fn with_client(
9495
client: Client<C>,
9596
server_name: String,
@@ -122,14 +123,18 @@ where
122123

123124
/// Send the request.
124125
///
125-
/// For `matrix://` URIs the request body must be JSON (if not empty) and
126-
/// the request will be signed.
126+
/// For `matrix://` or `matrix-federation://` URIs the request body must be JSON (if
127+
/// not empty) and the request will be signed.
127128
pub async fn request(&self, mut req: Request<Body>) -> Result<Response<Body>, Error> {
128129
req = handle_delegated_server(&self.client, req).await?;
129130

130-
if req.uri().scheme() != Some(&"matrix".parse()?) {
131-
return Ok(self.client.request(req).await?);
131+
// Return-early and make a normal request if the URI scheme is not `matrix://`
132+
// or `matrix-federation://`.
133+
match req.uri().scheme_str() {
134+
Some("matrix") | Some("matrix-federation") => {}
135+
_ => return Ok(self.client.request(req).await?),
132136
}
137+
133138
if !req.body().is_end_stream()
134139
&& req.headers().get(CONTENT_TYPE)
135140
!= Some(&HeaderValue::from_static("application/json"))

Diff for: src/lib.rs

+10-8
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
//!
33
//! # [`SigningFederationClient`]
44
//!
5-
//! The [`SigningFederationClient`] correctly routes `matrix://` URIs and
5+
//! The [`SigningFederationClient`] correctly routes `matrix://` (Synapse <1.87.0rc1
6+
//! (2023-06-27)) and `matrix-federation://` (Synapse >=1.87.0rc1 (2023-06-27)) URIs and
67
//! automatically signs such requests:
78
//!
89
//! ```no_run
@@ -11,9 +12,9 @@
1112
//! #
1213
//! # async fn run(secret_key: SigningKey) -> Result<(), anyhow::Error> {
1314
//! #
14-
//! let client = SigningFederationClient::new("local_server", "ed25519:sg5Sa", secret_key).await?;
15+
//! let client = SigningFederationClient::new("local_server", "ed25519:sg5Sa", secret_key)?;
1516
//!
16-
//! let uri = "matrix://matrix.org/_matrix/federation/v1/version".parse()?;
17+
//! let uri = "matrix-federation://matrix.org/_matrix/federation/v1/version".parse()?;
1718
//! let resp = client.get(uri).await?;
1819
//!
1920
//! assert_eq!(resp.status(), 200);
@@ -28,22 +29,23 @@
2829
//! # [`FederationClient`]
2930
//!
3031
//! The [`FederationClient`] is just a standard [`hyper::Client`] with a
31-
//! [`MatrixConnector`] that can route `matrix://` URIs, but does *not* sign the
32-
//! requests automatically:
32+
//! [`MatrixConnector`] that can route `matrix://` and `matrix-federation://` URIs, but
33+
//! does *not* sign the requests automatically:
3334
//!
3435
//! ```no_run
35-
//! # use matrix_hyper_federation_client::client::{new_federation_client, sign_and_build_json_request};
36+
//! # use matrix_hyper_federation_client::FederationClient;
3637
//! # use hyper::Request;
3738
//! use matrix_hyper_federation_client::SignedRequestBuilderExt;
3839
//! # use ed25519_dalek::SigningKey;
3940
//! #
4041
//! # async fn run(secret_key: &SigningKey) -> Result<(), anyhow::Error> {
4142
//! #
42-
//! let client = new_federation_client().await?;
43+
//! let client = FederationClient::new_with_default_resolver()
44+
//! .expect("failed to build federation client");
4345
//!
4446
//! let request = Request::builder()
4547
//! .method("GET")
46-
//! .uri("matrix://matrix.org/_matrix/federation/v1/version")
48+
//! .uri("matrix-federation://matrix.org/_matrix/federation/v1/version")
4749
//! .signed("localhost", "ed25519:sg5Sa", &secret_key)?;
4850
//!
4951
//! let resp = client.request(request).await?;

Diff for: src/server_resolver.rs

+33-34
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::task::{self, Poll};
1010
use anyhow::{format_err, Context, Error};
1111
use futures::FutureExt;
1212
use futures_util::stream::StreamExt;
13+
use hickory_resolver::error::ResolveErrorKind;
1314
use http::header::{HOST, LOCATION};
1415
use http::{Request, Uri};
1516
use hyper::client::connect::Connect;
@@ -20,7 +21,6 @@ use log::{debug, trace, warn};
2021
use serde::{Deserialize, Serialize};
2122
use tokio::net::TcpStream;
2223
use tokio_rustls::rustls::ClientConfig;
23-
use hickory_resolver::error::ResolveErrorKind;
2424
use url::Url;
2525

2626
/// A resolved host for a Matrix server.
@@ -53,7 +53,7 @@ pub struct MatrixResolver {
5353

5454
impl MatrixResolver {
5555
/// Create a new [`MatrixResolver`]
56-
pub async fn new() -> Result<MatrixResolver, Error> {
56+
pub fn new() -> Result<MatrixResolver, Error> {
5757
let resolver = hickory_resolver::TokioAsyncResolver::tokio_from_system_conf()?;
5858

5959
Ok(MatrixResolver { resolver })
@@ -120,17 +120,6 @@ impl MatrixResolver {
120120
host.to_string()
121121
};
122122

123-
// If a literal IP or includes port then we shortcircuit.
124-
if host.parse::<IpAddr>().is_ok() || port.is_some() {
125-
return Ok(vec![Endpoint {
126-
host: host.to_string(),
127-
port: port.unwrap_or(8448),
128-
129-
host_header: authority.to_string(),
130-
tls_name: host.to_string(),
131-
}]);
132-
}
133-
134123
// If a literal IP or includes port then we short circuit.
135124
if host.parse::<IpAddr>().is_ok() || port.is_some() {
136125
debug!("Host is IP or port is set");
@@ -262,10 +251,13 @@ where
262251
C: Connect + Clone + Sync + Send + 'static,
263252
{
264253
debug!("URI: {:?}", req.uri());
265-
if req.uri().scheme_str() != Some("matrix") {
266-
debug!("Got scheme: {:?}", req.uri().scheme_str());
267-
return Ok(req);
268-
}
254+
let matrix_url_scheme: &str = match req.uri().scheme_str() {
255+
Some(scheme @ ("matrix" | "matrix-federation")) => scheme,
256+
_ => {
257+
debug!("Got scheme: {:?}", req.uri().scheme_str());
258+
return Ok(req);
259+
}
260+
};
269261

270262
let host = req.uri().host().context("missing host")?;
271263
let port = req.uri().port();
@@ -280,7 +272,9 @@ where
280272
debug!("Found well-known: {}", &w.server);
281273

282274
let a = http::uri::Authority::from_str(&w.server)?;
283-
let mut builder = Uri::builder().scheme("matrix").authority(a);
275+
// When building the new URL, use whatever scheme that was used in the
276+
// original request.
277+
let mut builder = Uri::builder().scheme(matrix_url_scheme).authority(a);
284278
if let Some(p) = req.uri().path_and_query() {
285279
builder = builder.path_and_query(p.clone());
286280
}
@@ -309,7 +303,7 @@ pub struct WellKnownServer {
309303
}
310304

311305
/// A connector that can be used with a [`hyper::Client`] that correctly
312-
/// resolves and connects to `matrix://` URIs.
306+
/// resolves and connects to `matrix://` and `matrix-federation://` URIs.
313307
#[derive(Debug, Clone)]
314308
pub struct MatrixConnector {
315309
resolver: MatrixResolver,
@@ -331,8 +325,8 @@ impl MatrixConnector {
331325
}
332326

333327
/// Create new [`MatrixConnector`] with a default [`MatrixResolver`].
334-
pub async fn with_default_resolver() -> Result<MatrixConnector, Error> {
335-
let resolver = MatrixResolver::new().await?;
328+
pub fn with_default_resolver() -> Result<MatrixConnector, Error> {
329+
let resolver = MatrixResolver::new()?;
336330

337331
Ok(MatrixConnector::with_resolver(resolver))
338332
}
@@ -356,19 +350,24 @@ impl Service<Uri> for MatrixConnector {
356350
let client_config = self.client_config.clone();
357351

358352
async move {
359-
if dst.scheme_str() != Some("matrix") {
360-
let mut https = hyper_rustls::HttpsConnectorBuilder::new()
361-
.with_tls_config(client_config)
362-
.https_only()
363-
.enable_http1()
364-
.build();
365-
366-
let r = https.call(dst).await;
367-
368-
return match r {
369-
Ok(r) => Ok(r),
370-
Err(e) => Err(format_err!("{}", e)),
371-
};
353+
// Return-early and make a normal request if the URI scheme is not
354+
// `matrix://` or `matrix-federation://`.
355+
match dst.scheme_str() {
356+
Some("matrix" | "matrix-federation") => {}
357+
_ => {
358+
let mut https = hyper_rustls::HttpsConnectorBuilder::new()
359+
.with_tls_config(client_config)
360+
.https_only()
361+
.enable_http1()
362+
.build();
363+
364+
let r = https.call(dst).await;
365+
366+
return match r {
367+
Ok(r) => Ok(r),
368+
Err(e) => Err(format_err!("{}", e)),
369+
};
370+
}
372371
}
373372

374373
let endpoints = resolver

0 commit comments

Comments
 (0)