feat(server): Upload endpoint#5638
Conversation
Add a new `/api/{project_id}/upload/` endpoint that implements the TUS
protocol "Creation With Upload" feature. This allows uploading data in
a single POST request with TUS headers.
The endpoint validates:
- Tus-Resumable header (must be "1.0.0")
- Upload-Length header (must match body size)
- Upload-Metadata header (optional, base64-encoded key-value pairs)
Actual upload storage is not yet implemented (marked with TODOs).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Process the upload body as a stream instead of buffering the entire upload into memory. This makes the endpoint suitable for large file uploads. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Split `upload.rs` into `upload/mod.rs` (endpoint handler) and `upload/tus.rs` (protocol constants, error types, metadata parsing). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… tests Replace ContentLengthMismatch (400) with PayloadTooLarge (413) when the streaming body exceeds the declared Upload-Length. Add integration tests covering: successful upload, missing/invalid TUS headers, and the 413 response for oversized bodies. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add a new `limits.max_upload_size` config option and apply it as axum's DefaultBodyLimit on the TUS upload route. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix the upload endpoint to properly handle results from both the upstream relay and the local upload service: - Sink::upload now returns Result<SinkResult, UploadError> with proper error types for each sink variant - Fix UploadError::UploadFailed to delegate to BadStoreRequest's IntoResponse (was calling .to_string() producing 200 OK) - Add UploadError variants for Forward, UploadService, ServiceUnavailable with appropriate status code mapping (503, 504, 500) - Fix handle_stream to create objectstore session with scoping and stream the body via put_stream - Add scoping field to UploadStream so objectstore can scope uploads - Change UploadStream response to AsyncResponse<Result<(), Error>> for proper error propagation - Remove dbg!() call and dead code Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap UploadStream in Managed<> for proper outcome tracking: - Add expected_length field and implement Counted for UploadStream - Change FromMessage impl to accept Managed<UploadStream> - Create Managed<UploadStream> in the endpoint using envelope.wrap() to inherit scoping and outcome tracking metadata - Update handle_stream to accept/reject the Managed wrapper - On load-shed, properly reject the Managed instance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce ExactStream, a Sync streaming wrapper that validates the total byte count against the announced Upload-Length header. It returns an error if the stream provides more or fewer bytes than expected, catching protocol violations at the stream level rather than after buffering. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move ExactStream from the upload service module to relay-server utils, making it generic over the inner stream type. The Stream impl requires S: Unpin, which is naturally satisfied by boxed streams (Pin<Box<…>>). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| StatusCode::PAYLOAD_TOO_LARGE.into_response() | ||
| } else { | ||
| StatusCode::BAD_REQUEST.into_response() | ||
| } |
There was a problem hiding this comment.
Forwarding error status change affects all forwarded requests
Medium Severity
The refactored SendFailed error handling introduces a new 400 Bad Request response for hyper user errors that aren't LengthLimitError. Previously, these errors fell through to return 502 Bad Gateway. This change applies to ForwardError::into_response, which is shared by ALL forwarded request types (store, envelope, security_report, minidump, etc.), not just the new upload endpoint. Any body stream error classified as a hyper user error during forwarding will now return 400 instead of 502, which could affect client retry logic, monitoring, and load balancer behavior for existing endpoints.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.


This PR implements the first version of the
/uploadendpoint for large files.Follow-up:
check_envelopefrom upload endpointCloses https://linear.app/getsentry/issue/INGEST-724/implement-minimal-tus-protocol.