relay_server/endpoints/
nel.rs

1//! Endpoint for Network Error Logging (NEL) reports.
2//!
3//! It split list of incoming events from the envelope into separate envelope with 1 item inside.
4//! Which later get failed by the service infrastructure.
5
6use axum::extract::{DefaultBodyLimit, FromRequest};
7use axum::http::StatusCode;
8use axum::response::IntoResponse;
9use axum::routing::{MethodRouter, post};
10use bytes::Bytes;
11use relay_config::Config;
12use relay_event_schema::protocol::EventId;
13use serde_json::value::RawValue;
14
15use crate::endpoints::common::{self, BadStoreRequest};
16use crate::envelope::{ContentType, Envelope, Item, ItemType};
17use crate::extractors::{Mime, RequestMeta};
18use crate::service::ServiceState;
19
20#[derive(Debug, FromRequest)]
21#[from_request(state(ServiceState))]
22struct NelReportParams {
23    meta: RequestMeta,
24    body: Bytes,
25}
26
27fn is_nel_mime(mime: Mime) -> bool {
28    let ty = mime.type_().as_str();
29    let subty = mime.subtype().as_str();
30    let suffix = mime.suffix().map(|suffix| suffix.as_str());
31
32    matches!(
33        (ty, subty, suffix),
34        ("application", "json", None) | ("application", "reports", Some("json"))
35    )
36}
37
38/// Handles all messages coming on the NEL endpoint.
39async fn handle(
40    state: ServiceState,
41    mime: Mime,
42    params: NelReportParams,
43) -> Result<impl IntoResponse, BadStoreRequest> {
44    if !is_nel_mime(mime) {
45        return Ok(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response());
46    }
47
48    let items: Vec<&RawValue> =
49        serde_json::from_slice(&params.body).map_err(BadStoreRequest::InvalidJson)?;
50
51    let mut envelope = Envelope::from_request(Some(EventId::new()), params.meta.clone());
52    for item in items {
53        let mut report_item = Item::new(ItemType::Nel);
54        report_item.set_payload(ContentType::Json, item.to_owned().to_string());
55        envelope.add_item(report_item);
56    }
57
58    common::handle_envelope(&state, envelope).await?;
59    Ok(().into_response())
60}
61
62pub fn route(config: &Config) -> MethodRouter<ServiceState> {
63    post(handle).route_layer(DefaultBodyLimit::max(config.max_event_size()))
64}