Skip to content

Commit 2268afd

Browse files
authored
Merge pull request #7811 from maiieul/revert-redirect-caching
Revert "fix: solve issue with Cache-Control header deletion (#6991)"
2 parents de7b96c + a6968af commit 2268afd

File tree

3 files changed

+102
-2
lines changed

3 files changed

+102
-2
lines changed

.changeset/free-views-boil.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@builder.io/qwik-city': patch
3+
---
4+
5+
FIX: redirects no longer take their parent layout's Cache-Control value by default and are instead set to `no-store`. This prevents issues in redirection logic. We might introduce another API to enable caching redirects in the future.

packages/qwik-city/src/middleware/request-handler/request-event.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ export function createRequestEvent(
228228

229229
error: <T = any>(statusCode: number, message: T) => {
230230
status = statusCode;
231+
headers.delete('Cache-Control');
231232
return new ServerError(statusCode, message);
232233
},
233234

@@ -241,8 +242,8 @@ export function createRequestEvent(
241242
}
242243
headers.set('Location', fixedURL);
243244
}
244-
// Fallback to 'no-store' when end user is not managing Cache-Control header
245-
if (statusCode > 301 && !headers.get('Cache-Control')) {
245+
headers.delete('Cache-Control');
246+
if (statusCode > 301) {
246247
headers.set('Cache-Control', 'no-store');
247248
}
248249
exit();
@@ -265,6 +266,7 @@ export function createRequestEvent(
265266
fail: <T extends Record<string, any>>(statusCode: number, data: T): FailReturn<T> => {
266267
check();
267268
status = statusCode;
269+
headers.delete('Cache-Control');
268270
return {
269271
failed: true,
270272
...data,
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import { describe, it, expect, vi } from 'vitest';
2+
import { createRequestEvent } from './request-event';
3+
import { RedirectMessage } from './redirect-handler';
4+
import type { ServerRequestEvent, QwikSerializer } from './types';
5+
6+
const mockQwikSerializer: QwikSerializer = {
7+
_deserializeData: vi.fn(),
8+
_serializeData: vi.fn(),
9+
_verifySerializable: vi.fn(),
10+
};
11+
12+
function createMockServerRequestEvent(url = 'http://localhost:3000/test'): ServerRequestEvent {
13+
const mockRequest = new Request(url);
14+
15+
return {
16+
mode: 'server',
17+
url: new URL(url),
18+
locale: undefined,
19+
platform: {},
20+
request: mockRequest,
21+
env: {
22+
get: vi.fn(),
23+
},
24+
getClientConn: vi.fn(() => ({ ip: '127.0.0.1' })),
25+
getWritableStream: vi.fn(() => {
26+
const writer = {
27+
write: vi.fn(),
28+
close: vi.fn(),
29+
};
30+
return {
31+
getWriter: () => writer,
32+
locked: false,
33+
pipeTo: vi.fn(),
34+
} as any;
35+
}),
36+
};
37+
}
38+
39+
function createMockRequestEvent(url = 'http://localhost:3000/test') {
40+
const serverRequestEv = createMockServerRequestEvent(url);
41+
return createRequestEvent(serverRequestEv, null, [], true, '/', mockQwikSerializer, vi.fn());
42+
}
43+
44+
describe('request-event redirect', () => {
45+
it('should not cache redirects by default', () => {
46+
const requestEv = createMockRequestEvent();
47+
48+
requestEv.headers.set('Cache-Control', 'max-age=3600, public');
49+
50+
const result = requestEv.redirect(301, '/new-location');
51+
52+
expect(result).toBeInstanceOf(RedirectMessage);
53+
expect(requestEv.headers.get('Location')).toBe('/new-location');
54+
expect(requestEv.headers.get('Cache-Control')).toBeNull();
55+
expect(requestEv.status()).toBe(301);
56+
});
57+
58+
it('should set Cache-Control to no-store for redirects with status > 301', () => {
59+
const requestEv = createMockRequestEvent();
60+
61+
const result = requestEv.redirect(307, '/new-location');
62+
63+
expect(result).toBeInstanceOf(RedirectMessage);
64+
expect(requestEv.headers.get('Location')).toBe('/new-location');
65+
expect(requestEv.headers.get('Cache-Control')).toBe('no-store');
66+
expect(requestEv.status()).toBe(307);
67+
});
68+
69+
it('should fix invalid redirect URLs with multiple slashes', () => {
70+
const requestEv = createMockRequestEvent();
71+
72+
const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
73+
74+
const result = requestEv.redirect(302, '/path//with///multiple////slashes');
75+
76+
expect(result).toBeInstanceOf(RedirectMessage);
77+
expect(requestEv.headers.get('Location')).toBe('/path/with/multiple/slashes');
78+
expect(consoleSpy).toHaveBeenCalledWith(
79+
'Redirect URL /path//with///multiple////slashes is invalid, fixing to /path/with/multiple/slashes'
80+
);
81+
});
82+
83+
it('should throw error when trying to redirect after headers are sent', () => {
84+
const requestEv = createMockRequestEvent();
85+
86+
// Trigger getWritableStream to simulate headers being sent
87+
requestEv.getWritableStream();
88+
89+
expect(() => {
90+
requestEv.redirect(302, '/should-fail');
91+
}).toThrow('Response already sent');
92+
});
93+
});

0 commit comments

Comments
 (0)