feat: add rawHeaders to IncomingMessage (#31853)
* Add response.rawHeaders to docs for IncomingMessage * Remove trailing spaces * Implement raw headers, add tests * Fix lint issues * Add example from NodeJS docs * Fix lint issue in doc example * Add missing #
This commit is contained in:
parent
d1b48c0636
commit
d26d337bb8
4 changed files with 179 additions and 11 deletions
|
@ -80,3 +80,25 @@ An `Integer` indicating the HTTP protocol major version number.
|
|||
An `Integer` indicating the HTTP protocol minor version number.
|
||||
|
||||
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
||||
|
||||
#### `response.rawHeaders`
|
||||
|
||||
A `string[]` containing the raw HTTP response headers exactly as they were
|
||||
received. The keys and values are in the same list. It is not a list of
|
||||
tuples. So, the even-numbered offsets are key values, and the odd-numbered
|
||||
offsets are the associated values. Header names are not lowercased, and
|
||||
duplicates are not merged.
|
||||
|
||||
```javascript
|
||||
// Prints something like:
|
||||
//
|
||||
// [ 'user-agent',
|
||||
// 'this is invalid because there can be only one',
|
||||
// 'User-Agent',
|
||||
// 'curl/7.22.0',
|
||||
// 'Host',
|
||||
// '127.0.0.1:8000',
|
||||
// 'ACCEPT',
|
||||
// '*/*' ]
|
||||
console.log(request.rawHeaders)
|
||||
```
|
||||
|
|
|
@ -61,24 +61,25 @@ class IncomingMessage extends Readable {
|
|||
const filteredHeaders: Record<string, string | string[]> = {};
|
||||
const { rawHeaders } = this._responseHead;
|
||||
rawHeaders.forEach(header => {
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) &&
|
||||
discardableDuplicateHeaders.has(header.key)) {
|
||||
const keyLowerCase = header.key.toLowerCase();
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, keyLowerCase) &&
|
||||
discardableDuplicateHeaders.has(keyLowerCase)) {
|
||||
// do nothing with discardable duplicate headers
|
||||
} else {
|
||||
if (header.key === 'set-cookie') {
|
||||
if (keyLowerCase === 'set-cookie') {
|
||||
// keep set-cookie as an array per Node.js rules
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
|
||||
(filteredHeaders[header.key] as string[]).push(header.value);
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, keyLowerCase)) {
|
||||
(filteredHeaders[keyLowerCase] as string[]).push(header.value);
|
||||
} else {
|
||||
filteredHeaders[header.key] = [header.value];
|
||||
filteredHeaders[keyLowerCase] = [header.value];
|
||||
}
|
||||
} else {
|
||||
// for non-cookie headers, the values are joined together with ', '
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
|
||||
filteredHeaders[header.key] += `, ${header.value}`;
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, keyLowerCase)) {
|
||||
filteredHeaders[keyLowerCase] += `, ${header.value}`;
|
||||
} else {
|
||||
filteredHeaders[header.key] = header.value;
|
||||
filteredHeaders[keyLowerCase] = header.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +87,15 @@ class IncomingMessage extends Readable {
|
|||
return filteredHeaders;
|
||||
}
|
||||
|
||||
get rawHeaders () {
|
||||
const rawHeadersArr: string[] = [];
|
||||
const { rawHeaders } = this._responseHead;
|
||||
rawHeaders.forEach(header => {
|
||||
rawHeadersArr.push(header.key, header.value);
|
||||
});
|
||||
return rawHeadersArr;
|
||||
}
|
||||
|
||||
get httpVersion () {
|
||||
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ struct Converter<network::mojom::HttpRawHeaderPairPtr> {
|
|||
v8::Isolate* isolate,
|
||||
const network::mojom::HttpRawHeaderPairPtr& pair) {
|
||||
gin_helper::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("key", base::ToLowerASCII(pair->key));
|
||||
dict.Set("key", pair->key);
|
||||
dict.Set("value", pair->value);
|
||||
return dict.GetHandle();
|
||||
}
|
||||
|
|
|
@ -1565,6 +1565,11 @@ describe('net module', () => {
|
|||
const headerValue = headers[customHeaderName.toLowerCase()];
|
||||
expect(headerValue).to.equal(customHeaderValue);
|
||||
|
||||
const rawHeaders = response.rawHeaders;
|
||||
expect(rawHeaders).to.be.an('array');
|
||||
expect(rawHeaders[0]).to.equal(customHeaderName);
|
||||
expect(rawHeaders[1]).to.equal(customHeaderValue);
|
||||
|
||||
const httpVersion = response.httpVersion;
|
||||
expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1);
|
||||
|
||||
|
@ -1606,7 +1611,7 @@ describe('net module', () => {
|
|||
await collectStreamBody(response);
|
||||
});
|
||||
|
||||
it('should join repeated non-discardable value with ,', async () => {
|
||||
it('should join repeated non-discardable header values with ,', async () => {
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.statusCode = 200;
|
||||
response.statusMessage = 'OK';
|
||||
|
@ -1626,6 +1631,137 @@ describe('net module', () => {
|
|||
await collectStreamBody(response);
|
||||
});
|
||||
|
||||
it('should not join repeated discardable header values with ,', async () => {
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.statusCode = 200;
|
||||
response.statusMessage = 'OK';
|
||||
response.setHeader('last-modified', ['yesterday', 'today']);
|
||||
response.end();
|
||||
});
|
||||
const urlRequest = net.request(serverUrl);
|
||||
const response = await getResponse(urlRequest);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.statusMessage).to.equal('OK');
|
||||
|
||||
const headers = response.headers;
|
||||
expect(headers).to.be.an('object');
|
||||
expect(headers).to.have.property('last-modified');
|
||||
expect(headers['last-modified']).to.equal('yesterday');
|
||||
|
||||
await collectStreamBody(response);
|
||||
});
|
||||
|
||||
it('should make set-cookie header an array even if single value', async () => {
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.statusCode = 200;
|
||||
response.statusMessage = 'OK';
|
||||
response.setHeader('set-cookie', 'chocolate-chip');
|
||||
response.end();
|
||||
});
|
||||
const urlRequest = net.request(serverUrl);
|
||||
const response = await getResponse(urlRequest);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.statusMessage).to.equal('OK');
|
||||
|
||||
const headers = response.headers;
|
||||
expect(headers).to.be.an('object');
|
||||
expect(headers).to.have.property('set-cookie');
|
||||
expect(headers['set-cookie']).to.be.an('array');
|
||||
expect(headers['set-cookie'][0]).to.equal('chocolate-chip');
|
||||
|
||||
await collectStreamBody(response);
|
||||
});
|
||||
|
||||
it('should keep set-cookie header an array when an array', async () => {
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.statusCode = 200;
|
||||
response.statusMessage = 'OK';
|
||||
response.setHeader('set-cookie', ['chocolate-chip', 'oatmeal']);
|
||||
response.end();
|
||||
});
|
||||
const urlRequest = net.request(serverUrl);
|
||||
const response = await getResponse(urlRequest);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.statusMessage).to.equal('OK');
|
||||
|
||||
const headers = response.headers;
|
||||
expect(headers).to.be.an('object');
|
||||
expect(headers).to.have.property('set-cookie');
|
||||
expect(headers['set-cookie']).to.be.an('array');
|
||||
expect(headers['set-cookie'][0]).to.equal('chocolate-chip');
|
||||
expect(headers['set-cookie'][1]).to.equal('oatmeal');
|
||||
|
||||
await collectStreamBody(response);
|
||||
});
|
||||
|
||||
it('should lowercase header keys', async () => {
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.statusCode = 200;
|
||||
response.statusMessage = 'OK';
|
||||
response.setHeader('HEADER-KEY', ['header-value']);
|
||||
response.setHeader('SeT-CookiE', ['chocolate-chip', 'oatmeal']);
|
||||
response.setHeader('rEFERREr-pOLICy', ['first-text', 'second-text']);
|
||||
response.setHeader('LAST-modified', 'yesterday');
|
||||
|
||||
response.end();
|
||||
});
|
||||
const urlRequest = net.request(serverUrl);
|
||||
const response = await getResponse(urlRequest);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.statusMessage).to.equal('OK');
|
||||
|
||||
const headers = response.headers;
|
||||
expect(headers).to.be.an('object');
|
||||
|
||||
expect(headers).to.have.property('header-key');
|
||||
expect(headers).to.have.property('set-cookie');
|
||||
expect(headers).to.have.property('referrer-policy');
|
||||
expect(headers).to.have.property('last-modified');
|
||||
|
||||
await collectStreamBody(response);
|
||||
});
|
||||
|
||||
it('should return correct raw headers', async () => {
|
||||
const customHeaders: [string, string|string[]][] = [
|
||||
['HEADER-KEY-ONE', 'header-value-one'],
|
||||
['set-cookie', 'chocolate-chip'],
|
||||
['header-key-two', 'header-value-two'],
|
||||
['referrer-policy', ['first-text', 'second-text']],
|
||||
['HEADER-KEY-THREE', 'header-value-three'],
|
||||
['last-modified', ['first-text', 'second-text']],
|
||||
['header-key-four', 'header-value-four']
|
||||
];
|
||||
|
||||
const serverUrl = await respondOnce.toSingleURL((request, response) => {
|
||||
response.statusCode = 200;
|
||||
response.statusMessage = 'OK';
|
||||
customHeaders.forEach((headerTuple) => {
|
||||
response.setHeader(headerTuple[0], headerTuple[1]);
|
||||
});
|
||||
response.end();
|
||||
});
|
||||
const urlRequest = net.request(serverUrl);
|
||||
const response = await getResponse(urlRequest);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.statusMessage).to.equal('OK');
|
||||
|
||||
const rawHeaders = response.rawHeaders;
|
||||
expect(rawHeaders).to.be.an('array');
|
||||
|
||||
let rawHeadersIdx = 0;
|
||||
customHeaders.forEach((headerTuple) => {
|
||||
const headerKey = headerTuple[0];
|
||||
const headerValues = Array.isArray(headerTuple[1]) ? headerTuple[1] : [headerTuple[1]];
|
||||
headerValues.forEach((headerValue) => {
|
||||
expect(rawHeaders[rawHeadersIdx]).to.equal(headerKey);
|
||||
expect(rawHeaders[rawHeadersIdx + 1]).to.equal(headerValue);
|
||||
rawHeadersIdx += 2;
|
||||
});
|
||||
});
|
||||
|
||||
await collectStreamBody(response);
|
||||
});
|
||||
|
||||
it('should be able to pipe a net response into a writable stream', async () => {
|
||||
const bodyData = randomString(kOneKiloByte);
|
||||
let nodeRequestProcessed = false;
|
||||
|
|
Loading…
Reference in a new issue