Skip to content

Commit b17d7d0

Browse files
authored
Merge pull request #141 from dfinity/igor/add-shutdown
feat(BOUN-1483): Add generic alternate error page, add shutdown handler
2 parents dd99a85 + 7a32fa7 commit b17d7d0

File tree

10 files changed

+336
-301
lines changed

10 files changed

+336
-301
lines changed

Cargo.lock

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

src/api.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use ic_bn_lib::{
1414
http::middleware::waf::{self, WafLayer},
1515
types::Healthy,
1616
};
17+
use tokio_util::sync::CancellationToken;
1718
use tracing::Level;
1819
use tracing_core::LevelFilter;
1920
use tracing_subscriber::{Registry, reload::Handle};
@@ -24,6 +25,7 @@ use crate::{cli::Cli, routing::middleware::cors};
2425
pub struct ApiState {
2526
token: Option<String>,
2627
log_handle: Arc<Handle<LevelFilter, Registry>>,
28+
shutdown_token: CancellationToken,
2729
}
2830

2931
pub async fn auth_middleware(
@@ -72,6 +74,11 @@ pub async fn log_handler(
7274
"Ok\n".into_response()
7375
}
7476

77+
pub async fn shutdown_handler(State(state): State<Arc<ApiState>>) -> Response {
78+
state.shutdown_token.cancel();
79+
"Shutting down gracefully\n".into_response()
80+
}
81+
7582
/// Handles health requests
7683
pub async fn health_handler(State(state): State<Arc<dyn Healthy>>) -> impl IntoResponse {
7784
if state.healthy() {
@@ -85,6 +92,7 @@ pub fn setup_api_router(
8592
cli: &Cli,
8693
log_handle: Handle<LevelFilter, Registry>,
8794
healthy: Arc<dyn Healthy>,
95+
shutdown_token: CancellationToken,
8896
waf_layer: Option<WafLayer>,
8997
) -> Result<Router, Error> {
9098
let cors_layer = cors::layer(cli.cors.cors_max_age, cli.cors.cors_allow_origin.clone())
@@ -93,12 +101,14 @@ pub fn setup_api_router(
93101
let state = Arc::new(ApiState::new(
94102
cli.api.api_token.clone(),
95103
Arc::new(log_handle),
104+
shutdown_token,
96105
));
97106

98107
let auth = from_fn_with_state(state.clone(), auth_middleware);
99108

100109
let mut router = Router::new()
101110
.route("/log/{log_level}", get(log_handler).layer(auth.clone()))
111+
.route("/shutdown", get(shutdown_handler).layer(auth.clone()))
102112
.route("/health", get(health_handler).with_state(healthy));
103113

104114
// Enable WAF if requested
@@ -127,7 +137,8 @@ mod test {
127137

128138
let (_, reload_handle) = reload::Layer::new(LevelFilter::WARN);
129139
let healthy = Arc::new(HealthManager::default());
130-
let router = setup_api_router(&cli, reload_handle, healthy, None).unwrap();
140+
let router =
141+
setup_api_router(&cli, reload_handle, healthy, CancellationToken::new(), None).unwrap();
131142

132143
// Bad header
133144
let mut req = Request::builder()

src/core.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub async fn main(
8282
.map_err(|_| anyhow!("unable to install Rustls crypto provider"))?;
8383

8484
// Prepare some general stuff
85-
let token = CancellationToken::new();
85+
let shutdown_token = CancellationToken::new();
8686

8787
let health_manager = Arc::new(HealthManager::default());
8888
let mut custom_domain_providers: Vec<Arc<dyn ProvidesCustomDomains>> = vec![];
@@ -167,7 +167,7 @@ pub async fn main(
167167

168168
// Handle SIGTERM/SIGHUP and Ctrl+C
169169
// Cancelling a token cancels all of its clones too
170-
let handler_token = token.clone();
170+
let handler_token = shutdown_token.clone();
171171
ctrlc::set_handler(move || handler_token.cancel())?;
172172

173173
// HTTP server metrics
@@ -239,6 +239,7 @@ pub async fn main(
239239
http_client_hyper,
240240
route_provider.clone(),
241241
&registry,
242+
shutdown_token.clone(),
242243
vector.clone(),
243244
waf_layer,
244245
#[cfg(feature = "clickhouse")]
@@ -317,7 +318,7 @@ pub async fn main(
317318
tasks.start();
318319

319320
warn!("Service is running, waiting for the shutdown signal");
320-
token.cancelled().await;
321+
shutdown_token.cancelled().await;
321322

322323
warn!("Shutdown signal received, cleaning up");
323324
tasks.stop().await;

src/routing/error_cause.rs

Lines changed: 77 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::error::Error as StdError;
1+
use std::{cell::RefCell, error::Error as StdError};
22

33
use crate::routing::RequestType;
44
use axum::response::{IntoResponse, Response};
@@ -12,7 +12,6 @@ use ic_bn_lib::{
1212
};
1313
use ic_http_gateway::HttpGatewayError;
1414
use ic_transport_types::RejectCode;
15-
use std::sync::Arc;
1615
use strum::{Display, IntoStaticStr};
1716
use tokio::task_local;
1817

@@ -21,11 +20,12 @@ use super::ic::BNResponseMetadata;
2120
#[derive(Default, Clone)]
2221
pub struct ErrorContext {
2322
pub request_type: RequestType,
23+
pub authority: Option<FQDN>,
2424
pub alternate_error_domain: Option<FQDN>,
2525
}
2626

2727
task_local! {
28-
pub static ERROR_CONTEXT: Arc<ErrorContext>;
28+
pub static ERROR_CONTEXT: RefCell<ErrorContext>;
2929
}
3030

3131
const ERROR_PAGE_TEMPLATE: &str = include_str!("error_pages/template.html");
@@ -36,6 +36,8 @@ const RETRY_LOGIC: &str = include_str!("error_pages/components/retry_logic.js");
3636
const APPEAL_SECTION: &str = include_str!("error_pages/components/appeal_section.html");
3737

3838
const ALTERNATE_ERROR: &str = include_str!("error_pages/caffeine_error.html");
39+
const ALTERNATE_ERROR_UNKNOWN_DOMAIN: &str =
40+
include_str!("error_pages/caffeine_error_unknown_domain.html");
3941

4042
const CANISTER_ERROR_SVG: &str = include_str!("error_pages/assets/canister-error.svg");
4143
const CANISTER_WARNING_SVG: &str = include_str!("error_pages/assets/canister-warning.svg");
@@ -518,29 +520,37 @@ impl IntoResponse for ErrorClientFacing {
518520
fn into_response(self) -> Response {
519521
let context = ERROR_CONTEXT
520522
.try_with(|ctx| ctx.clone())
521-
.unwrap_or_else(|_| Arc::new(ErrorContext::default()));
523+
.unwrap_or_default();
524+
let context = context.borrow();
522525

523526
let error_data = self.data();
524527

525528
// Return an HTML error page if it was an HTTP request
526529
let body = match context.request_type {
527-
RequestType::Http => match self {
528-
Self::UnknownDomain(domain) => {
529-
// Check if we have a configured alternate error domain and if the current domain is a subdomain of it
530-
if context.alternate_error_domain.as_ref().is_some_and(
531-
|alternate_error_domain| domain.is_subdomain_of(alternate_error_domain),
532-
) {
533-
ALTERNATE_ERROR.to_string()
534-
} else {
535-
error_data.html()
530+
RequestType::Http => {
531+
// Check if this is an alternate error domain
532+
// and produce alternate errors then
533+
if context
534+
.alternate_error_domain
535+
.as_ref()
536+
.zip(context.authority.as_ref())
537+
.map(|(alternate, authority)| authority.is_subdomain_of(alternate))
538+
== Some(true)
539+
{
540+
match self {
541+
Self::UnknownDomain(_) => ALTERNATE_ERROR_UNKNOWN_DOMAIN,
542+
_ => ALTERNATE_ERROR,
536543
}
544+
.to_string()
545+
} else {
546+
error_data.html()
537547
}
538-
_ => error_data.html(),
539-
},
548+
}
549+
540550
_ => format!("error: {}\ndetails:\n{}", self, error_data.description),
541551
};
542552

543-
// build the final response
553+
// Build the final response
544554
let mut resp = (error_data.status_code, body).into_response();
545555
if context.request_type == RequestType::Http {
546556
resp.headers_mut().insert(CONTENT_TYPE, CONTENT_TYPE_HTML);
@@ -619,13 +629,15 @@ impl ErrorData {
619629
#[cfg(test)]
620630
mod test {
621631
use super::*;
632+
use fqdn::fqdn;
622633
use http::HeaderMap;
634+
use http_body_util::BodyExt;
623635
use ic_bn_lib::{http::headers::X_IC_ERROR_CAUSE, hval, ic_agent::AgentError};
624636
use ic_transport_types::RejectResponse;
625637
use std::sync::Arc;
626638

627-
#[test]
628-
fn test_error_cause() {
639+
#[tokio::test]
640+
async fn test_error_cause() {
629641
// Mapping of Rustls errors
630642
let err = anyhow::Error::new(rustls::Error::NoCertificatesPresented);
631643
assert!(matches!(
@@ -743,6 +755,52 @@ mod test {
743755
assert!(matches!(
744756
ErrorCause::from(http_gw_error),
745757
ErrorCause::BackendError(_)
746-
))
758+
));
759+
760+
// Test alternate errors
761+
let context = RefCell::new(ErrorContext {
762+
alternate_error_domain: Some(fqdn!("caffeine.ai")),
763+
request_type: RequestType::Http,
764+
authority: Some(fqdn!("foobar.caffeine.ai")),
765+
});
766+
767+
let error = ErrorCause::UnknownDomain(fqdn!("foo"));
768+
let error: ErrorClientFacing = (&error).into();
769+
770+
ERROR_CONTEXT
771+
.scope(context.clone(), async move {
772+
let resp = error.into_response();
773+
let body = resp.into_body().collect().await.unwrap().to_bytes();
774+
assert_eq!(body, ALTERNATE_ERROR_UNKNOWN_DOMAIN.as_bytes());
775+
})
776+
.await;
777+
778+
let error = ErrorCause::CanisterError;
779+
let error: ErrorClientFacing = (&error).into();
780+
781+
ERROR_CONTEXT
782+
.scope(context, async move {
783+
let resp = error.into_response();
784+
let body = resp.into_body().collect().await.unwrap().to_bytes();
785+
assert_eq!(body, ALTERNATE_ERROR.as_bytes());
786+
})
787+
.await;
788+
789+
let context = RefCell::new(ErrorContext {
790+
alternate_error_domain: Some(fqdn!("caffeine.ai")),
791+
request_type: RequestType::Http,
792+
authority: Some(fqdn!("foobar.cocaine.ai")),
793+
});
794+
795+
let error = ErrorCause::UnknownDomain(fqdn!("foo"));
796+
let error: ErrorClientFacing = (&error).into();
797+
798+
ERROR_CONTEXT
799+
.scope(context.clone(), async move {
800+
let resp = error.into_response();
801+
let body = resp.into_body().collect().await.unwrap().to_bytes();
802+
assert_ne!(body, ALTERNATE_ERROR_UNKNOWN_DOMAIN.as_bytes());
803+
})
804+
.await;
747805
}
748806
}
Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<!DOCTYPE html>
22
<html lang="en">
3+
34
<head>
45
<meta charset="UTF-8">
56
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -14,29 +15,38 @@
1415
}
1516
</style>
1617
</head>
18+
1719
<body>
18-
<div id="sourceFilesEmpty" data-testid="draft-waiting-message" style="display: flex; flex-direction: column; align-items: center; justify-content: space-between; width: 100%; height: 100%;">
19-
<div style="display: flex; flex-direction: column; flex-grow: 1; align-items: center; padding: 20px; height: 100%; max-width: 477px; justify-content: center; align-content: center; font-size: 1.5rem; line-height: 1.25; font-weight: 400; text-align: center;">
20-
<p style="margin: 0 0 1.25rem 0; font-size: 1rem;; line-height: 1.75rem">Error 503</p>
21-
<h2 style="margin: 0 0 1rem 0; font-size: 2rem; line-height: 1.25; font-weight: 400;">Draft app expired</h2>
22-
<p style="margin: 0 0 1rem 0; font-size: 1.25rem; line-height: 1.75rem">Your app was in draft mode and became inactive. The system automatically cleaned up the temporary resources, resetting your app data</p>
23-
<p style="margin: 0; font-size: 1.3rem; line-height: 2rem">To continue working:</p>
24-
<p style="margin: 0 0 1rem 0; font-size: 1.25rem; line-height: 1.75rem">Ask Caffeine to get your app running again</p>
25-
<p style="margin: 0; font-size: 1.3rem; line-height: 2rem">To keep your app in the future:</p>
26-
<p style="margin: 0 0 1rem 0; font-size: 1.25rem; line-height: 1.75rem">Push your draft app "live" for permanent storage</p>
20+
<div id="sourceFilesEmpty" data-testid="draft-waiting-message"
21+
style="display: flex; flex-direction: column; align-items: center; justify-content: space-between; width: 100%; height: 100%;">
22+
<div
23+
style="display: flex; flex-direction: column; flex-grow: 1; align-items: center; padding: 20px; height: 100%; max-width: 477px; justify-content: center; align-content: center; font-size: 1.5rem; line-height: 1.25; font-weight: 400; text-align: center;">
24+
<h2 style="margin: 0 0 1rem 0; font-size: 2rem; line-height: 1.25; font-weight: 400;">Application error
25+
occured</h2>
26+
<p style="margin: 0 0 1rem 0; font-size: 1.25rem; line-height: 1.75rem">An error occured during execution of
27+
your request, please refresh the page to try again</p>
2728
</div>
2829
<div style="padding: 0 0 4rem 0; display: flex; justify-content: center; align-items: center; margin-top: 3rem">
2930
<svg width="100" viewBox="0 0 898 156" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
30-
<path d="M0 97.8379C0 63.6156 27.9237 40.1009 66.555 40.1009C99.5175 40.1009 120.933 58.3668 126.811 76.6327L97.8379 89.0199C94.0588 77.4725 82.7213 67.6047 66.345 67.6047C47.0294 67.6047 32.7526 78.9422 32.7526 97.8379C32.7526 116.524 47.0294 128.071 66.345 128.071C82.7213 128.071 94.0588 118.203 97.8379 106.446L126.811 119.253C120.723 137.309 99.5175 155.365 66.555 155.365C27.9237 155.365 0 131.85 0 97.8379Z"/>
31-
<path d="M183.051 155.365C155.758 155.365 136.232 142.978 136.232 121.143C136.232 99.5175 155.338 88.8099 181.372 86.2905L225.462 82.3014V81.6716C225.462 73.2735 218.323 66.555 200.477 66.555C185.361 66.555 173.813 72.8536 170.454 81.4616L140.221 73.2735C147.15 53.328 171.504 40.1009 201.947 40.1009C237.219 40.1009 258.004 54.1678 258.004 81.8815V102.037V153.053H228.611V140.878C218.533 149.696 202.577 155.365 183.051 155.365ZM225.462 110.645V104.766L186.201 108.755C174.653 109.805 168.775 112.535 168.775 119.883C168.775 127.021 176.123 130.8 189.14 130.8C206.356 130.8 225.462 123.452 225.462 110.645Z"/>
32-
<path d="M275.824 40.7308C275.824 13.227 295.139 0 327.892 0C338.18 0 348.887 1.46966 355.606 3.9891L351.197 30.2332C344.268 28.3436 337.76 27.2938 329.992 27.2938C315.715 27.2938 308.996 31.4929 308.996 41.3607V42.4104H349.727V70.1242H308.996V153.055H275.824V40.7308Z"/>
33-
<path d="M363.782 40.7308C363.782 13.227 383.098 0 415.851 0C426.138 0 436.846 1.46966 443.564 3.9891L439.155 30.2332C432.227 28.3436 425.718 27.2938 417.95 27.2938C403.673 27.2938 396.955 31.4929 396.955 41.3607V42.4104H437.686V70.1242H396.955V153.055H363.782V70.1242H338.798V42.4104H363.782V40.7308Z"/>
34-
<path d="M505.107 128.701C519.803 128.701 531.561 121.982 536.39 111.275L565.993 121.982C558.015 141.508 534.29 155.365 505.317 155.365C466.056 155.365 438.552 132.06 438.552 97.628C438.552 64.8754 466.056 40.1009 505.107 40.1009C544.788 40.1009 567.883 65.7152 567.883 97.2081V106.446H471.304C475.084 120.513 487.891 128.701 505.107 128.701ZM504.057 66.1351C489.36 66.1351 476.763 72.8535 472.144 85.8706H534.08C532.611 76.6327 521.693 66.1351 504.057 66.1351Z"/>
35-
<path d="M582.412 31.073V2.30947H616.634V31.073H582.412ZM616.214 42.4104V153.055H582.622V42.4104H616.214Z"/>
36-
<path d="M634.955 153.055V42.4104H668.548V55.8474C677.156 46.8194 691.013 40.1009 708.859 40.1009C737.622 40.1009 757.148 59.4166 757.148 90.0697V153.055H723.555V99.9374C723.555 81.2517 714.527 69.9142 697.311 69.9142C679.045 69.9142 668.548 81.0417 668.548 100.147V153.055H634.955Z"/>
37-
<path d="M834.338 128.701C849.034 128.701 860.792 121.982 865.62 111.275L895.224 121.982C887.246 141.508 863.521 155.365 834.547 155.365C795.286 155.365 767.783 132.06 767.783 97.628C767.783 64.8754 795.286 40.1009 834.338 40.1009C874.019 40.1009 897.113 65.7152 897.113 97.2081V106.446H800.535C804.314 120.513 817.121 128.701 834.338 128.701ZM833.288 66.1351C818.591 66.1351 805.994 72.8535 801.375 85.8706H863.311C861.841 76.6327 850.924 66.1351 833.288 66.1351Z"/>
31+
<path
32+
d="M0 97.8379C0 63.6156 27.9237 40.1009 66.555 40.1009C99.5175 40.1009 120.933 58.3668 126.811 76.6327L97.8379 89.0199C94.0588 77.4725 82.7213 67.6047 66.345 67.6047C47.0294 67.6047 32.7526 78.9422 32.7526 97.8379C32.7526 116.524 47.0294 128.071 66.345 128.071C82.7213 128.071 94.0588 118.203 97.8379 106.446L126.811 119.253C120.723 137.309 99.5175 155.365 66.555 155.365C27.9237 155.365 0 131.85 0 97.8379Z" />
33+
<path
34+
d="M183.051 155.365C155.758 155.365 136.232 142.978 136.232 121.143C136.232 99.5175 155.338 88.8099 181.372 86.2905L225.462 82.3014V81.6716C225.462 73.2735 218.323 66.555 200.477 66.555C185.361 66.555 173.813 72.8536 170.454 81.4616L140.221 73.2735C147.15 53.328 171.504 40.1009 201.947 40.1009C237.219 40.1009 258.004 54.1678 258.004 81.8815V102.037V153.053H228.611V140.878C218.533 149.696 202.577 155.365 183.051 155.365ZM225.462 110.645V104.766L186.201 108.755C174.653 109.805 168.775 112.535 168.775 119.883C168.775 127.021 176.123 130.8 189.14 130.8C206.356 130.8 225.462 123.452 225.462 110.645Z" />
35+
<path
36+
d="M275.824 40.7308C275.824 13.227 295.139 0 327.892 0C338.18 0 348.887 1.46966 355.606 3.9891L351.197 30.2332C344.268 28.3436 337.76 27.2938 329.992 27.2938C315.715 27.2938 308.996 31.4929 308.996 41.3607V42.4104H349.727V70.1242H308.996V153.055H275.824V40.7308Z" />
37+
<path
38+
d="M363.782 40.7308C363.782 13.227 383.098 0 415.851 0C426.138 0 436.846 1.46966 443.564 3.9891L439.155 30.2332C432.227 28.3436 425.718 27.2938 417.95 27.2938C403.673 27.2938 396.955 31.4929 396.955 41.3607V42.4104H437.686V70.1242H396.955V153.055H363.782V70.1242H338.798V42.4104H363.782V40.7308Z" />
39+
<path
40+
d="M505.107 128.701C519.803 128.701 531.561 121.982 536.39 111.275L565.993 121.982C558.015 141.508 534.29 155.365 505.317 155.365C466.056 155.365 438.552 132.06 438.552 97.628C438.552 64.8754 466.056 40.1009 505.107 40.1009C544.788 40.1009 567.883 65.7152 567.883 97.2081V106.446H471.304C475.084 120.513 487.891 128.701 505.107 128.701ZM504.057 66.1351C489.36 66.1351 476.763 72.8535 472.144 85.8706H534.08C532.611 76.6327 521.693 66.1351 504.057 66.1351Z" />
41+
<path
42+
d="M582.412 31.073V2.30947H616.634V31.073H582.412ZM616.214 42.4104V153.055H582.622V42.4104H616.214Z" />
43+
<path
44+
d="M634.955 153.055V42.4104H668.548V55.8474C677.156 46.8194 691.013 40.1009 708.859 40.1009C737.622 40.1009 757.148 59.4166 757.148 90.0697V153.055H723.555V99.9374C723.555 81.2517 714.527 69.9142 697.311 69.9142C679.045 69.9142 668.548 81.0417 668.548 100.147V153.055H634.955Z" />
45+
<path
46+
d="M834.338 128.701C849.034 128.701 860.792 121.982 865.62 111.275L895.224 121.982C887.246 141.508 863.521 155.365 834.547 155.365C795.286 155.365 767.783 132.06 767.783 97.628C767.783 64.8754 795.286 40.1009 834.338 40.1009C874.019 40.1009 897.113 65.7152 897.113 97.2081V106.446H800.535C804.314 120.513 817.121 128.701 834.338 128.701ZM833.288 66.1351C818.591 66.1351 805.994 72.8535 801.375 85.8706H863.311C861.841 76.6327 850.924 66.1351 833.288 66.1351Z" />
3847
</svg>
3948
</div>
4049
</div>
4150
</body>
42-
</html>
51+
52+
</html>

0 commit comments

Comments
 (0)