diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 6da12dea6b10..379245b4c9bf 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -96,11 +96,11 @@ class IncomingMessage extends Readable { } get rawTrailers () { - throw new Error('HTTP trailers are not supported.') + throw new Error('HTTP trailers are not supported') } get trailers () { - throw new Error('HTTP trailers are not supported.') + throw new Error('HTTP trailers are not supported') } _storeInternalData (chunk) { @@ -161,7 +161,7 @@ class ClientRequest extends EventEmitter { const urlObj = {} const protocol = options.protocol || 'http:' if (!kSupportedProtocols.has(protocol)) { - throw new Error('Protocol "' + protocol + '" not supported. ') + throw new Error('Protocol "' + protocol + '" not supported') } urlObj.protocol = protocol @@ -186,7 +186,7 @@ class ClientRequest extends EventEmitter { // well, and b) possibly too restrictive for real-world usage. That's // why it only scans for spaces because those are guaranteed to create // an invalid request. - throw new TypeError('Request path contains unescaped characters.') + throw new TypeError('Request path contains unescaped characters') } const pathObj = url.parse(options.path || '/') urlObj.pathname = pathObj.pathname @@ -209,13 +209,13 @@ class ClientRequest extends EventEmitter { if (options.session instanceof Session) { urlRequestOptions.session = options.session } else { - throw new TypeError('`session` should be an instance of the Session class.') + throw new TypeError('`session` should be an instance of the Session class') } } else if (options.partition) { if (typeof options.partition === 'string') { urlRequestOptions.partition = options.partition } else { - throw new TypeError('`partition` should be an a string.') + throw new TypeError('`partition` should be a string') } } @@ -276,20 +276,20 @@ class ClientRequest extends EventEmitter { set chunkedEncoding (value) { if (!this.urlRequest.notStarted) { - throw new Error('Can\'t set the transfer encoding, headers have been sent.') + throw new Error('Can\'t set the transfer encoding, headers have been sent') } this.chunkedEncodingEnabled = value } setHeader (name, value) { if (typeof name !== 'string') { - throw new TypeError('`name` should be a string in setHeader(name, value).') + throw new TypeError('`name` should be a string in setHeader(name, value)') } if (value == null) { - throw new Error('`value` required in setHeader("' + name + '", value).') + throw new Error('`value` required in setHeader("' + name + '", value)') } if (!this.urlRequest.notStarted) { - throw new Error('Can\'t set headers after they are sent.') + throw new Error('Can\'t set headers after they are sent') } const key = name.toLowerCase() @@ -299,7 +299,7 @@ class ClientRequest extends EventEmitter { getHeader (name) { if (name == null) { - throw new Error('`name` is required for getHeader(name).') + throw new Error('`name` is required for getHeader(name)') } if (!this.extraHeaders) { @@ -312,11 +312,11 @@ class ClientRequest extends EventEmitter { removeHeader (name) { if (name == null) { - throw new Error('`name` is required for removeHeader(name).') + throw new Error('`name` is required for removeHeader(name)') } if (!this.urlRequest.notStarted) { - throw new Error('Can\'t remove headers after they are sent.') + throw new Error('Can\'t remove headers after they are sent') } const key = name.toLowerCase() @@ -328,7 +328,7 @@ class ClientRequest extends EventEmitter { const chunkIsString = typeof chunk === 'string' const chunkIsBuffer = chunk instanceof Buffer if (!chunkIsString && !chunkIsBuffer) { - throw new TypeError('First argument must be a string or Buffer.') + throw new TypeError('First argument must be a string or Buffer') } if (chunkIsString) { @@ -357,7 +357,7 @@ class ClientRequest extends EventEmitter { write (data, encoding, callback) { if (this.urlRequest.finished) { - const error = new Error('Write after end.') + const error = new Error('Write after end') process.nextTick(writeAfterEndNT, this, error, callback) return true } diff --git a/spec-main/api-net-spec.ts b/spec-main/api-net-spec.ts new file mode 100644 index 000000000000..c1863225d8a7 --- /dev/null +++ b/spec-main/api-net-spec.ts @@ -0,0 +1,1289 @@ +import { expect } from 'chai' +import { net, session, ClientRequest } from 'electron' +import * as http from 'http' +import * as url from 'url' +import { AddressInfo } from 'net' + +const kOneKiloByte = 1024 +const kOneMegaByte = kOneKiloByte * kOneKiloByte + +function randomBuffer (size: number, start: number = 0, end: number = 255) { + const range = 1 + end - start + const buffer = Buffer.allocUnsafe(size) + for (let i = 0; i < size; ++i) { + buffer[i] = start + Math.floor(Math.random() * range) + } + return buffer +} + +function randomString (length: number) { + const buffer = randomBuffer(length, '0'.charCodeAt(0), 'z'.charCodeAt(0)) + return buffer.toString() +} + +function respondOnce(fn: http.RequestListener): Promise { + return new Promise((resolve) => { + const server = http.createServer((request, response) => { + fn(request, response) + // don't close if a redirect was returned + if (response.statusCode < 300 || response.statusCode >= 399) + server.close() + }) + server.listen(0, '127.0.0.1', () => { + resolve(`http://127.0.0.1:${(server.address() as AddressInfo).port}`) + }) + }) +} + +respondOnce.toRoutes = (routes: Record) => { + return respondOnce((request, response) => { + if (routes.hasOwnProperty(request.url || '')) { + routes[request.url || ''](request, response) + } else { + response.statusCode = 500 + response.end() + expect.fail(`Unexpected URL: ${request.url}`) + } + }) +} + +respondOnce.toURL = (url: string, fn: http.RequestListener) => { + return respondOnce.toRoutes({[url]: fn}) +} + +respondOnce.toSingleURL = (fn: http.RequestListener) => { + const requestUrl = '/requestUrl' + return respondOnce.toURL(requestUrl, fn).then(url => `${url}${requestUrl}`) +} + +describe('net module', () => { + describe('HTTP basics', () => { + it('should be able to issue a basic GET request', (done) => { + respondOnce.toSingleURL((request, response) => { + expect(request.method).to.equal('GET') + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.end() + }) + }) + + it('should be able to issue a basic POST request', (done) => { + respondOnce.toSingleURL((request, response) => { + expect(request.method).to.equal('POST') + response.end() + }).then(serverUrl => { + const urlRequest = net.request({ + method: 'POST', + url: serverUrl + }) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + response.on('data', () => { }) + response.on('end', () => { + done() + }) + }) + urlRequest.end() + }) + }) + + it('should fetch correct data in a GET request', (done) => { + const bodyData = 'Hello World!' + respondOnce.toSingleURL((request, response) => { + expect(request.method).to.equal('GET') + response.end(bodyData) + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + let expectedBodyData = '' + expect(response.statusCode).to.equal(200) + response.on('data', (chunk) => { + expectedBodyData += chunk.toString() + }) + response.on('end', () => { + expect(expectedBodyData).to.equal(bodyData) + done() + }) + }) + urlRequest.end() + }) + }) + + it('should post the correct data in a POST request', (done) => { + const bodyData = 'Hello World!' + respondOnce.toSingleURL((request, response) => { + let postedBodyData = '' + expect(request.method).to.equal('POST') + request.on('data', (chunk: Buffer) => { + postedBodyData += chunk.toString() + }) + request.on('end', () => { + expect(postedBodyData).to.equal(bodyData) + response.end() + }) + }).then(serverUrl => { + const urlRequest = net.request({ + method: 'POST', + url: serverUrl + }) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.write(bodyData) + urlRequest.end() + }) + }) + + it('should support chunked encoding', (done) => { + respondOnce.toSingleURL((request, response) => { + response.statusCode = 200 + response.statusMessage = 'OK' + response.chunkedEncoding = true + expect(request.method).to.equal('POST') + expect(request.headers['transfer-encoding']).to.equal('chunked') + expect(request.headers['content-length']).to.equal(undefined) + request.on('data', (chunk: Buffer) => { + response.write(chunk) + }) + request.on('end', (chunk: Buffer) => { + response.end(chunk) + }) + }).then(serverUrl => { + const urlRequest = net.request({ + method: 'POST', + url: serverUrl + }) + + let chunkIndex = 0 + const chunkCount = 100 + const sentChunks: Array = [] + const receivedChunks: Array = [] + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + response.on('data', (chunk) => { + receivedChunks.push(chunk) + }) + response.on('end', () => { + const sentData = Buffer.concat(sentChunks) + const receivedData = Buffer.concat(receivedChunks) + expect(sentData.toString()).to.equal(receivedData.toString()) + expect(chunkIndex).to.be.equal(chunkCount) + done() + }) + }) + urlRequest.chunkedEncoding = true + while (chunkIndex < chunkCount) { + chunkIndex += 1 + const chunk = randomBuffer(kOneKiloByte) + sentChunks.push(chunk) + expect(urlRequest.write(chunk)).to.equal(true) + } + urlRequest.end() + }) + }) + }) + + describe('ClientRequest API', () => { + it('request/response objects should emit expected events', (done) => { + const bodyData = randomString(kOneKiloByte) + respondOnce.toSingleURL((request, response) => { + response.end(bodyData) + }).then(serverUrl => { + let requestResponseEventEmitted = false + let requestFinishEventEmitted = false + let requestCloseEventEmitted = false + let responseDataEventEmitted = false + let responseEndEventEmitted = false + + function maybeDone (done: () => void) { + if (!requestCloseEventEmitted || !responseEndEventEmitted) { + return + } + + expect(requestResponseEventEmitted).to.equal(true) + expect(requestFinishEventEmitted).to.equal(true) + expect(requestCloseEventEmitted).to.equal(true) + expect(responseDataEventEmitted).to.equal(true) + expect(responseEndEventEmitted).to.equal(true) + done() + } + + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + requestResponseEventEmitted = true + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + const buffers: Buffer[] = [] + response.on('data', (chunk) => { + buffers.push(chunk) + responseDataEventEmitted = true + }) + response.on('end', () => { + const receivedBodyData = Buffer.concat(buffers) + expect(receivedBodyData.toString()).to.equal(bodyData) + responseEndEventEmitted = true + maybeDone(done) + }) + response.on('error', (error: Error) => { + expect(error).to.be.an('Error') + }) + response.on('aborted', () => { + expect.fail('response aborted') + }) + }) + urlRequest.on('finish', () => { + requestFinishEventEmitted = true + }) + urlRequest.on('error', (error) => { + expect(error).to.be.an('Error') + }) + urlRequest.on('abort', () => { + expect.fail('request aborted') + }) + urlRequest.on('close', () => { + requestCloseEventEmitted = true + maybeDone(done) + }) + urlRequest.end() + }) + }) + + it('should be able to set a custom HTTP request header before first write', (done) => { + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', () => { + }) + response.on('end', () => { + done() + }) + }) + urlRequest.setHeader(customHeaderName, customHeaderValue) + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) + urlRequest.write('') + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) + urlRequest.end() + }) + }) + + it('should be able to set a non-string object as a header value', (done) => { + const customHeaderName = 'Some-Integer-Value' + const customHeaderValue = 900 + respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue.toString()) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.setHeader(customHeaderName, customHeaderValue) + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) + urlRequest.write('') + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) + urlRequest.end() + }) + }) + + it('should not be able to set a custom HTTP request header after first write', (done) => { + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.write('') + expect(() => { + urlRequest.setHeader(customHeaderName, customHeaderValue) + }).to.throw() + expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined) + urlRequest.end() + }) + }) + + it('should be able to remove a custom HTTP request header before first write', (done) => { + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.setHeader(customHeaderName, customHeaderValue) + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) + urlRequest.removeHeader(customHeaderName) + expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined) + urlRequest.write('') + urlRequest.end() + }) + }) + + it('should not be able to remove a custom HTTP request header after first write', (done) => { + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.setHeader(customHeaderName, customHeaderValue) + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) + urlRequest.write('') + expect(() => { + urlRequest.removeHeader(customHeaderName) + }).to.throw() + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) + urlRequest.end() + }) + }) + + it('should be able to set cookie header line', (done) => { + const cookieHeaderName = 'Cookie' + const cookieHeaderValue = 'test=12345' + const customSession = session.fromPartition('test-cookie-header') + respondOnce.toSingleURL((request, response) => { + expect(request.headers[cookieHeaderName.toLowerCase()]).to.equal(cookieHeaderValue) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }).then(serverUrl => { + customSession.cookies.set({ + url: `${serverUrl}`, + name: 'test', + value: '11111' + }).then(() => { // resolved + const urlRequest = net.request({ + method: 'GET', + url: serverUrl, + session: customSession + }) + urlRequest.on('response', (response) => { + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.setHeader(cookieHeaderName, cookieHeaderValue) + expect(urlRequest.getHeader(cookieHeaderName)).to.equal(cookieHeaderValue) + urlRequest.end() + }, (error) => { + done(error) + }) + }) + }) + + it('should be able to abort an HTTP request before first write', (done) => { + respondOnce.toSingleURL((request, response) => { + response.end() + expect.fail('Unexpected request event') + }).then(serverUrl => { + let requestAbortEventEmitted = false + + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + expect.fail('Unexpected response event') + }) + urlRequest.on('finish', () => { + expect.fail('Unexpected finish event') + }) + urlRequest.on('error', () => { + expect.fail('Unexpected error event') + }) + urlRequest.on('abort', () => { + requestAbortEventEmitted = true + }) + urlRequest.on('close', () => { + expect(requestAbortEventEmitted).to.equal(true) + done() + }) + urlRequest.abort() + expect(urlRequest.write('')).to.equal(false) + urlRequest.end() + }) + }) + + it('it should be able to abort an HTTP request before request end', (done) => { + let requestReceivedByServer = false + let urlRequest: ClientRequest | null = null + respondOnce.toSingleURL((request, response) => { + requestReceivedByServer = true + urlRequest!.abort() + }).then(serverUrl => { + let requestAbortEventEmitted = false + + urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + expect.fail('Unexpected response event') + }) + urlRequest.on('finish', () => { + expect.fail('Unexpected finish event') + }) + urlRequest.on('error', () => { + expect.fail('Unexpected error event') + }) + urlRequest.on('abort', () => { + requestAbortEventEmitted = true + }) + urlRequest.on('close', () => { + expect(requestReceivedByServer).to.equal(true) + expect(requestAbortEventEmitted).to.equal(true) + done() + }) + + urlRequest.chunkedEncoding = true + urlRequest.write(randomString(kOneKiloByte)) + }) + }) + + it('it should be able to abort an HTTP request after request end and before response', (done) => { + let requestReceivedByServer = false + let urlRequest: ClientRequest | null = null + respondOnce.toSingleURL((request, response) => { + requestReceivedByServer = true + urlRequest!.abort() + process.nextTick(() => { + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }) + }).then(serverUrl => { + let requestAbortEventEmitted = false + let requestFinishEventEmitted = false + + urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + expect.fail('Unexpected response event') + }) + urlRequest.on('finish', () => { + requestFinishEventEmitted = true + }) + urlRequest.on('error', () => { + expect.fail('Unexpected error event') + }) + urlRequest.on('abort', () => { + requestAbortEventEmitted = true + }) + urlRequest.on('close', () => { + expect(requestFinishEventEmitted).to.equal(true) + expect(requestReceivedByServer).to.equal(true) + expect(requestAbortEventEmitted).to.equal(true) + done() + }) + + urlRequest.end(randomString(kOneKiloByte)) + }) + }) + + it('it should be able to abort an HTTP request after response start', (done) => { + let requestReceivedByServer = false + respondOnce.toSingleURL((request, response) => { + requestReceivedByServer = true + response.statusCode = 200 + response.statusMessage = 'OK' + response.write(randomString(kOneKiloByte)) + }).then(serverUrl => { + let requestFinishEventEmitted = false + let requestResponseEventEmitted = false + let requestAbortEventEmitted = false + let responseAbortedEventEmitted = false + + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + requestResponseEventEmitted = true + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', (chunk) => { + }) + response.on('end', () => { + expect.fail('Unexpected end event') + }) + response.on('error', () => { + expect.fail('Unexpected error event') + }) + response.on('aborted', () => { + responseAbortedEventEmitted = true + }) + urlRequest.abort() + }) + urlRequest.on('finish', () => { + requestFinishEventEmitted = true + }) + urlRequest.on('error', () => { + expect.fail('Unexpected error event') + }) + urlRequest.on('abort', () => { + requestAbortEventEmitted = true + }) + urlRequest.on('close', () => { + expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event') + expect(requestReceivedByServer).to.be.true('request should be received by the server') + expect(requestResponseEventEmitted).to.be.true('"response" event should be emitted') + expect(requestAbortEventEmitted).to.be.true('request should emit "abort" event') + expect(responseAbortedEventEmitted).to.be.true('response should emit "aborted" event') + done() + }) + urlRequest.end(randomString(kOneKiloByte)) + }) + }) + + it('abort event should be emitted at most once', (done) => { + let requestReceivedByServer = false + let urlRequest: ClientRequest | null = null + respondOnce.toSingleURL((request, response) => { + requestReceivedByServer = true + urlRequest!.abort() + urlRequest!.abort() + }).then(serverUrl => { + let requestFinishEventEmitted = false + let abortEmitted = false + + urlRequest = net.request(serverUrl) + urlRequest.on('response', () => { + expect.fail('Unexpected response event') + }) + urlRequest.on('finish', () => { + requestFinishEventEmitted = true + }) + urlRequest.on('error', () => { + expect.fail('Unexpected error event') + }) + urlRequest.on('abort', () => { + expect(abortEmitted).to.be.false('abort event should not be emitted more than once') + abortEmitted = true + urlRequest!.abort() + }) + urlRequest.on('close', () => { + expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event') + expect(requestReceivedByServer).to.be.true('request should be received by server') + expect(abortEmitted).to.be.true('request should emit "abort" event') + done() + }) + + urlRequest.end(randomString(kOneKiloByte)) + }) + }) + + describe('webRequest', () => { + afterEach(() => { + session.defaultSession.webRequest.onBeforeRequest(null) + }) + + it('Requests should be intercepted by webRequest module', (done) => { + const requestUrl = '/requestUrl' + const redirectUrl = '/redirectUrl' + let requestIsRedirected = false + respondOnce.toURL(redirectUrl, (request, response) => { + requestIsRedirected = true + response.end() + }).then(serverUrl => { + let requestIsIntercepted = false + session.defaultSession.webRequest.onBeforeRequest( + (details, callback) => { + if (details.url === `${serverUrl}${requestUrl}`) { + requestIsIntercepted = true + // Disabled due to false positive in StandardJS + // eslint-disable-next-line standard/no-callback-literal + callback({ + redirectURL: `${serverUrl}${redirectUrl}` + }) + } else { + callback({ + cancel: false + }) + } + }) + + const urlRequest = net.request(`${serverUrl}${requestUrl}`) + + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + response.on('data', () => {}) + response.on('end', () => { + expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') + expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') + done() + }) + }) + urlRequest.end() + }) + }) + + it('should to able to create and intercept a request using a custom session object', (done) => { + const requestUrl = '/requestUrl' + const redirectUrl = '/redirectUrl' + const customPartitionName = 'custom-partition' + let requestIsRedirected = false + respondOnce.toURL(redirectUrl, (request, response) => { + requestIsRedirected = true + response.end() + }).then(serverUrl => { + session.defaultSession.webRequest.onBeforeRequest((details, callback) => { + expect.fail('Request should not be intercepted by the default session') + }) + + const customSession = session.fromPartition(customPartitionName, { cache: false }) + let requestIsIntercepted = false + customSession.webRequest.onBeforeRequest((details, callback) => { + if (details.url === `${serverUrl}${requestUrl}`) { + requestIsIntercepted = true + // Disabled due to false positive in StandardJS + // eslint-disable-next-line standard/no-callback-literal + callback({ + redirectURL: `${serverUrl}${redirectUrl}` + }) + } else { + callback({ + cancel: false + }) + } + }) + + const urlRequest = net.request({ + url: `${serverUrl}${requestUrl}`, + session: customSession + }) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + response.on('data', (chunk) => { + }) + response.on('end', () => { + expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') + expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') + done() + }) + }) + urlRequest.end() + }) + }) + + it('should to able to create and intercept a request using a custom session object', (done) => { + const requestUrl = '/requestUrl' + const redirectUrl = '/redirectUrl' + const customPartitionName = 'custom-partition' + let requestIsRedirected = false + respondOnce.toURL(redirectUrl, (request, response) => { + requestIsRedirected = true + response.end() + }).then(serverUrl => { + session.defaultSession.webRequest.onBeforeRequest((details, callback) => { + expect.fail('Request should not be intercepted by the default session') + }) + + const customSession = session.fromPartition(customPartitionName, { cache: false }) + let requestIsIntercepted = false + customSession.webRequest.onBeforeRequest((details, callback) => { + if (details.url === `${serverUrl}${requestUrl}`) { + requestIsIntercepted = true + // Disabled due to false positive in StandardJS + // eslint-disable-next-line standard/no-callback-literal + callback({ + redirectURL: `${serverUrl}${redirectUrl}` + }) + } else { + callback({ + cancel: false + }) + } + }) + + const urlRequest = net.request({ + url: `${serverUrl}${requestUrl}`, + partition: customPartitionName + }) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + response.on('data', (chunk) => { + }) + response.on('end', () => { + expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') + expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') + done() + }) + }) + urlRequest.end() + }) + }) + }) + + it('should throw if given an invalid redirect mode', () => { + expect(() => { + net.request({ + url: 'https://test', + redirect: 'custom' + }) + }).to.throw('redirect mode should be one of follow, error or manual') + }) + + it('should throw when calling getHeader without a name', () => { + expect(() => { + (net.request({ url: 'https://test' }).getHeader as any)() + }).to.throw(/`name` is required for getHeader\(name\)/) + + expect(() => { + net.request({ url: 'https://test' }).getHeader(null as any) + }).to.throw(/`name` is required for getHeader\(name\)/) + }) + + it('should throw when calling removeHeader without a name', () => { + expect(() => { + (net.request({ url: 'https://test' }).removeHeader as any)() + }).to.throw(/`name` is required for removeHeader\(name\)/) + + expect(() => { + net.request({ url: 'https://test' }).removeHeader(null as any) + }).to.throw(/`name` is required for removeHeader\(name\)/) + }) + + it('should follow redirect when no redirect mode is provided', (done) => { + const requestUrl = '/301' + respondOnce.toRoutes({ + '/301': (request, response) => { + response.statusCode = 301 + response.setHeader('Location', '/200') + response.end() + }, + '/200': (request, response) => { + response.statusCode = 200 + response.end() + }, + }).then(serverUrl => { + const urlRequest = net.request({ + url: `${serverUrl}${requestUrl}` + }) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + done() + }) + urlRequest.end() + }) + }) + + it('should follow redirect chain when no redirect mode is provided', (done) => { + respondOnce.toRoutes({ + '/redirectChain': (request, response) => { + response.statusCode = 301 + response.setHeader('Location', '/301') + response.end() + }, + '/301': (request, response) => { + response.statusCode = 301 + response.setHeader('Location', '/200') + response.end() + }, + '/200': (request, response) => { + response.statusCode = 200 + response.end() + }, + }).then(serverUrl => { + const urlRequest = net.request({ + url: `${serverUrl}/redirectChain` + }) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + done() + }) + urlRequest.end() + }) + }) + + it('should not follow redirect when mode is error', (done) => { + respondOnce.toSingleURL((request, response) => { + response.statusCode = 301 + response.setHeader('Location', '/200') + response.end() + }).then(serverUrl => { + const urlRequest = net.request({ + url: serverUrl, + redirect: 'error' + }) + urlRequest.on('error', (error) => { + expect(error.message).to.equal('Request cannot follow redirect with the current redirect mode') + }) + urlRequest.on('close', () => { + done() + }) + urlRequest.end() + }) + }) + + it('should allow follow redirect when mode is manual', (done) => { + respondOnce.toRoutes({ + '/redirectChain': (request, response) => { + response.statusCode = 301 + response.setHeader('Location', '/301') + response.end() + }, + '/301': (request, response) => { + response.statusCode = 301 + response.setHeader('Location', '/200') + response.end() + }, + '/200': (request, response) => { + response.statusCode = 200 + response.end() + }, + }).then(serverUrl => { + const urlRequest = net.request({ + url: `${serverUrl}/redirectChain`, + redirect: 'manual' + }) + let redirectCount = 0 + urlRequest.on('response', (response) => { + expect(response.statusCode).to.equal(200) + expect(redirectCount).to.equal(2) + done() + }) + urlRequest.on('redirect', (status, method, url) => { + if (url === `${serverUrl}/301` || url === `${serverUrl}/200`) { + redirectCount += 1 + urlRequest.followRedirect() + } + }) + urlRequest.end() + }) + }) + + it('should allow cancelling redirect when mode is manual', (done) => { + respondOnce.toRoutes({ + '/redirect': (request, response) => { + response.statusCode = 301 + response.setHeader('Location', '/200') + response.end() + }, + '/200': (request, response) => { + response.statusCode = 200 + response.end() + }, + }).then(serverUrl => { + const urlRequest = net.request({ + url: `${serverUrl}/redirect`, + redirect: 'manual' + }) + urlRequest.on('response', (response) => { + expect(response.statusCode).that.equal(200) + response.on('data', () => {}) + response.on('end', () => { + urlRequest.abort() + }) + }) + let redirectCount = 0 + urlRequest.on('close', () => { + expect(redirectCount).to.equal(1) + done() + }) + urlRequest.on('redirect', (status, method, url) => { + if (url === `${serverUrl}/200`) { + redirectCount += 1 + urlRequest.followRedirect() + } + }) + urlRequest.end() + }) + }) + + it('should throw if given an invalid session option', () => { + expect(() => { + net.request({ + url: 'https://foo', + session: 1 + }) + }).to.throw("`session` should be an instance of the Session class") + }) + + it('should throw if given an invalid partition option', () => { + expect(() => { + net.request({ + url: 'https://foo', + partition: 1 + }) + }).to.throw("`partition` should be a string") + }) + + it('should be able to create a request with options', (done) => { + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + respondOnce.toURL('/', (request, response) => { + expect(request.method).to.equal('GET') + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + }).then(serverUrlUnparsed => { + const serverUrl = url.parse(serverUrlUnparsed) + const options = { + port: serverUrl.port, + hostname: '127.0.0.1', + headers: { [customHeaderName]: customHeaderValue } + } + const urlRequest = net.request(options) + urlRequest.on('response', (response) => { + expect(response.statusCode).to.be.equal(200) + response.on('data', () => {}) + response.on('end', () => { + done() + }) + }) + urlRequest.end() + }) + }) + + it('should be able to pipe a readable stream into a net request', (done) => { + const bodyData = randomString(kOneMegaByte) + let netRequestReceived = false + let netRequestEnded = false + + Promise.all([ + respondOnce.toSingleURL((request, response) => response.end(bodyData)), + respondOnce.toSingleURL((request, response) => { + netRequestReceived = true + let receivedBodyData = '' + request.on('data', (chunk) => { + receivedBodyData += chunk.toString() + }) + request.on('end', (chunk: Buffer | undefined) => { + netRequestEnded = true + if (chunk) { + receivedBodyData += chunk.toString() + } + expect(receivedBodyData).to.be.equal(bodyData) + response.end() + }) + }) + ]).then(([nodeServerUrl, netServerUrl]) => { + const nodeRequest = http.request(nodeServerUrl) + nodeRequest.on('response', (nodeResponse) => { + const netRequest = net.request(netServerUrl) + netRequest.on('response', (netResponse) => { + expect(netResponse.statusCode).to.equal(200) + netResponse.on('data', (chunk) => {}) + netResponse.on('end', () => { + expect(netRequestReceived).to.be.true('net request received') + expect(netRequestEnded).to.be.true('net request ended') + done() + }) + }) + nodeResponse.pipe(netRequest) + }) + nodeRequest.end() + }) + }) + + it('should emit error event on server socket close', (done) => { + respondOnce.toSingleURL((request, response) => { + request.socket.destroy() + }).then(serverUrl => { + let requestErrorEventEmitted = false + const urlRequest = net.request(serverUrl) + urlRequest.on('error', (error) => { + expect(error).to.be.an('Error') + requestErrorEventEmitted = true + }) + urlRequest.on('close', () => { + expect(requestErrorEventEmitted).to.be.true('request error event was emitted') + done() + }) + urlRequest.end() + }) + }) + }) + + describe('IncomingMessage API', () => { + it('response object should implement the IncomingMessage API', (done) => { + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + + respondOnce.toSingleURL((request, response) => { + response.statusCode = 200 + response.statusMessage = 'OK' + response.setHeader(customHeaderName, customHeaderValue) + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + + 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') + const headerValue = headers[customHeaderName.toLowerCase()] + expect(headerValue).to.equal(customHeaderValue) + + const httpVersion = response.httpVersion + expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1) + + const httpVersionMajor = response.httpVersionMajor + expect(httpVersionMajor).to.be.a('number').and.to.be.at.least(1) + + const httpVersionMinor = response.httpVersionMinor + expect(httpVersionMinor).to.be.a('number').and.to.be.at.least(0) + + response.on('data', chunk => {}) + response.on('end', () => { done() }) + }) + urlRequest.end() + }) + }) + + it('should discard duplicate headers', (done) => { + const includedHeader = 'max-forwards' + const discardableHeader = 'Max-Forwards' + + const includedHeaderValue = 'max-fwds-val' + const discardableHeaderValue = 'max-fwds-val-two' + + respondOnce.toSingleURL((request, response) => { + response.statusCode = 200 + response.statusMessage = 'OK' + response.setHeader(discardableHeader, discardableHeaderValue) + response.setHeader(includedHeader, includedHeaderValue) + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + + 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.on('data', chunk => {}) + response.on('end', () => { done() }) + }) + urlRequest.end() + }) + }) + + it('should join repeated non-discardable value with ,', (done) => { + respondOnce.toSingleURL((request, response) => { + response.statusCode = 200 + response.statusMessage = 'OK' + response.setHeader('referrer-policy', ['first-text', 'second-text']) + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + + 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('referrer-policy') + expect(headers['referrer-policy']).to.equal('first-text, second-text') + + response.on('data', chunk => {}) + response.on('end', () => { done() }) + }) + urlRequest.end() + }) + }) + + it('should be able to pipe a net response into a writable stream', (done) => { + const bodyData = randomString(kOneKiloByte) + Promise.all([ + respondOnce.toSingleURL((request, response) => response.end(bodyData)), + respondOnce.toSingleURL((request, response) => { + let receivedBodyData = '' + request.on('data', (chunk) => { + receivedBodyData += chunk.toString() + }) + request.on('end', (chunk: Buffer | undefined) => { + if (chunk) { + receivedBodyData += chunk.toString() + } + expect(receivedBodyData).to.be.equal(bodyData) + response.end() + }) + }) + ]).then(([netServerUrl, nodeServerUrl]) => { + const netRequest = net.request(netServerUrl) + netRequest.on('response', (netResponse) => { + const serverUrl = url.parse(nodeServerUrl) + const nodeOptions = { + method: 'POST', + path: serverUrl.path, + port: serverUrl.port + } + const nodeRequest = http.request(nodeOptions, res => { + res.on('data', (chunk) => {}) + res.on('end', () => { + done() + }) + }); + // TODO: IncomingMessage should properly extend ReadableStream in the + // docs + (netResponse as any).pipe(nodeRequest) + }) + netRequest.end() + }) + }) + + it('should not emit any event after close', (done) => { + const bodyData = randomString(kOneKiloByte) + respondOnce.toSingleURL((request, response) => { + response.end(bodyData) + }).then(serverUrl => { + let requestCloseEventEmitted = false + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + expect(requestCloseEventEmitted).to.be.false('request close event emitted') + const statusCode = response.statusCode + expect(statusCode).to.equal(200) + response.on('data', () => { }) + response.on('end', () => { }) + response.on('error', () => { + expect(requestCloseEventEmitted).to.be.false('request close event emitted') + }) + response.on('aborted', () => { + expect(requestCloseEventEmitted).to.be.false('request close event emitted') + }) + }) + urlRequest.on('finish', () => { + expect(requestCloseEventEmitted).to.be.false('request close event emitted') + }) + urlRequest.on('error', () => { + expect(requestCloseEventEmitted).to.be.false('request close event emitted') + }) + urlRequest.on('abort', () => { + expect(requestCloseEventEmitted).to.be.false('request close event emitted') + }) + urlRequest.on('close', () => { + requestCloseEventEmitted = true + // Wait so that all async events get scheduled. + setTimeout(() => { + done() + }, 100) + }) + urlRequest.end() + }) + }) + }) + + describe('Stability and performance', () => { + it('should free unreferenced, never-started request objects without crash', (done) => { + net.request('https://test') + process.nextTick(() => { + const v8Util = process.electronBinding('v8_util') + v8Util.requestGarbageCollectionForTesting() + done() + }) + }) + + it('should collect on-going requests without crash', (done) => { + let finishResponse: (() => void) | null = null + respondOnce.toSingleURL((request, response) => { + response.write(randomString(kOneKiloByte)) + finishResponse = () => { + response.write(randomString(kOneKiloByte)) + response.end() + } + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + response.on('data', () => { }) + response.on('end', () => { + done() + }) + process.nextTick(() => { + // Trigger a garbage collection. + const v8Util = process.electronBinding('v8_util') + v8Util.requestGarbageCollectionForTesting() + finishResponse!() + }) + }) + urlRequest.end() + }) + }) + + it('should collect unreferenced, ended requests without crash', (done) => { + respondOnce.toSingleURL((request, response) => { + response.end() + }).then(serverUrl => { + const urlRequest = net.request(serverUrl) + urlRequest.on('response', (response) => { + response.on('data', () => {}) + response.on('end', () => {}) + }) + urlRequest.on('close', () => { + process.nextTick(() => { + const v8Util = process.electronBinding('v8_util') + v8Util.requestGarbageCollectionForTesting() + done() + }) + }) + urlRequest.end() + }) + }) + }) +}) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js deleted file mode 100644 index df26550e56ce..000000000000 --- a/spec/api-net-spec.js +++ /dev/null @@ -1,1701 +0,0 @@ -const chai = require('chai') -const dirtyChai = require('dirty-chai') - -const { remote } = require('electron') -const { ipcRenderer } = require('electron') -const http = require('http') -const url = require('url') -const { net } = remote -const { session } = remote - -const { expect } = chai -chai.use(dirtyChai) - -/* The whole net API doesn't use standard callbacks */ -/* eslint-disable standard/no-callback-literal */ - -function randomBuffer (size, start, end) { - start = start || 0 - end = end || 255 - const range = 1 + end - start - const buffer = Buffer.allocUnsafe(size) - for (let i = 0; i < size; ++i) { - buffer[i] = start + Math.floor(Math.random() * range) - } - return buffer -} - -function randomString (length) { - const buffer = randomBuffer(length, '0'.charCodeAt(0), 'z'.charCodeAt(0)) - return buffer.toString() -} - -const kOneKiloByte = 1024 -const kOneMegaByte = kOneKiloByte * kOneKiloByte - -describe('net module', () => { - let server - const connections = new Set() - - beforeEach((done) => { - server = http.createServer() - server.listen(0, '127.0.0.1', () => { - server.url = `http://127.0.0.1:${server.address().port}` - done() - }) - server.on('connection', (connection) => { - connections.add(connection) - connection.once('close', () => { - connections.delete(connection) - }) - }) - }) - - afterEach((done) => { - for (const connection of connections) { - connection.destroy() - } - server.close(() => { - server = null - done() - }) - }) - - describe('HTTP basics', () => { - it('should be able to issue a basic GET request', (done) => { - const requestUrl = '/requestUrl' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.method).to.equal('GET') - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request(`${server.url}${requestUrl}`) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.end() - }) - - it('should be able to issue a basic POST request', (done) => { - const requestUrl = '/requestUrl' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.method).to.equal('POST') - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - method: 'POST', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.end() - }) - - it('should fetch correct data in a GET request', (done) => { - const requestUrl = '/requestUrl' - const bodyData = 'Hello World!' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.method).to.equal('GET') - response.write(bodyData) - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request(`${server.url}${requestUrl}`) - urlRequest.on('response', (response) => { - let expectedBodyData = '' - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - expectedBodyData += chunk.toString() - }) - response.on('end', () => { - expect(expectedBodyData).to.equal(bodyData) - done() - }) - response.resume() - }) - urlRequest.end() - }) - - it('should post the correct data in a POST request', (done) => { - const requestUrl = '/requestUrl' - const bodyData = 'Hello World!' - server.on('request', (request, response) => { - let postedBodyData = '' - switch (request.url) { - case requestUrl: - expect(request.method).to.equal('POST') - request.on('data', (chunk) => { - postedBodyData += chunk.toString() - }) - request.on('end', () => { - expect(postedBodyData).to.equal(bodyData) - response.end() - }) - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - method: 'POST', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => {}) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.write(bodyData) - urlRequest.end() - }) - - it('should support chunked encoding', (done) => { - const requestUrl = '/requestUrl' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - response.statusCode = 200 - response.statusMessage = 'OK' - response.chunkedEncoding = true - expect(request.method).to.equal('POST') - expect(request.headers['transfer-encoding']).to.equal('chunked') - expect(request.headers['content-length']).to.be.undefined() - request.on('data', (chunk) => { - response.write(chunk) - }) - request.on('end', (chunk) => { - response.end(chunk) - }) - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - method: 'POST', - url: `${server.url}${requestUrl}` - }) - - let chunkIndex = 0 - const chunkCount = 100 - const sentChunks = [] - const receivedChunks = [] - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - receivedChunks.push(chunk) - }) - response.on('end', () => { - const sentData = Buffer.concat(sentChunks) - const receivedData = Buffer.concat(receivedChunks) - expect(sentData.toString()).to.equal(receivedData.toString()) - expect(chunkIndex).to.be.equal(chunkCount) - done() - }) - response.resume() - }) - urlRequest.chunkedEncoding = true - while (chunkIndex < chunkCount) { - chunkIndex += 1 - const chunk = randomBuffer(kOneKiloByte) - sentChunks.push(chunk) - expect(urlRequest.write(chunk)).to.be.true() - } - urlRequest.end() - }) - }) - - describe('ClientRequest API', () => { - afterEach(() => { - session.defaultSession.webRequest.onBeforeRequest(null) - }) - - it('request/response objects should emit expected events', (done) => { - const requestUrl = '/requestUrl' - const bodyData = randomString(kOneMegaByte) - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - response.statusCode = 200 - response.statusMessage = 'OK' - response.write(bodyData) - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - - let requestResponseEventEmitted = false - let requestFinishEventEmitted = false - let requestCloseEventEmitted = false - let responseDataEventEmitted = false - let responseEndEventEmitted = false - - function maybeDone (done) { - if (!requestCloseEventEmitted || !responseEndEventEmitted) { - return - } - - expect(requestResponseEventEmitted).to.be.true() - expect(requestFinishEventEmitted).to.be.true() - expect(requestCloseEventEmitted).to.be.true() - expect(responseDataEventEmitted).to.be.true() - expect(responseEndEventEmitted).to.be.true() - done() - } - - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - requestResponseEventEmitted = true - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - const buffers = [] - response.pause() - response.on('data', (chunk) => { - buffers.push(chunk) - responseDataEventEmitted = true - }) - response.on('end', () => { - const receivedBodyData = Buffer.concat(buffers) - expect(receivedBodyData.toString()).to.equal(bodyData) - responseEndEventEmitted = true - maybeDone(done) - }) - response.resume() - response.on('error', (error) => { - expect(error).to.be.an('Error') - }) - response.on('aborted', () => { - expect.fail('response aborted') - }) - }) - urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) - urlRequest.on('error', (error) => { - expect(error).to.be.an('Error') - }) - urlRequest.on('abort', () => { - expect.fail('request aborted') - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - maybeDone(done) - }) - urlRequest.end() - }) - - it('should be able to set a custom HTTP request header before first write', (done) => { - const requestUrl = '/requestUrl' - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.setHeader(customHeaderName, customHeaderValue) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.write('') - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.end() - }) - - it('should be able to set a non-string object as a header value', (done) => { - const requestUrl = '/requestUrl' - const customHeaderName = 'Some-Integer-Value' - const customHeaderValue = 900 - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue.toString()) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - expect(request.url).to.equal(requestUrl) - } - }) - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.setHeader(customHeaderName, customHeaderValue) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.write('') - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.end() - }) - - it('should not be able to set a custom HTTP request header after first write', (done) => { - const requestUrl = '/requestUrl' - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.headers[customHeaderName.toLowerCase()]).to.be.undefined() - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.write('') - expect(() => { - urlRequest.setHeader(customHeaderName, customHeaderValue) - }).to.throw() - expect(urlRequest.getHeader(customHeaderName)).to.be.undefined() - urlRequest.end() - }) - - it('should be able to remove a custom HTTP request header before first write', (done) => { - const requestUrl = '/requestUrl' - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.headers[customHeaderName.toLowerCase()]).to.be.undefined() - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.setHeader(customHeaderName, customHeaderValue) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - urlRequest.removeHeader(customHeaderName) - expect(urlRequest.getHeader(customHeaderName)).to.be.undefined() - urlRequest.write('') - urlRequest.end() - }) - - it('should not be able to remove a custom HTTP request header after first write', (done) => { - const requestUrl = '/requestUrl' - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.setHeader(customHeaderName, customHeaderValue) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - urlRequest.write('') - expect(() => { - urlRequest.removeHeader(customHeaderName) - }).to.throw() - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - urlRequest.end() - }) - - it('should be able to set cookie header line', (done) => { - const requestUrl = '/requestUrl' - const cookieHeaderName = 'Cookie' - const cookieHeaderValue = 'test=12345' - const customSession = session.fromPartition('test-cookie-header') - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.headers[cookieHeaderName.toLowerCase()]).to.equal(cookieHeaderValue) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - - customSession.cookies.set({ - url: `${server.url}`, - name: 'test', - value: '11111' - }).then(() => { // resolved - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}`, - session: customSession - }) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => {}) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.setHeader(cookieHeaderName, cookieHeaderValue) - expect(urlRequest.getHeader(cookieHeaderName)).to.equal(cookieHeaderValue) - urlRequest.end() - }, (error) => { - done(error) - }) - }) - - it('should be able to abort an HTTP request before first write', (done) => { - const requestUrl = '/requestUrl' - server.on('request', (request, response) => { - response.end() - expect.fail('Unexpected request event') - }) - - let requestAbortEventEmitted = false - let requestCloseEventEmitted = false - - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect.fail('Unexpected response event') - }) - urlRequest.on('finish', () => { - expect.fail('Unexpected finish event') - }) - urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.on('abort', () => { - requestAbortEventEmitted = true - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - expect(requestAbortEventEmitted).to.be.true() - expect(requestCloseEventEmitted).to.be.true() - done() - }) - urlRequest.abort() - expect(urlRequest.write('')).to.be.false() - urlRequest.end() - }) - - it('it should be able to abort an HTTP request before request end', (done) => { - const requestUrl = '/requestUrl' - let requestReceivedByServer = false - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - requestReceivedByServer = true - cancelRequest() - break - default: - handleUnexpectedURL(request, response) - } - }) - - let requestAbortEventEmitted = false - let requestCloseEventEmitted = false - - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect.fail('Unexpected response event') - }) - urlRequest.on('finish', () => { - expect.fail('Unexpected finish event') - }) - urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.on('abort', () => { - requestAbortEventEmitted = true - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - expect(requestReceivedByServer).to.be.true() - expect(requestAbortEventEmitted).to.be.true() - expect(requestCloseEventEmitted).to.be.true() - done() - }) - - urlRequest.chunkedEncoding = true - urlRequest.write(randomString(kOneKiloByte)) - function cancelRequest () { - urlRequest.abort() - } - }) - - it('it should be able to abort an HTTP request after request end and before response', (done) => { - const requestUrl = '/requestUrl' - let requestReceivedByServer = false - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - requestReceivedByServer = true - cancelRequest() - process.nextTick(() => { - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }) - break - default: - handleUnexpectedURL(request, response) - } - }) - - let requestAbortEventEmitted = false - let requestFinishEventEmitted = false - let requestCloseEventEmitted = false - - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect.fail('Unexpected response event') - }) - urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) - urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.on('abort', () => { - requestAbortEventEmitted = true - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - expect(requestFinishEventEmitted).to.be.true() - expect(requestReceivedByServer).to.be.true() - expect(requestAbortEventEmitted).to.be.true() - expect(requestCloseEventEmitted).to.be.true() - done() - }) - - urlRequest.end(randomString(kOneKiloByte)) - function cancelRequest () { - urlRequest.abort() - } - }) - - it('it should be able to abort an HTTP request after response start', (done) => { - const requestUrl = '/requestUrl' - let requestReceivedByServer = false - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - requestReceivedByServer = true - response.statusCode = 200 - response.statusMessage = 'OK' - response.write(randomString(kOneKiloByte)) - break - default: - handleUnexpectedURL(request, response) - } - }) - - let requestFinishEventEmitted = false - let requestResponseEventEmitted = false - let requestAbortEventEmitted = false - let requestCloseEventEmitted = false - let responseAbortedEventEmitted = false - - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - requestResponseEventEmitted = true - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - expect.fail('Unexpected end event') - }) - response.resume() - response.on('error', () => { - expect.fail('Unexpected error event') - }) - response.on('aborted', () => { - responseAbortedEventEmitted = true - }) - urlRequest.abort() - }) - urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) - urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.on('abort', () => { - requestAbortEventEmitted = true - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event') - expect(requestReceivedByServer).to.be.true('request should be received by the server') - expect(requestResponseEventEmitted).to.be.true('"response" event should be emitted') - expect(requestAbortEventEmitted).to.be.true('request should emit "abort" event') - expect(responseAbortedEventEmitted).to.be.true('response should emit "aborted" event') - expect(requestCloseEventEmitted).to.be.true('request should emit "close" event') - done() - }) - urlRequest.end(randomString(kOneKiloByte)) - }) - - it('abort event should be emitted at most once', (done) => { - const requestUrl = '/requestUrl' - let requestReceivedByServer = false - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - requestReceivedByServer = true - cancelRequest() - break - default: - handleUnexpectedURL(request, response) - } - }) - - let requestFinishEventEmitted = false - let requestAbortEventCount = 0 - let requestCloseEventEmitted = false - - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', () => { - expect.fail('Unexpected response event') - }) - urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) - urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.on('abort', () => { - ++requestAbortEventCount - urlRequest.abort() - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - // Let all pending async events to be emitted - setTimeout(() => { - expect(requestFinishEventEmitted).to.be.true() - expect(requestReceivedByServer).to.be.true() - expect(requestAbortEventCount).to.equal(1) - expect(requestCloseEventEmitted).to.be.true() - done() - }, 500) - }) - - urlRequest.end(randomString(kOneKiloByte)) - function cancelRequest () { - urlRequest.abort() - urlRequest.abort() - } - }) - - it('Requests should be intercepted by webRequest module', (done) => { - const requestUrl = '/requestUrl' - const redirectUrl = '/redirectUrl' - let requestIsRedirected = false - server.on('request', (request, response) => { - switch (request.url) { - case redirectUrl: - requestIsRedirected = true - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - - let requestIsIntercepted = false - session.defaultSession.webRequest.onBeforeRequest( - (details, callback) => { - if (details.url === `${server.url}${requestUrl}`) { - requestIsIntercepted = true - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - callback({ - redirectURL: `${server.url}${redirectUrl}` - }) - } else { - callback({ - cancel: false - }) - } - }) - - const urlRequest = net.request(`${server.url}${requestUrl}`) - - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') - expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') - done() - }) - response.resume() - }) - urlRequest.end() - }) - - it('should to able to create and intercept a request using a custom session object', (done) => { - const requestUrl = '/requestUrl' - const redirectUrl = '/redirectUrl' - const customPartitionName = 'custom-partition' - let requestIsRedirected = false - server.on('request', (request, response) => { - switch (request.url) { - case redirectUrl: - requestIsRedirected = true - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - - session.defaultSession.webRequest.onBeforeRequest((details, callback) => { - expect.fail('Request should not be intercepted by the default session') - }) - - const customSession = session.fromPartition(customPartitionName, { cache: false }) - let requestIsIntercepted = false - customSession.webRequest.onBeforeRequest((details, callback) => { - if (details.url === `${server.url}${requestUrl}`) { - requestIsIntercepted = true - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - callback({ - redirectURL: `${server.url}${redirectUrl}` - }) - } else { - callback({ - cancel: false - }) - } - }) - - const urlRequest = net.request({ - url: `${server.url}${requestUrl}`, - session: customSession - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') - expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') - done() - }) - response.resume() - }) - urlRequest.end() - }) - - it('should throw if given an invalid redirect mode', () => { - const requestUrl = '/requestUrl' - const options = { - url: `${server.url}${requestUrl}`, - redirect: 'custom' - } - expect(() => { - net.request(options) - }).to.throw('redirect mode should be one of follow, error or manual') - }) - - it('should throw when calling getHeader without a name', () => { - expect(() => { - net.request({ url: `${server.url}/requestUrl` }).getHeader() - }).to.throw(/`name` is required for getHeader\(name\)\./) - - expect(() => { - net.request({ url: `${server.url}/requestUrl` }).getHeader(null) - }).to.throw(/`name` is required for getHeader\(name\)\./) - }) - - it('should throw when calling removeHeader without a name', () => { - expect(() => { - net.request({ url: `${server.url}/requestUrl` }).removeHeader() - }).to.throw(/`name` is required for removeHeader\(name\)\./) - - expect(() => { - net.request({ url: `${server.url}/requestUrl` }).removeHeader(null) - }).to.throw(/`name` is required for removeHeader\(name\)\./) - }) - - it('should follow redirect when no redirect mode is provided', (done) => { - const requestUrl = '/301' - server.on('request', (request, response) => { - switch (request.url) { - case '/301': - response.statusCode = '301' - response.setHeader('Location', '/200') - response.end() - break - case '/200': - response.statusCode = '200' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - done() - }) - urlRequest.end() - }) - - it('should follow redirect chain when no redirect mode is provided', (done) => { - const requestUrl = '/redirectChain' - server.on('request', (request, response) => { - switch (request.url) { - case '/redirectChain': - response.statusCode = '301' - response.setHeader('Location', '/301') - response.end() - break - case '/301': - response.statusCode = '301' - response.setHeader('Location', '/200') - response.end() - break - case '/200': - response.statusCode = '200' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - done() - }) - urlRequest.end() - }) - - it('should not follow redirect when mode is error', (done) => { - const requestUrl = '/301' - server.on('request', (request, response) => { - switch (request.url) { - case '/301': - response.statusCode = '301' - response.setHeader('Location', '/200') - response.end() - break - case '/200': - response.statusCode = '200' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - url: `${server.url}${requestUrl}`, - redirect: 'error' - }) - urlRequest.on('error', (error) => { - expect(error.message).to.equal('Request cannot follow redirect with the current redirect mode') - }) - urlRequest.on('close', () => { - done() - }) - urlRequest.end() - }) - - it('should allow follow redirect when mode is manual', (done) => { - const requestUrl = '/redirectChain' - let redirectCount = 0 - server.on('request', (request, response) => { - switch (request.url) { - case '/redirectChain': - response.statusCode = '301' - response.setHeader('Location', '/301') - response.end() - break - case '/301': - response.statusCode = '301' - response.setHeader('Location', '/200') - response.end() - break - case '/200': - response.statusCode = '200' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - url: `${server.url}${requestUrl}`, - redirect: 'manual' - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - expect(redirectCount).to.equal(2) - done() - }) - urlRequest.on('redirect', (status, method, url) => { - if (url === `${server.url}/301` || url === `${server.url}/200`) { - redirectCount += 1 - urlRequest.followRedirect() - } - }) - urlRequest.end() - }) - - it('should allow cancelling redirect when mode is manual', (done) => { - const requestUrl = '/redirectChain' - let redirectCount = 0 - server.on('request', (request, response) => { - switch (request.url) { - case '/redirectChain': - response.statusCode = '301' - response.setHeader('Location', '/redirect/1') - response.end() - break - case '/redirect/1': - response.statusCode = '200' - response.setHeader('Location', '/redirect/2') - response.end() - break - case '/redirect/2': - response.statusCode = '200' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - const urlRequest = net.request({ - url: `${server.url}${requestUrl}`, - redirect: 'manual' - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).that.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - urlRequest.abort() - }) - response.resume() - }) - urlRequest.on('close', () => { - expect(redirectCount).to.equal(1) - done() - }) - urlRequest.on('redirect', (status, method, url) => { - if (url === `${server.url}/redirect/1`) { - redirectCount += 1 - urlRequest.followRedirect() - } - }) - urlRequest.end() - }) - - it('should throw if given an invalid session option', (done) => { - const requestUrl = '/requestUrl' - try { - const urlRequest = net.request({ - url: `${server.url}${requestUrl}`, - session: 1 - }) - - // eslint-disable-next-line - urlRequest - } catch (exception) { - done() - } - }) - - it('should to able to create and intercept a request using a custom partition name', (done) => { - const requestUrl = '/requestUrl' - const redirectUrl = '/redirectUrl' - const customPartitionName = 'custom-partition' - let requestIsRedirected = false - server.on('request', (request, response) => { - switch (request.url) { - case redirectUrl: - requestIsRedirected = true - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - - session.defaultSession.webRequest.onBeforeRequest((details, callback) => { - expect.fail('Request should not be intercepted by the default session') - }) - - const customSession = session.fromPartition(customPartitionName, { - cache: false - }) - let requestIsIntercepted = false - customSession.webRequest.onBeforeRequest((details, callback) => { - if (details.url === `${server.url}${requestUrl}`) { - requestIsIntercepted = true - callback({ - redirectURL: `${server.url}${redirectUrl}` - }) - } else { - callback({ - cancel: false - }) - } - }) - - const urlRequest = net.request({ - url: `${server.url}${requestUrl}`, - partition: customPartitionName - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') - expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') - done() - }) - response.resume() - }) - urlRequest.end() - }) - - it('should throw if given an invalid partition option', (done) => { - const requestUrl = '/requestUrl' - try { - const urlRequest = net.request({ - url: `${server.url}${requestUrl}`, - partition: 1 - }) - - // eslint-disable-next-line - urlRequest - } catch (exception) { - done() - } - }) - - it('should be able to create a request with options', (done) => { - const requestUrl = '/' - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - expect(request.method).to.equal('GET') - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - - const serverUrl = url.parse(server.url) - const options = { - port: serverUrl.port, - hostname: '127.0.0.1', - headers: {} - } - options.headers[customHeaderName] = customHeaderValue - const urlRequest = net.request(options) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.be.equal(200) - response.pause() - response.on('data', (chunk) => { - }) - response.on('end', () => { - done() - }) - response.resume() - }) - urlRequest.end() - }) - - it('should be able to pipe a readable stream into a net request', (done) => { - const nodeRequestUrl = '/nodeRequestUrl' - const netRequestUrl = '/netRequestUrl' - const bodyData = randomString(kOneMegaByte) - let netRequestReceived = false - let netRequestEnded = false - server.on('request', (request, response) => { - switch (request.url) { - case nodeRequestUrl: - response.write(bodyData) - response.end() - break - case netRequestUrl: - netRequestReceived = true - let receivedBodyData = '' - request.on('data', (chunk) => { - receivedBodyData += chunk.toString() - }) - request.on('end', (chunk) => { - netRequestEnded = true - if (chunk) { - receivedBodyData += chunk.toString() - } - expect(receivedBodyData).to.be.equal(bodyData) - response.end() - }) - break - default: - handleUnexpectedURL(request, response) - } - }) - - const nodeRequest = http.request(`${server.url}${nodeRequestUrl}`) - nodeRequest.on('response', (nodeResponse) => { - const netRequest = net.request(`${server.url}${netRequestUrl}`) - netRequest.on('response', (netResponse) => { - expect(netResponse.statusCode).to.be.equal(200) - netResponse.pause() - netResponse.on('data', (chunk) => {}) - netResponse.on('end', () => { - expect(netRequestReceived).to.be.true() - expect(netRequestEnded).to.be.true() - done() - }) - netResponse.resume() - }) - nodeResponse.pipe(netRequest) - }) - nodeRequest.end() - }) - - it('should emit error event on server socket close', (done) => { - const requestUrl = '/requestUrl' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - request.socket.destroy() - break - default: - handleUnexpectedURL(request, response) - } - }) - let requestErrorEventEmitted = false - const urlRequest = net.request(`${server.url}${requestUrl}`) - urlRequest.on('error', (error) => { - expect(error).to.be.an('Error') - requestErrorEventEmitted = true - }) - urlRequest.on('close', () => { - expect(requestErrorEventEmitted).to.be.true() - done() - }) - urlRequest.end() - }) - }) - - describe('IncomingMessage API', () => { - it('response object should implement the IncomingMessage API', (done) => { - const requestUrl = '/requestUrl' - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - response.statusCode = 200 - response.statusMessage = 'OK' - response.setHeader(customHeaderName, customHeaderValue) - 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') - const headerValue = headers[customHeaderName.toLowerCase()] - expect(headerValue).to.equal(customHeaderValue) - - const httpVersion = response.httpVersion - expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1) - - const httpVersionMajor = response.httpVersionMajor - expect(httpVersionMajor).to.be.a('number').and.to.be.at.least(1) - - const httpVersionMinor = response.httpVersionMinor - expect(httpVersionMinor).to.be.a('number').and.to.be.at.least(0) - - response.pause() - response.on('data', chunk => {}) - response.on('end', () => { done() }) - response.resume() - }) - urlRequest.end() - }) - - 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() - }) - urlRequest.end() - }) - - it('should be able to pipe a net response into a writable stream', (done) => { - const nodeRequestUrl = '/nodeRequestUrl' - const netRequestUrl = '/netRequestUrl' - const bodyData = randomString(kOneMegaByte) - server.on('request', (request, response) => { - switch (request.url) { - case netRequestUrl: - response.statusCode = 200 - response.statusMessage = 'OK' - response.write(bodyData) - response.end() - break - case nodeRequestUrl: - let receivedBodyData = '' - request.on('data', (chunk) => { - receivedBodyData += chunk.toString() - }) - request.on('end', (chunk) => { - if (chunk) { - receivedBodyData += chunk.toString() - } - expect(receivedBodyData).to.equal(bodyData) - response.end() - }) - break - default: - handleUnexpectedURL(request, response) - } - }) - ipcRenderer.once('api-net-spec-done', () => { - done() - }) - // Execute below code directly within the browser context without - // using the remote module. - ipcRenderer.send('eval', ` - const {net} = require('electron') - const http = require('http') - const url = require('url') - const netRequest = net.request('${server.url}${netRequestUrl}') - netRequest.on('response', function (netResponse) { - const serverUrl = url.parse('${server.url}') - const nodeOptions = { - method: 'POST', - path: '${nodeRequestUrl}', - port: serverUrl.port - } - let nodeRequest = http.request(nodeOptions) - nodeRequest.on('response', function (nodeResponse) { - nodeResponse.on('data', (chunk) => { - }) - nodeResponse.on('end', (chunk) => { - event.sender.send('api-net-spec-done') - }) - }) - netResponse.pipe(nodeRequest) - }) - netRequest.end() - `) - }) - - it('should not emit any event after close', (done) => { - const requestUrl = '/requestUrl' - const bodyData = randomString(kOneKiloByte) - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - response.statusCode = 200 - response.statusMessage = 'OK' - response.write(bodyData) - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - let requestCloseEventEmitted = false - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect(requestCloseEventEmitted).to.be.false() - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.pause() - response.on('data', () => { - }) - response.on('end', () => { - }) - response.resume() - response.on('error', () => { - expect(requestCloseEventEmitted).to.be.false() - }) - response.on('aborted', () => { - expect(requestCloseEventEmitted).to.be.false() - }) - }) - urlRequest.on('finish', () => { - expect(requestCloseEventEmitted).to.be.false() - }) - urlRequest.on('error', () => { - expect(requestCloseEventEmitted).to.be.false() - }) - urlRequest.on('abort', () => { - expect(requestCloseEventEmitted).to.be.false() - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - // Wait so that all async events get scheduled. - setTimeout(() => { - done() - }, 100) - }) - urlRequest.end() - }) - }) - - describe('Stability and performance', () => { - it('should free unreferenced, never-started request objects without crash', (done) => { - const requestUrl = '/requestUrl' - ipcRenderer.once('api-net-spec-done', () => { - done() - }) - ipcRenderer.send('eval', ` - const {net} = require('electron') - const urlRequest = net.request('${server.url}${requestUrl}') - process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - event.sender.send('api-net-spec-done') - }) - `) - }) - - it('should not collect on-going requests without crash', (done) => { - const requestUrl = '/requestUrl' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - response.statusCode = 200 - response.statusMessage = 'OK' - response.write(randomString(kOneKiloByte)) - ipcRenderer.once('api-net-spec-resume', () => { - response.write(randomString(kOneKiloByte)) - response.end() - }) - break - default: - handleUnexpectedURL(request, response) - } - }) - ipcRenderer.once('api-net-spec-done', () => { - done() - }) - // Execute below code directly within the browser context without - // using the remote module. - ipcRenderer.send('eval', ` - const {net} = require('electron') - const urlRequest = net.request('${server.url}${requestUrl}') - urlRequest.on('response', (response) => { - response.on('data', () => { - }) - response.on('end', () => { - event.sender.send('api-net-spec-done') - }) - process.nextTick(() => { - // Trigger a garbage collection. - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - event.sender.send('api-net-spec-resume') - }) - }) - urlRequest.end() - `) - }) - - it('should collect unreferenced, ended requests without crash', (done) => { - const requestUrl = '/requestUrl' - server.on('request', (request, response) => { - switch (request.url) { - case requestUrl: - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - break - default: - handleUnexpectedURL(request, response) - } - }) - ipcRenderer.once('api-net-spec-done', () => { - done() - }) - ipcRenderer.send('eval', ` - const {net} = require('electron') - const urlRequest = net.request('${server.url}${requestUrl}') - urlRequest.on('response', (response) => { - response.on('data', () => { - }) - response.on('end', () => { - }) - }) - urlRequest.on('close', () => { - process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - event.sender.send('api-net-spec-done') - }) - }) - urlRequest.end() - `) - }) - }) -}) - -function handleUnexpectedURL (request, response) { - response.statusCode = '500' - response.end() - expect.fail(`Unexpected URL: ${request.url}`) -}