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::service::ServiceState;
14
15#[derive(Debug, Deserialize)]
16struct UnrealQuery {
17    #[serde(rename = "UserID")]
18    user_id: Option<String>,
19}
20
21#[derive(Debug, FromRequest)]
22#[from_request(state(ServiceState))]
23struct UnrealParams {
24    meta: RequestMeta,
25    #[from_request(via(Query))]
26    query: UnrealQuery,
27    data: Bytes,
28}
29
30impl UnrealParams {
31    fn extract_envelope(self) -> Result<Box<Envelope>, BadStoreRequest> {
32        let Self { meta, query, data } = self;
33
34        if data.is_empty() {
35            return Err(BadStoreRequest::EmptyBody);
36        }
37
38        let mut envelope = Envelope::from_request(Some(EventId::new()), meta);
39
40        let mut item = Item::new(ItemType::UnrealReport);
41        item.set_payload(ContentType::OctetStream, data);
42        envelope.add_item(item);
43
44        if let Some(user_id) = query.user_id {
45            envelope.set_header(UNREAL_USER_HEADER, user_id);
46        }
47
48        Ok(envelope)
49    }
50}
51
52async fn handle(
53    state: ServiceState,
54    params: UnrealParams,
55) -> Result<impl IntoResponse, BadStoreRequest> {
56    let envelope = params.extract_envelope()?;
57    let id = envelope.event_id();
58
59    // Never respond with a 429 since clients often retry these
60    match common::handle_envelope(&state, envelope).await {
61        Ok(_) | Err(BadStoreRequest::RateLimited(_)) => (),
62        Err(error) => return Err(error),
63    };
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: &Config) -> MethodRouter<ServiceState> {
71    post(handle).route_layer(DefaultBodyLimit::max(config.max_attachments_size()))
72}