Skip to content

Commit 38d9689

Browse files
authored
fix(astro): Only track access request headers in dynamic page requests (#13306)
In Astro middleware, we're not allowed to access the `request.headers` object if the incoming request is for a statically generated/prerendered route. Since we accessed these headers previously, users would get a warning as reported multiple times and tracked in #13116. This patch fixes that by checking for static vs dynamic route
1 parent 51bbf32 commit 38d9689

File tree

2 files changed

+133
-50
lines changed

2 files changed

+133
-50
lines changed

packages/astro/src/server/middleware.ts

+28-7
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,27 @@ async function instrumentRequest(
8484
}
8585
addNonEnumerableProperty(locals, '__sentry_wrapped__', true);
8686

87-
const { method, headers } = ctx.request;
87+
const isDynamicPageRequest = checkIsDynamicPageRequest(ctx);
88+
89+
const { method, headers } = isDynamicPageRequest
90+
? ctx.request
91+
: // headers can only be accessed in dynamic routes. Accessing `ctx.request.headers` in a static route
92+
// will make the server log a warning.
93+
{ method: ctx.request.method, headers: undefined };
8894

8995
return continueTrace(
9096
{
91-
sentryTrace: headers.get('sentry-trace') || undefined,
92-
baggage: headers.get('baggage'),
97+
sentryTrace: headers?.get('sentry-trace') || undefined,
98+
baggage: headers?.get('baggage'),
9399
},
94100
async () => {
95-
// We store this on the current scope, not isolation scope,
96-
// because we may have multiple requests nested inside each other
97-
getCurrentScope().setSDKProcessingMetadata({ request: ctx.request });
101+
getCurrentScope().setSDKProcessingMetadata({
102+
// We store the request on the current scope, not isolation scope,
103+
// because we may have multiple requests nested inside each other
104+
request: isDynamicPageRequest ? ctx.request : { method, url: ctx.request.url },
105+
});
98106

99-
if (options.trackClientIp) {
107+
if (options.trackClientIp && isDynamicPageRequest) {
100108
getCurrentScope().setUser({ ip_address: ctx.clientAddress });
101109
}
102110

@@ -277,3 +285,16 @@ function tryDecodeUrl(url: string): string | undefined {
277285
return undefined;
278286
}
279287
}
288+
289+
/**
290+
* Checks if the incoming request is a request for a dynamic (server-side rendered) page.
291+
* We can check this by looking at the middleware's `clientAddress` context property because accessing
292+
* this prop in a static route will throw an error which we can conveniently catch.
293+
*/
294+
function checkIsDynamicPageRequest(context: Parameters<MiddlewareResponseHandler>[0]): boolean {
295+
try {
296+
return context.clientAddress != null;
297+
} catch {
298+
return false;
299+
}
300+
}

packages/astro/test/server/middleware.test.ts

+105-43
Original file line numberDiff line numberDiff line change
@@ -149,55 +149,117 @@ describe('sentryMiddleware', () => {
149149
});
150150
});
151151

152-
it('attaches client IP if `trackClientIp=true`', async () => {
153-
const middleware = handleRequest({ trackClientIp: true });
154-
const ctx = {
155-
request: {
156-
method: 'GET',
157-
url: '/users',
158-
headers: new Headers({
159-
'some-header': 'some-value',
160-
}),
161-
},
162-
clientAddress: '192.168.0.1',
163-
params: {},
164-
url: new URL('https://myDomain.io/users/'),
165-
};
166-
const next = vi.fn(() => nextResult);
152+
describe('track client IP address', () => {
153+
it('attaches client IP if `trackClientIp=true` when handling dynamic page requests', async () => {
154+
const middleware = handleRequest({ trackClientIp: true });
155+
const ctx = {
156+
request: {
157+
method: 'GET',
158+
url: '/users',
159+
headers: new Headers({
160+
'some-header': 'some-value',
161+
}),
162+
},
163+
clientAddress: '192.168.0.1',
164+
params: {},
165+
url: new URL('https://myDomain.io/users/'),
166+
};
167+
const next = vi.fn(() => nextResult);
167168

168-
// @ts-expect-error, a partial ctx object is fine here
169-
await middleware(ctx, next);
169+
// @ts-expect-error, a partial ctx object is fine here
170+
await middleware(ctx, next);
170171

171-
expect(setUserMock).toHaveBeenCalledWith({ ip_address: '192.168.0.1' });
172+
expect(setUserMock).toHaveBeenCalledWith({ ip_address: '192.168.0.1' });
173+
});
174+
175+
it("doesn't attach a client IP if `trackClientIp=true` when handling static page requests", async () => {
176+
const middleware = handleRequest({ trackClientIp: true });
177+
178+
const ctx = {
179+
request: {
180+
method: 'GET',
181+
url: '/users',
182+
headers: new Headers({
183+
'some-header': 'some-value',
184+
}),
185+
},
186+
get clientAddress() {
187+
throw new Error('clientAddress.get() should not be called in static page requests');
188+
},
189+
params: {},
190+
url: new URL('https://myDomain.io/users/'),
191+
};
192+
193+
const next = vi.fn(() => nextResult);
194+
195+
// @ts-expect-error, a partial ctx object is fine here
196+
await middleware(ctx, next);
197+
198+
expect(setUserMock).not.toHaveBeenCalled();
199+
expect(next).toHaveBeenCalledTimes(1);
200+
});
172201
});
173202

174-
it('attaches request as SDK processing metadata', async () => {
175-
const middleware = handleRequest({});
176-
const ctx = {
177-
request: {
178-
method: 'GET',
179-
url: '/users',
180-
headers: new Headers({
181-
'some-header': 'some-value',
182-
}),
183-
},
184-
clientAddress: '192.168.0.1',
185-
params: {},
186-
url: new URL('https://myDomain.io/users/'),
187-
};
188-
const next = vi.fn(() => nextResult);
203+
describe('request data', () => {
204+
it('attaches request as SDK processing metadata in dynamic page requests', async () => {
205+
const middleware = handleRequest({});
206+
const ctx = {
207+
request: {
208+
method: 'GET',
209+
url: '/users',
210+
headers: new Headers({
211+
'some-header': 'some-value',
212+
}),
213+
},
214+
clientAddress: '192.168.0.1',
215+
params: {},
216+
url: new URL('https://myDomain.io/users/'),
217+
};
218+
const next = vi.fn(() => nextResult);
189219

190-
// @ts-expect-error, a partial ctx object is fine here
191-
await middleware(ctx, next);
220+
// @ts-expect-error, a partial ctx object is fine here
221+
await middleware(ctx, next);
192222

193-
expect(setSDKProcessingMetadataMock).toHaveBeenCalledWith({
194-
request: {
195-
method: 'GET',
196-
url: '/users',
197-
headers: new Headers({
198-
'some-header': 'some-value',
199-
}),
200-
},
223+
expect(setSDKProcessingMetadataMock).toHaveBeenCalledWith({
224+
request: {
225+
method: 'GET',
226+
url: '/users',
227+
headers: new Headers({
228+
'some-header': 'some-value',
229+
}),
230+
},
231+
});
232+
expect(next).toHaveBeenCalledTimes(1);
233+
});
234+
235+
it("doesn't attach request headers as processing metadata for static page requests", async () => {
236+
const middleware = handleRequest({});
237+
const ctx = {
238+
request: {
239+
method: 'GET',
240+
url: '/users',
241+
headers: new Headers({
242+
'some-header': 'some-value',
243+
}),
244+
},
245+
get clientAddress() {
246+
throw new Error('clientAddress.get() should not be called in static page requests');
247+
},
248+
params: {},
249+
url: new URL('https://myDomain.io/users/'),
250+
};
251+
const next = vi.fn(() => nextResult);
252+
253+
// @ts-expect-error, a partial ctx object is fine here
254+
await middleware(ctx, next);
255+
256+
expect(setSDKProcessingMetadataMock).toHaveBeenCalledWith({
257+
request: {
258+
method: 'GET',
259+
url: '/users',
260+
},
261+
});
262+
expect(next).toHaveBeenCalledTimes(1);
201263
});
202264
});
203265

0 commit comments

Comments
 (0)