Skip to main content

relay_server/endpoints/
mod.rs

1//! Web server endpoints.
2//!
3//! This module contains implementations for all supported relay endpoints, as well as a generic
4//! `forward` endpoint that sends unknown requests to the upstream.
5
6// Axum's standard `ErrorResponse` is larger than Clippy's default threshold.
7#![allow(clippy::result_large_err)]
8
9pub(crate) mod common;
10
11mod attachments;
12mod autoscaling;
13mod batch_metrics;
14mod envelope;
15mod forward;
16mod health_check;
17mod integrations;
18mod minidump;
19mod monitor;
20mod nel;
21#[cfg(sentry)]
22mod playstation;
23mod project_configs;
24mod public_keys;
25mod register;
26mod security_report;
27mod statics;
28mod store;
29mod unreal;
30mod upload;
31
32use axum::extract::DefaultBodyLimit;
33use axum::routing::{Router, any, get, post};
34use relay_config::Config;
35
36use crate::middlewares;
37use crate::service::ServiceState;
38use crate::services::upload::UPLOAD_PATCH_PATH;
39
40/// Size limit for internal batch endpoints.
41const BATCH_JSON_BODY_LIMIT: usize = 50_000_000; // 50 MB
42
43/// All of Relay's routes.
44///
45/// This includes [`public_routes`] as well as [`internal_routes`].
46pub fn all_routes(config: &Config) -> Router<ServiceState> {
47    public_routes_raw(config).merge(internal_routes(config))
48}
49
50/// Relay's internal routes.
51///
52/// Routes which do not need to be exposed.
53#[rustfmt::skip]
54pub fn internal_routes(_: &Config) -> Router<ServiceState>{
55    Router::new()
56        .route("/api/relay/healthcheck/{kind}/", get(health_check::handle))
57        .route("/api/relay/autoscaling/", get(autoscaling::handle))
58        // Fallback route, but with a name, and just on `/api/relay/*`.
59        .route("/api/relay/{*not_found}", any(statics::not_found))
60}
61
62/// Relay's public routes.
63///
64/// Routes which are public API and must be exposed.
65pub fn public_routes(config: &Config) -> Router<ServiceState> {
66    // Exclude internal routes, they must be configured separately.
67    public_routes_raw(config).route("/api/relay/{*not_found}", any(statics::not_found))
68}
69
70#[rustfmt::skip]
71fn public_routes_raw(config: &Config) -> Router<ServiceState> {
72    // Sentry Web API routes pointing to /api/0/relays/
73    let web_routes = Router::new()
74        .route("/api/0/relays/projectconfigs/", post(project_configs::handle))
75        .route("/api/0/relays/publickeys/", post(public_keys::handle))
76        .route("/api/0/relays/register/challenge/", post(register::challenge))
77        .route("/api/0/relays/register/response/", post(register::response))
78        // Network connectivity check for downstream Relays, same as the internal health check.
79        .route("/api/0/relays/live/", get(health_check::handle_live))
80        .route_layer(DefaultBodyLimit::max(crate::constants::MAX_JSON_SIZE));
81
82    let batch_routes = Router::new()
83        .route("/api/0/relays/metrics/", post(batch_metrics::handle))
84        .route_layer(DefaultBodyLimit::max(BATCH_JSON_BODY_LIMIT));
85
86    // Ingestion routes pointing to /api/:project_id/
87    let store_routes = Router::new()
88        // Legacy store path that is missing the project parameter.
89        .route("/api/store/", store::route(config))
90        // cron monitor level routes.  These are user facing APIs and as such support trailing slashes.
91        .route("/api/{project_id}/cron/{monitor_slug}/{sentry_key}", monitor::route(config))
92        .route("/api/{project_id}/cron/{monitor_slug}/{sentry_key}/", monitor::route(config))
93        .route("/api/{project_id}/cron/{monitor_slug}", monitor::route(config))
94        .route("/api/{project_id}/cron/{monitor_slug}/", monitor::route(config))
95
96        .route("/api/{project_id}/store/", store::route(config))
97        .route("/api/{project_id}/envelope/", envelope::route(config))
98        .route("/api/{project_id}/security/", security_report::route(config))
99        .route("/api/{project_id}/csp-report/", security_report::route(config))
100        .route("/api/{project_id}/nel/", nel::route(config))
101        // No mandatory trailing slash here because people already use it like this.
102        .route("/api/{project_id}/minidump", minidump::route(config))
103        .route("/api/{project_id}/minidump/", minidump::route(config))
104        .route("/api/{project_id}/events/{event_id}/attachments/", attachments::route(config))
105        .route("/api/{project_id}/unreal/{sentry_key}/", unreal::route(config))
106        .route("/api/{project_id}/upload/", upload::route_post(config))
107        .route(UPLOAD_PATCH_PATH, upload::route_patch(config));
108
109    #[cfg(sentry)]
110    let store_routes = store_routes.route("/api/{project_id}/playstation/", playstation::route(config));
111    let store_routes = store_routes.route_layer(middlewares::cors());
112
113    // Integration routes.
114    //
115    // For integrations we want to be lenient on trailing `/`, as they are often manually
116    // configured by users or protocols may force a specific variant.
117    let integration_routes = Router::new()
118        .nest("/api/{project_id}/integration/otlp", integrations::otlp::routes(config))
119        .nest("/api/{project_id}/integration/vercel", integrations::vercel::routes(config))
120        .route_layer(middlewares::cors());
121
122    // NOTE: If you add a new (non-experimental) route here, please also list it in
123    // https://github.com/getsentry/sentry-docs/blob/master/docs/product/relay/operating-guidelines.mdx
124
125    Router::new()
126        .merge(web_routes)
127        .merge(batch_routes)
128        .merge(store_routes)
129        .merge(integration_routes)
130        // Forward all other API routes to the upstream. This will 404 for non-API routes.
131        .fallback(forward::forward)
132}