Skip to content

Commit a4efcfe

Browse files
authored
Merge pull request #199 from dfinity/igor/caffeine-prerender
feat(NODE-1947): Add pre-renderer proxying
2 parents 60c3ef6 + 2ec8221 commit a4efcfe

8 files changed

Lines changed: 704 additions & 6 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ ic-custom-domains-base = "0.1"
5555
ic-http-certification = { version = "3.1.0", optional = true }
5656
ic-transport-types = "0.47"
5757
ic-http-gateway-protocol = { package = "ic-http-gateway-protocol", git = "https://github.com/dfinity/ic-http-gateway-protocol", tag = "v0.5.1" }
58+
isbot = "0.1"
5859
itertools = "0.14.0"
5960
lazy_static = "1.5.0"
6061
maxminddb = "0.27.0"
@@ -96,6 +97,7 @@ tracing-subscriber = { version = "0.3.18", features = [
9697
"json",
9798
] }
9899
url = "2.5.3"
100+
urlencoding = "2.1.3"
99101
# DO NOT upgrade, this breaks monorepo compatibility
100102
# Read https://github.com/uuid-rs/uuid/releases/tag/1.13.0
101103
uuid = { version = "=1.12.1", features = ["v7"] }

src/cli.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration};
33
use ::http::HeaderValue;
44
use clap::{Args, Parser};
55
use fqdn::FQDN;
6+
use http::Uri;
67
use humantime::parse_duration;
78
#[cfg(feature = "acme")]
89
use ic_bn_lib_common::types::acme::{AcmeUrl, Challenge, DnsBackend};
@@ -97,6 +98,9 @@ pub struct Cli {
9798
#[command(flatten, next_help_heading = "Shedding Latency")]
9899
pub shed_latency: ShedShardedCli<RequestType>,
99100

101+
#[command(flatten, next_help_heading = "Prerender")]
102+
pub prerender: Prerender,
103+
100104
#[cfg(all(target_os = "linux", feature = "sev-snp"))]
101105
#[command(flatten, next_help_heading = "SEV-SNP")]
102106
pub sev_snp: ic_bn_lib_common::types::utils::SevSnpCli,
@@ -562,6 +566,26 @@ pub struct RateLimit {
562566
pub rate_limit_bypass_token: Option<String>,
563567
}
564568

569+
#[derive(Args)]
570+
pub struct Prerender {
571+
/// Domains that are eligible for pre-prender.
572+
/// If no domains specified - pre-render is not active.
573+
#[clap(env, long)]
574+
pub prerender_domains: Vec<FQDN>,
575+
576+
/// URL of the server-side renderer
577+
#[clap(env, long)]
578+
pub prerender_url: Option<Uri>,
579+
580+
/// Secret to authenticate with a pre-renderer
581+
#[clap(env, long)]
582+
pub prerender_secret: Option<HeaderValue>,
583+
584+
/// Timeout for executing pre-render request
585+
#[clap(env, long, default_value = "1m", value_parser = parse_duration)]
586+
pub prerender_timeout: Duration,
587+
}
588+
565589
#[cfg(test)]
566590
mod test {
567591
use super::*;

src/routing/middleware/is_bot.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::sync::Arc;
2+
3+
use axum::{
4+
extract::{Request, State},
5+
middleware::Next,
6+
response::Response,
7+
};
8+
use http::header::USER_AGENT;
9+
use isbot::Bots;
10+
11+
#[derive(Clone, Debug)]
12+
pub struct IsBot(pub bool);
13+
14+
pub struct IsBotState {
15+
bots: Bots,
16+
}
17+
18+
impl Default for IsBotState {
19+
fn default() -> Self {
20+
let mut bots = Bots::default();
21+
bots.append(&["GoogleOther"]);
22+
23+
Self { bots }
24+
}
25+
}
26+
27+
impl IsBotState {
28+
pub fn is_bot(&self, ua: &str) -> bool {
29+
self.bots.is_bot(ua)
30+
}
31+
}
32+
33+
pub async fn middleware(
34+
State(state): State<Arc<IsBotState>>,
35+
mut request: Request,
36+
next: Next,
37+
) -> Response {
38+
let ua = request
39+
.headers()
40+
.get(USER_AGENT)
41+
.and_then(|x| x.to_str().ok());
42+
43+
let is_bot = IsBot(ua.is_some_and(|x| state.is_bot(x)));
44+
request.extensions_mut().insert(is_bot);
45+
46+
next.run(request).await
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use super::*;
52+
53+
#[test]
54+
fn test_is_bot() {
55+
let state = IsBotState::default();
56+
57+
let bots = &[
58+
"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
59+
"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/W.X.Y.Z Safari/537.36",
60+
"Googlebot-Image/1.0",
61+
"Googlebot-Video/1.0",
62+
"Mozilla/5.0 (X11; Linux x86_64; Storebot-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Safari/537.36",
63+
"Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012; Storebot-Google/1.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36",
64+
"Mozilla/5.0 (compatible; Google-InspectionTool/1.0;)",
65+
"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; Google-InspectionTool/1.0;)",
66+
"Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/W.X.Y.Z Mobile Safari/537.36 (compatible; GoogleOther)",
67+
"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.3; +https://openai.com/gptbot)",
68+
"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot",
69+
"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm) Chrome/116.0.1938.76 Safari/537.36",
70+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36; compatible; OAI-SearchBot/1.3; +https://openai.com/searchbot",
71+
"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; ClaudeBot/1.0; +claudebot@anthropic.com)",
72+
];
73+
74+
for ua in bots {
75+
assert!(state.is_bot(ua))
76+
}
77+
78+
let browsers = &[
79+
"Mozilla/5.0 (Linux; Android 15; SM-S931B Build/AP3A.240905.015.A2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/127.0.6533.103 Mobile Safari/537.36",
80+
"Mozilla/5.0 (Linux; Android 15; SM-S931U Build/AP3A.240905.015.A2; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/132.0.6834.163 Mobile Safari/537.36",
81+
"Mozilla/5.0 (Android 15; Mobile; SM-G556B/DS; rv:130.0) Gecko/130.0 Firefox/130.0",
82+
"Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko",
83+
];
84+
85+
for ua in browsers {
86+
assert!(!state.is_bot(ua))
87+
}
88+
}
89+
}

src/routing/middleware/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ pub mod cors;
44
pub mod denylist;
55
pub mod geoip;
66
pub mod headers;
7+
pub mod is_bot;
78
pub mod preprocess;
9+
pub mod prerender;
810
pub mod request_id;
911
pub mod validate;

0 commit comments

Comments
 (0)