net: allow controlling redirects

This commit is contained in:
deepak1556 2017-03-24 01:07:54 +05:30
parent 637bdc239b
commit 3ae62615f4
6 changed files with 312 additions and 6 deletions

View file

@ -8,6 +8,7 @@
#include "atom/browser/net/atom_url_request.h" #include "atom/browser/net/atom_url_request.h"
#include "atom/common/api/event_emitter_caller.h" #include "atom/common/api/event_emitter_caller.h"
#include "atom/common/native_mate_converters/callback.h" #include "atom/common/native_mate_converters/callback.h"
#include "atom/common/native_mate_converters/gurl_converter.h"
#include "atom/common/native_mate_converters/net_converter.h" #include "atom/common/native_mate_converters/net_converter.h"
#include "atom/common/native_mate_converters/string16_converter.h" #include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/node_includes.h" #include "atom/common/node_includes.h"
@ -145,6 +146,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) {
dict.Get("method", &method); dict.Get("method", &method);
std::string url; std::string url;
dict.Get("url", &url); dict.Get("url", &url);
std::string redirect_policy;
dict.Get("redirect", &redirect_policy);
std::string partition; std::string partition;
mate::Handle<api::Session> session; mate::Handle<api::Session> session;
if (dict.Get("session", &session)) { if (dict.Get("session", &session)) {
@ -156,8 +159,8 @@ mate::WrappableBase* URLRequest::New(mate::Arguments* args) {
} }
auto browser_context = session->browser_context(); auto browser_context = session->browser_context();
auto api_url_request = new URLRequest(args->isolate(), args->GetThis()); auto api_url_request = new URLRequest(args->isolate(), args->GetThis());
auto atom_url_request = auto atom_url_request = AtomURLRequest::Create(
AtomURLRequest::Create(browser_context, method, url, api_url_request); browser_context, method, url, redirect_policy, api_url_request);
api_url_request->atom_request_ = atom_url_request; api_url_request->atom_request_ = atom_url_request;
@ -176,6 +179,7 @@ void URLRequest::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setExtraHeader", &URLRequest::SetExtraHeader) .SetMethod("setExtraHeader", &URLRequest::SetExtraHeader)
.SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader) .SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader)
.SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload) .SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload)
.SetMethod("followRedirect", &URLRequest::FollowRedirect)
.SetMethod("_setLoadFlags", &URLRequest::SetLoadFlags) .SetMethod("_setLoadFlags", &URLRequest::SetLoadFlags)
.SetProperty("notStarted", &URLRequest::NotStarted) .SetProperty("notStarted", &URLRequest::NotStarted)
.SetProperty("finished", &URLRequest::Finished) .SetProperty("finished", &URLRequest::Finished)
@ -246,6 +250,17 @@ void URLRequest::Cancel() {
Close(); Close();
} }
void URLRequest::FollowRedirect() {
if (request_state_.Canceled() || request_state_.Closed()) {
return;
}
DCHECK(atom_request_);
if (atom_request_) {
atom_request_->FollowRedirect();
}
}
bool URLRequest::SetExtraHeader(const std::string& name, bool URLRequest::SetExtraHeader(const std::string& name,
const std::string& value) { const std::string& value) {
// Request state must be in the initial non started state. // Request state must be in the initial non started state.
@ -305,6 +320,24 @@ void URLRequest::SetLoadFlags(int flags) {
} }
} }
void URLRequest::OnReceivedRedirect(
int status_code,
const std::string& method,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers) {
if (request_state_.Canceled() || request_state_.Closed()) {
return;
}
DCHECK(atom_request_);
if (!atom_request_) {
return;
}
EmitRequestEvent(false, "redirect", status_code, method, url,
response_headers.get());
}
void URLRequest::OnAuthenticationRequired( void URLRequest::OnAuthenticationRequired(
scoped_refptr<const net::AuthChallengeInfo> auth_info) { scoped_refptr<const net::AuthChallengeInfo> auth_info) {
if (request_state_.Canceled() || request_state_.Closed()) { if (request_state_.Canceled() || request_state_.Closed()) {

View file

@ -99,6 +99,11 @@ class URLRequest : public mate::EventEmitter<URLRequest> {
v8::Local<v8::FunctionTemplate> prototype); v8::Local<v8::FunctionTemplate> prototype);
// Methods for reporting events into JavaScript. // Methods for reporting events into JavaScript.
void OnReceivedRedirect(
int status_code,
const std::string& method,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers);
void OnAuthenticationRequired( void OnAuthenticationRequired(
scoped_refptr<const net::AuthChallengeInfo> auth_info); scoped_refptr<const net::AuthChallengeInfo> auth_info);
void OnResponseStarted( void OnResponseStarted(
@ -170,6 +175,7 @@ class URLRequest : public mate::EventEmitter<URLRequest> {
bool Failed() const; bool Failed() const;
bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last); bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last);
void Cancel(); void Cancel();
void FollowRedirect();
bool SetExtraHeader(const std::string& name, const std::string& value); bool SetExtraHeader(const std::string& name, const std::string& value);
void RemoveExtraHeader(const std::string& name); void RemoveExtraHeader(const std::string& name);
void SetChunkedUpload(bool is_chunked_upload); void SetChunkedUpload(bool is_chunked_upload);

View file

@ -13,6 +13,7 @@
#include "net/base/io_buffer.h" #include "net/base/io_buffer.h"
#include "net/base/load_flags.h" #include "net/base/load_flags.h"
#include "net/base/upload_bytes_element_reader.h" #include "net/base/upload_bytes_element_reader.h"
#include "net/url_request/redirect_info.h"
namespace { namespace {
const int kBufferSize = 4096; const int kBufferSize = 4096;
@ -58,6 +59,7 @@ scoped_refptr<AtomURLRequest> AtomURLRequest::Create(
AtomBrowserContext* browser_context, AtomBrowserContext* browser_context,
const std::string& method, const std::string& method,
const std::string& url, const std::string& url,
const std::string& redirect_policy,
api::URLRequest* delegate) { api::URLRequest* delegate) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@ -76,7 +78,7 @@ scoped_refptr<AtomURLRequest> AtomURLRequest::Create(
if (content::BrowserThread::PostTask( if (content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE, content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::DoInitialize, atom_url_request, base::Bind(&AtomURLRequest::DoInitialize, atom_url_request,
request_context_getter, method, url))) { request_context_getter, method, url, redirect_policy))) {
return atom_url_request; return atom_url_request;
} }
return nullptr; return nullptr;
@ -93,10 +95,12 @@ void AtomURLRequest::Terminate() {
void AtomURLRequest::DoInitialize( void AtomURLRequest::DoInitialize(
scoped_refptr<net::URLRequestContextGetter> request_context_getter, scoped_refptr<net::URLRequestContextGetter> request_context_getter,
const std::string& method, const std::string& method,
const std::string& url) { const std::string& url,
const std::string& redirect_policy) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(request_context_getter); DCHECK(request_context_getter);
redirect_policy_ = redirect_policy;
request_context_getter_ = request_context_getter; request_context_getter_ = request_context_getter;
request_context_getter_->AddObserver(this); request_context_getter_->AddObserver(this);
auto context = request_context_getter_->GetURLRequestContext(); auto context = request_context_getter_->GetURLRequestContext();
@ -150,6 +154,13 @@ void AtomURLRequest::Cancel() {
base::Bind(&AtomURLRequest::DoCancel, this)); base::Bind(&AtomURLRequest::DoCancel, this));
} }
void AtomURLRequest::FollowRedirect() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::DoFollowRedirect, this));
}
void AtomURLRequest::SetExtraHeader(const std::string& name, void AtomURLRequest::SetExtraHeader(const std::string& name,
const std::string& value) const { const std::string& value) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI); DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
@ -246,6 +257,13 @@ void AtomURLRequest::DoCancel() {
DoTerminate(); DoTerminate();
} }
void AtomURLRequest::DoFollowRedirect() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (request_ && request_->is_redirecting() && redirect_policy_ == "manual") {
request_->FollowDeferredRedirect();
}
}
void AtomURLRequest::DoSetExtraHeader(const std::string& name, void AtomURLRequest::DoSetExtraHeader(const std::string& name,
const std::string& value) const { const std::string& value) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@ -297,6 +315,29 @@ void AtomURLRequest::DoSetLoadFlags(int flags) const {
request_->SetLoadFlags(request_->load_flags() | flags); request_->SetLoadFlags(request_->load_flags() | flags);
} }
void AtomURLRequest::OnReceivedRedirect(net::URLRequest* request,
const net::RedirectInfo& info,
bool* defer_redirect) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!request_ || redirect_policy_ == "follow")
return;
if (redirect_policy_ == "error") {
request->Cancel();
DoCancelWithError(
"Request cannot follow redirect with the current redirect mode", true);
} else if (redirect_policy_ == "manual") {
*defer_redirect = true;
scoped_refptr<net::HttpResponseHeaders> response_headers =
request->response_headers();
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&AtomURLRequest::InformDelegateReceivedRedirect, this,
info.status_code, info.new_method, info.new_url,
response_headers));
}
}
void AtomURLRequest::OnAuthRequired(net::URLRequest* request, void AtomURLRequest::OnAuthRequired(net::URLRequest* request,
net::AuthChallengeInfo* auth_info) { net::AuthChallengeInfo* auth_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO); DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
@ -399,6 +440,16 @@ bool AtomURLRequest::CopyAndPostBuffer(int bytes_read) {
buffer_copy)); buffer_copy));
} }
void AtomURLRequest::InformDelegateReceivedRedirect(
int status_code,
const std::string& method,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (delegate_)
delegate_->OnReceivedRedirect(status_code, method, url, response_headers);
}
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);

View file

@ -30,12 +30,14 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
AtomBrowserContext* browser_context, AtomBrowserContext* browser_context,
const std::string& method, const std::string& method,
const std::string& url, const std::string& url,
const std::string& redirect_policy,
api::URLRequest* delegate); api::URLRequest* delegate);
void Terminate(); void Terminate();
bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last); bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last);
void SetChunkedUpload(bool is_chunked_upload); void SetChunkedUpload(bool is_chunked_upload);
void Cancel(); void Cancel();
void FollowRedirect();
void SetExtraHeader(const std::string& name, const std::string& value) const; void SetExtraHeader(const std::string& name, const std::string& value) const;
void RemoveExtraHeader(const std::string& name) const; void RemoveExtraHeader(const std::string& name) const;
void PassLoginInformation(const base::string16& username, void PassLoginInformation(const base::string16& username,
@ -44,6 +46,9 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
protected: protected:
// Overrides of net::URLRequest::Delegate // Overrides of net::URLRequest::Delegate
void OnReceivedRedirect(net::URLRequest* request,
const net::RedirectInfo& info,
bool* defer_redirect) override;
void OnAuthRequired(net::URLRequest* request, void OnAuthRequired(net::URLRequest* request,
net::AuthChallengeInfo* auth_info) override; net::AuthChallengeInfo* auth_info) override;
void OnResponseStarted(net::URLRequest* request) override; void OnResponseStarted(net::URLRequest* request) override;
@ -60,11 +65,13 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
void DoInitialize(scoped_refptr<net::URLRequestContextGetter>, void DoInitialize(scoped_refptr<net::URLRequestContextGetter>,
const std::string& method, const std::string& method,
const std::string& url); const std::string& url,
const std::string& redirect_policy);
void DoTerminate(); void DoTerminate();
void DoWriteBuffer(scoped_refptr<const net::IOBufferWithSize> buffer, void DoWriteBuffer(scoped_refptr<const net::IOBufferWithSize> buffer,
bool is_last); bool is_last);
void DoCancel(); void DoCancel();
void DoFollowRedirect();
void DoSetExtraHeader(const std::string& name, void DoSetExtraHeader(const std::string& name,
const std::string& value) const; const std::string& value) const;
void DoRemoveExtraHeader(const std::string& name) const; void DoRemoveExtraHeader(const std::string& name) const;
@ -77,6 +84,11 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
void ReadResponse(); void ReadResponse();
bool CopyAndPostBuffer(int bytes_read); bool CopyAndPostBuffer(int bytes_read);
void InformDelegateReceivedRedirect(
int status_code,
const std::string& method,
const GURL& url,
scoped_refptr<net::HttpResponseHeaders> response_headers) const;
void InformDelegateAuthenticationRequired( void InformDelegateAuthenticationRequired(
scoped_refptr<net::AuthChallengeInfo> auth_info) const; scoped_refptr<net::AuthChallengeInfo> auth_info) const;
void InformDelegateResponseStarted( void InformDelegateResponseStarted(
@ -92,6 +104,7 @@ class AtomURLRequest : public base::RefCountedThreadSafe<AtomURLRequest>,
scoped_refptr<net::URLRequestContextGetter> request_context_getter_; scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
bool is_chunked_upload_; bool is_chunked_upload_;
std::string redirect_policy_;
std::unique_ptr<net::ChunkedUploadDataStream> chunked_stream_; std::unique_ptr<net::ChunkedUploadDataStream> chunked_stream_;
std::unique_ptr<net::ChunkedUploadDataStream::Writer> chunked_stream_writer_; std::unique_ptr<net::ChunkedUploadDataStream::Writer> chunked_stream_writer_;
std::vector<std::unique_ptr<net::UploadElementReader>> std::vector<std::unique_ptr<net::UploadElementReader>>

View file

@ -156,9 +156,15 @@ class ClientRequest extends EventEmitter {
urlStr = url.format(urlObj) urlStr = url.format(urlObj)
} }
const redirectPolicy = options.redirect || 'follow'
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
throw new Error('redirect mode should be one of follow, error or manual')
}
let urlRequestOptions = { let urlRequestOptions = {
method: method, method: method,
url: urlStr url: urlStr,
redirect: redirectPolicy
} }
if (options.session) { if (options.session) {
if (options.session instanceof Session) { if (options.session instanceof Session) {
@ -339,6 +345,10 @@ class ClientRequest extends EventEmitter {
return this._write(data, encoding, callback, true) return this._write(data, encoding, callback, true)
} }
followRedirect () {
this.urlRequest.followRedirect()
}
abort () { abort () {
this.urlRequest.cancel() this.urlRequest.cancel()
} }

View file

@ -906,6 +906,199 @@ describe('net module', function () {
urlRequest.end() urlRequest.end()
}) })
it('should throw if given an invalid redirect mode', function (done) {
const requestUrl = '/requestUrl'
try {
const urlRequest = net.request({
url: `${server.url}${requestUrl}`,
redirect: 'custom'
})
urlRequest
} catch (exception) {
done()
}
})
it('should follow redirect when no redirect mode is provided', function (done) {
const requestUrl = '/301'
server.on('request', function (request, response) {
switch (request.url) {
case '/301':
response.statusCode = '301'
response.setHeader('Location', '/200')
response.end()
break
case '/200':
response.statusCode = '200'
response.end()
break
default:
assert(false)
}
})
const urlRequest = net.request({
url: `${server.url}${requestUrl}`
})
urlRequest.on('response', function (response) {
assert.equal(response.statusCode, 200)
done()
})
urlRequest.end()
})
it('should follow redirect chain when no redirect mode is provided', function (done) {
const requestUrl = '/redirectChain'
server.on('request', function (request, response) {
switch (request.url) {
case '/redirectChain':
response.statusCode = '301'
response.setHeader('Location', '/301')
response.end()
break
case '/301':
response.statusCode = '301'
response.setHeader('Location', '/200')
response.end()
break
case '/200':
response.statusCode = '200'
response.end()
break
default:
assert(false)
}
})
const urlRequest = net.request({
url: `${server.url}${requestUrl}`
})
urlRequest.on('response', function (response) {
assert.equal(response.statusCode, 200)
done()
})
urlRequest.end()
})
it('should not follow redirect when mode is error', function (done) {
const requestUrl = '/301'
server.on('request', function (request, response) {
switch (request.url) {
case '/301':
response.statusCode = '301'
response.setHeader('Location', '/200')
response.end()
break
case '/200':
response.statusCode = '200'
response.end()
break
default:
assert(false)
}
})
const urlRequest = net.request({
url: `${server.url}${requestUrl}`,
redirect: 'error'
})
urlRequest.on('error', function (error) {
assert.equal(error.message, 'Request cannot follow redirect with the current redirect mode')
})
urlRequest.on('close', function () {
done()
})
urlRequest.end()
})
it('should allow follow redirect when mode is manual', function (done) {
const requestUrl = '/redirectChain'
let redirectCount = 0
server.on('request', function (request, response) {
switch (request.url) {
case '/redirectChain':
response.statusCode = '301'
response.setHeader('Location', '/301')
response.end()
break
case '/301':
response.statusCode = '301'
response.setHeader('Location', '/200')
response.end()
break
case '/200':
response.statusCode = '200'
response.end()
break
default:
assert(false)
}
})
const urlRequest = net.request({
url: `${server.url}${requestUrl}`,
redirect: 'manual'
})
urlRequest.on('response', function (response) {
assert.equal(response.statusCode, 200)
assert.equal(redirectCount, 2)
done()
})
urlRequest.on('redirect', function (status, method, url) {
if (url === `${server.url}/301` || url === `${server.url}/200`) {
redirectCount += 1
urlRequest.followRedirect()
}
})
urlRequest.end()
})
it('should allow cancelling redirect when mode is manual', function (done) {
const requestUrl = '/redirectChain'
let redirectCount = 0
server.on('request', function (request, response) {
switch (request.url) {
case '/redirectChain':
response.statusCode = '301'
response.setHeader('Location', '/redirect/1')
response.end()
break
case '/redirect/1':
response.statusCode = '200'
response.setHeader('Location', '/redirect/2')
response.end()
break
case '/redirect/2':
response.statusCode = '200'
response.end()
break
default:
assert(false)
}
})
const urlRequest = net.request({
url: `${server.url}${requestUrl}`,
redirect: 'manual'
})
urlRequest.on('response', function (response) {
assert.equal(response.statusCode, 200)
response.pause()
response.on('data', function (chunk) {
})
response.on('end', function () {
urlRequest.abort()
})
response.resume()
})
urlRequest.on('close', function () {
assert.equal(redirectCount, 1)
done()
})
urlRequest.on('redirect', function (status, method, url) {
if (url === `${server.url}/redirect/1`) {
redirectCount += 1
urlRequest.followRedirect()
}
})
urlRequest.end()
})
it('should throw if given an invalid session option', function (done) { it('should throw if given an invalid session option', function (done) {
const requestUrl = '/requestUrl' const requestUrl = '/requestUrl'
try { try {