Skip to content

Commit a6bd439

Browse files
authored
Merge pull request #334 from umccr/feat/cache-http
feat: add HTTP caching
2 parents 403abc8 + 7ec8ad9 commit a6bd439

File tree

28 files changed

+458
-114
lines changed

28 files changed

+458
-114
lines changed

Cargo.lock

Lines changed: 279 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

htsget-axum/src/server/data.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ impl DataServer {
7171
}
7272

7373
impl From<DataServerConfig> for BindServer {
74-
/// Returns a data server with TLS enabled if the tls config is not None or without TLS enabled
74+
/// Returns a data server with TLS enabled if the http config is not None or without TLS enabled
7575
/// if it is None.
7676
fn from(config: DataServerConfig) -> Self {
7777
let addr = config.addr();
@@ -114,7 +114,7 @@ mod tests {
114114
use tokio::io::AsyncWriteExt;
115115

116116
use htsget_config::config::Config;
117-
use htsget_config::tls::TlsServerConfig;
117+
use htsget_config::http::TlsServerConfig;
118118
use htsget_config::types::Scheme;
119119
use htsget_test::http::cors::{test_cors_preflight_request_uri, test_cors_simple_request_uri};
120120
use htsget_test::http::{

htsget-axum/src/server/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use axum::extract::Request;
1717
use htsget_config::config::advanced::auth::AuthConfig;
1818
use htsget_config::config::advanced::cors::CorsConfig;
1919
use htsget_config::config::service_info::ServiceInfo;
20-
use htsget_config::tls::TlsServerConfig;
20+
use htsget_config::http::TlsServerConfig;
2121
use htsget_config::types::Scheme;
2222
use htsget_http::middleware::auth::Auth;
2323
use htsget_search::HtsGet;
@@ -245,7 +245,7 @@ impl Server {
245245

246246
tokio::spawn(async move {
247247
let Ok(stream) = tls_acceptor.accept(cnx).await else {
248-
error!("error during tls handshake connection from {}", addr);
248+
error!("error during http handshake connection from {}", addr);
249249
return;
250250
};
251251

htsget-axum/src/server/ticket.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use tower_http::trace::TraceLayer;
2222
use tracing::info;
2323

2424
impl From<TicketServerConfig> for BindServer {
25-
/// Returns a ticket server with TLS enabled if the tls config is not None or without TLS enabled
25+
/// Returns a ticket server with TLS enabled if the http config is not None or without TLS enabled
2626
/// if it is None.
2727
fn from(config: TicketServerConfig) -> Self {
2828
let addr = config.addr();

htsget-config/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ rustls = "0.23"
3737
rustls-pki-types = "1"
3838
chrono = { version = "0.4", features = ["now"], default-features = false }
3939
reqwest = { version = "0.12", features = ["rustls-tls"], default-features = false }
40+
http-cache-reqwest = "0.16"
41+
reqwest-middleware = "0.4"
4042
schemars = "1"
4143

4244
# Crypt4GH

htsget-config/README.md

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,13 @@ backend.path_style = true
253253

254254
To manually configure `Url` locations, set `backend.kind = "Url"`, specify any additional options from below under the `backend` table:
255255

256-
| Option | Description | Type | Default |
257-
|--------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------|
258-
| <span id="url">`url`</span> | The URL to fetch data from. | HTTP URL | `"https://127.0.0.1:8081/"` |
259-
| <span id="url">`response_url`</span> | The URL to return to the client for fetching tickets. | HTTP URL | `"https://127.0.0.1:8081/"` |
260-
| `forward_headers` | When constructing the URL tickets, copy HTTP headers received in the initial query. | Boolean | `true` |
261-
| `header_blacklist` | List of headers that should not be forwarded. | Array of headers | `[]` |
262-
| `tls` | Additionally enables client authentication, or sets non-native root certificates for TLS. See [server configuration](#server-configuration) for more details. | TOML table | TLS is always allowed, however the default performs no client authentication and uses native root certificates. |
256+
| Option | Description | Type | Default |
257+
|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------|-----------------------------------------------------------------------------------------------------------------|
258+
| <span id="url">`url`</span> | The URL to fetch data from. | HTTP URL | `"https://127.0.0.1:8081/"` |
259+
| <span id="url">`response_url`</span> | The URL to return to the client for fetching tickets. | HTTP URL | `"https://127.0.0.1:8081/"` |
260+
| `forward_headers` | When constructing the URL tickets, copy HTTP headers received in the initial query. | Boolean | `true` |
261+
| `header_blacklist` | List of headers that should not be forwarded. | Array of headers | `[]` |
262+
| `http` | Additionally enables client authentication, or sets non-native root certificates for TLS, or disables HTTP header caching. See [server configuration](#server-configuration) for more details. | TOML table | TLS is always allowed, however the default performs no client authentication and uses native root certificates. |
263263

264264
For example, the following forwards all headers to response tickets except `Host`, and constructs tickets using `https://example.com` instead of `http://localhost:8080`:
265265

@@ -275,6 +275,12 @@ backend.forward_headers = true
275275
backend.header_blacklist = ["Host"]
276276
```
277277

278+
> [!NOTE]
279+
> Calls to the url endpoint use HTTP caching from headers like cache-control and expires. Requests to the url endpoint
280+
> only involve the beginning of a file to obtain the header. These requests are cached in a temporary file called
281+
> `htsget_rs_client_cache` in the system temp directory. To disable this functionality set
282+
> `http.use_cache = false`.
283+
278284
Regex-based locations also support multiple locations:
279285

280286
```toml
@@ -340,7 +346,9 @@ substitution_string = "$0"
340346

341347
backend.kind = "Url"
342348
backend.url = "https://example.com"
343-
backend.tls.root_store = "root.crt"
349+
backend.http.root_store = "root.crt"
350+
## Disable HTTP caching.
351+
#backend.http.use_cache = false
344352
```
345353

346354
This project uses [rustls] for all TLS logic, and it does not depend on OpenSSL. The rustls library can be more
@@ -500,6 +508,11 @@ JWT is invalid or the client is not allowed to view the requested `<id>` path. P
500508
after authorizing the request, so invalid requests may return an unauthorized or forbidden response instead of a bad
501509
request if the user lacks authorization.
502510

511+
> [!NOTE]
512+
> Calls to both the authorization service and jwks endpoint correctly handle and support HTTP cache headers like
513+
> cache-control and expires. Requests are cached in a temporary file called `htsget_rs_client_cache` in the system
514+
> temp directory. To disable this functionality set `http.use_cache = false`.
515+
503516
[auth-json]: docs/schemas/auth.schema.json
504517
[auth-example]: docs/examples/auth.toml
505518

htsget-config/docs/examples/auth.toml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,14 @@ validate_issuer = ["https://www.example.com"]
2323
validate_subject = "htsget"
2424

2525
## Set client authentication
26-
#tls.key = "key.pem"
27-
#tls.cert = "cert.pem"
26+
#http.key = "key.pem"
27+
#http.cert = "cert.pem"
2828
#
2929
## Set root certificates
30-
#tls.root_store = "root.crt"
30+
#http.root_store = "root.crt"
31+
## Disable HTTP caching
32+
#http.use_cache = false
3133

32-
## It's also possible to set auth individually for the ticket and auth servers:
34+
## It's also possible to set auth individually for the ticket and data servers:
3335
#[ticket_server.auth]
3436
#[data_server.auth]

htsget-config/docs/examples/url_storage.toml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,11 @@ locations = "https://127.0.0.1:8081"
2424
#backend.forward_headers = true
2525
#
2626
## Set client authentication
27-
#backend.tls.key = "key.pem"
28-
#backend.tls.cert = "cert.pem"
27+
#backend.http.key = "key.pem"
28+
#backend.http.cert = "cert.pem"
2929
#
3030
## Set root certificates
31-
#backend.tls.root_store = "root.crt"
31+
#backend.http.root_store = "root.crt"
32+
#
33+
## Disable HTTP caching
34+
#backend.http.use_cache = false

htsget-config/src/config/advanced/auth/mod.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ use crate::config::advanced::{Bytes, HttpClient};
88
use crate::config::{deserialize_vec_from_str, serialize_array_display};
99
use crate::error::Error::{BuilderError, ParseError};
1010
use crate::error::{Error, Result};
11+
use crate::http::client::HttpClientConfig;
1112
use http::Uri;
13+
use reqwest_middleware::ClientWithMiddleware;
1214
pub use response::{AuthorizationRestrictions, AuthorizationRule, ReferenceNameRestriction};
1315
use serde::{Deserialize, Serialize};
1416
use std::path::PathBuf;
@@ -127,7 +129,7 @@ impl AuthConfig {
127129
}
128130

129131
/// Get the http client.
130-
pub fn http_client(&self) -> &reqwest::Client {
132+
pub fn http_client(&self) -> &ClientWithMiddleware {
131133
&self.http_client.0
132134
}
133135

@@ -152,7 +154,7 @@ pub struct AuthConfigBuilder {
152154
)]
153155
trusted_authorization_urls: Vec<Uri>,
154156
authorization_path: Option<String>,
155-
#[serde(rename = "tls", skip_serializing)]
157+
#[serde(rename = "http", alias = "tls", skip_serializing)]
156158
http_client: Option<HttpClient>,
157159
authentication_only: bool,
158160
#[cfg(feature = "experimental")]
@@ -259,7 +261,9 @@ impl AuthConfigBuilder {
259261
validate_subject: self.validate_subject,
260262
trusted_authorization_urls: self.trusted_authorization_urls,
261263
authorization_path: self.authorization_path,
262-
http_client: HttpClient::default(),
264+
http_client: self
265+
.http_client
266+
.unwrap_or(HttpClient::try_from(HttpClientConfig::default())?),
263267
authentication_only: self.authentication_only,
264268
#[cfg(feature = "experimental")]
265269
suppress_errors: self.suppress_errors,
@@ -302,7 +306,7 @@ impl TryFrom<AuthConfigBuilder> for AuthConfig {
302306
#[cfg(test)]
303307
mod tests {
304308
use super::*;
305-
use crate::tls::tests::with_test_certificates;
309+
use crate::http::tests::with_test_certificates;
306310

307311
#[test]
308312
fn auth_config() {

htsget-config/src/config/advanced/mod.rs

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@
33
44
use crate::error::Error::ParseError;
55
use crate::error::{Error, Result};
6-
use crate::tls::client::TlsClientConfig;
6+
use crate::http::client::HttpClientConfig;
7+
use http_cache_reqwest::{CACacheManager, Cache, CacheMode, HttpCache, HttpCacheOptions};
78
use reqwest::Client;
9+
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
810
use serde::{Deserialize, Serialize};
11+
use std::env::temp_dir;
912
use std::fs::File;
1013
use std::io::Read;
1114
use std::path::PathBuf;
@@ -29,29 +32,29 @@ pub enum FormattingStyle {
2932
}
3033

3134
/// A wrapper around a reqwest client to support creating from config fields.
32-
#[derive(Debug, Clone, Deserialize, Default)]
33-
#[serde(deny_unknown_fields, try_from = "TlsClientConfig")]
34-
pub struct HttpClient(Client);
35+
#[derive(Debug, Clone, Deserialize)]
36+
#[serde(deny_unknown_fields, try_from = "HttpClientConfig")]
37+
pub struct HttpClient(ClientWithMiddleware);
3538

3639
impl HttpClient {
3740
/// Create a new client.
38-
pub fn new(client: Client) -> Self {
41+
pub fn new(client: ClientWithMiddleware) -> Self {
3942
Self(client)
4043
}
4144

4245
/// Get the inner client value.
43-
pub fn into_inner(self) -> Client {
46+
pub fn into_inner(self) -> ClientWithMiddleware {
4447
self.0
4548
}
4649
}
4750

48-
impl TryFrom<TlsClientConfig> for HttpClient {
51+
impl TryFrom<HttpClientConfig> for HttpClient {
4952
type Error = Error;
5053

51-
fn try_from(config: TlsClientConfig) -> Result<Self> {
54+
fn try_from(config: HttpClientConfig) -> Result<Self> {
5255
let mut builder = Client::builder();
5356

54-
let (certs, identity) = config.into_inner();
57+
let (certs, identity, use_cache) = config.into_inner();
5558

5659
if let Some(certs) = certs {
5760
for cert in certs {
@@ -62,9 +65,24 @@ impl TryFrom<TlsClientConfig> for HttpClient {
6265
builder = builder.identity(identity);
6366
}
6467

65-
Ok(Self(builder.build().map_err(|err| {
66-
ParseError(format!("building http client: {err}"))
67-
})?))
68+
let client = builder
69+
.build()
70+
.map_err(|err| ParseError(format!("building http client: {err}")))?;
71+
72+
let client = if use_cache {
73+
let client_cache = temp_dir().join("htsget_rs_client_cache");
74+
ClientBuilder::new(client)
75+
.with(Cache(HttpCache {
76+
mode: CacheMode::Default,
77+
manager: CACacheManager::new(client_cache, false),
78+
options: HttpCacheOptions::default(),
79+
}))
80+
.build()
81+
} else {
82+
ClientBuilder::new(client).build()
83+
};
84+
85+
Ok(Self::new(client))
6886
}
6987
}
7088

0 commit comments

Comments
 (0)