Skip to content

Commit 4f6375e

Browse files
committed
Reapply "feat(tus): split into separate creation and upload requests"
This reverts commit 4f3fa6d.
1 parent 9868777 commit 4f6375e

4 files changed

Lines changed: 166 additions & 41 deletions

File tree

src/transports/sentry_http_transport.c

Lines changed: 73 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,6 @@ prepare_tus_request_common(
225225
h->key = "x-sentry-auth";
226226
h->value = sentry__dsn_get_auth_header(dsn, user_agent);
227227

228-
h = &req->headers[req->headers_len++];
229-
h->key = "content-type";
230-
h->value = sentry__string_clone(TUS_MIME);
231-
232228
h = &req->headers[req->headers_len++];
233229
h->key = "tus-resumable";
234230
h->value = sentry__string_clone("1.0.0");
@@ -241,18 +237,46 @@ prepare_tus_request_common(
241237
}
242238

243239
static sentry_prepared_http_request_t *
244-
prepare_tus_request(const sentry_path_t *path, size_t file_size,
245-
const sentry_dsn_t *dsn, const char *user_agent)
240+
prepare_tus_upload_request(
241+
const char *location, const sentry_path_t *path, size_t file_size)
246242
{
247-
if (!path) {
243+
if (!location || !path) {
248244
return NULL;
249245
}
246+
250247
sentry_prepared_http_request_t *req
251-
= prepare_tus_request_common(file_size, dsn, user_agent);
252-
if (req) {
253-
req->body_path = sentry__path_clone(path);
254-
req->body_len = file_size;
248+
= SENTRY_MAKE(sentry_prepared_http_request_t);
249+
if (!req) {
250+
return NULL;
251+
}
252+
memset(req, 0, sizeof(*req));
253+
254+
req->headers = sentry_malloc(
255+
sizeof(sentry_prepared_http_header_t) * TUS_MAX_HTTP_HEADERS);
256+
if (!req->headers) {
257+
sentry_free(req);
258+
return NULL;
255259
}
260+
req->headers_len = 0;
261+
262+
req->method = "PATCH";
263+
req->url = sentry__string_clone(location);
264+
req->body_path = sentry__path_clone(path);
265+
req->body_len = file_size;
266+
267+
sentry_prepared_http_header_t *h;
268+
h = &req->headers[req->headers_len++];
269+
h->key = "tus-resumable";
270+
h->value = sentry__string_clone("1.0.0");
271+
272+
h = &req->headers[req->headers_len++];
273+
h->key = "content-type";
274+
h->value = sentry__string_clone(TUS_MIME);
275+
276+
h = &req->headers[req->headers_len++];
277+
h->key = "upload-offset";
278+
h->value = sentry__string_clone("0");
279+
256280
return req;
257281
}
258282

@@ -307,8 +331,9 @@ tus_upload_ref(http_transport_state_t *state, const sentry_path_t *att_dir,
307331
return false;
308332
}
309333

310-
sentry_prepared_http_request_t *req = prepare_tus_request(
311-
att_file, file_size, state->dsn, state->user_agent);
334+
// Step 1: TUS creation request (POST, no body)
335+
sentry_prepared_http_request_t *req
336+
= prepare_tus_request_common(file_size, state->dsn, state->user_agent);
312337
if (!req) {
313338
sentry__path_free(att_file);
314339
return false;
@@ -339,19 +364,42 @@ tus_upload_ref(http_transport_state_t *state, const sentry_path_t *att_dir,
339364
return false;
340365
}
341366

367+
// Step 2: TUS upload request (PATCH with file body)
368+
req = prepare_tus_upload_request(resp.location, att_file, file_size);
369+
char *location = resp.location;
370+
resp.location = NULL;
371+
http_response_cleanup(&resp);
372+
373+
if (!req) {
374+
sentry__path_free(att_file);
375+
sentry_free(location);
376+
return false;
377+
}
378+
379+
memset(&resp, 0, sizeof(resp));
380+
ok = state->send_func(state->client, req, &resp);
381+
sentry__prepared_http_request_free(req);
382+
http_response_cleanup(&resp);
383+
384+
if (!ok || resp.status_code != 204) {
385+
sentry__path_free(att_file);
386+
sentry_free(location);
387+
return false;
388+
}
389+
342390
const char *ref_ct = sentry_value_as_string(
343391
sentry_value_get_by_key(entry, "content_type"));
344392
const char *att_type = sentry_value_as_string(
345393
sentry_value_get_by_key(entry, "attachment_type"));
346394
sentry_value_t att_length
347395
= sentry_value_get_by_key(entry, "attachment_length");
348396
sentry_value_incref(att_length);
349-
sentry__envelope_add_attachment_ref(tus_envelope, resp.location, filename,
397+
sentry__envelope_add_attachment_ref(tus_envelope, location, filename,
350398
*ref_ct ? ref_ct : NULL, *att_type ? att_type : NULL, att_length);
351399

352400
sentry__path_remove(att_file);
353401
sentry__path_free(att_file);
354-
http_response_cleanup(&resp);
402+
sentry_free(location);
355403
return true;
356404
}
357405

@@ -772,9 +820,16 @@ sentry__http_transport_get_bgworker(sentry_transport_t *transport)
772820
}
773821

774822
sentry_prepared_http_request_t *
775-
sentry__prepare_tus_request(const sentry_path_t *path, size_t file_size,
776-
const sentry_dsn_t *dsn, const char *user_agent)
823+
sentry__prepare_tus_create_request(
824+
size_t file_size, const sentry_dsn_t *dsn, const char *user_agent)
825+
{
826+
return prepare_tus_request_common(file_size, dsn, user_agent);
827+
}
828+
829+
sentry_prepared_http_request_t *
830+
sentry__prepare_tus_upload_request(
831+
const char *location, const sentry_path_t *path, size_t file_size)
777832
{
778-
return prepare_tus_request(path, file_size, dsn, user_agent);
833+
return prepare_tus_upload_request(location, path, file_size);
779834
}
780835
#endif

src/transports/sentry_http_transport.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ void sentry__http_transport_set_shutdown_client(
5555

5656
#ifdef SENTRY_UNITTEST
5757
void *sentry__http_transport_get_bgworker(sentry_transport_t *transport);
58-
sentry_prepared_http_request_t *sentry__prepare_tus_request(
59-
const sentry_path_t *path, size_t file_size, const sentry_dsn_t *dsn,
60-
const char *user_agent);
58+
sentry_prepared_http_request_t *sentry__prepare_tus_create_request(
59+
size_t file_size, const sentry_dsn_t *dsn, const char *user_agent);
60+
sentry_prepared_http_request_t *sentry__prepare_tus_upload_request(
61+
const char *location, const sentry_path_t *path, size_t file_size);
6162
#endif
6263

6364
#endif

tests/test_integration_tus.py

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,11 @@ def test_tus_upload_large_attachment(cmake, httpserver):
2626
{"SENTRY_BACKEND": "none", "SENTRY_TRANSPORT_COMPRESSION": "Off"},
2727
)
2828

29-
location = "/api/123456/upload/abc123def456789/?length=104857600&signature=xyz"
29+
upload_uri = "/api/123456/upload/abc123def456789/"
30+
upload_qs = "length=104857600&signature=xyz"
31+
location = httpserver.url_for(upload_uri) + "?" + upload_qs
3032

33+
# TUS creation request (POST, no body) -> 201 + Location
3134
httpserver.expect_oneshot_request(
3235
"/api/123456/upload/",
3336
headers={"tus-resumable": "1.0.0"},
@@ -37,6 +40,14 @@ def test_tus_upload_large_attachment(cmake, httpserver):
3740
headers={"Location": location},
3841
)
3942

43+
# TUS upload request (PATCH with body) -> 204
44+
httpserver.expect_oneshot_request(
45+
upload_uri,
46+
method="PATCH",
47+
headers={"tus-resumable": "1.0.0"},
48+
query_string=upload_qs,
49+
).respond_with_data("", status=204)
50+
4051
httpserver.expect_request(
4152
"/api/123456/envelope/",
4253
headers={"x-sentry-auth": auth_header},
@@ -51,27 +62,35 @@ def test_tus_upload_large_attachment(cmake, httpserver):
5162
env=env,
5263
)
5364

54-
assert len(httpserver.log) == 3
65+
assert len(httpserver.log) == 4
5566

56-
# Find the upload request and envelope requests
67+
# Find the create, upload, and envelope requests
68+
create_req = None
5769
upload_req = None
5870
envelope_reqs = []
5971
for entry in httpserver.log:
6072
req = entry[0]
61-
if "/upload/" in req.path:
73+
if req.path == "/api/123456/upload/" and req.method == "POST":
74+
create_req = req
75+
elif upload_uri in req.path and req.method == "PATCH":
6276
upload_req = req
6377
elif "/envelope/" in req.path:
6478
envelope_reqs.append(req)
6579

80+
assert create_req is not None
6681
assert upload_req is not None
6782
assert len(envelope_reqs) == 2
6883

84+
# Verify TUS creation request headers
85+
assert create_req.headers.get("tus-resumable") == "1.0.0"
86+
upload_length = create_req.headers.get("upload-length")
87+
assert upload_length is not None
88+
assert int(upload_length) == 100 * 1024 * 1024
89+
6990
# Verify TUS upload request headers
7091
assert upload_req.headers.get("tus-resumable") == "1.0.0"
7192
assert upload_req.headers.get("content-type") == "application/offset+octet-stream"
72-
upload_length = upload_req.headers.get("upload-length")
73-
assert upload_length is not None
74-
assert int(upload_length) == 100 * 1024 * 1024
93+
assert upload_req.headers.get("upload-offset") == "0"
7594

7695
# One envelope has the resolved attachment-refs, the other is the original
7796
attachment_ref = None
@@ -183,7 +202,9 @@ def test_tus_crash_restart(cmake, httpserver):
183202
att_size = os.path.getsize(os.path.join(att_dir, att_files[0]))
184203
assert att_size >= 100 * 1024 * 1024
185204

186-
location = "/api/123456/upload/abc123def456789/?length=104857600&signature=xyz"
205+
upload_uri = "/api/123456/upload/abc123def456789/"
206+
upload_qs = "length=104857600&signature=xyz"
207+
location = httpserver.url_for(upload_uri) + "?" + upload_qs
187208

188209
# Second run: restart picks up crash and uploads via TUS
189210
httpserver.expect_oneshot_request(
@@ -195,6 +216,13 @@ def test_tus_crash_restart(cmake, httpserver):
195216
headers={"Location": location},
196217
)
197218

219+
httpserver.expect_oneshot_request(
220+
upload_uri,
221+
method="PATCH",
222+
headers={"tus-resumable": "1.0.0"},
223+
query_string=upload_qs,
224+
).respond_with_data("", status=204)
225+
198226
httpserver.expect_request(
199227
"/api/123456/envelope/",
200228
headers={"x-sentry-auth": auth_header},
@@ -207,24 +235,32 @@ def test_tus_crash_restart(cmake, httpserver):
207235
env=env,
208236
)
209237

210-
assert len(httpserver.log) == 3
238+
assert len(httpserver.log) == 4
211239

240+
create_req = None
212241
upload_req = None
213242
envelope_reqs = []
214243
for entry in httpserver.log:
215244
req = entry[0]
216-
if "/upload/" in req.path:
245+
if req.path == "/api/123456/upload/" and req.method == "POST":
246+
create_req = req
247+
elif upload_uri in req.path and req.method == "PATCH":
217248
upload_req = req
218249
elif "/envelope/" in req.path:
219250
envelope_reqs.append(req)
220251

252+
assert create_req is not None
221253
assert upload_req is not None
222254
assert len(envelope_reqs) == 2
223255

256+
# Verify TUS creation request headers
257+
assert create_req.headers.get("tus-resumable") == "1.0.0"
258+
assert int(create_req.headers.get("upload-length")) == 100 * 1024 * 1024
259+
224260
# Verify TUS upload request headers
225261
assert upload_req.headers.get("tus-resumable") == "1.0.0"
226262
assert upload_req.headers.get("content-type") == "application/offset+octet-stream"
227-
assert int(upload_req.headers.get("upload-length")) == 100 * 1024 * 1024
263+
assert upload_req.headers.get("upload-offset") == "0"
228264

229265
# One envelope has the resolved attachment-refs, the other is the original
230266
attachment_ref = None

tests/unit/test_envelopes.c

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -742,19 +742,15 @@ SENTRY_TEST(tus_request_preparation)
742742
{
743743
SENTRY_TEST_DSN_NEW_DEFAULT(dsn);
744744

745-
const char *test_file_str = SENTRY_TEST_PATH_PREFIX "sentry_test_tus_file";
746-
sentry_path_t *test_file_path = sentry__path_from_str(test_file_str);
747-
TEST_CHECK_INT_EQUAL(
748-
sentry__path_write_buffer(test_file_path, "test-data", 9), 0);
749-
745+
// Test creation request (POST, no body)
750746
sentry_prepared_http_request_t *req
751-
= sentry__prepare_tus_request(test_file_path, 9, dsn, NULL);
747+
= sentry__prepare_tus_create_request(9, dsn, NULL);
752748
TEST_CHECK(!!req);
753749
TEST_CHECK_STRING_EQUAL(req->method, "POST");
754750
TEST_CHECK_STRING_EQUAL(
755751
req->url, "https://sentry.invalid:443/api/42/upload/");
756-
TEST_CHECK(!!req->body_path);
757-
TEST_CHECK_INT_EQUAL(req->body_len, 9);
752+
TEST_CHECK(!req->body_path);
753+
TEST_CHECK_INT_EQUAL(req->body_len, 0);
758754
TEST_CHECK(!req->body);
759755

760756
bool has_tus_resumable = false;
@@ -769,15 +765,52 @@ SENTRY_TEST(tus_request_preparation)
769765
TEST_CHECK_STRING_EQUAL(req->headers[i].value, "9");
770766
has_upload_length = true;
771767
}
768+
if (strcmp(req->headers[i].key, "content-type") == 0) {
769+
has_content_type = true;
770+
}
771+
}
772+
TEST_CHECK(has_tus_resumable);
773+
TEST_CHECK(has_upload_length);
774+
TEST_CHECK(!has_content_type);
775+
776+
sentry__prepared_http_request_free(req);
777+
778+
// Test upload request (PATCH with body)
779+
const char *test_file_str = SENTRY_TEST_PATH_PREFIX "sentry_test_tus_file";
780+
sentry_path_t *test_file_path = sentry__path_from_str(test_file_str);
781+
TEST_CHECK_INT_EQUAL(
782+
sentry__path_write_buffer(test_file_path, "test-data", 9), 0);
783+
784+
const char *location = "https://sentry.invalid/api/42/upload/abc123/";
785+
req = sentry__prepare_tus_upload_request(location, test_file_path, 9);
786+
TEST_CHECK(!!req);
787+
TEST_CHECK_STRING_EQUAL(req->method, "PATCH");
788+
TEST_CHECK_STRING_EQUAL(req->url, location);
789+
TEST_CHECK(!!req->body_path);
790+
TEST_CHECK_INT_EQUAL(req->body_len, 9);
791+
TEST_CHECK(!req->body);
792+
793+
has_tus_resumable = false;
794+
has_content_type = false;
795+
bool has_upload_offset = false;
796+
for (size_t i = 0; i < req->headers_len; i++) {
797+
if (strcmp(req->headers[i].key, "tus-resumable") == 0) {
798+
TEST_CHECK_STRING_EQUAL(req->headers[i].value, "1.0.0");
799+
has_tus_resumable = true;
800+
}
772801
if (strcmp(req->headers[i].key, "content-type") == 0) {
773802
TEST_CHECK_STRING_EQUAL(
774803
req->headers[i].value, "application/offset+octet-stream");
775804
has_content_type = true;
776805
}
806+
if (strcmp(req->headers[i].key, "upload-offset") == 0) {
807+
TEST_CHECK_STRING_EQUAL(req->headers[i].value, "0");
808+
has_upload_offset = true;
809+
}
777810
}
778811
TEST_CHECK(has_tus_resumable);
779-
TEST_CHECK(has_upload_length);
780812
TEST_CHECK(has_content_type);
813+
TEST_CHECK(has_upload_offset);
781814

782815
sentry__prepared_http_request_free(req);
783816
sentry__path_remove(test_file_path);

0 commit comments

Comments
 (0)