Skip to content

Commit

Permalink
fix: ERR_HTTP2_HEADER_SINGLE_VALUE error
Browse files Browse the repository at this point in the history
  • Loading branch information
divdavem committed Nov 27, 2024
1 parent 3e8a9a4 commit ecc82c0
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 3 deletions.
12 changes: 9 additions & 3 deletions packages/app/server/response/impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ServerResponse, IncomingHttpHeaders } from 'http';

// ---------------------------------------------------------------------- common

import { headersContainer } from '../../../lib/headers';
import { headersContainer, singleValueHeaders } from '../../../lib/headers';
import { stringifyPretty } from '../../../lib/json';
import { UserProperty } from '../../../lib/user-property';
import { logError } from '../../logger';
Expand Down Expand Up @@ -104,21 +104,27 @@ export class Response implements IResponse {
code = CONF.defaultStatusCode;
}
const { message } = status;
const http2 = response instanceof Http2ServerResponse;

Object.entries(headers)
.map(([key, value]) => ({ key, value }))
.filter((header) => header.value != null)
.forEach((header) => {
try {
response.setHeader(header.key, header.value!);
const { key, value } = header;
if (http2 && Array.isArray(value) && singleValueHeaders.has(key.toLowerCase())) {
response.setHeader(key, value[0]);
} else {
response.setHeader(key, value!);
}
} catch (exception) {
logError({
message: `${CONF.messages.setHeaderError}\n${JSON.stringify(header)}`,
exception,
});
}
});
if (response instanceof Http2ServerResponse) {
if (http2) {
response.writeHead(code);
} else {
response.writeHead(code, message);
Expand Down
49 changes: 49 additions & 0 deletions packages/e2e/use-cases.js
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,55 @@ const useCases = [
},
},

{
name: 'http2-header-single-value',
iterations: 1,

async nodeRequest({ proxyPort, iteration }) {
const http2 = require('http2');
const connection = http2.connect(
`https://localhost:${proxyPort}`,
{ rejectUnauthorized: false },
);
const request = connection.request({
[http2.constants.HTTP2_HEADER_METHOD]: 'GET',
[http2.constants.HTTP2_HEADER_PATH]: '/',
});
request.end();
const response = await new Promise((resolve) => request.on('response', resolve));
let data = '';
request.on('data', (chunk) => (data += chunk.toString('utf8')));
await new Promise((resolve) => request.on('end', resolve));
await new Promise((resolve) => connection.close(resolve));
return {
proxyPort,
data,
headers: response,
};
},

serve: async ({ req, response }) => {
response.set('Content-Type', 'text/plain');
response.set('X-Content-Type-Options', ['nosniff', 'nosniff']);
response.set('Access-Control-Allow-Origin', '*');
response.status = 200;
response.body = 'ok';
},

proxy: async ({ mock }) => {
mock.setMode('remote');
},

defineAssertions: ({ it, getData, expect }) => {
it('checks response headers', () => {
const { data } = getData(0);
expect(data.client.data).to.equal('ok');
console.log(data.client.headers);
expect(data.client.headers['x-content-type-options']).to.equal('nosniff');
});
},
},

{
name: 'http2-client',
description: 'http2 client test',
Expand Down
44 changes: 44 additions & 0 deletions packages/lib/headers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,49 @@
import { IncomingHttpHeaders } from 'http';

// copied from https://github.com/nodejs/node/blob/db8ff56629e74e8c997947b8d3960db64c1ce4f9/lib/internal/http2/util.js#L113C1-L154
export const singleValueHeaders = new Set([
':status',
':method',
':authority',
':scheme',
':path',
':protocol',
'access-control-allow-credentials',
'access-control-max-age',
'access-control-request-method',
'age',
'authorization',
'content-encoding',
'content-language',
'content-length',
'content-location',
'content-md5',
'content-range',
'content-type',
'date',
'dnt',
'etag',
'expires',
'from',
'host',
'if-match',
'if-modified-since',
'if-none-match',
'if-range',
'if-unmodified-since',
'last-modified',
'location',
'max-forwards',
'proxy-authorization',
'range',
'referer',
'retry-after',
'tk',
'upgrade-insecure-requests',
'user-agent',
'x-content-type-options',
]);

/**
* A map from strings to strings or array of strings
*
Expand Down

0 comments on commit ecc82c0

Please sign in to comment.