mod utils;
use {
::axum::{http::header::*, routing::*, *},
kutil::http::*,
moka::future::Cache,
std::time::*,
tokio::{net::*, *},
tower_http::trace::*,
tower_http_cache_plus::{
cache::{axum::*, implementation::moka::*, *},
*,
},
};
const CACHE_SIZE: u64 = 1024 * 1024;
const CACHE_DURATION: Duration = Duration::from_secs(10);
const MAX_BODY_SIZE: usize = 200;
const ENGLISH: Language = Language::new_fostered(&["en"]);
const ENGLISH_USA: Language = Language::new_fostered(&["en", "us"]);
const CHINESE: Language = Language::new_fostered(&["zh"]);
const CHINESE_TRADITIONAL: Language = Language::new_fostered(&["zh", "tw"]);
const CHINESE_SIMPLIFIED: Language = Language::new_fostered(&["zh", "cn"]);
const COMPRESSED_MEDIA_TYPES: &[MediaType] = &[
MediaType::new_fostered("image", "png"),
MediaType::new_fostered("image", "jpeg"),
MediaType::new_fostered("audio", "mpeg"),
MediaType::new_fostered("video", "mpeg"),
];
#[main]
async fn main() {
utils::init_tracing();
let cache = Cache::<CommonCacheKey, _, _>::builder()
.name("http")
.for_http_response()
.max_capacity(CACHE_SIZE)
.time_to_live(CACHE_DURATION)
.eviction_listener(|key, _value, cause| {
tracing::debug!("evict ({:?}): {}", cause, key);
})
.build();
let cache = MokaCacheImplementation::new(cache);
static LANGUAGES: &[Language] = &[
CHINESE_TRADITIONAL,
CHINESE_SIMPLIFIED,
CHINESE,
ENGLISH_USA,
ENGLISH,
];
let router = Router::default()
.route("/", get(("Hello, world!\n",)))
.route(
"/toobig",
get(("This response is too big to cache\n".repeat(10),)),
)
.route(
"/clientcache",
get((
[("Last-Modified", "Wed, 21 Oct 2015 07:28:00 GMT")],
"This response might be cached by the client\n",
)),
)
.route(
"/clientcache2",
get((
[("ETag", r#""stuff""#)],
"This response might also be cached by the client\n",
)),
)
.route(
"/nevercache",
get(([("XX-Cache", "false")], "This response is never cached\n")),
)
.route(
"/nevercache2",
get(("This response is also never cached\n",)),
)
.route(
"/neverencode",
get(([("XX-Encode", "false")], "This response is never encoded\n")),
)
.route(
"/neverencode2",
get(("This response is also never encoded\n",)),
)
.route(
"/quickie",
get((
[("XX-Cache-Duration", "1 ms")],
"This response has a custom cache duration of 1 ms\n",
)),
)
.route(
"/quickie2",
get(("This response also has a custom cache duration of 1 ms\n",)),
)
.route(
"/png",
get((
[
("Content-Type", "image/png"),
("Content-Length", utils::TINY_PNG_SIZE),
],
utils::TINY_PNG,
)),
)
.route("/put", put(("You put something here, thanks!",)))
.route(
"/language",
get(async |headers: HeaderMap| {
let language = headers.accept_language().best_or_first(LANGUAGES).clone();
if (language == ENGLISH_USA) || (language == ENGLISH) {
([("Content-Language", "en")], "This is in English\n")
} else if (language == CHINESE_TRADITIONAL) || (language == CHINESE) {
([("Content-Language", "zh-TW")], "這是中文的\n")
} else if language == CHINESE_SIMPLIFIED {
([("Content-Language", "zh-CN")], "这是中文的\n")
} else {
([("Content-Language", "nope")], "This cannot be seen\n")
}
}),
)
.route(
"/reset",
post(reset_cache_handler::<MokaCacheImplementation<_>, _>),
)
.with_state(cache.clone()) .layer(
CachingLayer::default()
.cache(cache.clone())
.max_cacheable_body_size(MAX_BODY_SIZE)
.cache_key(|context| {
if context.request.uri().path() == "/language" {
let language = context
.request
.headers()
.accept_language()
.best_or_first(LANGUAGES)
.clone();
context.cache_key.languages = Some([language].into());
}
})
.cache_duration(|context| {
if context.uri.path() == "/quickie2" {
Some(Duration::from_millis(1))
} else {
None
}
})
.cacheable_by_request(|context| {
context.uri.path() != "/nevercache2"
})
.encodable_by_request(|context| {
context.uri.path() != "/neverencode2"
})
.encodable_by_response(|context| {
match context.headers.content_type() {
Some(content_type) => !COMPRESSED_MEDIA_TYPES.contains(&content_type),
None => true,
}
})
.keep_identity_encoding(false),
)
.layer(TraceLayer::new_for_http());
let listener = TcpListener::bind("[::]:8080")
.await
.expect("TcpListener::bind");
tracing::info!("bound to: {:?}", listener.local_addr());
serve(listener, router).await.expect("axum::serve");
}