#include "atom/browser/net/atom_url_request.h"
#include "atom/common/api/event_emitter_caller.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/string16_converter.h"
#include "atom/common/node_includes.h"
dict.Get("method", &method);
std::string url;
dict.Get("url", &url);
+ std::string redirect_policy;
+ dict.Get("redirect", &redirect_policy);
std::string partition;
mate::Handle<api::Session> session;
if (dict.Get("session", &session)) {
}
auto browser_context = session->browser_context();
auto api_url_request = new URLRequest(args->isolate(), args->GetThis());
- auto atom_url_request =
- AtomURLRequest::Create(browser_context, method, url, api_url_request);
+ auto atom_url_request = AtomURLRequest::Create(
+ browser_context, method, url, redirect_policy, api_url_request);
api_url_request->atom_request_ = atom_url_request;
.SetMethod("setExtraHeader", &URLRequest::SetExtraHeader)
.SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader)
.SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload)
+ .SetMethod("followRedirect", &URLRequest::FollowRedirect)
.SetMethod("_setLoadFlags", &URLRequest::SetLoadFlags)
.SetProperty("notStarted", &URLRequest::NotStarted)
.SetProperty("finished", &URLRequest::Finished)
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,
const std::string& value) {
// Request state must be in the initial non started state.
}
}
+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(
scoped_refptr<const net::AuthChallengeInfo> auth_info) {
if (request_state_.Canceled() || request_state_.Closed()) {
v8::Local<v8::FunctionTemplate> prototype);
// 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(
scoped_refptr<const net::AuthChallengeInfo> auth_info);
void OnResponseStarted(
bool Failed() const;
bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last);
void Cancel();
+ void FollowRedirect();
bool SetExtraHeader(const std::string& name, const std::string& value);
void RemoveExtraHeader(const std::string& name);
void SetChunkedUpload(bool is_chunked_upload);
#include "net/base/io_buffer.h"
#include "net/base/load_flags.h"
#include "net/base/upload_bytes_element_reader.h"
+#include "net/url_request/redirect_info.h"
namespace {
const int kBufferSize = 4096;
AtomBrowserContext* browser_context,
const std::string& method,
const std::string& url,
+ const std::string& redirect_policy,
api::URLRequest* delegate) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (content::BrowserThread::PostTask(
content::BrowserThread::IO, FROM_HERE,
base::Bind(&AtomURLRequest::DoInitialize, atom_url_request,
- request_context_getter, method, url))) {
+ request_context_getter, method, url, redirect_policy))) {
return atom_url_request;
}
return nullptr;
void AtomURLRequest::DoInitialize(
scoped_refptr<net::URLRequestContextGetter> request_context_getter,
const std::string& method,
- const std::string& url) {
+ const std::string& url,
+ const std::string& redirect_policy) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(request_context_getter);
+ redirect_policy_ = redirect_policy;
request_context_getter_ = request_context_getter;
request_context_getter_->AddObserver(this);
auto context = request_context_getter_->GetURLRequestContext();
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,
const std::string& value) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
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,
const std::string& value) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
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,
net::AuthChallengeInfo* auth_info) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
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(
scoped_refptr<net::AuthChallengeInfo> auth_info) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
AtomBrowserContext* browser_context,
const std::string& method,
const std::string& url,
+ const std::string& redirect_policy,
api::URLRequest* delegate);
void Terminate();
bool Write(scoped_refptr<const net::IOBufferWithSize> buffer, bool is_last);
void SetChunkedUpload(bool is_chunked_upload);
void Cancel();
+ void FollowRedirect();
void SetExtraHeader(const std::string& name, const std::string& value) const;
void RemoveExtraHeader(const std::string& name) const;
void PassLoginInformation(const base::string16& username,
protected:
// Overrides of net::URLRequest::Delegate
+ void OnReceivedRedirect(net::URLRequest* request,
+ const net::RedirectInfo& info,
+ bool* defer_redirect) override;
void OnAuthRequired(net::URLRequest* request,
net::AuthChallengeInfo* auth_info) override;
void OnResponseStarted(net::URLRequest* request) override;
void DoInitialize(scoped_refptr<net::URLRequestContextGetter>,
const std::string& method,
- const std::string& url);
+ const std::string& url,
+ const std::string& redirect_policy);
void DoTerminate();
void DoWriteBuffer(scoped_refptr<const net::IOBufferWithSize> buffer,
bool is_last);
void DoCancel();
+ void DoFollowRedirect();
void DoSetExtraHeader(const std::string& name,
const std::string& value) const;
void DoRemoveExtraHeader(const std::string& name) const;
void ReadResponse();
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(
scoped_refptr<net::AuthChallengeInfo> auth_info) const;
void InformDelegateResponseStarted(
scoped_refptr<net::URLRequestContextGetter> request_context_getter_;
bool is_chunked_upload_;
+ std::string redirect_policy_;
std::unique_ptr<net::ChunkedUploadDataStream> chunked_stream_;
std::unique_ptr<net::ChunkedUploadDataStream::Writer> chunked_stream_writer_;
std::vector<std::unique_ptr<net::UploadElementReader>>
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 = {
method: method,
- url: urlStr
+ url: urlStr,
+ redirect: redirectPolicy
}
if (options.session) {
if (options.session instanceof Session) {
return this._write(data, encoding, callback, true)
}
+ followRedirect () {
+ this.urlRequest.followRedirect()
+ }
+
abort () {
this.urlRequest.cancel()
}
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) {
const requestUrl = '/requestUrl'
try {