Skip to main content

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::ConfigSnapshot;
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        if let Some(user_id) = query.user_id {
42            envelope.set_header(UNREAL_USER_HEADER, user_id);
43        }
44
45        let mut item = Item::new(ItemType::UnrealReport);
46        item.set_payload(ContentType::OctetStream, data);
47        envelope.add_item(item);
48
49        Ok(envelope)
50    }
51}
52
53async fn handle(
54    state: ServiceState,
55    params: UnrealParams,
56) -> axum::response::Result<impl IntoResponse> {
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    common::handle_envelope(&state, envelope)
62        .await?
63        .ignore_rate_limits();
64
65    // The return here is only useful for consistency because the UE4 crash reporter doesn't
66    // care about it.
67    Ok(TextResponse(id))
68}
69
70pub fn route(config: &ConfigSnapshot) -> MethodRouter<ServiceState> {
71    post(handle)
72        .route_layer(DefaultBodyLimit::max(config.max_attachments_size()))
73        .route_layer(axum::middleware::from_fn(middlewares::content_length))
74}