relay_server/endpoints/
unreal.rs

1use axum::extract::{DefaultBodyLimit, FromRequest, Query};
2use axum::response::IntoResponse;
3use axum::routing::{MethodRouter, post};
4use bytes::Bytes;
5use relay_config::Config;
6use relay_event_schema::protocol::EventId;
7use serde::Deserialize;
8
9use crate::constants::UNREAL_USER_HEADER;
10use crate::endpoints::common::{self, BadStoreRequest, TextResponse};
11use crate::envelope::{ContentType, Envelope, Item, ItemType};
12use crate::extractors::RequestMeta;
13use crate::middlewares;
14use crate::service::ServiceState;
15
16#[derive(Debug, Deserialize)]
17struct UnrealQuery {
18    #[serde(rename = "UserID")]
19    user_id: Option<String>,
20}
21
22#[derive(Debug, FromRequest)]
23#[from_request(state(ServiceState))]
24struct UnrealParams {
25    meta: RequestMeta,
26    #[from_request(via(Query))]
27    query: UnrealQuery,
28    data: Bytes,
29}
30
31impl UnrealParams {
32    fn extract_envelope(self) -> Result<Box<Envelope>, BadStoreRequest> {
33        let Self { meta, query, data } = self;
34
35        if data.is_empty() {
36            return Err(BadStoreRequest::EmptyBody);
37        }
38
39        let mut envelope = Envelope::from_request(Some(EventId::new()), meta);
40
41        let mut item = Item::new(ItemType::UnrealReport);
42        item.set_payload(ContentType::OctetStream, data);
43        envelope.add_item(item);
44
45        if let Some(user_id) = query.user_id {
46            envelope.set_header(UNREAL_USER_HEADER, user_id);
47        }
48
49        Ok(envelope)
50    }
51}
52
53async fn handle(
54    state: ServiceState,
55    params: UnrealParams,
56) -> Result<impl IntoResponse, BadStoreRequest> {
57    let envelope = params.extract_envelope()?;
58    let id = envelope.event_id();
59
60    // Never respond with a 429 since clients often retry these
61    match common::handle_envelope(&state, envelope)
62        .await
63        .map_err(|err| err.into_inner())
64    {
65        Ok(_) | Err(BadStoreRequest::RateLimited(_)) => (),
66        Err(error) => return Err(error),
67    };
68
69    // The return here is only useful for consistency because the UE4 crash reporter doesn't
70    // care about it.
71    Ok(TextResponse(id))
72}
73
74pub fn route(config: &Config) -> MethodRouter<ServiceState> {
75    post(handle)
76        .route_layer(DefaultBodyLimit::max(config.max_attachments_size()))
77        .route_layer(axum::middleware::from_fn(middlewares::content_length))
78}