diff --git a/src/client/network.ts b/src/client/network.ts index c044b160c5..f8cf379fe9 100644 --- a/src/client/network.ts +++ b/src/client/network.ts @@ -555,6 +555,10 @@ export class FetchResponse { return { ...this._headers }; } + headersArray(): string[][] { + return this._initializer.headers.map(({name, value}) => [name, value]); + } + async body(): Promise { return this._context._wrapApiCall(async (channel: channels.BrowserContextChannel) => { const result = await channel.fetchResponseBody({ fetchUid: this._fetchUid() }); diff --git a/src/server/fetch.ts b/src/server/fetch.ts index f1b008e56c..1714806f8f 100644 --- a/src/server/fetch.ts +++ b/src/server/fetch.ts @@ -196,7 +196,7 @@ async function sendRequest(context: BrowserContext, url: URL, options: https.Req url: response.url || url.toString(), status: response.statusCode || 0, statusText: response.statusMessage || '', - headers: flattenHeaders(response.headers), + headers: toHeadersArray(response.rawHeaders), body }); }); @@ -219,18 +219,10 @@ async function sendRequest(context: BrowserContext, url: URL, options: https.Req }); } -function flattenHeaders(headers: http.IncomingHttpHeaders): types.HeadersArray { +function toHeadersArray(rawHeaders: string[]): types.HeadersArray { const result: types.HeadersArray = []; - for (const [name, values] of Object.entries(headers)) { - if (values === undefined) - continue; - if (typeof values === 'string') { - result.push({name, value: values as string}); - } else { - for (const value of values) - result.push({name, value}); - } - } + for (let i = 0; i < rawHeaders.length; i += 2) + result.push({ name: rawHeaders[i], value: rawHeaders[i + 1] }); return result; } diff --git a/tests/browsercontext-fetch.spec.ts b/tests/browsercontext-fetch.spec.ts index a63a9e6c0a..379f6775f4 100644 --- a/tests/browsercontext-fetch.spec.ts +++ b/tests/browsercontext-fetch.spec.ts @@ -18,7 +18,6 @@ import http from 'http'; import zlib from 'zlib'; import { pipeline } from 'stream'; import { contextTest as it, expect } from './config/browserTest'; -import type { Response } from '..'; import { suppressCertificateWarning } from './config/utils'; it.skip(({ mode }) => mode !== 'default'); @@ -43,13 +42,14 @@ it.afterAll(() => { it('should work', async ({context, server}) => { // @ts-expect-error - const response: Response = await context._fetch(server.PREFIX + '/simple.json'); + const response = await context._fetch(server.PREFIX + '/simple.json'); expect(response.url()).toBe(server.PREFIX + '/simple.json'); expect(response.status()).toBe(200); expect(response.statusText()).toBe('OK'); expect(response.ok()).toBeTruthy(); expect(response.url()).toBe(server.PREFIX + '/simple.json'); expect(response.headers()['content-type']).toBe('application/json; charset=utf-8'); + expect(response.headersArray()).toContainEqual(['Content-Type', 'application/json; charset=utf-8']); expect(await response.text()).toBe('{"foo": "bar"}\n'); }); @@ -264,6 +264,30 @@ it('should handle cookies on redirects', async ({context, server, browserName, i ])); }); +it('should return raw headers', async ({context, page, server}) => { + server.setRoute('/headers', (req, res) => { + // Headers array is only supported since Node v14.14.0 so we write directly to the socket. + // res.writeHead(200, ['name-a', 'v1','name-b', 'v4','Name-a', 'v2', 'name-A', 'v3']); + const conn = res.connection; + conn.write('HTTP/1.1 200 OK\r\n'); + conn.write('Name-A: v1\r\n'); + conn.write('name-b: v4\r\n'); + conn.write('Name-a: v2\r\n'); + conn.write('name-A: v3\r\n'); + conn.write('\r\n'); + conn.uncork(); + conn.end(); + }); + // @ts-expect-error + const response = await context._fetch(`${server.PREFIX}/headers`); + expect(response.status()).toBe(200); + const headers = response.headersArray().filter(([name, value]) => name.toLowerCase().includes('name-')); + expect(headers).toEqual([['Name-A', 'v1'], ['name-b', 'v4'], ['Name-a', 'v2'], ['name-A', 'v3']]); + // Last value wins, this matches Response.headers() + expect(response.headers()['name-a']).toBe('v3'); + expect(response.headers()['name-b']).toBe('v4'); +}); + it('should work with context level proxy', async ({browserOptions, browserType, contextOptions, server, proxyServer}) => { server.setRoute('/target.html', async (req, res) => { res.end('Served by the proxy');