fix: match net module headers & http.IncomingMessage headers (#17517)
* fix: match net module headers & http.IncomingMessage headers * update net doc for cleanliness * address feedback from review * Update spec/api-net-spec.js Co-Authored-By: codebytere <codebytere@github.com> * add special cookie case
This commit is contained in:
parent
7c6cedb119
commit
8ea33d69ac
3 changed files with 145 additions and 30 deletions
|
@ -21,13 +21,10 @@ module instead of the native Node.js modules:
|
||||||
* Support for traffic monitoring proxies: Fiddler-like proxies used for access
|
* Support for traffic monitoring proxies: Fiddler-like proxies used for access
|
||||||
control and monitoring.
|
control and monitoring.
|
||||||
|
|
||||||
The `net` module API has been specifically designed to mimic, as closely as
|
The API components (including classes, methods, properties and event names) are similar to those used in
|
||||||
possible, the familiar Node.js API. The API components including classes,
|
|
||||||
methods, properties and event names are similar to those commonly used in
|
|
||||||
Node.js.
|
Node.js.
|
||||||
|
|
||||||
For instance, the following example quickly shows how the `net` API might be
|
Example usage:
|
||||||
used:
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const { app } = require('electron')
|
const { app } = require('electron')
|
||||||
|
@ -48,10 +45,6 @@ app.on('ready', () => {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
By the way, it is almost identical to how you would normally use the
|
|
||||||
[HTTP](https://nodejs.org/api/http.html)/[HTTPS](https://nodejs.org/api/https.html)
|
|
||||||
modules of Node.js
|
|
||||||
|
|
||||||
The `net` API can be used only after the application emits the `ready` event.
|
The `net` API can be used only after the application emits the `ready` event.
|
||||||
Trying to use the module before the `ready` event will throw an error.
|
Trying to use the module before the `ready` event will throw an error.
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,29 @@ Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype)
|
||||||
|
|
||||||
const kSupportedProtocols = new Set(['http:', 'https:'])
|
const kSupportedProtocols = new Set(['http:', 'https:'])
|
||||||
|
|
||||||
|
// set of headers that Node.js discards duplicates for
|
||||||
|
// see https://nodejs.org/api/http.html#http_message_headers
|
||||||
|
const discardableDuplicateHeaders = new Set([
|
||||||
|
'content-type',
|
||||||
|
'content-length',
|
||||||
|
'user-agent',
|
||||||
|
'referer',
|
||||||
|
'host',
|
||||||
|
'authorization',
|
||||||
|
'proxy-authorization',
|
||||||
|
'if-modified-since',
|
||||||
|
'if-unmodified-since',
|
||||||
|
'from',
|
||||||
|
'location',
|
||||||
|
'max-forwards',
|
||||||
|
'retry-after',
|
||||||
|
'etag',
|
||||||
|
'last-modified',
|
||||||
|
'server',
|
||||||
|
'age',
|
||||||
|
'expires'
|
||||||
|
])
|
||||||
|
|
||||||
class IncomingMessage extends Readable {
|
class IncomingMessage extends Readable {
|
||||||
constructor (urlRequest) {
|
constructor (urlRequest) {
|
||||||
super()
|
super()
|
||||||
|
@ -41,7 +64,23 @@ class IncomingMessage extends Readable {
|
||||||
}
|
}
|
||||||
|
|
||||||
get headers () {
|
get headers () {
|
||||||
return this.urlRequest.rawResponseHeaders
|
const filteredHeaders = {}
|
||||||
|
const rawHeaders = this.urlRequest.rawResponseHeaders
|
||||||
|
Object.keys(rawHeaders).forEach(header => {
|
||||||
|
if (header in filteredHeaders && discardableDuplicateHeaders.has(header)) {
|
||||||
|
// do nothing with discardable duplicate headers
|
||||||
|
} else {
|
||||||
|
if (header === 'set-cookie') {
|
||||||
|
// keep set-cookie as an array per Node.js rules
|
||||||
|
// see https://nodejs.org/api/http.html#http_message_headers
|
||||||
|
filteredHeaders[header] = rawHeaders[header]
|
||||||
|
} else {
|
||||||
|
// for non-cookie headers, the values are joined together with ', '
|
||||||
|
filteredHeaders[header] = rawHeaders[header].join(', ')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return filteredHeaders
|
||||||
}
|
}
|
||||||
|
|
||||||
get httpVersion () {
|
get httpVersion () {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const assert = require('assert')
|
const assert = require('assert')
|
||||||
|
const { expect } = require('chai')
|
||||||
const { remote } = require('electron')
|
const { remote } = require('electron')
|
||||||
const { ipcRenderer } = require('electron')
|
const { ipcRenderer } = require('electron')
|
||||||
const http = require('http')
|
const http = require('http')
|
||||||
|
@ -1363,6 +1364,7 @@ describe('net module', () => {
|
||||||
const requestUrl = '/requestUrl'
|
const requestUrl = '/requestUrl'
|
||||||
const customHeaderName = 'Some-Custom-Header-Name'
|
const customHeaderName = 'Some-Custom-Header-Name'
|
||||||
const customHeaderValue = 'Some-Customer-Header-Value'
|
const customHeaderValue = 'Some-Customer-Header-Value'
|
||||||
|
|
||||||
server.on('request', (request, response) => {
|
server.on('request', (request, response) => {
|
||||||
switch (request.url) {
|
switch (request.url) {
|
||||||
case requestUrl:
|
case requestUrl:
|
||||||
|
@ -1375,36 +1377,117 @@ describe('net module', () => {
|
||||||
handleUnexpectedURL(request, response)
|
handleUnexpectedURL(request, response)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const urlRequest = net.request({
|
const urlRequest = net.request({
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${server.url}${requestUrl}`
|
url: `${server.url}${requestUrl}`
|
||||||
})
|
})
|
||||||
|
|
||||||
urlRequest.on('response', (response) => {
|
urlRequest.on('response', (response) => {
|
||||||
const statusCode = response.statusCode
|
expect(response.statusCode).to.equal(200)
|
||||||
assert(typeof statusCode === 'number')
|
expect(response.statusMessage).to.equal('OK')
|
||||||
assert.strictEqual(statusCode, 200)
|
|
||||||
const statusMessage = response.statusMessage
|
|
||||||
assert(typeof statusMessage === 'string')
|
|
||||||
assert.strictEqual(statusMessage, 'OK')
|
|
||||||
const headers = response.headers
|
const headers = response.headers
|
||||||
assert(typeof headers === 'object')
|
expect(headers).to.be.an('object')
|
||||||
assert.deepStrictEqual(headers[customHeaderName.toLowerCase()],
|
const headerValue = headers[customHeaderName.toLowerCase()]
|
||||||
[customHeaderValue])
|
expect(headerValue).to.equal(customHeaderValue)
|
||||||
|
|
||||||
const httpVersion = response.httpVersion
|
const httpVersion = response.httpVersion
|
||||||
assert(typeof httpVersion === 'string')
|
expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1)
|
||||||
assert(httpVersion.length > 0)
|
|
||||||
const httpVersionMajor = response.httpVersionMajor
|
const httpVersionMajor = response.httpVersionMajor
|
||||||
assert(typeof httpVersionMajor === 'number')
|
expect(httpVersionMajor).to.be.a('number').and.to.be.at.least(1)
|
||||||
assert(httpVersionMajor >= 1)
|
|
||||||
const httpVersionMinor = response.httpVersionMinor
|
const httpVersionMinor = response.httpVersionMinor
|
||||||
assert(typeof httpVersionMinor === 'number')
|
expect(httpVersionMinor).to.be.a('number').and.to.be.at.least(0)
|
||||||
assert(httpVersionMinor >= 0)
|
|
||||||
response.pause()
|
response.pause()
|
||||||
response.on('data', (chunk) => {
|
response.on('data', chunk => {})
|
||||||
|
response.on('end', () => { done() })
|
||||||
|
response.resume()
|
||||||
})
|
})
|
||||||
response.on('end', () => {
|
urlRequest.end()
|
||||||
done()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should discard duplicate headers', (done) => {
|
||||||
|
const requestUrl = '/duplicateRequestUrl'
|
||||||
|
const includedHeader = 'max-forwards'
|
||||||
|
const discardableHeader = 'Max-Forwards'
|
||||||
|
|
||||||
|
const includedHeaderValue = 'max-fwds-val'
|
||||||
|
const discardableHeaderValue = 'max-fwds-val-two'
|
||||||
|
|
||||||
|
server.on('request', (request, response) => {
|
||||||
|
switch (request.url) {
|
||||||
|
case requestUrl:
|
||||||
|
response.statusCode = 200
|
||||||
|
response.statusMessage = 'OK'
|
||||||
|
response.setHeader(discardableHeader, discardableHeaderValue)
|
||||||
|
response.setHeader(includedHeader, includedHeaderValue)
|
||||||
|
response.end()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
handleUnexpectedURL(request, response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const urlRequest = net.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${server.url}${requestUrl}`
|
||||||
|
})
|
||||||
|
|
||||||
|
urlRequest.on('response', response => {
|
||||||
|
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(includedHeader)
|
||||||
|
expect(headers).to.not.have.property(discardableHeader)
|
||||||
|
expect(headers[includedHeader]).to.equal(includedHeaderValue)
|
||||||
|
|
||||||
|
response.pause()
|
||||||
|
response.on('data', chunk => {})
|
||||||
|
response.on('end', () => { done() })
|
||||||
|
response.resume()
|
||||||
|
})
|
||||||
|
urlRequest.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should join repeated non-discardable value with ,', (done) => {
|
||||||
|
const requestUrl = '/requestUrl'
|
||||||
|
|
||||||
|
server.on('request', (request, response) => {
|
||||||
|
switch (request.url) {
|
||||||
|
case requestUrl:
|
||||||
|
response.statusCode = 200
|
||||||
|
response.statusMessage = 'OK'
|
||||||
|
response.setHeader('referrer-policy', ['first-text', 'second-text'])
|
||||||
|
response.end()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
handleUnexpectedURL(request, response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const urlRequest = net.request({
|
||||||
|
method: 'GET',
|
||||||
|
url: `${server.url}${requestUrl}`
|
||||||
|
})
|
||||||
|
|
||||||
|
urlRequest.on('response', response => {
|
||||||
|
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.a.property('referrer-policy')
|
||||||
|
expect(headers['referrer-policy']).to.equal('first-text, second-text')
|
||||||
|
|
||||||
|
response.pause()
|
||||||
|
response.on('data', chunk => {})
|
||||||
|
response.on('end', () => { done() })
|
||||||
response.resume()
|
response.resume()
|
||||||
})
|
})
|
||||||
urlRequest.end()
|
urlRequest.end()
|
||||||
|
|
Loading…
Reference in a new issue