From a5c508d2d715c4f269d79603f566d3ed0dfc032d Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 10 Oct 2016 16:21:12 +0200 Subject: [PATCH] Adding abort, webRequest interception and creation tests. --- lib/browser/api/net.js | 22 ++- spec/api-net-spec.js | 416 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 387 insertions(+), 51 deletions(-) diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 6761abed1992..36af3b08af52 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -98,7 +98,7 @@ URLRequest.prototype._emitRequestEvent = function (async, ...rest) { URLRequest.prototype._emitResponseEvent = function (async, ...rest) { if (async) { process.nextTick(() => { - this._request.emit.apply(this._request, rest) + this._request.emit.apply(this._response, rest) }) } else { this._response.emit.apply(this._response, rest) @@ -141,6 +141,7 @@ class ClientRequest extends EventEmitter { } } + if (options.path && / /.test(options.path)) { // The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/ // with an additional rule for ignoring percentage-escaped characters @@ -149,10 +150,11 @@ class ClientRequest extends EventEmitter { // why it only scans for spaces because those are guaranteed to create // an invalid request. throw new TypeError('Request path contains unescaped characters.') - } - urlObj.pathname = options.pathname || '/' - urlObj.search = options.search - urlObj.hash = options.hash + } + let pathObj = url.parse(options.path || '/') + urlObj.pathname = pathObj.pathname + urlObj.search = pathObj.search + urlObj.hash = pathObj.hash urlStr = url.format(urlObj) } @@ -167,6 +169,11 @@ class ClientRequest extends EventEmitter { this._url_request = urlRequest urlRequest._request = this + // This is a copy of the extra headers structure held by the native + // net::URLRequest. The main reason is to keep the getHeader API synchronous + // after the request starts. + this._extra_headers = {} + if (options.headers) { const keys = Object.keys(options.headers) for (let i = 0, l = keys.length; i < l; i++) { @@ -179,11 +186,6 @@ class ClientRequest extends EventEmitter { // to true only once and never set back to false. this._chunkedEncoding = false - // This is a copy of the extra headers structure held by the native - // net::URLRequest. The main reason is to keep the getHeader API synchronous - // after the request starts. - this._extra_headers = {} - urlRequest.on('response', () => { const response = new IncomingMessage(urlRequest) urlRequest._response = response diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 8cba404a893a..9fe3623d78bc 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -3,6 +3,7 @@ const {remote} = require('electron') const http = require('http') const url = require('url') const {net} = remote +const {session} = remote function randomBuffer(size, start, end) { start = start || 0 @@ -24,7 +25,7 @@ const kOneKiloByte = 1024 const kOneMegaByte = kOneKiloByte * kOneKiloByte -describe.only('net module', function() { +describe('net module', function() { this.timeout(0) describe('HTTP basics', function() { @@ -52,9 +53,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request(`${server.url}${request_url}`) @@ -80,9 +79,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request({ @@ -113,9 +110,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request(`${server.url}${request_url}`) @@ -152,9 +147,7 @@ describe.only('net module', function() { }) break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request({ @@ -194,9 +187,7 @@ describe.only('net module', function() { }) break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request({ @@ -264,9 +255,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) let response_event_emitted = false; @@ -324,9 +313,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) @@ -408,9 +395,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request({ @@ -454,9 +439,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request({ @@ -495,9 +478,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request({ @@ -538,9 +519,7 @@ describe.only('net module', function() { response.end(); break; default: - response.statusCode = 501 - response.statusMessage = 'Not Implemented' - response.end() + assert(false) } }) const urlRequest = net.request({ @@ -570,20 +549,375 @@ describe.only('net module', function() { urlRequest.end(); }) + it('should be able to abort an HTTP request before first write', function() { + const request_url = '/request_url' + server.on('request', function(request, response) { + assert(false) + }) + + let request_abort_event_emitted = false + let request_close_event_emitted = false + + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + assert(false) + }) + urlRequest.on('finish', function() { + assert(false); + }) + urlRequest.on('error', function(error) { + assert(false); + }) + urlRequest.on('abort', function() { + request_abort_event_emitted = true + }) + urlRequest.on('close', function() { + request_close_event_emitted = true + assert(request_abort_event_emitted) + assert(request_close_event_emitted) + done(); + }) + urlRequest.abort() + assert(!urlRequest.write('')) + urlRequest.end(); + }) + + + it('it should be able to abort an HTTP request before request end', function(done) { + const request_url = '/request_url' + let request_received_by_server = false + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + request_received_by_server = true; + cancelRequest(); + break; + default: + assert(false) + } + }) + + let request_abort_event_emitted = false + let request_close_event_emitted = false + + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + assert(false) + }) + urlRequest.on('finish', function() { + assert(false) + }) + urlRequest.on('error', function(error) { + assert(false); + }) + urlRequest.on('abort', function() { + request_abort_event_emitted = true + }) + urlRequest.on('close', function() { + request_close_event_emitted = true + assert(request_received_by_server) + assert(request_abort_event_emitted) + assert(request_close_event_emitted) + 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', function(done) { + const request_url = '/request_url' + let request_received_by_server = false + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + request_received_by_server = true; + cancelRequest(); + process.nextTick( () => { + response.statusCode = 200 + response.statusMessage = 'OK' + response.end(); + }) + break; + default: + assert(false) + } + }) + + let request_abort_event_emitted = false + let request_finish_event_emitted = false + let request_close_event_emitted = false + + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + assert(false) + }) + urlRequest.on('finish', function() { + request_finish_event_emitted = true + }) + urlRequest.on('error', function(error) { + assert(false); + }) + urlRequest.on('abort', function() { + request_abort_event_emitted = true + }) + urlRequest.on('close', function() { + request_close_event_emitted = true + assert(request_finish_event_emitted) + assert(request_received_by_server) + assert(request_abort_event_emitted) + assert(request_close_event_emitted) + done() + }) + + urlRequest.end(randomString(kOneKiloByte)) + function cancelRequest() { + urlRequest.abort() + } + }) + + it('it should be able to abort an HTTP request after response start', function(done) { + const request_url = '/request_url' + let request_received_by_server = false + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + request_received_by_server = true; + response.statusCode = 200 + response.statusMessage = 'OK' + response.write(randomString(kOneKiloByte)) + break; + default: + assert(false) + } + }) + + let request_finish_event_emitted = false + let request_response_event_emitted = false + let request_abort_event_emitted = false + let request_close_event_emitted = false + let response_aborted_event_emitted = false - it ('should be able to abort an HTTP request', function() { + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + request_response_event_emitted = true + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause(); + response.on('data', function(chunk) { + }) + response.on('end', function() { + assert(false) + }) + response.resume(); + response.on('error', function(error) { + assert(false) + }) + response.on('aborted', function() { + response_aborted_event_emitted = true + }) + urlRequest.abort() + }) + urlRequest.on('finish', function() { + request_finish_event_emitted = true + }) + urlRequest.on('error', function(error) { + assert(false); + }) + urlRequest.on('abort', function() { + request_abort_event_emitted = true + }) + urlRequest.on('close', function() { + request_close_event_emitted = true + assert(request_finish_event_emitted, 'request should emit "finish" event') + assert(request_received_by_server, 'request should be received by the server') + assert(request_response_event_emitted, '"response" event should be emitted') + assert(request_abort_event_emitted, 'request should emit "abort" event') + assert(response_aborted_event_emitted, 'response should emit "aborted" event') + assert(request_close_event_emitted, 'request should emit "close" event') + done() + }) + urlRequest.end(randomString(kOneKiloByte)) + }) + + it('Requests should be intercepted by webRequest module', function(done) { + + const request_url = '/request_url' + const redirect_url = '/redirect_url' + let request_is_redirected = false; + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + assert(false) + break + case redirect_url: + request_is_redirected = true + response.end(); + break + default: + assert(false) + } + }) + + let request_is_intercepted = false + session.defaultSession.webRequest.onBeforeRequest( + function(details, callback){ + if (details.url === `${server.url}${request_url}`) { + request_is_intercepted = true + callback({ + redirectURL: `${server.url}${redirect_url}` + }) + } else { + callback( { + cancel: false + }) + } + }); + + const urlRequest = net.request(`${server.url}${request_url}`) + + urlRequest.on('response', function(response) { + assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }) + response.on('end', function() { + assert(request_is_redirected, 'The server should receive a request to the forward URL') + assert(request_is_intercepted, 'The request should be intercepted by the webRequest module') + done() + }) + response.resume() + }) + urlRequest.end(); + }) + + it('should to able to create and intercept a request on a custom session', function(done) { + const request_url = '/request_url' + const redirect_url = '/redirect_url' + const custom_session_name = 'custom-session' + let request_is_redirected = false; + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + assert(false) + break + case redirect_url: + request_is_redirected = true + response.end(); + break + default: + assert(false) + } + }) + + session.defaultSession.webRequest.onBeforeRequest( + function(details, callback) { + assert(false, 'Request should not be intercepted by the default session') + }); + + let custom_session = session.fromPartition(custom_session_name, { + cache: false + }) + let request_is_intercepted = false + custom_session.webRequest.onBeforeRequest( + function(details, callback){ + if (details.url === `${server.url}${request_url}`) { + request_is_intercepted = true + callback({ + redirectURL: `${server.url}${redirect_url}` + }) + } else { + callback( { + cancel: false + }) + } + }); + + const urlRequest = net.request({ + url: `${server.url}${request_url}`, + session: custom_session_name + }) + urlRequest.on('response', function(response) { + assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }) + response.on('end', function() { + assert(request_is_redirected, 'The server should receive a request to the forward URL') + assert(request_is_intercepted, 'The request should be intercepted by the webRequest module') + done() + }) + response.resume() + }) + urlRequest.end(); + }) + + it.only ('should be able to create a request with options', function() { + const request_url = '/' + const custom_header_name = 'Some-Custom-Header-Name' + const custom_header_value = 'Some-Customer-Header-Value' + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + assert.equal(request.method, 'GET') + assert.equal(request.headers[custom_header_name.toLowerCase()], + custom_header_value) + response.end(); + break; + default: + assert(false) + } + }) + + const server_url = url.parse(server.url) + let options = { + port: server_url.port, + headers: {} + } + options.headers[custom_header_name] = custom_header_value + const urlRequest = net.request(options) + urlRequest.on('response', function(response) { + assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }) + response.on('end', function() { + done() + }) + response.resume() + }) + urlRequest.end(); + }) + + it('abort request should be emitted at most once', function() { assert(false) }) + + it('headers cannot be manipulated after abort', function() { + assert(false) + }) + it ('should be able to pipe into a request', function() { assert(false) }) - it ('should be able to create a request with options', function() { - assert(false) - }) - it ('should be able to specify a custom session', function() { - assert(false) - }) + + }) describe('IncomingMessage API', function() { it('should provide a Node.js-similar API', function() {