Implementing URLRequest API, getting response body.
authorali.ibrahim <ali.ibrahim@thomsonreuters.com>
Wed, 21 Sep 2016 07:23:00 +0000 (09:23 +0200)
committerali.ibrahim <ali.ibrahim@thomsonreuters.com>
Wed, 12 Oct 2016 12:54:17 +0000 (14:54 +0200)
atom/browser/api/atom_api_menu.cc
atom/browser/api/atom_api_url_request.cc
atom/browser/api/atom_api_url_request.h
atom/browser/net/atom_url_request.cc
atom/browser/net/atom_url_request.h
lib/browser/api/net.js

index 0de2dff..627ce60 100644 (file)
@@ -191,8 +191,7 @@ using atom::api::Menu;
 void Initialize(v8::Local<v8::Object> exports, v8::Local<v8::Value> unused,
                 v8::Local<v8::Context> 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());
index f31c07c..5e71f24 100644 (file)
@@ -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<scoped_refptr<net::HttpResponseHeaders>> {
+  static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
+    scoped_refptr<net::HttpResponseHeaders> 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<scoped_refptr<net::IOBufferWithSize>> {
+  static v8::Local<v8::Value> ToV8(
+    v8::Isolate* isolate,
+    scoped_refptr<net::IOBufferWithSize> buffer) {
+      return node::Buffer::Copy(isolate, buffer->data(), buffer->size()).ToLocalChecked();
+  }
+};
+
+}
 namespace atom {
 
 namespace api {
-  
+
 URLRequest::URLRequest(v8::Isolate* isolate, 
                        v8::Local<v8::Object> 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<v8::Function> _emitResponse;
+  //v8::Local<v8::Function> _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<net::IOBufferWithSize> buffer) {
+  if (!buffer || !buffer->data() || !buffer->size()) {
+    return;
+  }
+
+  EmitResponseEvent("data", buffer);
+  //v8::Local<v8::Function> _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<v8::Function> _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<net::HttpResponseHeaders> 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() {
index 172cc51..1ab4f1c 100644 (file)
@@ -5,9 +5,12 @@
 #ifndef ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_
 #define ATOM_BROWSER_API_ATOM_API_URL_REQUEST_H_
 
+#include <array>
 #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<net::IOBufferWithSize> data);
+  void OnResponseCompleted();
 
   int StatusCode();
-  void StatusMessage();
-  void ResponseHeaders();
-  void ResponseHttpVersion();
+  std::string StatusMessage();
+  scoped_refptr<net::HttpResponseHeaders> RawResponseHeaders();
+  uint32_t ResponseHttpVersionMajor();
+  uint32_t ResponseHttpVersionMinor();
+
+
+  template <typename ... ArgTypes>
+  std::array<v8::Local<v8::Value>, sizeof...(ArgTypes)> 
+  BuildArgsArray(ArgTypes... args);
+
+  template <typename ... ArgTypes>
+  void EmitRequestEvent(ArgTypes... args);
+
+  template <typename ... ArgTypes>
+  void EmitResponseEvent(ArgTypes... args);
+
 
 
   void pin();
@@ -53,10 +69,41 @@ private:
   scoped_refptr<AtomURLRequest> atom_url_request_;
   v8::Global<v8::Object> wrapper_;
   base::WeakPtrFactory<URLRequest> weak_ptr_factory_;
+  
 
   DISALLOW_COPY_AND_ASSIGN(URLRequest);
 };
 
+template <typename ... ArgTypes>
+std::array<v8::Local<v8::Value>, sizeof...(ArgTypes)>
+URLRequest::BuildArgsArray(ArgTypes... args) {
+  std::array<v8::Local<v8::Value>, sizeof...(ArgTypes)> result
+    = { mate::ConvertToV8(isolate(), args)... };
+  return result;
+}
+
+template <typename ... ArgTypes>
+void URLRequest::EmitRequestEvent(ArgTypes... args) {
+  auto arguments = BuildArgsArray(args...);
+  v8::Local<v8::Function> _emitRequestEvent;
+  auto wrapper = GetWrapper();
+  if (mate::Dictionary(isolate(), wrapper).Get("_emitRequestEvent", &_emitRequestEvent))
+    _emitRequestEvent->Call(wrapper, arguments.size(), arguments.data());
+}
+
+
+template <typename ... ArgTypes>
+void URLRequest::EmitResponseEvent(ArgTypes... args) {
+  auto arguments = BuildArgsArray(args...);
+  v8::Local<v8::Function> _emitResponseEvent;
+  auto wrapper = GetWrapper();
+  if (mate::Dictionary(isolate(), wrapper).Get("_emitResponseEvent", &_emitResponseEvent))
+    _emitResponseEvent->Call(wrapper, arguments.size(), arguments.data());
+}
+
+
+
+
 }  // namepsace api
 
 }  // namepsace atom
index 1770d01..bdabbfc 100644 (file)
@@ -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<api::URLRequest> 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<net::HttpResponseHeaders> 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<net::IOBufferWithSize> 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<net::IOBufferWithSize> 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
index d9a4588..1aeffd5 100644 (file)
@@ -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<net::HttpResponseHeaders> GetResponseHeaders();
 
 protected:
   // Overrides of net::URLRequest::Delegate
@@ -47,13 +51,21 @@ protected:
 private:
   friend class base::RefCountedThreadSafe<AtomURLRequest>;
   void StartOnIOThread();
-  void InformDelegeteResponseStarted();
+
+  void ReadResponse();
+  bool CopyAndPostBuffer(int bytes_read);
+
+  void InformDelegateResponseStarted();
+  void InformDelegateResponseData(scoped_refptr<net::IOBufferWithSize> data);
+  void InformDelegateResponseCompleted();
 
   AtomURLRequest(base::WeakPtr<api::URLRequest> delegate);
   virtual ~AtomURLRequest();
 
   base::WeakPtr<api::URLRequest> delegate_;
   std::unique_ptr<net::URLRequest> url_request_;
+  scoped_refptr<net::IOBuffer> buffer_;
+  scoped_refptr<net::HttpResponseHeaders> response_headers_;
 
    DISALLOW_COPY_AND_ASSIGN(AtomURLRequest);
  };
index 517c13b..96c4480 100644 (file)
@@ -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