Skip to content

Commit a480edb

Browse files
authored
Merge pull request #1591 from truehazker/bugfix/set-headers-merge-conflict
🔧 fix: merge set.headers without duplicating Response
2 parents ff313a2 + 2fce4b7 commit a480edb

File tree

2 files changed

+38
-5
lines changed

2 files changed

+38
-5
lines changed

src/adapter/utils.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,7 @@ export const createResponseHandler = (handler: CreateHandlerParameter) => {
338338
return (response: Response, set: Context['set'], request?: Request) => {
339339
let isCookieSet = false
340340

341+
// Merge headers: Response headers take precedence, set.headers fill in non-conflicting ones
341342
if (set.headers instanceof Headers)
342343
for (const key of set.headers.keys()) {
343344
if (key === 'set-cookie') {
@@ -347,14 +348,21 @@ export const createResponseHandler = (handler: CreateHandlerParameter) => {
347348

348349
for (const cookie of set.headers.getSetCookie())
349350
response.headers.append('set-cookie', cookie)
350-
} else response.headers.append(key, set.headers?.get(key) ?? '')
351+
} else if (!response.headers.has(key))
352+
response.headers.set(key, set.headers?.get(key) ?? '')
351353
}
352354
else
353355
for (const key in set.headers)
354-
(response as Response).headers.append(
355-
key,
356-
set.headers[key] as any
357-
)
356+
if (key === 'set-cookie')
357+
(response as Response).headers.append(
358+
key,
359+
set.headers[key] as any
360+
)
361+
else if (!response.headers.has(key))
362+
(response as Response).headers.set(
363+
key,
364+
set.headers[key] as any
365+
)
358366

359367
const status = set.status ?? 200
360368

test/response/custom-response.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,29 @@ describe('Custom Response Type', () => {
3636

3737
expect(await response.text()).toBe('Shuba Shuba')
3838
})
39+
40+
it('Response headers take precedence, set.headers merge non-conflicting', async () => {
41+
const app = new Elysia()
42+
.onRequest(({ set }) => {
43+
set.headers['Content-Type'] = 'application/json'
44+
set.headers['X-Framework'] = 'Elysia'
45+
})
46+
.get('/', () => {
47+
return new Response('{"message":"hello"}', {
48+
headers: {
49+
'Content-Type': 'text/plain',
50+
'X-Custom': 'custom-value'
51+
}
52+
})
53+
})
54+
55+
const response = await app.handle(req('/'))
56+
57+
// Response's Content-Type takes precedence
58+
expect(response.headers.get('Content-Type')).toBe('text/plain')
59+
// set.headers adds non-conflicting headers
60+
expect(response.headers.get('X-Framework')).toBe('Elysia')
61+
// Response's own headers are preserved
62+
expect(response.headers.get('X-Custom')).toBe('custom-value')
63+
})
3964
})

0 commit comments

Comments
 (0)