Skip to content

Commit beb44b5

Browse files
committed
DO NOT MERGE: setting the Content-Type to '' does trigger a preflight request
feat: Set outgoing request Content-Type to '' Sentry's Relay server expects envelopes to have Content-Type: application/x-sentry-envelope however, that header would cause CORS preflight requests that we want to avoid. If we don't set the Content-Type header, browsers fill it in with text/plain. We prefer sending an empty string than setting an incorrect Content-Type. Relay accepts the empty string and treats the body as an envelope.
1 parent 8afcb73 commit beb44b5

File tree

4 files changed

+36
-25
lines changed

4 files changed

+36
-25
lines changed

packages/browser/src/transports/fetch.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class FetchTransport extends BaseTransport {
2323
});
2424
}
2525

26-
const sentryReq = eventToSentryRequest(event, this._api);
26+
const sentryReq = eventToSentryRequest(event, this._api, this.options.headers);
2727

2828
const options: RequestInit = {
2929
body: sentryReq.body,
@@ -33,12 +33,9 @@ export class FetchTransport extends BaseTransport {
3333
// It doesn't. And it throw exception instead of ignoring this parameter...
3434
// REF: https://github.com/getsentry/raven-js/issues/1233
3535
referrerPolicy: (supportsReferrerPolicy() ? 'origin' : '') as ReferrerPolicy,
36+
headers: sentryReq.headers,
3637
};
3738

38-
if (this.options.headers !== undefined) {
39-
options.headers = this.options.headers;
40-
}
41-
4239
return this._buffer.add(
4340
new SyncPromise<Response>((resolve, reject) => {
4441
global

packages/browser/src/transports/xhr.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class XHRTransport extends BaseTransport {
2121
});
2222
}
2323

24-
const sentryReq = eventToSentryRequest(event, this._api);
24+
const sentryReq = eventToSentryRequest(event, this._api, this.options.headers);
2525

2626
return this._buffer.add(
2727
new SyncPromise<Response>((resolve, reject) => {
@@ -49,9 +49,9 @@ export class XHRTransport extends BaseTransport {
4949
};
5050

5151
request.open('POST', sentryReq.url);
52-
for (const header in this.options.headers) {
53-
if (this.options.headers.hasOwnProperty(header)) {
54-
request.setRequestHeader(header, this.options.headers[header]);
52+
for (const header in sentryReq.headers) {
53+
if (sentryReq.headers.hasOwnProperty(header)) {
54+
request.setRequestHeader(header, sentryReq.headers[header]);
5555
}
5656
}
5757
request.send(sentryReq.body);

packages/core/src/request.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,38 @@ import { API } from './api';
44

55
/** A generic client request. */
66
interface SentryRequest {
7-
body: string;
87
url: string;
9-
// headers would contain auth & content-type headers for @sentry/node, but
10-
// since @sentry/browser avoids custom headers to prevent CORS preflight
11-
// requests, we can use the same approach for @sentry/browser and @sentry/node
12-
// for simplicity -- no headers involved.
13-
// headers: { [key: string]: string };
8+
headers: { [key: string]: string };
9+
body: string;
1410
}
1511

1612
/** Creates a SentryRequest from an event. */
17-
export function eventToSentryRequest(event: Event, api: API): SentryRequest {
13+
export function eventToSentryRequest(event: Event, api: API, extraHeaders?: { [key: string]: string }): SentryRequest {
1814
const useEnvelope = event.type === 'transaction';
1915

2016
const req: SentryRequest = {
2117
body: JSON.stringify(event),
18+
headers: {
19+
// To simplify maintenance, eventToSentryRequest is used by both
20+
// @sentry/browser and @sentry/node.
21+
//
22+
// In @sentry/browser we want to avoid CORS preflight requests and thus we
23+
// want to ensure outgoing requests are "simple requests" as explained in
24+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Simple_requests.
25+
//
26+
// Therefore, we do not include any custom headers (auth goes in the query
27+
// string instead) and we are limited in the values of Content-Type. If we
28+
// were to not set the Content-Type header, browsers fill it in as
29+
// `text/plain`, which is rejected by Relay for envelopes. If we set it to
30+
// the empty string, current versions of mainstream browsers seem to
31+
// respect it and despite empty string not being in the list of accepted
32+
// values for "simple requests", empirically browsers do not send
33+
// preflight requests in that case.
34+
//
35+
// 'Content-Type': useEnvelope ? 'application/x-sentry-envelope' : 'application/json',
36+
'Content-Type': '',
37+
...extraHeaders,
38+
},
2239
url: useEnvelope ? api.getEnvelopeEndpointWithUrlEncodedAuth() : api.getStoreEndpointWithUrlEncodedAuth(),
2340
};
2441

packages/node/src/transports/base.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,10 @@ export abstract class BaseTransport implements Transport {
5757
}
5858

5959
/** Returns a build request option object used by request */
60-
protected _getRequestOptions(uri: url.URL): http.RequestOptions | https.RequestOptions {
61-
const headers = {
62-
// The auth headers are not included because auth is done via query string to match @sentry/browser.
63-
//
64-
// ...this._api.getRequestHeaders(SDK_NAME, SDK_VERSION)
65-
...this.options.headers,
66-
};
60+
protected _getRequestOptions(
61+
uri: url.URL,
62+
headers: { [key: string]: string },
63+
): http.RequestOptions | https.RequestOptions {
6764
const { hostname, pathname, port, protocol, search } = uri;
6865
// See https://github.com/nodejs/node/blob/38146e717fed2fabe3aacb6540d839475e0ce1c6/lib/internal/url.js#L1268-L1290
6966
const path = `${pathname}${search}`;
@@ -93,8 +90,8 @@ export abstract class BaseTransport implements Transport {
9390
}
9491
return this._buffer.add(
9592
new Promise<Response>((resolve, reject) => {
96-
const sentryReq = eventToSentryRequest(event, this._api);
97-
const options = this._getRequestOptions(new url.URL(sentryReq.url));
93+
const sentryReq = eventToSentryRequest(event, this._api, this.options.headers);
94+
const options = this._getRequestOptions(new url.URL(sentryReq.url), sentryReq.headers);
9895

9996
const req = httpModule.request(options, (res: http.IncomingMessage) => {
10097
const statusCode = res.statusCode || 500;

0 commit comments

Comments
 (0)