Skip to content

Commit

Permalink
add support for variable cache expiration jitter
Browse files Browse the repository at this point in the history
  • Loading branch information
walterbm committed Mar 31, 2022
1 parent 3f40c56 commit 22bd2b0
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 24 deletions.
15 changes: 3 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ httpdate = "1.0.2"
log = "0.4.16"
magick_rust = { git = "https://github.com/nlfiedler/magick-rust", branch = "master", features = ["disable-hdri"] }
openssl = "0.10.38"
rand = "0.8.5"
serde = { version = "1.0.136", features = ["derive"] }
url = "2.2.2"

Expand Down
19 changes: 10 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,16 @@ curl localhost:8080/resize?source=image.jpeg&height=100&width=100&quality=85

The Rusty Resizer accepts all its configuration options through ENV variables:

| ENV var | description | default |
| ------------------------ | --------------------------------------------------------------------------------- | ---------- |
| `ALLOWED_HOSTS` | **required** list of image hosts that will be accepted for resizing | |
| `DEFAULT_QUALITY` | default compression quality for image formats that accept compression (e.g. jpeg) | 85 |
| `CACHE_EXPIRATION_HOURS` | used to populate the Cache-Control max-age headers in the final resized response | 2880 hours |
| `STATSD_HOST` | StatsD host to accept metric data (metrics are only emitted when this is present) | |
| `WORKERS` | number of HTTP workers | 4 |
| `PORT` | TCP port to bind the server | 8080 |
| `ENV` | environment the server is running in | local |
| ENV var | description | default |
| ------------------------ | -------------------------------------------------------------------------------------- | ---------- |
| `ALLOWED_HOSTS` | **required** list of image hosts that will be accepted for resizing | |
| `DEFAULT_QUALITY` | default compression quality for image formats that accept compression (e.g. jpeg) | 85 |
| `CACHE_EXPIRATION_HOURS` | use to populate `Cache-Control` & `Expires` headers in the final resized response | 2880 hours |
| `CACHE_JITTER_SECONDS` | help give `Cache-Control` & `Expires` headers some variance to avoid a thundering herd | 0 |
| `STATSD_HOST` | StatsD host to accept metric data (metrics are only emitted when this is present) | |
| `WORKERS` | number of HTTP workers | 4 |
| `PORT` | TCP port to bind the server | 8080 |
| `ENV` | environment the server is running in | local |

## Security

Expand Down
14 changes: 12 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use futures_util::future::FutureExt;
use http::Client;
use img::{Image, ImageError};
use magick_rust::magick_wand_genesis;
use rand::Rng;
use serde::Deserialize;
use std::net::TcpListener;
use std::sync::{Arc, Once};
Expand All @@ -26,6 +27,7 @@ pub struct Configuration {
pub env: String,
pub allowed_hosts: Vec<Host>,
pub cache_expiration: u64,
pub cache_jitter: u64,
pub default_quality: u8,
}

Expand All @@ -36,17 +38,19 @@ impl Configuration {
/// # use url::Host;
/// # use rusty_resizer::Configuration;
///
/// let config = Configuration::new(String::from("test"), String::from(" x.com, y.com,z.com"), 2880, 50);
/// let config = Configuration::new(String::from("test"), String::from(" x.com, y.com,z.com"), 2880, 60, 50);
///
/// assert_eq!("test", config.env);
/// assert_eq!(vec![Host::parse("x.com").unwrap(), Host::parse("y.com").unwrap(), Host::parse("z.com").unwrap()], config.allowed_hosts);
/// assert_eq!(2880, config.cache_expiration);
/// assert_eq!(60, config.cache_jitter);
/// assert_eq!(50, config.default_quality);
/// ```
pub fn new(
env: String,
allowed_hosts: String,
cache_expiration: u64,
cache_jitter: u64,
default_quality: u8,
) -> Self {
let allowed_hosts = allowed_hosts
Expand All @@ -62,6 +66,7 @@ impl Configuration {
env,
allowed_hosts,
cache_expiration,
cache_jitter,
default_quality,
}
}
Expand Down Expand Up @@ -109,7 +114,12 @@ async fn resize(
let content_type = image.mime_type()?;

let now = SystemTime::now();
let expire_time_in_seconds = configuration.cache_expiration * 60 * 60;
let jitter = if configuration.cache_jitter > 0 {
rand::thread_rng().gen_range(0..configuration.cache_jitter)
} else {
0
};
let expire_time_in_seconds = configuration.cache_expiration * 60 * 60 + jitter;

let response = HttpResponse::Ok()
.content_type(content_type)
Expand Down
13 changes: 12 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use std::net::UdpSocket;
const DEFAULT_WORKERS: usize = 4;
const DEFAULT_QUALITY: u8 = 85;
const DEFAULT_CACHE_EXPIRATION_HOURS: u64 = 2880;
const DEFAULT_CACHE_JITTER_SECONDS: u64 = 0;

#[actix_web::main]
async fn main() -> std::io::Result<()> {
Expand All @@ -26,12 +27,22 @@ async fn main() -> std::io::Result<()> {
.ok()
.and_then(|ce| ce.parse::<u64>().ok())
.unwrap_or(DEFAULT_CACHE_EXPIRATION_HOURS);
let cache_jitter = env::var("CACHE_JITTER_SECONDS")
.ok()
.and_then(|ce| ce.parse::<u64>().ok())
.unwrap_or(DEFAULT_CACHE_JITTER_SECONDS);
let statsd_host = env::var("STATSD_HOST").ok();
// App Configuration
let address = format!("0.0.0.0:{}", port);
let listener =
TcpListener::bind(address).unwrap_or_else(|_| panic!("Failed to bind to port {}!", port));
let configuration = Configuration::new(env, allowed_hosts, cache_expiration, default_quality);
let configuration = Configuration::new(
env,
allowed_hosts,
cache_expiration,
cache_jitter,
default_quality,
);
// Logging
use env_logger;
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
Expand Down
1 change: 1 addition & 0 deletions tests/support/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub fn spawn_app() -> String {
env: String::from("test"),
allowed_hosts: vec![Host::parse("raw.githubusercontent.com").unwrap()],
cache_expiration: 1,
cache_jitter: 0,
default_quality: 85,
};
let statsd = StatsdClient::from_sink("rusty.resizer", NopMetricSink);
Expand Down

0 comments on commit 22bd2b0

Please sign in to comment.