Skip to content

feat(server): Upload endpoint#5638

Merged
jjbayer merged 79 commits into
masterfrom
feat/tus-upload-endpoint
Feb 19, 2026
Merged

feat(server): Upload endpoint#5638
jjbayer merged 79 commits into
masterfrom
feat/tus-upload-endpoint

Conversation

@jjbayer

@jjbayer jjbayer commented Feb 13, 2026

Copy link
Copy Markdown
Member

This PR implements the first version of the /upload endpoint for large files.

  • It uses TUS-compliant headers, but in violation of the protocol only supports "Creation with Upload", not "Creation" with subsequent uploads.
  • The endpoint dispatches to either the upload service or the upstream relay.

Follow-up:

Closes https://linear.app/getsentry/issue/INGEST-724/implement-minimal-tus-protocol.

jjbayer and others added 30 commits February 10, 2026 09:17
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>
Comment thread relay-server/src/utils/upload.rs Outdated
StatusCode::PAYLOAD_TOO_LARGE.into_response()
} else {
StatusCode::BAD_REQUEST.into_response()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Fix in Cursor Fix in Web

Base automatically changed from feat/project-await to master February 18, 2026 13:44
Comment thread relay-server/src/services/upload.rs
@jjbayer jjbayer enabled auto-merge February 19, 2026 14:51
Comment thread relay-server/src/endpoints/upload.rs

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread relay-server/src/endpoints/upload.rs
@jjbayer jjbayer added this pull request to the merge queue Feb 19, 2026
Merged via the queue into master with commit cd4ce50 Feb 19, 2026
29 checks passed
@jjbayer jjbayer deleted the feat/tus-upload-endpoint branch February 19, 2026 15:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants