From e8d4abe78fcaafccbf265ed33db7afe12f91d010 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 15 Sep 2016 15:59:40 +0200 Subject: [PATCH 01/62] Adding net module and URLRequest class. --- atom/browser/api/atom_api_net.cc | 65 ++++++++++++++++++++++++ atom/browser/api/atom_api_net.h | 37 ++++++++++++++ atom/browser/api/atom_api_url_request.cc | 39 ++++++++++++++ atom/browser/api/atom_api_url_request.h | 37 ++++++++++++++ atom/common/node_bindings.cc | 1 + filenames.gypi | 5 ++ lib/browser/api/exports/electron.js | 6 +++ lib/browser/api/net.js | 13 +++++ 8 files changed, 203 insertions(+) create mode 100644 atom/browser/api/atom_api_net.cc create mode 100644 atom/browser/api/atom_api_net.h create mode 100644 atom/browser/api/atom_api_url_request.cc create mode 100644 atom/browser/api/atom_api_url_request.h create mode 100644 lib/browser/api/net.js diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc new file mode 100644 index 0000000000..ee1b437392 --- /dev/null +++ b/atom/browser/api/atom_api_net.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_net.h" +#include "atom/browser/api/atom_api_url_request.h" +#include "native_mate/dictionary.h" +#include "atom/common/node_includes.h" + +namespace atom { + +namespace api { + +Net::Net(v8::Isolate* isolate) { + Init(isolate); +} + +Net::~Net() { +} + + +// static +v8::Local Net::Create(v8::Isolate* isolate) { + return mate::CreateHandle(isolate, new Net(isolate)).ToV8(); +} + +// static +void Net::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "Net")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetProperty("URLRequest", &Net::URLRequest); +} + +v8::Local Net::URLRequest(v8::Isolate* isolate) { + return URLRequest::GetConstructor(isolate)->GetFunction(); +} + + + +} // namespace api + +} // namespace atom + + +namespace { + +using atom::api::Net; +using atom::api::URLRequest; + +void Initialize(v8::Local exports, v8::Local unused, + v8::Local context, void* priv) { + + v8::Isolate* isolate = context->GetIsolate(); + + URLRequest::SetConstructor(isolate, base::Bind(URLRequest::New)); + + mate::Dictionary dict(isolate, exports); + dict.Set("net", Net::Create(isolate)); + +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_net, Initialize) \ No newline at end of file diff --git a/atom/browser/api/atom_api_net.h b/atom/browser/api/atom_api_net.h new file mode 100644 index 0000000000..dd97bb9653 --- /dev/null +++ b/atom/browser/api/atom_api_net.h @@ -0,0 +1,37 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_NET_H_ +#define ATOM_BROWSER_API_ATOM_API_NET_H_ + +#include "atom/browser/api/event_emitter.h" + +namespace atom { + +namespace api { + +class Net : public mate::EventEmitter { + +public: + static v8::Local Create(v8::Isolate* isolate); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + v8::Local URLRequest(v8::Isolate* isolate); +protected: + Net(v8::Isolate* isolate); + ~Net() override; + +private: + + DISALLOW_COPY_AND_ASSIGN(Net); +}; + +} // namespace api + +} // namespace atom + + +#endif // ATOM_BROWSER_API_ATOM_API_NET_H_ \ No newline at end of file diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc new file mode 100644 index 0000000000..8862fbaee2 --- /dev/null +++ b/atom/browser/api/atom_api_url_request.cc @@ -0,0 +1,39 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/api/atom_api_url_request.h" + +namespace atom { + +namespace api { + +URLRequest::URLRequest(v8::Isolate* isolate) { + Init(isolate); +} + +URLRequest::~URLRequest() { +} + +// static +mate::WrappableBase* URLRequest::New(mate::Arguments* args) { + + return new URLRequest(args->isolate()); +} + + +// static +void URLRequest::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "WebRequest")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .SetMethod("start", &URLRequest::start); +} + +void URLRequest::start() { + +} + +} // namespace mate + +} // namepsace mate \ No newline at end of file diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h new file mode 100644 index 0000000000..3d07b0712b --- /dev/null +++ b/atom/browser/api/atom_api_url_request.h @@ -0,0 +1,37 @@ +// Copyright (c) 2013 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ +#define ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ + +#include "atom/browser/api/trackable_object.h" +#include "native_mate/handle.h" + +namespace atom { + +namespace api { + + +class URLRequest : public mate::TrackableObject { + public: + static mate::WrappableBase* New(mate::Arguments* args); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + void start(); + + protected: + URLRequest(v8::Isolate* isolate); + ~URLRequest() override; + + private: + DISALLOW_COPY_AND_ASSIGN(URLRequest); +}; + +} // namepsace api + +} // namepsace atom + +#endif // ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ \ No newline at end of file diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index a049be23a6..e869d2a703 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -39,6 +39,7 @@ REFERENCE_MODULE(atom_browser_debugger); REFERENCE_MODULE(atom_browser_desktop_capturer); REFERENCE_MODULE(atom_browser_download_item); REFERENCE_MODULE(atom_browser_menu); +REFERENCE_MODULE(atom_browser_net); REFERENCE_MODULE(atom_browser_power_monitor); REFERENCE_MODULE(atom_browser_power_save_blocker); REFERENCE_MODULE(atom_browser_protocol); diff --git a/filenames.gypi b/filenames.gypi index 9c4b42f622..db6bd23d68 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -23,6 +23,7 @@ 'lib/browser/api/menu-item.js', 'lib/browser/api/menu-item-roles.js', 'lib/browser/api/navigation-controller.js', + 'lib/browser/api/net.js', 'lib/browser/api/power-monitor.js', 'lib/browser/api/power-save-blocker.js', 'lib/browser/api/protocol.js', @@ -116,6 +117,8 @@ 'atom/browser/api/atom_api_menu_views.h', 'atom/browser/api/atom_api_menu_mac.h', 'atom/browser/api/atom_api_menu_mac.mm', + 'atom/browser/api/atom_api_net.cc', + 'atom/browser/api/atom_api_net.h', 'atom/browser/api/atom_api_power_monitor.cc', 'atom/browser/api/atom_api_power_monitor.h', 'atom/browser/api/atom_api_power_save_blocker.cc', @@ -134,6 +137,8 @@ 'atom/browser/api/atom_api_system_preferences_win.cc', 'atom/browser/api/atom_api_tray.cc', 'atom/browser/api/atom_api_tray.h', + 'atom/browser/api/atom_api_url_request.cc', + 'atom/browser/api/atom_api_url_request.h', 'atom/browser/api/atom_api_web_contents.cc', 'atom/browser/api/atom_api_web_contents.h', 'atom/browser/api/atom_api_web_contents_mac.mm', diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index c47c046906..a96f5a711b 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -107,6 +107,12 @@ Object.defineProperties(exports, { return require('../web-contents') } }, + //net: { + // enumerable: true, + // get: function () { + // return require('../net') + // } + //}, // The internal modules, invisible unless you know their names. NavigationController: { diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js new file mode 100644 index 0000000000..98e8104e92 --- /dev/null +++ b/lib/browser/api/net.js @@ -0,0 +1,13 @@ +'use strict' + +const binding = process.atomBinding('net') +const {URLRequest} = binding + +// Public API. +Object.defineProperties(exports, { + URLRequest: { + enumerable: true, + value: URLRequest + } +}) + \ No newline at end of file From 7521aeea09c994e4afef3c00d457246054798fee Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 19 Sep 2016 11:21:09 +0200 Subject: [PATCH 02/62] Implement URLRequest::Delegate, handle thread sync. --- atom/browser/api/atom_api_net.cc | 1 + atom/browser/api/atom_api_url_request.cc | 67 ++++++++++++++++++-- atom/browser/api/atom_api_url_request.h | 21 ++++-- atom/browser/net/atom_url_request.cc | 81 ++++++++++++++++++++++++ atom/browser/net/atom_url_request.h | 52 +++++++++++++++ filenames.gypi | 2 + lib/browser/api/exports/electron.js | 12 ++-- lib/browser/api/net.js | 15 ++--- 8 files changed, 229 insertions(+), 22 deletions(-) create mode 100644 atom/browser/net/atom_url_request.cc create mode 100644 atom/browser/net/atom_url_request.h diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index ee1b437392..1559cee4ca 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -57,6 +57,7 @@ void Initialize(v8::Local exports, v8::Local unused, mate::Dictionary dict(isolate, exports); dict.Set("net", Net::Create(isolate)); + dict.Set("Net", Net::GetConstructor(isolate)->GetFunction()); } diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 8862fbaee2..44b4581ef4 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -3,13 +3,20 @@ // found in the LICENSE file. #include "atom/browser/api/atom_api_url_request.h" +#include "atom/browser/api/atom_api_session.h" + +#include "native_mate/dictionary.h" +#include "atom/browser/net/atom_url_request.h" + namespace atom { namespace api { -URLRequest::URLRequest(v8::Isolate* isolate) { - Init(isolate); +URLRequest::URLRequest(v8::Isolate* isolate, + v8::Local wrapper) + : weak_ptr_factory_(this) { + InitWith(isolate, wrapper); } URLRequest::~URLRequest() { @@ -18,20 +25,72 @@ URLRequest::~URLRequest() { // static mate::WrappableBase* URLRequest::New(mate::Arguments* args) { - return new URLRequest(args->isolate()); + v8::Local options; + args->GetNext(&options); + mate::Dictionary dict(args->isolate(), options); + std::string url; + dict.Get("url", &url); + std::string method; + dict.Get("method", &method); + std::string session_name; + dict.Get("session", &session_name); + + auto session = Session::FromPartition(args->isolate(), session_name); + + auto browser_context = session->browser_context(); + + //auto url_request_context_getter = browser_context->url_request_context_getter(); + // auto url_request_context = url_request_context_getter->GetURLRequestContext(); + + //auto net_url_request = url_request_context->CreateRequest(GURL(url), + // net::RequestPriority::DEFAULT_PRIORITY, + // nullptr); + // net_url_request->set_method(method); + + // auto atom_url_request = new URLRequest(args->isolate(), args->GetThis(), net_url_request.release()); + + auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); + auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr(); + auto atom_url_request = AtomURLRequest::create(browser_context, url, weak_ptr); + + atom_url_request->set_method(method); + + api_url_request->atom_url_request_ = atom_url_request; + + + return api_url_request; } // static void URLRequest::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { - prototype->SetClassName(mate::StringToV8(isolate, "WebRequest")); + prototype->SetClassName(mate::StringToV8(isolate, "URLRequest")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .MakeDestroyable() .SetMethod("start", &URLRequest::start); } void URLRequest::start() { + pin(); + atom_url_request_->Start(); +} +void URLRequest::stop() { + +} +void URLRequest::OnResponseStarted() { + Emit("response-started"); +} + +void URLRequest::pin() { + if (wrapper_.IsEmpty()) { + wrapper_.Reset(isolate(), GetWrapper()); + } +} + +void URLRequest::unpin() { + wrapper_.Reset(); } } // namespace mate diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 3d07b0712b..474b13a748 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -7,13 +7,15 @@ #include "atom/browser/api/trackable_object.h" #include "native_mate/handle.h" +#include "net/url_request/url_request_context.h" namespace atom { +class AtomURLRequest; + namespace api { - -class URLRequest : public mate::TrackableObject { +class URLRequest : public mate::EventEmitter { public: static mate::WrappableBase* New(mate::Arguments* args); @@ -21,12 +23,23 @@ class URLRequest : public mate::TrackableObject { v8::Local prototype); void start(); - + void stop(); + void OnResponseStarted(); protected: - URLRequest(v8::Isolate* isolate); + URLRequest(v8::Isolate* isolate, + v8::Local wrapper); ~URLRequest() override; + + private: + void pin(); + void unpin(); + + scoped_refptr atom_url_request_; + v8::Global wrapper_; + base::WeakPtrFactory weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(URLRequest); }; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc new file mode 100644 index 0000000000..50aee48653 --- /dev/null +++ b/atom/browser/net/atom_url_request.cc @@ -0,0 +1,81 @@ +// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/net/atom_url_request.h" +#include "atom/browser/api/atom_api_url_request.h" +#include "atom/browser/atom_browser_context.h" +#include "base/callback.h" +#include "content/public/browser/browser_thread.h" + +namespace atom { + +AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) + : delegate_(delegate) { +} + +AtomURLRequest::~AtomURLRequest() { +} + +scoped_refptr AtomURLRequest::create( + AtomBrowserContext* browser_context, + const std::string& url, + base::WeakPtr delegate) { + + + auto url_request_context_getter = browser_context->url_request_context_getter(); + auto url_request_context = url_request_context_getter->GetURLRequestContext(); + + auto net_url_request = url_request_context->CreateRequest(GURL(url), + net::RequestPriority::DEFAULT_PRIORITY, + nullptr); + // net_url_request->set_method(method); + + scoped_refptr atom_url_request = new AtomURLRequest(delegate); + + net_url_request->set_delegate(atom_url_request.get()); + + atom_url_request->url_request_ = std::move(net_url_request); + + return atom_url_request; + +} + +void AtomURLRequest::Start() { + // post to io thread + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::StartOnIOThread, this)); +} + +void AtomURLRequest::StartOnIOThread() { + url_request_->Start(); +} + + +void AtomURLRequest::set_method(const std::string& method) { + url_request_->set_method(method); +} + +void AtomURLRequest::OnResponseStarted(net::URLRequest* request) +{ + // post to main thread + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegeteResponseStarted, this)); +} + +void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) +{ + // post to main thread +} + +void AtomURLRequest::InformDelegeteResponseStarted() { + if (delegate_) { + delegate_->OnResponseStarted(); + } +} + + +} // namespace atom \ No newline at end of file diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h new file mode 100644 index 0000000000..e581008eb4 --- /dev/null +++ b/atom/browser/net/atom_url_request.h @@ -0,0 +1,52 @@ +// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NET_ATOM_URL_REQUEST_H_ +#define ATOM_BROWSER_NET_ATOM_URL_REQUEST_H_ + +#include "base/memory/ref_counted.h" +#include "net/url_request/url_request.h" + +namespace atom { + +class AtomBrowserContext; + +namespace api { + class URLRequest; +} + +class AtomURLRequest : public base::RefCountedThreadSafe, + public net::URLRequest::Delegate { +public: + static scoped_refptr create( + AtomBrowserContext* browser_context, + const std::string& url, + base::WeakPtr delegate); + + void Start(); + void set_method(const std::string& method); + +protected: + // Overrides of net::URLRequest::Delegate + virtual void OnResponseStarted(net::URLRequest* request) override; + virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) override; + +private: + friend class base::RefCountedThreadSafe; + void StartOnIOThread(); + void InformDelegeteResponseStarted(); + + AtomURLRequest(base::WeakPtr delegate); + virtual ~AtomURLRequest(); + + base::WeakPtr delegate_; + std::unique_ptr url_request_; + + DISALLOW_COPY_AND_ASSIGN(AtomURLRequest); + }; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_ATOM_URL_REQUEST_H_ \ No newline at end of file diff --git a/filenames.gypi b/filenames.gypi index db6bd23d68..20375c6f4e 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -238,6 +238,8 @@ 'atom/browser/net/atom_network_delegate.h', 'atom/browser/net/atom_ssl_config_service.cc', 'atom/browser/net/atom_ssl_config_service.h', + 'atom/browser/net/atom_url_request.cc', + 'atom/browser/net/atom_url_request.h', 'atom/browser/net/atom_url_request_job_factory.cc', 'atom/browser/net/atom_url_request_job_factory.h', 'atom/browser/net/http_protocol_handler.cc', diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index a96f5a711b..09d5b0e47a 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -107,12 +107,12 @@ Object.defineProperties(exports, { return require('../web-contents') } }, - //net: { - // enumerable: true, - // get: function () { - // return require('../net') - // } - //}, + net: { + enumerable: true, + get: function () { + return require('../net') + } + }, // The internal modules, invisible unless you know their names. NavigationController: { diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 98e8104e92..c443a8ee9e 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -1,13 +1,12 @@ 'use strict' +const {EventEmitter} = require('events') const binding = process.atomBinding('net') -const {URLRequest} = binding +const {net, Net} = binding +const {URLRequest} = net -// Public API. -Object.defineProperties(exports, { - URLRequest: { - enumerable: true, - value: URLRequest - } -}) +Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) +Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) + +module.exports = net \ No newline at end of file From 81eab9887be50ed653ea1d7819890363e4fa4b72 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 19 Sep 2016 15:06:13 +0200 Subject: [PATCH 03/62] Adding URLResponse to emit response events, implementing status code. --- atom/browser/api/atom_api_menu.cc | 3 +- atom/browser/api/atom_api_url_request.cc | 78 +++++++++++++++++++----- atom/browser/api/atom_api_url_request.h | 22 +++++-- atom/browser/net/atom_url_request.cc | 59 +++++++++++++++--- atom/browser/net/atom_url_request.h | 13 +++- lib/browser/api/net.js | 42 +++++++++++++ 6 files changed, 185 insertions(+), 32 deletions(-) diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 627ce601f5..0de2dffc77 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -191,7 +191,8 @@ using atom::api::Menu; void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); - Menu::SetConstructor(isolate, base::Bind(&Menu::New)); + Menu::thehub + SetConstructor(isolate, base::Bind(&Menu::New)); mate::Dictionary dict(isolate, exports); dict.Set("Menu", Menu::GetConstructor(isolate)->GetFunction()); diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 44b4581ef4..f31c07cbba 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -39,16 +39,7 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { auto browser_context = session->browser_context(); - //auto url_request_context_getter = browser_context->url_request_context_getter(); - // auto url_request_context = url_request_context_getter->GetURLRequestContext(); - //auto net_url_request = url_request_context->CreateRequest(GURL(url), - // net::RequestPriority::DEFAULT_PRIORITY, - // nullptr); - // net_url_request->set_method(method); - - // auto atom_url_request = new URLRequest(args->isolate(), args->GetThis(), net_url_request.release()); - auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr(); auto atom_url_request = AtomURLRequest::create(browser_context, url, weak_ptr); @@ -67,20 +58,75 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "URLRequest")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) - .MakeDestroyable() - .SetMethod("start", &URLRequest::start); + // Request API + .MakeDestroyable() + .SetMethod("write", &URLRequest::Write) + .SetMethod("end", &URLRequest::End) + .SetMethod("abort", &URLRequest::Abort) + .SetMethod("setHeader", &URLRequest::SetHeader) + .SetMethod("getHeader", &URLRequest::GetHeader) + .SetMethod("removaHeader", &URLRequest::RemoveHeader) + // Response APi + .SetProperty("statusCode", &URLRequest::StatusCode) + .SetProperty("statusMessage", &URLRequest::StatusMessage) + .SetProperty("responseHeaders", &URLRequest::ResponseHeaders) + .SetProperty("responseHttpVersion", &URLRequest::ResponseHttpVersion); } -void URLRequest::start() { +void URLRequest::Write() { + atom_url_request_->Write(); +} + +void URLRequest::End() { pin(); - atom_url_request_->Start(); + atom_url_request_->End(); } -void URLRequest::stop() { - +void URLRequest::Abort() { + atom_url_request_->Abort(); } + +void URLRequest::SetHeader() { + atom_url_request_->SetHeader(); +} +void URLRequest::GetHeader() { + atom_url_request_->GetHeader(); +} +void URLRequest::RemoveHeader() { + atom_url_request_->RemoveHeader(); +} + + void URLRequest::OnResponseStarted() { - Emit("response-started"); + v8::Local _emitResponse; + + auto wrapper = GetWrapper(); + if (mate::Dictionary(isolate(), wrapper).Get("_emitResponse", &_emitResponse)) + _emitResponse->Call(wrapper, 0, nullptr); +} + +void URLRequest::OnResponseData() { + Emit("data"); +} + +void URLRequest::OnResponseEnd() { + Emit("end"); +} + +int URLRequest::StatusCode() { + return atom_url_request_->StatusCode(); +} + +void URLRequest::StatusMessage() { + return atom_url_request_->StatusMessage(); +} + +void URLRequest::ResponseHeaders() { + return atom_url_request_->ResponseHeaders(); +} + +void URLRequest::ResponseHttpVersion() { + return atom_url_request_->ResponseHttpVersion(); } void URLRequest::pin() { diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 474b13a748..172cc51e47 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -22,17 +22,31 @@ class URLRequest : public mate::EventEmitter { static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); - void start(); - void stop(); - void OnResponseStarted(); protected: URLRequest(v8::Isolate* isolate, v8::Local wrapper); ~URLRequest() override; +private: + void Write(); + void End(); + void Abort(); + void SetHeader(); + void GetHeader(); + void RemoveHeader(); + + friend class AtomURLRequest; + void OnResponseStarted(); + void OnResponseData(); + void OnResponseEnd(); + + int StatusCode(); + void StatusMessage(); + void ResponseHeaders(); + void ResponseHttpVersion(); + - private: void pin(); void unpin(); diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 50aee48653..1770d01ca6 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -23,32 +23,71 @@ scoped_refptr AtomURLRequest::create( const std::string& url, base::WeakPtr delegate) { + DCHECK(browser_context); + DCHECK(!url.empty()); - auto url_request_context_getter = browser_context->url_request_context_getter(); - auto url_request_context = url_request_context_getter->GetURLRequestContext(); + auto request_context_getter = browser_context->url_request_context_getter(); - auto net_url_request = url_request_context->CreateRequest(GURL(url), - net::RequestPriority::DEFAULT_PRIORITY, - nullptr); - // net_url_request->set_method(method); + DCHECK(request_context_getter); + + auto context = request_context_getter->GetURLRequestContext(); + + DCHECK(context); scoped_refptr atom_url_request = new AtomURLRequest(delegate); - net_url_request->set_delegate(atom_url_request.get()); - - atom_url_request->url_request_ = std::move(net_url_request); + atom_url_request->url_request_ = context->CreateRequest(GURL(url), + net::RequestPriority::DEFAULT_PRIORITY, + atom_url_request.get()); return atom_url_request; } -void AtomURLRequest::Start() { +void AtomURLRequest::Write() { +} + +void AtomURLRequest::End() { // post to io thread content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::StartOnIOThread, this)); } +void AtomURLRequest::Abort() { +} + +void AtomURLRequest::SetHeader() { + +} + +void AtomURLRequest::GetHeader() { + +} + +void AtomURLRequest::RemoveHeader() { + +} + + + + + + +int AtomURLRequest::StatusCode() { + return url_request_->GetResponseCode(); +} + +void AtomURLRequest::StatusMessage() { +} +void AtomURLRequest::ResponseHeaders() { +} + +void AtomURLRequest::ResponseHttpVersion() { +} + + + void AtomURLRequest::StartOnIOThread() { url_request_->Start(); } diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index e581008eb4..d9a45885e6 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -25,9 +25,20 @@ public: const std::string& url, base::WeakPtr delegate); - void Start(); void set_method(const std::string& method); + void Write(); + void End(); + void Abort(); + void SetHeader(); + void GetHeader(); + void RemoveHeader(); + + int StatusCode(); + void StatusMessage(); + void ResponseHeaders(); + void ResponseHttpVersion(); + protected: // Overrides of net::URLRequest::Delegate virtual void OnResponseStarted(net::URLRequest* request) override; diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index c443a8ee9e..517c13bada 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -8,5 +8,47 @@ const {URLRequest} = net Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) +class URLResponse extends EventEmitter { + constructor(request) { + super(); + this.request = request; + } + + get statusCode() { + return this.request.statusCode; + } + + get statusMessage() { + return this.request.statusMessage; + } + + get headers() { + return this.request.responseHeaders; + } + + get httpVersion() { + return this.request.responseHttpVersion; + } + + + +} + +Net.prototype.request = function(options, callback) { + let request = new URLRequest(options) + + if (callback) { + request.once('response', callback) + } + + + return request +} + +URLRequest.prototype._emitResponse = function() { + this.response = new URLResponse(this); + this.emit('response', this.response); +} + module.exports = net \ No newline at end of file From 2d9d4af98dca29fafa0b800b5245d93ab69f3b3e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 21 Sep 2016 09:23:00 +0200 Subject: [PATCH 04/62] Implementing URLRequest API, getting response body. --- atom/browser/api/atom_api_menu.cc | 3 +- atom/browser/api/atom_api_url_request.cc | 112 +++++++++++++++---- atom/browser/api/atom_api_url_request.h | 57 +++++++++- atom/browser/net/atom_url_request.cc | 130 +++++++++++++++++++---- atom/browser/net/atom_url_request.h | 22 +++- lib/browser/api/net.js | 65 ++++++++---- 6 files changed, 313 insertions(+), 76 deletions(-) diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 0de2dffc77..627ce601f5 100644 --- a/atom/browser/api/atom_api_menu.cc +++ b/atom/browser/api/atom_api_menu.cc @@ -191,8 +191,7 @@ using atom::api::Menu; void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { v8::Isolate* isolate = context->GetIsolate(); - Menu::thehub - SetConstructor(isolate, base::Bind(&Menu::New)); + Menu::SetConstructor(isolate, base::Bind(&Menu::New)); mate::Dictionary dict(isolate, exports); dict.Set("Menu", Menu::GetConstructor(isolate)->GetFunction()); diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index f31c07cbba..5e71f243c4 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -7,12 +7,49 @@ #include "native_mate/dictionary.h" #include "atom/browser/net/atom_url_request.h" +#include "atom/common/node_includes.h" +namespace { +const char* const kResponse = "response"; +const char* const kData = "data"; +const char* const kEnd = "end"; + +} +namespace mate { + +template<> +struct Converter> { + static v8::Local ToV8(v8::Isolate* isolate, + scoped_refptr val) { + + mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); + if (val) { + size_t iter = 0; + std::string name; + std::string value; + while (val->EnumerateHeaderLines(&iter, &name, &value)) { + dict.Set(name, value); + } + } + return dict.GetHandle(); + } +}; + +template<> +struct Converter> { + static v8::Local ToV8( + v8::Isolate* isolate, + scoped_refptr buffer) { + return node::Buffer::Copy(isolate, buffer->data(), buffer->size()).ToLocalChecked(); + } +}; + +} namespace atom { namespace api { - + URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) : weak_ptr_factory_(this) { @@ -69,8 +106,11 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, // Response APi .SetProperty("statusCode", &URLRequest::StatusCode) .SetProperty("statusMessage", &URLRequest::StatusMessage) - .SetProperty("responseHeaders", &URLRequest::ResponseHeaders) - .SetProperty("responseHttpVersion", &URLRequest::ResponseHttpVersion); + .SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders) + .SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) + .SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); + + } void URLRequest::Write() { @@ -98,35 +138,71 @@ void URLRequest::RemoveHeader() { void URLRequest::OnResponseStarted() { - v8::Local _emitResponse; + //v8::Local _emitResponse; - auto wrapper = GetWrapper(); - if (mate::Dictionary(isolate(), wrapper).Get("_emitResponse", &_emitResponse)) - _emitResponse->Call(wrapper, 0, nullptr); + //auto wrapper = GetWrapper(); + //if (mate::Dictionary(isolate(), wrapper).Get("_emitResponse", &_emitResponse)) + // _emitResponse->Call(wrapper, 0, nullptr); + EmitRequestEvent("response"); } -void URLRequest::OnResponseData() { - Emit("data"); +void URLRequest::OnResponseData(scoped_refptr buffer) { + if (!buffer || !buffer->data() || !buffer->size()) { + return; + } + + EmitResponseEvent("data", buffer); + //v8::Local _emitData; + //auto data = mate::ConvertToV8(isolate(), buffer); + + //auto wrapper = GetWrapper(); + //if (mate::Dictionary(isolate(), wrapper).Get("_emitData", &_emitData)) + // _emitData->Call(wrapper, 1, &data); } -void URLRequest::OnResponseEnd() { - Emit("end"); +void URLRequest::OnResponseCompleted() { + + //v8::Local _emitEnd; + + //auto wrapper = GetWrapper(); + //if (mate::Dictionary(isolate(), wrapper).Get("_emitEnd", &_emitEnd)) + // _emitEnd->Call(wrapper, 0, nullptr); + + EmitResponseEvent("end"); } + int URLRequest::StatusCode() { - return atom_url_request_->StatusCode(); + if (auto response_headers = atom_url_request_->GetResponseHeaders()) { + return response_headers->response_code(); + } + return -1; } -void URLRequest::StatusMessage() { - return atom_url_request_->StatusMessage(); +std::string URLRequest::StatusMessage() { + std::string result; + if (auto response_headers = atom_url_request_->GetResponseHeaders()) { + result = response_headers->GetStatusText(); + } + return result; } -void URLRequest::ResponseHeaders() { - return atom_url_request_->ResponseHeaders(); +scoped_refptr URLRequest::RawResponseHeaders() { + return atom_url_request_->GetResponseHeaders(); } -void URLRequest::ResponseHttpVersion() { - return atom_url_request_->ResponseHttpVersion(); +uint32_t URLRequest::ResponseHttpVersionMajor() { + if (auto response_headers = atom_url_request_->GetResponseHeaders()) { + return response_headers->GetHttpVersion().major_value(); + } + return 0; +} + +uint32_t URLRequest::ResponseHttpVersionMinor() { + if (auto response_headers = atom_url_request_->GetResponseHeaders()) { + return response_headers->GetHttpVersion().minor_value(); + } + return 0; } void URLRequest::pin() { diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 172cc51e47..1ab4f1cc91 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -5,9 +5,12 @@ #ifndef ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ #define ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ +#include #include "atom/browser/api/trackable_object.h" #include "native_mate/handle.h" #include "net/url_request/url_request_context.h" +#include "net/http/http_response_headers.h" + namespace atom { @@ -38,13 +41,26 @@ private: friend class AtomURLRequest; void OnResponseStarted(); - void OnResponseData(); - void OnResponseEnd(); + void OnResponseData(scoped_refptr data); + void OnResponseCompleted(); int StatusCode(); - void StatusMessage(); - void ResponseHeaders(); - void ResponseHttpVersion(); + std::string StatusMessage(); + scoped_refptr RawResponseHeaders(); + uint32_t ResponseHttpVersionMajor(); + uint32_t ResponseHttpVersionMinor(); + + + template + std::array, sizeof...(ArgTypes)> + BuildArgsArray(ArgTypes... args); + + template + void EmitRequestEvent(ArgTypes... args); + + template + void EmitResponseEvent(ArgTypes... args); + void pin(); @@ -53,10 +69,41 @@ private: scoped_refptr atom_url_request_; v8::Global wrapper_; base::WeakPtrFactory weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(URLRequest); }; +template +std::array, sizeof...(ArgTypes)> +URLRequest::BuildArgsArray(ArgTypes... args) { + std::array, sizeof...(ArgTypes)> result + = { mate::ConvertToV8(isolate(), args)... }; + return result; +} + +template +void URLRequest::EmitRequestEvent(ArgTypes... args) { + auto arguments = BuildArgsArray(args...); + v8::Local _emitRequestEvent; + auto wrapper = GetWrapper(); + if (mate::Dictionary(isolate(), wrapper).Get("_emitRequestEvent", &_emitRequestEvent)) + _emitRequestEvent->Call(wrapper, arguments.size(), arguments.data()); +} + + +template +void URLRequest::EmitResponseEvent(ArgTypes... args) { + auto arguments = BuildArgsArray(args...); + v8::Local _emitResponseEvent; + auto wrapper = GetWrapper(); + if (mate::Dictionary(isolate(), wrapper).Get("_emitResponseEvent", &_emitResponseEvent)) + _emitResponseEvent->Call(wrapper, arguments.size(), arguments.data()); +} + + + + } // namepsace api } // namepsace atom diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 1770d01ca6..bdabbfc9fa 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -8,11 +8,19 @@ #include "atom/browser/atom_browser_context.h" #include "base/callback.h" #include "content/public/browser/browser_thread.h" +#include "net/base/io_buffer.h" + +namespace { + +const int kBufferSize = 4096; + +} // namespace namespace atom { AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) - : delegate_(delegate) { + : delegate_(delegate) + , buffer_( new net::IOBuffer(kBufferSize)) { } AtomURLRequest::~AtomURLRequest() { @@ -48,7 +56,7 @@ void AtomURLRequest::Write() { } void AtomURLRequest::End() { - // post to io thread + // Called on content::BrowserThread::UI content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::StartOnIOThread, this)); @@ -71,24 +79,15 @@ void AtomURLRequest::RemoveHeader() { - - - -int AtomURLRequest::StatusCode() { - return url_request_->GetResponseCode(); -} - -void AtomURLRequest::StatusMessage() { -} -void AtomURLRequest::ResponseHeaders() { -} - -void AtomURLRequest::ResponseHttpVersion() { +scoped_refptr AtomURLRequest::GetResponseHeaders() { + return url_request_->response_headers(); } void AtomURLRequest::StartOnIOThread() { + // Called on content::BrowserThread::IO + url_request_->Start(); } @@ -97,24 +96,109 @@ void AtomURLRequest::set_method(const std::string& method) { url_request_->set_method(method); } -void AtomURLRequest::OnResponseStarted(net::URLRequest* request) -{ - // post to main thread +void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { + // Called on content::BrowserThread::IO + + DCHECK_EQ(request, url_request_.get()); + + if (url_request_->status().is_success()) { + // Cache net::HttpResponseHeaders instance, a read-only objects + // so that headers and other http metainformation can be simultaneously + // read from UI thread while request data is simulataneously streaming + // on IO thread. + response_headers_ = url_request_->response_headers(); + } + content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegeteResponseStarted, this)); + base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this)); + + ReadResponse(); } -void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) -{ - // post to main thread +void AtomURLRequest::ReadResponse() { + + // Called on content::BrowserThread::IO + + // Some servers may treat HEAD requests as GET requests. To free up the + // network connection as soon as possible, signal that the request has + // completed immediately, without trying to read any data back (all we care + // about is the response code and headers, which we already have). + int bytes_read = 0; + if (url_request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) { + if (!url_request_->Read(buffer_.get(), kBufferSize, &bytes_read)) + bytes_read = -1; + } + OnReadCompleted(url_request_.get(), bytes_read); } -void AtomURLRequest::InformDelegeteResponseStarted() { + +void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { + // Called on content::BrowserThread::IO + + DCHECK_EQ(request, url_request_.get()); + + do { + if (!url_request_->status().is_success() || bytes_read <= 0) + break; + + + const auto result = CopyAndPostBuffer(bytes_read); + if (!result) { + // Failed to transfer data to UI thread. + return; + } + } while (url_request_->Read(buffer_.get(), kBufferSize, &bytes_read)); + + const auto status = url_request_->status(); + + if (!status.is_io_pending() /* TODO || request_type_ == URLFetcher::HEAD*/ ) { + + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); + } + +} + + +bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { + // Called on content::BrowserThread::IO. + + // data is only a wrapper for the async buffer_. + // Make a deep copy of payload and transfer ownership to the UI thread. + scoped_refptr buffer_copy(new net::IOBufferWithSize(bytes_read)); + memcpy(buffer_copy->data(), buffer_->data(), bytes_read); + + return content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseData, this, buffer_copy)); +} + + +void AtomURLRequest::InformDelegateResponseStarted() { + // Called on content::BrowserThread::UI. + if (delegate_) { delegate_->OnResponseStarted(); } } +void AtomURLRequest::InformDelegateResponseData(scoped_refptr data) { + // Called on content::BrowserThread::IO. + + // Transfer ownership of the data buffer, data will be released + // by the delegate's OnResponseData. + if (delegate_) { + delegate_->OnResponseData(data); + } +} + +void AtomURLRequest::InformDelegateResponseCompleted() { + if (delegate_) { + delegate_->OnResponseCompleted(); + } +} + } // namespace atom \ No newline at end of file diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index d9a45885e6..1aeffd5560 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -9,6 +9,13 @@ #include "base/memory/ref_counted.h" #include "net/url_request/url_request.h" + +namespace net { +class IOBuffer; +class IOBufferWithSize; +class DrainableIOBuffer; +} + namespace atom { class AtomBrowserContext; @@ -34,10 +41,7 @@ public: void GetHeader(); void RemoveHeader(); - int StatusCode(); - void StatusMessage(); - void ResponseHeaders(); - void ResponseHttpVersion(); + scoped_refptr GetResponseHeaders(); protected: // Overrides of net::URLRequest::Delegate @@ -47,13 +51,21 @@ protected: private: friend class base::RefCountedThreadSafe; void StartOnIOThread(); - void InformDelegeteResponseStarted(); + + void ReadResponse(); + bool CopyAndPostBuffer(int bytes_read); + + void InformDelegateResponseStarted(); + void InformDelegateResponseData(scoped_refptr data); + void InformDelegateResponseCompleted(); AtomURLRequest(base::WeakPtr delegate); virtual ~AtomURLRequest(); base::WeakPtr delegate_; std::unique_ptr url_request_; + scoped_refptr buffer_; + scoped_refptr response_headers_; DISALLOW_COPY_AND_ASSIGN(AtomURLRequest); }; diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 517c13bada..96c448020b 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -9,46 +9,65 @@ Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) class URLResponse extends EventEmitter { - constructor(request) { - super(); - this.request = request; - } + constructor(request) { + super(); + this.request = request; + } - get statusCode() { - return this.request.statusCode; - } + get statusCode() { + return this.request.statusCode; + } - get statusMessage() { - return this.request.statusMessage; - } + get statusMessage() { + return this.request.statusMessage; + } - get headers() { - return this.request.responseHeaders; - } + get headers() { + return this.request.rawResponseHeaders; + } - get httpVersion() { - return this.request.responseHttpVersion; - } + get httpVersion() { + return `${this.httpVersionMajor}.${this.httpVersionMinor}`; + } + get httpVersionMajor() { + return this.request.httpVersionMajor; + } + get httpVersionMinor() { + return this.request.httpVersionMinor; + } + + get rawHeaders() { + return this.request.rawResponseHeaders; + } } Net.prototype.request = function(options, callback) { - let request = new URLRequest(options) + let request = new URLRequest(options) - if (callback) { - request.once('response', callback) - } + if (callback) { + request.once('response', callback) + } - return request + return request } -URLRequest.prototype._emitResponse = function() { +URLRequest.prototype._emitRequestEvent = function(name) { + if (name === 'response') { this.response = new URLResponse(this); - this.emit('response', this.response); + this.emit(name, this.response); + } else { + this.emit.apply(this, arguments); + } } +URLRequest.prototype._emitResponseEvent = function() { + this.response.emit.apply(this.response, arguments); +} + + module.exports = net \ No newline at end of file From 2b3b41d5f93f8899850bcf3ca9ab42152c72667e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 21 Sep 2016 17:35:03 +0200 Subject: [PATCH 05/62] Implementing authentication callback. --- atom/browser/api/atom_api_url_request.cc | 98 +++++++-------- atom/browser/api/atom_api_url_request.h | 28 ++--- atom/browser/net/atom_url_request.cc | 153 ++++++++++++++++------- atom/browser/net/atom_url_request.h | 43 ++++--- lib/browser/api/net.js | 119 ++++++++++++++++++ 5 files changed, 312 insertions(+), 129 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 5e71f243c4..0b4939f2b9 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -8,6 +8,9 @@ #include "native_mate/dictionary.h" #include "atom/browser/net/atom_url_request.h" #include "atom/common/node_includes.h" +#include "atom/common/native_mate_converters/net_converter.h" +#include "atom/common/native_mate_converters/string16_converter.h" +#include "atom/common/native_mate_converters/callback.h" namespace { @@ -19,9 +22,9 @@ const char* const kEnd = "end"; namespace mate { template<> -struct Converter> { +struct Converter> { static v8::Local ToV8(v8::Isolate* isolate, - scoped_refptr val) { + scoped_refptr val) { mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); if (val) { @@ -37,10 +40,10 @@ struct Converter> { }; template<> -struct Converter> { +struct Converter> { static v8::Local ToV8( v8::Isolate* isolate, - scoped_refptr buffer) { + scoped_refptr buffer) { return node::Buffer::Copy(isolate, buffer->data(), buffer->size()).ToLocalChecked(); } }; @@ -79,11 +82,13 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr(); - auto atom_url_request = AtomURLRequest::create(browser_context, url, weak_ptr); - - atom_url_request->set_method(method); + auto atom_url_request = AtomURLRequest::Create( + browser_context, + method, + url, + weak_ptr); - api_url_request->atom_url_request_ = atom_url_request; + api_url_request->atom_request_ = atom_url_request; return api_url_request; @@ -97,12 +102,12 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) // Request API .MakeDestroyable() - .SetMethod("write", &URLRequest::Write) - .SetMethod("end", &URLRequest::End) + .SetMethod("_write", &URLRequest::Write) + .SetMethod("_end", &URLRequest::End) .SetMethod("abort", &URLRequest::Abort) - .SetMethod("setHeader", &URLRequest::SetHeader) - .SetMethod("getHeader", &URLRequest::GetHeader) - .SetMethod("removaHeader", &URLRequest::RemoveHeader) + .SetMethod("_setHeader", &URLRequest::SetHeader) + .SetMethod("_getHeader", &URLRequest::GetHeader) + .SetMethod("_removaHeader", &URLRequest::RemoveHeader) // Response APi .SetProperty("statusCode", &URLRequest::StatusCode) .SetProperty("statusMessage", &URLRequest::StatusMessage) @@ -114,92 +119,83 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, } void URLRequest::Write() { - atom_url_request_->Write(); + atom_request_->Write(); } void URLRequest::End() { pin(); - atom_url_request_->End(); + atom_request_->End(); } void URLRequest::Abort() { - atom_url_request_->Abort(); + atom_request_->Abort(); } -void URLRequest::SetHeader() { - atom_url_request_->SetHeader(); +void URLRequest::SetHeader(const std::string& name, const std::string& value) { + atom_request_->SetHeader(name, value); } -void URLRequest::GetHeader() { - atom_url_request_->GetHeader(); +std::string URLRequest::GetHeader(const std::string& name) { + return atom_request_->GetHeader(name); } -void URLRequest::RemoveHeader() { - atom_url_request_->RemoveHeader(); +void URLRequest::RemoveHeader(const std::string& name) { + atom_request_->RemoveHeader(name); } +void URLRequest::OnAuthenticationRequired( + scoped_refptr auth_info) { + EmitRequestEvent( + "login", + auth_info.get(), + base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); +} + void URLRequest::OnResponseStarted() { - //v8::Local _emitResponse; - - //auto wrapper = GetWrapper(); - //if (mate::Dictionary(isolate(), wrapper).Get("_emitResponse", &_emitResponse)) - // _emitResponse->Call(wrapper, 0, nullptr); EmitRequestEvent("response"); } -void URLRequest::OnResponseData(scoped_refptr buffer) { +void URLRequest::OnResponseData( + scoped_refptr buffer) { if (!buffer || !buffer->data() || !buffer->size()) { return; } EmitResponseEvent("data", buffer); - //v8::Local _emitData; - //auto data = mate::ConvertToV8(isolate(), buffer); - - //auto wrapper = GetWrapper(); - //if (mate::Dictionary(isolate(), wrapper).Get("_emitData", &_emitData)) - // _emitData->Call(wrapper, 1, &data); } void URLRequest::OnResponseCompleted() { - - //v8::Local _emitEnd; - - //auto wrapper = GetWrapper(); - //if (mate::Dictionary(isolate(), wrapper).Get("_emitEnd", &_emitEnd)) - // _emitEnd->Call(wrapper, 0, nullptr); - EmitResponseEvent("end"); } -int URLRequest::StatusCode() { - if (auto response_headers = atom_url_request_->GetResponseHeaders()) { +int URLRequest::StatusCode() const { + if (auto response_headers = atom_request_->GetResponseHeaders()) { return response_headers->response_code(); } return -1; } -std::string URLRequest::StatusMessage() { +std::string URLRequest::StatusMessage() const { std::string result; - if (auto response_headers = atom_url_request_->GetResponseHeaders()) { + if (auto response_headers = atom_request_->GetResponseHeaders()) { result = response_headers->GetStatusText(); } return result; } -scoped_refptr URLRequest::RawResponseHeaders() { - return atom_url_request_->GetResponseHeaders(); +scoped_refptr URLRequest::RawResponseHeaders() const { + return atom_request_->GetResponseHeaders(); } -uint32_t URLRequest::ResponseHttpVersionMajor() { - if (auto response_headers = atom_url_request_->GetResponseHeaders()) { +uint32_t URLRequest::ResponseHttpVersionMajor() const { + if (auto response_headers = atom_request_->GetResponseHeaders()) { return response_headers->GetHttpVersion().major_value(); } return 0; } -uint32_t URLRequest::ResponseHttpVersionMinor() { - if (auto response_headers = atom_url_request_->GetResponseHeaders()) { +uint32_t URLRequest::ResponseHttpVersionMinor() const { + if (auto response_headers = atom_request_->GetResponseHeaders()) { return response_headers->GetHttpVersion().minor_value(); } return 0; diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 1ab4f1cc91..7fd5c6ee92 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -35,25 +35,27 @@ private: void Write(); void End(); void Abort(); - void SetHeader(); - void GetHeader(); - void RemoveHeader(); + void SetHeader(const std::string& name, const std::string& value); + std::string GetHeader(const std::string& name); + void RemoveHeader(const std::string& name); friend class AtomURLRequest; + void OnAuthenticationRequired( + scoped_refptr auth_info); void OnResponseStarted(); - void OnResponseData(scoped_refptr data); + void OnResponseData(scoped_refptr data); void OnResponseCompleted(); - int StatusCode(); - std::string StatusMessage(); - scoped_refptr RawResponseHeaders(); - uint32_t ResponseHttpVersionMajor(); - uint32_t ResponseHttpVersionMinor(); + int StatusCode() const; + std::string StatusMessage() const; + scoped_refptr RawResponseHeaders() const; + uint32_t ResponseHttpVersionMajor() const; + uint32_t ResponseHttpVersionMinor() const; template std::array, sizeof...(ArgTypes)> - BuildArgsArray(ArgTypes... args); + BuildArgsArray(ArgTypes... args) const; template void EmitRequestEvent(ArgTypes... args); @@ -61,12 +63,10 @@ private: template void EmitResponseEvent(ArgTypes... args); - - void pin(); void unpin(); - scoped_refptr atom_url_request_; + scoped_refptr atom_request_; v8::Global wrapper_; base::WeakPtrFactory weak_ptr_factory_; @@ -76,7 +76,7 @@ private: template std::array, sizeof...(ArgTypes)> -URLRequest::BuildArgsArray(ArgTypes... args) { +URLRequest::BuildArgsArray(ArgTypes... args) const { std::array, sizeof...(ArgTypes)> result = { mate::ConvertToV8(isolate(), args)... }; return result; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index bdabbfc9fa..9c825888aa 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -10,6 +10,7 @@ #include "content/public/browser/browser_thread.h" #include "net/base/io_buffer.h" + namespace { const int kBufferSize = 4096; @@ -18,19 +19,20 @@ const int kBufferSize = 4096; namespace atom { -AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) +AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) : delegate_(delegate) - , buffer_( new net::IOBuffer(kBufferSize)) { + , buffer_(new net::IOBuffer(kBufferSize)) { } AtomURLRequest::~AtomURLRequest() { } -scoped_refptr AtomURLRequest::create( +scoped_refptr AtomURLRequest::Create( AtomBrowserContext* browser_context, + const std::string& method, const std::string& url, base::WeakPtr delegate) { - + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(browser_context); DCHECK(!url.empty()); @@ -44,69 +46,117 @@ scoped_refptr AtomURLRequest::create( scoped_refptr atom_url_request = new AtomURLRequest(delegate); - atom_url_request->url_request_ = context->CreateRequest(GURL(url), + atom_url_request->request_ = context->CreateRequest(GURL(url), net::RequestPriority::DEFAULT_PRIORITY, atom_url_request.get()); + atom_url_request->request_->set_method(method); return atom_url_request; } -void AtomURLRequest::Write() { +void AtomURLRequest::Write() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); } -void AtomURLRequest::End() { - // Called on content::BrowserThread::UI +void AtomURLRequest::End() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, - base::Bind(&AtomURLRequest::StartOnIOThread, this)); + base::Bind(&AtomURLRequest::DoStart, this)); } -void AtomURLRequest::Abort() { +void AtomURLRequest::Abort() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); } -void AtomURLRequest::SetHeader() { - +void AtomURLRequest::SetHeader(const std::string& name, + const std::string& value) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + request_->SetExtraRequestHeaderByName(name, value, true); } -void AtomURLRequest::GetHeader() { - +std::string AtomURLRequest::GetHeader(const std::string& name) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + std::string result; + const auto& extra_headers = request_->extra_request_headers(); + if (!extra_headers.GetHeader(name, &result)) { + net::HttpRequestHeaders* request_headers = nullptr; + if (request_->GetFullRequestHeaders(request_headers) && request_headers) { + request_headers->GetHeader(name, &result); + } + } + return result; } -void AtomURLRequest::RemoveHeader() { - +void AtomURLRequest::RemoveHeader(const std::string& name) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + request_->RemoveRequestHeaderByName(name); } -scoped_refptr AtomURLRequest::GetResponseHeaders() { - return url_request_->response_headers(); +scoped_refptr +AtomURLRequest::GetResponseHeaders() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + return request_->response_headers(); } - -void AtomURLRequest::StartOnIOThread() { - // Called on content::BrowserThread::IO - - url_request_->Start(); +void AtomURLRequest::PassLoginInformation(const base::string16& username, + const base::string16& password) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (username.empty() || password.empty()) { + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoCancelAuth, this)); + } + else { + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoSetAuth, this, username, password)); + } } -void AtomURLRequest::set_method(const std::string& method) { - url_request_->set_method(method); +void AtomURLRequest::DoStart() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + request_->Start(); +} + +void AtomURLRequest::DoSetAuth(const base::string16& username, + const base::string16& password) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + request_->SetAuth(net::AuthCredentials(username, password)); +} + +void AtomURLRequest::DoCancelAuth() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + request_->CancelAuth(); +} + +void AtomURLRequest::OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired, + this, + scoped_refptr(auth_info))); } void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { - // Called on content::BrowserThread::IO + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + DCHECK_EQ(request, request_.get()); - DCHECK_EQ(request, url_request_.get()); - - if (url_request_->status().is_success()) { + if (request_->status().is_success()) { // Cache net::HttpResponseHeaders instance, a read-only objects // so that headers and other http metainformation can be simultaneously // read from UI thread while request data is simulataneously streaming // on IO thread. - response_headers_ = url_request_->response_headers(); + response_headers_ = request_->response_headers(); } content::BrowserThread::PostTask( @@ -117,29 +167,28 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { } void AtomURLRequest::ReadResponse() { - - // Called on content::BrowserThread::IO + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); // Some servers may treat HEAD requests as GET requests. To free up the // network connection as soon as possible, signal that the request has // completed immediately, without trying to read any data back (all we care // about is the response code and headers, which we already have). int bytes_read = 0; - if (url_request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) { - if (!url_request_->Read(buffer_.get(), kBufferSize, &bytes_read)) + if (request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) { + if (!request_->Read(buffer_.get(), kBufferSize, &bytes_read)) bytes_read = -1; } - OnReadCompleted(url_request_.get(), bytes_read); + OnReadCompleted(request_.get(), bytes_read); } void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { - // Called on content::BrowserThread::IO + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - DCHECK_EQ(request, url_request_.get()); + DCHECK_EQ(request, request_.get()); do { - if (!url_request_->status().is_success() || bytes_read <= 0) + if (!request_->status().is_success() || bytes_read <= 0) break; @@ -148,9 +197,9 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { // Failed to transfer data to UI thread. return; } - } while (url_request_->Read(buffer_.get(), kBufferSize, &bytes_read)); + } while (request_->Read(buffer_.get(), kBufferSize, &bytes_read)); - const auto status = url_request_->status(); + const auto status = request_->status(); if (!status.is_io_pending() /* TODO || request_type_ == URLFetcher::HEAD*/ ) { @@ -161,9 +210,8 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { } - bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { - // Called on content::BrowserThread::IO. + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); // data is only a wrapper for the async buffer_. // Make a deep copy of payload and transfer ownership to the UI thread. @@ -176,16 +224,25 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { } -void AtomURLRequest::InformDelegateResponseStarted() { - // Called on content::BrowserThread::UI. +void AtomURLRequest::InformDelegateAuthenticationRequired( + scoped_refptr auth_info) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + if (delegate_) { + delegate_->OnAuthenticationRequired(auth_info); + } +} + +void AtomURLRequest::InformDelegateResponseStarted() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) { delegate_->OnResponseStarted(); } } -void AtomURLRequest::InformDelegateResponseData(scoped_refptr data) { - // Called on content::BrowserThread::IO. +void AtomURLRequest::InformDelegateResponseData( + scoped_refptr data) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Transfer ownership of the data buffer, data will be released // by the delegate's OnResponseData. @@ -194,7 +251,9 @@ void AtomURLRequest::InformDelegateResponseData(scoped_refptrOnResponseCompleted(); } diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 1aeffd5560..be3c270ff9 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -27,43 +27,52 @@ namespace api { class AtomURLRequest : public base::RefCountedThreadSafe, public net::URLRequest::Delegate { public: - static scoped_refptr create( + static scoped_refptr Create( AtomBrowserContext* browser_context, + const std::string& method, const std::string& url, base::WeakPtr delegate); - void set_method(const std::string& method); - - void Write(); - void End(); - void Abort(); - void SetHeader(); - void GetHeader(); - void RemoveHeader(); - - scoped_refptr GetResponseHeaders(); + void Write() const; + void End() const; + void Abort() const; + void SetHeader(const std::string& name, const std::string& value) const; + std::string GetHeader(const std::string& name) const; + void RemoveHeader(const std::string& name) const; + void PassLoginInformation(const base::string16& username, + const base::string16& password) const; + scoped_refptr GetResponseHeaders() const; protected: // Overrides of net::URLRequest::Delegate + virtual void OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) override; virtual void OnResponseStarted(net::URLRequest* request) override; - virtual void OnReadCompleted(net::URLRequest* request, int bytes_read) override; + virtual void OnReadCompleted(net::URLRequest* request, + int bytes_read) override; private: friend class base::RefCountedThreadSafe; - void StartOnIOThread(); + void DoStart() const; + void DoSetAuth(const base::string16& username, + const base::string16& password) const; + void DoCancelAuth() const; void ReadResponse(); bool CopyAndPostBuffer(int bytes_read); - void InformDelegateResponseStarted(); - void InformDelegateResponseData(scoped_refptr data); - void InformDelegateResponseCompleted(); + void InformDelegateAuthenticationRequired( + scoped_refptr auth_info) const; + void InformDelegateResponseStarted() const; + void InformDelegateResponseData( + scoped_refptr data) const; + void InformDelegateResponseCompleted() const; AtomURLRequest(base::WeakPtr delegate); virtual ~AtomURLRequest(); base::WeakPtr delegate_; - std::unique_ptr url_request_; + std::unique_ptr request_; scoped_refptr buffer_; scoped_refptr response_headers_; diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 96c448020b..a476cdf87a 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -55,6 +55,48 @@ Net.prototype.request = function(options, callback) { return request } +URLRequest.prototype._init = function() { + this._finished = false; + this._hasBody = true; + this._chunkedEncoding = false; + this._headersSent = false; +} + + +URLRequest.prototype.setHeader = function(name, value) { + if (typeof name !== 'string') + throw new TypeError('`name` should be a string in setHeader(name, value).'); + if (value === undefined) + throw new Error('`value` required in setHeader("' + name + '", value).'); + if (this._headersSent) + throw new Error('Can\'t set headers after they are sent.'); + + this._setHeader(name, value) +}; + + +URLRequest.prototype.getHeader = function(name) { + if (arguments.length < 1) { + throw new Error('`name` is required for getHeader(name).'); + } + + return this._getHeader(name); +}; + + +URLRequest.prototype.removeHeader = function(name) { + if (arguments.length < 1) { + throw new Error('`name` is required for removeHeader(name).'); + } + + if (this._headersSent) { + throw new Error('Can\'t remove headers after they are sent.'); + } + + this._removeHeader(name); +}; + + URLRequest.prototype._emitRequestEvent = function(name) { if (name === 'response') { this.response = new URLResponse(this); @@ -69,5 +111,82 @@ URLRequest.prototype._emitResponseEvent = function() { } +URLRequest.prototype.write = function(chunk, encoding, callback) { + + if (this.finished) { + var err = new Error('write after end'); + process.nextTick(writeAfterEndNT, this, err, callback); + + return true; + } + + /* TODO + if (!this._header) { + this._implicitHeader(); + } + */ + + if (!this._hasBody) { + //debug('This type of response MUST NOT have a body. ' + + // 'Ignoring write() calls.'); + return true; + } + + + if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { + throw new TypeError('First argument must be a string or Buffer'); + } + + + // If we get an empty string or buffer, then just do nothing, and + // signal the user to keep writing. + if (chunk.length === 0) return true; + + var len, ret; + if (this.chunkedEncoding) { + if (typeof chunk === 'string' && + encoding !== 'hex' && + encoding !== 'base64' && + encoding !== 'binary') { + len = Buffer.byteLength(chunk, encoding); + chunk = len.toString(16) + CRLF + chunk + CRLF; + ret = this._send(chunk, encoding, callback); + } else { + // buffer, or a non-toString-friendly encoding + if (typeof chunk === 'string') + len = Buffer.byteLength(chunk, encoding); + else + len = chunk.length; + + if (this.connection && !this.connection.corked) { + this.connection.cork(); + process.nextTick(connectionCorkNT, this.connection); + } + this._send(len.toString(16), 'binary', null); + this._send(crlf_buf, null, null); + this._send(chunk, encoding, null); + ret = this._send(crlf_buf, null, callback); + } + } else { + ret = this._send(chunk, encoding, callback); + } + + //debug('write ret = ' + ret); + return ret; +}; + + + +URLRequest.prototype.end = function() { + this._end(); +}; + + +function writeAfterEndNT(self, err, callback) { + self.emit('error', err); + if (callback) callback(err); +} + + module.exports = net \ No newline at end of file From f7525d7877ebbf4fc9c0b67f15716072d8cd7d0f Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 26 Sep 2016 14:03:49 +0200 Subject: [PATCH 06/62] Adding support for POST requests. --- atom/browser/api/atom_api_url_request.cc | 59 +++++++++-- atom/browser/api/atom_api_url_request.h | 7 +- atom/browser/net/atom_url_request.cc | 112 +++++++++++++++++--- atom/browser/net/atom_url_request.h | 20 ++-- lib/browser/api/net.js | 129 +++++++++++------------ 5 files changed, 230 insertions(+), 97 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 0b4939f2b9..5ded4cd52f 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -41,11 +41,45 @@ struct Converter> { template<> struct Converter> { + static v8::Local ToV8( v8::Isolate* isolate, scoped_refptr buffer) { return node::Buffer::Copy(isolate, buffer->data(), buffer->size()).ToLocalChecked(); } + + static bool FromV8(v8::Isolate* isolate, v8::Local val, + scoped_refptr* out) { + + auto size = node::Buffer::Length(val); + + if (size == 0) { + // Support conversoin from empty buffer. A use case is + // a GET request without body. + // Since zero-sized IOBuffer(s) are not supported, we set the + // out pointer to null. + *out = nullptr; + return true; + } + + auto data = node::Buffer::Data(val); + if (!data) { + // This is an error as size is positif but data is null. + return false; + } + + auto io_buffer = new net::IOBufferWithSize(size); + if (!io_buffer) { + // Assuming allocation failed. + return false; + } + + // We do a deep copy. We could have used Buffer's internal memory + // but that is much more complicated to be properly handled. + memcpy(io_buffer->data(), data, size); + *out = io_buffer; + return true; + } }; } @@ -102,8 +136,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) // Request API .MakeDestroyable() - .SetMethod("_write", &URLRequest::Write) - .SetMethod("_end", &URLRequest::End) + .SetMethod("_writeBuffer", &URLRequest::WriteBuffer) .SetMethod("abort", &URLRequest::Abort) .SetMethod("_setHeader", &URLRequest::SetHeader) .SetMethod("_getHeader", &URLRequest::GetHeader) @@ -118,21 +151,27 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, } -void URLRequest::Write() { - atom_request_->Write(); +bool URLRequest::WriteBuffer(scoped_refptr buffer, bool is_last) { + atom_request_->WriteBuffer(buffer, is_last); + return true; } -void URLRequest::End() { - pin(); - atom_request_->End(); -} void URLRequest::Abort() { atom_request_->Abort(); } -void URLRequest::SetHeader(const std::string& name, const std::string& value) { +bool URLRequest::SetHeader(const std::string& name, const std::string& value) { + if (!net::HttpUtil::IsValidHeaderName(name)) { + return false; + } + + if (!net::HttpUtil::IsValidHeaderValue(value)) { + return false; + } + atom_request_->SetHeader(name, value); + return true; } std::string URLRequest::GetHeader(const std::string& name) { return atom_request_->GetHeader(name); @@ -165,6 +204,8 @@ void URLRequest::OnResponseData( void URLRequest::OnResponseCompleted() { EmitResponseEvent("end"); + unpin(); + atom_request_ = nullptr; } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 7fd5c6ee92..3831348f78 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -32,10 +32,9 @@ class URLRequest : public mate::EventEmitter { private: - void Write(); - void End(); + bool WriteBuffer(scoped_refptr buffer, bool is_last); void Abort(); - void SetHeader(const std::string& name, const std::string& value); + bool SetHeader(const std::string& name, const std::string& value); std::string GetHeader(const std::string& name); void RemoveHeader(const std::string& name); @@ -66,7 +65,7 @@ private: void pin(); void unpin(); - scoped_refptr atom_request_; + scoped_refptr atom_request_; v8::Global wrapper_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 9c825888aa..09d787a51f 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -9,6 +9,8 @@ #include "base/callback.h" #include "content/public/browser/browser_thread.h" #include "net/base/io_buffer.h" +#include "net/base/elements_upload_data_stream.h" +#include "net/base/upload_bytes_element_reader.h" namespace { @@ -19,9 +21,37 @@ const int kBufferSize = 4096; namespace atom { +namespace internal { + + +class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { +public: + explicit UploadOwnedIOBufferElementReader( + scoped_refptr buffer) + : net::UploadBytesElementReader(buffer->data(), buffer->size()) + , buffer_(buffer) { + } + ~UploadOwnedIOBufferElementReader() override { + } + + + static UploadOwnedIOBufferElementReader* CreateWithBuffer( + scoped_refptr buffer) { + return new UploadOwnedIOBufferElementReader(std::move(buffer)); + } + +private: + scoped_refptr buffer_; + + DISALLOW_COPY_AND_ASSIGN(UploadOwnedIOBufferElementReader); +}; + +} + AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) : delegate_(delegate) - , buffer_(new net::IOBuffer(kBufferSize)) { + , response_read_buffer_(new net::IOBuffer(kBufferSize)) + , is_chunked_upload_(is_chunked_upload_) { } AtomURLRequest::~AtomURLRequest() { @@ -55,17 +85,25 @@ scoped_refptr AtomURLRequest::Create( } -void AtomURLRequest::Write() const { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); -} -void AtomURLRequest::End() const { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); +void AtomURLRequest::WriteBuffer(scoped_refptr buffer, + bool is_last) { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, - base::Bind(&AtomURLRequest::DoStart, this)); + base::Bind(&AtomURLRequest::DoWriteBuffer, this, buffer, is_last)); } +void AtomURLRequest::SetChunkedUpload() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + // The method can be called only before switching to multi-threaded mode, + // i.e. before the first call to write. + // So it is safe to change the object in the UI thread. + is_chunked_upload_ = true; +} + + void AtomURLRequest::Abort() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); } @@ -119,10 +157,58 @@ void AtomURLRequest::PassLoginInformation(const base::string16& username, } -void AtomURLRequest::DoStart() const { +void AtomURLRequest::DoWriteBuffer(scoped_refptr buffer, + bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - request_->Start(); + if (is_chunked_upload_) { + + // Chunked encoding case. + + bool first_call = false; + if (!chunked_stream_writer_) { + std::unique_ptr chunked_stream( + new net::ChunkedUploadDataStream(0)); + chunked_stream_writer_ = chunked_stream->CreateWriter(); + request_->set_upload(std::move(chunked_stream)); + first_call = true; + } + + if (buffer) { + // Non-empty buffer. + auto write_result = chunked_stream_writer_->AppendData( + buffer->data(), + buffer->size(), + is_last); + } + else if (is_last) { + // Empty buffer and last chunck, i.e. request.end(). + auto write_result = chunked_stream_writer_->AppendData( + nullptr, + 0, + true); + } + + if (first_call) { + request_->Start(); + } + } + else { + + if (buffer) { + // Handling potential empty buffers. + std::unique_ptr element_reader(internal::UploadOwnedIOBufferElementReader + ::CreateWithBuffer(std::move(buffer))); + upload_element_readers_.push_back(std::move(element_reader)); + } + + if (is_last) { + std::unique_ptr elements_upload_data_stream( + new net::ElementsUploadDataStream(std::move(upload_element_readers_), 0)); + request_->set_upload(std::move(elements_upload_data_stream)); + request_->Start(); + } + } } void AtomURLRequest::DoSetAuth(const base::string16& username, @@ -175,7 +261,7 @@ void AtomURLRequest::ReadResponse() { // about is the response code and headers, which we already have). int bytes_read = 0; if (request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) { - if (!request_->Read(buffer_.get(), kBufferSize, &bytes_read)) + if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) bytes_read = -1; } OnReadCompleted(request_.get(), bytes_read); @@ -197,7 +283,7 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { // Failed to transfer data to UI thread. return; } - } while (request_->Read(buffer_.get(), kBufferSize, &bytes_read)); + } while (request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)); const auto status = request_->status(); @@ -213,10 +299,10 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - // data is only a wrapper for the async buffer_. + // data is only a wrapper for the async response_read_buffer_. // Make a deep copy of payload and transfer ownership to the UI thread. scoped_refptr buffer_copy(new net::IOBufferWithSize(bytes_read)); - memcpy(buffer_copy->data(), buffer_->data(), bytes_read); + memcpy(buffer_copy->data(), response_read_buffer_->data(), bytes_read); return content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index be3c270ff9..3a923a002e 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -8,20 +8,21 @@ #include "base/memory/ref_counted.h" #include "net/url_request/url_request.h" +#include "net/base/chunked_upload_data_stream.h" namespace net { class IOBuffer; class IOBufferWithSize; class DrainableIOBuffer; -} +}; namespace atom { class AtomBrowserContext; namespace api { - class URLRequest; +class URLRequest; } class AtomURLRequest : public base::RefCountedThreadSafe, @@ -33,8 +34,8 @@ public: const std::string& url, base::WeakPtr delegate); - void Write() const; - void End() const; + void WriteBuffer(scoped_refptr buffer, bool is_last); + void SetChunkedUpload(); void Abort() const; void SetHeader(const std::string& name, const std::string& value) const; std::string GetHeader(const std::string& name) const; @@ -53,7 +54,7 @@ protected: private: friend class base::RefCountedThreadSafe; - void DoStart() const; + void DoWriteBuffer(scoped_refptr buffer, bool is_last); void DoSetAuth(const base::string16& username, const base::string16& password) const; void DoCancelAuth() const; @@ -73,7 +74,14 @@ private: base::WeakPtr delegate_; std::unique_ptr request_; - scoped_refptr buffer_; + + bool is_chunked_upload_; + std::unique_ptr chunked_stream_; + std::unique_ptr chunked_stream_writer_; + + std::vector>upload_element_readers_; + + scoped_refptr response_read_buffer_; scoped_refptr response_headers_; DISALLOW_COPY_AND_ASSIGN(AtomURLRequest); diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index a476cdf87a..d1d8cb7e7c 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -56,10 +56,16 @@ Net.prototype.request = function(options, callback) { } URLRequest.prototype._init = function() { - this._finished = false; - this._hasBody = true; + // Flag to prevent writings after end. + this._finished = false; + + // Set when the request uses chuned encoding. Can be switched + // to true only once and never set back to false. this._chunkedEncoding = false; - this._headersSent = false; + + // Flag to prevent request's headers modifications after + // headers flush. + this._headersSent = false; } @@ -111,80 +117,73 @@ URLRequest.prototype._emitResponseEvent = function() { } -URLRequest.prototype.write = function(chunk, encoding, callback) { - - if (this.finished) { - var err = new Error('write after end'); - process.nextTick(writeAfterEndNT, this, err, callback); - - return true; - } - - /* TODO - if (!this._header) { - this._implicitHeader(); - } - */ - - if (!this._hasBody) { - //debug('This type of response MUST NOT have a body. ' + - // 'Ignoring write() calls.'); - return true; - } +URLRequest.prototype._write = function(chunk, encoding, callback, is_last) { - - if (typeof chunk !== 'string' && !(chunk instanceof Buffer)) { - throw new TypeError('First argument must be a string or Buffer'); + let chunk_is_string = typeof chunk === 'string'; + let chunk_is_buffer = chunk instanceof Buffer; + if (!chunk_is_string && !chunk_is_buffer) { + throw new TypeError('First argument must be a string or Buffer.'); } - - // If we get an empty string or buffer, then just do nothing, and - // signal the user to keep writing. - if (chunk.length === 0) return true; - - var len, ret; - if (this.chunkedEncoding) { - if (typeof chunk === 'string' && - encoding !== 'hex' && - encoding !== 'base64' && - encoding !== 'binary') { - len = Buffer.byteLength(chunk, encoding); - chunk = len.toString(16) + CRLF + chunk + CRLF; - ret = this._send(chunk, encoding, callback); - } else { - // buffer, or a non-toString-friendly encoding - if (typeof chunk === 'string') - len = Buffer.byteLength(chunk, encoding); - else - len = chunk.length; - - if (this.connection && !this.connection.corked) { - this.connection.cork(); - process.nextTick(connectionCorkNT, this.connection); - } - this._send(len.toString(16), 'binary', null); - this._send(crlf_buf, null, null); - this._send(chunk, encoding, null); - ret = this._send(crlf_buf, null, callback); - } - } else { - ret = this._send(chunk, encoding, callback); + if (chunk_is_string) { + // We convert all strings into binary buffers. + chunk = Buffer.from(chunk, encoding); } - //debug('write ret = ' + ret); - return ret; + // Headers are assumed to be sent on first call to _writeBuffer, + // i.e. after the first call to write or end. + let result = this._writeBuffer(chunk, is_last); + + // Since writing to the network is asynchronous, we conservatively + // assume that request headers are written after delivering the first + // buffer to the network IO thread. + this._headersSent = true; + + // The write callback is fired asynchronously to mimic Node.js. + if (callback) { + process.nextTick(callback); + } + + return result; +} + +URLRequest.prototype.write = function(data, encoding, callback) { + + if (this._finished) { + let error = new Error('Write after end.'); + process.nextTick(writeAfterEndNT, this, error, callback); + return true; + } + + return this._write(data, encoding, callback, false); }; +URLRequest.prototype.end = function(data, encoding, callback) { + if (this._finished) { + return false; + } + + this._finished = true; -URLRequest.prototype.end = function() { - this._end(); + if (typeof data === 'function') { + callback = data; + encoding = null; + data = null; + } else if (typeof encoding === 'function') { + callback = encoding; + encoding = null; + } + + data = data || ''; + + return this._write(data, encoding, callback, true); }; -function writeAfterEndNT(self, err, callback) { - self.emit('error', err); - if (callback) callback(err); +function writeAfterEndNT(self, error, callback) { + self.emit('error', error); + if (callback) callback(error); } From fcaf9cb0312ac28fe0c8d32d66db4e9750da2db2 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 26 Sep 2016 14:59:53 +0200 Subject: [PATCH 07/62] Some code cleaning. --- atom/browser/api/atom_api_url_request.cc | 53 ++++++++++--------- atom/browser/api/atom_api_url_request.h | 9 ++-- atom/browser/net/atom_url_request.cc | 65 ++++++++++++++---------- atom/browser/net/atom_url_request.h | 6 ++- lib/browser/api/net.js | 12 ++--- 5 files changed, 85 insertions(+), 60 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 5ded4cd52f..623487fa67 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -23,8 +23,9 @@ namespace mate { template<> struct Converter> { - static v8::Local ToV8(v8::Isolate* isolate, - scoped_refptr val) { + static v8::Local ToV8( + v8::Isolate* isolate, + scoped_refptr val) { mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); if (val) { @@ -41,15 +42,18 @@ struct Converter> { template<> struct Converter> { - static v8::Local ToV8( - v8::Isolate* isolate, - scoped_refptr buffer) { - return node::Buffer::Copy(isolate, buffer->data(), buffer->size()).ToLocalChecked(); + v8::Isolate* isolate, + scoped_refptr buffer) { + return node::Buffer::Copy(isolate, + buffer->data(), + buffer->size()).ToLocalChecked(); } - static bool FromV8(v8::Isolate* isolate, v8::Local val, - scoped_refptr* out) { + static bool FromV8( + v8::Isolate* isolate, + v8::Local val, + scoped_refptr* out) { auto size = node::Buffer::Length(val); @@ -87,10 +91,9 @@ namespace atom { namespace api { -URLRequest::URLRequest(v8::Isolate* isolate, - v8::Local wrapper) +URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) : weak_ptr_factory_(this) { - InitWith(isolate, wrapper); + InitWith(isolate, wrapper); } URLRequest::~URLRequest() { @@ -142,18 +145,20 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, .SetMethod("_getHeader", &URLRequest::GetHeader) .SetMethod("_removaHeader", &URLRequest::RemoveHeader) // Response APi - .SetProperty("statusCode", &URLRequest::StatusCode) - .SetProperty("statusMessage", &URLRequest::StatusMessage) - .SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders) - .SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) - .SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); + .SetProperty("_statusCode", &URLRequest::StatusCode) + .SetProperty("_statusMessage", &URLRequest::StatusMessage) + .SetProperty("_rawResponseHeaders", &URLRequest::RawResponseHeaders) + .SetProperty("_httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) + .SetProperty("_httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); } -bool URLRequest::WriteBuffer(scoped_refptr buffer, bool is_last) { - atom_request_->WriteBuffer(buffer, is_last); - return true; +bool URLRequest::WriteBuffer( + scoped_refptr buffer, + bool is_last) { + return atom_request_->WriteBuffer(buffer, is_last); + } @@ -161,7 +166,8 @@ void URLRequest::Abort() { atom_request_->Abort(); } -bool URLRequest::SetHeader(const std::string& name, const std::string& value) { +bool URLRequest::SetHeader(const std::string& name, + const std::string& value) { if (!net::HttpUtil::IsValidHeaderName(name)) { return false; } @@ -181,7 +187,7 @@ void URLRequest::RemoveHeader(const std::string& name) { } void URLRequest::OnAuthenticationRequired( - scoped_refptr auth_info) { + scoped_refptr auth_info) { EmitRequestEvent( "login", auth_info.get(), @@ -194,7 +200,7 @@ void URLRequest::OnResponseStarted() { } void URLRequest::OnResponseData( - scoped_refptr buffer) { + scoped_refptr buffer) { if (!buffer || !buffer->data() || !buffer->size()) { return; } @@ -224,7 +230,8 @@ std::string URLRequest::StatusMessage() const { return result; } -scoped_refptr URLRequest::RawResponseHeaders() const { +scoped_refptr +URLRequest::RawResponseHeaders() const { return atom_request_->GetResponseHeaders(); } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 3831348f78..55bc516c4d 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -32,7 +32,8 @@ class URLRequest : public mate::EventEmitter { private: - bool WriteBuffer(scoped_refptr buffer, bool is_last); + bool WriteBuffer(scoped_refptr buffer, + bool is_last); void Abort(); bool SetHeader(const std::string& name, const std::string& value); std::string GetHeader(const std::string& name); @@ -86,7 +87,8 @@ void URLRequest::EmitRequestEvent(ArgTypes... args) { auto arguments = BuildArgsArray(args...); v8::Local _emitRequestEvent; auto wrapper = GetWrapper(); - if (mate::Dictionary(isolate(), wrapper).Get("_emitRequestEvent", &_emitRequestEvent)) + if (mate::Dictionary(isolate(), wrapper) + .Get("_emitRequestEvent", &_emitRequestEvent)) _emitRequestEvent->Call(wrapper, arguments.size(), arguments.data()); } @@ -96,7 +98,8 @@ void URLRequest::EmitResponseEvent(ArgTypes... args) { auto arguments = BuildArgsArray(args...); v8::Local _emitResponseEvent; auto wrapper = GetWrapper(); - if (mate::Dictionary(isolate(), wrapper).Get("_emitResponseEvent", &_emitResponseEvent)) + if (mate::Dictionary(isolate(), wrapper) + .Get("_emitResponseEvent", &_emitResponseEvent)) _emitResponseEvent->Call(wrapper, arguments.size(), arguments.data()); } diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 09d787a51f..6afc0cbb04 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -14,9 +14,7 @@ namespace { - const int kBufferSize = 4096; - } // namespace namespace atom { @@ -31,6 +29,7 @@ public: : net::UploadBytesElementReader(buffer->data(), buffer->size()) , buffer_(buffer) { } + ~UploadOwnedIOBufferElementReader() override { } @@ -58,10 +57,10 @@ AtomURLRequest::~AtomURLRequest() { } scoped_refptr AtomURLRequest::Create( - AtomBrowserContext* browser_context, - const std::string& method, - const std::string& url, - base::WeakPtr delegate) { + AtomBrowserContext* browser_context, + const std::string& method, + const std::string& url, + base::WeakPtr delegate) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(browser_context); DCHECK(!url.empty()); @@ -74,7 +73,8 @@ scoped_refptr AtomURLRequest::Create( DCHECK(context); - scoped_refptr atom_url_request = new AtomURLRequest(delegate); + scoped_refptr atom_url_request = + new AtomURLRequest(delegate); atom_url_request->request_ = context->CreateRequest(GURL(url), net::RequestPriority::DEFAULT_PRIORITY, @@ -86,12 +86,13 @@ scoped_refptr AtomURLRequest::Create( } -void AtomURLRequest::WriteBuffer(scoped_refptr buffer, - bool is_last) { +bool AtomURLRequest::WriteBuffer( + scoped_refptr buffer, + bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - content::BrowserThread::PostTask( - content::BrowserThread::IO, FROM_HERE, - base::Bind(&AtomURLRequest::DoWriteBuffer, this, buffer, is_last)); + return content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoWriteBuffer, this, buffer, is_last)); } void AtomURLRequest::SetChunkedUpload() { @@ -157,8 +158,9 @@ void AtomURLRequest::PassLoginInformation(const base::string16& username, } -void AtomURLRequest::DoWriteBuffer(scoped_refptr buffer, - bool is_last) { +void AtomURLRequest::DoWriteBuffer( + scoped_refptr buffer, + bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (is_chunked_upload_) { @@ -197,15 +199,19 @@ void AtomURLRequest::DoWriteBuffer(scoped_refptr bu if (buffer) { // Handling potential empty buffers. - std::unique_ptr element_reader(internal::UploadOwnedIOBufferElementReader - ::CreateWithBuffer(std::move(buffer))); - upload_element_readers_.push_back(std::move(element_reader)); + using internal::UploadOwnedIOBufferElementReader; + auto element_reader = UploadOwnedIOBufferElementReader::CreateWithBuffer( + std::move(buffer)); + upload_element_readers_.push_back( + std::unique_ptr(element_reader)); } if (is_last) { - std::unique_ptr elements_upload_data_stream( - new net::ElementsUploadDataStream(std::move(upload_element_readers_), 0)); - request_->set_upload(std::move(elements_upload_data_stream)); + auto elements_upload_data_stream = new net::ElementsUploadDataStream( + std::move(upload_element_readers_), + 0); + request_->set_upload( + std::unique_ptr(elements_upload_data_stream)); request_->Start(); } } @@ -260,7 +266,8 @@ void AtomURLRequest::ReadResponse() { // completed immediately, without trying to read any data back (all we care // about is the response code and headers, which we already have). int bytes_read = 0; - if (request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) { + if (request_->status().is_success() + /* TODO && (request_type_ != URLFetcher::HEAD)*/) { if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) bytes_read = -1; } @@ -268,7 +275,8 @@ void AtomURLRequest::ReadResponse() { } -void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { +void AtomURLRequest::OnReadCompleted(net::URLRequest* request, + int bytes_read) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_EQ(request, request_.get()); @@ -283,11 +291,14 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { // Failed to transfer data to UI thread. return; } - } while (request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)); + } while (request_->Read(response_read_buffer_.get(), + kBufferSize, + &bytes_read)); const auto status = request_->status(); - if (!status.is_io_pending() /* TODO || request_type_ == URLFetcher::HEAD*/ ) { + if (!status.is_io_pending() + /* TODO || request_type_ == URLFetcher::HEAD*/ ) { content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, @@ -301,12 +312,14 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { // data is only a wrapper for the async response_read_buffer_. // Make a deep copy of payload and transfer ownership to the UI thread. - scoped_refptr buffer_copy(new net::IOBufferWithSize(bytes_read)); + auto buffer_copy = new net::IOBufferWithSize(bytes_read); memcpy(buffer_copy->data(), response_read_buffer_->data(), bytes_read); return content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseData, this, buffer_copy)); + base::Bind(&AtomURLRequest::InformDelegateResponseData, + this, + buffer_copy)); } diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 3a923a002e..38f60c8d3b 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -34,7 +34,8 @@ public: const std::string& url, base::WeakPtr delegate); - void WriteBuffer(scoped_refptr buffer, bool is_last); + bool WriteBuffer(scoped_refptr buffer, + bool is_last); void SetChunkedUpload(); void Abort() const; void SetHeader(const std::string& name, const std::string& value) const; @@ -54,7 +55,8 @@ protected: private: friend class base::RefCountedThreadSafe; - void DoWriteBuffer(scoped_refptr buffer, bool is_last); + void DoWriteBuffer(scoped_refptr buffer, + bool is_last); void DoSetAuth(const base::string16& username, const base::string16& password) const; void DoCancelAuth() const; diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index d1d8cb7e7c..7392c7687c 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -15,15 +15,15 @@ class URLResponse extends EventEmitter { } get statusCode() { - return this.request.statusCode; + return this.request._statusCode; } get statusMessage() { - return this.request.statusMessage; + return this.request._statusMessage; } get headers() { - return this.request.rawResponseHeaders; + return this.request._rawResponseHeaders; } get httpVersion() { @@ -31,15 +31,15 @@ class URLResponse extends EventEmitter { } get httpVersionMajor() { - return this.request.httpVersionMajor; + return this.request._httpVersionMajor; } get httpVersionMinor() { - return this.request.httpVersionMinor; + return this.request._httpVersionMinor; } get rawHeaders() { - return this.request.rawResponseHeaders; + return this.request._rawResponseHeaders; } } From 9498a5738a7e94739c0806dbb0b99c653e832dd4 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 27 Sep 2016 10:21:11 +0200 Subject: [PATCH 08/62] Refactoring net module, adding a ClientRequest and IncomingMessage classes. --- atom/browser/api/atom_api_url_request.cc | 38 ++- atom/browser/api/atom_api_url_request.h | 6 +- atom/browser/net/atom_url_request.cc | 20 +- atom/browser/net/atom_url_request.h | 7 +- lib/browser/api/net.js | 292 +++++++++++++---------- 5 files changed, 191 insertions(+), 172 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 623487fa67..8bd8074f96 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -115,8 +115,6 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { auto session = Session::FromPartition(args->isolate(), session_name); auto browser_context = session->browser_context(); - - auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr(); auto atom_url_request = AtomURLRequest::Create( @@ -126,12 +124,10 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { weak_ptr); api_url_request->atom_request_ = atom_url_request; - return api_url_request; } - // static void URLRequest::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { @@ -139,17 +135,17 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) // Request API .MakeDestroyable() - .SetMethod("_writeBuffer", &URLRequest::WriteBuffer) + .SetMethod("writeBuffer", &URLRequest::WriteBuffer) .SetMethod("abort", &URLRequest::Abort) - .SetMethod("_setHeader", &URLRequest::SetHeader) - .SetMethod("_getHeader", &URLRequest::GetHeader) - .SetMethod("_removaHeader", &URLRequest::RemoveHeader) + .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) + .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) + .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload) // Response APi - .SetProperty("_statusCode", &URLRequest::StatusCode) - .SetProperty("_statusMessage", &URLRequest::StatusMessage) - .SetProperty("_rawResponseHeaders", &URLRequest::RawResponseHeaders) - .SetProperty("_httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) - .SetProperty("_httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); + .SetProperty("statusCode", &URLRequest::StatusCode) + .SetProperty("statusMessage", &URLRequest::StatusMessage) + .SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders) + .SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) + .SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); } @@ -166,7 +162,7 @@ void URLRequest::Abort() { atom_request_->Abort(); } -bool URLRequest::SetHeader(const std::string& name, +bool URLRequest::SetExtraHeader(const std::string& name, const std::string& value) { if (!net::HttpUtil::IsValidHeaderName(name)) { return false; @@ -176,14 +172,16 @@ bool URLRequest::SetHeader(const std::string& name, return false; } - atom_request_->SetHeader(name, value); + atom_request_->SetExtraHeader(name, value); return true; } -std::string URLRequest::GetHeader(const std::string& name) { - return atom_request_->GetHeader(name); + +void URLRequest::RemoveExtraHeader(const std::string& name) { + atom_request_->RemoveExtraHeader(name); } -void URLRequest::RemoveHeader(const std::string& name) { - atom_request_->RemoveHeader(name); + +void URLRequest::SetChunkedUpload(bool is_chunked_upload) { + atom_request_->SetChunkedUpload(is_chunked_upload); } void URLRequest::OnAuthenticationRequired( @@ -196,7 +194,7 @@ void URLRequest::OnAuthenticationRequired( void URLRequest::OnResponseStarted() { - EmitRequestEvent("response"); + Emit("response"); } void URLRequest::OnResponseData( diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 55bc516c4d..3437397884 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -35,9 +35,9 @@ private: bool WriteBuffer(scoped_refptr buffer, bool is_last); void Abort(); - bool SetHeader(const std::string& name, const std::string& value); - std::string GetHeader(const std::string& name); - void RemoveHeader(const std::string& name); + bool SetExtraHeader(const std::string& name, const std::string& value); + void RemoveExtraHeader(const std::string& name); + void SetChunkedUpload(bool is_chunked_upload); friend class AtomURLRequest; void OnAuthenticationRequired( diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 6afc0cbb04..f9707f3dae 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -95,13 +95,13 @@ bool AtomURLRequest::WriteBuffer( base::Bind(&AtomURLRequest::DoWriteBuffer, this, buffer, is_last)); } -void AtomURLRequest::SetChunkedUpload() { +void AtomURLRequest::SetChunkedUpload(bool is_chunked_upload) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // The method can be called only before switching to multi-threaded mode, // i.e. before the first call to write. // So it is safe to change the object in the UI thread. - is_chunked_upload_ = true; + is_chunked_upload_ = is_chunked_upload; } @@ -109,26 +109,14 @@ void AtomURLRequest::Abort() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); } -void AtomURLRequest::SetHeader(const std::string& name, +void AtomURLRequest::SetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); request_->SetExtraRequestHeaderByName(name, value, true); } -std::string AtomURLRequest::GetHeader(const std::string& name) const { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - std::string result; - const auto& extra_headers = request_->extra_request_headers(); - if (!extra_headers.GetHeader(name, &result)) { - net::HttpRequestHeaders* request_headers = nullptr; - if (request_->GetFullRequestHeaders(request_headers) && request_headers) { - request_headers->GetHeader(name, &result); - } - } - return result; -} -void AtomURLRequest::RemoveHeader(const std::string& name) const { +void AtomURLRequest::RemoveExtraHeader(const std::string& name) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); request_->RemoveRequestHeaderByName(name); } diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 38f60c8d3b..a3288bfab6 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -36,11 +36,10 @@ public: bool WriteBuffer(scoped_refptr buffer, bool is_last); - void SetChunkedUpload(); + void SetChunkedUpload(bool is_chunked_upload); void Abort() const; - void SetHeader(const std::string& name, const std::string& value) const; - std::string GetHeader(const std::string& name) const; - void RemoveHeader(const std::string& name) const; + void SetExtraHeader(const std::string& name, const std::string& value) const; + void RemoveExtraHeader(const std::string& name) const; void PassLoginInformation(const base::string16& username, const base::string16& password) const; scoped_refptr GetResponseHeaders() const; diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 7392c7687c..3594d0b2a3 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -8,22 +8,22 @@ const {URLRequest} = net Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) -class URLResponse extends EventEmitter { - constructor(request) { +class IncomingMessage extends EventEmitter { + constructor(url_request) { super(); - this.request = request; + this._url_request = url_request; } get statusCode() { - return this.request._statusCode; + return this._url_request.statusCode; } get statusMessage() { - return this.request._statusMessage; + return this._url_request.statusMessage; } get headers() { - return this.request._rawResponseHeaders; + return this._url_request.rawResponseHeaders; } get httpVersion() { @@ -31,161 +31,195 @@ class URLResponse extends EventEmitter { } get httpVersionMajor() { - return this.request._httpVersionMajor; + return this._url_request.httpVersionMajor; } get httpVersionMinor() { - return this.request._httpVersionMinor; + return this._url_request.httpVersionMinor; } get rawHeaders() { - return this.request._rawResponseHeaders; + return this._url_request.rawResponseHeaders; } } -Net.prototype.request = function(options, callback) { - let request = new URLRequest(options) - - if (callback) { - request.once('response', callback) - } - - - return request -} - -URLRequest.prototype._init = function() { - // Flag to prevent writings after end. - this._finished = false; - - // Set when the request uses chuned encoding. Can be switched - // to true only once and never set back to false. - this._chunkedEncoding = false; - - // Flag to prevent request's headers modifications after - // headers flush. - this._headersSent = false; -} - - -URLRequest.prototype.setHeader = function(name, value) { - if (typeof name !== 'string') - throw new TypeError('`name` should be a string in setHeader(name, value).'); - if (value === undefined) - throw new Error('`value` required in setHeader("' + name + '", value).'); - if (this._headersSent) - throw new Error('Can\'t set headers after they are sent.'); - - this._setHeader(name, value) -}; - - -URLRequest.prototype.getHeader = function(name) { - if (arguments.length < 1) { - throw new Error('`name` is required for getHeader(name).'); - } - - return this._getHeader(name); -}; - - -URLRequest.prototype.removeHeader = function(name) { - if (arguments.length < 1) { - throw new Error('`name` is required for removeHeader(name).'); - } - - if (this._headersSent) { - throw new Error('Can\'t remove headers after they are sent.'); - } - - this._removeHeader(name); -}; - - -URLRequest.prototype._emitRequestEvent = function(name) { - if (name === 'response') { - this.response = new URLResponse(this); - this.emit(name, this.response); - } else { - this.emit.apply(this, arguments); - } +URLRequest.prototype._emitRequestEvent = function() { + this._request.emit.apply(this._request, arguments); } URLRequest.prototype._emitResponseEvent = function() { - this.response.emit.apply(this.response, arguments); + this._response.emit.apply(this._response, arguments); } +class ClientRequest extends EventEmitter { -URLRequest.prototype._write = function(chunk, encoding, callback, is_last) { + constructor(options, callback) { + super(); + + // Flag to prevent writings after end. + this._finished = false; + + // Set when the request uses chuned encoding. Can be switched + // to true only once and never set back to false. + this._chunkedEncoding = false; + + // Flag to prevent request's headers modifications after + // headers flush. + this._headersSent = 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 = {}; + + let url = options.url; + let method = options.method; + let session = options.session; + + let url_request = new URLRequest({ + url: url, + method: method, + session: session + }); + + this._url_request = url_request; + url_request._request = this; + + url_request.on('response', ()=> { + let response = new IncomingMessage(url_request); + url_request._response = response; + this.emit('response', response); + }); + + if (callback) { + this.once('response', callback) + } + } + + + setHeader(name, value) { + if (typeof name !== 'string') + throw new TypeError('`name` should be a string in setHeader(name, value).'); + if (value === undefined) + throw new Error('`value` required in setHeader("' + name + '", value).'); + if (this._headersSent) + throw new Error('Can\'t set headers after they are sent.'); + + let key = name.toLowerCase(); + this._extra_headers[key] = value; + this._url_request.setExtraHeader(name, value) + } + + + getHeader(name) { + if (arguments.length < 1) { + throw new Error('`name` is required for getHeader(name).'); + } + + if (!this._extra_headers) { + return; + } + + var key = name.toLowerCase(); + return this._extra_headers[key]; + } + + + removeHeader(name) { + if (arguments.length < 1) { + throw new Error('`name` is required for removeHeader(name).'); + } + + if (this._headersSent) { + throw new Error('Can\'t remove headers after they are sent.'); + } + + var key = name.toLowerCase(); + delete this._extra_headers[key]; + this._url_request.removeExtraHeader(name); + } + + + _write(chunk, encoding, callback, is_last) { - let chunk_is_string = typeof chunk === 'string'; - let chunk_is_buffer = chunk instanceof Buffer; - if (!chunk_is_string && !chunk_is_buffer) { - throw new TypeError('First argument must be a string or Buffer.'); + let chunk_is_string = typeof chunk === 'string'; + let chunk_is_buffer = chunk instanceof Buffer; + if (!chunk_is_string && !chunk_is_buffer) { + throw new TypeError('First argument must be a string or Buffer.'); + } + + if (chunk_is_string) { + // We convert all strings into binary buffers. + chunk = Buffer.from(chunk, encoding); + } + + // Headers are assumed to be sent on first call to _writeBuffer, + // i.e. after the first call to write or end. + let result = this._url_request.writeBuffer(chunk, is_last); + + // Since writing to the network is asynchronous, we conservatively + // assume that request headers are written after delivering the first + // buffer to the network IO thread. + if (!this._headersSent) { + this._url_request.setChunkedUpload(this._chunkedEncoding); + this._headersSent = true; + } + + // The write callback is fired asynchronously to mimic Node.js. + if (callback) { + process.nextTick(callback); + } + + return result; } - if (chunk_is_string) { - // We convert all strings into binary buffers. - chunk = Buffer.from(chunk, encoding); + write(data, encoding, callback) { + + if (this._finished) { + let error = new Error('Write after end.'); + process.nextTick(writeAfterEndNT, this, error, callback); + return true; + } + + return this._write(data, encoding, callback, false); } - // Headers are assumed to be sent on first call to _writeBuffer, - // i.e. after the first call to write or end. - let result = this._writeBuffer(chunk, is_last); - // Since writing to the network is asynchronous, we conservatively - // assume that request headers are written after delivering the first - // buffer to the network IO thread. - this._headersSent = true; + end(data, encoding, callback) { + if (this._finished) { + return false; + } + + this._finished = true; - // The write callback is fired asynchronously to mimic Node.js. - if (callback) { - process.nextTick(callback); + if (typeof data === 'function') { + callback = data; + encoding = null; + data = null; + } else if (typeof encoding === 'function') { + callback = encoding; + encoding = null; + } + + data = data || ''; + + return this._write(data, encoding, callback, true); } - return result; } -URLRequest.prototype.write = function(data, encoding, callback) { - - if (this._finished) { - let error = new Error('Write after end.'); - process.nextTick(writeAfterEndNT, this, error, callback); - return true; - } - - return this._write(data, encoding, callback, false); -}; - - -URLRequest.prototype.end = function(data, encoding, callback) { - if (this._finished) { - return false; - } - - this._finished = true; - - if (typeof data === 'function') { - callback = data; - encoding = null; - data = null; - } else if (typeof encoding === 'function') { - callback = encoding; - encoding = null; - } - - data = data || ''; - - return this._write(data, encoding, callback, true); -}; - - function writeAfterEndNT(self, error, callback) { self.emit('error', error); if (callback) callback(error); } +Net.prototype.request = function(options, callback) { + return new ClientRequest(options, callback); +} + +net.ClientRequest = ClientRequest; + module.exports = net \ No newline at end of file From cbbc4376cad4c67139c30ec8ed1c14ed541ae192 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 27 Sep 2016 12:33:51 +0200 Subject: [PATCH 09/62] Adding support for upload chunked encoding. --- atom/browser/api/atom_api_url_request.cc | 10 +-- atom/browser/api/atom_api_url_request.h | 2 +- atom/browser/net/atom_url_request.cc | 2 +- atom/browser/net/atom_url_request.h | 2 +- lib/browser/api/net.js | 103 +++++++++++++++++++---- 5 files changed, 95 insertions(+), 24 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 8bd8074f96..ec14cf5904 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -105,10 +105,10 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { v8::Local options; args->GetNext(&options); mate::Dictionary dict(args->isolate(), options); - std::string url; - dict.Get("url", &url); std::string method; dict.Get("method", &method); + std::string url; + dict.Get("url", &url); std::string session_name; dict.Get("session", &session_name); @@ -135,7 +135,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) // Request API .MakeDestroyable() - .SetMethod("writeBuffer", &URLRequest::WriteBuffer) + .SetMethod("writeBuffer", &URLRequest::Write) .SetMethod("abort", &URLRequest::Abort) .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) @@ -150,10 +150,10 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, } -bool URLRequest::WriteBuffer( +bool URLRequest::Write( scoped_refptr buffer, bool is_last) { - return atom_request_->WriteBuffer(buffer, is_last); + return atom_request_->Write(buffer, is_last); } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 3437397884..a34810d3d8 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -32,7 +32,7 @@ class URLRequest : public mate::EventEmitter { private: - bool WriteBuffer(scoped_refptr buffer, + bool Write(scoped_refptr buffer, bool is_last); void Abort(); bool SetExtraHeader(const std::string& name, const std::string& value); diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index f9707f3dae..f6549b9d3b 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -86,7 +86,7 @@ scoped_refptr AtomURLRequest::Create( } -bool AtomURLRequest::WriteBuffer( +bool AtomURLRequest::Write( scoped_refptr buffer, bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index a3288bfab6..5902ba6ffa 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -34,7 +34,7 @@ public: const std::string& url, base::WeakPtr delegate); - bool WriteBuffer(scoped_refptr buffer, + bool Write(scoped_refptr buffer, bool is_last); void SetChunkedUpload(bool is_chunked_upload); void Abort() const; diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 3594d0b2a3..3679c925f2 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -1,6 +1,7 @@ 'use strict' const {EventEmitter} = require('events') +const util = require('util') const binding = process.atomBinding('net') const {net, Net} = binding const {URLRequest} = net @@ -8,6 +9,10 @@ const {URLRequest} = net Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) +let kSupportedProtocols = new Set(); +kSupportedProtocols.add('http:'); +kSupportedProtocols.add('https:'); + class IncomingMessage extends EventEmitter { constructor(url_request) { super(); @@ -57,6 +62,74 @@ class ClientRequest extends EventEmitter { constructor(options, callback) { super(); + if (typeof options === 'string') { + options = url.parse(options); + } else { + options = util._extend({}, options); + } + + const method = (options.method || 'GET').toUpperCase(); + let url_str = options.url; + + if (!url_str) { + let url_obj = {}; + + const protocol = options.protocol || 'http'; + if (!kSupportedProtocols.has(protocol)) { + throw new Error('Protocol "' + protocol + '" not supported. '); + } + url_obj.protocol = protocol; + + + if (options.host) { + url_obj.host = options.host; + } else { + + if (options.hostname) { + url_obj.hostname = options.hostname; + } else { + url_obj.hostname = 'localhost'; + } + + if (options.port) { + url_obj.port = options.port; + } + } + + const path = options.path || '/'; + 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 + // but that's a) hard to capture in a regular expression that performs + // 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.'); + } + url_obj.path = path; + + url_str = url.format(url_obj); + } + + const session_name = options.session || ''; + let url_request = new URLRequest({ + method: method, + url: url_str, + session: session_name + }); + + // Set back and forward links. + this._url_request = url_request; + url_request._request = this; + + if (options.headers) { + let keys = Object.keys(options.headers); + for (let i = 0, l = keys.length; i < l; i++) { + let key = keys[i]; + this.setHeader(key, options.headers[key]); + } + } + // Flag to prevent writings after end. this._finished = false; @@ -73,19 +146,6 @@ class ClientRequest extends EventEmitter { // after the request starts. this._extra_headers = {}; - let url = options.url; - let method = options.method; - let session = options.session; - - let url_request = new URLRequest({ - url: url, - method: method, - session: session - }); - - this._url_request = url_request; - url_request._request = this; - url_request.on('response', ()=> { let response = new IncomingMessage(url_request); url_request._response = response; @@ -97,6 +157,17 @@ class ClientRequest extends EventEmitter { } } + get chunkedEncoding() { + return this._chunkedEncoding; + } + + set chunkedEncoding(value) { + if (this._headersSent) { + throw new Error('Can\'t set the transfer encoding, headers have been sent.'); + } + this._chunkedEncoding = value; + } + setHeader(name, value) { if (typeof name !== 'string') @@ -121,7 +192,7 @@ class ClientRequest extends EventEmitter { return; } - var key = name.toLowerCase(); + let key = name.toLowerCase(); return this._extra_headers[key]; } @@ -135,7 +206,7 @@ class ClientRequest extends EventEmitter { throw new Error('Can\'t remove headers after they are sent.'); } - var key = name.toLowerCase(); + let key = name.toLowerCase(); delete this._extra_headers[key]; this._url_request.removeExtraHeader(name); } @@ -162,7 +233,7 @@ class ClientRequest extends EventEmitter { // assume that request headers are written after delivering the first // buffer to the network IO thread. if (!this._headersSent) { - this._url_request.setChunkedUpload(this._chunkedEncoding); + this._url_request.setChunkedUpload(this.chunkedEncoding); this._headersSent = true; } From 08947682b07eb14dc5caa0344e0976087297b5f8 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 28 Sep 2016 15:07:54 +0200 Subject: [PATCH 10/62] Implementing abort workflow, emitting error events. --- atom/browser/api/atom_api_url_request.cc | 6 +- atom/browser/api/atom_api_url_request.h | 1 + atom/browser/net/atom_url_request.cc | 84 ++++++++++++++---------- atom/browser/net/atom_url_request.h | 2 + lib/browser/api/net.js | 37 ++++++++--- 5 files changed, 84 insertions(+), 46 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index ec14cf5904..a98bd58e74 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -135,7 +135,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) // Request API .MakeDestroyable() - .SetMethod("writeBuffer", &URLRequest::Write) + .SetMethod("write", &URLRequest::Write) .SetMethod("abort", &URLRequest::Abort) .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) @@ -212,6 +212,10 @@ void URLRequest::OnResponseCompleted() { atom_request_ = nullptr; } +void URLRequest::OnError(const std::string& error) { + auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); + EmitRequestEvent("error", error_object); +} int URLRequest::StatusCode() const { if (auto response_headers = atom_request_->GetResponseHeaders()) { diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index a34810d3d8..9fb0724302 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -45,6 +45,7 @@ private: void OnResponseStarted(); void OnResponseData(scoped_refptr data); void OnResponseCompleted(); + void OnError(const std::string& error); int StatusCode() const; std::string StatusMessage() const; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index f6549b9d3b..d7f5096951 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -107,6 +107,10 @@ void AtomURLRequest::SetChunkedUpload(bool is_chunked_upload) { void AtomURLRequest::Abort() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind(&AtomURLRequest::DoAbort, this)); } void AtomURLRequest::SetExtraHeader(const std::string& name, @@ -133,16 +137,14 @@ AtomURLRequest::GetResponseHeaders() const { void AtomURLRequest::PassLoginInformation(const base::string16& username, const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (username.empty() || password.empty()) { + if (username.empty() || password.empty()) content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoCancelAuth, this)); - } - else { + else content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoSetAuth, this, username, password)); - } } @@ -164,24 +166,21 @@ void AtomURLRequest::DoWriteBuffer( first_call = true; } - if (buffer) { + if (buffer) // Non-empty buffer. auto write_result = chunked_stream_writer_->AppendData( buffer->data(), buffer->size(), is_last); - } - else if (is_last) { + else if (is_last) // Empty buffer and last chunck, i.e. request.end(). auto write_result = chunked_stream_writer_->AppendData( nullptr, 0, true); - } - if (first_call) { + if (first_call) request_->Start(); - } } else { @@ -205,6 +204,11 @@ void AtomURLRequest::DoWriteBuffer( } } +void AtomURLRequest::DoAbort() const { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + request_->Cancel(); +} + void AtomURLRequest::DoSetAuth(const base::string16& username, const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -231,19 +235,25 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_EQ(request, request_.get()); - if (request_->status().is_success()) { + const auto& status = request_->status(); + if (status.is_success()) { // Cache net::HttpResponseHeaders instance, a read-only objects // so that headers and other http metainformation can be simultaneously // read from UI thread while request data is simulataneously streaming // on IO thread. response_headers_ = request_->response_headers(); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this)); + + ReadResponse(); + } + else { + auto error = net::ErrorToString(status.ToNetError()); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateErrorOccured, this, std::move(error))); } - - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this)); - - ReadResponse(); } void AtomURLRequest::ReadResponse() { @@ -255,10 +265,9 @@ void AtomURLRequest::ReadResponse() { // about is the response code and headers, which we already have). int bytes_read = 0; if (request_->status().is_success() - /* TODO && (request_type_ != URLFetcher::HEAD)*/) { + /* TODO && (request_type_ != URLFetcher::HEAD)*/) if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) bytes_read = -1; - } OnReadCompleted(request_.get(), bytes_read); } @@ -269,29 +278,31 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, DCHECK_EQ(request, request_.get()); + const auto status = request_->status(); do { - if (!request_->status().is_success() || bytes_read <= 0) + if (!status.is_success() || bytes_read <= 0) { + auto error = net::ErrorToString(status.ToNetError()); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateErrorOccured, this, std::move(error))); break; - + } const auto result = CopyAndPostBuffer(bytes_read); - if (!result) { + if (!result) // Failed to transfer data to UI thread. return; - } } while (request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)); - const auto status = request_->status(); if (!status.is_io_pending() - /* TODO || request_type_ == URLFetcher::HEAD*/ ) { + /* TODO || request_type_ == URLFetcher::HEAD*/ ) content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); - } } @@ -314,17 +325,14 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { void AtomURLRequest::InformDelegateAuthenticationRequired( scoped_refptr auth_info) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (delegate_) { + if (delegate_) delegate_->OnAuthenticationRequired(auth_info); - } } void AtomURLRequest::InformDelegateResponseStarted() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - - if (delegate_) { + if (delegate_) delegate_->OnResponseStarted(); - } } void AtomURLRequest::InformDelegateResponseData( @@ -333,17 +341,23 @@ void AtomURLRequest::InformDelegateResponseData( // Transfer ownership of the data buffer, data will be released // by the delegate's OnResponseData. - if (delegate_) { + if (delegate_) delegate_->OnResponseData(data); - } } void AtomURLRequest::InformDelegateResponseCompleted() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (delegate_) { + if (delegate_) delegate_->OnResponseCompleted(); - } +} + +void AtomURLRequest::InformDelegateErrorOccured( + const std::string& error) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (delegate_) + delegate_->OnError(error); } diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 5902ba6ffa..7de6e1a40a 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -56,6 +56,7 @@ private: friend class base::RefCountedThreadSafe; void DoWriteBuffer(scoped_refptr buffer, bool is_last); + void DoAbort() const; void DoSetAuth(const base::string16& username, const base::string16& password) const; void DoCancelAuth() const; @@ -69,6 +70,7 @@ private: void InformDelegateResponseData( scoped_refptr data) const; void InformDelegateResponseCompleted() const; + void InformDelegateErrorOccured(const std::string& error) const; AtomURLRequest(base::WeakPtr delegate); virtual ~AtomURLRequest(); diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 3679c925f2..398c30c07f 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -130,6 +130,12 @@ class ClientRequest extends EventEmitter { } } + // Flag to prevent request's headers modifications after + // headers flush. + this._started = false; + + this._aborted = false; + // Flag to prevent writings after end. this._finished = false; @@ -137,10 +143,6 @@ class ClientRequest extends EventEmitter { // to true only once and never set back to false. this._chunkedEncoding = false; - // Flag to prevent request's headers modifications after - // headers flush. - this._headersSent = 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. @@ -162,7 +164,7 @@ class ClientRequest extends EventEmitter { } set chunkedEncoding(value) { - if (this._headersSent) { + if (this._started) { throw new Error('Can\'t set the transfer encoding, headers have been sent.'); } this._chunkedEncoding = value; @@ -174,7 +176,7 @@ class ClientRequest extends EventEmitter { throw new TypeError('`name` should be a string in setHeader(name, value).'); if (value === undefined) throw new Error('`value` required in setHeader("' + name + '", value).'); - if (this._headersSent) + if (this._started) throw new Error('Can\'t set headers after they are sent.'); let key = name.toLowerCase(); @@ -202,7 +204,7 @@ class ClientRequest extends EventEmitter { throw new Error('`name` is required for removeHeader(name).'); } - if (this._headersSent) { + if (this._started) { throw new Error('Can\'t remove headers after they are sent.'); } @@ -227,14 +229,14 @@ class ClientRequest extends EventEmitter { // Headers are assumed to be sent on first call to _writeBuffer, // i.e. after the first call to write or end. - let result = this._url_request.writeBuffer(chunk, is_last); + let result = this._url_request.write(chunk, is_last); // Since writing to the network is asynchronous, we conservatively // assume that request headers are written after delivering the first // buffer to the network IO thread. - if (!this._headersSent) { + if (!this._started) { this._url_request.setChunkedUpload(this.chunkedEncoding); - this._headersSent = true; + this._started = true; } // The write callback is fired asynchronously to mimic Node.js. @@ -278,6 +280,21 @@ class ClientRequest extends EventEmitter { return this._write(data, encoding, callback, true); } + abort() { + if (!this._started) { + // Does nothing if stream + return; + } + + if (!this._aborted) { + this._url_request.abort(); + this._aborted = true; + process.nextTick( ()=>{ + this.emit('abort'); + } ); + } + } + } function writeAfterEndNT(self, error, callback) { From 05884358827a7645fe65d55722affa4ee64dda0d Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 29 Sep 2016 11:31:08 +0200 Subject: [PATCH 11/62] Fixing cpplint issues. --- atom/browser/api/atom_api_net.cc | 6 +-- atom/browser/api/atom_api_net.h | 12 ++--- atom/browser/api/atom_api_url_request.cc | 64 +++++++++--------------- atom/browser/api/atom_api_url_request.h | 21 ++++---- atom/browser/net/atom_url_request.cc | 51 +++++++++---------- atom/browser/net/atom_url_request.h | 29 +++++------ 6 files changed, 83 insertions(+), 100 deletions(-) diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index 1559cee4ca..2464393fcd 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -4,8 +4,8 @@ #include "atom/browser/api/atom_api_net.h" #include "atom/browser/api/atom_api_url_request.h" -#include "native_mate/dictionary.h" #include "atom/common/node_includes.h" +#include "native_mate/dictionary.h" namespace atom { @@ -50,7 +50,6 @@ using atom::api::URLRequest; void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { - v8::Isolate* isolate = context->GetIsolate(); URLRequest::SetConstructor(isolate, base::Bind(URLRequest::New)); @@ -58,9 +57,8 @@ void Initialize(v8::Local exports, v8::Local unused, mate::Dictionary dict(isolate, exports); dict.Set("net", Net::Create(isolate)); dict.Set("Net", Net::GetConstructor(isolate)->GetFunction()); - } } // namespace -NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_net, Initialize) \ No newline at end of file +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_browser_net, Initialize) diff --git a/atom/browser/api/atom_api_net.h b/atom/browser/api/atom_api_net.h index dd97bb9653..640a6445eb 100644 --- a/atom/browser/api/atom_api_net.h +++ b/atom/browser/api/atom_api_net.h @@ -12,20 +12,18 @@ namespace atom { namespace api { class Net : public mate::EventEmitter { - -public: + public: static v8::Local Create(v8::Isolate* isolate); static void BuildPrototype(v8::Isolate* isolate, v8::Local prototype); v8::Local URLRequest(v8::Isolate* isolate); -protected: - Net(v8::Isolate* isolate); + protected: + explicit Net(v8::Isolate* isolate); ~Net() override; -private: - + private: DISALLOW_COPY_AND_ASSIGN(Net); }; @@ -34,4 +32,4 @@ private: } // namespace atom -#endif // ATOM_BROWSER_API_ATOM_API_NET_H_ \ No newline at end of file +#endif // ATOM_BROWSER_API_ATOM_API_NET_H_ diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index a98bd58e74..ad2d66f5fe 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -2,31 +2,24 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/browser/api/atom_api_url_request.h" +#include #include "atom/browser/api/atom_api_session.h" - -#include "native_mate/dictionary.h" +#include "atom/browser/api/atom_api_url_request.h" #include "atom/browser/net/atom_url_request.h" -#include "atom/common/node_includes.h" +#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" -#include "atom/common/native_mate_converters/callback.h" +#include "atom/common/node_includes.h" +#include "native_mate/dictionary.h" -namespace { -const char* const kResponse = "response"; -const char* const kData = "data"; -const char* const kEnd = "end"; - -} namespace mate { template<> struct Converter> { static v8::Local ToV8( - v8::Isolate* isolate, - scoped_refptr val) { - + v8::Isolate* isolate, + scoped_refptr val) { mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); if (val) { size_t iter = 0; @@ -34,7 +27,7 @@ struct Converter> { std::string value; while (val->EnumerateHeaderLines(&iter, &name, &value)) { dict.Set(name, value); - } + } } return dict.GetHandle(); } @@ -43,29 +36,27 @@ struct Converter> { template<> struct Converter> { static v8::Local ToV8( - v8::Isolate* isolate, - scoped_refptr buffer) { + v8::Isolate* isolate, + scoped_refptr buffer) { return node::Buffer::Copy(isolate, - buffer->data(), - buffer->size()).ToLocalChecked(); + buffer->data(), + buffer->size()).ToLocalChecked(); } static bool FromV8( - v8::Isolate* isolate, - v8::Local val, - scoped_refptr* out) { - + v8::Isolate* isolate, + v8::Local val, + scoped_refptr* out) { auto size = node::Buffer::Length(val); - + if (size == 0) { - // Support conversoin from empty buffer. A use case is - // a GET request without body. + // Support conversoin from empty buffer. A use case is + // a GET request without body. // Since zero-sized IOBuffer(s) are not supported, we set the // out pointer to null. *out = nullptr; return true; } - auto data = node::Buffer::Data(val); if (!data) { // This is an error as size is positif but data is null. @@ -86,9 +77,9 @@ struct Converter> { } }; -} -namespace atom { +} // namespace mate +namespace atom { namespace api { URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) @@ -101,7 +92,6 @@ URLRequest::~URLRequest() { // static mate::WrappableBase* URLRequest::New(mate::Arguments* args) { - v8::Local options; args->GetNext(&options); mate::Dictionary dict(args->isolate(), options); @@ -122,7 +112,7 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { method, url, weak_ptr); - + api_url_request->atom_request_ = atom_url_request; return api_url_request; @@ -146,15 +136,12 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, .SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders) .SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) .SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); - - } bool URLRequest::Write( scoped_refptr buffer, bool is_last) { return atom_request_->Write(buffer, is_last); - } @@ -191,7 +178,7 @@ void URLRequest::OnAuthenticationRequired( auth_info.get(), base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); } - + void URLRequest::OnResponseStarted() { Emit("response"); @@ -202,7 +189,6 @@ void URLRequest::OnResponseData( if (!buffer || !buffer->data() || !buffer->size()) { return; } - EmitResponseEvent("data", buffer); } @@ -234,7 +220,7 @@ std::string URLRequest::StatusMessage() const { scoped_refptr URLRequest::RawResponseHeaders() const { - return atom_request_->GetResponseHeaders(); + return atom_request_->GetResponseHeaders(); } uint32_t URLRequest::ResponseHttpVersionMajor() const { @@ -261,6 +247,6 @@ void URLRequest::unpin() { wrapper_.Reset(); } -} // namespace mate +} // namespace api -} // namepsace mate \ No newline at end of file +} // namespace atom diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 9fb0724302..89702b0d40 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -6,10 +6,11 @@ #define ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ #include +#include #include "atom/browser/api/trackable_object.h" #include "native_mate/handle.h" -#include "net/url_request/url_request_context.h" #include "net/http/http_response_headers.h" +#include "net/url_request/url_request_context.h" namespace atom { @@ -22,18 +23,19 @@ class URLRequest : public mate::EventEmitter { public: static mate::WrappableBase* New(mate::Arguments* args); - static void BuildPrototype(v8::Isolate* isolate, + static void BuildPrototype( + v8::Isolate* isolate, v8::Local prototype); protected: - URLRequest(v8::Isolate* isolate, + explicit URLRequest(v8::Isolate* isolate, v8::Local wrapper); ~URLRequest() override; -private: + private: bool Write(scoped_refptr buffer, - bool is_last); + bool is_last); void Abort(); bool SetExtraHeader(const std::string& name, const std::string& value); void RemoveExtraHeader(const std::string& name); @@ -55,7 +57,7 @@ private: template - std::array, sizeof...(ArgTypes)> + std::array, sizeof...(ArgTypes)> BuildArgsArray(ArgTypes... args) const; template @@ -70,7 +72,6 @@ private: scoped_refptr atom_request_; v8::Global wrapper_; base::WeakPtrFactory weak_ptr_factory_; - DISALLOW_COPY_AND_ASSIGN(URLRequest); }; @@ -107,8 +108,8 @@ void URLRequest::EmitResponseEvent(ArgTypes... args) { -} // namepsace api +} // namespace api -} // namepsace atom +} // namespace atom -#endif // ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ \ No newline at end of file +#endif // ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_ diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index d7f5096951..6b07a2e3ef 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -3,19 +3,21 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/browser/net/atom_url_request.h" +#include #include "atom/browser/api/atom_api_url_request.h" #include "atom/browser/atom_browser_context.h" +#include "atom/browser/net/atom_url_request.h" #include "base/callback.h" #include "content/public/browser/browser_thread.h" -#include "net/base/io_buffer.h" #include "net/base/elements_upload_data_stream.h" +#include "net/base/io_buffer.h" #include "net/base/upload_bytes_element_reader.h" + namespace { const int kBufferSize = 4096; -} // namespace +} // namespace namespace atom { @@ -23,7 +25,7 @@ namespace internal { class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { -public: + public: explicit UploadOwnedIOBufferElementReader( scoped_refptr buffer) : net::UploadBytesElementReader(buffer->data(), buffer->size()) @@ -39,18 +41,18 @@ public: return new UploadOwnedIOBufferElementReader(std::move(buffer)); } -private: + private: scoped_refptr buffer_; DISALLOW_COPY_AND_ASSIGN(UploadOwnedIOBufferElementReader); }; -} +} // namespace internal AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) : delegate_(delegate) , response_read_buffer_(new net::IOBuffer(kBufferSize)) - , is_chunked_upload_(is_chunked_upload_) { + , is_chunked_upload_(false) { } AtomURLRequest::~AtomURLRequest() { @@ -73,7 +75,7 @@ scoped_refptr AtomURLRequest::Create( DCHECK(context); - scoped_refptr atom_url_request = + scoped_refptr atom_url_request = new AtomURLRequest(delegate); atom_url_request->request_ = context->CreateRequest(GURL(url), @@ -82,14 +84,13 @@ scoped_refptr AtomURLRequest::Create( atom_url_request->request_->set_method(method); return atom_url_request; - } bool AtomURLRequest::Write( scoped_refptr buffer, bool is_last) { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); return content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoWriteBuffer, this, buffer, is_last)); @@ -154,7 +155,6 @@ void AtomURLRequest::DoWriteBuffer( DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (is_chunked_upload_) { - // Chunked encoding case. bool first_call = false; @@ -181,9 +181,7 @@ void AtomURLRequest::DoWriteBuffer( if (first_call) request_->Start(); - } - else { - + } else { if (buffer) { // Handling potential empty buffers. using internal::UploadOwnedIOBufferElementReader; @@ -223,10 +221,10 @@ void AtomURLRequest::DoCancelAuth() const { void AtomURLRequest::OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - + content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired, + base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired, this, scoped_refptr(auth_info))); } @@ -247,12 +245,13 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this)); ReadResponse(); - } - else { + } else { auto error = net::ErrorToString(status.ToNetError()); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateErrorOccured, this, std::move(error))); + base::Bind(&AtomURLRequest::InformDelegateErrorOccured, + this, + std::move(error))); } } @@ -264,10 +263,10 @@ void AtomURLRequest::ReadResponse() { // completed immediately, without trying to read any data back (all we care // about is the response code and headers, which we already have). int bytes_read = 0; - if (request_->status().is_success() + if (request_->status().is_success() /* TODO && (request_type_ != URLFetcher::HEAD)*/) if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) - bytes_read = -1; + bytes_read = -1; OnReadCompleted(request_.get(), bytes_read); } @@ -284,7 +283,9 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, auto error = net::ErrorToString(status.ToNetError()); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateErrorOccured, this, std::move(error))); + base::Bind(&AtomURLRequest::InformDelegateErrorOccured, + this, + std::move(error))); break; } @@ -295,15 +296,13 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, } while (request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)); - - if (!status.is_io_pending() + if (!status.is_io_pending() /* TODO || request_type_ == URLFetcher::HEAD*/ ) content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); - } bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { @@ -361,4 +360,4 @@ void AtomURLRequest::InformDelegateErrorOccured( } -} // namespace atom \ No newline at end of file +} // namespace atom diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 7de6e1a40a..dd6e3d11e0 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -6,9 +6,11 @@ #ifndef ATOM_BROWSER_NET_ATOM_URL_REQUEST_H_ #define ATOM_BROWSER_NET_ATOM_URL_REQUEST_H_ +#include +#include #include "base/memory/ref_counted.h" -#include "net/url_request/url_request.h" #include "net/base/chunked_upload_data_stream.h" +#include "net/url_request/url_request.h" namespace net { @@ -27,7 +29,7 @@ class URLRequest; class AtomURLRequest : public base::RefCountedThreadSafe, public net::URLRequest::Delegate { -public: + public: static scoped_refptr Create( AtomBrowserContext* browser_context, const std::string& method, @@ -44,15 +46,15 @@ public: const base::string16& password) const; scoped_refptr GetResponseHeaders() const; -protected: + protected: // Overrides of net::URLRequest::Delegate - virtual void OnAuthRequired(net::URLRequest* request, + void OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) override; - virtual void OnResponseStarted(net::URLRequest* request) override; - virtual void OnReadCompleted(net::URLRequest* request, + void OnResponseStarted(net::URLRequest* request) override; + void OnReadCompleted(net::URLRequest* request, int bytes_read) override; -private: + private: friend class base::RefCountedThreadSafe; void DoWriteBuffer(scoped_refptr buffer, bool is_last); @@ -72,7 +74,7 @@ private: void InformDelegateResponseCompleted() const; void InformDelegateErrorOccured(const std::string& error) const; - AtomURLRequest(base::WeakPtr delegate); + explicit AtomURLRequest(base::WeakPtr delegate); virtual ~AtomURLRequest(); base::WeakPtr delegate_; @@ -81,15 +83,14 @@ private: bool is_chunked_upload_; std::unique_ptr chunked_stream_; std::unique_ptr chunked_stream_writer_; - - std::vector>upload_element_readers_; - + std::vector> + upload_element_readers_; scoped_refptr response_read_buffer_; scoped_refptr response_headers_; - DISALLOW_COPY_AND_ASSIGN(AtomURLRequest); - }; + DISALLOW_COPY_AND_ASSIGN(AtomURLRequest); +}; } // namespace atom -#endif // ATOM_BROWSER_NET_ATOM_URL_REQUEST_H_ \ No newline at end of file +#endif // ATOM_BROWSER_NET_ATOM_URL_REQUEST_H_ From 42adb2afd4a4d9e99e2b12ba4a1f4ede0ee8cc12 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 29 Sep 2016 15:24:28 +0200 Subject: [PATCH 12/62] Fixing lint-js issues. --- lib/browser/api/exports/electron.js | 8 +- lib/browser/api/net.js | 281 ++++++++++++++-------------- 2 files changed, 140 insertions(+), 149 deletions(-) diff --git a/lib/browser/api/exports/electron.js b/lib/browser/api/exports/electron.js index 09d5b0e47a..9a486b575d 100644 --- a/lib/browser/api/exports/electron.js +++ b/lib/browser/api/exports/electron.js @@ -108,10 +108,10 @@ Object.defineProperties(exports, { } }, net: { - enumerable: true, - get: function () { - return require('../net') - } + enumerable: true, + get: function () { + return require('../net') + } }, // The internal modules, invisible unless you know their names. diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 398c30c07f..1471ae4f1d 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -1,5 +1,6 @@ 'use strict' +const url = require('url') const {EventEmitter} = require('events') const util = require('util') const binding = process.atomBinding('net') @@ -9,94 +10,91 @@ const {URLRequest} = net Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) -let kSupportedProtocols = new Set(); -kSupportedProtocols.add('http:'); -kSupportedProtocols.add('https:'); +let kSupportedProtocols = new Set() +kSupportedProtocols.add('http:') +kSupportedProtocols.add('https:') class IncomingMessage extends EventEmitter { - constructor(url_request) { - super(); - this._url_request = url_request; + constructor (urlRequest) { + super() + this._url_request = urlRequest } - get statusCode() { - return this._url_request.statusCode; + get statusCode () { + return this._url_request.statusCode } - get statusMessage() { - return this._url_request.statusMessage; + get statusMessage () { + return this._url_request.statusMessage } - get headers() { - return this._url_request.rawResponseHeaders; + get headers () { + return this._url_request.rawResponseHeaders } - get httpVersion() { - return `${this.httpVersionMajor}.${this.httpVersionMinor}`; + get httpVersion () { + return `${this.httpVersionMajor}.${this.httpVersionMinor}` } - get httpVersionMajor() { - return this._url_request.httpVersionMajor; + get httpVersionMajor () { + return this._url_request.httpVersionMajor } - get httpVersionMinor() { - return this._url_request.httpVersionMinor; + get httpVersionMinor () { + return this._url_request.httpVersionMinor } - get rawHeaders() { - return this._url_request.rawResponseHeaders; + get rawHeaders () { + return this._url_request.rawResponseHeaders } } -URLRequest.prototype._emitRequestEvent = function() { - this._request.emit.apply(this._request, arguments); +URLRequest.prototype._emitRequestEvent = function () { + this._request.emit.apply(this._request, arguments) } -URLRequest.prototype._emitResponseEvent = function() { - this._response.emit.apply(this._response, arguments); +URLRequest.prototype._emitResponseEvent = function () { + this._response.emit.apply(this._response, arguments) } class ClientRequest extends EventEmitter { - constructor(options, callback) { - super(); + constructor (options, callback) { + super() if (typeof options === 'string') { - options = url.parse(options); + options = url.parse(options) } else { - options = util._extend({}, options); + options = util._extend({}, options) } - const method = (options.method || 'GET').toUpperCase(); - let url_str = options.url; + const method = (options.method || 'GET').toUpperCase() + let urlStr = options.url - if (!url_str) { - let url_obj = {}; - - const protocol = options.protocol || 'http'; + if (!urlStr) { + let urlObj = {} + const protocol = options.protocol || 'http' if (!kSupportedProtocols.has(protocol)) { - throw new Error('Protocol "' + protocol + '" not supported. '); + throw new Error('Protocol "' + protocol + '" not supported. ') } - url_obj.protocol = protocol; - + urlObj.protocol = protocol if (options.host) { - url_obj.host = options.host; + urlObj.host = options.host } else { - if (options.hostname) { - url_obj.hostname = options.hostname; + urlObj.hostname = options.hostname } else { - url_obj.hostname = 'localhost'; + urlObj.hostname = 'localhost' } if (options.port) { - url_obj.port = options.port; + urlObj.port = options.port } } - const path = options.path || '/'; + const path = options.path || '/' 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 @@ -104,210 +102,203 @@ 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.') } - url_obj.path = path; - - url_str = url.format(url_obj); + urlObj.path = path + urlStr = url.format(urlObj) } - const session_name = options.session || ''; - let url_request = new URLRequest({ + const sessionName = options.session || '' + let urlRequest = new URLRequest({ method: method, - url: url_str, - session: session_name - }); + url: urlStr, + session: sessionName + }) // Set back and forward links. - this._url_request = url_request; - url_request._request = this; + this._url_request = urlRequest + urlRequest._request = this if (options.headers) { - let keys = Object.keys(options.headers); + const keys = Object.keys(options.headers) for (let i = 0, l = keys.length; i < l; i++) { - let key = keys[i]; - this.setHeader(key, options.headers[key]); + const key = keys[i] + this.setHeader(key, options.headers[key]) } } - // Flag to prevent request's headers modifications after + // Flag to prevent request's headers modifications after // headers flush. - this._started = false; + this._started = false - this._aborted = false; + this._aborted = false // Flag to prevent writings after end. - this._finished = false; + this._finished = false // Set when the request uses chuned encoding. Can be switched // to true only once and never set back to false. - this._chunkedEncoding = 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 = {}; + this._extra_headers = {} - url_request.on('response', ()=> { - let response = new IncomingMessage(url_request); - url_request._response = response; - this.emit('response', response); - }); + urlRequest.on('response', () => { + const response = new IncomingMessage(urlRequest) + urlRequest._response = response + this.emit('response', response) + }) if (callback) { this.once('response', callback) } } - get chunkedEncoding() { - return this._chunkedEncoding; + get chunkedEncoding () { + return this._chunkedEncoding } - set chunkedEncoding(value) { + set chunkedEncoding (value) { if (this._started) { - 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._chunkedEncoding = value; + this._chunkedEncoding = value } + setHeader (name, value) { + if (typeof name !== 'string') { + throw new TypeError('`name` should be a string in setHeader(name, value).') + } + if (value === undefined) { + throw new Error('`value` required in setHeader("' + name + '", value).') + } + if (this._started) { + throw new Error('Can\'t set headers after they are sent.') + } - setHeader(name, value) { - if (typeof name !== 'string') - throw new TypeError('`name` should be a string in setHeader(name, value).'); - if (value === undefined) - throw new Error('`value` required in setHeader("' + name + '", value).'); - if (this._started) - throw new Error('Can\'t set headers after they are sent.'); - - let key = name.toLowerCase(); - this._extra_headers[key] = value; + const key = name.toLowerCase() + this._extra_headers[key] = value this._url_request.setExtraHeader(name, value) } - - getHeader(name) { + getHeader (name) { if (arguments.length < 1) { - throw new Error('`name` is required for getHeader(name).'); + throw new Error('`name` is required for getHeader(name).') } if (!this._extra_headers) { - return; + return } - let key = name.toLowerCase(); - return this._extra_headers[key]; + const key = name.toLowerCase() + return this._extra_headers[key] } - - removeHeader(name) { + removeHeader (name) { if (arguments.length < 1) { - throw new Error('`name` is required for removeHeader(name).'); + throw new Error('`name` is required for removeHeader(name).') } if (this._started) { - throw new Error('Can\'t remove headers after they are sent.'); + throw new Error('Can\'t remove headers after they are sent.') } - let key = name.toLowerCase(); - delete this._extra_headers[key]; - this._url_request.removeExtraHeader(name); + const key = name.toLowerCase() + delete this._extra_headers[key] + this._url_request.removeExtraHeader(name) } - - _write(chunk, encoding, callback, is_last) { - - let chunk_is_string = typeof chunk === 'string'; - let chunk_is_buffer = chunk instanceof Buffer; - if (!chunk_is_string && !chunk_is_buffer) { - throw new TypeError('First argument must be a string or Buffer.'); + _write (chunk, encoding, callback, isLast) { + let chunkIsString = typeof chunk === 'string' + let chunkIsBuffer = chunk instanceof Buffer + if (!chunkIsString && !chunkIsBuffer) { + throw new TypeError('First argument must be a string or Buffer.') } - if (chunk_is_string) { + if (chunkIsString) { // We convert all strings into binary buffers. - chunk = Buffer.from(chunk, encoding); + chunk = Buffer.from(chunk, encoding) } // Headers are assumed to be sent on first call to _writeBuffer, // i.e. after the first call to write or end. - let result = this._url_request.write(chunk, is_last); + let result = this._url_request.write(chunk, isLast) - // Since writing to the network is asynchronous, we conservatively + // Since writing to the network is asynchronous, we conservatively // assume that request headers are written after delivering the first // buffer to the network IO thread. if (!this._started) { - this._url_request.setChunkedUpload(this.chunkedEncoding); - this._started = true; + this._url_request.setChunkedUpload(this.chunkedEncoding) + this._started = true } // The write callback is fired asynchronously to mimic Node.js. if (callback) { - process.nextTick(callback); + process.nextTick(callback) } - return result; + return result } - write(data, encoding, callback) { - + write (data, encoding, callback) { if (this._finished) { - let error = new Error('Write after end.'); - process.nextTick(writeAfterEndNT, this, error, callback); - return true; + let error = new Error('Write after end.') + process.nextTick(writeAfterEndNT, this, error, callback) + return true } - return this._write(data, encoding, callback, false); + return this._write(data, encoding, callback, false) } - - end(data, encoding, callback) { + end (data, encoding, callback) { if (this._finished) { - return false; + return false } - - this._finished = true; + + this._finished = true if (typeof data === 'function') { - callback = data; - encoding = null; - data = null; + callback = data + encoding = null + data = null } else if (typeof encoding === 'function') { - callback = encoding; - encoding = null; + callback = encoding + encoding = null } - data = data || ''; + data = data || '' - return this._write(data, encoding, callback, true); + return this._write(data, encoding, callback, true) } - abort() { + abort () { if (!this._started) { - // Does nothing if stream - return; + // Does nothing if stream + return } if (!this._aborted) { - this._url_request.abort(); - this._aborted = true; - process.nextTick( ()=>{ - this.emit('abort'); - } ); + this._url_request.abort() + this._aborted = true + process.nextTick(() => { + this.emit('abort') + }) } } } -function writeAfterEndNT(self, error, callback) { - self.emit('error', error); - if (callback) callback(error); +function writeAfterEndNT (self, error, callback) { + self.emit('error', error) + if (callback) callback(error) } - -Net.prototype.request = function(options, callback) { - return new ClientRequest(options, callback); +Net.prototype.request = function (options, callback) { + return new ClientRequest(options, callback) } -net.ClientRequest = ClientRequest; +net.ClientRequest = ClientRequest module.exports = net - \ No newline at end of file From bde30b90e871cba8414e416528e30caa0edadfbd Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 30 Sep 2016 14:17:29 +0200 Subject: [PATCH 13/62] Adding some implementation comments. Enforcing Chromium coding conventions. --- atom/browser/api/atom_api_url_request.cc | 5 +- atom/browser/api/atom_api_url_request.h | 97 ++++++++++++++++++++---- atom/browser/net/atom_url_request.cc | 12 ++- atom/browser/net/atom_url_request.h | 27 +++---- 4 files changed, 101 insertions(+), 40 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index ad2d66f5fe..016edbe0c7 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -13,6 +13,7 @@ #include "native_mate/dictionary.h" + namespace mate { template<> @@ -83,8 +84,8 @@ namespace atom { namespace api { URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) - : weak_ptr_factory_(this) { - InitWith(isolate, wrapper); + : weak_ptr_factory_(this) { + InitWith(isolate, wrapper); } URLRequest::~URLRequest() { diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 89702b0d40..20eb66d3f0 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -7,18 +7,88 @@ #include #include +#include "atom/browser/api/event_emitter.h" #include "atom/browser/api/trackable_object.h" +#include "base/memory/weak_ptr.h" #include "native_mate/handle.h" +#include "native_mate/wrappable_base.h" +#include "net/base/auth.h" +#include "net/base/io_buffer.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request_context.h" - namespace atom { class AtomURLRequest; namespace api { +// +// The URLRequest class implements the V8 binding between the JavaScript API +// and Chromium native net library. It is responsible for handling HTTP/HTTPS +// requests. +// +// The current class provides only the binding layer. Two other JavaScript +// classes (ClientRequest and IncomingMessage) in the net module provide the +// final API, including some state management and arguments validation. +// +// URLRequest's methods fall into two main categories: command and event methods. +// They are always executed on the Browser's UI thread. +// Command methods are called directly from JavaScript code via the API defined +// in BuildPrototype. A command method is generally implemented by forwarding +// the call to a corresponding method on AtomURLRequest which does the +// synchronization on the Browser IO thread. The latter then calls into Chromium +// net library. On the other hand, net library events originate on the IO +// thread in AtomURLRequest and are synchronized back on the UI thread, then +// forwarded to a corresponding event method in URLRequest and then to +// JavaScript via the EmitRequestEvent/EmitResponseEvent helpers. +// +// URLRequest lifetime management: we followed the Wrapper/Wrappable pattern +// defined in native_mate. However, we augment that pattern with a pin/unpin +// mechanism. The main reason is that we want the JS API to provide a similar +// lifetime guarantees as the XMLHttpRequest. +// https://xhr.spec.whatwg.org/#garbage-collection +// +// The primary motivation is to not garbage collect a URLInstance as long as the +// object is emitting network events. For instance, in the following JS code +// +// (function() { +// let request = new URLRequest(...); +// request.on('response', (response)=>{ +// response.on('data', (data) = > { +// console.log(data.toString()); +// }); +// }); +// })(); +// +// we still want data to be logged even if the response/request objects are no +// more referenced in JavaScript. +// +// Binding by simply following the native_mate Wrapper/Wrappable pattern will +// delete the URLRequest object when the corresponding JS object is collected. +// The v8 handle is a private member in WrappableBase and it is always weak, +// there is no way to make it strong without changing native_mate. +// The solution we implement consists of maintaining some kind of state that +// prevents collection of JS wrappers as long as the request is emitting network +// events. At initialization, the object is unpinned. When the request starts, +// it is pinned. When no more events would be emitted, the object is unpinned +// and lifetime is again managed by the standard native mate Wrapper/Wrappable +// pattern. +// +// pin/unpin: are implemented by constructing/reseting a V8 strong persistent +// handle. +// +// The URLRequest/AtmURLRequest interaction could have been implemented in a +// single class. However, it implies that the resulting class lifetime will be +// managed by two conflicting mechanisms: JavaScript garbage collection and +// Chromium reference counting. Reasoning about lifetime issues become much +// more complex. +// +// We chose to split the implementation into two classes linked via a strong/weak +// pointers. A URLRequest instance is deleted if it is unpinned and the +// corresponding JS wrapper object is garbage collected. On the other hand, +// an AtmURLRequest instance lifetime is totally governed by reference counting. +// class URLRequest : public mate::EventEmitter { public: static mate::WrappableBase* New(mate::Arguments* args); @@ -27,12 +97,19 @@ class URLRequest : public mate::EventEmitter { v8::Isolate* isolate, v8::Local prototype); + // Methods for reporting events into JavaScript. + void OnAuthenticationRequired( + scoped_refptr auth_info); + void OnResponseStarted(); + void OnResponseData(scoped_refptr data); + void OnResponseCompleted(); + void OnError(const std::string& error); + protected: explicit URLRequest(v8::Isolate* isolate, v8::Local wrapper); ~URLRequest() override; - private: bool Write(scoped_refptr buffer, bool is_last); @@ -41,21 +118,12 @@ class URLRequest : public mate::EventEmitter { void RemoveExtraHeader(const std::string& name); void SetChunkedUpload(bool is_chunked_upload); - friend class AtomURLRequest; - void OnAuthenticationRequired( - scoped_refptr auth_info); - void OnResponseStarted(); - void OnResponseData(scoped_refptr data); - void OnResponseCompleted(); - void OnError(const std::string& error); - int StatusCode() const; std::string StatusMessage() const; scoped_refptr RawResponseHeaders() const; uint32_t ResponseHttpVersionMajor() const; uint32_t ResponseHttpVersionMinor() const; - template std::array, sizeof...(ArgTypes)> BuildArgsArray(ArgTypes... args) const; @@ -70,7 +138,10 @@ class URLRequest : public mate::EventEmitter { void unpin(); scoped_refptr atom_request_; + + // Used to implement pin/unpin. v8::Global wrapper_; + base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(URLRequest); @@ -94,7 +165,6 @@ void URLRequest::EmitRequestEvent(ArgTypes... args) { _emitRequestEvent->Call(wrapper, arguments.size(), arguments.data()); } - template void URLRequest::EmitResponseEvent(ArgTypes... args) { auto arguments = BuildArgsArray(args...); @@ -105,9 +175,6 @@ void URLRequest::EmitResponseEvent(ArgTypes... args) { _emitResponseEvent->Call(wrapper, arguments.size(), arguments.data()); } - - - } // namespace api } // namespace atom diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 6b07a2e3ef..6a20dc4768 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -50,9 +50,9 @@ class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { } // namespace internal AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) - : delegate_(delegate) - , response_read_buffer_(new net::IOBuffer(kBufferSize)) - , is_chunked_upload_(false) { + : delegate_(delegate), + response_read_buffer_(new net::IOBuffer(kBufferSize)), + is_chunked_upload_(false) { } AtomURLRequest::~AtomURLRequest() { @@ -263,8 +263,7 @@ void AtomURLRequest::ReadResponse() { // completed immediately, without trying to read any data back (all we care // about is the response code and headers, which we already have). int bytes_read = 0; - if (request_->status().is_success() - /* TODO && (request_type_ != URLFetcher::HEAD)*/) + if (request_->status().is_success()) if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) bytes_read = -1; OnReadCompleted(request_.get(), bytes_read); @@ -297,8 +296,7 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, kBufferSize, &bytes_read)); - if (!status.is_io_pending() - /* TODO || request_type_ == URLFetcher::HEAD*/ ) + if (!status.is_io_pending()) content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index dd6e3d11e0..f065bc5249 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -8,25 +8,19 @@ #include #include +#include "atom/browser/api/atom_api_url_request.h" +#include "atom/browser/atom_browser_context.h" #include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "net/base/auth.h" #include "net/base/chunked_upload_data_stream.h" +#include "net/base/io_buffer.h" +#include "net/base/upload_element_reader.h" +#include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" - -namespace net { -class IOBuffer; -class IOBufferWithSize; -class DrainableIOBuffer; -}; - namespace atom { -class AtomBrowserContext; - -namespace api { -class URLRequest; -} - class AtomURLRequest : public base::RefCountedThreadSafe, public net::URLRequest::Delegate { public: @@ -56,6 +50,10 @@ class AtomURLRequest : public base::RefCountedThreadSafe, private: friend class base::RefCountedThreadSafe; + + explicit AtomURLRequest(base::WeakPtr delegate); + ~AtomURLRequest()override; + void DoWriteBuffer(scoped_refptr buffer, bool is_last); void DoAbort() const; @@ -74,9 +72,6 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void InformDelegateResponseCompleted() const; void InformDelegateErrorOccured(const std::string& error) const; - explicit AtomURLRequest(base::WeakPtr delegate); - virtual ~AtomURLRequest(); - base::WeakPtr delegate_; std::unique_ptr request_; From ec1fc5a17ba09d03a7fcfb261e11824798126b3c Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 4 Oct 2016 17:12:17 +0200 Subject: [PATCH 14/62] Implementing error, close, finish, abort events management. --- atom/browser/api/atom_api_net.cc | 9 +- atom/browser/api/atom_api_net.h | 1 + atom/browser/api/atom_api_url_request.cc | 219 +++++++++++++++++++++-- atom/browser/api/atom_api_url_request.h | 116 +++++++++--- atom/browser/net/atom_url_request.cc | 92 ++++++---- atom/browser/net/atom_url_request.h | 12 +- lib/browser/api/net.js | 59 +++--- 7 files changed, 391 insertions(+), 117 deletions(-) diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index 2464393fcd..5d23f04886 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -29,7 +29,9 @@ void Net::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "Net")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) - .SetProperty("URLRequest", &Net::URLRequest); + .SetProperty("URLRequest", &Net::URLRequest) + .SetMethod("RequestGarbageCollectionForTesting", + &Net::RequestGarbageCollectionForTesting); } v8::Local Net::URLRequest(v8::Isolate* isolate) { @@ -37,6 +39,11 @@ v8::Local Net::URLRequest(v8::Isolate* isolate) { } +void Net::RequestGarbageCollectionForTesting() { + isolate()->RequestGarbageCollectionForTesting( + v8::Isolate::GarbageCollectionType::kFullGarbageCollection); +} + } // namespace api diff --git a/atom/browser/api/atom_api_net.h b/atom/browser/api/atom_api_net.h index 640a6445eb..04b882d259 100644 --- a/atom/browser/api/atom_api_net.h +++ b/atom/browser/api/atom_api_net.h @@ -24,6 +24,7 @@ class Net : public mate::EventEmitter { ~Net() override; private: + void RequestGarbageCollectionForTesting(); DISALLOW_COPY_AND_ASSIGN(Net); }; diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 016edbe0c7..29736a5d06 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -83,6 +83,84 @@ struct Converter> { namespace atom { namespace api { + +template +URLRequest::StateBase::StateBase(Flags initialState) + : state_(initialState) { +} + +template +void URLRequest::StateBase::SetFlag(Flags flag) { + state_ = static_cast(static_cast(state_) & + static_cast(flag)); +} + +template +bool URLRequest::StateBase::operator==(Flags flag) const { + return state_ == flag; +} + +template +bool URLRequest::StateBase::IsFlagSet(Flags flag) const { + return static_cast(state_) & static_cast(flag); +} + +URLRequest::RequestState::RequestState() + : StateBase(RequestStateFlags::kNotStarted) { +} + +bool URLRequest::RequestState::NotStarted() const { + return *this == RequestStateFlags::kNotStarted; +} + +bool URLRequest::RequestState::Started() const { + return IsFlagSet(RequestStateFlags::kStarted); +} + +bool URLRequest::RequestState::Finished() const { + return IsFlagSet(RequestStateFlags::kFinished); +} + +bool URLRequest::RequestState::Canceled() const { + return IsFlagSet(RequestStateFlags::kCanceled); +} + +bool URLRequest::RequestState::Failed() const { + return IsFlagSet(RequestStateFlags::kFailed); +} + +bool URLRequest::RequestState::Closed() const { + return IsFlagSet(RequestStateFlags::kClosed); +} + +URLRequest::ResponseState::ResponseState() + : StateBase(ResponseStateFlags::kNotStarted) { +} + +bool URLRequest::ResponseState::NotStarted() const { + return *this == ResponseStateFlags::kNotStarted; +} + +bool URLRequest::ResponseState::Started() const { + return IsFlagSet(ResponseStateFlags::kStarted); +} + +bool URLRequest::ResponseState::Ended() const { + return IsFlagSet(ResponseStateFlags::kEnded); +} + +bool URLRequest::ResponseState::Canceled() const { + return IsFlagSet(ResponseStateFlags::kCanceled); +} + +bool URLRequest::ResponseState::Failed() const { + return IsFlagSet(ResponseStateFlags::kFailed); +} + +bool URLRequest::ResponseState::Closed() const { + return IsFlagSet(ResponseStateFlags::kClosed); +} + URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) : weak_ptr_factory_(this) { InitWith(isolate, wrapper); @@ -127,10 +205,12 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, // Request API .MakeDestroyable() .SetMethod("write", &URLRequest::Write) - .SetMethod("abort", &URLRequest::Abort) + .SetMethod("cancel", &URLRequest::Cancel) .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload) + .SetProperty("notStarted", &URLRequest::NotStarted) + .SetProperty("finished", &URLRequest::Finished) // Response APi .SetProperty("statusCode", &URLRequest::StatusCode) .SetProperty("statusMessage", &URLRequest::StatusMessage) @@ -139,19 +219,81 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, .SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); } +bool URLRequest::NotStarted() const { + return request_state_.NotStarted(); +} + +bool URLRequest::Finished() const { + return request_state_.Finished(); +} + +bool URLRequest::Canceled() const { + return request_state_.Canceled(); +} + bool URLRequest::Write( scoped_refptr buffer, bool is_last) { - return atom_request_->Write(buffer, is_last); + if (request_state_.Canceled() || + request_state_.Failed() || + request_state_.Finished() || + request_state_.Closed()) { + return false; + } + + if (request_state_.NotStarted()) { + request_state_.SetFlag(RequestStateFlags::kStarted); + // Pin on first write. + pin(); + } + + if (is_last) { + request_state_.SetFlag(RequestStateFlags::kFinished); + EmitRequestEvent(true, "finish"); + } + + DCHECK(atom_request_); + if (atom_request_) { + return atom_request_->Write(buffer, is_last); + } + return false; } -void URLRequest::Abort() { - atom_request_->Abort(); +void URLRequest::Cancel() { + if (request_state_.Canceled()) { + // Cancel only once. + return; + } + + // Mark as canceled. + request_state_.SetFlag(RequestStateFlags::kCanceled); + + if (request_state_.Started()) { + // Really cancel if it was started. + atom_request_->Cancel(); + } + + if (!request_state_.Closed()) { + EmitRequestEvent(true, "abort"); + } + + + response_state_.SetFlag(ResponseStateFlags::kCanceled); + if (response_state_.Started() && !response_state_.Closed()) { + EmitResponseEvent(true, "aborted"); + } + Close(); } bool URLRequest::SetExtraHeader(const std::string& name, const std::string& value) { + // State must be equal to not started. + if (!request_state_.NotStarted()) { + // Cannot change headers after send. + return false; + } + if (!net::HttpUtil::IsValidHeaderName(name)) { return false; } @@ -165,45 +307,84 @@ bool URLRequest::SetExtraHeader(const std::string& name, } void URLRequest::RemoveExtraHeader(const std::string& name) { + // State must be equal to not started. + if (!request_state_.NotStarted()) { + // Cannot change headers after send. + return; + } atom_request_->RemoveExtraHeader(name); } void URLRequest::SetChunkedUpload(bool is_chunked_upload) { + // State must be equal to not started. + if (!request_state_.NotStarted()) { + // Cannot change headers after send. + return; + } atom_request_->SetChunkedUpload(is_chunked_upload); } void URLRequest::OnAuthenticationRequired( scoped_refptr auth_info) { EmitRequestEvent( + false, "login", auth_info.get(), base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); } - void URLRequest::OnResponseStarted() { + if (request_state_.Canceled() || + request_state_.Failed() || + request_state_.Closed()) { + // Don't emit any event after request cancel. + return; + } + response_state_.SetFlag(ResponseStateFlags::kStarted); Emit("response"); } void URLRequest::OnResponseData( scoped_refptr buffer) { + if (request_state_.Canceled()) { + // Don't emit any event after request cancel. + return; + } if (!buffer || !buffer->data() || !buffer->size()) { return; } - EmitResponseEvent("data", buffer); + if (!response_state_.Closed()) { + EmitResponseEvent(false, "data", buffer); + } } void URLRequest::OnResponseCompleted() { - EmitResponseEvent("end"); - unpin(); - atom_request_ = nullptr; + response_state_.SetFlag(ResponseStateFlags::kEnded); + if (request_state_.Canceled()) { + // Don't emit any event after request cancel. + return; + } + if (!response_state_.Closed()) { + EmitResponseEvent(false, "end"); + } + Close(); } -void URLRequest::OnError(const std::string& error) { +void URLRequest::OnRequestError(const std::string& error) { + request_state_.SetFlag(RequestStateFlags::kFailed); auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); - EmitRequestEvent("error", error_object); + EmitRequestEvent(false, "error", error_object); + Close(); } +void URLRequest::OnResponseError(const std::string& error) { + response_state_.SetFlag(ResponseStateFlags::kFailed); + auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); + EmitResponseEvent(false, "error", error_object); + Close(); +} + + int URLRequest::StatusCode() const { if (auto response_headers = atom_request_->GetResponseHeaders()) { return response_headers->response_code(); @@ -238,6 +419,22 @@ uint32_t URLRequest::ResponseHttpVersionMinor() const { return 0; } +void URLRequest::Close() { + if (!response_state_.Closed()) { + response_state_.SetFlag(ResponseStateFlags::kClosed); + if (response_state_.Started()) { + // Emit a close event if we really have a response object. + EmitResponseEvent(true, "close"); + } + } + if (!request_state_.Closed()) { + request_state_.SetFlag(RequestStateFlags::kClosed); + EmitRequestEvent(true, "close"); + } + unpin(); + atom_request_ = nullptr; +} + void URLRequest::pin() { if (wrapper_.IsEmpty()) { wrapper_.Reset(isolate(), GetWrapper()); diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 20eb66d3f0..e5baed4f05 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -25,28 +25,28 @@ namespace api { // // The URLRequest class implements the V8 binding between the JavaScript API -// and Chromium native net library. It is responsible for handling HTTP/HTTPS +// and Chromium native net library. It is responsible for handling HTTP/HTTPS // requests. // -// The current class provides only the binding layer. Two other JavaScript -// classes (ClientRequest and IncomingMessage) in the net module provide the +// The current class provides only the binding layer. Two other JavaScript +// classes (ClientRequest and IncomingMessage) in the net module provide the // final API, including some state management and arguments validation. // -// URLRequest's methods fall into two main categories: command and event methods. -// They are always executed on the Browser's UI thread. +// URLRequest's methods fall into two main categories: command and event +// methods. They are always executed on the Browser's UI thread. // Command methods are called directly from JavaScript code via the API defined -// in BuildPrototype. A command method is generally implemented by forwarding -// the call to a corresponding method on AtomURLRequest which does the -// synchronization on the Browser IO thread. The latter then calls into Chromium -// net library. On the other hand, net library events originate on the IO +// in BuildPrototype. A command method is generally implemented by forwarding +// the call to a corresponding method on AtomURLRequest which does the +// synchronization on the Browser IO thread. The latter then calls into Chromium +// net library. On the other hand, net library events originate on the IO // thread in AtomURLRequest and are synchronized back on the UI thread, then -// forwarded to a corresponding event method in URLRequest and then to +// forwarded to a corresponding event method in URLRequest and then to // JavaScript via the EmitRequestEvent/EmitResponseEvent helpers. // -// URLRequest lifetime management: we followed the Wrapper/Wrappable pattern +// URLRequest lifetime management: we followed the Wrapper/Wrappable pattern // defined in native_mate. However, we augment that pattern with a pin/unpin // mechanism. The main reason is that we want the JS API to provide a similar -// lifetime guarantees as the XMLHttpRequest. +// lifetime guarantees as the XMLHttpRequest. // https://xhr.spec.whatwg.org/#garbage-collection // // The primary motivation is to not garbage collect a URLInstance as long as the @@ -61,32 +61,32 @@ namespace api { // }); // })(); // -// we still want data to be logged even if the response/request objects are no +// we still want data to be logged even if the response/request objects are n // more referenced in JavaScript. // // Binding by simply following the native_mate Wrapper/Wrappable pattern will // delete the URLRequest object when the corresponding JS object is collected. -// The v8 handle is a private member in WrappableBase and it is always weak, +// The v8 handle is a private member in WrappableBase and it is always weak, // there is no way to make it strong without changing native_mate. -// The solution we implement consists of maintaining some kind of state that +// The solution we implement consists of maintaining some kind of state that // prevents collection of JS wrappers as long as the request is emitting network -// events. At initialization, the object is unpinned. When the request starts, -// it is pinned. When no more events would be emitted, the object is unpinned -// and lifetime is again managed by the standard native mate Wrapper/Wrappable +// events. At initialization, the object is unpinned. When the request starts, +// it is pinned. When no more events would be emitted, the object is unpinned +// and lifetime is again managed by the standard native mate Wrapper/Wrappable // pattern. // -// pin/unpin: are implemented by constructing/reseting a V8 strong persistent +// pin/unpin: are implemented by constructing/reseting a V8 strong persistent // handle. // -// The URLRequest/AtmURLRequest interaction could have been implemented in a +// The URLRequest/AtmURLRequest interaction could have been implemented in a // single class. However, it implies that the resulting class lifetime will be -// managed by two conflicting mechanisms: JavaScript garbage collection and -// Chromium reference counting. Reasoning about lifetime issues become much +// managed by two conflicting mechanisms: JavaScript garbage collection and +// Chromium reference counting. Reasoning about lifetime issues become much // more complex. // -// We chose to split the implementation into two classes linked via a strong/weak -// pointers. A URLRequest instance is deleted if it is unpinned and the -// corresponding JS wrapper object is garbage collected. On the other hand, +// We chose to split the implementation into two classes linked via a +// strong/weak pointers. A URLRequest instance is deleted if it is unpinned and +// the corresponding JS wrapper object is garbage collected. On the other hand, // an AtmURLRequest instance lifetime is totally governed by reference counting. // class URLRequest : public mate::EventEmitter { @@ -103,7 +103,8 @@ class URLRequest : public mate::EventEmitter { void OnResponseStarted(); void OnResponseData(scoped_refptr data); void OnResponseCompleted(); - void OnError(const std::string& error); + void OnRequestError(const std::string& error); + void OnResponseError(const std::string& error); protected: explicit URLRequest(v8::Isolate* isolate, @@ -111,13 +112,70 @@ class URLRequest : public mate::EventEmitter { ~URLRequest() override; private: + template + class StateBase { + public: + void SetFlag(Flags flag); + protected: + explicit StateBase(Flags initialState); + bool operator==(Flags flag) const; + bool IsFlagSet(Flags flag) const; + private: + Flags state_; + }; + + enum class RequestStateFlags { + kNotStarted = 0x0, + kStarted = 0x1, + kFinished = 0x2, + kCanceled = 0x4, + kFailed = 0x8, + kClosed = 0x10 + }; + + class RequestState : public StateBase { + public: + RequestState(); + bool NotStarted() const; + bool Started() const; + bool Finished() const; + bool Canceled() const; + bool Failed() const; + bool Closed() const; + }; + + enum class ResponseStateFlags { + kNotStarted = 0x0, + kStarted = 0x1, + kEnded = 0x2, + kCanceled = 0x4, + kFailed = 0x8, + kClosed = 0x10 + }; + + class ResponseState : public StateBase { + public: + ResponseState(); + bool NotStarted() const; + bool Started() const; + bool Ended() const; + bool Canceled() const; + bool Failed() const; + bool Closed() const; + }; + + bool NotStarted() const; + bool Finished() const; + bool Canceled() const; + bool Failed() const; bool Write(scoped_refptr buffer, bool is_last); - void Abort(); + void Cancel(); bool SetExtraHeader(const std::string& name, const std::string& value); void RemoveExtraHeader(const std::string& name); void SetChunkedUpload(bool is_chunked_upload); + bool CanReadHeaders() const; int StatusCode() const; std::string StatusMessage() const; scoped_refptr RawResponseHeaders() const; @@ -134,16 +192,20 @@ class URLRequest : public mate::EventEmitter { template void EmitResponseEvent(ArgTypes... args); + void Close(); void pin(); void unpin(); scoped_refptr atom_request_; + RequestState request_state_; + ResponseState response_state_; // Used to implement pin/unpin. v8::Global wrapper_; base::WeakPtrFactory weak_ptr_factory_; + DISALLOW_COPY_AND_ASSIGN(URLRequest); }; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 6a20dc4768..f985915018 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -106,12 +106,12 @@ void AtomURLRequest::SetChunkedUpload(bool is_chunked_upload) { } -void AtomURLRequest::Abort() const { +void AtomURLRequest::Cancel() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, - base::Bind(&AtomURLRequest::DoAbort, this)); + base::Bind(&AtomURLRequest::DoCancel, this)); } void AtomURLRequest::SetExtraHeader(const std::string& name, @@ -202,7 +202,7 @@ void AtomURLRequest::DoWriteBuffer( } } -void AtomURLRequest::DoAbort() const { +void AtomURLRequest::DoCancel() const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); request_->Cancel(); } @@ -235,38 +235,32 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { const auto& status = request_->status(); if (status.is_success()) { - // Cache net::HttpResponseHeaders instance, a read-only objects - // so that headers and other http metainformation can be simultaneously - // read from UI thread while request data is simulataneously streaming - // on IO thread. - response_headers_ = request_->response_headers(); + // Success or pending trigger a Read. content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this)); ReadResponse(); - } else { + } else if (status.status() == net::URLRequestStatus::Status::FAILED) { + // Report error on Start. + DoCancel(); auto error = net::ErrorToString(status.ToNetError()); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateErrorOccured, + base::Bind(&AtomURLRequest::InformDelegateRequestErrorOccured, this, std::move(error))); } + // We don't report an error is the request is canceled. } void AtomURLRequest::ReadResponse() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - // Some servers may treat HEAD requests as GET requests. To free up the - // network connection as soon as possible, signal that the request has - // completed immediately, without trying to read any data back (all we care - // about is the response code and headers, which we already have). - int bytes_read = 0; - if (request_->status().is_success()) - if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) - bytes_read = -1; - OnReadCompleted(request_.get(), bytes_read); + int bytes_read = -1; + if (request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) { + OnReadCompleted(request_.get(), bytes_read); + } } @@ -277,36 +271,53 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, DCHECK_EQ(request, request_.get()); const auto status = request_->status(); + + bool response_error = false; + bool data_ended = false; + bool data_transfer_error = false; do { - if (!status.is_success() || bytes_read <= 0) { - auto error = net::ErrorToString(status.ToNetError()); - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateErrorOccured, - this, - std::move(error))); + if (!status.is_success()) { + response_error = true; + break; + } + if (bytes_read == 0) { + data_ended = true; + break; + } + if (bytes_read < 0 || !CopyAndPostBuffer(bytes_read)) { + data_transfer_error = true; break; } - - const auto result = CopyAndPostBuffer(bytes_read); - if (!result) - // Failed to transfer data to UI thread. - return; } while (request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)); - - if (!status.is_io_pending()) - + if (response_error) { + DoCancel(); + auto error = net::ErrorToString(status.ToNetError()); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, + this, + std::move(error))); + } else if (data_ended) { content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); + } else if (data_transfer_error) { + // We abort the request on corrupted data transfer. + DoCancel(); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, + this, + "Failed to transfer data from IO to UI thread.")); + } } bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - // data is only a wrapper for the async response_read_buffer_. + // data is only a wrapper for the asynchronous response_read_buffer_. // Make a deep copy of payload and transfer ownership to the UI thread. auto buffer_copy = new net::IOBufferWithSize(bytes_read); memcpy(buffer_copy->data(), response_read_buffer_->data(), bytes_read); @@ -349,13 +360,20 @@ void AtomURLRequest::InformDelegateResponseCompleted() const { delegate_->OnResponseCompleted(); } -void AtomURLRequest::InformDelegateErrorOccured( +void AtomURLRequest::InformDelegateRequestErrorOccured( const std::string& error) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) - delegate_->OnError(error); + delegate_->OnRequestError(error); } +void AtomURLRequest::InformDelegateResponseErrorOccured( + const std::string& error) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + + if (delegate_) + delegate_->OnResponseError(error); +} } // namespace atom diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index f065bc5249..f0708c0ac2 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -33,7 +33,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, bool Write(scoped_refptr buffer, bool is_last); void SetChunkedUpload(bool is_chunked_upload); - void Abort() const; + void Cancel() const; void SetExtraHeader(const std::string& name, const std::string& value) const; void RemoveExtraHeader(const std::string& name) const; void PassLoginInformation(const base::string16& username, @@ -51,12 +51,12 @@ class AtomURLRequest : public base::RefCountedThreadSafe, private: friend class base::RefCountedThreadSafe; - explicit AtomURLRequest(base::WeakPtr delegate); - ~AtomURLRequest()override; + explicit AtomURLRequest(base::WeakPtr delegate); + ~AtomURLRequest()override; void DoWriteBuffer(scoped_refptr buffer, bool is_last); - void DoAbort() const; + void DoCancel() const; void DoSetAuth(const base::string16& username, const base::string16& password) const; void DoCancelAuth() const; @@ -70,7 +70,8 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void InformDelegateResponseData( scoped_refptr data) const; void InformDelegateResponseCompleted() const; - void InformDelegateErrorOccured(const std::string& error) const; + void InformDelegateRequestErrorOccured(const std::string& error) const; + void InformDelegateResponseErrorOccured(const std::string& error) const; base::WeakPtr delegate_; std::unique_ptr request_; @@ -81,7 +82,6 @@ class AtomURLRequest : public base::RefCountedThreadSafe, std::vector> upload_element_readers_; scoped_refptr response_read_buffer_; - scoped_refptr response_headers_; DISALLOW_COPY_AND_ASSIGN(AtomURLRequest); }; diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 1471ae4f1d..5f2c7345d4 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -50,12 +50,24 @@ class IncomingMessage extends EventEmitter { } -URLRequest.prototype._emitRequestEvent = function () { - this._request.emit.apply(this._request, arguments) +URLRequest.prototype._emitRequestEvent = function (async, ...rest) { + if (async) { + process.nextTick(() => { + this._request.emit.apply(this._request, rest) + }) + } else { + this._request.emit.apply(this._request, rest) + } } -URLRequest.prototype._emitResponseEvent = function () { - this._response.emit.apply(this._response, arguments) +URLRequest.prototype._emitResponseEvent = function (async, ...rest) { + if (async) { + process.nextTick(() => { + this._request.emit.apply(this._request, rest) + }) + } else { + this._response.emit.apply(this._response, rest) + } } class ClientRequest extends EventEmitter { @@ -127,16 +139,7 @@ class ClientRequest extends EventEmitter { } } - // Flag to prevent request's headers modifications after - // headers flush. - this._started = false - - this._aborted = false - - // Flag to prevent writings after end. - this._finished = false - - // Set when the request uses chuned encoding. Can be switched + // Set when the request uses chunked encoding. Can be switched // to true only once and never set back to false. this._chunkedEncoding = false @@ -161,7 +164,7 @@ class ClientRequest extends EventEmitter { } set chunkedEncoding (value) { - if (this._started) { + if (!this._url_request.notStarted) { throw new Error('Can\'t set the transfer encoding, headers have been sent.') } this._chunkedEncoding = value @@ -174,7 +177,7 @@ class ClientRequest extends EventEmitter { if (value === undefined) { throw new Error('`value` required in setHeader("' + name + '", value).') } - if (this._started) { + if (!this._url_request.notStarted) { throw new Error('Can\'t set headers after they are sent.') } @@ -201,7 +204,7 @@ class ClientRequest extends EventEmitter { throw new Error('`name` is required for removeHeader(name).') } - if (this._started) { + if (!this._url_request.notStarted) { throw new Error('Can\'t remove headers after they are sent.') } @@ -229,9 +232,8 @@ class ClientRequest extends EventEmitter { // Since writing to the network is asynchronous, we conservatively // assume that request headers are written after delivering the first // buffer to the network IO thread. - if (!this._started) { + if (!this._url_request.notStarted) { this._url_request.setChunkedUpload(this.chunkedEncoding) - this._started = true } // The write callback is fired asynchronously to mimic Node.js. @@ -243,7 +245,7 @@ class ClientRequest extends EventEmitter { } write (data, encoding, callback) { - if (this._finished) { + if (this._url_request.finished) { let error = new Error('Write after end.') process.nextTick(writeAfterEndNT, this, error, callback) return true @@ -253,12 +255,10 @@ class ClientRequest extends EventEmitter { } end (data, encoding, callback) { - if (this._finished) { + if (this._url_request.finished) { return false } - this._finished = true - if (typeof data === 'function') { callback = data encoding = null @@ -274,18 +274,7 @@ class ClientRequest extends EventEmitter { } abort () { - if (!this._started) { - // Does nothing if stream - return - } - - if (!this._aborted) { - this._url_request.abort() - this._aborted = true - process.nextTick(() => { - this.emit('abort') - }) - } + this._url_request.cancel() } } From e472d1176124c7dd8775762fda49352e3b7ad4c9 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 4 Oct 2016 17:33:34 +0200 Subject: [PATCH 15/62] Caching response headers so that AtomURLRequest can be freed after the close event. --- atom/browser/api/atom_api_url_request.cc | 26 +++++++++++++----------- atom/browser/api/atom_api_url_request.h | 5 +++-- atom/browser/net/atom_url_request.cc | 22 ++++++++------------ atom/browser/net/atom_url_request.h | 4 ++-- 4 files changed, 28 insertions(+), 29 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 29736a5d06..14a1d8a379 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -51,7 +51,7 @@ struct Converter> { auto size = node::Buffer::Length(val); if (size == 0) { - // Support conversoin from empty buffer. A use case is + // Support conversion from empty buffer. A use case is // a GET request without body. // Since zero-sized IOBuffer(s) are not supported, we set the // out pointer to null. @@ -60,7 +60,7 @@ struct Converter> { } auto data = node::Buffer::Data(val); if (!data) { - // This is an error as size is positif but data is null. + // This is an error as size is positive but data is null. return false; } @@ -333,13 +333,15 @@ void URLRequest::OnAuthenticationRequired( base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); } -void URLRequest::OnResponseStarted() { +void URLRequest::OnResponseStarted( + scoped_refptr response_headers) { if (request_state_.Canceled() || request_state_.Failed() || request_state_.Closed()) { // Don't emit any event after request cancel. return; } + response_headers_ = response_headers; response_state_.SetFlag(ResponseStateFlags::kStarted); Emit("response"); } @@ -386,35 +388,35 @@ void URLRequest::OnResponseError(const std::string& error) { int URLRequest::StatusCode() const { - if (auto response_headers = atom_request_->GetResponseHeaders()) { - return response_headers->response_code(); + if (response_headers_) { + return response_headers_->response_code(); } return -1; } std::string URLRequest::StatusMessage() const { std::string result; - if (auto response_headers = atom_request_->GetResponseHeaders()) { - result = response_headers->GetStatusText(); + if (response_headers_) { + result = response_headers_->GetStatusText(); } return result; } scoped_refptr URLRequest::RawResponseHeaders() const { - return atom_request_->GetResponseHeaders(); + return response_headers_; } uint32_t URLRequest::ResponseHttpVersionMajor() const { - if (auto response_headers = atom_request_->GetResponseHeaders()) { - return response_headers->GetHttpVersion().major_value(); + if (response_headers_) { + return response_headers_->GetHttpVersion().major_value(); } return 0; } uint32_t URLRequest::ResponseHttpVersionMinor() const { - if (auto response_headers = atom_request_->GetResponseHeaders()) { - return response_headers->GetHttpVersion().minor_value(); + if (response_headers_) { + return response_headers_->GetHttpVersion().minor_value(); } return 0; } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index e5baed4f05..4f688e4545 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -100,7 +100,8 @@ class URLRequest : public mate::EventEmitter { // Methods for reporting events into JavaScript. void OnAuthenticationRequired( scoped_refptr auth_info); - void OnResponseStarted(); + void OnResponseStarted( + scoped_refptr response_headers); void OnResponseData(scoped_refptr data); void OnResponseCompleted(); void OnRequestError(const std::string& error); @@ -202,7 +203,7 @@ class URLRequest : public mate::EventEmitter { // Used to implement pin/unpin. v8::Global wrapper_; - + scoped_refptr response_headers_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index f985915018..d795b0b518 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -126,15 +126,6 @@ void AtomURLRequest::RemoveExtraHeader(const std::string& name) const { request_->RemoveRequestHeaderByName(name); } - - -scoped_refptr -AtomURLRequest::GetResponseHeaders() const { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - return request_->response_headers(); -} - - void AtomURLRequest::PassLoginInformation(const base::string16& username, const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -173,7 +164,7 @@ void AtomURLRequest::DoWriteBuffer( buffer->size(), is_last); else if (is_last) - // Empty buffer and last chunck, i.e. request.end(). + // Empty buffer and last chunk, i.e. request.end(). auto write_result = chunked_stream_writer_->AppendData( nullptr, 0, @@ -233,12 +224,16 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_EQ(request, request_.get()); + scoped_refptr response_headers = + request->response_headers(); const auto& status = request_->status(); if (status.is_success()) { // Success or pending trigger a Read. content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this)); + base::Bind(&AtomURLRequest::InformDelegateResponseStarted, + this, + response_headers)); ReadResponse(); } else if (status.status() == net::URLRequestStatus::Status::FAILED) { @@ -337,10 +332,11 @@ void AtomURLRequest::InformDelegateAuthenticationRequired( delegate_->OnAuthenticationRequired(auth_info); } -void AtomURLRequest::InformDelegateResponseStarted() const { +void AtomURLRequest::InformDelegateResponseStarted( + scoped_refptr response_headers) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) - delegate_->OnResponseStarted(); + delegate_->OnResponseStarted(response_headers); } void AtomURLRequest::InformDelegateResponseData( diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index f0708c0ac2..6a867aac08 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -38,7 +38,6 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void RemoveExtraHeader(const std::string& name) const; void PassLoginInformation(const base::string16& username, const base::string16& password) const; - scoped_refptr GetResponseHeaders() const; protected: // Overrides of net::URLRequest::Delegate @@ -66,7 +65,8 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void InformDelegateAuthenticationRequired( scoped_refptr auth_info) const; - void InformDelegateResponseStarted() const; + void InformDelegateResponseStarted( + scoped_refptr) const; void InformDelegateResponseData( scoped_refptr data) const; void InformDelegateResponseCompleted() const; From a655cca0a1f9f5a542a72d2c74de1f3ecddbf672 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 4 Oct 2016 17:54:34 +0200 Subject: [PATCH 16/62] Request/Response state simplification. Removing redundant state flags. --- atom/browser/api/atom_api_url_request.cc | 55 ++++++++++-------------- atom/browser/api/atom_api_url_request.h | 4 +- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 14a1d8a379..8e16c02bb5 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -149,18 +149,11 @@ bool URLRequest::ResponseState::Ended() const { return IsFlagSet(ResponseStateFlags::kEnded); } -bool URLRequest::ResponseState::Canceled() const { - return IsFlagSet(ResponseStateFlags::kCanceled); -} bool URLRequest::ResponseState::Failed() const { return IsFlagSet(ResponseStateFlags::kFailed); } -bool URLRequest::ResponseState::Closed() const { - return IsFlagSet(ResponseStateFlags::kClosed); -} - URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) : weak_ptr_factory_(this) { InitWith(isolate, wrapper); @@ -261,7 +254,8 @@ bool URLRequest::Write( void URLRequest::Cancel() { - if (request_state_.Canceled()) { + if (request_state_.Canceled() || + request_state_.Closed()) { // Cancel only once. return; } @@ -269,18 +263,14 @@ void URLRequest::Cancel() { // Mark as canceled. request_state_.SetFlag(RequestStateFlags::kCanceled); - if (request_state_.Started()) { + DCHECK(atom_request_); + if (atom_request_ && request_state_.Started()) { // Really cancel if it was started. atom_request_->Cancel(); } + EmitRequestEvent(true, "abort"); - if (!request_state_.Closed()) { - EmitRequestEvent(true, "abort"); - } - - - response_state_.SetFlag(ResponseStateFlags::kCanceled); - if (response_state_.Started() && !response_state_.Closed()) { + if (response_state_.Started() && !response_state_.Ended()) { EmitResponseEvent(true, "aborted"); } Close(); @@ -348,27 +338,31 @@ void URLRequest::OnResponseStarted( void URLRequest::OnResponseData( scoped_refptr buffer) { - if (request_state_.Canceled()) { - // Don't emit any event after request cancel. + if (request_state_.Canceled() || + request_state_.Closed() || + request_state_.Failed() || + response_state_.Failed()) { + // In case we received an unexpected event from Chromium net, + // don't emit any data event after request cancel/error/close. return; } if (!buffer || !buffer->data() || !buffer->size()) { return; } - if (!response_state_.Closed()) { - EmitResponseEvent(false, "data", buffer); - } + EmitResponseEvent(false, "data", buffer); } void URLRequest::OnResponseCompleted() { - response_state_.SetFlag(ResponseStateFlags::kEnded); - if (request_state_.Canceled()) { - // Don't emit any event after request cancel. + if (request_state_.Canceled() || + request_state_.Closed() || + request_state_.Failed() || + response_state_.Failed()) { + // In case we received an unexpected event from Chromium net, + // don't emit any data event after request cancel/error/close. return; } - if (!response_state_.Closed()) { - EmitResponseEvent(false, "end"); - } + response_state_.SetFlag(ResponseStateFlags::kEnded); + EmitResponseEvent(false, "end"); Close(); } @@ -422,15 +416,12 @@ uint32_t URLRequest::ResponseHttpVersionMinor() const { } void URLRequest::Close() { - if (!response_state_.Closed()) { - response_state_.SetFlag(ResponseStateFlags::kClosed); + if (!request_state_.Closed()) { + request_state_.SetFlag(RequestStateFlags::kClosed); if (response_state_.Started()) { // Emit a close event if we really have a response object. EmitResponseEvent(true, "close"); } - } - if (!request_state_.Closed()) { - request_state_.SetFlag(RequestStateFlags::kClosed); EmitRequestEvent(true, "close"); } unpin(); diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 4f688e4545..672a6693ee 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -149,9 +149,7 @@ class URLRequest : public mate::EventEmitter { kNotStarted = 0x0, kStarted = 0x1, kEnded = 0x2, - kCanceled = 0x4, - kFailed = 0x8, - kClosed = 0x10 + kFailed = 0x4 }; class ResponseState : public StateBase { From 8c5751e9f7f666b64e42e76d0ef35d592ce2447e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 4 Oct 2016 18:09:36 +0200 Subject: [PATCH 17/62] Adding systematic checks on the atom_request_ pointer as it may be reset to null. --- atom/browser/api/atom_api_url_request.cc | 33 +++++++++++++++++++----- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 8e16c02bb5..c1c95e27e5 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -278,7 +278,7 @@ void URLRequest::Cancel() { bool URLRequest::SetExtraHeader(const std::string& name, const std::string& value) { - // State must be equal to not started. + // Request state must be in the initial non started state. if (!request_state_.NotStarted()) { // Cannot change headers after send. return false; @@ -292,7 +292,10 @@ bool URLRequest::SetExtraHeader(const std::string& name, return false; } - atom_request_->SetExtraHeader(name, value); + DCHECK(atom_request_); + if (atom_request_) { + atom_request_->SetExtraHeader(name, value); + } return true; } @@ -302,7 +305,10 @@ void URLRequest::RemoveExtraHeader(const std::string& name) { // Cannot change headers after send. return; } - atom_request_->RemoveExtraHeader(name); + DCHECK(atom_request_); + if (atom_request_) { + atom_request_->RemoveExtraHeader(name); + } } void URLRequest::SetChunkedUpload(bool is_chunked_upload) { @@ -311,11 +317,24 @@ void URLRequest::SetChunkedUpload(bool is_chunked_upload) { // Cannot change headers after send. return; } - atom_request_->SetChunkedUpload(is_chunked_upload); + DCHECK(atom_request_); + if (atom_request_) { + atom_request_->SetChunkedUpload(is_chunked_upload); + } } void URLRequest::OnAuthenticationRequired( - scoped_refptr auth_info) { + scoped_refptr auth_info) { + if (request_state_.Canceled() || + request_state_.Closed()) { + return; + } + + DCHECK(atom_request_); + if (!atom_request_) { + return; + } + EmitRequestEvent( false, "login", @@ -367,15 +386,15 @@ void URLRequest::OnResponseCompleted() { } void URLRequest::OnRequestError(const std::string& error) { - request_state_.SetFlag(RequestStateFlags::kFailed); auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); + request_state_.SetFlag(RequestStateFlags::kFailed); EmitRequestEvent(false, "error", error_object); Close(); } void URLRequest::OnResponseError(const std::string& error) { - response_state_.SetFlag(ResponseStateFlags::kFailed); auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); + response_state_.SetFlag(ResponseStateFlags::kFailed); EmitResponseEvent(false, "error", error_object); Close(); } From dcffb51e5e5e4002e4d440192f5dd1535f4ae162 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 5 Oct 2016 15:06:35 +0200 Subject: [PATCH 18/62] Adding net module spec file skeleton. --- spec/api-net-spec.js | 137 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 spec/api-net-spec.js diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js new file mode 100644 index 0000000000..442e045ac9 --- /dev/null +++ b/spec/api-net-spec.js @@ -0,0 +1,137 @@ +const assert = require('assert') +const {net} = require('electron'); + +describe('net module', function() { + describe('HTTP basics', function() { + it ('should be able to fetch google.com', function(done) { + let response_event_emitted = false; + let data_event_emitted = false; + let end_event_emitted = false; + let finish_event_emitted = false; + const urlRequest = net.request({ + method: 'GET', + url: 'https://www.google.com' + }) + urlRequest.on('response', function(response) { + response_event_emitted = true; + const statusCode = response.statusCode + assert(typeof statusCode === 'number') + assert.equal(statusCode, 200) + const statusMessage = response.statusMessage + const rawHeaders = response.rawHeaders + assert(typeof rawHeaders === 'string') + assert(rawHeaders.length > 0) + const httpVersion = response.httpVersion; + assert(typeof httpVersion === 'string') + assert(httpVersion.length > 0) + const httpVersionMajor = response.httpVersionMajor; + assert(typeof httpVersionMajor === 'number') + assert(httpVersionMajor >= 1) + const httpVersionMinor = response.httpVersionMinor; + assert(typeof rawHeaders === 'number') + assert(httpVersionMinor >= 0) + let body = ''; + response.on('data', function(buffer) { + data_event_emitted = true; + body += buffer.toString() + assert(typeof body === 'string') + assert(body.length > 0) + }); + response.on('end', function() { + end_event_emitted = true; + }) + }); + urlRequest.on('finish', function() { + finish_event_emitted = true; + }) + urlRequest.on('error', function(error) { + assert.ifError(error); + }) + urlRequest.on('close', function() { + asset(response_event_emitted) + assert(data_event_emitted) + assert(end_event_emitted) + assert(finish_event_emitted) + done() + }) + urlRequest.end(); + }) + + it ('should be able to post data', function(done) { + let response_event_emitted = false; + let data_event_emitted = false; + let end_event_emitted = false; + let finish_event_emitted = false; + let urlRequest = net.request({ + method: 'POST', + url: 'http://httpbin.org/post' + }); + urlRequest.on('response', function(response) { + response_event_emitted = true; + const statusCode = response.statusCode + assert(typeof statusCode === 'number') + assert.equal(statusCode, 200) + const statusMessage = response.statusMessage + const rawHeaders = response.rawHeaders + assert(typeof rawHeaders === 'string') + assert(rawHeaders.length > 0) + const httpVersion = response.httpVersion; + assert(typeof httpVersion === 'string') + assert(httpVersion.length > 0) + const httpVersionMajor = response.httpVersionMajor; + assert(typeof httpVersionMajor === 'number') + assert(httpVersionMajor >= 1) + const httpVersionMinor = response.httpVersionMinor; + assert(typeof rawHeaders === 'number') + assert(httpVersionMinor >= 0) + let body = ''; + response.on('data', function(buffer) { + data_event_emitted = true; + body += buffer.toString() + assert(typeof body === 'string') + assert(body.length > 0) + }); + response.on('end', function() { + end_event_emitted = true; + }) + }); + urlRequest.on('finish', function() { + finish_event_emitted = true; + }) + urlRequest.on('error', function(error) { + assert.ifError(error); + }) + urlRequest.on('close', function() { + asset(response_event_emitted) + assert(data_event_emitted) + assert(end_event_emitted) + assert(finish_event_emitted) + done() + }) + for (let i = 0; i < 100; ++i) { + urlRequest.write('Hello World!'); + } + urlRequest.end(); + }) + }) + describe('ClientRequest API', function() { + it ('should be able to set a custom HTTP header', function() { + assert(false) + }) + it ('should be able to abort an HTTP request', function() { + assert(false) + }) + it ('should be able to pipe into a request', function() { + + }) + it ('should be able to create a request with options', function() { + + }) + it ('should be able to specify a custom session', function() { + }) + it ('should support chunked encoding', function() { + + }) + + }) +}) \ No newline at end of file From 42bae9d71d1f69d0a8d4e794a8f08d1984854982 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 6 Oct 2016 14:14:05 +0200 Subject: [PATCH 19/62] Making the HTTP response a full-fledged Readable stream. --- atom/browser/api/atom_api_url_request.cc | 4 ++-- lib/browser/api/net.js | 29 +++++++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index c1c95e27e5..e3d0f59929 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -368,7 +368,7 @@ void URLRequest::OnResponseData( if (!buffer || !buffer->data() || !buffer->size()) { return; } - EmitResponseEvent(false, "data", buffer); + Emit("data", buffer); } void URLRequest::OnResponseCompleted() { @@ -381,7 +381,7 @@ void URLRequest::OnResponseCompleted() { return; } response_state_.SetFlag(ResponseStateFlags::kEnded); - EmitResponseEvent(false, "end"); + Emit("end"); Close(); } diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 5f2c7345d4..d00946be83 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -3,6 +3,7 @@ const url = require('url') const {EventEmitter} = require('events') const util = require('util') +const Readable = require('stream').Readable const binding = process.atomBinding('net') const {net, Net} = binding const {URLRequest} = net @@ -14,10 +15,20 @@ let kSupportedProtocols = new Set() kSupportedProtocols.add('http:') kSupportedProtocols.add('https:') -class IncomingMessage extends EventEmitter { +class IncomingMessage extends Readable { constructor (urlRequest) { super() this._url_request = urlRequest + this._shouldPush = false; + this._data = []; + this._url_request.on('data', (event, chunk) => { + this._storeInternalData(chunk) + this._pushInternalData() + }) + this._url_request.on('end', () => { + this._storeInternalData(null) + this._pushInternalData() + }) } get statusCode () { @@ -48,6 +59,22 @@ class IncomingMessage extends EventEmitter { return this._url_request.rawResponseHeaders } + _storeInternalData(chunk) { + this._data.push(chunk) + } + + _pushInternalData() { + while (this._shouldPush && this._data.length > 0) { + const chunk = this._data.shift() + this._shouldPush = this.push(chunk) + } + } + + _read() { + this._shouldPush = true + this._pushInternalData() + } + } URLRequest.prototype._emitRequestEvent = function (async, ...rest) { From b57ffbf1ab2ba9e29c9a2cb9a46af71f4df3ca1e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 6 Oct 2016 14:32:20 +0200 Subject: [PATCH 20/62] Temporary fix for the post data test. --- spec/api-net-spec.js | 53 ++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 442e045ac9..2cf92327da 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -1,9 +1,11 @@ const assert = require('assert') -const {net} = require('electron'); +const {remote} = require('electron') +const {net} = remote -describe('net module', function() { +describe.only('net module', function() { describe('HTTP basics', function() { it ('should be able to fetch google.com', function(done) { + this.timeout(30000); let response_event_emitted = false; let data_event_emitted = false; let end_event_emitted = false; @@ -19,8 +21,7 @@ describe('net module', function() { assert.equal(statusCode, 200) const statusMessage = response.statusMessage const rawHeaders = response.rawHeaders - assert(typeof rawHeaders === 'string') - assert(rawHeaders.length > 0) + assert(typeof rawHeaders === 'object') const httpVersion = response.httpVersion; assert(typeof httpVersion === 'string') assert(httpVersion.length > 0) @@ -28,7 +29,7 @@ describe('net module', function() { assert(typeof httpVersionMajor === 'number') assert(httpVersionMajor >= 1) const httpVersionMinor = response.httpVersionMinor; - assert(typeof rawHeaders === 'number') + assert(typeof httpVersionMinor === 'number') assert(httpVersionMinor >= 0) let body = ''; response.on('data', function(buffer) { @@ -48,7 +49,7 @@ describe('net module', function() { assert.ifError(error); }) urlRequest.on('close', function() { - asset(response_event_emitted) + assert(response_event_emitted) assert(data_event_emitted) assert(end_event_emitted) assert(finish_event_emitted) @@ -58,11 +59,12 @@ describe('net module', function() { }) it ('should be able to post data', function(done) { + this.timeout(20000); let response_event_emitted = false; let data_event_emitted = false; let end_event_emitted = false; let finish_event_emitted = false; - let urlRequest = net.request({ + const urlRequest = net.request({ method: 'POST', url: 'http://httpbin.org/post' }); @@ -73,8 +75,7 @@ describe('net module', function() { assert.equal(statusCode, 200) const statusMessage = response.statusMessage const rawHeaders = response.rawHeaders - assert(typeof rawHeaders === 'string') - assert(rawHeaders.length > 0) + assert(typeof rawHeaders === 'object') const httpVersion = response.httpVersion; assert(typeof httpVersion === 'string') assert(httpVersion.length > 0) @@ -82,18 +83,24 @@ describe('net module', function() { assert(typeof httpVersionMajor === 'number') assert(httpVersionMajor >= 1) const httpVersionMinor = response.httpVersionMinor; - assert(typeof rawHeaders === 'number') + assert(typeof httpVersionMinor === 'number') assert(httpVersionMinor >= 0) let body = ''; + response.on('end', function() { + end_event_emitted = true; + assert(response_event_emitted) + assert(data_event_emitted) + assert(end_event_emitted) + assert(finish_event_emitted) + done() + }) response.on('data', function(buffer) { data_event_emitted = true; body += buffer.toString() assert(typeof body === 'string') assert(body.length > 0) }); - response.on('end', function() { - end_event_emitted = true; - }) + }); urlRequest.on('finish', function() { finish_event_emitted = true; @@ -102,11 +109,7 @@ describe('net module', function() { assert.ifError(error); }) urlRequest.on('close', function() { - asset(response_event_emitted) - assert(data_event_emitted) - assert(end_event_emitted) - assert(finish_event_emitted) - done() + }) for (let i = 0; i < 100; ++i) { urlRequest.write('Hello World!'); @@ -122,16 +125,22 @@ describe('net module', function() { assert(false) }) it ('should be able to pipe into a request', function() { - + assert(false) + }) + it ('should be able to pipe from a response', 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) }) it ('should support chunked encoding', function() { - + assert(false) + }) + it ('should not emit any event after close', function() { + assert(false) }) - }) }) \ No newline at end of file From 046f48db51e4631db2d1798ff20eac6a17d4259f Mon Sep 17 00:00:00 2001 From: Ali Ibrahim Date: Thu, 6 Oct 2016 17:28:21 +0200 Subject: [PATCH 21/62] Fixing build on Linux --- atom/browser/api/atom_api_url_request.h | 1 + 1 file changed, 1 insertion(+) diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 672a6693ee..c10b542856 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -10,6 +10,7 @@ #include "atom/browser/api/event_emitter.h" #include "atom/browser/api/trackable_object.h" #include "base/memory/weak_ptr.h" +#include "native_mate/dictionary.h" #include "native_mate/handle.h" #include "native_mate/wrappable_base.h" #include "net/base/auth.h" From 9b94dfcbdc4af4b6b69e1133ad01919d0a7c9e5d Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 7 Oct 2016 11:07:49 +0200 Subject: [PATCH 22/62] Adding basic http tests, fixing issues in ClientRequest constructor. --- lib/browser/api/net.js | 23 +++-- spec/api-net-spec.js | 221 ++++++++++++++++++++++++++++++++++------- 2 files changed, 202 insertions(+), 42 deletions(-) diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index d00946be83..9c9c57c202 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -59,6 +59,14 @@ class IncomingMessage extends Readable { return this._url_request.rawResponseHeaders } + get rawTrailers() { + throw (new Error('HTTP trailers are not supported.')) + } + + get trailers() { + throw (new Error('HTTP trailers are not supported.')) + } + _storeInternalData(chunk) { this._data.push(chunk) } @@ -113,7 +121,7 @@ class ClientRequest extends EventEmitter { if (!urlStr) { let urlObj = {} - const protocol = options.protocol || 'http' + const protocol = options.protocol || 'http:' if (!kSupportedProtocols.has(protocol)) { throw new Error('Protocol "' + protocol + '" not supported. ') } @@ -133,7 +141,6 @@ class ClientRequest extends EventEmitter { } } - const path = options.path || '/' 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 @@ -143,7 +150,9 @@ class ClientRequest extends EventEmitter { // an invalid request. throw new TypeError('Request path contains unescaped characters.') } - urlObj.path = path + urlObj.pathname = options.pathname || '/' + urlObj.search = options.search + urlObj.hash = options.hash urlStr = url.format(urlObj) } @@ -252,10 +261,6 @@ class ClientRequest extends EventEmitter { chunk = Buffer.from(chunk, encoding) } - // Headers are assumed to be sent on first call to _writeBuffer, - // i.e. after the first call to write or end. - let result = this._url_request.write(chunk, isLast) - // Since writing to the network is asynchronous, we conservatively // assume that request headers are written after delivering the first // buffer to the network IO thread. @@ -263,6 +268,10 @@ class ClientRequest extends EventEmitter { this._url_request.setChunkedUpload(this.chunkedEncoding) } + // Headers are assumed to be sent on first call to _writeBuffer, + // i.e. after the first call to write or end. + let result = this._url_request.write(chunk, isLast) + // The write callback is fired asynchronously to mimic Node.js. if (callback) { process.nextTick(callback) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 2cf92327da..20bced19be 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -1,19 +1,166 @@ const assert = require('assert') const {remote} = require('electron') +const http = require('http') +const url = require('url') const {net} = remote describe.only('net module', function() { + this.timeout(0) describe('HTTP basics', function() { - it ('should be able to fetch google.com', function(done) { + + let server + beforeEach(function (done) { + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) + }) + + afterEach(function () { + server.close(function() { + }) + server = null + }) + + it('should be able to issue a basic GET request', function(done) { + const request_url = '/request_url' + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + assert.equal(request.method, 'GET') + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request(`${server.url}${request_url}`) + urlRequest.on('response', function(response) { + assert.equal(response.statusCode, 200) + response.on('end', function() { + done() + }) + response.on('data', function(chunk) { + + }) + }) + urlRequest.end(); + }) + + it('should be able to issue a basic POST request', function(done) { + const request_url = '/request_url' + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + assert.equal(request.method, 'POST') + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request({ + method: 'POST', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + assert.equal(response.statusCode, 200) + response.on('end', function() { + done() + }) + response.on('data', function(chunk) { + + }) + }) + urlRequest.end(); + }) + + it('should fetch correct data in a GET request', function(done) { + const request_url = '/request_url' + const body_data = "Hello World!" + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + assert.equal(request.method, 'GET') + response.write(body_data) + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request(`${server.url}${request_url}`) + urlRequest.on('response', function(response) { + let expected_body_data = ''; + assert.equal(response.statusCode, 200) + response.on('end', function() { + assert.equal(expected_body_data, body_data) + done() + }) + response.on('data', function(chunk) { + expected_body_data += chunk.toString(); + }) + }) + urlRequest.end(); + }) + + it('should post the correct data in a POST request', function(done) { + const request_url = '/request_url' + const body_data = "Hello World!" + server.on('request', function(request, response) { + let posted_body_data = '' + switch (request.url) { + case request_url: + assert.equal(request.method, 'POST') + request.on('data', function(chunk) { + posted_body_data += chunk.toString() + }) + request.on('end', function() { + assert.equal(posted_body_data, body_data) + response.end(); + }) + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request({ + method: 'POST', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + assert.equal(response.statusCode, 200) + response.on('end', function() { + done() + }) + response.on('data', function(chunk) { + }) + }) + urlRequest.write(body_data) + urlRequest.end(); + }) + + }) + describe('ClientRequest API', function() { + it ('should emit ClientRequest events in a GET request', function(done) { this.timeout(30000); let response_event_emitted = false; let data_event_emitted = false; let end_event_emitted = false; let finish_event_emitted = false; const urlRequest = net.request({ - method: 'GET', - url: 'https://www.google.com' - }) + method: 'GET', + url: 'https://www.google.com' + }) urlRequest.on('response', function(response) { response_event_emitted = true; const statusCode = response.statusCode @@ -37,37 +184,37 @@ describe.only('net module', function() { body += buffer.toString() assert(typeof body === 'string') assert(body.length > 0) - }); + }); response.on('end', function() { end_event_emitted = true; - }) - }); + }) + }); urlRequest.on('finish', function() { finish_event_emitted = true; - }) + }) urlRequest.on('error', function(error) { assert.ifError(error); - }) + }) urlRequest.on('close', function() { assert(response_event_emitted) assert(data_event_emitted) assert(end_event_emitted) assert(finish_event_emitted) done() - }) + }) urlRequest.end(); - }) + }) - it ('should be able to post data', function(done) { + it ('should emit ClientRequest events in a POST request', function(done) { this.timeout(20000); let response_event_emitted = false; let data_event_emitted = false; let end_event_emitted = false; let finish_event_emitted = false; const urlRequest = net.request({ - method: 'POST', - url: 'http://httpbin.org/post' - }); + method: 'POST', + url: 'http://httpbin.org/post' + }); urlRequest.on('response', function(response) { response_event_emitted = true; const statusCode = response.statusCode @@ -93,54 +240,58 @@ describe.only('net module', function() { assert(end_event_emitted) assert(finish_event_emitted) done() - }) + }) response.on('data', function(buffer) { data_event_emitted = true; body += buffer.toString() assert(typeof body === 'string') assert(body.length > 0) - }); + }); - }); + }); urlRequest.on('finish', function() { finish_event_emitted = true; - }) + }) urlRequest.on('error', function(error) { assert.ifError(error); - }) + }) urlRequest.on('close', function() { - }) + }) for (let i = 0; i < 100; ++i) { urlRequest.write('Hello World!'); - } + } urlRequest.end(); - }) }) - describe('ClientRequest API', function() { + it ('should be able to set a custom HTTP header', function() { assert(false) - }) + }) it ('should be able to abort an HTTP request', function() { assert(false) - }) + }) it ('should be able to pipe into a request', function() { assert(false) - }) - it ('should be able to pipe from a response', 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) - }) + }) it ('should support chunked encoding', function() { assert(false) - }) + }) + }) + describe('IncomingMessage API', function() { + it('should provide a Node.js-similar API', function() { + assert(false) + }) it ('should not emit any event after close', function() { assert(false) - }) }) -}) \ No newline at end of file + it ('should be able to pipe from a response', function() { + assert(false) + }) + }) + }) \ No newline at end of file From 4eb9fc1bb6581616b6192fe526418b6bcb10b358 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 7 Oct 2016 16:34:36 +0200 Subject: [PATCH 23/62] Adding chunked uploading test. --- lib/browser/api/net.js | 2 +- spec/api-net-spec.js | 366 +++++++++++++++++++++++++++-------------- 2 files changed, 248 insertions(+), 120 deletions(-) diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 9c9c57c202..6761abed19 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -264,7 +264,7 @@ class ClientRequest extends EventEmitter { // Since writing to the network is asynchronous, we conservatively // assume that request headers are written after delivering the first // buffer to the network IO thread. - if (!this._url_request.notStarted) { + if (this._url_request.notStarted) { this._url_request.setChunkedUpload(this.chunkedEncoding) } diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 20bced19be..75bcb0f42f 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -4,7 +4,27 @@ const http = require('http') const url = require('url') const {net} = remote -describe.only('net module', function() { +function randomBuffer(size, start, end) { + start = start || 0 + end = end || 255 + let 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) { + let buffer = randomBuffer(length, '0'.charCodeAt(0), 'z'.charCodeAt(0)) + return buffer.toString(); +} + +const kOneKiloByte = 1024 +const kOneMegaByte = kOneKiloByte * kOneKiloByte + + +describe('net module', function() { this.timeout(0) describe('HTTP basics', function() { @@ -40,12 +60,13 @@ describe.only('net module', function() { 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() { done() }) - response.on('data', function(chunk) { - - }) + response.resume() }) urlRequest.end(); }) @@ -70,12 +91,13 @@ describe.only('net module', function() { }) urlRequest.on('response', function(response) { assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }) response.on('end', function() { done() }) - response.on('data', function(chunk) { - - }) + response.resume() }) urlRequest.end(); }) @@ -100,13 +122,15 @@ describe.only('net module', function() { urlRequest.on('response', function(response) { let expected_body_data = ''; assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function(chunk) { + expected_body_data += chunk.toString(); + }) response.on('end', function() { assert.equal(expected_body_data, body_data) done() }) - response.on('data', function(chunk) { - expected_body_data += chunk.toString(); - }) + response.resume() }) urlRequest.end(); }) @@ -139,130 +163,237 @@ describe.only('net module', function() { }) urlRequest.on('response', function(response) { assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }) response.on('end', function() { done() }) - response.on('data', function(chunk) { - }) + response.resume() }) urlRequest.write(body_data) urlRequest.end(); }) - }) - describe('ClientRequest API', function() { - it ('should emit ClientRequest events in a GET request', function(done) { - this.timeout(30000); - let response_event_emitted = false; - let data_event_emitted = false; - let end_event_emitted = false; - let finish_event_emitted = false; - const urlRequest = net.request({ - method: 'GET', - url: 'https://www.google.com' - }) - urlRequest.on('response', function(response) { - response_event_emitted = true; - const statusCode = response.statusCode - assert(typeof statusCode === 'number') - assert.equal(statusCode, 200) - const statusMessage = response.statusMessage - const rawHeaders = response.rawHeaders - assert(typeof rawHeaders === 'object') - const httpVersion = response.httpVersion; - assert(typeof httpVersion === 'string') - assert(httpVersion.length > 0) - const httpVersionMajor = response.httpVersionMajor; - assert(typeof httpVersionMajor === 'number') - assert(httpVersionMajor >= 1) - const httpVersionMinor = response.httpVersionMinor; - assert(typeof httpVersionMinor === 'number') - assert(httpVersionMinor >= 0) - let body = ''; - response.on('data', function(buffer) { - data_event_emitted = true; - body += buffer.toString() - assert(typeof body === 'string') - assert(body.length > 0) - }); - response.on('end', function() { - end_event_emitted = true; - }) - }); - urlRequest.on('finish', function() { - finish_event_emitted = true; - }) - urlRequest.on('error', function(error) { - assert.ifError(error); - }) - urlRequest.on('close', function() { - assert(response_event_emitted) - assert(data_event_emitted) - assert(end_event_emitted) - assert(finish_event_emitted) - done() - }) - urlRequest.end(); - }) + it.only('should support chunked encoding', function(done) { + const request_url = '/request_url' + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + response.statusCode = 200 + response.statusMessage = 'OK' + response.chunkedEncoding = true + assert.equal(request.method, 'POST') + assert.equal(request.headers['transfer-encoding'], 'chunked') + assert(!request.headers['content-length']) + request.on('data', function(chunk) { + response.write(chunk) + }) + request.on('end', function(chunk) { + response.end(chunk); + }) + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request({ + method: 'POST', + url: `${server.url}${request_url}` + }) - it ('should emit ClientRequest events in a POST request', function(done) { - this.timeout(20000); - let response_event_emitted = false; - let data_event_emitted = false; - let end_event_emitted = false; - let finish_event_emitted = false; - const urlRequest = net.request({ - method: 'POST', - url: 'http://httpbin.org/post' - }); + let chunk_index = 0 + let chunk_count = 100 + let sent_chunks = []; + let received_chunks = []; urlRequest.on('response', function(response) { - response_event_emitted = true; - const statusCode = response.statusCode - assert(typeof statusCode === 'number') - assert.equal(statusCode, 200) - const statusMessage = response.statusMessage - const rawHeaders = response.rawHeaders - assert(typeof rawHeaders === 'object') - const httpVersion = response.httpVersion; - assert(typeof httpVersion === 'string') - assert(httpVersion.length > 0) - const httpVersionMajor = response.httpVersionMajor; - assert(typeof httpVersionMajor === 'number') - assert(httpVersionMajor >= 1) - const httpVersionMinor = response.httpVersionMinor; - assert(typeof httpVersionMinor === 'number') - assert(httpVersionMinor >= 0) - let body = ''; + assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function(chunk) { + received_chunks.push(chunk) + }) response.on('end', function() { - end_event_emitted = true; - assert(response_event_emitted) - assert(data_event_emitted) - assert(end_event_emitted) - assert(finish_event_emitted) + let sent_data = Buffer.concat(sent_chunks) + let received_data = Buffer.concat(received_chunks) + assert.equal(sent_data.toString(), received_data.toString()) + assert.equal(chunk_index, chunk_count) done() + }) + response.resume() + }) + urlRequest.chunkedEncoding = true + while (chunk_index < chunk_count) { + ++chunk_index + let chunk = randomBuffer(kOneKiloByte) + sent_chunks.push(chunk) + assert(urlRequest.write(chunk)) + } + urlRequest.end(); + }) }) - response.on('data', function(buffer) { - data_event_emitted = true; - body += buffer.toString() - assert(typeof body === 'string') - assert(body.length > 0) - }); - }); + describe('ClientRequest API', function() { + + let server + beforeEach(function (done) { + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) + }) + + afterEach(function () { + server.close(function() { + }) + server = null + }) + + it ('response object should implement the IncomingMessage API', function(done) { + const request_url = '/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: + response.statusCode = 200 + response.statusMessage = 'OK' + response.setHeader(custom_header_name, custom_header_value) + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + let response_event_emitted = false; + let data_event_emitted = false; + let end_event_emitted = false; + let finish_event_emitted = false; + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + response_event_emitted = true; + const statusCode = response.statusCode + assert(typeof statusCode === 'number') + assert.equal(statusCode, 200) + const statusMessage = response.statusMessage + assert(typeof statusMessage === 'string') + assert.equal(statusMessage, 'OK') + const rawHeaders = response.rawHeaders + assert(typeof rawHeaders === 'object') + assert(rawHeaders[custom_header_name] === + custom_header_value) + const httpVersion = response.httpVersion; + assert(typeof httpVersion === 'string') + assert(httpVersion.length > 0) + const httpVersionMajor = response.httpVersionMajor; + assert(typeof httpVersionMajor === 'number') + assert(httpVersionMajor >= 1) + const httpVersionMinor = response.httpVersionMinor; + assert(typeof httpVersionMinor === 'number') + assert(httpVersionMinor >= 0) + response.pause() + response.on('data', function(chunk) { + }); + response.on('end', function() { + done() + }) + response.resume() + }) + urlRequest.end(); + }) + + + + it('request/response objects should emit expected events', function(done) { + + const request_url = '/request_url' + let body_data = randomString(kOneMegaByte) + server.on('request', function(request, response) { + switch (request.url) { + case request_url: + response.statusCode = 200 + response.statusMessage = 'OK' + response.write(body_data) + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + + let request_response_event_emitted = false + let request_finish_event_emitted = false + let request_close_event_emitted = false + let response_data_event_emitted = false + let response_end_event_emitted = false + let response_close_event_emitted = false + + function maybeDone(done) { + if (!request_close_event_emitted || !response_end_event_emitted) { + return + } + + assert(request_response_event_emitted) + assert(request_finish_event_emitted) + assert(request_close_event_emitted) + assert(response_data_event_emitted) + assert(response_end_event_emitted) + done() + } + + 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) + let buffers = []; + response.pause(); + response.on('data', function(chunk) { + buffers.push(chunk) + response_data_event_emitted = true + }) + response.on('end', function() { + let received_body_data = Buffer.concat(buffers); + assert(received_body_data.toString() === body_data) + response_end_event_emitted = true + maybeDone(done) + }) + response.resume(); + response.on('error', function(error) { + assert.ifError(error); + }) + response.on('aborted', function() { + assert(false) + }) + }) urlRequest.on('finish', function() { - finish_event_emitted = true; - }) + request_finish_event_emitted = true + }) urlRequest.on('error', function(error) { assert.ifError(error); - }) + }) + urlRequest.on('abort', function() { + assert(false); + }) urlRequest.on('close', function() { - - }) - for (let i = 0; i < 100; ++i) { - urlRequest.write('Hello World!'); - } + request_close_event_emitted = true + maybeDone(done) + }) urlRequest.end(); - }) + }) + it ('should be able to set a custom HTTP header', function() { assert(false) @@ -278,10 +409,7 @@ describe.only('net module', function() { }) it ('should be able to specify a custom session', function() { assert(false) - }) - it ('should support chunked encoding', function() { - assert(false) - }) + }) }) describe('IncomingMessage API', function() { it('should provide a Node.js-similar API', function() { From b731ca50bc49842a741e9c6c51f2d400ec368afd Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 10 Oct 2016 10:11:22 +0200 Subject: [PATCH 24/62] Adding HTTP request headers manipulation tests. --- atom/browser/api/atom_api_url_request.cc | 2 +- spec/api-net-spec.js | 198 +++++++++++++++++++++-- 2 files changed, 187 insertions(+), 13 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index e3d0f59929..bc11ecad52 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -91,7 +91,7 @@ URLRequest::StateBase::StateBase(Flags initialState) template void URLRequest::StateBase::SetFlag(Flags flag) { - state_ = static_cast(static_cast(state_) & + state_ = static_cast(static_cast(state_) | static_cast(flag)); } diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 75bcb0f42f..8cba404a89 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -24,7 +24,7 @@ const kOneKiloByte = 1024 const kOneMegaByte = kOneKiloByte * kOneKiloByte -describe('net module', function() { +describe.only('net module', function() { this.timeout(0) describe('HTTP basics', function() { @@ -175,7 +175,7 @@ describe('net module', function() { urlRequest.end(); }) - it.only('should support chunked encoding', function(done) { + it('should support chunked encoding', function(done) { const request_url = '/request_url' server.on('request', function(request, response) { switch (request.url) { @@ -394,19 +394,193 @@ describe('net module', function() { urlRequest.end(); }) + it('should be able to set a custom HTTP request header before first write', function(done) { + const request_url = '/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.headers[custom_header_name.toLowerCase()], + custom_header_value) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }); + response.on('end', function() { + done() + }) + response.resume() + }) + urlRequest.setHeader(custom_header_name, custom_header_value) + assert.equal(urlRequest.getHeader(custom_header_name), + custom_header_value) + assert.equal(urlRequest.getHeader(custom_header_name.toLowerCase()), + custom_header_value) + urlRequest.write(''); + assert.equal(urlRequest.getHeader(custom_header_name), + custom_header_value) + assert.equal(urlRequest.getHeader(custom_header_name.toLowerCase()), + custom_header_value) + urlRequest.end(); + }) + + it('should not be able to set a custom HTTP request header after first write', function(done) { + const request_url = '/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(!request.headers[custom_header_name.toLowerCase()]) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }); + response.on('end', function() { + done() + }) + response.resume() + }) + urlRequest.write(''); + assert.throws( () => { + urlRequest.setHeader(custom_header_name, custom_header_value) + }) + assert(!urlRequest.getHeader(custom_header_name)) + urlRequest.end(); + }) + + it('should be able to remove a custom HTTP request header before first write', function(done) { + const request_url = '/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(!request.headers[custom_header_name.toLowerCase()]) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }); + response.on('end', function() { + done() + }) + response.resume() + }) + urlRequest.setHeader(custom_header_name, custom_header_value) + assert.equal(urlRequest.getHeader(custom_header_name), + custom_header_value) + urlRequest.removeHeader(custom_header_name) + assert(!urlRequest.getHeader(custom_header_name)) + urlRequest.write(''); + urlRequest.end(); + }) + + it('should not be able to remove a custom HTTP request header after first write', function(done) { + const request_url = '/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.headers[custom_header_name.toLowerCase()], + custom_header_value) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end(); + break; + default: + response.statusCode = 501 + response.statusMessage = 'Not Implemented' + response.end() + } + }) + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('data', function(chunk) { + }); + response.on('end', function() { + done() + }) + response.resume() + }) + urlRequest.setHeader(custom_header_name, custom_header_value) + assert.equal(urlRequest.getHeader(custom_header_name), + custom_header_value) + urlRequest.write(''); + assert.throws(function() { + urlRequest.removeHeader(custom_header_name) + }) + assert.equal(urlRequest.getHeader(custom_header_name), + custom_header_value) + urlRequest.end(); + }) + + - it ('should be able to set a custom HTTP header', function() { - assert(false) - }) it ('should be able to abort an HTTP request', 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) }) @@ -414,12 +588,12 @@ describe('net module', function() { describe('IncomingMessage API', function() { it('should provide a Node.js-similar API', function() { assert(false) - }) + }) it ('should not emit any event after close', function() { assert(false) - }) + }) it ('should be able to pipe from a response', function() { assert(false) + }) }) - }) - }) \ No newline at end of file +}) \ No newline at end of file From a5c508d2d715c4f269d79603f566d3ed0dfc032d Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 10 Oct 2016 16:21:12 +0200 Subject: [PATCH 25/62] 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 6761abed19..36af3b08af 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 8cba404a89..9fe3623d78 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() { From bd5e622bec4befd89f2235456a0f5c879f31a489 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 11 Oct 2016 15:25:26 +0200 Subject: [PATCH 26/62] Adding request/response piping tests. --- spec/api-net-spec.js | 299 ++++++++++++++++++++++++++++++++----------- 1 file changed, 227 insertions(+), 72 deletions(-) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 9fe3623d78..c5bf32505e 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -242,64 +242,6 @@ describe('net module', function() { server = null }) - it ('response object should implement the IncomingMessage API', function(done) { - const request_url = '/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: - response.statusCode = 200 - response.statusMessage = 'OK' - response.setHeader(custom_header_name, custom_header_value) - response.end(); - break; - default: - assert(false) - } - }) - let response_event_emitted = false; - let data_event_emitted = false; - let end_event_emitted = false; - let finish_event_emitted = false; - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${request_url}` - }) - urlRequest.on('response', function(response) { - response_event_emitted = true; - const statusCode = response.statusCode - assert(typeof statusCode === 'number') - assert.equal(statusCode, 200) - const statusMessage = response.statusMessage - assert(typeof statusMessage === 'string') - assert.equal(statusMessage, 'OK') - const rawHeaders = response.rawHeaders - assert(typeof rawHeaders === 'object') - assert(rawHeaders[custom_header_name] === - custom_header_value) - const httpVersion = response.httpVersion; - assert(typeof httpVersion === 'string') - assert(httpVersion.length > 0) - const httpVersionMajor = response.httpVersionMajor; - assert(typeof httpVersionMajor === 'number') - assert(httpVersionMajor >= 1) - const httpVersionMinor = response.httpVersionMinor; - assert(typeof httpVersionMinor === 'number') - assert(httpVersionMinor >= 0) - response.pause() - response.on('data', function(chunk) { - }); - response.on('end', function() { - done() - }) - response.resume() - }) - urlRequest.end(); - }) - - - it('request/response objects should emit expected events', function(done) { const request_url = '/request_url' @@ -756,6 +698,60 @@ describe('net module', function() { urlRequest.end(randomString(kOneKiloByte)) }) + it('abort event should be emitted at most once', 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_finish_event_emitted = false + let request_abort_event_count = 0 + 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_count + urlRequest.abort() + }) + urlRequest.on('close', function() { + request_close_event_emitted = true + // Let all pending async events to be emitted + setTimeout(function() { + assert(request_finish_event_emitted) + assert(request_received_by_server) + assert.equal(request_abort_event_count, 1) + assert(request_close_event_emitted) + done() + }, 500) + }) + + urlRequest.end(randomString(kOneKiloByte)) + function cancelRequest() { + urlRequest.abort() + urlRequest.abort() + } + }) + it('Requests should be intercepted by webRequest module', function(done) { const request_url = '/request_url' @@ -868,7 +864,7 @@ describe('net module', function() { urlRequest.end(); }) - it.only ('should be able to create a request with options', function() { + it('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' @@ -905,29 +901,188 @@ describe('net module', function() { urlRequest.end(); }) - it('abort request should be emitted at most once', function() { - assert(false) - }) + it('should be able to pipe a readable stream into a net request', function(done) { + const node_request_url = '/node_request_url' + const net_request_url = '/net_request_url' + const body_data = randomString(kOneMegaByte) + let net_request_received = false + let net_request_ended = false + server.on('request', function(request, response) { + switch (request.url) { + case node_request_url: + response.write(body_data) + response.end(); + break; + case net_request_url: + net_request_received = true + let received_body_data = '' + request.on('data', function(chunk) { + received_body_data += chunk.toString() + }) + request.on('end', function(chunk) { + net_request_ended = true + if (chunk) { + received_body_data += chunk.toString() + } + assert.equal(received_body_data, body_data) + response.end() + }) + break; + default: + assert(false) + } + }) + + let nodeRequest = http.request(`${server.url}${node_request_url}`); + nodeRequest.on('response', function(nodeResponse) { + const netRequest = net.request(`${server.url}${net_request_url}`) + netRequest.on('response', function(netResponse) { + + assert.equal(netResponse.statusCode, 200) + netResponse.pause() + netResponse.on('data', function(chunk) { + }) + netResponse.on('end', function() { + assert(net_request_received) + assert(net_request_ended) + done() + }) + netResponse.resume() + }) + nodeResponse.pipe(netRequest) + }) + nodeRequest.end() + }) it('headers cannot be manipulated after abort', function() { assert(false) }) - - it ('should be able to pipe into a request', function() { - assert(false) - }) - - }) describe('IncomingMessage API', function() { - it('should provide a Node.js-similar API', function() { - assert(false) + + let server + beforeEach(function (done) { + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) }) + + afterEach(function () { + server.close(function() { + }) + server = null + }) + + it ('response object should implement the IncomingMessage API', function(done) { + const request_url = '/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: + response.statusCode = 200 + response.statusMessage = 'OK' + response.setHeader(custom_header_name, custom_header_value) + response.end(); + break; + default: + assert(false) + } + }) + let response_event_emitted = false; + let data_event_emitted = false; + let end_event_emitted = false; + let finish_event_emitted = false; + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${request_url}` + }) + urlRequest.on('response', function(response) { + response_event_emitted = true; + const statusCode = response.statusCode + assert(typeof statusCode === 'number') + assert.equal(statusCode, 200) + const statusMessage = response.statusMessage + assert(typeof statusMessage === 'string') + assert.equal(statusMessage, 'OK') + const rawHeaders = response.rawHeaders + assert(typeof rawHeaders === 'object') + assert(rawHeaders[custom_header_name] === + custom_header_value) + const httpVersion = response.httpVersion; + assert(typeof httpVersion === 'string') + assert(httpVersion.length > 0) + const httpVersionMajor = response.httpVersionMajor; + assert(typeof httpVersionMajor === 'number') + assert(httpVersionMajor >= 1) + const httpVersionMinor = response.httpVersionMinor; + assert(typeof httpVersionMinor === 'number') + assert(httpVersionMinor >= 0) + response.pause() + response.on('data', function(chunk) { + }); + response.on('end', function() { + done() + }) + response.resume() + }) + urlRequest.end(); + }) + it ('should not emit any event after close', function() { assert(false) }) - it ('should be able to pipe from a response', function() { - assert(false) + + it.only('should be able to net response into a writable stream', function() { + const node_request_url = '/node_request_url' + const net_request_url = '/net_request_url' + const body_data = randomString(kOneMegaByte) + let node_request_received = false + let node_request_ended = false + server.on('request', function(request, response) { + switch (request.url) { + case net_request_url: + response.write(body_data) + response.end(); + break; + case node_request_url: + node_request_received = true + let received_body_data = '' + request.on('data', function(chunk) { + received_body_data += chunk.toString() + }) + request.on('end', function(chunk) { + node_request_ended = true + if (chunk) { + received_body_data += chunk.toString() + } + assert.equal(received_body_data, body_data) + response.end() + }) + break; + default: + assert(false) + } + }) + const netRequest = net.request(`${server.url}${net_request_url}`) + netRequest.on('response', function(netResponse) { + assert.equal(netResponse.statusCode, 200) + let nodeRequest = http.request(`${server.url}${node_request_url}`); + nodeRequest.on('response', function(nodeResponse) { + nodeResponse.on('data', function(chunk) { + + }) + nodeResponse.on('end', function(chunk) { + assert(node_request_received) + assert(node_request_ended) + done() + }) + }) + netResponse.pipe(nodeRequest) + }) + netRequest.end() }) }) }) \ No newline at end of file From d21def7b8db62b1996305de658b0aa1c713a0099 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 11 Oct 2016 19:16:23 +0200 Subject: [PATCH 27/62] Fixing various issues in tests. --- spec/api-net-spec.js | 45 ++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index c5bf32505e..c0fa0f4b95 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -240,6 +240,7 @@ describe('net module', function() { server.close(function() { }) server = null + session.defaultSession.webRequest.onBeforeRequest(null) }) it('request/response objects should emit expected events', function(done) { @@ -491,7 +492,7 @@ describe('net module', function() { urlRequest.end(); }) - it('should be able to abort an HTTP request before first write', function() { + it('should be able to abort an HTTP request before first write', function(done) { const request_url = '/request_url' server.on('request', function(request, response) { assert(false) @@ -864,7 +865,7 @@ describe('net module', function() { urlRequest.end(); }) - it('should be able to create a request with options', function() { + it('should be able to create a request with options', function(done) { const request_url = '/' const custom_header_name = 'Some-Custom-Header-Name' const custom_header_value = 'Some-Customer-Header-Value' @@ -874,6 +875,8 @@ describe('net module', function() { assert.equal(request.method, 'GET') assert.equal(request.headers[custom_header_name.toLowerCase()], custom_header_value) + response.statusCode = 200 + response.statusMessage = 'OK' response.end(); break; default: @@ -881,9 +884,10 @@ describe('net module', function() { } }) - const server_url = url.parse(server.url) + const serverUrl = url.parse(server.url) let options = { - port: server_url.port, + port: serverUrl.port, + hostname: '127.0.0.1', headers: {} } options.headers[custom_header_name] = custom_header_value @@ -954,13 +958,14 @@ describe('net module', function() { }) nodeRequest.end() }) - it('headers cannot be manipulated after abort', function() { + + it.skip('should emit error event on server socket close', function(done) { assert(false) }) }) describe('IncomingMessage API', function() { - let server + let server beforeEach(function (done) { server = http.createServer() server.listen(0, '127.0.0.1', function () { @@ -970,8 +975,7 @@ describe('net module', function() { }) afterEach(function () { - server.close(function() { - }) + server.close() server = null }) @@ -1031,11 +1035,7 @@ describe('net module', function() { urlRequest.end(); }) - it ('should not emit any event after close', function() { - assert(false) - }) - - it.only('should be able to net response into a writable stream', function() { + it.skip('should be able to pipe a net response into a writable stream', function(done) { const node_request_url = '/node_request_url' const net_request_url = '/net_request_url' const body_data = randomString(kOneMegaByte) @@ -1044,6 +1044,8 @@ describe('net module', function() { server.on('request', function(request, response) { switch (request.url) { case net_request_url: + response.statusCode = 200 + response.statusMessage = 'OK' response.write(body_data) response.end(); break; @@ -1066,13 +1068,20 @@ describe('net module', function() { assert(false) } }) - const netRequest = net.request(`${server.url}${net_request_url}`) + + netRequest = net.request(`${server.url}${net_request_url}`); netRequest.on('response', function(netResponse) { assert.equal(netResponse.statusCode, 200) - let nodeRequest = http.request(`${server.url}${node_request_url}`); + const serverUrl = url.parse(server.url) + const nodeOptions = { + method: 'POST', + path: node_request_url, + port: serverUrl.port + } + let nodeRequest = http.request(nodeOptions); + nodeRequest.on('response', function(nodeResponse) { nodeResponse.on('data', function(chunk) { - }) nodeResponse.on('end', function(chunk) { assert(node_request_received) @@ -1084,5 +1093,9 @@ describe('net module', function() { }) netRequest.end() }) + + it.skip('should not emit any event after close', function() { + assert(false) + }) }) }) \ No newline at end of file From ae1c33b863d0509017a5a7e4e533f34aed62ec45 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 12 Oct 2016 12:29:25 +0200 Subject: [PATCH 28/62] fixing linter issues. --- atom/browser/api/atom_api_url_request.cc | 2 +- lib/browser/api/net.js | 27 +- spec/api-net-spec.js | 1100 +++++++++++----------- 3 files changed, 556 insertions(+), 573 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index bc11ecad52..1bcf44eeb6 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -334,7 +334,7 @@ void URLRequest::OnAuthenticationRequired( if (!atom_request_) { return; } - + EmitRequestEvent( false, "login", diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 36af3b08af..02a3f3a164 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -19,8 +19,8 @@ class IncomingMessage extends Readable { constructor (urlRequest) { super() this._url_request = urlRequest - this._shouldPush = false; - this._data = []; + this._shouldPush = false + this._data = [] this._url_request.on('data', (event, chunk) => { this._storeInternalData(chunk) this._pushInternalData() @@ -59,26 +59,26 @@ class IncomingMessage extends Readable { return this._url_request.rawResponseHeaders } - get rawTrailers() { - throw (new Error('HTTP trailers are not supported.')) + get rawTrailers () { + throw new Error('HTTP trailers are not supported.') } - get trailers() { - throw (new Error('HTTP trailers are not supported.')) + get trailers () { + throw new Error('HTTP trailers are not supported.') } - _storeInternalData(chunk) { + _storeInternalData (chunk) { this._data.push(chunk) } - _pushInternalData() { + _pushInternalData () { while (this._shouldPush && this._data.length > 0) { - const chunk = this._data.shift() - this._shouldPush = this.push(chunk) + const chunk = this._data.shift() + this._shouldPush = this.push(chunk) } } - _read() { + _read () { this._shouldPush = true this._pushInternalData() } @@ -141,7 +141,6 @@ 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 @@ -150,9 +149,9 @@ 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.') - } + } let pathObj = url.parse(options.path || '/') - urlObj.pathname = pathObj.pathname + urlObj.pathname = pathObj.pathname urlObj.search = pathObj.search urlObj.hash = pathObj.hash urlStr = url.format(urlObj) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index c0fa0f4b95..bb7f866c59 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -5,30 +5,28 @@ const url = require('url') const {net} = remote const {session} = remote -function randomBuffer(size, start, end) { +function randomBuffer (size, start, end) { start = start || 0 end = end || 255 let range = 1 + end - start const buffer = Buffer.allocUnsafe(size) for (let i = 0; i < size; ++i) { - buffer[i] = start + Math.floor(Math.random()*range) + buffer[i] = start + Math.floor(Math.random() * range) } - return buffer; + return buffer } -function randomString(length) { +function randomString (length) { let buffer = randomBuffer(length, '0'.charCodeAt(0), 'z'.charCodeAt(0)) - return buffer.toString(); + return buffer.toString() } const kOneKiloByte = 1024 const kOneMegaByte = kOneKiloByte * kOneKiloByte - -describe('net module', function() { +describe('net module', function () { this.timeout(0) - describe('HTTP basics', function() { - + describe('HTTP basics', function () { let server beforeEach(function (done) { server = http.createServer() @@ -39,194 +37,193 @@ describe('net module', function() { }) afterEach(function () { - server.close(function() { + server.close(function () { }) server = null }) - it('should be able to issue a basic GET request', function(done) { - const request_url = '/request_url' - server.on('request', function(request, response) { + it('should be able to issue a basic GET request', function (done) { + const requestUrl = '/requestUrl' + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: assert.equal(request.method, 'GET') - response.end(); - break; + response.end() + break default: assert(false) } }) - const urlRequest = net.request(`${server.url}${request_url}`) - urlRequest.on('response', function(response) { + const urlRequest = net.request(`${server.url}${requestUrl}`) + urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { + response.on('data', function (chunk) { }) - response.on('end', function() { + response.on('end', function () { done() }) response.resume() }) - urlRequest.end(); + urlRequest.end() }) - it('should be able to issue a basic POST request', function(done) { - const request_url = '/request_url' - server.on('request', function(request, response) { + it('should be able to issue a basic POST request', function (done) { + const requestUrl = '/requestUrl' + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: assert.equal(request.method, 'POST') - response.end(); - break; + response.end() + break default: assert(false) } }) const urlRequest = net.request({ method: 'POST', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { + response.on('data', function (chunk) { }) - response.on('end', function() { + response.on('end', function () { done() }) response.resume() }) - urlRequest.end(); + urlRequest.end() }) - it('should fetch correct data in a GET request', function(done) { - const request_url = '/request_url' - const body_data = "Hello World!" - server.on('request', function(request, response) { + it('should fetch correct data in a GET request', function (done) { + const requestUrl = '/requestUrl' + const bodyData = 'Hello World!' + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: assert.equal(request.method, 'GET') - response.write(body_data) - response.end(); - break; + response.write(bodyData) + response.end() + break default: assert(false) } }) - const urlRequest = net.request(`${server.url}${request_url}`) - urlRequest.on('response', function(response) { - let expected_body_data = ''; + const urlRequest = net.request(`${server.url}${requestUrl}`) + urlRequest.on('response', function (response) { + let expectedBodyData = '' assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { - expected_body_data += chunk.toString(); + response.on('data', function (chunk) { + expectedBodyData += chunk.toString() }) - response.on('end', function() { - assert.equal(expected_body_data, body_data) + response.on('end', function () { + assert.equal(expectedBodyData, bodyData) done() }) response.resume() }) - urlRequest.end(); + urlRequest.end() }) - it('should post the correct data in a POST request', function(done) { - const request_url = '/request_url' - const body_data = "Hello World!" - server.on('request', function(request, response) { - let posted_body_data = '' + it('should post the correct data in a POST request', function (done) { + const requestUrl = '/requestUrl' + const bodyData = 'Hello World!' + server.on('request', function (request, response) { + let postedBodyData = '' switch (request.url) { - case request_url: + case requestUrl: assert.equal(request.method, 'POST') - request.on('data', function(chunk) { - posted_body_data += chunk.toString() + request.on('data', function (chunk) { + postedBodyData += chunk.toString() }) - request.on('end', function() { - assert.equal(posted_body_data, body_data) - response.end(); + request.on('end', function () { + assert.equal(postedBodyData, bodyData) + response.end() }) - break; + break default: assert(false) } }) const urlRequest = net.request({ method: 'POST', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { + response.on('data', function (chunk) { }) - response.on('end', function() { + response.on('end', function () { done() }) response.resume() }) - urlRequest.write(body_data) - urlRequest.end(); + urlRequest.write(bodyData) + urlRequest.end() }) - it('should support chunked encoding', function(done) { - const request_url = '/request_url' - server.on('request', function(request, response) { + it('should support chunked encoding', function (done) { + const requestUrl = '/requestUrl' + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: response.statusCode = 200 response.statusMessage = 'OK' response.chunkedEncoding = true assert.equal(request.method, 'POST') assert.equal(request.headers['transfer-encoding'], 'chunked') assert(!request.headers['content-length']) - request.on('data', function(chunk) { + request.on('data', function (chunk) { response.write(chunk) }) - request.on('end', function(chunk) { - response.end(chunk); + request.on('end', function (chunk) { + response.end(chunk) }) - break; + break default: assert(false) } }) const urlRequest = net.request({ method: 'POST', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - let chunk_index = 0 - let chunk_count = 100 - let sent_chunks = []; - let received_chunks = []; - urlRequest.on('response', function(response) { + let chunkIndex = 0 + let chunkCount = 100 + let sentChunks = [] + let receivedChunks = [] + urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { - received_chunks.push(chunk) + response.on('data', function (chunk) { + receivedChunks.push(chunk) }) - response.on('end', function() { - let sent_data = Buffer.concat(sent_chunks) - let received_data = Buffer.concat(received_chunks) - assert.equal(sent_data.toString(), received_data.toString()) - assert.equal(chunk_index, chunk_count) + response.on('end', function () { + let sentData = Buffer.concat(sentChunks) + let receivedData = Buffer.concat(receivedChunks) + assert.equal(sentData.toString(), receivedData.toString()) + assert.equal(chunkIndex, chunkCount) done() }) response.resume() }) urlRequest.chunkedEncoding = true - while (chunk_index < chunk_count) { - ++chunk_index + while (chunkIndex < chunkCount) { + ++chunkIndex let chunk = randomBuffer(kOneKiloByte) - sent_chunks.push(chunk) + sentChunks.push(chunk) assert(urlRequest.write(chunk)) } - urlRequest.end(); + urlRequest.end() }) }) - describe('ClientRequest API', function() { - + describe('ClientRequest API', function () { let server beforeEach(function (done) { server = http.createServer() @@ -237,586 +234,581 @@ describe('net module', function() { }) afterEach(function () { - server.close(function() { + server.close(function () { }) server = null session.defaultSession.webRequest.onBeforeRequest(null) }) - it('request/response objects should emit expected events', function(done) { - - const request_url = '/request_url' - let body_data = randomString(kOneMegaByte) - server.on('request', function(request, response) { + it('request/response objects should emit expected events', function (done) { + const requestUrl = '/requestUrl' + let bodyData = randomString(kOneMegaByte) + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: response.statusCode = 200 response.statusMessage = 'OK' - response.write(body_data) - response.end(); - break; + response.write(bodyData) + response.end() + break default: assert(false) } }) - let request_response_event_emitted = false - let request_finish_event_emitted = false - let request_close_event_emitted = false - let response_data_event_emitted = false - let response_end_event_emitted = false - let response_close_event_emitted = false + let requestResponseEventEmitted = false + let requestFinishEventEmitted = false + let requestCloseEventEmitted = false + let responseDataEventEmitted = false + let responseEndEventEmitted = false - function maybeDone(done) { - if (!request_close_event_emitted || !response_end_event_emitted) { + function maybeDone (done) { + if (!requestCloseEventEmitted || !responseEndEventEmitted) { return } - assert(request_response_event_emitted) - assert(request_finish_event_emitted) - assert(request_close_event_emitted) - assert(response_data_event_emitted) - assert(response_end_event_emitted) + assert(requestResponseEventEmitted) + assert(requestFinishEventEmitted) + assert(requestCloseEventEmitted) + assert(responseDataEventEmitted) + assert(responseEndEventEmitted) done() } - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { - request_response_event_emitted = true; + urlRequest.on('response', function (response) { + requestResponseEventEmitted = true const statusCode = response.statusCode assert.equal(statusCode, 200) - let buffers = []; - response.pause(); - response.on('data', function(chunk) { + let buffers = [] + response.pause() + response.on('data', function (chunk) { buffers.push(chunk) - response_data_event_emitted = true + responseDataEventEmitted = true }) - response.on('end', function() { - let received_body_data = Buffer.concat(buffers); - assert(received_body_data.toString() === body_data) - response_end_event_emitted = true + response.on('end', function () { + let receivedBodyData = Buffer.concat(buffers) + assert(receivedBodyData.toString() === bodyData) + responseEndEventEmitted = true maybeDone(done) }) - response.resume(); - response.on('error', function(error) { - assert.ifError(error); + response.resume() + response.on('error', function (error) { + assert.ifError(error) }) - response.on('aborted', function() { + response.on('aborted', function () { assert(false) }) }) - urlRequest.on('finish', function() { - request_finish_event_emitted = true + urlRequest.on('finish', function () { + requestFinishEventEmitted = true }) - urlRequest.on('error', function(error) { - assert.ifError(error); + urlRequest.on('error', function (error) { + assert.ifError(error) }) - urlRequest.on('abort', function() { - assert(false); + urlRequest.on('abort', function () { + assert(false) }) - urlRequest.on('close', function() { - request_close_event_emitted = true + urlRequest.on('close', function () { + requestCloseEventEmitted = true maybeDone(done) }) - urlRequest.end(); + urlRequest.end() }) - it('should be able to set a custom HTTP request header before first write', function(done) { - const request_url = '/request_url' - const custom_header_name = 'Some-Custom-Header-Name' - const custom_header_value = 'Some-Customer-Header-Value' - server.on('request', function(request, response) { + it('should be able to set a custom HTTP request header before first write', function (done) { + const requestUrl = '/requestUrl' + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + server.on('request', function (request, response) { switch (request.url) { - case request_url: - assert.equal(request.headers[custom_header_name.toLowerCase()], - custom_header_value) + case requestUrl: + assert.equal(request.headers[customHeaderName.toLowerCase()], + customHeaderValue) response.statusCode = 200 response.statusMessage = 'OK' - response.end(); - break; + response.end() + break default: assert(false) } }) - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { const statusCode = response.statusCode assert.equal(statusCode, 200) response.pause() - response.on('data', function(chunk) { - }); - response.on('end', function() { + response.on('data', function (chunk) { + }) + response.on('end', function () { done() }) response.resume() }) - urlRequest.setHeader(custom_header_name, custom_header_value) - assert.equal(urlRequest.getHeader(custom_header_name), - custom_header_value) - assert.equal(urlRequest.getHeader(custom_header_name.toLowerCase()), - custom_header_value) - urlRequest.write(''); - assert.equal(urlRequest.getHeader(custom_header_name), - custom_header_value) - assert.equal(urlRequest.getHeader(custom_header_name.toLowerCase()), - custom_header_value) - urlRequest.end(); + urlRequest.setHeader(customHeaderName, customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName.toLowerCase()), + customHeaderValue) + urlRequest.write('') + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName.toLowerCase()), + customHeaderValue) + urlRequest.end() }) - it('should not be able to set a custom HTTP request header after first write', function(done) { - const request_url = '/request_url' - const custom_header_name = 'Some-Custom-Header-Name' - const custom_header_value = 'Some-Customer-Header-Value' - server.on('request', function(request, response) { + it('should not be able to set a custom HTTP request header after first write', function (done) { + const requestUrl = '/requestUrl' + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + server.on('request', function (request, response) { switch (request.url) { - case request_url: - assert(!request.headers[custom_header_name.toLowerCase()]) + case requestUrl: + assert(!request.headers[customHeaderName.toLowerCase()]) response.statusCode = 200 response.statusMessage = 'OK' - response.end(); - break; + response.end() + break default: assert(false) } }) - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { const statusCode = response.statusCode assert.equal(statusCode, 200) response.pause() - response.on('data', function(chunk) { - }); - response.on('end', function() { + response.on('data', function (chunk) { + }) + response.on('end', function () { done() }) response.resume() }) - urlRequest.write(''); - assert.throws( () => { - urlRequest.setHeader(custom_header_name, custom_header_value) + urlRequest.write('') + assert.throws(() => { + urlRequest.setHeader(customHeaderName, customHeaderValue) }) - assert(!urlRequest.getHeader(custom_header_name)) - urlRequest.end(); + assert(!urlRequest.getHeader(customHeaderName)) + urlRequest.end() }) - it('should be able to remove a custom HTTP request header before first write', function(done) { - const request_url = '/request_url' - const custom_header_name = 'Some-Custom-Header-Name' - const custom_header_value = 'Some-Customer-Header-Value' - server.on('request', function(request, response) { + it('should be able to remove a custom HTTP request header before first write', function (done) { + const requestUrl = '/requestUrl' + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + server.on('request', function (request, response) { switch (request.url) { - case request_url: - assert(!request.headers[custom_header_name.toLowerCase()]) + case requestUrl: + assert(!request.headers[customHeaderName.toLowerCase()]) response.statusCode = 200 response.statusMessage = 'OK' - response.end(); - break; - default: - assert(false) - } - }) - const urlRequest = net.request({ - method: 'GET', - url: `${server.url}${request_url}` - }) - urlRequest.on('response', function(response) { - const statusCode = response.statusCode - assert.equal(statusCode, 200) - response.pause() - response.on('data', function(chunk) { - }); - response.on('end', function() { - done() - }) - response.resume() - }) - urlRequest.setHeader(custom_header_name, custom_header_value) - assert.equal(urlRequest.getHeader(custom_header_name), - custom_header_value) - urlRequest.removeHeader(custom_header_name) - assert(!urlRequest.getHeader(custom_header_name)) - urlRequest.write(''); - urlRequest.end(); - }) - - it('should not be able to remove a custom HTTP request header after first write', function(done) { - const request_url = '/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.headers[custom_header_name.toLowerCase()], - custom_header_value) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end(); - break; + response.end() + break default: assert(false) } }) - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { const statusCode = response.statusCode assert.equal(statusCode, 200) response.pause() - response.on('data', function(chunk) { - }); - response.on('end', function() { + response.on('data', function (chunk) { + }) + response.on('end', function () { done() }) response.resume() }) - urlRequest.setHeader(custom_header_name, custom_header_value) - assert.equal(urlRequest.getHeader(custom_header_name), - custom_header_value) - urlRequest.write(''); - assert.throws(function() { - urlRequest.removeHeader(custom_header_name) - }) - assert.equal(urlRequest.getHeader(custom_header_name), - custom_header_value) - urlRequest.end(); + urlRequest.setHeader(customHeaderName, customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + urlRequest.removeHeader(customHeaderName) + assert(!urlRequest.getHeader(customHeaderName)) + urlRequest.write('') + urlRequest.end() }) - it('should be able to abort an HTTP request before first write', function(done) { - const request_url = '/request_url' - server.on('request', function(request, response) { + it('should not be able to remove a custom HTTP request header after first write', function (done) { + const requestUrl = '/requestUrl' + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + assert.equal(request.headers[customHeaderName.toLowerCase()], + customHeaderValue) + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + break + default: + assert(false) + } + }) + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('data', function (chunk) { + }) + response.on('end', function () { + done() + }) + response.resume() + }) + urlRequest.setHeader(customHeaderName, customHeaderValue) + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + urlRequest.write('') + assert.throws(function () { + urlRequest.removeHeader(customHeaderName) + }) + assert.equal(urlRequest.getHeader(customHeaderName), + customHeaderValue) + urlRequest.end() + }) + + it('should be able to abort an HTTP request before first write', function (done) { + const requestUrl = '/requestUrl' + server.on('request', function (request, response) { assert(false) }) - let request_abort_event_emitted = false - let request_close_event_emitted = false + let requestAbortEventEmitted = false + let requestCloseEventEmitted = false - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert(false) }) - urlRequest.on('finish', function() { - assert(false); + urlRequest.on('finish', function () { + assert(false) }) - urlRequest.on('error', function(error) { - assert(false); + urlRequest.on('error', function () { + assert(false) }) - urlRequest.on('abort', function() { - request_abort_event_emitted = true + urlRequest.on('abort', function () { + requestAbortEventEmitted = true }) - urlRequest.on('close', function() { - request_close_event_emitted = true - assert(request_abort_event_emitted) - assert(request_close_event_emitted) - done(); + urlRequest.on('close', function () { + requestCloseEventEmitted = true + assert(requestAbortEventEmitted) + assert(requestCloseEventEmitted) + done() }) urlRequest.abort() assert(!urlRequest.write('')) - urlRequest.end(); + 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) { + it('it should be able to abort an HTTP request before request end', function (done) { + const requestUrl = '/requestUrl' + let requestReceivedByServer = false + server.on('request', function (request, response) { switch (request.url) { - case request_url: - request_received_by_server = true; - cancelRequest(); - break; + case requestUrl: + requestReceivedByServer = true + cancelRequest() + break default: assert(false) } }) - let request_abort_event_emitted = false - let request_close_event_emitted = false + let requestAbortEventEmitted = false + let requestCloseEventEmitted = false - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert(false) }) - urlRequest.on('finish', function() { + urlRequest.on('finish', function () { assert(false) }) - urlRequest.on('error', function(error) { - assert(false); + urlRequest.on('error', function () { + assert(false) }) - urlRequest.on('abort', function() { - request_abort_event_emitted = true + urlRequest.on('abort', function () { + requestAbortEventEmitted = 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) + urlRequest.on('close', function () { + requestCloseEventEmitted = true + assert(requestReceivedByServer) + assert(requestAbortEventEmitted) + assert(requestCloseEventEmitted) done() }) urlRequest.chunkedEncoding = true urlRequest.write(randomString(kOneKiloByte)) - function cancelRequest() { + 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) { + it('it should be able to abort an HTTP request after request end and before response', function (done) { + const requestUrl = '/requestUrl' + let requestReceivedByServer = false + server.on('request', function (request, response) { switch (request.url) { - case request_url: - request_received_by_server = true; - cancelRequest(); - process.nextTick( () => { + case requestUrl: + requestReceivedByServer = true + cancelRequest() + process.nextTick(() => { response.statusCode = 200 response.statusMessage = 'OK' - response.end(); + response.end() }) - break; + break default: assert(false) } }) - let request_abort_event_emitted = false - let request_finish_event_emitted = false - let request_close_event_emitted = false + let requestAbortEventEmitted = false + let requestFinishEventEmitted = false + let requestCloseEventEmitted = false - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert(false) }) - urlRequest.on('finish', function() { - request_finish_event_emitted = true + urlRequest.on('finish', function () { + requestFinishEventEmitted = true }) - urlRequest.on('error', function(error) { - assert(false); + urlRequest.on('error', function () { + assert(false) }) - urlRequest.on('abort', function() { - request_abort_event_emitted = true + urlRequest.on('abort', function () { + requestAbortEventEmitted = 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) + urlRequest.on('close', function () { + requestCloseEventEmitted = true + assert(requestFinishEventEmitted) + assert(requestReceivedByServer) + assert(requestAbortEventEmitted) + assert(requestCloseEventEmitted) done() }) urlRequest.end(randomString(kOneKiloByte)) - function cancelRequest() { + 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) { + it('it should be able to abort an HTTP request after response start', function (done) { + const requestUrl = '/requestUrl' + let requestReceivedByServer = 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; + case requestUrl: + requestReceivedByServer = 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 + let requestFinishEventEmitted = false + let requestResponseEventEmitted = false + let requestAbortEventEmitted = false + let requestCloseEventEmitted = false + let responseAbortedEventEmitted = false - - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { - request_response_event_emitted = true + urlRequest.on('response', function (response) { + requestResponseEventEmitted = true const statusCode = response.statusCode assert.equal(statusCode, 200) - response.pause(); - response.on('data', function(chunk) { + response.pause() + response.on('data', function (chunk) { }) - response.on('end', function() { + response.on('end', function () { assert(false) }) - response.resume(); - response.on('error', function(error) { + response.resume() + response.on('error', function () { assert(false) }) - response.on('aborted', function() { - response_aborted_event_emitted = true + response.on('aborted', function () { + responseAbortedEventEmitted = true }) urlRequest.abort() }) - urlRequest.on('finish', function() { - request_finish_event_emitted = true + urlRequest.on('finish', function () { + requestFinishEventEmitted = true }) - urlRequest.on('error', function(error) { - assert(false); + urlRequest.on('error', function () { + assert(false) }) - urlRequest.on('abort', function() { - request_abort_event_emitted = true + urlRequest.on('abort', function () { + requestAbortEventEmitted = 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') + urlRequest.on('close', function () { + requestCloseEventEmitted = true + assert(requestFinishEventEmitted, 'request should emit "finish" event') + assert(requestReceivedByServer, 'request should be received by the server') + assert(requestResponseEventEmitted, '"response" event should be emitted') + assert(requestAbortEventEmitted, 'request should emit "abort" event') + assert(responseAbortedEventEmitted, 'response should emit "aborted" event') + assert(requestCloseEventEmitted, 'request should emit "close" event') done() }) urlRequest.end(randomString(kOneKiloByte)) }) - it('abort event should be emitted at most once', function(done) { - const request_url = '/request_url' - let request_received_by_server = false - server.on('request', function(request, response) { + it('abort event should be emitted at most once', function (done) { + const requestUrl = '/requestUrl' + let requestReceivedByServer = false + server.on('request', function (request, response) { switch (request.url) { - case request_url: - request_received_by_server = true; - cancelRequest(); - break; + case requestUrl: + requestReceivedByServer = true + cancelRequest() + break default: assert(false) } }) - let request_finish_event_emitted = false - let request_abort_event_count = 0 - let request_close_event_emitted = false + let requestFinishEventEmitted = false + let requestAbortEventCount = 0 + let requestCloseEventEmitted = false - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert(false) }) - urlRequest.on('finish', function() { - request_finish_event_emitted = true + urlRequest.on('finish', function () { + requestFinishEventEmitted = true }) - urlRequest.on('error', function(error) { - assert(false); + urlRequest.on('error', function () { + assert(false) }) - urlRequest.on('abort', function() { - ++request_abort_event_count + urlRequest.on('abort', function () { + ++requestAbortEventCount urlRequest.abort() }) - urlRequest.on('close', function() { - request_close_event_emitted = true + urlRequest.on('close', function () { + requestCloseEventEmitted = true // Let all pending async events to be emitted - setTimeout(function() { - assert(request_finish_event_emitted) - assert(request_received_by_server) - assert.equal(request_abort_event_count, 1) - assert(request_close_event_emitted) + setTimeout(function () { + assert(requestFinishEventEmitted) + assert(requestReceivedByServer) + assert.equal(requestAbortEventCount, 1) + assert(requestCloseEventEmitted) done() }, 500) }) urlRequest.end(randomString(kOneKiloByte)) - function cancelRequest() { + function cancelRequest () { urlRequest.abort() urlRequest.abort() } }) - 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) { + it('Requests should be intercepted by webRequest module', function (done) { + const requestUrl = '/requestUrl' + const redirectUrl = '/redirectUrl' + let requestIsRedirected = false + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: assert(false) break - case redirect_url: - request_is_redirected = true - response.end(); + case redirectUrl: + requestIsRedirected = true + response.end() break default: assert(false) } }) - let request_is_intercepted = false + let requestIsIntercepted = false session.defaultSession.webRequest.onBeforeRequest( - function(details, callback){ - if (details.url === `${server.url}${request_url}`) { - request_is_intercepted = true + function (details, callback) { + if (details.url === `${server.url}${requestUrl}`) { + requestIsIntercepted = true callback({ - redirectURL: `${server.url}${redirect_url}` + redirectURL: `${server.url}${redirectUrl}` }) } else { - callback( { + callback({ cancel: false }) } - }); + }) - const urlRequest = net.request(`${server.url}${request_url}`) + const urlRequest = net.request(`${server.url}${requestUrl}`) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { + 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') + response.on('end', function () { + assert(requestIsRedirected, 'The server should receive a request to the forward URL') + assert(requestIsIntercepted, 'The request should be intercepted by the webRequest module') done() }) response.resume() }) - urlRequest.end(); + 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) { + it('should to able to create and intercept a request on a custom session', function (done) { + const requestUrl = '/requestUrl' + const redirectUrl = '/redirectUrl' + const customSessionName = 'custom-session' + let requestIsRedirected = false + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: assert(false) break - case redirect_url: - request_is_redirected = true - response.end(); + case redirectUrl: + requestIsRedirected = true + response.end() break default: assert(false) @@ -824,61 +816,61 @@ describe('net module', function() { }) session.defaultSession.webRequest.onBeforeRequest( - function(details, callback) { + function (details, callback) { assert(false, 'Request should not be intercepted by the default session') - }); + }) - let custom_session = session.fromPartition(custom_session_name, { + let customSession = session.fromPartition(customSessionName, { 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 + let requestIsIntercepted = false + customSession.webRequest.onBeforeRequest( + function (details, callback) { + if (details.url === `${server.url}${requestUrl}`) { + requestIsIntercepted = true callback({ - redirectURL: `${server.url}${redirect_url}` + redirectURL: `${server.url}${redirectUrl}` }) } else { - callback( { + callback({ cancel: false }) } - }); + }) const urlRequest = net.request({ - url: `${server.url}${request_url}`, - session: custom_session_name + url: `${server.url}${requestUrl}`, + session: customSessionName }) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { + 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') + response.on('end', function () { + assert(requestIsRedirected, 'The server should receive a request to the forward URL') + assert(requestIsIntercepted, 'The request should be intercepted by the webRequest module') done() }) response.resume() }) - urlRequest.end(); + urlRequest.end() }) - it('should be able to create a request with options', function(done) { - 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) { + it('should be able to create a request with options', function (done) { + const requestUrl = '/' + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: assert.equal(request.method, 'GET') - assert.equal(request.headers[custom_header_name.toLowerCase()], - custom_header_value) + assert.equal(request.headers[customHeaderName.toLowerCase()], + customHeaderValue) response.statusCode = 200 response.statusMessage = 'OK' - response.end(); - break; + response.end() + break default: assert(false) } @@ -890,66 +882,64 @@ describe('net module', function() { hostname: '127.0.0.1', headers: {} } - options.headers[custom_header_name] = custom_header_value + options.headers[customHeaderName] = customHeaderValue const urlRequest = net.request(options) - urlRequest.on('response', function(response) { + urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) response.pause() - response.on('data', function(chunk) { + response.on('data', function (chunk) { }) - response.on('end', function() { + response.on('end', function () { done() }) response.resume() }) - urlRequest.end(); + urlRequest.end() }) - - it('should be able to pipe a readable stream into a net request', function(done) { - const node_request_url = '/node_request_url' - const net_request_url = '/net_request_url' - const body_data = randomString(kOneMegaByte) - let net_request_received = false - let net_request_ended = false - server.on('request', function(request, response) { + it('should be able to pipe a readable stream into a net request', function (done) { + const nodeRequestUrl = '/nodeRequestUrl' + const netRequestUrl = '/netRequestUrl' + const bodyData = randomString(kOneMegaByte) + let netRequestReceived = false + let netRequestEnded = false + server.on('request', function (request, response) { switch (request.url) { - case node_request_url: - response.write(body_data) - response.end(); - break; - case net_request_url: - net_request_received = true - let received_body_data = '' - request.on('data', function(chunk) { - received_body_data += chunk.toString() + case nodeRequestUrl: + response.write(bodyData) + response.end() + break + case netRequestUrl: + netRequestReceived = true + let receivedBodyData = '' + request.on('data', function (chunk) { + receivedBodyData += chunk.toString() }) - request.on('end', function(chunk) { - net_request_ended = true + request.on('end', function (chunk) { + netRequestEnded = true if (chunk) { - received_body_data += chunk.toString() + receivedBodyData += chunk.toString() } - assert.equal(received_body_data, body_data) + assert.equal(receivedBodyData, bodyData) response.end() }) - break; + break default: assert(false) } }) - let nodeRequest = http.request(`${server.url}${node_request_url}`); - nodeRequest.on('response', function(nodeResponse) { - const netRequest = net.request(`${server.url}${net_request_url}`) - netRequest.on('response', function(netResponse) { - + let nodeRequest = http.request(`${server.url}${nodeRequestUrl}`) + nodeRequest.on('response', function (nodeResponse) { + const netRequest = net.request(`${server.url}${netRequestUrl}`) + netRequest.on('response', function (netResponse) { assert.equal(netResponse.statusCode, 200) netResponse.pause() - netResponse.on('data', function(chunk) { + netResponse.on('data', function (chunk) { }) - netResponse.on('end', function() { - assert(net_request_received) - assert(net_request_ended) + netResponse.on('end', function () { + assert(netRequestReceived) + assert(netRequestEnded) done() }) netResponse.resume() @@ -959,12 +949,11 @@ describe('net module', function() { nodeRequest.end() }) - it.skip('should emit error event on server socket close', function(done) { + it.skip('should emit error event on server socket close', function (done) { assert(false) }) }) - describe('IncomingMessage API', function() { - + describe('IncomingMessage API', function () { let server beforeEach(function (done) { server = http.createServer() @@ -979,32 +968,27 @@ describe('net module', function() { server = null }) - it ('response object should implement the IncomingMessage API', function(done) { - const request_url = '/request_url' - const custom_header_name = 'Some-Custom-Header-Name' - const custom_header_value = 'Some-Customer-Header-Value' - server.on('request', function(request, response) { + it('response object should implement the IncomingMessage API', function (done) { + const requestUrl = '/requestUrl' + const customHeaderName = 'Some-Custom-Header-Name' + const customHeaderValue = 'Some-Customer-Header-Value' + server.on('request', function (request, response) { switch (request.url) { - case request_url: + case requestUrl: response.statusCode = 200 response.statusMessage = 'OK' - response.setHeader(custom_header_name, custom_header_value) - response.end(); - break; + response.setHeader(customHeaderName, customHeaderValue) + response.end() + break default: assert(false) } }) - let response_event_emitted = false; - let data_event_emitted = false; - let end_event_emitted = false; - let finish_event_emitted = false; - const urlRequest = net.request({ + const urlRequest = net.request({ method: 'GET', - url: `${server.url}${request_url}` + url: `${server.url}${requestUrl}` }) - urlRequest.on('response', function(response) { - response_event_emitted = true; + urlRequest.on('response', function (response) { const statusCode = response.statusCode assert(typeof statusCode === 'number') assert.equal(statusCode, 200) @@ -1013,79 +997,79 @@ describe('net module', function() { assert.equal(statusMessage, 'OK') const rawHeaders = response.rawHeaders assert(typeof rawHeaders === 'object') - assert(rawHeaders[custom_header_name] === - custom_header_value) - const httpVersion = response.httpVersion; + assert(rawHeaders[customHeaderName] === + customHeaderValue) + const httpVersion = response.httpVersion assert(typeof httpVersion === 'string') assert(httpVersion.length > 0) - const httpVersionMajor = response.httpVersionMajor; + const httpVersionMajor = response.httpVersionMajor assert(typeof httpVersionMajor === 'number') assert(httpVersionMajor >= 1) - const httpVersionMinor = response.httpVersionMinor; + const httpVersionMinor = response.httpVersionMinor assert(typeof httpVersionMinor === 'number') assert(httpVersionMinor >= 0) response.pause() - response.on('data', function(chunk) { - }); - response.on('end', function() { + response.on('data', function (chunk) { + }) + response.on('end', function () { done() }) response.resume() }) - urlRequest.end(); + urlRequest.end() }) - it.skip('should be able to pipe a net response into a writable stream', function(done) { - const node_request_url = '/node_request_url' - const net_request_url = '/net_request_url' - const body_data = randomString(kOneMegaByte) - let node_request_received = false - let node_request_ended = false - server.on('request', function(request, response) { + it.skip('should be able to pipe a net response into a writable stream', function (done) { + const nodeRequestUrl = '/nodeRequestUrl' + const netRequestUrl = '/netRequestUrl' + const bodyData = randomString(kOneMegaByte) + let nodeRequestReceived = false + let nodeRequestEnded = false + server.on('request', function (request, response) { switch (request.url) { - case net_request_url: + case netRequestUrl: response.statusCode = 200 response.statusMessage = 'OK' - response.write(body_data) - response.end(); - break; - case node_request_url: - node_request_received = true - let received_body_data = '' - request.on('data', function(chunk) { - received_body_data += chunk.toString() + response.write(bodyData) + response.end() + break + case nodeRequestUrl: + nodeRequestReceived = true + let receivedBodyData = '' + request.on('data', function (chunk) { + receivedBodyData += chunk.toString() }) - request.on('end', function(chunk) { - node_request_ended = true + request.on('end', function (chunk) { + nodeRequestEnded = true if (chunk) { - received_body_data += chunk.toString() + receivedBodyData += chunk.toString() } - assert.equal(received_body_data, body_data) + assert.equal(receivedBodyData, bodyData) response.end() }) - break; + break default: assert(false) } }) - - netRequest = net.request(`${server.url}${net_request_url}`); - netRequest.on('response', function(netResponse) { + + const netRequest = net.request(`${server.url}${netRequestUrl}`) + netRequest.on('response', function (netResponse) { assert.equal(netResponse.statusCode, 200) const serverUrl = url.parse(server.url) const nodeOptions = { method: 'POST', - path: node_request_url, + path: nodeRequestUrl, port: serverUrl.port } - let nodeRequest = http.request(nodeOptions); + let nodeRequest = http.request(nodeOptions) - nodeRequest.on('response', function(nodeResponse) { - nodeResponse.on('data', function(chunk) { + nodeRequest.on('response', function (nodeResponse) { + nodeResponse.on('data', function (chunk) { }) - nodeResponse.on('end', function(chunk) { - assert(node_request_received) - assert(node_request_ended) + nodeResponse.on('end', function (chunk) { + assert(nodeRequestReceived) + assert(nodeRequestEnded) done() }) }) @@ -1094,8 +1078,8 @@ describe('net module', function() { netRequest.end() }) - it.skip('should not emit any event after close', function() { + it.skip('should not emit any event after close', function () { assert(false) }) }) -}) \ No newline at end of file +}) From 16069cd477a0f36fd6d4ad906e35856d81465478 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 12 Oct 2016 17:13:06 +0200 Subject: [PATCH 29/62] Commenting some test code. --- atom/browser/api/atom_api_net.cc | 2 +- spec/api-net-spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index 5d23f04886..73b4364aa2 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -30,7 +30,7 @@ void Net::BuildPrototype(v8::Isolate* isolate, prototype->SetClassName(mate::StringToV8(isolate, "Net")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .SetProperty("URLRequest", &Net::URLRequest) - .SetMethod("RequestGarbageCollectionForTesting", + .SetMethod("_RequestGarbageCollectionForTesting", &Net::RequestGarbageCollectionForTesting); } diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index bb7f866c59..94788adefa 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -25,7 +25,7 @@ const kOneKiloByte = 1024 const kOneMegaByte = kOneKiloByte * kOneKiloByte describe('net module', function () { - this.timeout(0) + // this.timeout(0) describe('HTTP basics', function () { let server beforeEach(function (done) { From b290415bbd04bf10e188178d130a0247159010ee Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 12 Oct 2016 19:41:13 +0200 Subject: [PATCH 30/62] Fixing build on Mac OS --- atom/browser/api/atom_api_url_request.h | 2 +- atom/browser/net/atom_url_request.cc | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index c10b542856..71e6a81ca2 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -213,7 +213,7 @@ template std::array, sizeof...(ArgTypes)> URLRequest::BuildArgsArray(ArgTypes... args) const { std::array, sizeof...(ArgTypes)> result - = { mate::ConvertToV8(isolate(), args)... }; + = { { mate::ConvertToV8(isolate(), args)... } }; return result; } diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index d795b0b518..c5d5b9b42d 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -51,8 +51,8 @@ class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) : delegate_(delegate), - response_read_buffer_(new net::IOBuffer(kBufferSize)), - is_chunked_upload_(false) { + is_chunked_upload_(false), + response_read_buffer_(new net::IOBuffer(kBufferSize)) { } AtomURLRequest::~AtomURLRequest() { @@ -159,14 +159,12 @@ void AtomURLRequest::DoWriteBuffer( if (buffer) // Non-empty buffer. - auto write_result = chunked_stream_writer_->AppendData( - buffer->data(), + chunked_stream_writer_->AppendData(buffer->data(), buffer->size(), is_last); else if (is_last) // Empty buffer and last chunk, i.e. request.end(). - auto write_result = chunked_stream_writer_->AppendData( - nullptr, + chunked_stream_writer_->AppendData(nullptr, 0, true); From 6f5b0a28c57222987dffe27f8865a4b51c485f88 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 13 Oct 2016 17:14:23 +0200 Subject: [PATCH 31/62] Fixing code review issues: function call formatting, renaming JS member variables, refactoring response headers conversion. --- atom/browser/api/atom_api_net.cc | 2 +- atom/browser/api/atom_api_url_request.cc | 61 +++---- atom/browser/api/atom_api_web_contents.cc | 33 +--- atom/browser/net/atom_url_request.cc | 151 +++++++++++------- atom/browser/net/atom_url_request.h | 6 + .../native_mate_converters/net_converter.cc | 27 ++++ .../native_mate_converters/net_converter.h | 7 + lib/browser/api/net.js | 82 +++++----- spec/api-net-spec.js | 88 +++++++++- 9 files changed, 272 insertions(+), 185 deletions(-) diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index 73b4364aa2..2ce7aa3816 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -41,7 +41,7 @@ v8::Local Net::URLRequest(v8::Isolate* isolate) { void Net::RequestGarbageCollectionForTesting() { isolate()->RequestGarbageCollectionForTesting( - v8::Isolate::GarbageCollectionType::kFullGarbageCollection); + v8::Isolate::GarbageCollectionType::kFullGarbageCollection); } diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 1bcf44eeb6..5c47293411 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -16,38 +16,20 @@ namespace mate { -template<> -struct Converter> { - static v8::Local ToV8( - v8::Isolate* isolate, - scoped_refptr val) { - mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate); - if (val) { - size_t iter = 0; - std::string name; - std::string value; - while (val->EnumerateHeaderLines(&iter, &name, &value)) { - dict.Set(name, value); - } - } - return dict.GetHandle(); - } -}; - template<> struct Converter> { static v8::Local ToV8( - v8::Isolate* isolate, - scoped_refptr buffer) { + v8::Isolate* isolate, + scoped_refptr buffer) { return node::Buffer::Copy(isolate, - buffer->data(), - buffer->size()).ToLocalChecked(); + buffer->data(), + buffer->size()).ToLocalChecked(); } static bool FromV8( - v8::Isolate* isolate, - v8::Local val, - scoped_refptr* out) { + v8::Isolate* isolate, + v8::Local val, + scoped_refptr* out) { auto size = node::Buffer::Length(val); if (size == 0) { @@ -179,11 +161,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { auto browser_context = session->browser_context(); auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr(); - auto atom_url_request = AtomURLRequest::Create( - browser_context, - method, - url, - weak_ptr); + auto atom_url_request = AtomURLRequest::Create(browser_context, method, url, + weak_ptr); api_url_request->atom_request_ = atom_url_request; @@ -228,9 +207,9 @@ bool URLRequest::Write( scoped_refptr buffer, bool is_last) { if (request_state_.Canceled() || - request_state_.Failed() || - request_state_.Finished() || - request_state_.Closed()) { + request_state_.Failed() || + request_state_.Finished() || + request_state_.Closed()) { return false; } @@ -326,7 +305,7 @@ void URLRequest::SetChunkedUpload(bool is_chunked_upload) { void URLRequest::OnAuthenticationRequired( scoped_refptr auth_info) { if (request_state_.Canceled() || - request_state_.Closed()) { + request_state_.Closed()) { return; } @@ -336,10 +315,10 @@ void URLRequest::OnAuthenticationRequired( } EmitRequestEvent( - false, - "login", - auth_info.get(), - base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); + false, + "login", + auth_info.get(), + base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); } void URLRequest::OnResponseStarted( @@ -373,9 +352,9 @@ void URLRequest::OnResponseData( void URLRequest::OnResponseCompleted() { if (request_state_.Canceled() || - request_state_.Closed() || - request_state_.Failed() || - response_state_.Failed()) { + request_state_.Closed() || + request_state_.Failed() || + response_state_.Failed()) { // In case we received an unexpected event from Chromium net, // don't emit any data event after request cancel/error/close. return; diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index 124a0d3358..bd7de5a22a 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -35,10 +35,10 @@ #include "atom/common/native_mate_converters/gfx_converter.h" #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/image_converter.h" +#include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/options_switches.h" -#include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "brightray/browser/inspectable_web_contents.h" #include "brightray/browser/inspectable_web_contents_view.h" @@ -66,7 +66,6 @@ #include "content/public/common/context_menu_params.h" #include "native_mate/dictionary.h" #include "native_mate/object_template_builder.h" -#include "net/http/http_response_headers.h" #include "net/url_request/url_request_context.h" #include "third_party/WebKit/public/web/WebFindOptions.h" #include "third_party/WebKit/public/web/WebInputEvent.h" @@ -141,32 +140,6 @@ struct Converter { } }; -template<> -struct Converter { - static v8::Local ToV8(v8::Isolate* isolate, - net::HttpResponseHeaders* headers) { - base::DictionaryValue response_headers; - if (headers) { - size_t iter = 0; - std::string key; - std::string value; - while (headers->EnumerateHeaderLines(&iter, &key, &value)) { - key = base::ToLowerASCII(key); - if (response_headers.HasKey(key)) { - base::ListValue* values = nullptr; - if (response_headers.GetList(key, &values)) - values->AppendString(value); - } else { - std::unique_ptr values(new base::ListValue()); - values->AppendString(value); - response_headers.Set(key, std::move(values)); - } - } - } - return ConvertToV8(isolate, response_headers); - } -}; - template<> struct Converter { static bool FromV8(v8::Isolate* isolate, v8::Local val, @@ -691,7 +664,7 @@ void WebContents::DidGetResourceResponseStart( details.http_response_code, details.method, details.referrer, - details.headers.get(), + details.headers, ResourceTypeToString(details.resource_type)); } @@ -705,7 +678,7 @@ void WebContents::DidGetRedirectForResourceRequest( details.http_response_code, details.method, details.referrer, - details.headers.get()); + details.headers); } void WebContents::DidFinishNavigation( diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index c5d5b9b42d..7a3a0b1423 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -64,29 +64,42 @@ scoped_refptr AtomURLRequest::Create( const std::string& url, base::WeakPtr delegate) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + DCHECK(browser_context); DCHECK(!url.empty()); + if (!browser_context || url.empty()) { + return nullptr; + } auto request_context_getter = browser_context->url_request_context_getter(); + scoped_refptr atom_url_request(new AtomURLRequest(delegate)); + if (content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind(&AtomURLRequest::DoInitialize, atom_url_request, + request_context_getter, method, url))) { + return atom_url_request; + } + return nullptr; +} +void AtomURLRequest::DoInitialize( + scoped_refptr request_context_getter, + const std::string& method, + const std::string& url) { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK(request_context_getter); auto context = request_context_getter->GetURLRequestContext(); DCHECK(context); + request_ = context->CreateRequest(GURL(url), + net::RequestPriority::DEFAULT_PRIORITY, + this); - scoped_refptr atom_url_request = - new AtomURLRequest(delegate); - - atom_url_request->request_ = context->CreateRequest(GURL(url), - net::RequestPriority::DEFAULT_PRIORITY, - atom_url_request.get()); - - atom_url_request->request_->set_method(method); - return atom_url_request; + request_->set_method(method); } - bool AtomURLRequest::Write( scoped_refptr buffer, bool is_last) { @@ -105,41 +118,45 @@ void AtomURLRequest::SetChunkedUpload(bool is_chunked_upload) { is_chunked_upload_ = is_chunked_upload; } - void AtomURLRequest::Cancel() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask( - content::BrowserThread::IO, - FROM_HERE, - base::Bind(&AtomURLRequest::DoCancel, this)); + content::BrowserThread::IO, + FROM_HERE, + base::Bind(&AtomURLRequest::DoCancel, this)); } void AtomURLRequest::SetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - request_->SetExtraRequestHeaderByName(name, value, true); + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind(&AtomURLRequest::DoSetExtraHeader, this, name, value)); } - void AtomURLRequest::RemoveExtraHeader(const std::string& name) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - request_->RemoveRequestHeaderByName(name); + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind(&AtomURLRequest::DoRemoveExtraHeader, this, name)); } void AtomURLRequest::PassLoginInformation(const base::string16& username, const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - if (username.empty() || password.empty()) + if (username.empty() || password.empty()) { content::BrowserThread::PostTask( - content::BrowserThread::IO, FROM_HERE, - base::Bind(&AtomURLRequest::DoCancelAuth, this)); - else + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoCancelAuth, this)); + } else { content::BrowserThread::PostTask( - content::BrowserThread::IO, FROM_HERE, - base::Bind(&AtomURLRequest::DoSetAuth, this, username, password)); + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoSetAuth, this, username, password)); + } } - void AtomURLRequest::DoWriteBuffer( scoped_refptr buffer, bool is_last) { @@ -151,7 +168,7 @@ void AtomURLRequest::DoWriteBuffer( bool first_call = false; if (!chunked_stream_writer_) { std::unique_ptr chunked_stream( - new net::ChunkedUploadDataStream(0)); + new net::ChunkedUploadDataStream(0)); chunked_stream_writer_ = chunked_stream->CreateWriter(); request_->set_upload(std::move(chunked_stream)); first_call = true; @@ -160,32 +177,32 @@ void AtomURLRequest::DoWriteBuffer( if (buffer) // Non-empty buffer. chunked_stream_writer_->AppendData(buffer->data(), - buffer->size(), - is_last); + buffer->size(), + is_last); else if (is_last) // Empty buffer and last chunk, i.e. request.end(). chunked_stream_writer_->AppendData(nullptr, - 0, - true); + 0, + true); - if (first_call) + if (first_call) { request_->Start(); + } } else { if (buffer) { // Handling potential empty buffers. using internal::UploadOwnedIOBufferElementReader; auto element_reader = UploadOwnedIOBufferElementReader::CreateWithBuffer( - std::move(buffer)); + std::move(buffer)); upload_element_readers_.push_back( - std::unique_ptr(element_reader)); + std::unique_ptr(element_reader)); } if (is_last) { - auto elements_upload_data_stream = new net::ElementsUploadDataStream( - std::move(upload_element_readers_), - 0); + auto elements_upload_data_stream = new net::ElementsUploadDataStream( + std::move(upload_element_readers_), 0); request_->set_upload( - std::unique_ptr(elements_upload_data_stream)); + std::unique_ptr(elements_upload_data_stream)); request_->Start(); } } @@ -196,6 +213,16 @@ void AtomURLRequest::DoCancel() const { request_->Cancel(); } +void AtomURLRequest::DoSetExtraHeader(const std::string& name, + const std::string& value) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + request_->SetExtraRequestHeaderByName(name, value, true); +} +void AtomURLRequest::DoRemoveExtraHeader(const std::string& name) const { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + request_->RemoveRequestHeaderByName(name); +} + void AtomURLRequest::DoSetAuth(const base::string16& username, const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -212,10 +239,10 @@ void AtomURLRequest::OnAuthRequired(net::URLRequest* request, DCHECK_CURRENTLY_ON(content::BrowserThread::IO); content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired, - this, - scoped_refptr(auth_info))); + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired, + this, + scoped_refptr(auth_info))); } void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { @@ -228,10 +255,10 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { if (status.is_success()) { // Success or pending trigger a Read. content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseStarted, - this, - response_headers)); + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseStarted, + this, + response_headers)); ReadResponse(); } else if (status.status() == net::URLRequestStatus::Status::FAILED) { @@ -239,10 +266,10 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { DoCancel(); auto error = net::ErrorToString(status.ToNetError()); content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateRequestErrorOccured, - this, - std::move(error))); + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateRequestErrorOccured, + this, + std::move(error))); } // We don't report an error is the request is canceled. } @@ -288,22 +315,22 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, DoCancel(); auto error = net::ErrorToString(status.ToNetError()); content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, - this, - std::move(error))); + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, + this, + std::move(error))); } else if (data_ended) { content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); } else if (data_transfer_error) { // We abort the request on corrupted data transfer. DoCancel(); content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, - this, - "Failed to transfer data from IO to UI thread.")); + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, + this, + "Failed to transfer data from IO to UI thread.")); } } @@ -316,10 +343,10 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { memcpy(buffer_copy->data(), response_read_buffer_->data(), bytes_read); return content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseData, - this, - buffer_copy)); + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateResponseData, + this, + buffer_copy)); } diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 6a867aac08..c00616d198 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -53,9 +53,15 @@ class AtomURLRequest : public base::RefCountedThreadSafe, explicit AtomURLRequest(base::WeakPtr delegate); ~AtomURLRequest()override; + void DoInitialize(scoped_refptr, + const std::string& method, + const std::string& url); void DoWriteBuffer(scoped_refptr buffer, bool is_last); void DoCancel() const; + void DoSetExtraHeader(const std::string& name, + const std::string& value) const; + void DoRemoveExtraHeader(const std::string& name) const; void DoSetAuth(const base::string16& username, const base::string16& password) const; void DoCancelAuth() const; diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index d74356e956..de6a7bae98 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -10,6 +10,7 @@ #include "atom/common/native_mate_converters/gurl_converter.h" #include "atom/common/native_mate_converters/value_converter.h" #include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" #include "base/values.h" #include "native_mate/dictionary.h" #include "net/base/upload_bytes_element_reader.h" @@ -58,6 +59,32 @@ v8::Local Converter>::ToV8( return dict.GetHandle(); } +// static +v8::Local +Converter>::ToV8( + v8::Isolate* isolate, + scoped_refptr headers) { + base::DictionaryValue response_headers; + if (headers) { + size_t iter = 0; + std::string key; + std::string value; + while (headers->EnumerateHeaderLines(&iter, &key, &value)) { + key = base::ToLowerASCII(key); + if (response_headers.HasKey(key)) { + base::ListValue* values = nullptr; + if (response_headers.GetList(key, &values)) + values->AppendString(value); + } else { + std::unique_ptr values(new base::ListValue()); + values->AppendString(value); + response_headers.Set(key, std::move(values)); + } + } + } + return ConvertToV8(isolate, response_headers); +} + } // namespace mate namespace atom { diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index 37e4280695..de31f870b6 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -17,6 +17,7 @@ namespace net { class AuthChallengeInfo; class URLRequest; class X509Certificate; +class HttpResponseHeaders; } namespace mate { @@ -33,6 +34,12 @@ struct Converter> { const scoped_refptr& val); }; +template<> +struct Converter> { + static v8::Local ToV8(v8::Isolate* isolate, + scoped_refptr headers); +}; + } // namespace mate namespace atom { diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 02a3f3a164..74d6c05063 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -18,29 +18,29 @@ kSupportedProtocols.add('https:') class IncomingMessage extends Readable { constructor (urlRequest) { super() - this._url_request = urlRequest - this._shouldPush = false - this._data = [] - this._url_request.on('data', (event, chunk) => { + this.urlRequest = urlRequest + this.shouldPush = false + this.data = [] + this.urlRequest.on('data', (event, chunk) => { this._storeInternalData(chunk) this._pushInternalData() }) - this._url_request.on('end', () => { + this.urlRequest.on('end', () => { this._storeInternalData(null) this._pushInternalData() }) } get statusCode () { - return this._url_request.statusCode + return this.urlRequest.statusCode } get statusMessage () { - return this._url_request.statusMessage + return this.urlRequest.statusMessage } get headers () { - return this._url_request.rawResponseHeaders + return this.urlRequest.rawResponseHeaders } get httpVersion () { @@ -48,15 +48,11 @@ class IncomingMessage extends Readable { } get httpVersionMajor () { - return this._url_request.httpVersionMajor + return this.urlRequest.httpVersionMajor } get httpVersionMinor () { - return this._url_request.httpVersionMinor - } - - get rawHeaders () { - return this._url_request.rawResponseHeaders + return this.urlRequest.httpVersionMinor } get rawTrailers () { @@ -68,18 +64,18 @@ class IncomingMessage extends Readable { } _storeInternalData (chunk) { - this._data.push(chunk) + this.data.push(chunk) } _pushInternalData () { - while (this._shouldPush && this._data.length > 0) { - const chunk = this._data.shift() - this._shouldPush = this.push(chunk) + while (this.shouldPush && this.data.length > 0) { + const chunk = this.data.shift() + this.shouldPush = this.push(chunk) } } _read () { - this._shouldPush = true + this.shouldPush = true this._pushInternalData() } @@ -88,17 +84,17 @@ class IncomingMessage extends Readable { URLRequest.prototype._emitRequestEvent = function (async, ...rest) { if (async) { process.nextTick(() => { - this._request.emit.apply(this._request, rest) + this.clientRequest.emit.apply(this.clientRequest, rest) }) } else { - this._request.emit.apply(this._request, rest) + this.clientRequest.emit.apply(this.clientRequest, rest) } } URLRequest.prototype._emitResponseEvent = function (async, ...rest) { if (async) { process.nextTick(() => { - this._request.emit.apply(this._response, rest) + this.clientRequest.emit.apply(this._response, rest) }) } else { this._response.emit.apply(this._response, rest) @@ -165,13 +161,13 @@ class ClientRequest extends EventEmitter { }) // Set back and forward links. - this._url_request = urlRequest - urlRequest._request = this + this.urlRequest = urlRequest + urlRequest.clientRequest = 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 = {} + this.extraHeaders = {} if (options.headers) { const keys = Object.keys(options.headers) @@ -183,7 +179,7 @@ class ClientRequest extends EventEmitter { // Set when the request uses chunked encoding. Can be switched // to true only once and never set back to false. - this._chunkedEncoding = false + this.chunkedEncodingEnabled = false urlRequest.on('response', () => { const response = new IncomingMessage(urlRequest) @@ -197,14 +193,14 @@ class ClientRequest extends EventEmitter { } get chunkedEncoding () { - return this._chunkedEncoding + return this.chunkedEncodingEnabled } set chunkedEncoding (value) { - if (!this._url_request.notStarted) { + if (!this.urlRequest.notStarted) { throw new Error('Can\'t set the transfer encoding, headers have been sent.') } - this._chunkedEncoding = value + this.chunkedEncodingEnabled = value } setHeader (name, value) { @@ -214,13 +210,13 @@ class ClientRequest extends EventEmitter { if (value === undefined) { throw new Error('`value` required in setHeader("' + name + '", value).') } - if (!this._url_request.notStarted) { + if (!this.urlRequest.notStarted) { throw new Error('Can\'t set headers after they are sent.') } const key = name.toLowerCase() - this._extra_headers[key] = value - this._url_request.setExtraHeader(name, value) + this.extraHeaders[key] = value + this.urlRequest.setExtraHeader(name, value) } getHeader (name) { @@ -228,12 +224,12 @@ class ClientRequest extends EventEmitter { throw new Error('`name` is required for getHeader(name).') } - if (!this._extra_headers) { + if (!this.extraHeaders) { return } const key = name.toLowerCase() - return this._extra_headers[key] + return this.extraHeaders[key] } removeHeader (name) { @@ -241,13 +237,13 @@ class ClientRequest extends EventEmitter { throw new Error('`name` is required for removeHeader(name).') } - if (!this._url_request.notStarted) { + if (!this.urlRequest.notStarted) { throw new Error('Can\'t remove headers after they are sent.') } const key = name.toLowerCase() - delete this._extra_headers[key] - this._url_request.removeExtraHeader(name) + delete this.extraHeaders[key] + this.urlRequest.removeExtraHeader(name) } _write (chunk, encoding, callback, isLast) { @@ -265,13 +261,13 @@ class ClientRequest extends EventEmitter { // Since writing to the network is asynchronous, we conservatively // assume that request headers are written after delivering the first // buffer to the network IO thread. - if (this._url_request.notStarted) { - this._url_request.setChunkedUpload(this.chunkedEncoding) + if (this.urlRequest.notStarted) { + this.urlRequest.setChunkedUpload(this.chunkedEncoding) } // Headers are assumed to be sent on first call to _writeBuffer, // i.e. after the first call to write or end. - let result = this._url_request.write(chunk, isLast) + let result = this.urlRequest.write(chunk, isLast) // The write callback is fired asynchronously to mimic Node.js. if (callback) { @@ -282,7 +278,7 @@ class ClientRequest extends EventEmitter { } write (data, encoding, callback) { - if (this._url_request.finished) { + if (this.urlRequest.finished) { let error = new Error('Write after end.') process.nextTick(writeAfterEndNT, this, error, callback) return true @@ -292,7 +288,7 @@ class ClientRequest extends EventEmitter { } end (data, encoding, callback) { - if (this._url_request.finished) { + if (this.urlRequest.finished) { return false } @@ -311,7 +307,7 @@ class ClientRequest extends EventEmitter { } abort () { - this._url_request.cancel() + this.urlRequest.cancel() } } diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 94788adefa..e1970494a8 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -949,8 +949,28 @@ describe('net module', function () { nodeRequest.end() }) - it.skip('should emit error event on server socket close', function (done) { - assert(false) + it('should emit error event on server socket close', function (done) { + const requestUrl = '/requestUrl' + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + request.socket.destroy() + break + default: + assert(false) + } + }) + let requestErrorEventEmitted = false + const urlRequest = net.request(`${server.url}${requestUrl}`) + urlRequest.on('error', function (error) { + assert(error) + requestErrorEventEmitted = true + }) + urlRequest.on('close', function () { + assert(requestErrorEventEmitted) + done() + }) + urlRequest.end() }) }) describe('IncomingMessage API', function () { @@ -995,10 +1015,10 @@ describe('net module', function () { const statusMessage = response.statusMessage assert(typeof statusMessage === 'string') assert.equal(statusMessage, 'OK') - const rawHeaders = response.rawHeaders - assert(typeof rawHeaders === 'object') - assert(rawHeaders[customHeaderName] === - customHeaderValue) + const headers = response.headers + assert(typeof headers === 'object') + assert.deepEqual(headers[customHeaderName.toLowerCase()], + [customHeaderValue]) const httpVersion = response.httpVersion assert(typeof httpVersion === 'string') assert(httpVersion.length > 0) @@ -1078,8 +1098,60 @@ describe('net module', function () { netRequest.end() }) - it.skip('should not emit any event after close', function () { - assert(false) + it('should not emit any event after close', function (done) { + const requestUrl = '/requestUrl' + let bodyData = randomString(kOneKiloByte) + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + response.statusCode = 200 + response.statusMessage = 'OK' + response.write(bodyData) + response.end() + break + default: + assert(false) + } + }) + let requestCloseEventEmitted = false + const urlRequest = net.request({ + method: 'GET', + url: `${server.url}${requestUrl}` + }) + urlRequest.on('response', function (response) { + assert(!requestCloseEventEmitted) + const statusCode = response.statusCode + assert.equal(statusCode, 200) + response.pause() + response.on('data', function () { + }) + response.on('end', function () { + }) + response.resume() + response.on('error', function () { + assert(!requestCloseEventEmitted) + }) + response.on('aborted', function () { + assert(!requestCloseEventEmitted) + }) + }) + urlRequest.on('finish', function () { + assert(!requestCloseEventEmitted) + }) + urlRequest.on('error', function () { + assert(!requestCloseEventEmitted) + }) + urlRequest.on('abort', function () { + assert(!requestCloseEventEmitted) + }) + urlRequest.on('close', function () { + requestCloseEventEmitted = true + // Wait so that all async events get scheduled. + setTimeout(function () { + done() + }, 100) + }) + urlRequest.end() }) }) }) From de29f2dde5ed6bfe75a13ee304beaf3cc1b8d763 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 13 Oct 2016 17:51:19 +0200 Subject: [PATCH 32/62] Fixing build: removing constness on net::HttpResponseHeaders as otherwise we would need a change in libchromiumcontent. --- atom/browser/api/atom_api_url_request.cc | 4 ++-- atom/browser/api/atom_api_url_request.h | 6 +++--- atom/browser/net/atom_url_request.cc | 4 ++-- atom/browser/net/atom_url_request.h | 2 +- atom/common/native_mate_converters/net_converter.cc | 4 ++-- atom/common/native_mate_converters/net_converter.h | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 5c47293411..c4df35dda3 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -322,7 +322,7 @@ void URLRequest::OnAuthenticationRequired( } void URLRequest::OnResponseStarted( - scoped_refptr response_headers) { + scoped_refptr response_headers) { if (request_state_.Canceled() || request_state_.Failed() || request_state_.Closed()) { @@ -394,7 +394,7 @@ std::string URLRequest::StatusMessage() const { return result; } -scoped_refptr +scoped_refptr URLRequest::RawResponseHeaders() const { return response_headers_; } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 71e6a81ca2..1dcb901391 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -102,7 +102,7 @@ class URLRequest : public mate::EventEmitter { void OnAuthenticationRequired( scoped_refptr auth_info); void OnResponseStarted( - scoped_refptr response_headers); + scoped_refptr response_headers); void OnResponseData(scoped_refptr data); void OnResponseCompleted(); void OnRequestError(const std::string& error); @@ -178,7 +178,7 @@ class URLRequest : public mate::EventEmitter { bool CanReadHeaders() const; int StatusCode() const; std::string StatusMessage() const; - scoped_refptr RawResponseHeaders() const; + scoped_refptr RawResponseHeaders() const; uint32_t ResponseHttpVersionMajor() const; uint32_t ResponseHttpVersionMinor() const; @@ -202,7 +202,7 @@ class URLRequest : public mate::EventEmitter { // Used to implement pin/unpin. v8::Global wrapper_; - scoped_refptr response_headers_; + scoped_refptr response_headers_; base::WeakPtrFactory weak_ptr_factory_; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 7a3a0b1423..bbe835e926 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -249,7 +249,7 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_EQ(request, request_.get()); - scoped_refptr response_headers = + scoped_refptr response_headers = request->response_headers(); const auto& status = request_->status(); if (status.is_success()) { @@ -358,7 +358,7 @@ void AtomURLRequest::InformDelegateAuthenticationRequired( } void AtomURLRequest::InformDelegateResponseStarted( - scoped_refptr response_headers) const { + scoped_refptr response_headers) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) delegate_->OnResponseStarted(response_headers); diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index c00616d198..b16258e5e8 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -72,7 +72,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void InformDelegateAuthenticationRequired( scoped_refptr auth_info) const; void InformDelegateResponseStarted( - scoped_refptr) const; + scoped_refptr) const; void InformDelegateResponseData( scoped_refptr data) const; void InformDelegateResponseCompleted() const; diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index de6a7bae98..89120c94ab 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -61,9 +61,9 @@ v8::Local Converter>::ToV8( // static v8::Local -Converter>::ToV8( +Converter>::ToV8( v8::Isolate* isolate, - scoped_refptr headers) { + scoped_refptr headers) { base::DictionaryValue response_headers; if (headers) { size_t iter = 0; diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index de31f870b6..4ef9a7f339 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -35,9 +35,9 @@ struct Converter> { }; template<> -struct Converter> { +struct Converter> { static v8::Local ToV8(v8::Isolate* isolate, - scoped_refptr headers); + scoped_refptr headers); }; } // namespace mate From 9cc8bfae1c1ba28fb797145675479eee25bad371 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 14 Oct 2016 10:58:16 +0200 Subject: [PATCH 33/62] fixing space formatting issues. --- atom/browser/api/atom_api_url_request.cc | 6 +-- atom/browser/api/atom_api_url_request.h | 10 ++--- atom/browser/net/atom_url_request.cc | 40 +++++++++---------- atom/browser/net/atom_url_request.h | 25 ++++++------ .../native_mate_converters/net_converter.h | 5 ++- 5 files changed, 43 insertions(+), 43 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index c4df35dda3..4b0ebedb5a 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -256,7 +256,7 @@ void URLRequest::Cancel() { } bool URLRequest::SetExtraHeader(const std::string& name, - const std::string& value) { + const std::string& value) { // Request state must be in the initial non started state. if (!request_state_.NotStarted()) { // Cannot change headers after send. @@ -322,7 +322,7 @@ void URLRequest::OnAuthenticationRequired( } void URLRequest::OnResponseStarted( - scoped_refptr response_headers) { + scoped_refptr response_headers) { if (request_state_.Canceled() || request_state_.Failed() || request_state_.Closed()) { @@ -335,7 +335,7 @@ void URLRequest::OnResponseStarted( } void URLRequest::OnResponseData( - scoped_refptr buffer) { + scoped_refptr buffer) { if (request_state_.Canceled() || request_state_.Closed() || request_state_.Failed() || diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 1dcb901391..1ccbb3610d 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -95,14 +95,14 @@ class URLRequest : public mate::EventEmitter { static mate::WrappableBase* New(mate::Arguments* args); static void BuildPrototype( - v8::Isolate* isolate, - v8::Local prototype); + v8::Isolate* isolate, + v8::Local prototype); // Methods for reporting events into JavaScript. void OnAuthenticationRequired( - scoped_refptr auth_info); + scoped_refptr auth_info); void OnResponseStarted( - scoped_refptr response_headers); + scoped_refptr response_headers); void OnResponseData(scoped_refptr data); void OnResponseCompleted(); void OnRequestError(const std::string& error); @@ -110,7 +110,7 @@ class URLRequest : public mate::EventEmitter { protected: explicit URLRequest(v8::Isolate* isolate, - v8::Local wrapper); + v8::Local wrapper); ~URLRequest() override; private: diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index bbe835e926..2870e6dda5 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -14,7 +14,6 @@ #include "net/base/upload_bytes_element_reader.h" - namespace { const int kBufferSize = 4096; } // namespace @@ -23,21 +22,19 @@ namespace atom { namespace internal { - class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { public: explicit UploadOwnedIOBufferElementReader( - scoped_refptr buffer) - : net::UploadBytesElementReader(buffer->data(), buffer->size()) - , buffer_(buffer) { + scoped_refptr buffer) + : net::UploadBytesElementReader(buffer->data(), buffer->size()), + buffer_(buffer) { } ~UploadOwnedIOBufferElementReader() override { } - static UploadOwnedIOBufferElementReader* CreateWithBuffer( - scoped_refptr buffer) { + scoped_refptr buffer) { return new UploadOwnedIOBufferElementReader(std::move(buffer)); } @@ -127,7 +124,7 @@ void AtomURLRequest::Cancel() const { } void AtomURLRequest::SetExtraHeader(const std::string& name, - const std::string& value) const { + const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask( content::BrowserThread::IO, @@ -143,8 +140,9 @@ void AtomURLRequest::RemoveExtraHeader(const std::string& name) const { base::Bind(&AtomURLRequest::DoRemoveExtraHeader, this, name)); } -void AtomURLRequest::PassLoginInformation(const base::string16& username, - const base::string16& password) const { +void AtomURLRequest::PassLoginInformation( + const base::string16& username, + const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (username.empty() || password.empty()) { content::BrowserThread::PostTask( @@ -158,8 +156,8 @@ void AtomURLRequest::PassLoginInformation(const base::string16& username, } void AtomURLRequest::DoWriteBuffer( - scoped_refptr buffer, - bool is_last) { + scoped_refptr buffer, + bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); if (is_chunked_upload_) { @@ -223,8 +221,9 @@ void AtomURLRequest::DoRemoveExtraHeader(const std::string& name) const { request_->RemoveRequestHeaderByName(name); } -void AtomURLRequest::DoSetAuth(const base::string16& username, - const base::string16& password) const { +void AtomURLRequest::DoSetAuth( + const base::string16& username, + const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); request_->SetAuth(net::AuthCredentials(username, password)); } @@ -234,8 +233,9 @@ void AtomURLRequest::DoCancelAuth() const { request_->CancelAuth(); } -void AtomURLRequest::OnAuthRequired(net::URLRequest* request, - net::AuthChallengeInfo* auth_info) { +void AtomURLRequest::OnAuthRequired( + net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); content::BrowserThread::PostTask( @@ -351,7 +351,7 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { void AtomURLRequest::InformDelegateAuthenticationRequired( - scoped_refptr auth_info) const { + scoped_refptr auth_info) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) delegate_->OnAuthenticationRequired(auth_info); @@ -365,7 +365,7 @@ void AtomURLRequest::InformDelegateResponseStarted( } void AtomURLRequest::InformDelegateResponseData( - scoped_refptr data) const { + scoped_refptr data) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); // Transfer ownership of the data buffer, data will be released @@ -382,7 +382,7 @@ void AtomURLRequest::InformDelegateResponseCompleted() const { } void AtomURLRequest::InformDelegateRequestErrorOccured( - const std::string& error) const { + const std::string& error) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) @@ -390,7 +390,7 @@ void AtomURLRequest::InformDelegateRequestErrorOccured( } void AtomURLRequest::InformDelegateResponseErrorOccured( - const std::string& error) const { + const std::string& error) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index b16258e5e8..8d182a7da5 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -25,27 +25,26 @@ class AtomURLRequest : public base::RefCountedThreadSafe, public net::URLRequest::Delegate { public: static scoped_refptr Create( - AtomBrowserContext* browser_context, - const std::string& method, - const std::string& url, - base::WeakPtr delegate); + AtomBrowserContext* browser_context, + const std::string& method, + const std::string& url, + base::WeakPtr delegate); bool Write(scoped_refptr buffer, - bool is_last); + bool is_last); void SetChunkedUpload(bool is_chunked_upload); void Cancel() const; void SetExtraHeader(const std::string& name, const std::string& value) const; void RemoveExtraHeader(const std::string& name) const; void PassLoginInformation(const base::string16& username, - const base::string16& password) const; + const base::string16& password) const; protected: // Overrides of net::URLRequest::Delegate void OnAuthRequired(net::URLRequest* request, - net::AuthChallengeInfo* auth_info) override; + net::AuthChallengeInfo* auth_info) override; void OnResponseStarted(net::URLRequest* request) override; - void OnReadCompleted(net::URLRequest* request, - int bytes_read) override; + void OnReadCompleted(net::URLRequest* request, int bytes_read) override; private: friend class base::RefCountedThreadSafe; @@ -63,18 +62,18 @@ class AtomURLRequest : public base::RefCountedThreadSafe, const std::string& value) const; void DoRemoveExtraHeader(const std::string& name) const; void DoSetAuth(const base::string16& username, - const base::string16& password) const; + const base::string16& password) const; void DoCancelAuth() const; void ReadResponse(); bool CopyAndPostBuffer(int bytes_read); void InformDelegateAuthenticationRequired( - scoped_refptr auth_info) const; + scoped_refptr auth_info) const; void InformDelegateResponseStarted( - scoped_refptr) const; + scoped_refptr) const; void InformDelegateResponseData( - scoped_refptr data) const; + scoped_refptr data) const; void InformDelegateResponseCompleted() const; void InformDelegateRequestErrorOccured(const std::string& error) const; void InformDelegateResponseErrorOccured(const std::string& error) const; diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index 4ef9a7f339..e3ba6b60cc 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -36,8 +36,9 @@ struct Converter> { template<> struct Converter> { - static v8::Local ToV8(v8::Isolate* isolate, - scoped_refptr headers); + static v8::Local ToV8( + v8::Isolate* isolate, + scoped_refptr headers); }; } // namespace mate From 4347ce4a53fb957c0d388594097309c7444fe50e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 14 Oct 2016 11:50:47 +0200 Subject: [PATCH 34/62] Formatting C++ code using ClangFormat. --- atom/browser/api/atom_api_net.cc | 19 ++-- atom/browser/api/atom_api_net.h | 2 +- atom/browser/api/atom_api_url_request.cc | 119 +++++++++-------------- atom/browser/api/atom_api_url_request.h | 1 - atom/browser/net/atom_url_request.cc | 95 +++++++----------- atom/browser/net/atom_url_request.h | 7 +- 6 files changed, 93 insertions(+), 150 deletions(-) diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index 2ce7aa3816..dac8d9ee14 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -15,9 +15,7 @@ Net::Net(v8::Isolate* isolate) { Init(isolate); } -Net::~Net() { -} - +Net::~Net() {} // static v8::Local Net::Create(v8::Isolate* isolate) { @@ -29,34 +27,33 @@ void Net::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "Net")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) - .SetProperty("URLRequest", &Net::URLRequest) - .SetMethod("_RequestGarbageCollectionForTesting", - &Net::RequestGarbageCollectionForTesting); + .SetProperty("URLRequest", &Net::URLRequest) + .SetMethod("_RequestGarbageCollectionForTesting", + &Net::RequestGarbageCollectionForTesting); } v8::Local Net::URLRequest(v8::Isolate* isolate) { return URLRequest::GetConstructor(isolate)->GetFunction(); } - void Net::RequestGarbageCollectionForTesting() { isolate()->RequestGarbageCollectionForTesting( v8::Isolate::GarbageCollectionType::kFullGarbageCollection); } - } // namespace api } // namespace atom - namespace { using atom::api::Net; using atom::api::URLRequest; -void Initialize(v8::Local exports, v8::Local unused, - v8::Local context, void* priv) { +void Initialize(v8::Local exports, + v8::Local unused, + v8::Local context, + void* priv) { v8::Isolate* isolate = context->GetIsolate(); URLRequest::SetConstructor(isolate, base::Bind(URLRequest::New)); diff --git a/atom/browser/api/atom_api_net.h b/atom/browser/api/atom_api_net.h index 04b882d259..0b86931978 100644 --- a/atom/browser/api/atom_api_net.h +++ b/atom/browser/api/atom_api_net.h @@ -19,6 +19,7 @@ class Net : public mate::EventEmitter { v8::Local prototype); v8::Local URLRequest(v8::Isolate* isolate); + protected: explicit Net(v8::Isolate* isolate); ~Net() override; @@ -32,5 +33,4 @@ class Net : public mate::EventEmitter { } // namespace atom - #endif // ATOM_BROWSER_API_ATOM_API_NET_H_ diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 4b0ebedb5a..393f03ddb4 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -2,9 +2,9 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +#include "atom/browser/api/atom_api_url_request.h" #include #include "atom/browser/api/atom_api_session.h" -#include "atom/browser/api/atom_api_url_request.h" #include "atom/browser/net/atom_url_request.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/net_converter.h" @@ -12,24 +12,20 @@ #include "atom/common/node_includes.h" #include "native_mate/dictionary.h" - - namespace mate { -template<> +template <> struct Converter> { static v8::Local ToV8( v8::Isolate* isolate, scoped_refptr buffer) { - return node::Buffer::Copy(isolate, - buffer->data(), - buffer->size()).ToLocalChecked(); + return node::Buffer::Copy(isolate, buffer->data(), buffer->size()) + .ToLocalChecked(); } - static bool FromV8( - v8::Isolate* isolate, - v8::Local val, - scoped_refptr* out) { + static bool FromV8(v8::Isolate* isolate, + v8::Local val, + scoped_refptr* out) { auto size = node::Buffer::Length(val); if (size == 0) { @@ -65,16 +61,14 @@ struct Converter> { namespace atom { namespace api { - template URLRequest::StateBase::StateBase(Flags initialState) - : state_(initialState) { -} + : state_(initialState) {} template void URLRequest::StateBase::SetFlag(Flags flag) { - state_ = static_cast(static_cast(state_) | - static_cast(flag)); + state_ = + static_cast(static_cast(state_) | static_cast(flag)); } template @@ -88,8 +82,7 @@ bool URLRequest::StateBase::IsFlagSet(Flags flag) const { } URLRequest::RequestState::RequestState() - : StateBase(RequestStateFlags::kNotStarted) { -} + : StateBase(RequestStateFlags::kNotStarted) {} bool URLRequest::RequestState::NotStarted() const { return *this == RequestStateFlags::kNotStarted; @@ -116,8 +109,7 @@ bool URLRequest::RequestState::Closed() const { } URLRequest::ResponseState::ResponseState() - : StateBase(ResponseStateFlags::kNotStarted) { -} + : StateBase(ResponseStateFlags::kNotStarted) {} bool URLRequest::ResponseState::NotStarted() const { return *this == ResponseStateFlags::kNotStarted; @@ -131,7 +123,6 @@ bool URLRequest::ResponseState::Ended() const { return IsFlagSet(ResponseStateFlags::kEnded); } - bool URLRequest::ResponseState::Failed() const { return IsFlagSet(ResponseStateFlags::kFailed); } @@ -141,8 +132,7 @@ URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) InitWith(isolate, wrapper); } -URLRequest::~URLRequest() { -} +URLRequest::~URLRequest() {} // static mate::WrappableBase* URLRequest::New(mate::Arguments* args) { @@ -161,8 +151,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { auto browser_context = session->browser_context(); auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr(); - auto atom_url_request = AtomURLRequest::Create(browser_context, method, url, - weak_ptr); + auto atom_url_request = + AtomURLRequest::Create(browser_context, method, url, weak_ptr); api_url_request->atom_request_ = atom_url_request; @@ -174,21 +164,21 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "URLRequest")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) - // Request API - .MakeDestroyable() - .SetMethod("write", &URLRequest::Write) - .SetMethod("cancel", &URLRequest::Cancel) - .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) - .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) - .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload) - .SetProperty("notStarted", &URLRequest::NotStarted) - .SetProperty("finished", &URLRequest::Finished) - // Response APi - .SetProperty("statusCode", &URLRequest::StatusCode) - .SetProperty("statusMessage", &URLRequest::StatusMessage) - .SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders) - .SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) - .SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); + // Request API + .MakeDestroyable() + .SetMethod("write", &URLRequest::Write) + .SetMethod("cancel", &URLRequest::Cancel) + .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) + .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) + .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload) + .SetProperty("notStarted", &URLRequest::NotStarted) + .SetProperty("finished", &URLRequest::Finished) + // Response APi + .SetProperty("statusCode", &URLRequest::StatusCode) + .SetProperty("statusMessage", &URLRequest::StatusMessage) + .SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders) + .SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor) + .SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor); } bool URLRequest::NotStarted() const { @@ -203,13 +193,10 @@ bool URLRequest::Canceled() const { return request_state_.Canceled(); } -bool URLRequest::Write( - scoped_refptr buffer, - bool is_last) { - if (request_state_.Canceled() || - request_state_.Failed() || - request_state_.Finished() || - request_state_.Closed()) { +bool URLRequest::Write(scoped_refptr buffer, + bool is_last) { + if (request_state_.Canceled() || request_state_.Failed() || + request_state_.Finished() || request_state_.Closed()) { return false; } @@ -231,10 +218,8 @@ bool URLRequest::Write( return false; } - void URLRequest::Cancel() { - if (request_state_.Canceled() || - request_state_.Closed()) { + if (request_state_.Canceled() || request_state_.Closed()) { // Cancel only once. return; } @@ -303,9 +288,8 @@ void URLRequest::SetChunkedUpload(bool is_chunked_upload) { } void URLRequest::OnAuthenticationRequired( - scoped_refptr auth_info) { - if (request_state_.Canceled() || - request_state_.Closed()) { + scoped_refptr auth_info) { + if (request_state_.Canceled() || request_state_.Closed()) { return; } @@ -315,16 +299,13 @@ void URLRequest::OnAuthenticationRequired( } EmitRequestEvent( - false, - "login", - auth_info.get(), + false, "login", auth_info.get(), base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); } void URLRequest::OnResponseStarted( - scoped_refptr response_headers) { - if (request_state_.Canceled() || - request_state_.Failed() || + scoped_refptr response_headers) { + if (request_state_.Canceled() || request_state_.Failed() || request_state_.Closed()) { // Don't emit any event after request cancel. return; @@ -335,11 +316,9 @@ void URLRequest::OnResponseStarted( } void URLRequest::OnResponseData( - scoped_refptr buffer) { - if (request_state_.Canceled() || - request_state_.Closed() || - request_state_.Failed() || - response_state_.Failed()) { + scoped_refptr buffer) { + if (request_state_.Canceled() || request_state_.Closed() || + request_state_.Failed() || response_state_.Failed()) { // In case we received an unexpected event from Chromium net, // don't emit any data event after request cancel/error/close. return; @@ -351,10 +330,8 @@ void URLRequest::OnResponseData( } void URLRequest::OnResponseCompleted() { - if (request_state_.Canceled() || - request_state_.Closed() || - request_state_.Failed() || - response_state_.Failed()) { + if (request_state_.Canceled() || request_state_.Closed() || + request_state_.Failed() || response_state_.Failed()) { // In case we received an unexpected event from Chromium net, // don't emit any data event after request cancel/error/close. return; @@ -378,7 +355,6 @@ void URLRequest::OnResponseError(const std::string& error) { Close(); } - int URLRequest::StatusCode() const { if (response_headers_) { return response_headers_->response_code(); @@ -394,14 +370,13 @@ std::string URLRequest::StatusMessage() const { return result; } -scoped_refptr -URLRequest::RawResponseHeaders() const { +scoped_refptr URLRequest::RawResponseHeaders() const { return response_headers_; } uint32_t URLRequest::ResponseHttpVersionMajor() const { if (response_headers_) { - return response_headers_->GetHttpVersion().major_value(); + return response_headers_->GetHttpVersion().major_value(); } return 0; } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 1ccbb3610d..e7780556bc 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -175,7 +175,6 @@ class URLRequest : public mate::EventEmitter { void RemoveExtraHeader(const std::string& name); void SetChunkedUpload(bool is_chunked_upload); - bool CanReadHeaders() const; int StatusCode() const; std::string StatusMessage() const; scoped_refptr RawResponseHeaders() const; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 2870e6dda5..c0d41b3fb8 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -3,17 +3,16 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +#include "atom/browser/net/atom_url_request.h" #include #include "atom/browser/api/atom_api_url_request.h" #include "atom/browser/atom_browser_context.h" -#include "atom/browser/net/atom_url_request.h" #include "base/callback.h" #include "content/public/browser/browser_thread.h" #include "net/base/elements_upload_data_stream.h" #include "net/base/io_buffer.h" #include "net/base/upload_bytes_element_reader.h" - namespace { const int kBufferSize = 4096; } // namespace @@ -27,11 +26,9 @@ class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { explicit UploadOwnedIOBufferElementReader( scoped_refptr buffer) : net::UploadBytesElementReader(buffer->data(), buffer->size()), - buffer_(buffer) { - } + buffer_(buffer) {} - ~UploadOwnedIOBufferElementReader() override { - } + ~UploadOwnedIOBufferElementReader() override {} static UploadOwnedIOBufferElementReader* CreateWithBuffer( scoped_refptr buffer) { @@ -49,11 +46,9 @@ class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) : delegate_(delegate), is_chunked_upload_(false), - response_read_buffer_(new net::IOBuffer(kBufferSize)) { -} + response_read_buffer_(new net::IOBuffer(kBufferSize)) {} -AtomURLRequest::~AtomURLRequest() { -} +AtomURLRequest::~AtomURLRequest() {} scoped_refptr AtomURLRequest::Create( AtomBrowserContext* browser_context, @@ -71,10 +66,9 @@ scoped_refptr AtomURLRequest::Create( auto request_context_getter = browser_context->url_request_context_getter(); scoped_refptr atom_url_request(new AtomURLRequest(delegate)); if (content::BrowserThread::PostTask( - content::BrowserThread::IO, - FROM_HERE, - base::Bind(&AtomURLRequest::DoInitialize, atom_url_request, - request_context_getter, method, url))) { + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoInitialize, atom_url_request, + request_context_getter, method, url))) { return atom_url_request; } return nullptr; @@ -90,16 +84,14 @@ void AtomURLRequest::DoInitialize( auto context = request_context_getter->GetURLRequestContext(); DCHECK(context); - request_ = context->CreateRequest(GURL(url), - net::RequestPriority::DEFAULT_PRIORITY, - this); + request_ = context->CreateRequest( + GURL(url), net::RequestPriority::DEFAULT_PRIORITY, this); request_->set_method(method); } -bool AtomURLRequest::Write( - scoped_refptr buffer, - bool is_last) { +bool AtomURLRequest::Write(scoped_refptr buffer, + bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); return content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, @@ -117,26 +109,22 @@ void AtomURLRequest::SetChunkedUpload(bool is_chunked_upload) { void AtomURLRequest::Cancel() const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - content::BrowserThread::PostTask( - content::BrowserThread::IO, - FROM_HERE, - base::Bind(&AtomURLRequest::DoCancel, this)); + content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoCancel, this)); } void AtomURLRequest::SetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask( - content::BrowserThread::IO, - FROM_HERE, + content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoSetExtraHeader, this, name, value)); } void AtomURLRequest::RemoveExtraHeader(const std::string& name) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask( - content::BrowserThread::IO, - FROM_HERE, + content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoRemoveExtraHeader, this, name)); } @@ -174,14 +162,11 @@ void AtomURLRequest::DoWriteBuffer( if (buffer) // Non-empty buffer. - chunked_stream_writer_->AppendData(buffer->data(), - buffer->size(), + chunked_stream_writer_->AppendData(buffer->data(), buffer->size(), is_last); else if (is_last) // Empty buffer and last chunk, i.e. request.end(). - chunked_stream_writer_->AppendData(nullptr, - 0, - true); + chunked_stream_writer_->AppendData(nullptr, 0, true); if (first_call) { request_->Start(); @@ -190,8 +175,8 @@ void AtomURLRequest::DoWriteBuffer( if (buffer) { // Handling potential empty buffers. using internal::UploadOwnedIOBufferElementReader; - auto element_reader = UploadOwnedIOBufferElementReader::CreateWithBuffer( - std::move(buffer)); + auto element_reader = + UploadOwnedIOBufferElementReader::CreateWithBuffer(std::move(buffer)); upload_element_readers_.push_back( std::unique_ptr(element_reader)); } @@ -221,9 +206,8 @@ void AtomURLRequest::DoRemoveExtraHeader(const std::string& name) const { request_->RemoveRequestHeaderByName(name); } -void AtomURLRequest::DoSetAuth( - const base::string16& username, - const base::string16& password) const { +void AtomURLRequest::DoSetAuth(const base::string16& username, + const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); request_->SetAuth(net::AuthCredentials(username, password)); } @@ -233,15 +217,13 @@ void AtomURLRequest::DoCancelAuth() const { request_->CancelAuth(); } -void AtomURLRequest::OnAuthRequired( - net::URLRequest* request, - net::AuthChallengeInfo* auth_info) { +void AtomURLRequest::OnAuthRequired(net::URLRequest* request, + net::AuthChallengeInfo* auth_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired, - this, + base::Bind(&AtomURLRequest::InformDelegateAuthenticationRequired, this, scoped_refptr(auth_info))); } @@ -250,14 +232,13 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { DCHECK_EQ(request, request_.get()); scoped_refptr response_headers = - request->response_headers(); + request->response_headers(); const auto& status = request_->status(); if (status.is_success()) { // Success or pending trigger a Read. content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseStarted, - this, + base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this, response_headers)); ReadResponse(); @@ -267,8 +248,7 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { auto error = net::ErrorToString(status.ToNetError()); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateRequestErrorOccured, - this, + base::Bind(&AtomURLRequest::InformDelegateRequestErrorOccured, this, std::move(error))); } // We don't report an error is the request is canceled. @@ -283,9 +263,7 @@ void AtomURLRequest::ReadResponse() { } } - -void AtomURLRequest::OnReadCompleted(net::URLRequest* request, - int bytes_read) { +void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_EQ(request, request_.get()); @@ -308,16 +286,14 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, data_transfer_error = true; break; } - } while (request_->Read(response_read_buffer_.get(), - kBufferSize, - &bytes_read)); + } while ( + request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)); if (response_error) { DoCancel(); auto error = net::ErrorToString(status.ToNetError()); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, - this, + base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, this, std::move(error))); } else if (data_ended) { content::BrowserThread::PostTask( @@ -328,8 +304,7 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, DoCancel(); content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, - this, + base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, this, "Failed to transfer data from IO to UI thread.")); } } @@ -344,12 +319,10 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { return content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseData, - this, + base::Bind(&AtomURLRequest::InformDelegateResponseData, this, buffer_copy)); } - void AtomURLRequest::InformDelegateAuthenticationRequired( scoped_refptr auth_info) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 8d182a7da5..1acfd32204 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -30,8 +30,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, const std::string& url, base::WeakPtr delegate); - bool Write(scoped_refptr buffer, - bool is_last); + bool Write(scoped_refptr buffer, bool is_last); void SetChunkedUpload(bool is_chunked_upload); void Cancel() const; void SetExtraHeader(const std::string& name, const std::string& value) const; @@ -50,7 +49,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, friend class base::RefCountedThreadSafe; explicit AtomURLRequest(base::WeakPtr delegate); - ~AtomURLRequest()override; + ~AtomURLRequest() override; void DoInitialize(scoped_refptr, const std::string& method, @@ -85,7 +84,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, std::unique_ptr chunked_stream_; std::unique_ptr chunked_stream_writer_; std::vector> - upload_element_readers_; + upload_element_readers_; scoped_refptr response_read_buffer_; DISALLOW_COPY_AND_ASSIGN(AtomURLRequest); From c0c9e3ac3d675286550463d82aa2e7352ce43fd6 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 14 Oct 2016 11:51:45 +0200 Subject: [PATCH 35/62] Formatting C++ code using ClangFormat. --- atom/browser/api/atom_api_url_request.h | 52 ++++++++++++------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index e7780556bc..3198338a1b 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -94,9 +94,8 @@ class URLRequest : public mate::EventEmitter { public: static mate::WrappableBase* New(mate::Arguments* args); - static void BuildPrototype( - v8::Isolate* isolate, - v8::Local prototype); + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); // Methods for reporting events into JavaScript. void OnAuthenticationRequired( @@ -109,8 +108,7 @@ class URLRequest : public mate::EventEmitter { void OnResponseError(const std::string& error); protected: - explicit URLRequest(v8::Isolate* isolate, - v8::Local wrapper); + explicit URLRequest(v8::Isolate* isolate, v8::Local wrapper); ~URLRequest() override; private: @@ -118,21 +116,23 @@ class URLRequest : public mate::EventEmitter { class StateBase { public: void SetFlag(Flags flag); + protected: explicit StateBase(Flags initialState); bool operator==(Flags flag) const; bool IsFlagSet(Flags flag) const; + private: - Flags state_; + Flags state_; }; enum class RequestStateFlags { - kNotStarted = 0x0, - kStarted = 0x1, - kFinished = 0x2, - kCanceled = 0x4, - kFailed = 0x8, - kClosed = 0x10 + kNotStarted = 0x0, + kStarted = 0x1, + kFinished = 0x2, + kCanceled = 0x4, + kFailed = 0x8, + kClosed = 0x10 }; class RequestState : public StateBase { @@ -168,8 +168,7 @@ class URLRequest : public mate::EventEmitter { bool Finished() const; bool Canceled() const; bool Failed() const; - bool Write(scoped_refptr buffer, - bool is_last); + bool Write(scoped_refptr buffer, bool is_last); void Cancel(); bool SetExtraHeader(const std::string& name, const std::string& value); void RemoveExtraHeader(const std::string& name); @@ -181,14 +180,14 @@ class URLRequest : public mate::EventEmitter { uint32_t ResponseHttpVersionMajor() const; uint32_t ResponseHttpVersionMinor() const; - template - std::array, sizeof...(ArgTypes)> - BuildArgsArray(ArgTypes... args) const; + template + std::array, sizeof...(ArgTypes)> BuildArgsArray( + ArgTypes... args) const; - template + template void EmitRequestEvent(ArgTypes... args); - template + template void EmitResponseEvent(ArgTypes... args); void Close(); @@ -204,35 +203,34 @@ class URLRequest : public mate::EventEmitter { scoped_refptr response_headers_; base::WeakPtrFactory weak_ptr_factory_; - DISALLOW_COPY_AND_ASSIGN(URLRequest); }; -template +template std::array, sizeof...(ArgTypes)> URLRequest::BuildArgsArray(ArgTypes... args) const { - std::array, sizeof...(ArgTypes)> result - = { { mate::ConvertToV8(isolate(), args)... } }; + std::array, sizeof...(ArgTypes)> result = { + {mate::ConvertToV8(isolate(), args)...}}; return result; } -template +template void URLRequest::EmitRequestEvent(ArgTypes... args) { auto arguments = BuildArgsArray(args...); v8::Local _emitRequestEvent; auto wrapper = GetWrapper(); if (mate::Dictionary(isolate(), wrapper) - .Get("_emitRequestEvent", &_emitRequestEvent)) + .Get("_emitRequestEvent", &_emitRequestEvent)) _emitRequestEvent->Call(wrapper, arguments.size(), arguments.data()); } -template +template void URLRequest::EmitResponseEvent(ArgTypes... args) { auto arguments = BuildArgsArray(args...); v8::Local _emitResponseEvent; auto wrapper = GetWrapper(); if (mate::Dictionary(isolate(), wrapper) - .Get("_emitResponseEvent", &_emitResponseEvent)) + .Get("_emitResponseEvent", &_emitResponseEvent)) _emitResponseEvent->Call(wrapper, arguments.size(), arguments.data()); } From ac9e6eda9526e8b1e163e1c6e2136e85b06d25b2 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 14 Oct 2016 17:37:39 +0200 Subject: [PATCH 36/62] Fixing a crash with pending URLRequests on shutdown. --- atom/browser/api/atom_api_url_request.cc | 32 ++++-- atom/browser/api/atom_api_url_request.h | 3 +- atom/browser/net/atom_url_request.cc | 124 ++++++++++++++++------- atom/browser/net/atom_url_request.h | 19 +++- 4 files changed, 122 insertions(+), 56 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 393f03ddb4..9a9fb28892 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -132,7 +132,14 @@ URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) InitWith(isolate, wrapper); } -URLRequest::~URLRequest() {} +URLRequest::~URLRequest() { + // A request has been created in JS, it was not used and then + // it got collected, no close event to cleanup, only destructor + // is called. + if (atom_request_) { + atom_request_->Terminate(); + } +} // static mate::WrappableBase* URLRequest::New(mate::Arguments* args) { @@ -341,17 +348,15 @@ void URLRequest::OnResponseCompleted() { Close(); } -void URLRequest::OnRequestError(const std::string& error) { +void URLRequest::OnError(const std::string& error, bool isRequestError) { auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); - request_state_.SetFlag(RequestStateFlags::kFailed); - EmitRequestEvent(false, "error", error_object); - Close(); -} - -void URLRequest::OnResponseError(const std::string& error) { - auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); - response_state_.SetFlag(ResponseStateFlags::kFailed); - EmitResponseEvent(false, "error", error_object); + if (isRequestError) { + request_state_.SetFlag(RequestStateFlags::kFailed); + EmitRequestEvent(false, "error", error_object); + } else { + response_state_.SetFlag(ResponseStateFlags::kFailed); + EmitResponseEvent(false, "error", error_object); + } Close(); } @@ -398,6 +403,11 @@ void URLRequest::Close() { EmitRequestEvent(true, "close"); } unpin(); + if (atom_request_) { + // A request has been created in JS, used and then it ended. + // We release unneeded net resources. + atom_request_->Terminate(); + } atom_request_ = nullptr; } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 3198338a1b..7a2ea6949f 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -104,8 +104,7 @@ class URLRequest : public mate::EventEmitter { scoped_refptr response_headers); void OnResponseData(scoped_refptr data); void OnResponseCompleted(); - void OnRequestError(const std::string& error); - void OnResponseError(const std::string& error); + void OnError(const std::string& error, bool isRequestError); protected: explicit URLRequest(v8::Isolate* isolate, v8::Local wrapper); diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index c0d41b3fb8..576036819f 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -48,7 +48,10 @@ AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) is_chunked_upload_(false), response_read_buffer_(new net::IOBuffer(kBufferSize)) {} -AtomURLRequest::~AtomURLRequest() {} +AtomURLRequest::~AtomURLRequest() { + DCHECK(!request_context_getter_); + DCHECK(!request_); +} scoped_refptr AtomURLRequest::Create( AtomBrowserContext* browser_context, @@ -59,11 +62,15 @@ scoped_refptr AtomURLRequest::Create( DCHECK(browser_context); DCHECK(!url.empty()); - if (!browser_context || url.empty()) { + DCHECK(delegate); + if (!browser_context || url.empty() || !delegate) { return nullptr; } - auto request_context_getter = browser_context->url_request_context_getter(); + DCHECK(request_context_getter); + if (!request_context_getter) { + return nullptr; + } scoped_refptr atom_url_request(new AtomURLRequest(delegate)); if (content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, @@ -74,6 +81,13 @@ scoped_refptr AtomURLRequest::Create( return nullptr; } +void AtomURLRequest::Terminate() { + DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&AtomURLRequest::DoTerminate, this)); +} + void AtomURLRequest::DoInitialize( scoped_refptr request_context_getter, const std::string& method, @@ -81,15 +95,34 @@ void AtomURLRequest::DoInitialize( DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK(request_context_getter); - auto context = request_context_getter->GetURLRequestContext(); + request_context_getter_ = request_context_getter; + request_context_getter_->AddObserver(this); + auto context = request_context_getter_->GetURLRequestContext(); + if (!context) { + // Called after shutdown. + DoCancelWithError("Cannot start a request after shutdown.", true); + return; + } DCHECK(context); request_ = context->CreateRequest( GURL(url), net::RequestPriority::DEFAULT_PRIORITY, this); - + if (!request_) { + DoCancelWithError("Failed to create a net::URLRequest.", true); + return; + } request_->set_method(method); } +void AtomURLRequest::DoTerminate() { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + request_.reset(); + if (request_context_getter_) { + request_context_getter_->RemoveObserver(this); + request_context_getter_ = nullptr; + } +} + bool AtomURLRequest::Write(scoped_refptr buffer, bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); @@ -107,7 +140,7 @@ void AtomURLRequest::SetChunkedUpload(bool is_chunked_upload) { is_chunked_upload_ = is_chunked_upload; } -void AtomURLRequest::Cancel() const { +void AtomURLRequest::Cancel() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoCancel, this)); @@ -147,6 +180,9 @@ void AtomURLRequest::DoWriteBuffer( scoped_refptr buffer, bool is_last) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_) { + return; + } if (is_chunked_upload_) { // Chunked encoding case. @@ -191,32 +227,56 @@ void AtomURLRequest::DoWriteBuffer( } } -void AtomURLRequest::DoCancel() const { +void AtomURLRequest::DoCancel() { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - request_->Cancel(); + if (request_) { + request_->Cancel(); + } + DoTerminate(); } void AtomURLRequest::DoSetExtraHeader(const std::string& name, const std::string& value) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_) { + return; + } request_->SetExtraRequestHeaderByName(name, value, true); } void AtomURLRequest::DoRemoveExtraHeader(const std::string& name) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_) { + return; + } request_->RemoveRequestHeaderByName(name); } void AtomURLRequest::DoSetAuth(const base::string16& username, const base::string16& password) const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_) { + return; + } request_->SetAuth(net::AuthCredentials(username, password)); } void AtomURLRequest::DoCancelAuth() const { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_) { + return; + } request_->CancelAuth(); } +void AtomURLRequest::DoCancelWithError(const std::string& error, + bool isRequestError) { + DoCancel(); + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&AtomURLRequest::InformDelegateErrorOccured, this, error, + isRequestError)); +} + void AtomURLRequest::OnAuthRequired(net::URLRequest* request, net::AuthChallengeInfo* auth_info) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -229,6 +289,9 @@ void AtomURLRequest::OnAuthRequired(net::URLRequest* request, void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + if (!request_) { + return; + } DCHECK_EQ(request, request_.get()); scoped_refptr response_headers = @@ -240,16 +303,10 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) { content::BrowserThread::UI, FROM_HERE, base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this, response_headers)); - ReadResponse(); } else if (status.status() == net::URLRequestStatus::Status::FAILED) { // Report error on Start. - DoCancel(); - auto error = net::ErrorToString(status.ToNetError()); - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateRequestErrorOccured, this, - std::move(error))); + DoCancelWithError(net::ErrorToString(status.ToNetError()), true); } // We don't report an error is the request is canceled. } @@ -265,7 +322,9 @@ void AtomURLRequest::ReadResponse() { void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); - + if (!request_) { + return; + } DCHECK_EQ(request, request_.get()); const auto status = request_->status(); @@ -289,26 +348,23 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) { } while ( request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)); if (response_error) { - DoCancel(); - auto error = net::ErrorToString(status.ToNetError()); - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, this, - std::move(error))); + DoCancelWithError(net::ErrorToString(status.ToNetError()), false); } else if (data_ended) { content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); + DoTerminate(); } else if (data_transfer_error) { // We abort the request on corrupted data transfer. - DoCancel(); - content::BrowserThread::PostTask( - content::BrowserThread::UI, FROM_HERE, - base::Bind(&AtomURLRequest::InformDelegateResponseErrorOccured, this, - "Failed to transfer data from IO to UI thread.")); + DoCancelWithError("Failed to transfer data from IO to UI thread.", false); } } +void AtomURLRequest::OnContextShuttingDown() { + DCHECK_CURRENTLY_ON(content::BrowserThread::IO); + DoCancel(); +} + bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) { DCHECK_CURRENTLY_ON(content::BrowserThread::IO); @@ -354,20 +410,12 @@ void AtomURLRequest::InformDelegateResponseCompleted() const { delegate_->OnResponseCompleted(); } -void AtomURLRequest::InformDelegateRequestErrorOccured( - const std::string& error) const { +void AtomURLRequest::InformDelegateErrorOccured(const std::string& error, + bool isRequestError) const { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); if (delegate_) - delegate_->OnRequestError(error); -} - -void AtomURLRequest::InformDelegateResponseErrorOccured( - const std::string& error) const { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - - if (delegate_) - delegate_->OnResponseError(error); + delegate_->OnError(error, isRequestError); } } // namespace atom diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 1acfd32204..3efccae50f 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -18,21 +18,24 @@ #include "net/base/upload_element_reader.h" #include "net/http/http_response_headers.h" #include "net/url_request/url_request.h" +#include "net/url_request/url_request_context_getter_observer.h" namespace atom { class AtomURLRequest : public base::RefCountedThreadSafe, - public net::URLRequest::Delegate { + public net::URLRequest::Delegate, + public net::URLRequestContextGetterObserver { public: static scoped_refptr Create( AtomBrowserContext* browser_context, const std::string& method, const std::string& url, base::WeakPtr delegate); + void Terminate(); bool Write(scoped_refptr buffer, bool is_last); void SetChunkedUpload(bool is_chunked_upload); - void Cancel() const; + void Cancel(); void SetExtraHeader(const std::string& name, const std::string& value) const; void RemoveExtraHeader(const std::string& name) const; void PassLoginInformation(const base::string16& username, @@ -45,6 +48,9 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void OnResponseStarted(net::URLRequest* request) override; void OnReadCompleted(net::URLRequest* request, int bytes_read) override; + // Overrides of net::URLRequestContextGetterObserver + void OnContextShuttingDown() override; + private: friend class base::RefCountedThreadSafe; @@ -54,15 +60,17 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void DoInitialize(scoped_refptr, const std::string& method, const std::string& url); + void DoTerminate(); void DoWriteBuffer(scoped_refptr buffer, bool is_last); - void DoCancel() const; + void DoCancel(); void DoSetExtraHeader(const std::string& name, const std::string& value) const; void DoRemoveExtraHeader(const std::string& name) const; void DoSetAuth(const base::string16& username, const base::string16& password) const; void DoCancelAuth() const; + void DoCancelWithError(const std::string& error, bool isRequestError); void ReadResponse(); bool CopyAndPostBuffer(int bytes_read); @@ -74,11 +82,12 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void InformDelegateResponseData( scoped_refptr data) const; void InformDelegateResponseCompleted() const; - void InformDelegateRequestErrorOccured(const std::string& error) const; - void InformDelegateResponseErrorOccured(const std::string& error) const; + void InformDelegateErrorOccured(const std::string& error, + bool isRequestError) const; base::WeakPtr delegate_; std::unique_ptr request_; + scoped_refptr request_context_getter_; bool is_chunked_upload_; std::unique_ptr chunked_stream_; From 0e13b8dd015439798afe1ae3c79ecad7064ed8b0 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Fri, 14 Oct 2016 17:57:37 +0200 Subject: [PATCH 37/62] making the net module usable only after the ready event. --- lib/browser/api/net.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 74d6c05063..a0d39c636e 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -7,6 +7,7 @@ const Readable = require('stream').Readable const binding = process.atomBinding('net') const {net, Net} = binding const {URLRequest} = net +const {app} = require('electron') Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) @@ -106,6 +107,10 @@ class ClientRequest extends EventEmitter { constructor (options, callback) { super() + if (!app.isReady()) { + throw new Error('net module can only be used after app is ready') + } + if (typeof options === 'string') { options = url.parse(options) } else { From c6ae27c7c95272611125fea7bc9dc763934d54cb Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 17 Oct 2016 10:38:10 +0200 Subject: [PATCH 38/62] Adding a stability test: non referenced, unused requests are collected without crash. --- spec/api-net-spec.js | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index e1970494a8..e2236438ec 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -25,7 +25,7 @@ const kOneKiloByte = 1024 const kOneMegaByte = kOneKiloByte * kOneKiloByte describe('net module', function () { - // this.timeout(0) + this.timeout(0) describe('HTTP basics', function () { let server beforeEach(function (done) { @@ -1154,4 +1154,34 @@ describe('net module', function () { urlRequest.end() }) }) + describe('Stability and performance', function(done) { + let server + beforeEach(function (done) { + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) + }) + + afterEach(function () { + server.close() + server = null + }) + + it.only('should free unreferenced, never-started request objects', function() { + const requestUrl = '/requestUrl' + const urlRequest = net.request( `${server.url}${requestUrl}`) + process.nextTick(function() { + net._RequestGarbageCollectionForTesting() + process.nextTick(done) + }) + }) + it.skip('should not collect on-going requests', function(done) { + assert(false) + }) + it.skip('should collect unreferenced, ended requests', function(done) { + assert(false) + }) + } }) From 9b4e9c642ab4b8ba5004ae3552e787f7979d335a Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 17 Oct 2016 11:51:20 +0200 Subject: [PATCH 39/62] Making test code run directly in main browser context to avoid rpc persistent handles. --- spec/api-net-spec.js | 17 +++++++++++++---- spec/static/main.js | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index e2236438ec..4b1c2f48af 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -1,5 +1,6 @@ const assert = require('assert') const {remote} = require('electron') +const {ipcRenderer} = require('electron') const http = require('http') const url = require('url') const {net} = remote @@ -1169,13 +1170,21 @@ describe('net module', function () { server = null }) - it.only('should free unreferenced, never-started request objects', function() { + it.only('should free unreferenced, never-started request objects', function(done) { const requestUrl = '/requestUrl' - const urlRequest = net.request( `${server.url}${requestUrl}`) + ipcRenderer.on('api-net-spec-done', function() { + done() + }) + const testCode = ` + // Load the net module in the main browser process. + const {net} = require('electron') + const urlRequest = net.request('${server.url}${requestUrl}') process.nextTick(function() { net._RequestGarbageCollectionForTesting() - process.nextTick(done) + event.sender.send('api-net-spec-done') }) + ` + ipcRenderer.send('eval', testCode) }) it.skip('should not collect on-going requests', function(done) { assert(false) @@ -1183,5 +1192,5 @@ describe('net module', function () { it.skip('should collect unreferenced, ended requests', function(done) { assert(false) }) - } + }) }) diff --git a/spec/static/main.js b/spec/static/main.js index e910e4de2c..9a30fd8170 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -8,6 +8,7 @@ const ipcMain = electron.ipcMain const dialog = electron.dialog const BrowserWindow = electron.BrowserWindow const protocol = electron.protocol +const v8 = require('v8'); const Coverage = require('electabul').Coverage const fs = require('fs') @@ -24,6 +25,7 @@ var argv = require('yargs') var window = null process.port = 0 // will be used by crash-reporter spec. +v8.setFlagsFromString('--expose_gc') app.commandLine.appendSwitch('js-flags', '--expose_gc') app.commandLine.appendSwitch('ignore-certificate-errors') app.commandLine.appendSwitch('disable-renderer-backgrounding') From c198828e58a9fa4f0131957e4699bebe351a1230 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Mon, 17 Oct 2016 19:02:25 +0200 Subject: [PATCH 40/62] Adding gc tests: fixing a memory leak with C++ URLRequest objects. --- atom/browser/api/atom_api_url_request.h | 2 + lib/browser/api/net.js | 2 +- spec/api-net-spec.js | 116 +++++++++++++++++++----- spec/static/main.js | 2 +- 4 files changed, 95 insertions(+), 27 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 7a2ea6949f..f59f8bc72f 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -215,6 +215,7 @@ URLRequest::BuildArgsArray(ArgTypes... args) const { template void URLRequest::EmitRequestEvent(ArgTypes... args) { + v8::HandleScope handle_scope(isolate()); auto arguments = BuildArgsArray(args...); v8::Local _emitRequestEvent; auto wrapper = GetWrapper(); @@ -225,6 +226,7 @@ void URLRequest::EmitRequestEvent(ArgTypes... args) { template void URLRequest::EmitResponseEvent(ArgTypes... args) { + v8::HandleScope handle_scope(isolate()); auto arguments = BuildArgsArray(args...); v8::Local _emitResponseEvent; auto wrapper = GetWrapper(); diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index a0d39c636e..ef76984ae6 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -95,7 +95,7 @@ URLRequest.prototype._emitRequestEvent = function (async, ...rest) { URLRequest.prototype._emitResponseEvent = function (async, ...rest) { if (async) { process.nextTick(() => { - this.clientRequest.emit.apply(this._response, rest) + this._response.emit.apply(this._response, rest) }) } else { this._response.emit.apply(this._response, rest) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 4b1c2f48af..0ebb252030 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -26,7 +26,7 @@ const kOneKiloByte = 1024 const kOneMegaByte = kOneKiloByte * kOneKiloByte describe('net module', function () { - this.timeout(0) + // this.timeout(0) describe('HTTP basics', function () { let server beforeEach(function (done) { @@ -1155,42 +1155,108 @@ describe('net module', function () { urlRequest.end() }) }) - describe('Stability and performance', function(done) { + describe('Stability and performance', function (done) { let server beforeEach(function (done) { - server = http.createServer() - server.listen(0, '127.0.0.1', function () { - server.url = 'http://127.0.0.1:' + server.address().port - done() - }) + server = http.createServer() + server.listen(0, '127.0.0.1', function () { + server.url = 'http://127.0.0.1:' + server.address().port + done() + }) }) afterEach(function () { - server.close() - server = null + server.close() + server = null }) - it.only('should free unreferenced, never-started request objects', function(done) { + it('should free unreferenced, never-started request objects', function (done) { const requestUrl = '/requestUrl' - ipcRenderer.on('api-net-spec-done', function() { + ipcRenderer.once('api-net-spec-unused-done', function () { done() }) - const testCode = ` - // Load the net module in the main browser process. - const {net} = require('electron') - const urlRequest = net.request('${server.url}${requestUrl}') - process.nextTick(function() { - net._RequestGarbageCollectionForTesting() - event.sender.send('api-net-spec-done') + ipcRenderer.send('eval', ` + const {net} = require('electron') + const urlRequest = net.request('${server.url}${requestUrl}') + process.nextTick(function () { + net._RequestGarbageCollectionForTesting() + event.sender.send('api-net-spec-unused-done') + }) + `) + }) + it('should not collect on-going requests', function (done) { + const requestUrl = '/requestUrl' + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + response.statusCode = 200 + response.statusMessage = 'OK' + response.write(randomString(kOneKiloByte)) + ipcRenderer.once('api-net-spec-ongoing-resume-response', function () { + response.write(randomString(kOneKiloByte)) + response.end() + }) + break + default: + assert(false) + } }) - ` - ipcRenderer.send('eval', testCode) + ipcRenderer.once('api-net-spec-ongoing-done', function () { + 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', function (response) { + response.on('data', function () { + }) + response.on('end', function () { + event.sender.send('api-net-spec-ongoing-done') + }) + process.nextTick(function () { + // Trigger a garbage collection. + net._RequestGarbageCollectionForTesting() + event.sender.send('api-net-spec-ongoing-resume-response') + }) + }) + urlRequest.end() + `) }) - it.skip('should not collect on-going requests', function(done) { - assert(false) - }) - it.skip('should collect unreferenced, ended requests', function(done) { - assert(false) + it('should collect unreferenced, ended requests', function (done) { + const requestUrl = '/requestUrl' + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + response.statusCode = 200 + response.statusMessage = 'OK' + response.end() + break + default: + assert(false) + } + }) + ipcRenderer.once('api-net-spec-done', function () { + done() + }) + ipcRenderer.send('eval', ` + const {net} = require('electron') + const urlRequest = net.request('${server.url}${requestUrl}') + urlRequest.on('response', function (response) { + response.on('data', function () { + }) + response.on('end', function () { + }) + }) + urlRequest.on('close', function () { + process.nextTick(function () { + net._RequestGarbageCollectionForTesting() + event.sender.send('api-net-spec-done') + }) + }) + urlRequest.end() + `) }) }) }) diff --git a/spec/static/main.js b/spec/static/main.js index 9a30fd8170..58d8a1970a 100644 --- a/spec/static/main.js +++ b/spec/static/main.js @@ -8,7 +8,7 @@ const ipcMain = electron.ipcMain const dialog = electron.dialog const BrowserWindow = electron.BrowserWindow const protocol = electron.protocol -const v8 = require('v8'); +const v8 = require('v8') const Coverage = require('electabul').Coverage const fs = require('fs') From 43113fcfb465e32b7f4bacbb646ae482a9970a19 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 18 Oct 2016 10:28:35 +0200 Subject: [PATCH 41/62] Fixing net response objects being piped into writable streams test. --- spec/api-net-spec.js | 74 ++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 0ebb252030..fa73d7722c 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -1040,12 +1040,10 @@ describe('net module', function () { urlRequest.end() }) - it.skip('should be able to pipe a net response into a writable stream', function (done) { + it('should be able to pipe a net response into a writable stream', function (done) { const nodeRequestUrl = '/nodeRequestUrl' const netRequestUrl = '/netRequestUrl' const bodyData = randomString(kOneMegaByte) - let nodeRequestReceived = false - let nodeRequestEnded = false server.on('request', function (request, response) { switch (request.url) { case netRequestUrl: @@ -1055,13 +1053,11 @@ describe('net module', function () { response.end() break case nodeRequestUrl: - nodeRequestReceived = true let receivedBodyData = '' request.on('data', function (chunk) { receivedBodyData += chunk.toString() }) request.on('end', function (chunk) { - nodeRequestEnded = true if (chunk) { receivedBodyData += chunk.toString() } @@ -1073,30 +1069,34 @@ describe('net module', function () { assert(false) } }) - - const netRequest = net.request(`${server.url}${netRequestUrl}`) - netRequest.on('response', function (netResponse) { - assert.equal(netResponse.statusCode, 200) - 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', function (chunk) { - }) - nodeResponse.on('end', function (chunk) { - assert(nodeRequestReceived) - assert(nodeRequestEnded) - done() - }) - }) - netResponse.pipe(nodeRequest) + ipcRenderer.once('api-net-spec-done', function () { + done() }) - netRequest.end() + // 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 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', function (chunk) { + }) + nodeResponse.on('end', function (chunk) { + event.sender.send('api-net-spec-done') + }) + }) + netResponse.pipe(nodeRequest) + }) + netRequest.end() + `) }) it('should not emit any event after close', function (done) { @@ -1170,9 +1170,9 @@ describe('net module', function () { server = null }) - it('should free unreferenced, never-started request objects', function (done) { + it('should free unreferenced, never-started request objects without crash', function (done) { const requestUrl = '/requestUrl' - ipcRenderer.once('api-net-spec-unused-done', function () { + ipcRenderer.once('api-net-spec-done', function () { done() }) ipcRenderer.send('eval', ` @@ -1180,11 +1180,11 @@ describe('net module', function () { const urlRequest = net.request('${server.url}${requestUrl}') process.nextTick(function () { net._RequestGarbageCollectionForTesting() - event.sender.send('api-net-spec-unused-done') + event.sender.send('api-net-spec-done') }) `) }) - it('should not collect on-going requests', function (done) { + it('should not collect on-going requests without crash', function (done) { const requestUrl = '/requestUrl' server.on('request', function (request, response) { switch (request.url) { @@ -1192,7 +1192,7 @@ describe('net module', function () { response.statusCode = 200 response.statusMessage = 'OK' response.write(randomString(kOneKiloByte)) - ipcRenderer.once('api-net-spec-ongoing-resume-response', function () { + ipcRenderer.once('api-net-spec-resume', function () { response.write(randomString(kOneKiloByte)) response.end() }) @@ -1201,7 +1201,7 @@ describe('net module', function () { assert(false) } }) - ipcRenderer.once('api-net-spec-ongoing-done', function () { + ipcRenderer.once('api-net-spec-done', function () { done() }) // Execute below code directly within the browser context without @@ -1213,18 +1213,18 @@ describe('net module', function () { response.on('data', function () { }) response.on('end', function () { - event.sender.send('api-net-spec-ongoing-done') + event.sender.send('api-net-spec-done') }) process.nextTick(function () { // Trigger a garbage collection. net._RequestGarbageCollectionForTesting() - event.sender.send('api-net-spec-ongoing-resume-response') + event.sender.send('api-net-spec-resume') }) }) urlRequest.end() `) }) - it('should collect unreferenced, ended requests', function (done) { + it('should collect unreferenced, ended requests without crash', function (done) { const requestUrl = '/requestUrl' server.on('request', function (request, response) { switch (request.url) { From 9bddc2cbaacd13247688e3fff0c968060a48abc1 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 18 Oct 2016 19:14:43 +0200 Subject: [PATCH 42/62] Adding net documentation skeleton. --- docs/api/electron.md | 79 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 docs/api/electron.md diff --git a/docs/api/electron.md b/docs/api/electron.md new file mode 100644 index 0000000000..718238e254 --- /dev/null +++ b/docs/api/electron.md @@ -0,0 +1,79 @@ +# net + +> Issue HTTP/HTTPS requests + +The `net` module is a client-side API for issuing HTTP requests. It is similar to the [HTTP](https://nodejs.org/api/http.html) and [HTTPS](https://nodejs.org/api/https.html) modules of Node.js but uses Chromium networking library instead of the Node.js stack offering therefore a much grater support regarding web proxies. + +Following is a non-exhaustive list of why you may need to use the `net` module instead of Node.js [HTTP](https://nodejs.org/api/http.html): +* Automatic management of system proxy configuration, support of the wpad protocol and proxy pac configuration files. +* Automatic tunneling of HTTPS requests. +* Support for authenticating proxies using basic, digest, NTLM, Kerberos or negotiate authentication schemes. +* Support for traffic monitoring proxies: Fiddler-like proxies used for access control and monitoring. + +The following example quickly shows how the net API mgiht be used: +```javascript +const {app} = require('electron') + +app.on('ready', () => { + const {net} = require('electron') + const request = net.request('https://github.com') + request.on('response', (response) => { + console.log(`STATUS: ${response.statusCode}`); + console.log(`HEADERS: ${JSON.stringify(response.headers)}`); + response.on('data', (chunk) => { + console.log(`BODY: ${chunk}`) + }) + response.on('end', () => { + console.log('No more data in response.'); + }) + }) + request.end() +}) +``` + +## Methods + +### `net.request` + +## Class: ClientRequest + +### Instance Events + +#### Event: 'response' + +#### Event: 'login' + +#### Event: 'finish' + +#### Event: 'abort' + +#### Event: 'error' + +#### Event: 'close' + +### Instance Methods + +#### `request.setHeader(name, value)` + +#### `request.getHeader(name)` + +#### `request.removeHeader(name)` + +#### `request.write(chunk, [encoding], [callback])` + +#### `request.end(chunk, [encoding], [callback])` + +#### `request.abort()` + +### Instance Properties + +#### `request.chunkedEncoding` + +## Class: IncomingMessage + +#### Event: + + + + + From f651b21cda317d13fa37474809fa4866af9fb38f Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 19 Oct 2016 09:24:59 +0200 Subject: [PATCH 43/62] Fixing net doc file name. --- docs/api/{electron.md => net.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/api/{electron.md => net.md} (100%) diff --git a/docs/api/electron.md b/docs/api/net.md similarity index 100% rename from docs/api/electron.md rename to docs/api/net.md From 5cd20a8e5d15bf03b5498ecae2f0dd53b06ec792 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 19 Oct 2016 15:34:21 +0200 Subject: [PATCH 44/62] Documenting net.ClientRequest --- docs/api/net.md | 133 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 116 insertions(+), 17 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index 718238e254..225d084b95 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -15,63 +15,162 @@ The following example quickly shows how the net API mgiht be used: const {app} = require('electron') app.on('ready', () => { - const {net} = require('electron') - const request = net.request('https://github.com') - request.on('response', (response) => { - console.log(`STATUS: ${response.statusCode}`); - console.log(`HEADERS: ${JSON.stringify(response.headers)}`); - response.on('data', (chunk) => { - console.log(`BODY: ${chunk}`) - }) - response.on('end', () => { - console.log('No more data in response.'); - }) - }) - request.end() +const {net} = require('electron') +const request = net.request('https://github.com') +request.on('response', (response) => { + console.log(`STATUS: ${response.statusCode}`); + console.log(`HEADERS: ${JSON.stringify(response.headers)}`); + response.on('data', (chunk) => { + console.log(`BODY: ${chunk}`) + }) + response.on('end', () => { + console.log('No more data in response.'); + }) +}) +request.end() }) ``` ## Methods -### `net.request` +The `net` module has the following methods: + +### `net.request(options)` + +Create a `ClientRequest` instance using the provided `options` object. + +Returns `ClientRequest` ## Class: ClientRequest +### `new ClientRequest(options)` + +* `options` Object or String - If `options` is a String, it is interpreted as the request URL. If it is an object, it is expected to fully specify an HTTP request via the following properties: + * `method` String (optional) - The HTTP request method. Defaults to the GET method. + * `url` String (required) - The request URL. Must be provided in the absolute form with the protocol scheme specified as http or https. + * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. Current supported values are 'http:' or 'https:'. Defaults to 'http:'. + * `host` String (optional) - The server host provided as a concatenation of a hostname and a port number 'hostname:port' + * `hostname` String (optional) - The server host name. + * `port` Integer (optional) - The server's listening port number. + * `path` String (optional) - The path part of the request URL. + +`options` properties `protocol`, `host`, `hostname`, `port` and `path` strictly follow the Node.js model as described in the [URL](https://nodejs.org/api/url.html) module. + ### Instance Events #### Event: 'response' +Returns: + +* `response` IncomingMessage - An object representing the HTTP response message. + #### Event: 'login' +Returns: + +* `callback` Function + +Emitted when an authenticating proxy is asking for user credentials. + +The `callback` function is expected to be called back with user credentials: + +* `usrename` String +* `password` String + +Providing empty credentials will cancel the request. + #### Event: 'finish' +Emitted just after the last chunk of the `request`'s data has been written into the `request` object. + #### Event: 'abort' +Emitted when the `request` is aborted. The abort event will not be fired if the `request` is already closed. + #### Event: 'error' +Returns: + +* `error` Error - an error object providing some information about the failure. + +Emitted when the `net` module fails to issue a network request. Typically when the `request` object emits an error event, a close event will subsequently follow and no response object will be provided. + #### Event: 'close' +Emitted as the last event in the HTTP request-response transaction. The close event indicates that no more events will be emitted on either the `request` or `response` objects. + ### Instance Methods #### `request.setHeader(name, value)` +* `name` String - An extra header name. +* `value` String - An extra header value. + +Adds an extra HTTP header. The header name will issued as it is without lowercasing. + #### `request.getHeader(name)` +* `name` String - Specify an extra header name. + +Returns String - The value of a previously set extra header name. + #### `request.removeHeader(name)` -#### `request.write(chunk, [encoding], [callback])` +* `name` String - Specify an extra header name. -#### `request.end(chunk, [encoding], [callback])` +Removes a previously set extra header name. + +#### `request.write(chunk, [encoding, callback])` + +* `chunk` String or Buffer - A chunk of the request body' data. If it is a string, it is converted into a Buffer object using the specified encoding. +* `encoding` String (optional) - Used to convert string chunks into Buffer objects. Defaults to 'utf-8'. +* `callback` Function (optional) - Called after the write operation ends. + +Adds a chunk of data to the request body. Generally, the first write operation causes the request headers to be issued on the wire. +After the first write operation, it is not allowed to add or remove a custom header. + +#### `request.end([chunk, encoding, callback])` + +* `chunk` String or Buffer (optional) +* `encoding` String (optional) +* `callback` Function (optional) + +Sends the last chunk of the request data. Subsequent write or end operations will not be allowed. The finish event is emitted just after the end operation. #### `request.abort()` +Cancels an ongoing HTTP transaction. If the request has already closed, the abort operation will have no effect. +Otherwise an ongoing event will emit abort and close events. Additionally, if there is an ongoing response object, +it will emit the aborted event. + ### Instance Properties #### `request.chunkedEncoding` ## Class: IncomingMessage -#### Event: +### Instance Events + +#### Event 'data' + +#### Event 'end' + +#### Event 'aborted' + +#### Event 'error' + +### Instance properties + +#### `response.statusCode` + +#### `response.statusMessage` + +#### `response.headers` + +#### `response.httpVersion` + + + From 0d82fbcf370da077da91165e8c9d23f3f6ab1d6e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 19 Oct 2016 16:51:44 +0200 Subject: [PATCH 45/62] Documenting net.IncomingMessage. --- docs/api/net.md | 52 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index 225d084b95..bbe8e72371 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -37,12 +37,16 @@ The `net` module has the following methods: ### `net.request(options)` -Create a `ClientRequest` instance using the provided `options` object. +* `options`: Object or String - The `ClientRequest` constructor options. Returns `ClientRequest` +Create a `ClientRequest` instance using the provided `options` object. + ## Class: ClientRequest +`ClientRequest` is a [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams). + ### `new ClientRequest(options)` * `options` Object or String - If `options` is a String, it is interpreted as the request URL. If it is an object, it is expected to fully specify an HTTP request via the following properties: @@ -62,7 +66,7 @@ Returns `ClientRequest` Returns: -* `response` IncomingMessage - An object representing the HTTP response message. +* `response` IncomingMessage - An object representing an HTTP response message. #### Event: 'login' @@ -99,6 +103,17 @@ Emitted when the `net` module fails to issue a network request. Typically when t Emitted as the last event in the HTTP request-response transaction. The close event indicates that no more events will be emitted on either the `request` or `response` objects. +### Instance Properties + +#### `request.chunkedEncoding` + +A Boolean specifying whether the request will use HTTP chunked transfer encoding or not. Defaults to false. The property is readable and writable, +however it can be set only before the first write operation as the HTTP headers are not yet put on the wire. Trying to set the `chunkedEncoding` property +after a write will throw an error. + +Using chunked encoding is strongly recommended if you need to send a large request body as data will be streamed as small chunks instead of being internally buffered +in Electron memory. + ### Instance Methods #### `request.setHeader(name, value)` @@ -120,7 +135,7 @@ Returns String - The value of a previously set extra header name. Removes a previously set extra header name. -#### `request.write(chunk, [encoding, callback])` +#### `request.write(chunk[, encoding][, callback])` * `chunk` String or Buffer - A chunk of the request body' data. If it is a string, it is converted into a Buffer object using the specified encoding. * `encoding` String (optional) - Used to convert string chunks into Buffer objects. Defaults to 'utf-8'. @@ -129,7 +144,7 @@ Removes a previously set extra header name. Adds a chunk of data to the request body. Generally, the first write operation causes the request headers to be issued on the wire. After the first write operation, it is not allowed to add or remove a custom header. -#### `request.end([chunk, encoding, callback])` +#### `request.end([chunk][, encoding][, callback])` * `chunk` String or Buffer (optional) * `encoding` String (optional) @@ -143,32 +158,55 @@ Cancels an ongoing HTTP transaction. If the request has already closed, the abor Otherwise an ongoing event will emit abort and close events. Additionally, if there is an ongoing response object, it will emit the aborted event. -### Instance Properties - -#### `request.chunkedEncoding` ## Class: IncomingMessage +`IncomingMessage` is a [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams). It represents an HTTP response message. + ### Instance Events #### Event 'data' +Returns: + +* `chunk`: Buffer - A chunk of response body's data. + #### Event 'end' +Indicates that response body has ended. + #### Event 'aborted' +Emitted when a request has been canceled during an ongoing HTTP transaction. + #### Event 'error' +Returns + +`error` Error + +Emitted if an error is encountered + ### Instance properties +An `IncomingMessage` instance has the following readable properties: + #### `response.statusCode` +An Integer indicating the HTTP response status code. + #### `response.statusMessage` +A String representing the HTTP status message. + #### `response.headers` +An Object representing the response HTTP headers. + #### `response.httpVersion` +A String indicating the HTTP protocol version number. Typical values are '1.0' or '1.1'. Additionally `httpVersionMajor` and +`httpVersionMinor` are two Integer-valued readable properties that return respectively the HTTP major and minor version numbers. From 3b620b8c0c5df821140fe21bf5399d5fdb2d61ec Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 19 Oct 2016 18:05:38 +0200 Subject: [PATCH 46/62] Documenting net module: various fixes. --- docs/api/net.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index bbe8e72371..01de63c3bd 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -10,24 +10,24 @@ Following is a non-exhaustive list of why you may need to use the `net` module i * Support for authenticating proxies using basic, digest, NTLM, Kerberos or negotiate authentication schemes. * Support for traffic monitoring proxies: Fiddler-like proxies used for access control and monitoring. -The following example quickly shows how the net API mgiht be used: +The following example quickly shows how the `net` API mgiht be used: + ```javascript const {app} = require('electron') - app.on('ready', () => { -const {net} = require('electron') -const request = net.request('https://github.com') -request.on('response', (response) => { - console.log(`STATUS: ${response.statusCode}`); - console.log(`HEADERS: ${JSON.stringify(response.headers)}`); - response.on('data', (chunk) => { - console.log(`BODY: ${chunk}`) + const {net} = require('electron') + const request = net.request('https://github.com') + request.on('response', (response) => { + console.log(`STATUS: ${response.statusCode}`); + console.log(`HEADERS: ${JSON.stringify(response.headers)}`); + response.on('data', (chunk) => { + console.log(`BODY: ${chunk}`) + }) + response.on('end', () => { + console.log('No more data in response.'); + }) }) - response.on('end', () => { - console.log('No more data in response.'); - }) -}) -request.end() + request.end() }) ``` @@ -45,7 +45,7 @@ Create a `ClientRequest` instance using the provided `options` object. ## Class: ClientRequest -`ClientRequest` is a [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams). +`ClientRequest` implements the [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams) interface. ### `new ClientRequest(options)` @@ -201,7 +201,11 @@ A String representing the HTTP status message. #### `response.headers` -An Object representing the response HTTP headers. +An Object representing the response HTTP headers. The `headers` object is formatted as follows: + +* All header names are lowercased. +* Each header name produces an array-valued property on the headers object. +* Each header value is pushed into the array associated with its header name. #### `response.httpVersion` From 6e5951b764ecfb15a10cfd515887918f33d33485 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 19 Oct 2016 18:11:07 +0200 Subject: [PATCH 47/62] Documenting net module: formatting. --- docs/api/net.md | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index 01de63c3bd..8570fe4103 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -1,8 +1,10 @@ # net -> Issue HTTP/HTTPS requests +> Issue HTTP/HTTPS requests. -The `net` module is a client-side API for issuing HTTP requests. It is similar to the [HTTP](https://nodejs.org/api/http.html) and [HTTPS](https://nodejs.org/api/https.html) modules of Node.js but uses Chromium networking library instead of the Node.js stack offering therefore a much grater support regarding web proxies. +The `net` module is a client-side API for issuing HTTP(S) requests. It is similar to the +[HTTP](https://nodejs.org/api/http.html) and [HTTPS](https://nodejs.org/api/https.html) modules of Node.js +but uses Chromium networking library instead of the Node.js stack offering therefore a much grater support regarding web proxies. Following is a non-exhaustive list of why you may need to use the `net` module instead of Node.js [HTTP](https://nodejs.org/api/http.html): * Automatic management of system proxy configuration, support of the wpad protocol and proxy pac configuration files. @@ -14,19 +16,28 @@ The following example quickly shows how the `net` API mgiht be used: ```javascript const {app} = require('electron') + app.on('ready', () => { + const {net} = require('electron') + const request = net.request('https://github.com') + request.on('response', (response) => { + console.log(`STATUS: ${response.statusCode}`); console.log(`HEADERS: ${JSON.stringify(response.headers)}`); + response.on('data', (chunk) => { console.log(`BODY: ${chunk}`) }) + response.on('end', () => { console.log('No more data in response.'); }) + }) + request.end() }) ``` @@ -49,7 +60,8 @@ Create a `ClientRequest` instance using the provided `options` object. ### `new ClientRequest(options)` -* `options` Object or String - If `options` is a String, it is interpreted as the request URL. If it is an object, it is expected to fully specify an HTTP request via the following properties: +* `options` Object or String - If `options` is a String, it is interpreted as the request URL. +If it is an object, it is expected to fully specify an HTTP request via the following properties: * `method` String (optional) - The HTTP request method. Defaults to the GET method. * `url` String (required) - The request URL. Must be provided in the absolute form with the protocol scheme specified as http or https. * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. Current supported values are 'http:' or 'https:'. Defaults to 'http:'. @@ -58,7 +70,8 @@ Create a `ClientRequest` instance using the provided `options` object. * `port` Integer (optional) - The server's listening port number. * `path` String (optional) - The path part of the request URL. -`options` properties `protocol`, `host`, `hostname`, `port` and `path` strictly follow the Node.js model as described in the [URL](https://nodejs.org/api/url.html) module. +`options` properties `protocol`, `host`, `hostname`, `port` and `path` strictly +follow the Node.js model as described in the [URL](https://nodejs.org/api/url.html) module. ### Instance Events @@ -97,21 +110,26 @@ Returns: * `error` Error - an error object providing some information about the failure. -Emitted when the `net` module fails to issue a network request. Typically when the `request` object emits an error event, a close event will subsequently follow and no response object will be provided. +Emitted when the `net` module fails to issue a network request. Typically when the `request` +object emits an error event, a close event will subsequently follow and no response object will be provided. #### Event: 'close' -Emitted as the last event in the HTTP request-response transaction. The close event indicates that no more events will be emitted on either the `request` or `response` objects. +Emitted as the last event in the HTTP request-response transaction. The close event indicates +that no more events will be emitted on either the `request` or `response` objects. ### Instance Properties #### `request.chunkedEncoding` -A Boolean specifying whether the request will use HTTP chunked transfer encoding or not. Defaults to false. The property is readable and writable, -however it can be set only before the first write operation as the HTTP headers are not yet put on the wire. Trying to set the `chunkedEncoding` property +A Boolean specifying whether the request will use HTTP chunked transfer encoding or +not. Defaults to false. The property is readable and writable, +however it can be set only before the first write operation as the HTTP headers are +not yet put on the wire. Trying to set the `chunkedEncoding` property after a write will throw an error. -Using chunked encoding is strongly recommended if you need to send a large request body as data will be streamed as small chunks instead of being internally buffered +Using chunked encoding is strongly recommended if you need to send a large request body as +data will be streamed as small chunks instead of being internally buffered in Electron memory. ### Instance Methods @@ -137,7 +155,8 @@ Removes a previously set extra header name. #### `request.write(chunk[, encoding][, callback])` -* `chunk` String or Buffer - A chunk of the request body' data. If it is a string, it is converted into a Buffer object using the specified encoding. +* `chunk` String or Buffer - A chunk of the request body' data. If it is a string, +it is converted into a Buffer object using the specified encoding. * `encoding` String (optional) - Used to convert string chunks into Buffer objects. Defaults to 'utf-8'. * `callback` Function (optional) - Called after the write operation ends. @@ -150,7 +169,8 @@ After the first write operation, it is not allowed to add or remove a custom hea * `encoding` String (optional) * `callback` Function (optional) -Sends the last chunk of the request data. Subsequent write or end operations will not be allowed. The finish event is emitted just after the end operation. +Sends the last chunk of the request data. Subsequent write or end operations will not +be allowed. The finish event is emitted just after the end operation. #### `request.abort()` From fddbde2fd5327465a5c553532d1eb13886cc9e3e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 19 Oct 2016 18:19:28 +0200 Subject: [PATCH 48/62] Documenting net module: various fixes. --- docs/api/net.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index 8570fe4103..34cffd63d5 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -4,15 +4,19 @@ The `net` module is a client-side API for issuing HTTP(S) requests. It is similar to the [HTTP](https://nodejs.org/api/http.html) and [HTTPS](https://nodejs.org/api/https.html) modules of Node.js -but uses Chromium networking library instead of the Node.js stack offering therefore a much grater support regarding web proxies. +but it uses Chromium native networking library instead of the Node.js implementation offering +therefore a much greater support regarding web proxies. -Following is a non-exhaustive list of why you may need to use the `net` module instead of Node.js [HTTP](https://nodejs.org/api/http.html): +Following is a non-exhaustive list of why you may consider using the `net` module instead of the native Node.js modules: * Automatic management of system proxy configuration, support of the wpad protocol and proxy pac configuration files. * Automatic tunneling of HTTPS requests. * Support for authenticating proxies using basic, digest, NTLM, Kerberos or negotiate authentication schemes. * Support for traffic monitoring proxies: Fiddler-like proxies used for access control and monitoring. -The following example quickly shows how the `net` API mgiht be used: +The `net` module API has been specifically designed to mimic, as much closely as possible, the familiar Node.js API. +The API components including classes, methods, properties and event names are similar to those commonly used in Node.js. + +For instance, the following example quickly shows how the `net` API might be used: ```javascript const {app} = require('electron') @@ -42,6 +46,9 @@ app.on('ready', () => { }) ``` +By the way, it is almost identical to the way you would normally use the +[HTTP](https://nodejs.org/api/http.html)/[HTTPS](https://nodejs.org/api/https.html) modules of Node.js + ## Methods The `net` module has the following methods: @@ -52,7 +59,8 @@ The `net` module has the following methods: Returns `ClientRequest` -Create a `ClientRequest` instance using the provided `options` object. +Create a `ClientRequest` instance using the provided `options` object which is directly +passed to the `ClientRequest` constructor. ## Class: ClientRequest From 7549adcfce72bf2d2dca665d54a932839c67a28c Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 19 Oct 2016 18:31:08 +0200 Subject: [PATCH 49/62] Documenting net module: various fixes. --- docs/api/net.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index 34cffd63d5..e9932fb2d8 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -60,11 +60,13 @@ The `net` module has the following methods: Returns `ClientRequest` Create a `ClientRequest` instance using the provided `options` object which is directly -passed to the `ClientRequest` constructor. +passed to the `ClientRequest` constructor. The `net.request` method would be used to issue +both secure and insecure HTTP requests according to the specified protocol scheme in the `options` object. ## Class: ClientRequest -`ClientRequest` implements the [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams) interface. +`ClientRequest` implements the [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams) interface +and it is therefore an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). ### `new ClientRequest(options)` @@ -189,7 +191,9 @@ it will emit the aborted event. ## Class: IncomingMessage -`IncomingMessage` is a [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams). It represents an HTTP response message. +`IncomingMessage` represents an HTTP response message. +It is a [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams) and therefore +an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). ### Instance Events @@ -199,6 +203,8 @@ Returns: * `chunk`: Buffer - A chunk of response body's data. +The data event is the usual method of transferring response data into applicative code. + #### Event 'end' Indicates that response body has ended. @@ -211,9 +217,11 @@ Emitted when a request has been canceled during an ongoing HTTP transaction. Returns -`error` Error +`error` Error - Typically holds an error string identifying failure root cause. -Emitted if an error is encountered +Emitted if an error is encountered while streaming response data events. For instance, +if the server closes the underlying socket while streaming the response, an error event +will be emitted on the response object and a close event will subsequently follow in the request object. ### Instance properties From 7f8b180f7065b9ee91f56c964267d8803448b203 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 20 Oct 2016 11:43:26 +0200 Subject: [PATCH 50/62] Fixing authentication cancel with null/undefined credentials. --- atom/browser/api/atom_api_url_request.cc | 5 ++--- lib/browser/api/net.js | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 9a9fb28892..42f08c8f05 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -305,9 +305,8 @@ void URLRequest::OnAuthenticationRequired( return; } - EmitRequestEvent( - false, "login", auth_info.get(), - base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); + Emit("login", auth_info.get(), + base::Bind(&AtomURLRequest::PassLoginInformation, atom_request_)); } void URLRequest::OnResponseStarted( diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index ef76984ae6..7fb2c61495 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -192,6 +192,25 @@ class ClientRequest extends EventEmitter { this.emit('response', response) }) + urlRequest.on('login', (event, authInfo, callback) => { + this.emit('login', authInfo, (username, password) => { + // If null or undefined usrename/password, force to empty string. + if (username === null || username === undefined) { + username = '' + } + if (typeof username !== 'string') { + throw new Error('username must be a string') + } + if (password === null || password === undefined) { + password = '' + } + if (typeof password !== 'string') { + throw new Error('password must be a string') + } + callback(username, password) + }) + }) + if (callback) { this.once('response', callback) } From fdfa0f4a6d9141f402e24bd5669e82c4ce8519c0 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 20 Oct 2016 11:45:45 +0200 Subject: [PATCH 51/62] Documenting the login event. --- docs/api/net.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/docs/api/net.md b/docs/api/net.md index e9932fb2d8..9459b0cb3d 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -95,6 +95,12 @@ Returns: Returns: +* `authInfo` Object + * `isProxy` Boolean + * `scheme` String + * `host` String + * `port` Integer + * `realm` String * `callback` Function Emitted when an authenticating proxy is asking for user credentials. @@ -104,7 +110,24 @@ The `callback` function is expected to be called back with user credentials: * `usrename` String * `password` String -Providing empty credentials will cancel the request. +```JavaScript +request.on('login', (authInfo, callback) => { + callback('username', 'password') +}) +``` +Providing empty credentials will cancel the request and report an authentication error on the response object: + +```JavaScript +request.on('response', (response) => { + console.log(`STATUS: ${response.statusCode}`); + response.on('error', (error) => { + console.log(`ERROR: ${JSON.stringify(error)}`) + }) +}) +request.on('login', (authInfo, callback) => { + callback() +}) +``` #### Event: 'finish' From 69790bd7edc9babd4c927e8be7507956157bc80f Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 20 Oct 2016 11:58:06 +0200 Subject: [PATCH 52/62] Documenting ClientRequest constructor options. --- docs/api/net.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/api/net.md b/docs/api/net.md index 9459b0cb3d..ff23f88204 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -83,6 +83,18 @@ If it is an object, it is expected to fully specify an HTTP request via the foll `options` properties `protocol`, `host`, `hostname`, `port` and `path` strictly follow the Node.js model as described in the [URL](https://nodejs.org/api/url.html) module. +For instance, we could have created the same request to 'github.com' as follows: + +```JavaScript +const request = net.request({ + method: 'GET', + protocol: 'https:', + hostname: 'github.com', + port: 443, + path: '/' +}) +``` + ### Instance Events #### Event: 'response' From 9f151912807c614d00c8dea6a24f71a9e2429ad2 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 20 Oct 2016 12:30:03 +0200 Subject: [PATCH 53/62] Formatting net documentation. --- docs/api/net.md | 174 +++++++++++++++++++++++++++--------------------- 1 file changed, 99 insertions(+), 75 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index ff23f88204..d99d9d562f 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -2,52 +2,52 @@ > Issue HTTP/HTTPS requests. -The `net` module is a client-side API for issuing HTTP(S) requests. It is similar to the -[HTTP](https://nodejs.org/api/http.html) and [HTTPS](https://nodejs.org/api/https.html) modules of Node.js -but it uses Chromium native networking library instead of the Node.js implementation offering -therefore a much greater support regarding web proxies. +The `net` module is a client-side API for issuing HTTP(S) requests. It is +similar to the [HTTP](https://nodejs.org/api/http.html) and +[HTTPS](https://nodejs.org/api/https.html) modules of Node.js but it uses +Chromium native networking library instead of the Node.js implementation +offering therefore a much greater support regarding web proxies. -Following is a non-exhaustive list of why you may consider using the `net` module instead of the native Node.js modules: -* Automatic management of system proxy configuration, support of the wpad protocol and proxy pac configuration files. +Following is a non-exhaustive list of why you may consider using the `net` +module instead of the native Node.js modules: +* Automatic management of system proxy configuration, support of the wpad +protocol and proxy pac configuration files. * Automatic tunneling of HTTPS requests. -* Support for authenticating proxies using basic, digest, NTLM, Kerberos or negotiate authentication schemes. -* Support for traffic monitoring proxies: Fiddler-like proxies used for access control and monitoring. +* Support for authenticating proxies using basic, digest, NTLM, Kerberos or +negotiate authentication schemes. +* Support for traffic monitoring proxies: Fiddler-like proxies used for access +control and monitoring. -The `net` module API has been specifically designed to mimic, as much closely as possible, the familiar Node.js API. -The API components including classes, methods, properties and event names are similar to those commonly used in Node.js. +The `net` module API has been specifically designed to mimic, as much closely as +possible, the familiar Node.js API. The API components including classes, +methods, properties and event names are similar to those commonly used in +Node.js. -For instance, the following example quickly shows how the `net` API might be used: +For instance, the following example quickly shows how the `net` API might be +used: ```javascript const {app} = require('electron') - app.on('ready', () => { - const {net} = require('electron') - const request = net.request('https://github.com') - request.on('response', (response) => { - console.log(`STATUS: ${response.statusCode}`); console.log(`HEADERS: ${JSON.stringify(response.headers)}`); - response.on('data', (chunk) => { console.log(`BODY: ${chunk}`) }) - response.on('end', () => { console.log('No more data in response.'); }) - }) - request.end() }) ``` -By the way, it is almost identical to the way you would normally use the -[HTTP](https://nodejs.org/api/http.html)/[HTTPS](https://nodejs.org/api/https.html) modules of Node.js +By the way, it is almost identical to how you would normally use the +[HTTP](https://nodejs.org/api/http.html)/[HTTPS](https://nodejs.org/api/https.html) +modules of Node.js ## Methods @@ -59,29 +59,37 @@ The `net` module has the following methods: Returns `ClientRequest` -Create a `ClientRequest` instance using the provided `options` object which is directly -passed to the `ClientRequest` constructor. The `net.request` method would be used to issue -both secure and insecure HTTP requests according to the specified protocol scheme in the `options` object. +Creates a `ClientRequest` instance using the provided `options` which are +directly forwarded to the `ClientRequest` constructor. The `net.request` method +would be used to issue both secure and insecure HTTP requests according to the +specified protocol scheme in the `options` object. ## Class: ClientRequest -`ClientRequest` implements the [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams) interface -and it is therefore an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). +`ClientRequest` implements the [Writable Stream](https://nodejs.org/api/stream.html#stream_writable_streams) +interface and it is therefore an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). ### `new ClientRequest(options)` -* `options` Object or String - If `options` is a String, it is interpreted as the request URL. -If it is an object, it is expected to fully specify an HTTP request via the following properties: - * `method` String (optional) - The HTTP request method. Defaults to the GET method. - * `url` String (required) - The request URL. Must be provided in the absolute form with the protocol scheme specified as http or https. - * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. Current supported values are 'http:' or 'https:'. Defaults to 'http:'. - * `host` String (optional) - The server host provided as a concatenation of a hostname and a port number 'hostname:port' +* `options` Object or String - If `options` is a String, it is interpreted as +the request URL. +If it is an object, it is expected to fully specify an HTTP request via the +following properties: + * `method` String (optional) - The HTTP request method. Defaults to the GET +method. + * `url` String (optional) - The request URL. Must be provided in the absolute +form with the protocol scheme specified as http or https. + * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. +Currently supported values are 'http:' or 'https:'. Defaults to 'http:'. + * `host` String (optional) - The server host provided as a concatenation of +the hostname and the port number 'hostname:port' * `hostname` String (optional) - The server host name. * `port` Integer (optional) - The server's listening port number. * `path` String (optional) - The path part of the request URL. -`options` properties `protocol`, `host`, `hostname`, `port` and `path` strictly -follow the Node.js model as described in the [URL](https://nodejs.org/api/url.html) module. +`options` properties such as `protocol`, `host`, `hostname`, `port` and `path` +strictly follow the Node.js model as described in the +[URL](https://nodejs.org/api/url.html) module. For instance, we could have created the same request to 'github.com' as follows: @@ -101,7 +109,7 @@ const request = net.request({ Returns: -* `response` IncomingMessage - An object representing an HTTP response message. +* `response` IncomingMessage - An object representing the HTTP response message. #### Event: 'login' @@ -127,7 +135,8 @@ request.on('login', (authInfo, callback) => { callback('username', 'password') }) ``` -Providing empty credentials will cancel the request and report an authentication error on the response object: +Providing empty credentials will cancel the request and report an authentication +error on the response object: ```JavaScript request.on('response', (response) => { @@ -143,11 +152,13 @@ request.on('login', (authInfo, callback) => { #### Event: 'finish' -Emitted just after the last chunk of the `request`'s data has been written into the `request` object. +Emitted just after the last chunk of the `request`'s data has been written into +the `request` object. #### Event: 'abort' -Emitted when the `request` is aborted. The abort event will not be fired if the `request` is already closed. +Emitted when the `request` is aborted. The `abort` event will not be fired if +the `request` is already closed. #### Event: 'error' @@ -155,36 +166,40 @@ Returns: * `error` Error - an error object providing some information about the failure. -Emitted when the `net` module fails to issue a network request. Typically when the `request` -object emits an error event, a close event will subsequently follow and no response object will be provided. +Emitted when the `net` module fails to issue a network request. Typically when +the `request` object emits an `error` event, a `close` event will subsequently +follow and no response object will be provided. #### Event: 'close' -Emitted as the last event in the HTTP request-response transaction. The close event indicates -that no more events will be emitted on either the `request` or `response` objects. +Emitted as the last event in the HTTP request-response transaction. The `close` +event indicates that no more events will be emitted on either the `request` or +`response` objects. ### Instance Properties #### `request.chunkedEncoding` -A Boolean specifying whether the request will use HTTP chunked transfer encoding or -not. Defaults to false. The property is readable and writable, -however it can be set only before the first write operation as the HTTP headers are -not yet put on the wire. Trying to set the `chunkedEncoding` property -after a write will throw an error. +A Boolean specifying whether the request will use HTTP chunked transfer encoding +or not. Defaults to false. The property is readable and writable, however it can +be set only before the first write operation as the HTTP headers are not yet put +on the wire. Trying to set the `chunkedEncoding` property after the first write +will throw an error. -Using chunked encoding is strongly recommended if you need to send a large request body as -data will be streamed as small chunks instead of being internally buffered -in Electron memory. +Using chunked encoding is strongly recommended if you need to send a large +request body as data will be streamed in small chunks instead of being +internally buffered inside Electron process memory. ### Instance Methods #### `request.setHeader(name, value)` -* `name` String - An extra header name. -* `value` String - An extra header value. +* `name` String - An extra HTTP header name. +* `value` String - An extra HTTP header value. -Adds an extra HTTP header. The header name will issued as it is without lowercasing. +Adds an extra HTTP header. The header name will issued as it is without +lowercasing. It can be called only before first write. Calling this method after +the first write will throw an error. #### `request.getHeader(name)` @@ -196,17 +211,20 @@ Returns String - The value of a previously set extra header name. * `name` String - Specify an extra header name. -Removes a previously set extra header name. +Removes a previously set extra header name. This method can be called only +before first write. Trying to call it after the first write will throw an error. #### `request.write(chunk[, encoding][, callback])` -* `chunk` String or Buffer - A chunk of the request body' data. If it is a string, -it is converted into a Buffer object using the specified encoding. -* `encoding` String (optional) - Used to convert string chunks into Buffer objects. Defaults to 'utf-8'. +* `chunk` String or Buffer - A chunk of the request body's data. If it is a +string, it is converted into a Buffer using the specified encoding. +* `encoding` String (optional) - Used to convert string chunks into Buffer +objects. Defaults to 'utf-8'. * `callback` Function (optional) - Called after the write operation ends. -Adds a chunk of data to the request body. Generally, the first write operation causes the request headers to be issued on the wire. -After the first write operation, it is not allowed to add or remove a custom header. +Adds a chunk of data to the request body. The first write operation may cause +the request headers to be issued on the wire. After the first write operation, +it is not allowed to add or remove a custom header. #### `request.end([chunk][, encoding][, callback])` @@ -214,21 +232,22 @@ After the first write operation, it is not allowed to add or remove a custom hea * `encoding` String (optional) * `callback` Function (optional) -Sends the last chunk of the request data. Subsequent write or end operations will not -be allowed. The finish event is emitted just after the end operation. +Sends the last chunk of the request data. Subsequent write or end operations +will not be allowed. The `finish` event is emitted just after the end operation. #### `request.abort()` -Cancels an ongoing HTTP transaction. If the request has already closed, the abort operation will have no effect. -Otherwise an ongoing event will emit abort and close events. Additionally, if there is an ongoing response object, -it will emit the aborted event. +Cancels an ongoing HTTP transaction. If the request has already emitted the +`close` event, the abort operation will have no effect. Otherwise an ongoing +event will emit `abort` and `close` events. Additionally, if there is an ongoing +response object,it will emit the `aborted` event. ## Class: IncomingMessage `IncomingMessage` represents an HTTP response message. -It is a [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams) and therefore -an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). +It is a [Readable Stream](https://nodejs.org/api/stream.html#stream_readable_streams) +and consequently an [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter). ### Instance Events @@ -238,7 +257,8 @@ Returns: * `chunk`: Buffer - A chunk of response body's data. -The data event is the usual method of transferring response data into applicative code. +The `data` event is the usual method of transferring response data into +applicative code. #### Event 'end' @@ -250,13 +270,14 @@ Emitted when a request has been canceled during an ongoing HTTP transaction. #### Event 'error' -Returns +Returns: `error` Error - Typically holds an error string identifying failure root cause. -Emitted if an error is encountered while streaming response data events. For instance, -if the server closes the underlying socket while streaming the response, an error event -will be emitted on the response object and a close event will subsequently follow in the request object. +Emitted when an error was encountered while streaming response data events. For +instance, if the server closes the underlying while the response is still +streaming, an `error` event will be emitted on the response object and a `close` +event will subsequently follow on the request object. ### Instance properties @@ -272,7 +293,8 @@ A String representing the HTTP status message. #### `response.headers` -An Object representing the response HTTP headers. The `headers` object is formatted as follows: +An Object representing the response HTTP headers. The `headers` object is +formatted as follows: * All header names are lowercased. * Each header name produces an array-valued property on the headers object. @@ -280,8 +302,10 @@ An Object representing the response HTTP headers. The `headers` object is format #### `response.httpVersion` -A String indicating the HTTP protocol version number. Typical values are '1.0' or '1.1'. Additionally `httpVersionMajor` and -`httpVersionMinor` are two Integer-valued readable properties that return respectively the HTTP major and minor version numbers. +A String indicating the HTTP protocol version number. Typical values are '1.0' +or '1.1'. Additionally `httpVersionMajor` and `httpVersionMinor` are two +Integer-valued readable properties that return respectively the HTTP major and +minor version numbers. From 8c19d3b210eec1b40d770ce8eb41b7834ebcd14e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 20 Oct 2016 13:57:08 +0200 Subject: [PATCH 54/62] Documenting net module: various fixes. --- docs/api/net.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index d99d9d562f..4cf7079adf 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -4,7 +4,7 @@ The `net` module is a client-side API for issuing HTTP(S) requests. It is similar to the [HTTP](https://nodejs.org/api/http.html) and -[HTTPS](https://nodejs.org/api/https.html) modules of Node.js but it uses +[HTTPS](https://nodejs.org/api/https.html) modules of Node.js but uses Chromium native networking library instead of the Node.js implementation offering therefore a much greater support regarding web proxies. @@ -18,7 +18,7 @@ negotiate authentication schemes. * Support for traffic monitoring proxies: Fiddler-like proxies used for access control and monitoring. -The `net` module API has been specifically designed to mimic, as much closely as +The `net` module API has been specifically designed to mimic, as closely as possible, the familiar Node.js API. The API components including classes, methods, properties and event names are similar to those commonly used in Node.js. @@ -49,6 +49,9 @@ By the way, it is almost identical to how you would normally use the [HTTP](https://nodejs.org/api/http.html)/[HTTPS](https://nodejs.org/api/https.html) modules of Node.js +The `net` API can be used only after the application emits the `ready` event. +Trying to use the module before the `ready` event will throw an error. + ## Methods The `net` module has the following methods: From 7c17aeb6da9312079342cb53fd5cf5fc988335e9 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 20 Oct 2016 14:42:15 +0200 Subject: [PATCH 55/62] Documenting net module: fixing linter issues. --- docs/api/net.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api/net.md b/docs/api/net.md index 4cf7079adf..88497bf40a 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -32,13 +32,13 @@ app.on('ready', () => { const {net} = require('electron') const request = net.request('https://github.com') request.on('response', (response) => { - console.log(`STATUS: ${response.statusCode}`); - console.log(`HEADERS: ${JSON.stringify(response.headers)}`); + console.log(`STATUS: ${response.statusCode}`) + console.log(`HEADERS: ${JSON.stringify(response.headers)}`) response.on('data', (chunk) => { console.log(`BODY: ${chunk}`) }) response.on('end', () => { - console.log('No more data in response.'); + console.log('No more data in response.') }) }) request.end() From 0c44d19249101d913470c1f2612a203c780ed41c Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Thu, 20 Oct 2016 15:48:36 +0200 Subject: [PATCH 56/62] net module: documenting the session property. --- docs/api/net.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api/net.md b/docs/api/net.md index 88497bf40a..0fab893c56 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -82,6 +82,8 @@ following properties: method. * `url` String (optional) - The request URL. Must be provided in the absolute form with the protocol scheme specified as http or https. + * `session` String (optional) - The name of the [`Session`](session.md) +instance with which the request is associated. Defaults to the empty string. * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. Currently supported values are 'http:' or 'https:'. Defaults to 'http:'. * `host` String (optional) - The server host provided as a concatenation of From 61278f9aceaca9454c7aaa1d4d78222f17f64de7 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 25 Oct 2016 12:41:01 +0200 Subject: [PATCH 57/62] Fixing code review issues. --- atom/browser/api/atom_api_net.cc | 11 +-- atom/browser/api/atom_api_net.h | 3 +- atom/browser/api/atom_api_url_request.cc | 75 +++++++++++++------ atom/browser/api/atom_api_url_request.h | 52 +++---------- atom/browser/api/atom_api_web_contents.cc | 4 +- atom/browser/net/atom_url_request.cc | 2 +- atom/browser/net/atom_url_request.h | 2 +- atom/common/api/atom_api_v8_util.cc | 7 ++ .../native_mate_converters/net_converter.cc | 5 +- .../native_mate_converters/net_converter.h | 9 +-- docs/api/net.md | 12 ++- filenames.gypi | 4 +- lib/browser/api/net.js | 23 +++--- spec/api-net-spec.js | 9 ++- 14 files changed, 106 insertions(+), 112 deletions(-) diff --git a/atom/browser/api/atom_api_net.cc b/atom/browser/api/atom_api_net.cc index dac8d9ee14..24008ed7ae 100644 --- a/atom/browser/api/atom_api_net.cc +++ b/atom/browser/api/atom_api_net.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. @@ -27,20 +27,13 @@ void Net::BuildPrototype(v8::Isolate* isolate, v8::Local prototype) { prototype->SetClassName(mate::StringToV8(isolate, "Net")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) - .SetProperty("URLRequest", &Net::URLRequest) - .SetMethod("_RequestGarbageCollectionForTesting", - &Net::RequestGarbageCollectionForTesting); + .SetProperty("URLRequest", &Net::URLRequest); } v8::Local Net::URLRequest(v8::Isolate* isolate) { return URLRequest::GetConstructor(isolate)->GetFunction(); } -void Net::RequestGarbageCollectionForTesting() { - isolate()->RequestGarbageCollectionForTesting( - v8::Isolate::GarbageCollectionType::kFullGarbageCollection); -} - } // namespace api } // namespace atom diff --git a/atom/browser/api/atom_api_net.h b/atom/browser/api/atom_api_net.h index 0b86931978..2a0fa4140c 100644 --- a/atom/browser/api/atom_api_net.h +++ b/atom/browser/api/atom_api_net.h @@ -1,4 +1,4 @@ -// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. @@ -25,7 +25,6 @@ class Net : public mate::EventEmitter { ~Net() override; private: - void RequestGarbageCollectionForTesting(); DISALLOW_COPY_AND_ASSIGN(Net); }; diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 42f08c8f05..fc5c053648 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. @@ -42,22 +42,53 @@ struct Converter> { return false; } - auto io_buffer = new net::IOBufferWithSize(size); - if (!io_buffer) { - // Assuming allocation failed. - return false; - } - + *out = new net::IOBufferWithSize(size); // We do a deep copy. We could have used Buffer's internal memory // but that is much more complicated to be properly handled. - memcpy(io_buffer->data(), data, size); - *out = io_buffer; + memcpy((*out)->data(), data, size); return true; } }; } // namespace mate +namespace { + +template +std::array, sizeof...(ArgTypes)> BuildArgsArray( + v8::Isolate* isolate, + ArgTypes... args) { + std::array, sizeof...(ArgTypes)> result = { + {mate::ConvertToV8(isolate, args)...}}; + return result; +} + +template +void EmitRequestEvent(v8::Isolate* isolate, + v8::Local object, + ArgTypes... args) { + v8::HandleScope handle_scope(isolate); + auto arguments = BuildArgsArray(isolate, args...); + v8::Local _emitRequestEvent; + if (mate::Dictionary(isolate, object) + .Get("_emitRequestEvent", &_emitRequestEvent)) + _emitRequestEvent->Call(object, arguments.size(), arguments.data()); +} + +template +void EmitResponseEvent(v8::Isolate* isolate, + v8::Local object, + ArgTypes... args) { + v8::HandleScope handle_scope(isolate); + auto arguments = BuildArgsArray(isolate, args...); + v8::Local _emitResponseEvent; + if (mate::Dictionary(isolate, object) + .Get("_emitResponseEvent", &_emitResponseEvent)) + _emitResponseEvent->Call(object, arguments.size(), arguments.data()); +} + +} // namespace + namespace atom { namespace api { @@ -210,12 +241,12 @@ bool URLRequest::Write(scoped_refptr buffer, if (request_state_.NotStarted()) { request_state_.SetFlag(RequestStateFlags::kStarted); // Pin on first write. - pin(); + Pin(); } if (is_last) { request_state_.SetFlag(RequestStateFlags::kFinished); - EmitRequestEvent(true, "finish"); + EmitRequestEvent(isolate(), GetWrapper(), true, "finish"); } DCHECK(atom_request_); @@ -239,10 +270,10 @@ void URLRequest::Cancel() { // Really cancel if it was started. atom_request_->Cancel(); } - EmitRequestEvent(true, "abort"); + EmitRequestEvent(isolate(), GetWrapper(), true, "abort"); if (response_state_.Started() && !response_state_.Ended()) { - EmitResponseEvent(true, "aborted"); + EmitResponseEvent(isolate(), GetWrapper(), true, "aborted"); } Close(); } @@ -351,10 +382,10 @@ void URLRequest::OnError(const std::string& error, bool isRequestError) { auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); if (isRequestError) { request_state_.SetFlag(RequestStateFlags::kFailed); - EmitRequestEvent(false, "error", error_object); + EmitRequestEvent(isolate(), GetWrapper(), false, "error", error_object); } else { response_state_.SetFlag(ResponseStateFlags::kFailed); - EmitResponseEvent(false, "error", error_object); + EmitResponseEvent(isolate(), GetWrapper(), false, "error", error_object); } Close(); } @@ -374,8 +405,8 @@ std::string URLRequest::StatusMessage() const { return result; } -scoped_refptr URLRequest::RawResponseHeaders() const { - return response_headers_; +net::HttpResponseHeaders* URLRequest::RawResponseHeaders() const { + return response_headers_.get(); } uint32_t URLRequest::ResponseHttpVersionMajor() const { @@ -397,11 +428,11 @@ void URLRequest::Close() { request_state_.SetFlag(RequestStateFlags::kClosed); if (response_state_.Started()) { // Emit a close event if we really have a response object. - EmitResponseEvent(true, "close"); + EmitResponseEvent(isolate(), GetWrapper(), true, "close"); } - EmitRequestEvent(true, "close"); + EmitRequestEvent(isolate(), GetWrapper(), true, "close"); } - unpin(); + Unpin(); if (atom_request_) { // A request has been created in JS, used and then it ended. // We release unneeded net resources. @@ -410,13 +441,13 @@ void URLRequest::Close() { atom_request_ = nullptr; } -void URLRequest::pin() { +void URLRequest::Pin() { if (wrapper_.IsEmpty()) { wrapper_.Reset(isolate(), GetWrapper()); } } -void URLRequest::unpin() { +void URLRequest::Unpin() { wrapper_.Reset(); } diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index f59f8bc72f..935b9df3af 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -1,4 +1,4 @@ -// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. @@ -175,23 +175,23 @@ class URLRequest : public mate::EventEmitter { int StatusCode() const; std::string StatusMessage() const; - scoped_refptr RawResponseHeaders() const; + net::HttpResponseHeaders* RawResponseHeaders() const; uint32_t ResponseHttpVersionMajor() const; uint32_t ResponseHttpVersionMinor() const; - template - std::array, sizeof...(ArgTypes)> BuildArgsArray( - ArgTypes... args) const; + // template + // std::array, sizeof...(ArgTypes)> BuildArgsArray( + // ArgTypes... args) const; - template - void EmitRequestEvent(ArgTypes... args); + // template + // void EmitRequestEvent(ArgTypes... args); - template - void EmitResponseEvent(ArgTypes... args); + // template + // void EmitResponseEvent(ArgTypes... args); void Close(); - void pin(); - void unpin(); + void Pin(); + void Unpin(); scoped_refptr atom_request_; RequestState request_state_; @@ -205,36 +205,6 @@ class URLRequest : public mate::EventEmitter { DISALLOW_COPY_AND_ASSIGN(URLRequest); }; -template -std::array, sizeof...(ArgTypes)> -URLRequest::BuildArgsArray(ArgTypes... args) const { - std::array, sizeof...(ArgTypes)> result = { - {mate::ConvertToV8(isolate(), args)...}}; - return result; -} - -template -void URLRequest::EmitRequestEvent(ArgTypes... args) { - v8::HandleScope handle_scope(isolate()); - auto arguments = BuildArgsArray(args...); - v8::Local _emitRequestEvent; - auto wrapper = GetWrapper(); - if (mate::Dictionary(isolate(), wrapper) - .Get("_emitRequestEvent", &_emitRequestEvent)) - _emitRequestEvent->Call(wrapper, arguments.size(), arguments.data()); -} - -template -void URLRequest::EmitResponseEvent(ArgTypes... args) { - v8::HandleScope handle_scope(isolate()); - auto arguments = BuildArgsArray(args...); - v8::Local _emitResponseEvent; - auto wrapper = GetWrapper(); - if (mate::Dictionary(isolate(), wrapper) - .Get("_emitResponseEvent", &_emitResponseEvent)) - _emitResponseEvent->Call(wrapper, arguments.size(), arguments.data()); -} - } // namespace api } // namespace atom diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index bd7de5a22a..a1dc5203b6 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -664,7 +664,7 @@ void WebContents::DidGetResourceResponseStart( details.http_response_code, details.method, details.referrer, - details.headers, + details.headers.get(), ResourceTypeToString(details.resource_type)); } @@ -678,7 +678,7 @@ void WebContents::DidGetRedirectForResourceRequest( details.http_response_code, details.method, details.referrer, - details.headers); + details.headers.get()); } void WebContents::DidFinishNavigation( diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 576036819f..5d46beac80 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 3efccae50f..27cdbb5b3c 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -1,4 +1,4 @@ -// Copyright (c) 2013 GitHub, Inc. +// Copyright (c) 2016 GitHub, Inc. // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. diff --git a/atom/common/api/atom_api_v8_util.cc b/atom/common/api/atom_api_v8_util.cc index ddacbb0808..a587cd772a 100644 --- a/atom/common/api/atom_api_v8_util.cc +++ b/atom/common/api/atom_api_v8_util.cc @@ -92,6 +92,11 @@ void TakeHeapSnapshot(v8::Isolate* isolate) { isolate->GetHeapProfiler()->TakeHeapSnapshot(); } +void RequestGarbageCollectionForTesting(v8::Isolate* isolate) { + isolate->RequestGarbageCollectionForTesting( + v8::Isolate::GarbageCollectionType::kFullGarbageCollection); +} + void Initialize(v8::Local exports, v8::Local unused, v8::Local context, void* priv) { mate::Dictionary dict(context->GetIsolate(), exports); @@ -105,6 +110,8 @@ void Initialize(v8::Local exports, v8::Local unused, dict.SetMethod("createIDWeakMap", &atom::api::KeyWeakMap::Create); dict.SetMethod("createDoubleIDWeakMap", &atom::api::KeyWeakMap>::Create); + dict.SetMethod("requestGarbageCollectionForTesting", + &RequestGarbageCollectionForTesting); } } // namespace diff --git a/atom/common/native_mate_converters/net_converter.cc b/atom/common/native_mate_converters/net_converter.cc index 89120c94ab..00a06566a2 100644 --- a/atom/common/native_mate_converters/net_converter.cc +++ b/atom/common/native_mate_converters/net_converter.cc @@ -60,10 +60,9 @@ v8::Local Converter>::ToV8( } // static -v8::Local -Converter>::ToV8( +v8::Local Converter::ToV8( v8::Isolate* isolate, - scoped_refptr headers) { + net::HttpResponseHeaders* headers) { base::DictionaryValue response_headers; if (headers) { size_t iter = 0; diff --git a/atom/common/native_mate_converters/net_converter.h b/atom/common/native_mate_converters/net_converter.h index e3ba6b60cc..16013e34f9 100644 --- a/atom/common/native_mate_converters/net_converter.h +++ b/atom/common/native_mate_converters/net_converter.h @@ -34,11 +34,10 @@ struct Converter> { const scoped_refptr& val); }; -template<> -struct Converter> { - static v8::Local ToV8( - v8::Isolate* isolate, - scoped_refptr headers); +template <> +struct Converter { + static v8::Local ToV8(v8::Isolate* isolate, + net::HttpResponseHeaders* headers); }; } // namespace mate diff --git a/docs/api/net.md b/docs/api/net.md index 0fab893c56..cf7a456e5a 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -226,6 +226,11 @@ string, it is converted into a Buffer using the specified encoding. * `encoding` String (optional) - Used to convert string chunks into Buffer objects. Defaults to 'utf-8'. * `callback` Function (optional) - Called after the write operation ends. +`callback` is essentially a dummy function introduced in the purpose of keeping +similarity with the Node.js API. It is called asynchronously in the next tick +after `chunk` content have been delivered to the Chromium networking layer. +Contrary to the Node.js implementation, it is not guaranteed that `chunk` +content have been flushed on the wire before `callback` is called. Adds a chunk of data to the request body. The first write operation may cause the request headers to be issued on the wire. After the first write operation, @@ -311,10 +316,3 @@ A String indicating the HTTP protocol version number. Typical values are '1.0' or '1.1'. Additionally `httpVersionMajor` and `httpVersionMinor` are two Integer-valued readable properties that return respectively the HTTP major and minor version numbers. - - - - - - - diff --git a/filenames.gypi b/filenames.gypi index 20375c6f4e..1834a9949e 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -23,7 +23,7 @@ 'lib/browser/api/menu-item.js', 'lib/browser/api/menu-item-roles.js', 'lib/browser/api/navigation-controller.js', - 'lib/browser/api/net.js', + 'lib/browser/api/net.js', 'lib/browser/api/power-monitor.js', 'lib/browser/api/power-save-blocker.js', 'lib/browser/api/protocol.js', @@ -238,7 +238,7 @@ 'atom/browser/net/atom_network_delegate.h', 'atom/browser/net/atom_ssl_config_service.cc', 'atom/browser/net/atom_ssl_config_service.h', - 'atom/browser/net/atom_url_request.cc', + 'atom/browser/net/atom_url_request.cc', 'atom/browser/net/atom_url_request.h', 'atom/browser/net/atom_url_request_job_factory.cc', 'atom/browser/net/atom_url_request_job_factory.h', diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 7fb2c61495..1cb285184a 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -3,18 +3,15 @@ const url = require('url') const {EventEmitter} = require('events') const util = require('util') -const Readable = require('stream').Readable -const binding = process.atomBinding('net') -const {net, Net} = binding -const {URLRequest} = net +const {Readable} = require('stream') const {app} = require('electron') +const {net, Net} = process.atomBinding('net') +const {URLRequest} = net Object.setPrototypeOf(Net.prototype, EventEmitter.prototype) Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype) -let kSupportedProtocols = new Set() -kSupportedProtocols.add('http:') -kSupportedProtocols.add('https:') +const kSupportedProtocols = new Set(['http:', 'https:']) class IncomingMessage extends Readable { constructor (urlRequest) { @@ -82,8 +79,8 @@ class IncomingMessage extends Readable { } -URLRequest.prototype._emitRequestEvent = function (async, ...rest) { - if (async) { +URLRequest.prototype._emitRequestEvent = function (isAsync, ...rest) { + if (isAsync) { process.nextTick(() => { this.clientRequest.emit.apply(this.clientRequest, rest) }) @@ -92,8 +89,8 @@ URLRequest.prototype._emitRequestEvent = function (async, ...rest) { } } -URLRequest.prototype._emitResponseEvent = function (async, ...rest) { - if (async) { +URLRequest.prototype._emitResponseEvent = function (isAsync, ...rest) { + if (isAsync) { process.nextTick(() => { this._response.emit.apply(this._response, rest) }) @@ -175,9 +172,7 @@ class ClientRequest extends EventEmitter { this.extraHeaders = {} if (options.headers) { - const keys = Object.keys(options.headers) - for (let i = 0, l = keys.length; i < l; i++) { - const key = keys[i] + for (let key in options.headers) { this.setHeader(key, options.headers[key]) } } diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index fa73d7722c..08ec337ecb 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -1179,7 +1179,8 @@ describe('net module', function () { const {net} = require('electron') const urlRequest = net.request('${server.url}${requestUrl}') process.nextTick(function () { - net._RequestGarbageCollectionForTesting() + const v8Util = process.atomBinding('v8_util') + v8Util.requestGarbageCollectionForTesting() event.sender.send('api-net-spec-done') }) `) @@ -1217,7 +1218,8 @@ describe('net module', function () { }) process.nextTick(function () { // Trigger a garbage collection. - net._RequestGarbageCollectionForTesting() + const v8Util = process.atomBinding('v8_util') + v8Util.requestGarbageCollectionForTesting() event.sender.send('api-net-spec-resume') }) }) @@ -1251,7 +1253,8 @@ describe('net module', function () { }) urlRequest.on('close', function () { process.nextTick(function () { - net._RequestGarbageCollectionForTesting() + const v8Util = process.atomBinding('v8_util') + v8Util.requestGarbageCollectionForTesting() event.sender.send('api-net-spec-done') }) }) From 6d7f179a9bbb73fa7189599e42fb8e3144428d4a Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 25 Oct 2016 15:02:35 +0200 Subject: [PATCH 58/62] Fixing code review issues: making delegate_ a raw pointer. --- atom/browser/api/atom_api_url_request.cc | 6 ++---- atom/browser/api/atom_api_url_request.h | 1 - atom/browser/net/atom_url_request.cc | 5 +++-- atom/browser/net/atom_url_request.h | 6 +++--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index fc5c053648..60d163fe88 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -158,8 +158,7 @@ bool URLRequest::ResponseState::Failed() const { return IsFlagSet(ResponseStateFlags::kFailed); } -URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) - : weak_ptr_factory_(this) { +URLRequest::URLRequest(v8::Isolate* isolate, v8::Local wrapper) { InitWith(isolate, wrapper); } @@ -188,9 +187,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) { auto browser_context = session->browser_context(); auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); - auto weak_ptr = api_url_request->weak_ptr_factory_.GetWeakPtr(); auto atom_url_request = - AtomURLRequest::Create(browser_context, method, url, weak_ptr); + AtomURLRequest::Create(browser_context, method, url, api_url_request); api_url_request->atom_request_ = atom_url_request; diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 935b9df3af..83873f977b 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -200,7 +200,6 @@ class URLRequest : public mate::EventEmitter { // Used to implement pin/unpin. v8::Global wrapper_; scoped_refptr response_headers_; - base::WeakPtrFactory weak_ptr_factory_; DISALLOW_COPY_AND_ASSIGN(URLRequest); }; diff --git a/atom/browser/net/atom_url_request.cc b/atom/browser/net/atom_url_request.cc index 5d46beac80..628d316164 100644 --- a/atom/browser/net/atom_url_request.cc +++ b/atom/browser/net/atom_url_request.cc @@ -43,7 +43,7 @@ class UploadOwnedIOBufferElementReader : public net::UploadBytesElementReader { } // namespace internal -AtomURLRequest::AtomURLRequest(base::WeakPtr delegate) +AtomURLRequest::AtomURLRequest(api::URLRequest* delegate) : delegate_(delegate), is_chunked_upload_(false), response_read_buffer_(new net::IOBuffer(kBufferSize)) {} @@ -57,7 +57,7 @@ scoped_refptr AtomURLRequest::Create( AtomBrowserContext* browser_context, const std::string& method, const std::string& url, - base::WeakPtr delegate) { + api::URLRequest* delegate) { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK(browser_context); @@ -83,6 +83,7 @@ scoped_refptr AtomURLRequest::Create( void AtomURLRequest::Terminate() { DCHECK_CURRENTLY_ON(content::BrowserThread::UI); + delegate_ = nullptr; content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AtomURLRequest::DoTerminate, this)); diff --git a/atom/browser/net/atom_url_request.h b/atom/browser/net/atom_url_request.h index 27cdbb5b3c..d0b367e2d3 100644 --- a/atom/browser/net/atom_url_request.h +++ b/atom/browser/net/atom_url_request.h @@ -30,7 +30,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, AtomBrowserContext* browser_context, const std::string& method, const std::string& url, - base::WeakPtr delegate); + api::URLRequest* delegate); void Terminate(); bool Write(scoped_refptr buffer, bool is_last); @@ -54,7 +54,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, private: friend class base::RefCountedThreadSafe; - explicit AtomURLRequest(base::WeakPtr delegate); + explicit AtomURLRequest(api::URLRequest* delegate); ~AtomURLRequest() override; void DoInitialize(scoped_refptr, @@ -85,7 +85,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe, void InformDelegateErrorOccured(const std::string& error, bool isRequestError) const; - base::WeakPtr delegate_; + api::URLRequest* delegate_; std::unique_ptr request_; scoped_refptr request_context_getter_; From b44d5290e28760bce86a6d49ccc11fa847700651 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 25 Oct 2016 15:47:54 +0200 Subject: [PATCH 59/62] Fixing code review issues: adding a partition options and making the session option only takes Session objects. --- atom/browser/api/atom_api_url_request.cc | 17 +++++++++++------ docs/api/net.md | 8 ++++++-- lib/browser/api/net.js | 24 +++++++++++++++++++----- spec/api-net-spec.js | 8 ++++---- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 60d163fe88..cde0eecd58 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -175,16 +175,21 @@ URLRequest::~URLRequest() { mate::WrappableBase* URLRequest::New(mate::Arguments* args) { v8::Local options; args->GetNext(&options); - mate::Dictionary dict(args->isolate(), options); + auto isolate = args->isolate(); + mate::Dictionary dict(isolate, options); std::string method; dict.Get("method", &method); std::string url; dict.Get("url", &url); - std::string session_name; - dict.Get("session", &session_name); - - auto session = Session::FromPartition(args->isolate(), session_name); - + std::string partition; + mate::Handle session; + if (dict.Get("session", &session)) { + } else if (dict.Get("partition", &partition)) { + session = Session::FromPartition(isolate, partition); + } else { + // Use the default session if not specified. + session = Session::FromPartition(isolate, ""); + } auto browser_context = session->browser_context(); auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); auto atom_url_request = diff --git a/docs/api/net.md b/docs/api/net.md index cf7a456e5a..d14fe3dc02 100644 --- a/docs/api/net.md +++ b/docs/api/net.md @@ -82,8 +82,12 @@ following properties: method. * `url` String (optional) - The request URL. Must be provided in the absolute form with the protocol scheme specified as http or https. - * `session` String (optional) - The name of the [`Session`](session.md) -instance with which the request is associated. Defaults to the empty string. + * `session` Object (optional) - The [`Session`](session.md) instance with +which the request is associated. + * `partition` String (optional) - The name of the [`partition`](session.md) + with which the request is associated. Defaults to the empty string. The +`session` option prevails on `partition`. Thus if a `session` is explicitly +specified, `partition` is ignored. * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. Currently supported values are 'http:' or 'https:'. Defaults to 'http:'. * `host` String (optional) - The server host provided as a concatenation of diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 1cb285184a..3bf43b8715 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -5,6 +5,7 @@ const {EventEmitter} = require('events') const util = require('util') const {Readable} = require('stream') const {app} = require('electron') +const {session} = require('electron') const {net, Net} = process.atomBinding('net') const {URLRequest} = net @@ -155,12 +156,25 @@ class ClientRequest extends EventEmitter { urlStr = url.format(urlObj) } - const sessionName = options.session || '' - let urlRequest = new URLRequest({ + let urlRequestOptions = { method: method, - url: urlStr, - session: sessionName - }) + url: urlStr + } + if (options.session) { + if (options.session instanceof session.Session) { + urlRequestOptions.session = options.session + } else { + 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.') + } + } + + let urlRequest = new URLRequest(urlRequestOptions) // Set back and forward links. this.urlRequest = urlRequest diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index 08ec337ecb..a6315dc84d 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -797,10 +797,10 @@ describe('net module', function () { urlRequest.end() }) - it('should to able to create and intercept a request on a custom session', function (done) { + it('should to able to create and intercept a request using a custom parition name', function (done) { const requestUrl = '/requestUrl' const redirectUrl = '/redirectUrl' - const customSessionName = 'custom-session' + const customPartitionName = 'custom-partition' let requestIsRedirected = false server.on('request', function (request, response) { switch (request.url) { @@ -821,7 +821,7 @@ describe('net module', function () { assert(false, 'Request should not be intercepted by the default session') }) - let customSession = session.fromPartition(customSessionName, { + let customSession = session.fromPartition(customPartitionName, { cache: false }) let requestIsIntercepted = false @@ -841,7 +841,7 @@ describe('net module', function () { const urlRequest = net.request({ url: `${server.url}${requestUrl}`, - session: customSessionName + partition: customPartitionName }) urlRequest.on('response', function (response) { assert.equal(response.statusCode, 200) From bdb3f4d4cbd7243fbd3ff69458ebf0c2dd5f6cc1 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 25 Oct 2016 16:19:26 +0200 Subject: [PATCH 60/62] Fixing code review issues: adding some test cases for partition/session options. --- atom/browser/api/atom_api_url_request.cc | 2 +- atom/browser/api/atom_api_url_request.h | 17 ++--- lib/browser/api/net.js | 4 +- spec/api-net-spec.js | 85 +++++++++++++++++++++++- 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index cde0eecd58..465ee3e53c 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -173,9 +173,9 @@ URLRequest::~URLRequest() { // static mate::WrappableBase* URLRequest::New(mate::Arguments* args) { + auto isolate = args->isolate(); v8::Local options; args->GetNext(&options); - auto isolate = args->isolate(); mate::Dictionary dict(isolate, options); std::string method; dict.Get("method", &method); diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 83873f977b..8b2c327828 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -86,9 +86,10 @@ namespace api { // more complex. // // We chose to split the implementation into two classes linked via a -// strong/weak pointers. A URLRequest instance is deleted if it is unpinned and -// the corresponding JS wrapper object is garbage collected. On the other hand, -// an AtmURLRequest instance lifetime is totally governed by reference counting. +// reference counted/raw pointers. A URLRequest instance is deleted if it is +// unpinned and the corresponding JS wrapper object is garbage collected. On the +// other hand, an AtmURLRequest instance lifetime is totally governed by +// reference counting. // class URLRequest : public mate::EventEmitter { public: @@ -179,16 +180,6 @@ class URLRequest : public mate::EventEmitter { uint32_t ResponseHttpVersionMajor() const; uint32_t ResponseHttpVersionMinor() const; - // template - // std::array, sizeof...(ArgTypes)> BuildArgsArray( - // ArgTypes... args) const; - - // template - // void EmitRequestEvent(ArgTypes... args); - - // template - // void EmitResponseEvent(ArgTypes... args); - void Close(); void Pin(); void Unpin(); diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 3bf43b8715..e8b6f2a473 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -5,7 +5,7 @@ const {EventEmitter} = require('events') const util = require('util') const {Readable} = require('stream') const {app} = require('electron') -const {session} = require('electron') +const {Session} = process.atomBinding('session') const {net, Net} = process.atomBinding('net') const {URLRequest} = net @@ -161,7 +161,7 @@ class ClientRequest extends EventEmitter { url: urlStr } if (options.session) { - if (options.session instanceof session.Session) { + if (options.session instanceof Session) { urlRequestOptions.session = options.session } else { throw new TypeError('`session` should be an instance of the Session class.') diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index a6315dc84d..dca649f64e 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -797,7 +797,79 @@ describe('net module', function () { urlRequest.end() }) - it('should to able to create and intercept a request using a custom parition name', function (done) { + it('should to able to create and intercept a request using a custom session object', function (done) { + const requestUrl = '/requestUrl' + const redirectUrl = '/redirectUrl' + const customPartitionName = 'custom-partition' + let requestIsRedirected = false + server.on('request', function (request, response) { + switch (request.url) { + case requestUrl: + assert(false) + break + case redirectUrl: + requestIsRedirected = 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 customSession = session.fromPartition(customPartitionName, { + cache: false + }) + let requestIsIntercepted = false + customSession.webRequest.onBeforeRequest( + function (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}`, + session: customSession + }) + urlRequest.on('response', function (response) { + assert.equal(response.statusCode, 200) + response.pause() + response.on('data', function (chunk) { + }) + response.on('end', function () { + assert(requestIsRedirected, 'The server should receive a request to the forward URL') + assert(requestIsIntercepted, 'The request should be intercepted by the webRequest module') + done() + }) + response.resume() + }) + urlRequest.end() + }) + + it('should throw if given an invalid session option', function (done) { + try { + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + session: 1 + }) + } catch (exception) { + done() + } + }) + + it('should to able to create and intercept a request using a custom partition name', function (done) { const requestUrl = '/requestUrl' const redirectUrl = '/redirectUrl' const customPartitionName = 'custom-partition' @@ -858,6 +930,17 @@ describe('net module', function () { urlRequest.end() }) + it('should throw if given an invalid partition option', function (done) { + try { + const urlRequest = net.request({ + url: `${server.url}${requestUrl}`, + partition: 1 + }) + } catch (exception) { + done() + } + }) + it('should be able to create a request with options', function (done) { const requestUrl = '/' const customHeaderName = 'Some-Custom-Header-Name' From e9db926b486135ae79b822a858dd65a949464c96 Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Tue, 25 Oct 2016 16:55:17 +0200 Subject: [PATCH 61/62] Fixing code review issues: fixing linter issues in spec file. --- spec/api-net-spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/api-net-spec.js b/spec/api-net-spec.js index dca649f64e..9ddc11986c 100644 --- a/spec/api-net-spec.js +++ b/spec/api-net-spec.js @@ -859,11 +859,13 @@ describe('net module', function () { }) it('should throw if given an invalid session option', function (done) { + const requestUrl = '/requestUrl' try { const urlRequest = net.request({ url: `${server.url}${requestUrl}`, session: 1 }) + urlRequest } catch (exception) { done() } @@ -931,11 +933,13 @@ describe('net module', function () { }) it('should throw if given an invalid partition option', function (done) { + const requestUrl = '/requestUrl' try { const urlRequest = net.request({ url: `${server.url}${requestUrl}`, partition: 1 }) + urlRequest } catch (exception) { done() } From 6d92457095c9b45a169296aa8be31388158948ba Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 26 Oct 2016 11:10:15 +0200 Subject: [PATCH 62/62] Fixing code review issues: refactoring emit methods using CustomEmit. --- atom/browser/api/atom_api_url_request.cc | 64 ++++++++---------------- atom/browser/api/atom_api_url_request.h | 4 ++ atom/common/api/event_emitter_caller.cc | 14 +++--- atom/common/api/event_emitter_caller.h | 31 ++++++++---- 4 files changed, 53 insertions(+), 60 deletions(-) diff --git a/atom/browser/api/atom_api_url_request.cc b/atom/browser/api/atom_api_url_request.cc index 465ee3e53c..fe60bbb128 100644 --- a/atom/browser/api/atom_api_url_request.cc +++ b/atom/browser/api/atom_api_url_request.cc @@ -6,6 +6,7 @@ #include #include "atom/browser/api/atom_api_session.h" #include "atom/browser/net/atom_url_request.h" +#include "atom/common/api/event_emitter_caller.h" #include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/string16_converter.h" @@ -52,43 +53,6 @@ struct Converter> { } // namespace mate -namespace { - -template -std::array, sizeof...(ArgTypes)> BuildArgsArray( - v8::Isolate* isolate, - ArgTypes... args) { - std::array, sizeof...(ArgTypes)> result = { - {mate::ConvertToV8(isolate, args)...}}; - return result; -} - -template -void EmitRequestEvent(v8::Isolate* isolate, - v8::Local object, - ArgTypes... args) { - v8::HandleScope handle_scope(isolate); - auto arguments = BuildArgsArray(isolate, args...); - v8::Local _emitRequestEvent; - if (mate::Dictionary(isolate, object) - .Get("_emitRequestEvent", &_emitRequestEvent)) - _emitRequestEvent->Call(object, arguments.size(), arguments.data()); -} - -template -void EmitResponseEvent(v8::Isolate* isolate, - v8::Local object, - ArgTypes... args) { - v8::HandleScope handle_scope(isolate); - auto arguments = BuildArgsArray(isolate, args...); - v8::Local _emitResponseEvent; - if (mate::Dictionary(isolate, object) - .Get("_emitResponseEvent", &_emitResponseEvent)) - _emitResponseEvent->Call(object, arguments.size(), arguments.data()); -} - -} // namespace - namespace atom { namespace api { @@ -249,7 +213,7 @@ bool URLRequest::Write(scoped_refptr buffer, if (is_last) { request_state_.SetFlag(RequestStateFlags::kFinished); - EmitRequestEvent(isolate(), GetWrapper(), true, "finish"); + EmitRequestEvent(true, "finish"); } DCHECK(atom_request_); @@ -273,10 +237,10 @@ void URLRequest::Cancel() { // Really cancel if it was started. atom_request_->Cancel(); } - EmitRequestEvent(isolate(), GetWrapper(), true, "abort"); + EmitRequestEvent(true, "abort"); if (response_state_.Started() && !response_state_.Ended()) { - EmitResponseEvent(isolate(), GetWrapper(), true, "aborted"); + EmitResponseEvent(true, "aborted"); } Close(); } @@ -385,10 +349,10 @@ void URLRequest::OnError(const std::string& error, bool isRequestError) { auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error)); if (isRequestError) { request_state_.SetFlag(RequestStateFlags::kFailed); - EmitRequestEvent(isolate(), GetWrapper(), false, "error", error_object); + EmitRequestEvent(false, "error", error_object); } else { response_state_.SetFlag(ResponseStateFlags::kFailed); - EmitResponseEvent(isolate(), GetWrapper(), false, "error", error_object); + EmitResponseEvent(false, "error", error_object); } Close(); } @@ -431,9 +395,9 @@ void URLRequest::Close() { request_state_.SetFlag(RequestStateFlags::kClosed); if (response_state_.Started()) { // Emit a close event if we really have a response object. - EmitResponseEvent(isolate(), GetWrapper(), true, "close"); + EmitResponseEvent(true, "close"); } - EmitRequestEvent(isolate(), GetWrapper(), true, "close"); + EmitRequestEvent(true, "close"); } Unpin(); if (atom_request_) { @@ -454,6 +418,18 @@ void URLRequest::Unpin() { wrapper_.Reset(); } +template +void URLRequest::EmitRequestEvent(Args... args) { + v8::HandleScope handle_scope(isolate()); + mate::CustomEmit(isolate(), GetWrapper(), "_emitRequestEvent", args...); +} + +template +void URLRequest::EmitResponseEvent(Args... args) { + v8::HandleScope handle_scope(isolate()); + mate::CustomEmit(isolate(), GetWrapper(), "_emitResponseEvent", args...); +} + } // namespace api } // namespace atom diff --git a/atom/browser/api/atom_api_url_request.h b/atom/browser/api/atom_api_url_request.h index 8b2c327828..3aae14bb19 100644 --- a/atom/browser/api/atom_api_url_request.h +++ b/atom/browser/api/atom_api_url_request.h @@ -183,6 +183,10 @@ class URLRequest : public mate::EventEmitter { void Close(); void Pin(); void Unpin(); + template + void EmitRequestEvent(Args... args); + template + void EmitResponseEvent(Args... args); scoped_refptr atom_request_; RequestState request_state_; diff --git a/atom/common/api/event_emitter_caller.cc b/atom/common/api/event_emitter_caller.cc index 40448cad10..3fbb31c497 100644 --- a/atom/common/api/event_emitter_caller.cc +++ b/atom/common/api/event_emitter_caller.cc @@ -11,16 +11,16 @@ namespace mate { namespace internal { -v8::Local CallEmitWithArgs(v8::Isolate* isolate, - v8::Local obj, - ValueVector* args) { +v8::Local CallMethodWithArgs(v8::Isolate* isolate, + v8::Local obj, + const char* method, + ValueVector* args) { // Perform microtask checkpoint after running JavaScript. - v8::MicrotasksScope script_scope( - isolate, v8::MicrotasksScope::kRunMicrotasks); + v8::MicrotasksScope script_scope(isolate, + v8::MicrotasksScope::kRunMicrotasks); // Use node::MakeCallback to call the callback, and it will also run pending // tasks in Node.js. - return node::MakeCallback( - isolate, obj, "emit", args->size(), &args->front()); + return node::MakeCallback(isolate, obj, method, args->size(), &args->front()); } } // namespace internal diff --git a/atom/common/api/event_emitter_caller.h b/atom/common/api/event_emitter_caller.h index a2567da9d1..24917cbef6 100644 --- a/atom/common/api/event_emitter_caller.h +++ b/atom/common/api/event_emitter_caller.h @@ -15,37 +15,50 @@ namespace internal { using ValueVector = std::vector>; -v8::Local CallEmitWithArgs(v8::Isolate* isolate, - v8::Local obj, - ValueVector* args); +v8::Local CallMethodWithArgs(v8::Isolate* isolate, + v8::Local obj, + const char* method, + ValueVector* args); } // namespace internal // obj.emit.apply(obj, name, args...); // The caller is responsible of allocating a HandleScope. -template +template v8::Local EmitEvent(v8::Isolate* isolate, v8::Local obj, const StringType& name, const internal::ValueVector& args) { - internal::ValueVector concatenated_args = { StringToV8(isolate, name) }; + internal::ValueVector concatenated_args = {StringToV8(isolate, name)}; concatenated_args.reserve(1 + args.size()); concatenated_args.insert(concatenated_args.end(), args.begin(), args.end()); - return internal::CallEmitWithArgs(isolate, obj, &concatenated_args); + return internal::CallMethodWithArgs(isolate, obj, "emit", &concatenated_args); } // obj.emit(name, args...); // The caller is responsible of allocating a HandleScope. -template +template v8::Local EmitEvent(v8::Isolate* isolate, v8::Local obj, const StringType& name, const Args&... args) { internal::ValueVector converted_args = { - StringToV8(isolate, name), + StringToV8(isolate, name), ConvertToV8(isolate, args)..., + }; + return internal::CallMethodWithArgs(isolate, obj, "emit", &converted_args); +} + +// obj.custom_emit(args...) +template +v8::Local CustomEmit(v8::Isolate* isolate, + v8::Local object, + const char* custom_emit, + const Args&... args) { + internal::ValueVector converted_args = { ConvertToV8(isolate, args)..., }; - return internal::CallEmitWithArgs(isolate, obj, &converted_args); + return internal::CallMethodWithArgs(isolate, object, custom_emit, + &converted_args); } } // namespace mate