From 2d9d4af98dca29fafa0b800b5245d93ab69f3b3e Mon Sep 17 00:00:00 2001 From: "ali.ibrahim" Date: Wed, 21 Sep 2016 09:23:00 +0200 Subject: [PATCH] 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 | 128 +++++++++++++++++++++++++------ atom/browser/net/atom_url_request.h | 22 ++++-- lib/browser/api/net.js | 65 ++++++++++------ 6 files changed, 312 insertions(+), 75 deletions(-) diff --git a/atom/browser/api/atom_api_menu.cc b/atom/browser/api/atom_api_menu.cc index 0de2dff..627ce60 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 f31c07c..5e71f24 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; +} + +std::string URLRequest::StatusMessage() { + std::string result; + if (auto response_headers = atom_url_request_->GetResponseHeaders()) { + result = response_headers->GetStatusText(); + } + return result; } -void URLRequest::StatusMessage() { - return atom_url_request_->StatusMessage(); +scoped_refptr URLRequest::RawResponseHeaders() { + return atom_url_request_->GetResponseHeaders(); } -void URLRequest::ResponseHeaders() { - return atom_url_request_->ResponseHeaders(); +uint32_t URLRequest::ResponseHttpVersionMajor() { + if (auto response_headers = atom_url_request_->GetResponseHeaders()) { + return response_headers->GetHttpVersion().major_value(); + } + return 0; } -void URLRequest::ResponseHttpVersion() { - return atom_url_request_->ResponseHttpVersion(); +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 172cc51..1ab4f1c 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 1770d01..bdabbfc 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,50 +79,126 @@ void AtomURLRequest::RemoveHeader() { +scoped_refptr AtomURLRequest::GetResponseHeaders() { + return url_request_->response_headers(); +} -int AtomURLRequest::StatusCode() { - return url_request_->GetResponseCode(); -} +void AtomURLRequest::StartOnIOThread() { + // Called on content::BrowserThread::IO -void AtomURLRequest::StatusMessage() { + url_request_->Start(); } -void AtomURLRequest::ResponseHeaders() { + + +void AtomURLRequest::set_method(const std::string& method) { + url_request_->set_method(method); } -void AtomURLRequest::ResponseHttpVersion() { +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::InformDelegateResponseStarted, this)); + + ReadResponse(); } +void AtomURLRequest::ReadResponse() { + // Called on content::BrowserThread::IO -void AtomURLRequest::StartOnIOThread() { - url_request_->Start(); + // 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::set_method(const std::string& method) { - url_request_->set_method(method); +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)); + } + } -void AtomURLRequest::OnResponseStarted(net::URLRequest* request) -{ - // post to main thread - content::BrowserThread::PostTask( + +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::InformDelegeteResponseStarted, this)); + base::Bind(&AtomURLRequest::InformDelegateResponseData, this, buffer_copy)); } -void AtomURLRequest::OnReadCompleted(net::URLRequest* request, int bytes_read) -{ - // post to main thread -} -void AtomURLRequest::InformDelegeteResponseStarted() { +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 d9a4588..1aeffd5 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 517c13b..96c4480 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 -- 2.7.4