refactor: rewrite the net module to simplify state tracking (#21244)
This commit is contained in:
parent
4149d76890
commit
d25256dcf5
14 changed files with 1092 additions and 1033 deletions
|
@ -4,9 +4,11 @@
|
|||
|
||||
#include "shell/browser/api/atom_api_net.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gin/handle.h"
|
||||
#include "services/network/public/cpp/features.h"
|
||||
#include "shell/browser/api/atom_api_url_request.h"
|
||||
#include "shell/browser/api/atom_api_url_loader.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
|
||||
|
@ -32,12 +34,12 @@ void Net::BuildPrototype(v8::Isolate* isolate,
|
|||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
prototype->SetClassName(gin::StringToV8(isolate, "Net"));
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetProperty("URLRequest", &Net::URLRequest);
|
||||
.SetProperty("URLLoader", &Net::URLLoader);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
|
||||
v8::Local<v8::Value> Net::URLLoader(v8::Isolate* isolate) {
|
||||
v8::Local<v8::FunctionTemplate> constructor;
|
||||
constructor = URLRequest::GetConstructor(isolate);
|
||||
constructor = SimpleURLLoaderWrapper::GetConstructor(isolate);
|
||||
return constructor->GetFunction(isolate->GetCurrentContext())
|
||||
.ToLocalChecked();
|
||||
}
|
||||
|
@ -48,8 +50,16 @@ v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
|
|||
|
||||
namespace {
|
||||
|
||||
bool IsValidHeaderName(std::string header_name) {
|
||||
return net::HttpUtil::IsValidHeaderName(header_name);
|
||||
}
|
||||
|
||||
bool IsValidHeaderValue(std::string header_value) {
|
||||
return net::HttpUtil::IsValidHeaderValue(header_value);
|
||||
}
|
||||
|
||||
using electron::api::Net;
|
||||
using electron::api::URLRequest;
|
||||
using electron::api::SimpleURLLoaderWrapper;
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
|
@ -57,12 +67,15 @@ void Initialize(v8::Local<v8::Object> exports,
|
|||
void* priv) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
|
||||
URLRequest::SetConstructor(isolate, base::BindRepeating(URLRequest::New));
|
||||
SimpleURLLoaderWrapper::SetConstructor(
|
||||
isolate, base::BindRepeating(SimpleURLLoaderWrapper::New));
|
||||
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("net", Net::Create(isolate));
|
||||
dict.Set("Net",
|
||||
Net::GetConstructor(isolate)->GetFunction(context).ToLocalChecked());
|
||||
dict.SetMethod("_isValidHeaderName", &IsValidHeaderName);
|
||||
dict.SetMethod("_isValidHeaderValue", &IsValidHeaderValue);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -18,7 +18,7 @@ class Net : public mate::Wrappable<Net> {
|
|||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
|
||||
v8::Local<v8::Value> URLRequest(v8::Isolate* isolate);
|
||||
v8::Local<v8::Value> URLLoader(v8::Isolate* isolate);
|
||||
|
||||
protected:
|
||||
explicit Net(v8::Isolate* isolate);
|
||||
|
|
448
shell/browser/api/atom_api_url_loader.cc
Normal file
448
shell/browser/api/atom_api_url_loader.cc
Normal file
|
@ -0,0 +1,448 @@
|
|||
// Copyright (c) 2019 Slack Technologies, 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_loader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/id_map.h"
|
||||
#include "gin/handle.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "mojo/public/cpp/system/data_pipe_producer.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "services/network/public/cpp/simple_url_loader.h"
|
||||
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
|
||||
#include "services/network/public/mojom/url_loader_factory.mojom.h"
|
||||
#include "shell/browser/api/atom_api_session.h"
|
||||
#include "shell/browser/atom_browser_context.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/net_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
class BufferDataSource : public mojo::DataPipeProducer::DataSource {
|
||||
public:
|
||||
explicit BufferDataSource(base::span<char> buffer) {
|
||||
buffer_.resize(buffer.size());
|
||||
memcpy(buffer_.data(), buffer.data(), buffer_.size());
|
||||
}
|
||||
~BufferDataSource() override = default;
|
||||
|
||||
private:
|
||||
// mojo::DataPipeProducer::DataSource:
|
||||
uint64_t GetLength() const override { return buffer_.size(); }
|
||||
ReadResult Read(uint64_t offset, base::span<char> buffer) override {
|
||||
ReadResult result;
|
||||
if (offset <= buffer_.size()) {
|
||||
size_t readable_size = buffer_.size() - offset;
|
||||
size_t writable_size = buffer.size();
|
||||
size_t copyable_size = std::min(readable_size, writable_size);
|
||||
memcpy(buffer.data(), &buffer_[offset], copyable_size);
|
||||
result.bytes_read = copyable_size;
|
||||
} else {
|
||||
NOTREACHED();
|
||||
result.result = MOJO_RESULT_OUT_OF_RANGE;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<char> buffer_;
|
||||
};
|
||||
|
||||
class JSChunkedDataPipeGetter : public gin::Wrappable<JSChunkedDataPipeGetter>,
|
||||
public network::mojom::ChunkedDataPipeGetter {
|
||||
public:
|
||||
static gin::Handle<JSChunkedDataPipeGetter> Create(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Function> body_func,
|
||||
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
|
||||
chunked_data_pipe_getter) {
|
||||
return gin::CreateHandle(
|
||||
isolate, new JSChunkedDataPipeGetter(
|
||||
isolate, body_func, std::move(chunked_data_pipe_getter)));
|
||||
}
|
||||
|
||||
// gin::Wrappable
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override {
|
||||
return gin::Wrappable<JSChunkedDataPipeGetter>::GetObjectTemplateBuilder(
|
||||
isolate)
|
||||
.SetMethod("write", &JSChunkedDataPipeGetter::WriteChunk)
|
||||
.SetMethod("done", &JSChunkedDataPipeGetter::Done);
|
||||
}
|
||||
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
~JSChunkedDataPipeGetter() override = default;
|
||||
|
||||
private:
|
||||
JSChunkedDataPipeGetter(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Function> body_func,
|
||||
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
|
||||
chunked_data_pipe_getter)
|
||||
: isolate_(isolate), body_func_(isolate, body_func) {
|
||||
receiver_.Bind(std::move(chunked_data_pipe_getter));
|
||||
}
|
||||
|
||||
// network::mojom::ChunkedDataPipeGetter:
|
||||
void GetSize(GetSizeCallback callback) override {
|
||||
size_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
if (body_func_.IsEmpty()) {
|
||||
LOG(ERROR) << "Tried to read twice from a JSChunkedDataPipeGetter";
|
||||
// Drop the handle on the floor.
|
||||
return;
|
||||
}
|
||||
data_producer_ = std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
|
||||
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
v8::MicrotasksScope script_scope(isolate_,
|
||||
v8::MicrotasksScope::kRunMicrotasks);
|
||||
auto maybe_wrapper = GetWrapper(isolate_);
|
||||
v8::Local<v8::Value> wrapper;
|
||||
if (!maybe_wrapper.ToLocal(&wrapper)) {
|
||||
return;
|
||||
}
|
||||
v8::Local<v8::Value> argv[] = {wrapper};
|
||||
node::Environment* env = node::Environment::GetCurrent(isolate_);
|
||||
auto global = env->context()->Global();
|
||||
node::MakeCallback(isolate_, global, body_func_.Get(isolate_),
|
||||
node::arraysize(argv), argv, {0, 0});
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> WriteChunk(v8::Local<v8::Value> buffer_val) {
|
||||
gin_helper::Promise<void> promise(isolate_);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
if (!buffer_val->IsArrayBufferView()) {
|
||||
promise.RejectWithErrorMessage("Expected an ArrayBufferView");
|
||||
return handle;
|
||||
}
|
||||
if (is_writing_) {
|
||||
promise.RejectWithErrorMessage("Only one write can be pending at a time");
|
||||
return handle;
|
||||
}
|
||||
if (!size_callback_) {
|
||||
promise.RejectWithErrorMessage("Can't write after calling done()");
|
||||
return handle;
|
||||
}
|
||||
auto buffer = buffer_val.As<v8::ArrayBufferView>();
|
||||
is_writing_ = true;
|
||||
bytes_written_ += buffer->ByteLength();
|
||||
auto backing_store = buffer->Buffer()->GetBackingStore();
|
||||
auto buffer_span = base::make_span(
|
||||
static_cast<char*>(backing_store->Data()) + buffer->ByteOffset(),
|
||||
buffer->ByteLength());
|
||||
auto buffer_source = std::make_unique<BufferDataSource>(buffer_span);
|
||||
data_producer_->Write(
|
||||
std::move(buffer_source),
|
||||
base::BindOnce(&JSChunkedDataPipeGetter::OnWriteChunkComplete,
|
||||
// We're OK to use Unretained here because we own
|
||||
// |data_producer_|.
|
||||
base::Unretained(this), std::move(promise)));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void OnWriteChunkComplete(gin_helper::Promise<void> promise,
|
||||
MojoResult result) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
is_writing_ = false;
|
||||
if (result == MOJO_RESULT_OK) {
|
||||
promise.Resolve();
|
||||
} else {
|
||||
promise.RejectWithErrorMessage("mojo result not ok");
|
||||
Finished();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(nornagon): accept a net error here to allow the data provider to
|
||||
// cancel the request with an error.
|
||||
void Done() {
|
||||
if (size_callback_) {
|
||||
std::move(size_callback_).Run(net::OK, bytes_written_);
|
||||
Finished();
|
||||
}
|
||||
}
|
||||
|
||||
void Finished() {
|
||||
size_callback_.Reset();
|
||||
body_func_.Reset();
|
||||
receiver_.reset();
|
||||
data_producer_.reset();
|
||||
}
|
||||
|
||||
GetSizeCallback size_callback_;
|
||||
mojo::Receiver<network::mojom::ChunkedDataPipeGetter> receiver_{this};
|
||||
std::unique_ptr<mojo::DataPipeProducer> data_producer_;
|
||||
bool is_writing_ = false;
|
||||
uint64_t bytes_written_ = 0;
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
v8::Global<v8::Function> body_func_;
|
||||
};
|
||||
|
||||
gin::WrapperInfo JSChunkedDataPipeGetter::kWrapperInfo = {
|
||||
gin::kEmbedderNativeGin};
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace {
|
||||
|
||||
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."
|
||||
})");
|
||||
|
||||
base::IDMap<SimpleURLLoaderWrapper*>& GetAllRequests() {
|
||||
static base::NoDestructor<base::IDMap<SimpleURLLoaderWrapper*>>
|
||||
s_all_requests;
|
||||
return *s_all_requests;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SimpleURLLoaderWrapper::SimpleURLLoaderWrapper(
|
||||
std::unique_ptr<network::ResourceRequest> request,
|
||||
network::mojom::URLLoaderFactory* url_loader_factory)
|
||||
: id_(GetAllRequests().Add(this)) {
|
||||
// We slightly abuse the |render_frame_id| field in ResourceRequest so that
|
||||
// we can correlate any authentication events that arrive with this request.
|
||||
request->render_frame_id = id_;
|
||||
|
||||
// SimpleURLLoader wants to control the request body itself. We have other
|
||||
// ideas.
|
||||
auto request_body = std::move(request->request_body);
|
||||
auto* request_ref = request.get();
|
||||
loader_ =
|
||||
network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation);
|
||||
if (request_body) {
|
||||
request_ref->request_body = std::move(request_body);
|
||||
}
|
||||
|
||||
loader_->SetAllowHttpErrorResults(true);
|
||||
loader_->SetOnResponseStartedCallback(base::BindOnce(
|
||||
&SimpleURLLoaderWrapper::OnResponseStarted, base::Unretained(this)));
|
||||
loader_->SetOnRedirectCallback(base::BindRepeating(
|
||||
&SimpleURLLoaderWrapper::OnRedirect, base::Unretained(this)));
|
||||
loader_->SetOnUploadProgressCallback(base::BindRepeating(
|
||||
&SimpleURLLoaderWrapper::OnUploadProgress, base::Unretained(this)));
|
||||
loader_->SetOnDownloadProgressCallback(base::BindRepeating(
|
||||
&SimpleURLLoaderWrapper::OnDownloadProgress, base::Unretained(this)));
|
||||
|
||||
loader_->DownloadAsStream(url_loader_factory, this);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::Pin() {
|
||||
// Prevent ourselves from being GC'd until the request is complete.
|
||||
// Must be called after InitWithArgs(), otherwise GetWrapper() isn't
|
||||
// initialized.
|
||||
pinned_wrapper_.Reset(isolate(), GetWrapper());
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::PinBodyGetter(v8::Local<v8::Value> body_getter) {
|
||||
pinned_chunk_pipe_getter_.Reset(isolate(), body_getter);
|
||||
}
|
||||
|
||||
SimpleURLLoaderWrapper::~SimpleURLLoaderWrapper() {
|
||||
GetAllRequests().Remove(id_);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleURLLoaderWrapper* SimpleURLLoaderWrapper::FromID(uint32_t id) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
return GetAllRequests().Lookup(id);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
|
||||
std::move(auth_challenge_responder));
|
||||
// WeakPtr because if we're Cancel()ed while waiting for auth, and the
|
||||
// network service also decides to cancel at the same time and kill this
|
||||
// pipe, we might end up trying to call Cancel again on dead memory.
|
||||
auth_responder.set_disconnect_handler(base::BindOnce(
|
||||
&SimpleURLLoaderWrapper::Cancel, weak_factory_.GetWeakPtr()));
|
||||
auto cb = base::BindOnce(
|
||||
[](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
|
||||
gin::Arguments* args) {
|
||||
base::string16 username_str, password_str;
|
||||
if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
|
||||
auth_responder->OnAuthCredentials(base::nullopt);
|
||||
return;
|
||||
}
|
||||
auth_responder->OnAuthCredentials(
|
||||
net::AuthCredentials(username_str, password_str));
|
||||
},
|
||||
std::move(auth_responder));
|
||||
Emit("login", auth_info, base::AdaptCallbackForRepeating(std::move(cb)));
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::Cancel() {
|
||||
loader_.reset();
|
||||
pinned_wrapper_.Reset();
|
||||
pinned_chunk_pipe_getter_.Reset();
|
||||
// This ensures that no further callbacks will be called, so there's no need
|
||||
// for additional guards.
|
||||
}
|
||||
|
||||
// static
|
||||
mate::WrappableBase* SimpleURLLoaderWrapper::New(gin::Arguments* args) {
|
||||
gin_helper::Dictionary opts;
|
||||
if (!args->GetNext(&opts)) {
|
||||
args->ThrowTypeError("Expected a dictionary");
|
||||
return nullptr;
|
||||
}
|
||||
auto request = std::make_unique<network::ResourceRequest>();
|
||||
opts.Get("method", &request->method);
|
||||
opts.Get("url", &request->url);
|
||||
std::map<std::string, std::string> extra_headers;
|
||||
if (opts.Get("extraHeaders", &extra_headers)) {
|
||||
for (const auto& it : extra_headers) {
|
||||
if (!net::HttpUtil::IsValidHeaderName(it.first) ||
|
||||
!net::HttpUtil::IsValidHeaderValue(it.second)) {
|
||||
args->ThrowTypeError("Invalid header name or value");
|
||||
return nullptr;
|
||||
}
|
||||
request->headers.SetHeader(it.first, it.second);
|
||||
}
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> body;
|
||||
v8::Local<v8::Value> chunk_pipe_getter;
|
||||
if (opts.Get("body", &body)) {
|
||||
if (body->IsArrayBufferView()) {
|
||||
auto buffer_body = body.As<v8::ArrayBufferView>();
|
||||
auto backing_store = buffer_body->Buffer()->GetBackingStore();
|
||||
request->request_body = network::ResourceRequestBody::CreateFromBytes(
|
||||
static_cast<char*>(backing_store->Data()) + buffer_body->ByteOffset(),
|
||||
buffer_body->ByteLength());
|
||||
} else if (body->IsFunction()) {
|
||||
auto body_func = body.As<v8::Function>();
|
||||
|
||||
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
|
||||
data_pipe_getter;
|
||||
chunk_pipe_getter = JSChunkedDataPipeGetter::Create(
|
||||
args->isolate(), body_func,
|
||||
data_pipe_getter.InitWithNewPipeAndPassReceiver())
|
||||
.ToV8();
|
||||
request->request_body = new network::ResourceRequestBody();
|
||||
request->request_body->SetToChunkedDataPipe(std::move(data_pipe_getter));
|
||||
}
|
||||
}
|
||||
|
||||
std::string partition;
|
||||
gin::Handle<Session> session;
|
||||
if (!opts.Get("session", &session)) {
|
||||
if (opts.Get("partition", &partition))
|
||||
session = Session::FromPartition(args->isolate(), partition);
|
||||
else // default session
|
||||
session = Session::FromPartition(args->isolate(), "");
|
||||
}
|
||||
|
||||
auto url_loader_factory = session->browser_context()->GetURLLoaderFactory();
|
||||
|
||||
auto* ret =
|
||||
new SimpleURLLoaderWrapper(std::move(request), url_loader_factory.get());
|
||||
ret->InitWithArgs(args);
|
||||
ret->Pin();
|
||||
if (!chunk_pipe_getter.IsEmpty()) {
|
||||
ret->PinBodyGetter(chunk_pipe_getter);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnDataReceived(base::StringPiece string_piece,
|
||||
base::OnceClosure resume) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
auto array_buffer = v8::ArrayBuffer::New(isolate(), string_piece.size());
|
||||
auto backing_store = array_buffer->GetBackingStore();
|
||||
memcpy(backing_store->Data(), string_piece.data(), string_piece.size());
|
||||
Emit("data", array_buffer);
|
||||
std::move(resume).Run();
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnComplete(bool success) {
|
||||
if (success) {
|
||||
Emit("complete");
|
||||
} else {
|
||||
Emit("error", net::ErrorToString(loader_->NetError()));
|
||||
}
|
||||
loader_.reset();
|
||||
pinned_wrapper_.Reset();
|
||||
pinned_chunk_pipe_getter_.Reset();
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnRetry(base::OnceClosure start_retry) {}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnResponseStarted(
|
||||
const GURL& final_url,
|
||||
const network::mojom::URLResponseHead& response_head) {
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate());
|
||||
dict.Set("statusCode", response_head.headers->response_code());
|
||||
dict.Set("statusMessage", response_head.headers->GetStatusText());
|
||||
dict.Set("headers", response_head.headers.get());
|
||||
dict.Set("httpVersion", response_head.headers->GetHttpVersion());
|
||||
Emit("response-started", final_url, dict);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnRedirect(
|
||||
const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& response_head,
|
||||
std::vector<std::string>* removed_headers) {
|
||||
Emit("redirect", redirect_info, response_head.headers.get());
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnUploadProgress(uint64_t position,
|
||||
uint64_t total) {
|
||||
Emit("upload-progress", position, total);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnDownloadProgress(uint64_t current) {
|
||||
Emit("download-progress", current);
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleURLLoaderWrapper::BuildPrototype(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
prototype->SetClassName(gin::StringToV8(isolate, "SimpleURLLoaderWrapper"));
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetMethod("cancel", &SimpleURLLoaderWrapper::Cancel);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
93
shell/browser/api/atom_api_url_loader.h
Normal file
93
shell/browser/api/atom_api_url_loader.h
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) 2019 Slack Technologies, 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_LOADER_H_
|
||||
#define SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "net/base/auth.h"
|
||||
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
|
||||
#include "services/network/public/mojom/url_response_head.mojom.h"
|
||||
#include "shell/common/gin_helper/event_emitter.h"
|
||||
#include "url/gurl.h"
|
||||
#include "v8/include/v8.h"
|
||||
|
||||
namespace gin {
|
||||
class Arguments;
|
||||
}
|
||||
|
||||
namespace network {
|
||||
class SimpleURLLoader;
|
||||
struct ResourceRequest;
|
||||
} // namespace network
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
/** Wraps a SimpleURLLoader to make it usable from JavaScript */
|
||||
class SimpleURLLoaderWrapper
|
||||
: public gin_helper::EventEmitter<SimpleURLLoaderWrapper>,
|
||||
public network::SimpleURLLoaderStreamConsumer {
|
||||
public:
|
||||
~SimpleURLLoaderWrapper() override;
|
||||
static mate::WrappableBase* New(gin::Arguments* args);
|
||||
|
||||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
|
||||
static SimpleURLLoaderWrapper* FromID(uint32_t id);
|
||||
|
||||
void OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder);
|
||||
|
||||
void Cancel();
|
||||
|
||||
private:
|
||||
SimpleURLLoaderWrapper(std::unique_ptr<network::ResourceRequest> loader,
|
||||
network::mojom::URLLoaderFactory* url_loader_factory);
|
||||
|
||||
// SimpleURLLoaderStreamConsumer:
|
||||
void OnDataReceived(base::StringPiece string_piece,
|
||||
base::OnceClosure resume) override;
|
||||
void OnComplete(bool success) override;
|
||||
void OnRetry(base::OnceClosure start_retry) override;
|
||||
|
||||
// SimpleURLLoader callbacks
|
||||
void OnResponseStarted(const GURL& final_url,
|
||||
const network::mojom::URLResponseHead& response_head);
|
||||
void OnRedirect(const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& response_head,
|
||||
std::vector<std::string>* removed_headers);
|
||||
void OnUploadProgress(uint64_t position, uint64_t total);
|
||||
void OnDownloadProgress(uint64_t current);
|
||||
|
||||
void Start();
|
||||
void Pin();
|
||||
void PinBodyGetter(v8::Local<v8::Value>);
|
||||
|
||||
uint32_t id_;
|
||||
std::unique_ptr<network::SimpleURLLoader> loader_;
|
||||
v8::Global<v8::Value> pinned_wrapper_;
|
||||
v8::Global<v8::Value> pinned_chunk_pipe_getter_;
|
||||
|
||||
base::WeakPtrFactory<SimpleURLLoaderWrapper> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
|
|
@ -1,597 +0,0 @@
|
|||
// 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.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/containers/id_map.h"
|
||||
#include "gin/handle.h"
|
||||
#include "mojo/public/cpp/bindings/receiver_set.h"
|
||||
#include "mojo/public/cpp/system/string_data_source.h"
|
||||
#include "net/http/http_util.h"
|
||||
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
|
||||
#include "shell/browser/api/atom_api_session.h"
|
||||
#include "shell/browser/atom_browser_context.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/net_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
namespace gin {
|
||||
|
||||
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 gin
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace {
|
||||
|
||||
base::IDMap<URLRequest*>& GetAllRequests() {
|
||||
static base::NoDestructor<base::IDMap<URLRequest*>> s_all_requests;
|
||||
return *s_all_requests;
|
||||
}
|
||||
|
||||
// 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(URLRequest* 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::DataPipeProducer>(std::move(pipe));
|
||||
request_->StartWriting();
|
||||
}
|
||||
|
||||
private:
|
||||
URLRequest* request_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UploadDataPipeGetter);
|
||||
};
|
||||
|
||||
// Streaming multipart data to NetworkService.
|
||||
class MultipartDataPipeGetter : public UploadDataPipeGetter,
|
||||
public network::mojom::DataPipeGetter {
|
||||
public:
|
||||
explicit MultipartDataPipeGetter(URLRequest* request)
|
||||
: UploadDataPipeGetter(request) {}
|
||||
~MultipartDataPipeGetter() override = default;
|
||||
|
||||
void AttachToRequestBody(network::ResourceRequestBody* body) override {
|
||||
mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter_remote;
|
||||
receivers_.Add(this,
|
||||
data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
|
||||
body->AppendDataPipe(std::move(data_pipe_getter_remote));
|
||||
}
|
||||
|
||||
private:
|
||||
// network::mojom::DataPipeGetter:
|
||||
void Read(mojo::ScopedDataPipeProducerHandle pipe,
|
||||
ReadCallback callback) override {
|
||||
SetCallback(std::move(callback));
|
||||
SetPipe(std::move(pipe));
|
||||
}
|
||||
|
||||
void Clone(
|
||||
mojo::PendingReceiver<network::mojom::DataPipeGetter> receiver) override {
|
||||
receivers_.Add(this, std::move(receiver));
|
||||
}
|
||||
|
||||
mojo::ReceiverSet<network::mojom::DataPipeGetter> receivers_;
|
||||
};
|
||||
|
||||
// Streaming chunked data to NetworkService.
|
||||
class ChunkedDataPipeGetter : public UploadDataPipeGetter,
|
||||
public network::mojom::ChunkedDataPipeGetter {
|
||||
public:
|
||||
explicit ChunkedDataPipeGetter(URLRequest* request)
|
||||
: UploadDataPipeGetter(request) {}
|
||||
~ChunkedDataPipeGetter() override = default;
|
||||
|
||||
void AttachToRequestBody(network::ResourceRequestBody* body) override {
|
||||
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
|
||||
data_pipe_getter_remote;
|
||||
receiver_set_.Add(this,
|
||||
data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
|
||||
body->SetToChunkedDataPipe(std::move(data_pipe_getter_remote));
|
||||
}
|
||||
|
||||
private:
|
||||
// network::mojom::ChunkedDataPipeGetter:
|
||||
void GetSize(GetSizeCallback callback) override {
|
||||
SetCallback(std::move(callback));
|
||||
}
|
||||
|
||||
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
|
||||
SetPipe(std::move(pipe));
|
||||
}
|
||||
|
||||
mojo::ReceiverSet<network::mojom::ChunkedDataPipeGetter> receiver_set_;
|
||||
};
|
||||
|
||||
URLRequest::URLRequest(gin::Arguments* args)
|
||||
: id_(GetAllRequests().Add(this)), weak_factory_(this) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
request_ = std::make_unique<network::ResourceRequest>();
|
||||
gin_helper::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_;
|
||||
}
|
||||
|
||||
request_->render_frame_id = id_;
|
||||
|
||||
std::string partition;
|
||||
gin::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(), "");
|
||||
}
|
||||
|
||||
url_loader_factory_ = session->browser_context()->GetURLLoaderFactory();
|
||||
|
||||
InitWithArgs(args);
|
||||
}
|
||||
|
||||
URLRequest::~URLRequest() = default;
|
||||
|
||||
URLRequest* URLRequest::FromID(uint32_t id) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
return GetAllRequests().Lookup(id);
|
||||
}
|
||||
|
||||
void URLRequest::OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
|
||||
std::move(auth_challenge_responder));
|
||||
auth_responder.set_disconnect_handler(
|
||||
base::BindOnce(&URLRequest::Cancel, weak_factory_.GetWeakPtr()));
|
||||
auto cb = base::BindOnce(
|
||||
[](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
|
||||
gin::Arguments* args) {
|
||||
base::string16 username_str, password_str;
|
||||
if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
|
||||
auth_responder->OnAuthCredentials(base::nullopt);
|
||||
return;
|
||||
}
|
||||
auth_responder->OnAuthCredentials(
|
||||
net::AuthCredentials(username_str, password_str));
|
||||
},
|
||||
std::move(auth_responder));
|
||||
Emit("login", auth_info, base::AdaptCallbackForRepeating(std::move(cb)));
|
||||
}
|
||||
|
||||
bool URLRequest::NotStarted() const {
|
||||
return request_state_ == 0;
|
||||
}
|
||||
|
||||
bool URLRequest::Finished() const {
|
||||
return request_state_ & STATE_FINISHED;
|
||||
}
|
||||
|
||||
void URLRequest::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 URLRequest::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 URLRequest::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_->SetAllowHttpErrorResults(true);
|
||||
loader_->SetOnResponseStartedCallback(
|
||||
base::Bind(&URLRequest::OnResponseStarted, weak_factory_.GetWeakPtr()));
|
||||
loader_->SetOnRedirectCallback(
|
||||
base::Bind(&URLRequest::OnRedirect, weak_factory_.GetWeakPtr()));
|
||||
loader_->SetOnUploadProgressCallback(
|
||||
base::Bind(&URLRequest::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 URLRequest::FollowRedirect() {
|
||||
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
|
||||
return;
|
||||
follow_redirect_ = true;
|
||||
}
|
||||
|
||||
bool URLRequest::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 URLRequest::RemoveExtraHeader(const std::string& name) {
|
||||
if (request_)
|
||||
request_->headers.RemoveHeader(name);
|
||||
}
|
||||
|
||||
void URLRequest::SetChunkedUpload(bool is_chunked_upload) {
|
||||
if (request_)
|
||||
is_chunked_upload_ = is_chunked_upload;
|
||||
}
|
||||
|
||||
gin::Dictionary URLRequest::GetUploadProgress() {
|
||||
gin::Dictionary progress = gin::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 URLRequest::StatusCode() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->response_code();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string URLRequest::StatusMessage() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->GetStatusText();
|
||||
return "";
|
||||
}
|
||||
|
||||
net::HttpResponseHeaders* URLRequest::RawResponseHeaders() const {
|
||||
return response_headers_.get();
|
||||
}
|
||||
|
||||
uint32_t URLRequest::ResponseHttpVersionMajor() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->GetHttpVersion().major_value();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t URLRequest::ResponseHttpVersionMinor() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->GetHttpVersion().minor_value();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void URLRequest::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 URLRequest::OnRetry(base::OnceClosure start_retry) {}
|
||||
|
||||
void URLRequest::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 URLRequest::OnResponseStarted(
|
||||
const GURL& final_url,
|
||||
const network::mojom::URLResponseHead& 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 URLRequest::OnRedirect(
|
||||
const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& 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:
|
||||
Cancel();
|
||||
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 URLRequest::OnUploadProgress(uint64_t position, uint64_t total) {
|
||||
upload_position_ = position;
|
||||
upload_total_ = total;
|
||||
}
|
||||
|
||||
void URLRequest::OnWrite(MojoResult result) {
|
||||
if (result != MOJO_RESULT_OK)
|
||||
return;
|
||||
|
||||
// Continue the pending writes.
|
||||
pending_writes_.pop_front();
|
||||
if (!pending_writes_.empty())
|
||||
DoWrite();
|
||||
}
|
||||
|
||||
void URLRequest::DoWrite() {
|
||||
DCHECK(producer_);
|
||||
DCHECK(!pending_writes_.empty());
|
||||
producer_->Write(
|
||||
std::make_unique<mojo::StringDataSource>(
|
||||
pending_writes_.front(), mojo::StringDataSource::AsyncWritingMode::
|
||||
STRING_STAYS_VALID_UNTIL_COMPLETION),
|
||||
base::BindOnce(&URLRequest::OnWrite, weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void URLRequest::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 URLRequest::Pin() {
|
||||
if (wrapper_.IsEmpty()) {
|
||||
wrapper_.Reset(isolate(), GetWrapper());
|
||||
}
|
||||
}
|
||||
|
||||
void URLRequest::Unpin() {
|
||||
wrapper_.Reset();
|
||||
}
|
||||
|
||||
void URLRequest::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(gin::StringToV8(isolate(), message));
|
||||
EmitEvent(type, false, "error", error);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void URLRequest::EmitEvent(EventType type, Args&&... args) {
|
||||
const char* method =
|
||||
type == EventType::kRequest ? "_emitRequestEvent" : "_emitResponseEvent";
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
gin_helper::CustomEmit(isolate(), GetWrapper(), method,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// static
|
||||
mate::WrappableBase* URLRequest::New(gin::Arguments* args) {
|
||||
return new URLRequest(args);
|
||||
}
|
||||
|
||||
// static
|
||||
void URLRequest::BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
prototype->SetClassName(gin::StringToV8(isolate, "URLRequest"));
|
||||
gin_helper::Destroyable::MakeDestroyable(isolate, prototype);
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetMethod("write", &URLRequest::Write)
|
||||
.SetMethod("cancel", &URLRequest::Cancel)
|
||||
.SetMethod("setExtraHeader", &URLRequest::SetExtraHeader)
|
||||
.SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader)
|
||||
.SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload)
|
||||
.SetMethod("followRedirect", &URLRequest::FollowRedirect)
|
||||
.SetMethod("getUploadProgress", &URLRequest::GetUploadProgress)
|
||||
.SetProperty("notStarted", &URLRequest::NotStarted)
|
||||
.SetProperty("finished", &URLRequest::Finished)
|
||||
.SetProperty("statusCode", &URLRequest::StatusCode)
|
||||
.SetProperty("statusMessage", &URLRequest::StatusMessage)
|
||||
.SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders)
|
||||
.SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor)
|
||||
.SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
|
@ -1,156 +0,0 @@
|
|||
// 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_H_
|
||||
#define SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gin/arguments.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "mojo/public/cpp/system/data_pipe_producer.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 "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "shell/common/gin_helper/event_emitter.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
class UploadDataPipeGetter;
|
||||
|
||||
class URLRequest : public gin_helper::EventEmitter<URLRequest>,
|
||||
public network::SimpleURLLoaderStreamConsumer {
|
||||
public:
|
||||
static mate::WrappableBase* New(gin::Arguments* args);
|
||||
|
||||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
static URLRequest* FromID(uint32_t id);
|
||||
|
||||
void OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder);
|
||||
|
||||
protected:
|
||||
explicit URLRequest(gin::Arguments* args);
|
||||
~URLRequest() 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);
|
||||
gin::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::mojom::URLResponseHead& response_head);
|
||||
void OnRedirect(const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& 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::DataPipeProducer> 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_;
|
||||
|
||||
uint32_t id_;
|
||||
|
||||
base::WeakPtrFactory<URLRequest> weak_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(URLRequest);
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_
|
|
@ -33,7 +33,7 @@
|
|||
#include "services/network/public/cpp/features.h"
|
||||
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "shell/browser/api/atom_api_url_request.h"
|
||||
#include "shell/browser/api/atom_api_url_loader.h"
|
||||
#include "shell/browser/atom_browser_client.h"
|
||||
#include "shell/browser/atom_browser_main_parts.h"
|
||||
#include "shell/browser/atom_download_manager_delegate.h"
|
||||
|
@ -362,11 +362,12 @@ class AuthResponder : public network::mojom::TrustedAuthClient {
|
|||
::network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder) override {
|
||||
api::URLRequest* url_request = api::URLRequest::FromID(routing_id);
|
||||
if (url_request) {
|
||||
url_request->OnAuthRequired(url, first_auth_attempt, auth_info,
|
||||
std::move(head),
|
||||
std::move(auth_challenge_responder));
|
||||
api::SimpleURLLoaderWrapper* url_loader =
|
||||
api::SimpleURLLoaderWrapper::FromID(routing_id);
|
||||
if (url_loader) {
|
||||
url_loader->OnAuthRequired(url, first_auth_attempt, auth_info,
|
||||
std::move(head),
|
||||
std::move(auth_challenge_responder));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include "net/cert/x509_certificate.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "net/http/http_version.h"
|
||||
#include "net/url_request/redirect_info.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "shell/browser/api/atom_api_data_pipe_holder.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
|
@ -337,7 +339,7 @@ bool Converter<scoped_refptr<network::ResourceRequestBody>>::FromV8(
|
|||
v8::Local<v8::Value> Converter<network::ResourceRequest>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const network::ResourceRequest& val) {
|
||||
gin::Dictionary dict(isolate, v8::Object::New(isolate));
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("method", val.method);
|
||||
dict.Set("url", val.url.spec());
|
||||
dict.Set("referrer", val.referrer.spec());
|
||||
|
@ -359,4 +361,32 @@ v8::Local<v8::Value> Converter<electron::VerifyRequestParams>::ToV8(
|
|||
return ConvertToV8(isolate, dict);
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> Converter<net::HttpVersion>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const net::HttpVersion& val) {
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("major", static_cast<uint32_t>(val.major_value()));
|
||||
dict.Set("minor", static_cast<uint32_t>(val.minor_value()));
|
||||
return ConvertToV8(isolate, dict);
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> Converter<net::RedirectInfo>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const net::RedirectInfo& val) {
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
|
||||
dict.Set("statusCode", val.status_code);
|
||||
dict.Set("newMethod", val.new_method);
|
||||
dict.Set("newUrl", val.new_url);
|
||||
dict.Set("newSiteForCookies", val.new_site_for_cookies);
|
||||
dict.Set("newReferrer", val.new_referrer);
|
||||
dict.Set("insecureSchemeWasUpgraded", val.insecure_scheme_was_upgraded);
|
||||
dict.Set("isSignedExchangeFallbackRedirect",
|
||||
val.is_signed_exchange_fallback_redirect);
|
||||
|
||||
return ConvertToV8(isolate, dict);
|
||||
}
|
||||
|
||||
} // namespace gin
|
||||
|
|
|
@ -5,7 +5,10 @@
|
|||
#ifndef SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
|
||||
#define SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gin/converter.h"
|
||||
#include "services/network/public/mojom/fetch_api.mojom.h"
|
||||
#include "shell/browser/net/cert_verifier_client.h"
|
||||
|
||||
namespace base {
|
||||
|
@ -19,6 +22,7 @@ class URLRequest;
|
|||
class X509Certificate;
|
||||
class HttpResponseHeaders;
|
||||
struct CertPrincipal;
|
||||
class HttpVersion;
|
||||
} // namespace net
|
||||
|
||||
namespace network {
|
||||
|
@ -96,6 +100,18 @@ struct Converter<electron::VerifyRequestParams> {
|
|||
electron::VerifyRequestParams val);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<net::HttpVersion> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const net::HttpVersion& val);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<net::RedirectInfo> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const net::RedirectInfo& val);
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
#endif // SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
|
||||
|
|
|
@ -67,7 +67,7 @@ void InvokeIpcCallback(v8::Local<v8::Context> context,
|
|||
.ToLocalChecked();
|
||||
auto callback_value = ipcNative->Get(context, callback_key).ToLocalChecked();
|
||||
DCHECK(callback_value->IsFunction()); // set by init.ts
|
||||
auto callback = v8::Local<v8::Function>::Cast(callback_value);
|
||||
auto callback = callback_value.As<v8::Function>();
|
||||
ignore_result(callback->Call(context, ipcNative, args.size(), args.data()));
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue