relay_server/endpoints/
store.rsuse std::io::{self, Read};
use axum::extract::{DefaultBodyLimit, Query};
use axum::http::header;
use axum::response::IntoResponse;
use axum::routing::{post, MethodRouter};
use bytes::Bytes;
use data_encoding::BASE64;
use flate2::bufread::ZlibDecoder;
use relay_config::Config;
use relay_event_schema::protocol::EventId;
use serde::{Deserialize, Serialize};
use crate::endpoints::common::{self, BadStoreRequest};
use crate::envelope::{self, ContentType, Envelope, Item, ItemType};
use crate::extractors::{RawContentType, RequestMeta};
use crate::service::ServiceState;
fn decode_bytes(body: Bytes, limit: usize) -> Result<Bytes, io::Error> {
if body.is_empty() || body.starts_with(b"{") {
return Ok(body);
}
let binary_body = BASE64
.decode(body.as_ref())
.map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
if binary_body.starts_with(b"{") {
return Ok(binary_body.into());
}
let mut decode_stream = ZlibDecoder::new(binary_body.as_slice()).take(limit as u64);
let mut bytes = vec![];
decode_stream.read_to_end(&mut bytes)?;
Ok(Bytes::from(bytes))
}
fn parse_event(
mut body: Bytes,
meta: RequestMeta,
config: &Config,
) -> Result<Box<Envelope>, BadStoreRequest> {
body = decode_bytes(body, config.max_event_size()).map_err(BadStoreRequest::InvalidBody)?;
if body.is_empty() {
return Err(BadStoreRequest::EmptyBody);
}
let is_legacy_python_json = meta.client().map_or(false, |agent| {
agent.starts_with("raven-python/") || agent.starts_with("sentry-python/")
});
if is_legacy_python_json {
let mut data_mut = body.to_vec();
json_forensics::translate_slice(&mut data_mut[..]);
body = data_mut.into();
}
let minimal = common::minimal_event_from_json(&body)?;
let item_type = ItemType::from_event_type(minimal.ty);
let mut event_item = Item::new(item_type);
event_item.set_payload(ContentType::Json, body);
let event_id = minimal.id.unwrap_or_else(EventId::new);
let mut envelope = Envelope::from_request(Some(event_id), meta);
envelope.add_item(event_item);
Ok(envelope)
}
#[derive(Serialize)]
struct PostResponse {
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<EventId>,
}
async fn handle_post(
state: ServiceState,
meta: RequestMeta,
content_type: RawContentType,
body: Bytes,
) -> Result<impl IntoResponse, BadStoreRequest> {
let envelope = match content_type.as_ref() {
envelope::CONTENT_TYPE => Envelope::parse_request(body, meta)?,
_ => parse_event(body, meta, state.config())?,
};
let id = common::handle_envelope(&state, envelope).await?;
Ok(axum::Json(PostResponse { id }).into_response())
}
#[derive(Debug, Deserialize)]
struct GetQuery {
sentry_data: String,
}
static PIXEL: &[u8] =
b"GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;";
async fn handle_get(
state: ServiceState,
meta: RequestMeta,
Query(query): Query<GetQuery>,
) -> Result<impl IntoResponse, BadStoreRequest> {
let envelope = parse_event(query.sentry_data.into(), meta, state.config())?;
common::handle_envelope(&state, envelope).await?;
Ok(([(header::CONTENT_TYPE, "image/gif")], PIXEL))
}
pub fn route(config: &Config) -> MethodRouter<ServiceState> {
(post(handle_post).get(handle_get)).route_layer(DefaultBodyLimit::max(config.max_event_size()))
}