Browse Source

feat: implement net module with NetworkService (#19094)

* Stub class for NetworkService-based URLRequest

* Create SimpleURLLoader

* Implement downloading

* Implement response event

* Only write data afte previous one finished

* Use DataPipeGetter for uploading data

* Support chunked upload data

* Call size callback at last

* Simplify UploadDataPipeGetter

* Implement cancelling and closing

* Handle redirection

* Fix uploading large data

* Emit error when request fails

* Emit error for redirection error

* Simplify emitting error

* "follow" should also emit "redirect" event

* SetLoadFlags is not really used

* Implement GetUploadProgress

* Implement FollowRedirect

* Fix exception with multiple redirections

* Reduce number of EmitEvent methods

* Emit response errors

* FetchRedirectMode => RedirectMode
Cheng Zhao 5 years ago
parent
commit
2a3793485f

+ 2 - 0
filenames.gni

@@ -93,6 +93,8 @@ filenames = {
     "shell/browser/api/atom_api_tray.h",
     "shell/browser/api/atom_api_url_request.cc",
     "shell/browser/api/atom_api_url_request.h",
+    "shell/browser/api/atom_api_url_request_ns.cc",
+    "shell/browser/api/atom_api_url_request_ns.h",
     "shell/browser/api/atom_api_view.cc",
     "shell/browser/api/atom_api_view.h",
     "shell/browser/api/atom_api_web_contents.cc",

+ 18 - 3
shell/browser/api/atom_api_net.cc

@@ -3,8 +3,12 @@
 // found in the LICENSE file.
 
 #include "shell/browser/api/atom_api_net.h"
+
 #include "native_mate/dictionary.h"
+#include "services/network/public/cpp/features.h"
 #include "shell/browser/api/atom_api_url_request.h"
+#include "shell/browser/api/atom_api_url_request_ns.h"
+
 #include "shell/common/node_includes.h"
 
 namespace electron {
@@ -31,8 +35,12 @@ void Net::BuildPrototype(v8::Isolate* isolate,
 }
 
 v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
-  return URLRequest::GetConstructor(isolate)
-      ->GetFunction(isolate->GetCurrentContext())
+  v8::Local<v8::FunctionTemplate> constructor;
+  if (base::FeatureList::IsEnabled(network::features::kNetworkService))
+    constructor = URLRequestNS::GetConstructor(isolate);
+  else
+    constructor = URLRequest::GetConstructor(isolate);
+  return constructor->GetFunction(isolate->GetCurrentContext())
       .ToLocalChecked();
 }
 
@@ -44,6 +52,7 @@ namespace {
 
 using electron::api::Net;
 using electron::api::URLRequest;
+using electron::api::URLRequestNS;
 
 void Initialize(v8::Local<v8::Object> exports,
                 v8::Local<v8::Value> unused,
@@ -51,12 +60,18 @@ void Initialize(v8::Local<v8::Object> exports,
                 void* priv) {
   v8::Isolate* isolate = context->GetIsolate();
 
-  URLRequest::SetConstructor(isolate, base::BindRepeating(URLRequest::New));
+  if (base::FeatureList::IsEnabled(network::features::kNetworkService))
+    URLRequestNS::SetConstructor(isolate,
+                                 base::BindRepeating(URLRequestNS::New));
+  else
+    URLRequest::SetConstructor(isolate, base::BindRepeating(URLRequest::New));
 
   mate::Dictionary dict(isolate, exports);
   dict.Set("net", Net::Create(isolate));
   dict.Set("Net",
            Net::GetConstructor(isolate)->GetFunction(context).ToLocalChecked());
+  dict.Set("isNetworkServiceEnabled",
+           base::FeatureList::IsEnabled(network::features::kNetworkService));
 }
 
 }  // namespace

+ 1 - 2
shell/browser/api/atom_api_url_request.h

@@ -25,7 +25,6 @@ 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.
@@ -114,7 +113,7 @@ class URLRequest : public mate::EventEmitter<URLRequest> {
   mate::Dictionary GetUploadProgress(v8::Isolate* isolate);
 
  protected:
-  explicit URLRequest(v8::Isolate* isolate, v8::Local<v8::Object> wrapper);
+  URLRequest(v8::Isolate* isolate, v8::Local<v8::Object> wrapper);
   ~URLRequest() override;
 
  private:

+ 544 - 0
shell/browser/api/atom_api_url_request_ns.cc

@@ -0,0 +1,544 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#include "shell/browser/api/atom_api_url_request_ns.h"
+
+#include <utility>
+
+#include "content/public/browser/storage_partition.h"
+#include "native_mate/dictionary.h"
+#include "native_mate/object_template_builder.h"
+#include "shell/browser/api/atom_api_session.h"
+#include "shell/browser/atom_browser_context.h"
+#include "shell/common/native_mate_converters/gurl_converter.h"
+#include "shell/common/native_mate_converters/net_converter.h"
+
+#include "shell/common/node_includes.h"
+
+namespace mate {
+
+template <>
+struct Converter<network::mojom::RedirectMode> {
+  static bool FromV8(v8::Isolate* isolate,
+                     v8::Local<v8::Value> val,
+                     network::mojom::RedirectMode* out) {
+    std::string mode;
+    if (!ConvertFromV8(isolate, val, &mode))
+      return false;
+    if (mode == "follow")
+      *out = network::mojom::RedirectMode::kFollow;
+    else if (mode == "error")
+      *out = network::mojom::RedirectMode::kError;
+    else if (mode == "manual")
+      *out = network::mojom::RedirectMode::kManual;
+    else
+      return false;
+    return true;
+  }
+};
+
+}  // namespace mate
+
+namespace electron {
+
+namespace api {
+
+namespace {
+
+// Network state for request and response.
+enum State {
+  STATE_STARTED = 1 << 0,
+  STATE_FINISHED = 1 << 1,
+  STATE_CANCELED = 1 << 2,
+  STATE_FAILED = 1 << 3,
+  STATE_CLOSED = 1 << 4,
+  STATE_ERROR = STATE_CANCELED | STATE_FAILED | STATE_CLOSED,
+};
+
+// Annotation tag passed to NetworkService.
+const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
+    net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
+        semantics {
+          sender: "Electron Net module"
+          description:
+            "Issue HTTP/HTTPS requests using Chromium's native networking "
+            "library."
+          trigger: "Using the Net module"
+          data: "Anything the user wants to send."
+          destination: OTHER
+        }
+        policy {
+          cookies_allowed: YES
+          cookies_store: "user"
+          setting: "This feature cannot be disabled."
+        })");
+
+}  // namespace
+
+// Common class for streaming data.
+class UploadDataPipeGetter {
+ public:
+  explicit UploadDataPipeGetter(URLRequestNS* request) : request_(request) {}
+  virtual ~UploadDataPipeGetter() = default;
+
+  virtual void AttachToRequestBody(network::ResourceRequestBody* body) = 0;
+
+ protected:
+  void SetCallback(network::mojom::DataPipeGetter::ReadCallback callback) {
+    request_->size_callback_ = std::move(callback);
+  }
+
+  void SetPipe(mojo::ScopedDataPipeProducerHandle pipe) {
+    request_->producer_ =
+        std::make_unique<mojo::StringDataPipeProducer>(std::move(pipe));
+    request_->StartWriting();
+  }
+
+ private:
+  URLRequestNS* request_;
+
+  DISALLOW_COPY_AND_ASSIGN(UploadDataPipeGetter);
+};
+
+// Streaming multipart data to NetworkService.
+class MultipartDataPipeGetter : public UploadDataPipeGetter,
+                                public network::mojom::DataPipeGetter {
+ public:
+  explicit MultipartDataPipeGetter(URLRequestNS* request)
+      : UploadDataPipeGetter(request) {}
+  ~MultipartDataPipeGetter() override = default;
+
+  void AttachToRequestBody(network::ResourceRequestBody* body) override {
+    network::mojom::DataPipeGetterPtr data_pipe_getter;
+    binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter));
+    body->AppendDataPipe(std::move(data_pipe_getter));
+  }
+
+ private:
+  // network::mojom::DataPipeGetter:
+  void Read(mojo::ScopedDataPipeProducerHandle pipe,
+            ReadCallback callback) override {
+    SetCallback(std::move(callback));
+    SetPipe(std::move(pipe));
+  }
+
+  void Clone(network::mojom::DataPipeGetterRequest request) override {
+    binding_set_.AddBinding(this, std::move(request));
+  }
+
+  mojo::BindingSet<network::mojom::DataPipeGetter> binding_set_;
+};
+
+// Streaming chunked data to NetworkService.
+class ChunkedDataPipeGetter : public UploadDataPipeGetter,
+                              public network::mojom::ChunkedDataPipeGetter {
+ public:
+  explicit ChunkedDataPipeGetter(URLRequestNS* request)
+      : UploadDataPipeGetter(request) {}
+  ~ChunkedDataPipeGetter() override = default;
+
+  void AttachToRequestBody(network::ResourceRequestBody* body) override {
+    network::mojom::ChunkedDataPipeGetterPtr data_pipe_getter;
+    binding_set_.AddBinding(this, mojo::MakeRequest(&data_pipe_getter));
+    body->SetToChunkedDataPipe(std::move(data_pipe_getter));
+  }
+
+ private:
+  // network::mojom::ChunkedDataPipeGetter:
+  void GetSize(GetSizeCallback callback) override {
+    SetCallback(std::move(callback));
+  }
+
+  void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
+    SetPipe(std::move(pipe));
+  }
+
+  mojo::BindingSet<network::mojom::ChunkedDataPipeGetter> binding_set_;
+};
+
+URLRequestNS::URLRequestNS(mate::Arguments* args) : weak_factory_(this) {
+  request_ = std::make_unique<network::ResourceRequest>();
+  mate::Dictionary dict;
+  if (args->GetNext(&dict)) {
+    dict.Get("method", &request_->method);
+    dict.Get("url", &request_->url);
+    dict.Get("redirect", &redirect_mode_);
+    request_->redirect_mode = redirect_mode_;
+  }
+
+  std::string partition;
+  mate::Handle<api::Session> session;
+  if (!dict.Get("session", &session)) {
+    if (dict.Get("partition", &partition))
+      session = Session::FromPartition(args->isolate(), partition);
+    else  // default session
+      session = Session::FromPartition(args->isolate(), "");
+  }
+
+  auto* browser_context = session->browser_context();
+  url_loader_factory_ =
+      content::BrowserContext::GetDefaultStoragePartition(browser_context)
+          ->GetURLLoaderFactoryForBrowserProcess();
+
+  InitWith(args->isolate(), args->GetThis());
+}
+
+URLRequestNS::~URLRequestNS() {}
+
+bool URLRequestNS::NotStarted() const {
+  return request_state_ == 0;
+}
+
+bool URLRequestNS::Finished() const {
+  return request_state_ & STATE_FINISHED;
+}
+
+void URLRequestNS::Cancel() {
+  // Cancel only once.
+  if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
+    return;
+
+  // Mark as canceled.
+  request_state_ |= STATE_CANCELED;
+  EmitEvent(EventType::kRequest, true, "abort");
+
+  if ((response_state_ & STATE_STARTED) && !(response_state_ & STATE_FINISHED))
+    EmitEvent(EventType::kResponse, true, "aborted");
+
+  Close();
+}
+
+void URLRequestNS::Close() {
+  if (!(request_state_ & STATE_CLOSED)) {
+    request_state_ |= STATE_CLOSED;
+    if (response_state_ & STATE_STARTED) {
+      // Emit a close event if we really have a response object.
+      EmitEvent(EventType::kResponse, true, "close");
+    }
+    EmitEvent(EventType::kRequest, true, "close");
+  }
+  Unpin();
+  loader_.reset();
+}
+
+bool URLRequestNS::Write(v8::Local<v8::Value> data, bool is_last) {
+  if (request_state_ & (STATE_FINISHED | STATE_ERROR))
+    return false;
+
+  size_t length = node::Buffer::Length(data);
+
+  if (!loader_) {
+    // Pin on first write.
+    request_state_ = STATE_STARTED;
+    Pin();
+
+    // Create the loader.
+    network::ResourceRequest* request_ref = request_.get();
+    loader_ = network::SimpleURLLoader::Create(std::move(request_),
+                                               kTrafficAnnotation);
+    loader_->SetOnResponseStartedCallback(base::Bind(
+        &URLRequestNS::OnResponseStarted, weak_factory_.GetWeakPtr()));
+    loader_->SetOnRedirectCallback(
+        base::Bind(&URLRequestNS::OnRedirect, weak_factory_.GetWeakPtr()));
+    loader_->SetOnUploadProgressCallback(base::Bind(
+        &URLRequestNS::OnUploadProgress, weak_factory_.GetWeakPtr()));
+
+    // Create upload data pipe if we have data to write.
+    if (length > 0) {
+      request_ref->request_body = new network::ResourceRequestBody();
+      if (is_chunked_upload_)
+        data_pipe_getter_ = std::make_unique<ChunkedDataPipeGetter>(this);
+      else
+        data_pipe_getter_ = std::make_unique<MultipartDataPipeGetter>(this);
+      data_pipe_getter_->AttachToRequestBody(request_ref->request_body.get());
+    }
+
+    // Start downloading.
+    loader_->DownloadAsStream(url_loader_factory_.get(), this);
+  }
+
+  if (length > 0)
+    pending_writes_.emplace_back(node::Buffer::Data(data), length);
+
+  if (is_last) {
+    // The ElementsUploadDataStream requires the knowledge of content length
+    // before doing upload, while Node's stream does not give us any size
+    // information. So the only option left for us is to keep all the write
+    // data in memory and flush them after the write is done.
+    //
+    // While this looks frustrating, it is actually the behavior of the non-
+    // NetworkService implementation, and we are not breaking anything.
+    if (!pending_writes_.empty()) {
+      last_chunk_written_ = true;
+      StartWriting();
+    }
+
+    request_state_ |= STATE_FINISHED;
+    EmitEvent(EventType::kRequest, true, "finish");
+  }
+  return true;
+}
+
+void URLRequestNS::FollowRedirect() {
+  if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
+    return;
+  follow_redirect_ = true;
+}
+
+bool URLRequestNS::SetExtraHeader(const std::string& name,
+                                  const std::string& value) {
+  if (!request_)
+    return false;
+  if (!net::HttpUtil::IsValidHeaderName(name))
+    return false;
+  if (!net::HttpUtil::IsValidHeaderValue(value))
+    return false;
+  request_->headers.SetHeader(name, value);
+  return true;
+}
+
+void URLRequestNS::RemoveExtraHeader(const std::string& name) {
+  if (request_)
+    request_->headers.RemoveHeader(name);
+}
+
+void URLRequestNS::SetChunkedUpload(bool is_chunked_upload) {
+  if (request_)
+    is_chunked_upload_ = is_chunked_upload;
+}
+
+mate::Dictionary URLRequestNS::GetUploadProgress() {
+  mate::Dictionary progress = mate::Dictionary::CreateEmpty(isolate());
+  if (loader_) {
+    if (request_)
+      progress.Set("started", false);
+    else
+      progress.Set("started", true);
+    progress.Set("current", upload_position_);
+    progress.Set("total", upload_total_);
+    progress.Set("active", true);
+  } else {
+    progress.Set("active", false);
+  }
+  return progress;
+}
+
+int URLRequestNS::StatusCode() const {
+  if (response_headers_)
+    return response_headers_->response_code();
+  return -1;
+}
+
+std::string URLRequestNS::StatusMessage() const {
+  if (response_headers_)
+    return response_headers_->GetStatusText();
+  return "";
+}
+
+net::HttpResponseHeaders* URLRequestNS::RawResponseHeaders() const {
+  return response_headers_.get();
+}
+
+uint32_t URLRequestNS::ResponseHttpVersionMajor() const {
+  if (response_headers_)
+    return response_headers_->GetHttpVersion().major_value();
+  return 0;
+}
+
+uint32_t URLRequestNS::ResponseHttpVersionMinor() const {
+  if (response_headers_)
+    return response_headers_->GetHttpVersion().minor_value();
+  return 0;
+}
+
+void URLRequestNS::OnDataReceived(base::StringPiece data,
+                                  base::OnceClosure resume) {
+  // In case we received an unexpected event from Chromium net, don't emit any
+  // data event after request cancel/error/close.
+  if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
+    v8::HandleScope handle_scope(isolate());
+    v8::Local<v8::Value> buffer;
+    auto maybe = node::Buffer::Copy(isolate(), data.data(), data.size());
+    if (maybe.ToLocal(&buffer))
+      Emit("data", buffer);
+  }
+  std::move(resume).Run();
+}
+
+void URLRequestNS::OnRetry(base::OnceClosure start_retry) {}
+
+void URLRequestNS::OnComplete(bool success) {
+  if (success) {
+    // In case we received an unexpected event from Chromium net, don't emit any
+    // data event after request cancel/error/close.
+    if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
+      response_state_ |= STATE_FINISHED;
+      Emit("end");
+    }
+  } else {  // failed
+    // If response is started then emit response event, else emit request error.
+    //
+    // Error is only emitted when there is no previous failure. This is to align
+    // with the behavior of non-NetworkService implementation.
+    std::string error = net::ErrorToString(loader_->NetError());
+    if (response_state_ & STATE_STARTED) {
+      if (!(response_state_ & STATE_FAILED))
+        EmitError(EventType::kResponse, error);
+    } else {
+      if (!(request_state_ & STATE_FAILED))
+        EmitError(EventType::kRequest, error);
+    }
+  }
+
+  Close();
+}
+
+void URLRequestNS::OnResponseStarted(
+    const GURL& final_url,
+    const network::ResourceResponseHead& response_head) {
+  // Don't emit any event after request cancel.
+  if (request_state_ & STATE_ERROR)
+    return;
+
+  response_headers_ = response_head.headers;
+  response_state_ |= STATE_STARTED;
+  Emit("response");
+}
+
+void URLRequestNS::OnRedirect(
+    const net::RedirectInfo& redirect_info,
+    const network::ResourceResponseHead& response_head,
+    std::vector<std::string>* to_be_removed_headers) {
+  if (!loader_)
+    return;
+
+  if (request_state_ & (STATE_CLOSED | STATE_CANCELED)) {
+    NOTREACHED();
+    Cancel();
+    return;
+  }
+
+  switch (redirect_mode_) {
+    case network::mojom::RedirectMode::kError:
+      EmitError(
+          EventType::kRequest,
+          "Request cannot follow redirect with the current redirect mode");
+      break;
+    case network::mojom::RedirectMode::kManual:
+      // When redirect mode is "manual", the user has to explicitly call the
+      // FollowRedirect method to continue redirecting, otherwise the request
+      // would be cancelled.
+      //
+      // Note that the SimpleURLLoader always calls FollowRedirect and does not
+      // provide a formal way for us to cancel redirection, we have to cancel
+      // the request to prevent the redirection.
+      follow_redirect_ = false;
+      EmitEvent(EventType::kRequest, false, "redirect",
+                redirect_info.status_code, redirect_info.new_method,
+                redirect_info.new_url, response_head.headers.get());
+      if (!follow_redirect_)
+        Cancel();
+      break;
+    case network::mojom::RedirectMode::kFollow:
+      EmitEvent(EventType::kRequest, false, "redirect",
+                redirect_info.status_code, redirect_info.new_method,
+                redirect_info.new_url, response_head.headers.get());
+      break;
+  }
+}
+
+void URLRequestNS::OnUploadProgress(uint64_t position, uint64_t total) {
+  upload_position_ = position;
+  upload_total_ = total;
+}
+
+void URLRequestNS::OnWrite(MojoResult result) {
+  if (result != MOJO_RESULT_OK)
+    return;
+
+  // Continue the pending writes.
+  pending_writes_.pop_front();
+  if (!pending_writes_.empty())
+    DoWrite();
+}
+
+void URLRequestNS::DoWrite() {
+  DCHECK(producer_);
+  DCHECK(!pending_writes_.empty());
+  producer_->Write(
+      pending_writes_.front(),
+      mojo::StringDataPipeProducer::AsyncWritingMode::
+          STRING_STAYS_VALID_UNTIL_COMPLETION,
+      base::BindOnce(&URLRequestNS::OnWrite, weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestNS::StartWriting() {
+  if (!last_chunk_written_ || size_callback_.is_null())
+    return;
+
+  size_t size = 0;
+  for (const auto& data : pending_writes_)
+    size += data.size();
+  std::move(size_callback_).Run(net::OK, size);
+  DoWrite();
+}
+
+void URLRequestNS::Pin() {
+  if (wrapper_.IsEmpty()) {
+    wrapper_.Reset(isolate(), GetWrapper());
+  }
+}
+
+void URLRequestNS::Unpin() {
+  wrapper_.Reset();
+}
+
+void URLRequestNS::EmitError(EventType type, base::StringPiece message) {
+  if (type == EventType::kRequest)
+    request_state_ |= STATE_FAILED;
+  else
+    response_state_ |= STATE_FAILED;
+  v8::HandleScope handle_scope(isolate());
+  auto error = v8::Exception::Error(mate::StringToV8(isolate(), message));
+  EmitEvent(type, false, "error", error);
+}
+
+template <typename... Args>
+void URLRequestNS::EmitEvent(EventType type, Args... args) {
+  const char* method =
+      type == EventType::kRequest ? "_emitRequestEvent" : "_emitResponseEvent";
+  v8::HandleScope handle_scope(isolate());
+  mate::CustomEmit(isolate(), GetWrapper(), method, args...);
+}
+
+// static
+mate::WrappableBase* URLRequestNS::New(mate::Arguments* args) {
+  return new URLRequestNS(args);
+}
+
+// static
+void URLRequestNS::BuildPrototype(v8::Isolate* isolate,
+                                  v8::Local<v8::FunctionTemplate> prototype) {
+  prototype->SetClassName(mate::StringToV8(isolate, "URLRequest"));
+  mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
+      .MakeDestroyable()
+      .SetMethod("write", &URLRequestNS::Write)
+      .SetMethod("cancel", &URLRequestNS::Cancel)
+      .SetMethod("setExtraHeader", &URLRequestNS::SetExtraHeader)
+      .SetMethod("removeExtraHeader", &URLRequestNS::RemoveExtraHeader)
+      .SetMethod("setChunkedUpload", &URLRequestNS::SetChunkedUpload)
+      .SetMethod("followRedirect", &URLRequestNS::FollowRedirect)
+      .SetMethod("getUploadProgress", &URLRequestNS::GetUploadProgress)
+      .SetProperty("notStarted", &URLRequestNS::NotStarted)
+      .SetProperty("finished", &URLRequestNS::Finished)
+      .SetProperty("statusCode", &URLRequestNS::StatusCode)
+      .SetProperty("statusMessage", &URLRequestNS::StatusMessage)
+      .SetProperty("rawResponseHeaders", &URLRequestNS::RawResponseHeaders)
+      .SetProperty("httpVersionMajor", &URLRequestNS::ResponseHttpVersionMajor)
+      .SetProperty("httpVersionMinor", &URLRequestNS::ResponseHttpVersionMinor);
+}
+
+}  // namespace api
+
+}  // namespace electron

+ 143 - 0
shell/browser/api/atom_api_url_request_ns.h

@@ -0,0 +1,143 @@
+// Copyright (c) 2019 GitHub, Inc.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+#ifndef SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_
+#define SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_
+
+#include <list>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "mojo/public/cpp/system/string_data_pipe_producer.h"
+#include "native_mate/dictionary.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
+#include "services/network/public/mojom/data_pipe_getter.mojom.h"
+#include "shell/browser/api/event_emitter.h"
+
+namespace electron {
+
+namespace api {
+
+class UploadDataPipeGetter;
+
+class URLRequestNS : public mate::EventEmitter<URLRequestNS>,
+                     public network::SimpleURLLoaderStreamConsumer {
+ public:
+  static mate::WrappableBase* New(mate::Arguments* args);
+
+  static void BuildPrototype(v8::Isolate* isolate,
+                             v8::Local<v8::FunctionTemplate> prototype);
+
+ protected:
+  explicit URLRequestNS(mate::Arguments* args);
+  ~URLRequestNS() override;
+
+  bool NotStarted() const;
+  bool Finished() const;
+
+  void Cancel();
+  void Close();
+
+  bool Write(v8::Local<v8::Value> data, bool is_last);
+  void FollowRedirect();
+  bool SetExtraHeader(const std::string& name, const std::string& value);
+  void RemoveExtraHeader(const std::string& name);
+  void SetChunkedUpload(bool is_chunked_upload);
+  mate::Dictionary GetUploadProgress();
+  int StatusCode() const;
+  std::string StatusMessage() const;
+  net::HttpResponseHeaders* RawResponseHeaders() const;
+  uint32_t ResponseHttpVersionMajor() const;
+  uint32_t ResponseHttpVersionMinor() const;
+
+  // SimpleURLLoaderStreamConsumer:
+  void OnDataReceived(base::StringPiece string_piece,
+                      base::OnceClosure resume) override;
+  void OnComplete(bool success) override;
+  void OnRetry(base::OnceClosure start_retry) override;
+
+ private:
+  friend class UploadDataPipeGetter;
+
+  void OnResponseStarted(const GURL& final_url,
+                         const network::ResourceResponseHead& response_head);
+  void OnRedirect(const net::RedirectInfo& redirect_info,
+                  const network::ResourceResponseHead& response_head,
+                  std::vector<std::string>* to_be_removed_headers);
+  void OnUploadProgress(uint64_t position, uint64_t total);
+  void OnWrite(MojoResult result);
+
+  // Write the first data of |pending_writes_|.
+  void DoWrite();
+
+  // Start streaming.
+  void StartWriting();
+
+  // Manage lifetime of wrapper.
+  void Pin();
+  void Unpin();
+
+  // Emit events.
+  enum class EventType {
+    kRequest,
+    kResponse,
+  };
+  void EmitError(EventType type, base::StringPiece error);
+  template <typename... Args>
+  void EmitEvent(EventType type, Args... args);
+
+  std::unique_ptr<network::ResourceRequest> request_;
+  std::unique_ptr<network::SimpleURLLoader> loader_;
+  scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
+  scoped_refptr<net::HttpResponseHeaders> response_headers_;
+
+  // Redirect mode.
+  //
+  // Note that we store it ourselves instead of reading from the one stored in
+  // |request_|, this is because with multiple redirections, the original one
+  // might be modified.
+  network::mojom::RedirectMode redirect_mode_ =
+      network::mojom::RedirectMode::kFollow;
+
+  // The DataPipeGetter passed to reader.
+  bool is_chunked_upload_ = false;
+  std::unique_ptr<UploadDataPipeGetter> data_pipe_getter_;
+
+  // Passed from DataPipeGetter for streaming data.
+  network::mojom::DataPipeGetter::ReadCallback size_callback_;
+  std::unique_ptr<mojo::StringDataPipeProducer> producer_;
+
+  // Whether request.end() has been called.
+  bool last_chunk_written_ = false;
+
+  // Whether the redirect should be followed.
+  bool follow_redirect_ = true;
+
+  // Upload progress.
+  uint64_t upload_position_ = 0;
+  uint64_t upload_total_ = 0;
+
+  // Current status.
+  int request_state_ = 0;
+  int response_state_ = 0;
+
+  // Pending writes that not yet sent to NetworkService.
+  std::list<std::string> pending_writes_;
+
+  // Used by pin/unpin to manage lifetime.
+  v8::Global<v8::Object> wrapper_;
+
+  base::WeakPtrFactory<URLRequestNS> weak_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(URLRequestNS);
+};
+
+}  // namespace api
+
+}  // namespace electron
+
+#endif  // SHELL_BROWSER_API_ATOM_API_URL_REQUEST_NS_H_