Implementing abort workflow, emitting error events.

This commit is contained in:
ali.ibrahim 2016-09-28 15:07:54 +02:00
parent cbbc4376ca
commit 08947682b0
5 changed files with 84 additions and 46 deletions

View file

@ -135,7 +135,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate,
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
// Request API // Request API
.MakeDestroyable() .MakeDestroyable()
.SetMethod("writeBuffer", &URLRequest::Write) .SetMethod("write", &URLRequest::Write)
.SetMethod("abort", &URLRequest::Abort) .SetMethod("abort", &URLRequest::Abort)
.SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader)
.SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader)
@ -212,6 +212,10 @@ void URLRequest::OnResponseCompleted() {
atom_request_ = nullptr; atom_request_ = nullptr;
} }
void URLRequest::OnError(const std::string& error) {
auto error_object = v8::Exception::Error(mate::StringToV8(isolate(), error));
EmitRequestEvent("error", error_object);
}
int URLRequest::StatusCode() const { int URLRequest::StatusCode() const {
if (auto response_headers = atom_request_->GetResponseHeaders()) { if (auto response_headers = atom_request_->GetResponseHeaders()) {

View file

@ -45,6 +45,7 @@ private:
void OnResponseStarted(); void OnResponseStarted();
void OnResponseData(scoped_refptr<const net::IOBufferWithSize> data); void OnResponseData(scoped_refptr<const net::IOBufferWithSize> data);
void OnResponseCompleted(); void OnResponseCompleted();
void OnError(const std::string& error);
int StatusCode() const; int StatusCode() const;
std::string StatusMessage() const; std::string StatusMessage() const;

View file

@ -107,6 +107,10 @@ void AtomURLRequest::SetChunkedUpload(bool is_chunked_upload) {
void AtomURLRequest::Abort() const { void AtomURLRequest::Abort() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&AtomURLRequest::DoAbort, this));
} }
void AtomURLRequest::SetExtraHeader(const std::string& name, void AtomURLRequest::SetExtraHeader(const std::string& name,
@ -133,17 +137,15 @@ AtomURLRequest::GetResponseHeaders() const {
void AtomURLRequest::PassLoginInformation(const base::string16& username, void AtomURLRequest::PassLoginInformation(const base::string16& username,
const base::string16& password) const { const base::string16& password) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (username.empty() || password.empty()) { if (username.empty() || password.empty())
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE, content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::DoCancelAuth, this)); base::Bind(&AtomURLRequest::DoCancelAuth, this));
} else
else {
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE, content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::DoSetAuth, this, username, password)); base::Bind(&AtomURLRequest::DoSetAuth, this, username, password));
} }
}
void AtomURLRequest::DoWriteBuffer( void AtomURLRequest::DoWriteBuffer(
@ -164,25 +166,22 @@ void AtomURLRequest::DoWriteBuffer(
first_call = true; first_call = true;
} }
if (buffer) { if (buffer)
// Non-empty buffer. // Non-empty buffer.
auto write_result = chunked_stream_writer_->AppendData( auto write_result = chunked_stream_writer_->AppendData(
buffer->data(), buffer->data(),
buffer->size(), buffer->size(),
is_last); is_last);
} else if (is_last)
else if (is_last) {
// Empty buffer and last chunck, i.e. request.end(). // Empty buffer and last chunck, i.e. request.end().
auto write_result = chunked_stream_writer_->AppendData( auto write_result = chunked_stream_writer_->AppendData(
nullptr, nullptr,
0, 0,
true); true);
}
if (first_call) { if (first_call)
request_->Start(); request_->Start();
} }
}
else { else {
if (buffer) { if (buffer) {
@ -205,6 +204,11 @@ void AtomURLRequest::DoWriteBuffer(
} }
} }
void AtomURLRequest::DoAbort() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
request_->Cancel();
}
void AtomURLRequest::DoSetAuth(const base::string16& username, void AtomURLRequest::DoSetAuth(const base::string16& username,
const base::string16& password) const { const base::string16& password) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@ -231,20 +235,26 @@ void AtomURLRequest::OnResponseStarted(net::URLRequest* request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK_EQ(request, request_.get()); DCHECK_EQ(request, request_.get());
if (request_->status().is_success()) { const auto& status = request_->status();
if (status.is_success()) {
// Cache net::HttpResponseHeaders instance, a read-only objects // Cache net::HttpResponseHeaders instance, a read-only objects
// so that headers and other http metainformation can be simultaneously // so that headers and other http metainformation can be simultaneously
// read from UI thread while request data is simulataneously streaming // read from UI thread while request data is simulataneously streaming
// on IO thread. // on IO thread.
response_headers_ = request_->response_headers(); response_headers_ = request_->response_headers();
}
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, content::BrowserThread::UI, FROM_HERE,
base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this)); base::Bind(&AtomURLRequest::InformDelegateResponseStarted, this));
ReadResponse(); ReadResponse();
} }
else {
auto error = net::ErrorToString(status.ToNetError());
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&AtomURLRequest::InformDelegateErrorOccured, this, std::move(error)));
}
}
void AtomURLRequest::ReadResponse() { void AtomURLRequest::ReadResponse() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@ -255,10 +265,9 @@ void AtomURLRequest::ReadResponse() {
// about is the response code and headers, which we already have). // about is the response code and headers, which we already have).
int bytes_read = 0; int bytes_read = 0;
if (request_->status().is_success() if (request_->status().is_success()
/* TODO && (request_type_ != URLFetcher::HEAD)*/) { /* TODO && (request_type_ != URLFetcher::HEAD)*/)
if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read)) if (!request_->Read(response_read_buffer_.get(), kBufferSize, &bytes_read))
bytes_read = -1; bytes_read = -1;
}
OnReadCompleted(request_.get(), bytes_read); OnReadCompleted(request_.get(), bytes_read);
} }
@ -269,29 +278,31 @@ void AtomURLRequest::OnReadCompleted(net::URLRequest* request,
DCHECK_EQ(request, request_.get()); DCHECK_EQ(request, request_.get());
const auto status = request_->status();
do { do {
if (!request_->status().is_success() || bytes_read <= 0) if (!status.is_success() || bytes_read <= 0) {
auto error = net::ErrorToString(status.ToNetError());
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&AtomURLRequest::InformDelegateErrorOccured, this, std::move(error)));
break; break;
}
const auto result = CopyAndPostBuffer(bytes_read); const auto result = CopyAndPostBuffer(bytes_read);
if (!result) { if (!result)
// Failed to transfer data to UI thread. // Failed to transfer data to UI thread.
return; return;
}
} while (request_->Read(response_read_buffer_.get(), } while (request_->Read(response_read_buffer_.get(),
kBufferSize, kBufferSize,
&bytes_read)); &bytes_read));
const auto status = request_->status();
if (!status.is_io_pending() if (!status.is_io_pending()
/* TODO || request_type_ == URLFetcher::HEAD*/ ) { /* TODO || request_type_ == URLFetcher::HEAD*/ )
content::BrowserThread::PostTask( content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, content::BrowserThread::UI, FROM_HERE,
base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this)); base::Bind(&AtomURLRequest::InformDelegateResponseCompleted, this));
}
} }
@ -314,18 +325,15 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) {
void AtomURLRequest::InformDelegateAuthenticationRequired( void AtomURLRequest::InformDelegateAuthenticationRequired(
scoped_refptr<net::AuthChallengeInfo> auth_info) const { scoped_refptr<net::AuthChallengeInfo> auth_info) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_) { if (delegate_)
delegate_->OnAuthenticationRequired(auth_info); delegate_->OnAuthenticationRequired(auth_info);
} }
}
void AtomURLRequest::InformDelegateResponseStarted() const { void AtomURLRequest::InformDelegateResponseStarted() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_)
if (delegate_) {
delegate_->OnResponseStarted(); delegate_->OnResponseStarted();
} }
}
void AtomURLRequest::InformDelegateResponseData( void AtomURLRequest::InformDelegateResponseData(
scoped_refptr<net::IOBufferWithSize> data) const { scoped_refptr<net::IOBufferWithSize> data) const {
@ -333,17 +341,23 @@ void AtomURLRequest::InformDelegateResponseData(
// Transfer ownership of the data buffer, data will be released // Transfer ownership of the data buffer, data will be released
// by the delegate's OnResponseData. // by the delegate's OnResponseData.
if (delegate_) { if (delegate_)
delegate_->OnResponseData(data); delegate_->OnResponseData(data);
} }
}
void AtomURLRequest::InformDelegateResponseCompleted() const { void AtomURLRequest::InformDelegateResponseCompleted() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_) { if (delegate_)
delegate_->OnResponseCompleted(); delegate_->OnResponseCompleted();
} }
void AtomURLRequest::InformDelegateErrorOccured(
const std::string& error) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_)
delegate_->OnError(error);
} }

View file

@ -56,6 +56,7 @@ private:
friend class base::RefCountedThreadSafe<AtomURLRequest>; friend class base::RefCountedThreadSafe<AtomURLRequest>;
void DoWriteBuffer(scoped_refptr<const net::IOBufferWithSize> buffer, void DoWriteBuffer(scoped_refptr<const net::IOBufferWithSize> buffer,
bool is_last); bool is_last);
void DoAbort() const;
void DoSetAuth(const base::string16& username, void DoSetAuth(const base::string16& username,
const base::string16& password) const; const base::string16& password) const;
void DoCancelAuth() const; void DoCancelAuth() const;
@ -69,6 +70,7 @@ private:
void InformDelegateResponseData( void InformDelegateResponseData(
scoped_refptr<net::IOBufferWithSize> data) const; scoped_refptr<net::IOBufferWithSize> data) const;
void InformDelegateResponseCompleted() const; void InformDelegateResponseCompleted() const;
void InformDelegateErrorOccured(const std::string& error) const;
AtomURLRequest(base::WeakPtr<api::URLRequest> delegate); AtomURLRequest(base::WeakPtr<api::URLRequest> delegate);
virtual ~AtomURLRequest(); virtual ~AtomURLRequest();

View file

@ -130,6 +130,12 @@ class ClientRequest extends EventEmitter {
} }
} }
// Flag to prevent request's headers modifications after
// headers flush.
this._started = false;
this._aborted = false;
// Flag to prevent writings after end. // Flag to prevent writings after end.
this._finished = false; this._finished = false;
@ -137,10 +143,6 @@ class ClientRequest extends EventEmitter {
// to true only once and never set back to false. // to true only once and never set back to false.
this._chunkedEncoding = false; this._chunkedEncoding = false;
// Flag to prevent request's headers modifications after
// headers flush.
this._headersSent = false;
// This is a copy of the extra headers structure held by the native // This is a copy of the extra headers structure held by the native
// net::URLRequest. The main reason is to keep the getHeader API synchronous // net::URLRequest. The main reason is to keep the getHeader API synchronous
// after the request starts. // after the request starts.
@ -162,7 +164,7 @@ class ClientRequest extends EventEmitter {
} }
set chunkedEncoding(value) { set chunkedEncoding(value) {
if (this._headersSent) { if (this._started) {
throw new Error('Can\'t set the transfer encoding, headers have been sent.'); throw new Error('Can\'t set the transfer encoding, headers have been sent.');
} }
this._chunkedEncoding = value; this._chunkedEncoding = value;
@ -174,7 +176,7 @@ class ClientRequest extends EventEmitter {
throw new TypeError('`name` should be a string in setHeader(name, value).'); throw new TypeError('`name` should be a string in setHeader(name, value).');
if (value === undefined) if (value === undefined)
throw new Error('`value` required in setHeader("' + name + '", value).'); throw new Error('`value` required in setHeader("' + name + '", value).');
if (this._headersSent) if (this._started)
throw new Error('Can\'t set headers after they are sent.'); throw new Error('Can\'t set headers after they are sent.');
let key = name.toLowerCase(); let key = name.toLowerCase();
@ -202,7 +204,7 @@ class ClientRequest extends EventEmitter {
throw new Error('`name` is required for removeHeader(name).'); throw new Error('`name` is required for removeHeader(name).');
} }
if (this._headersSent) { if (this._started) {
throw new Error('Can\'t remove headers after they are sent.'); throw new Error('Can\'t remove headers after they are sent.');
} }
@ -227,14 +229,14 @@ class ClientRequest extends EventEmitter {
// Headers are assumed to be sent on first call to _writeBuffer, // Headers are assumed to be sent on first call to _writeBuffer,
// i.e. after the first call to write or end. // i.e. after the first call to write or end.
let result = this._url_request.writeBuffer(chunk, is_last); let result = this._url_request.write(chunk, is_last);
// Since writing to the network is asynchronous, we conservatively // Since writing to the network is asynchronous, we conservatively
// assume that request headers are written after delivering the first // assume that request headers are written after delivering the first
// buffer to the network IO thread. // buffer to the network IO thread.
if (!this._headersSent) { if (!this._started) {
this._url_request.setChunkedUpload(this.chunkedEncoding); this._url_request.setChunkedUpload(this.chunkedEncoding);
this._headersSent = true; this._started = true;
} }
// The write callback is fired asynchronously to mimic Node.js. // The write callback is fired asynchronously to mimic Node.js.
@ -278,6 +280,21 @@ class ClientRequest extends EventEmitter {
return this._write(data, encoding, callback, true); return this._write(data, encoding, callback, true);
} }
abort() {
if (!this._started) {
// Does nothing if stream
return;
}
if (!this._aborted) {
this._url_request.abort();
this._aborted = true;
process.nextTick( ()=>{
this.emit('abort');
} );
}
}
} }
function writeAfterEndNT(self, error, callback) { function writeAfterEndNT(self, error, callback) {