diff --git a/atom/browser/api/atom_api_protocol.cc b/atom/browser/api/atom_api_protocol.cc index 083ea32b2010..9bc2de4b42a9 100644 --- a/atom/browser/api/atom_api_protocol.cc +++ b/atom/browser/api/atom_api_protocol.cc @@ -8,6 +8,7 @@ #include "atom/browser/net/adapter_request_job.h" #include "atom/browser/net/atom_url_request_job_factory.h" #include "atom/common/native_mate_converters/file_path_converter.h" +#include "atom/common/native_mate_converters/gurl_converter.h" #include "content/public/browser/browser_thread.h" #include "native_mate/callback.h" #include "native_mate/dictionary.h" @@ -115,14 +116,24 @@ class CustomProtocolRequestJob : public AdapterRequestJob { GetWeakPtr(), path)); return; } else if (name == "RequestErrorJob") { - // Default value net::ERR_NOT_IMPLEMENTED - int error = -11; + int error = net::ERR_NOT_IMPLEMENTED; dict.Get("error", &error); BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, base::Bind(&AdapterRequestJob::CreateErrorJobAndStart, GetWeakPtr(), error)); return; + } else if (name == "RequestHttpJob") { + GURL url; + std::string method, referrer; + dict.Get("url", &url); + dict.Get("method", &method); + dict.Get("referrer", &referrer); + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, + base::Bind(&AdapterRequestJob::CreateHttpJobAndStart, + GetWeakPtr(), url, method, referrer)); + return; } } diff --git a/atom/browser/api/lib/protocol.coffee b/atom/browser/api/lib/protocol.coffee index c2db7800c08d..ff4bc5ba9a66 100644 --- a/atom/browser/api/lib/protocol.coffee +++ b/atom/browser/api/lib/protocol.coffee @@ -34,4 +34,8 @@ protocol.RequestErrorJob = class RequestErrorJob constructor: (@error) -> +protocol.RequestHttpJob = +class RequestHttpJob + constructor: ({@url, @method, @referrer}) -> + module.exports = protocol diff --git a/atom/browser/net/adapter_request_job.cc b/atom/browser/net/adapter_request_job.cc index af3b02f150f4..08331829b550 100644 --- a/atom/browser/net/adapter_request_job.cc +++ b/atom/browser/net/adapter_request_job.cc @@ -4,8 +4,10 @@ #include "atom/browser/net/adapter_request_job.h" +#include "atom/browser/atom_browser_context.h" #include "base/threading/sequenced_worker_pool.h" #include "atom/browser/net/url_request_buffer_job.h" +#include "atom/browser/net/url_request_fetch_job.h" #include "atom/browser/net/url_request_string_job.h" #include "atom/browser/net/asar/url_request_asar_job.h" #include "atom/common/asar/asar_util.h" @@ -66,6 +68,14 @@ bool AdapterRequestJob::GetCharset(std::string* charset) { return real_job_->GetCharset(charset); } +void AdapterRequestJob::GetResponseInfo(net::HttpResponseInfo* info) { + real_job_->GetResponseInfo(info); +} + +int AdapterRequestJob::GetResponseCode() const { + return real_job_->GetResponseCode(); +} + base::WeakPtr AdapterRequestJob::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } @@ -104,6 +114,19 @@ void AdapterRequestJob::CreateFileJobAndStart(const base::FilePath& path) { real_job_->Start(); } +void AdapterRequestJob::CreateHttpJobAndStart(const GURL& url, + const std::string& method, + const std::string& referrer) { + if (!url.is_valid()) { + CreateErrorJobAndStart(net::ERR_INVALID_URL); + return; + } + + real_job_ = new URLRequestFetchJob(request(), network_delegate(), url, + method, referrer); + real_job_->Start(); +} + void AdapterRequestJob::CreateJobFromProtocolHandlerAndStart() { real_job_ = protocol_handler_->MaybeCreateJob(request(), network_delegate()); diff --git a/atom/browser/net/adapter_request_job.h b/atom/browser/net/adapter_request_job.h index d87207464c08..d5e814d214d4 100644 --- a/atom/browser/net/adapter_request_job.h +++ b/atom/browser/net/adapter_request_job.h @@ -9,6 +9,7 @@ #include "base/memory/ref_counted_memory.h" #include "base/memory/weak_ptr.h" +#include "net/url_request/url_request_context.h" #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_job_factory.h" #include "v8/include/v8.h" @@ -40,6 +41,8 @@ class AdapterRequestJob : public net::URLRequestJob { net::Filter* SetupFilter() const override; bool GetMimeType(std::string* mime_type) const override; bool GetCharset(std::string* charset) override; + void GetResponseInfo(net::HttpResponseInfo* info) override; + int GetResponseCode() const override; base::WeakPtr GetWeakPtr(); @@ -56,6 +59,9 @@ class AdapterRequestJob : public net::URLRequestJob { const std::string& charset, scoped_refptr data); void CreateFileJobAndStart(const base::FilePath& path); + void CreateHttpJobAndStart(const GURL& url, + const std::string& method, + const std::string& referrer); void CreateJobFromProtocolHandlerAndStart(); private: diff --git a/atom/browser/net/url_request_fetch_job.cc b/atom/browser/net/url_request_fetch_job.cc new file mode 100644 index 000000000000..f77379aed6c2 --- /dev/null +++ b/atom/browser/net/url_request_fetch_job.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/net/url_request_fetch_job.h" + +#include +#include + +#include "atom/browser/atom_browser_context.h" +#include "base/strings/string_util.h" +#include "net/base/io_buffer.h" +#include "net/base/net_errors.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_response_writer.h" +#include "net/url_request/url_request_status.h" + +namespace atom { + +namespace { + +// Convert string to RequestType. +net::URLFetcher::RequestType GetRequestType(const std::string& raw) { + std::string method = StringToUpperASCII(raw); + if (method.empty() || method == "GET") + return net::URLFetcher::GET; + else if (method == "POST") + return net::URLFetcher::POST; + else if (method == "HEAD") + return net::URLFetcher::HEAD; + else if (method == "DELETE") + return net::URLFetcher::DELETE_REQUEST; + else if (method == "PUT") + return net::URLFetcher::PUT; + else if (method == "PATCH") + return net::URLFetcher::PATCH; + else // Use "GET" as fallback. + return net::URLFetcher::GET; +} + +// Pipe the response writer back to URLRequestFetchJob. +class ResponsePiper : public net::URLFetcherResponseWriter { + public: + explicit ResponsePiper(URLRequestFetchJob* job) + : first_write_(true), job_(job) {} + + // net::URLFetcherResponseWriter: + int Initialize(const net::CompletionCallback& callback) override { + return net::OK; + } + int Write(net::IOBuffer* buffer, + int num_bytes, + const net::CompletionCallback& callback) override { + if (first_write_) { + // The URLFetcherResponseWriter doesn't have an event when headers have + // been read, so we have to emulate by hooking to first write event. + job_->HeadersCompleted(); + first_write_ = false; + } + return job_->DataAvailable(buffer, num_bytes); + } + int Finish(const net::CompletionCallback& callback) override { + return net::OK; + } + + private: + bool first_write_; + URLRequestFetchJob* job_; + + DISALLOW_COPY_AND_ASSIGN(ResponsePiper); +}; + +} // namespace + +URLRequestFetchJob::URLRequestFetchJob( + net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const GURL& url, + const std::string& method, + const std::string& referrer) + : net::URLRequestJob(request, network_delegate), + pending_buffer_size_(0) { + // Use |request|'s method if |method| is not specified. + net::URLFetcher::RequestType request_type; + if (method.empty()) + request_type = GetRequestType(request->method()); + else + request_type = GetRequestType(method); + + fetcher_.reset(net::URLFetcher::Create(url, request_type, this)); + auto context = AtomBrowserContext::Get()->url_request_context_getter(); + fetcher_->SetRequestContext(context); + fetcher_->SaveResponseWithWriter(make_scoped_ptr(new ResponsePiper(this))); + + // Use |request|'s referrer if |referrer| is not specified. + if (referrer.empty()) { + fetcher_->SetReferrer(request->referrer()); + } else { + fetcher_->SetReferrer(referrer); + } + + // Use |request|'s headers. + net::HttpRequestHeaders headers; + if (request->GetFullRequestHeaders(&headers)) { + fetcher_->SetExtraRequestHeaders(headers.ToString()); + } +} + +void URLRequestFetchJob::HeadersCompleted() { + response_info_.reset(new net::HttpResponseInfo); + response_info_->headers = fetcher_->GetResponseHeaders(); + NotifyHeadersComplete(); +} + +int URLRequestFetchJob::DataAvailable(net::IOBuffer* buffer, int num_bytes) { + // Clear the IO_PENDING status. + SetStatus(net::URLRequestStatus()); + // Do nothing if pending_buffer_ is empty, i.e. there's no ReadRawData() + // operation waiting for IO completion. + if (!pending_buffer_.get()) + return net::ERR_IO_PENDING; + + // pending_buffer_ is set to the IOBuffer instance provided to ReadRawData() + // by URLRequestJob. + + int bytes_read = std::min(num_bytes, pending_buffer_size_); + memcpy(pending_buffer_->data(), buffer->data(), bytes_read); + + // Clear the buffers before notifying the read is complete, so that it is + // safe for the observer to read. + pending_buffer_ = nullptr; + pending_buffer_size_ = 0; + + NotifyReadComplete(bytes_read); + return bytes_read; +} + +void URLRequestFetchJob::Start() { + fetcher_->Start(); +} + +void URLRequestFetchJob::Kill() { + URLRequestJob::Kill(); + fetcher_.reset(); +} + +bool URLRequestFetchJob::ReadRawData(net::IOBuffer* dest, + int dest_size, + int* bytes_read) { + pending_buffer_ = dest; + pending_buffer_size_ = dest_size; + SetStatus(net::URLRequestStatus(net::URLRequestStatus::IO_PENDING, 0)); + return false; +} + +bool URLRequestFetchJob::GetMimeType(std::string* mime_type) const { + if (!response_info_) + return false; + + return response_info_->headers->GetMimeType(mime_type); +} + +void URLRequestFetchJob::GetResponseInfo(net::HttpResponseInfo* info) { + if (response_info_) + *info = *response_info_; +} + +int URLRequestFetchJob::GetResponseCode() const { + if (!response_info_) + return -1; + + return response_info_->headers->response_code(); +} + +void URLRequestFetchJob::OnURLFetchComplete(const net::URLFetcher* source) { + pending_buffer_ = nullptr; + pending_buffer_size_ = 0; + NotifyDone(fetcher_->GetStatus()); + if (fetcher_->GetStatus().is_success()) + NotifyReadComplete(0); +} + +} // namespace atom diff --git a/atom/browser/net/url_request_fetch_job.h b/atom/browser/net/url_request_fetch_job.h new file mode 100644 index 000000000000..7975aa715eec --- /dev/null +++ b/atom/browser/net/url_request_fetch_job.h @@ -0,0 +1,51 @@ +// Copyright (c) 2015 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_NET_URL_REQUEST_FETCH_JOB_H_ +#define ATOM_BROWSER_NET_URL_REQUEST_FETCH_JOB_H_ + +#include + +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_job.h" + +namespace atom { + +class URLRequestFetchJob : public net::URLRequestJob, + public net::URLFetcherDelegate { + public: + URLRequestFetchJob(net::URLRequest* request, + net::NetworkDelegate* network_delegate, + const GURL& url, + const std::string& method, + const std::string& referrer); + + void HeadersCompleted(); + int DataAvailable(net::IOBuffer* buffer, int num_bytes); + + // net::URLRequestJob: + void Start() override; + void Kill() override; + bool ReadRawData(net::IOBuffer* buf, + int buf_size, + int* bytes_read) override; + bool GetMimeType(std::string* mime_type) const override; + void GetResponseInfo(net::HttpResponseInfo* info) override; + int GetResponseCode() const override; + + // net::URLFetcherDelegate: + void OnURLFetchComplete(const net::URLFetcher* source) override; + + private: + scoped_ptr fetcher_; + scoped_refptr pending_buffer_; + int pending_buffer_size_; + scoped_ptr response_info_; + + DISALLOW_COPY_AND_ASSIGN(URLRequestFetchJob); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_NET_URL_REQUEST_FETCH_JOB_H_ diff --git a/docs/api/protocol.md b/docs/api/protocol.md index 181a2e57f733..ba62bf301f41 100644 --- a/docs/api/protocol.md +++ b/docs/api/protocol.md @@ -84,12 +84,21 @@ Create a request job which sends a string as response. Create a request job which sends a buffer as response. +## Class: protocol.RequestHttpJob(options) + +* `options` Object + * `url` String + * `method` String - Default is `GET` + * `referrer` String + +Send a request to `url` and pipe the response back. + ## Class: protocol.RequestErrorJob(code) * `code` Integer Create a request job which sets appropriate network error message to console. -Default message is `net::ERR_NOT_IMPLEMENTED`. Code should be in the following +Default message is `net::ERR_NOT_IMPLEMENTED`. Code should be in the following range. * Ranges: diff --git a/filenames.gypi b/filenames.gypi index 6ece2ba634c7..ab523c188beb 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -147,11 +147,13 @@ 'atom/browser/net/atom_url_request_job_factory.cc', 'atom/browser/net/atom_url_request_job_factory.h', 'atom/browser/net/http_protocol_handler.cc', - 'atom/browser/net/http_protocol_handler.h', + 'atom/browser/net/http_protocol_handler.h', 'atom/browser/net/url_request_string_job.cc', 'atom/browser/net/url_request_string_job.h', 'atom/browser/net/url_request_buffer_job.cc', 'atom/browser/net/url_request_buffer_job.h', + 'atom/browser/net/url_request_fetch_job.cc', + 'atom/browser/net/url_request_fetch_job.h', 'atom/browser/ui/accelerator_util.cc', 'atom/browser/ui/accelerator_util.h', 'atom/browser/ui/accelerator_util_mac.mm', diff --git a/spec/api-protocol-spec.coffee b/spec/api-protocol-spec.coffee index 5a7bda16555d..3ad1cbd91fad 100644 --- a/spec/api-protocol-spec.coffee +++ b/spec/api-protocol-spec.coffee @@ -1,5 +1,6 @@ assert = require 'assert' ipc = require 'ipc' +http = require 'http' path = require 'path' remote = require 'remote' protocol = remote.require 'protocol' @@ -73,6 +74,28 @@ describe 'protocol module', -> protocol.unregisterProtocol 'atom-error-job' done() + it 'returns RequestHttpJob should send respone', (done) -> + server = http.createServer (req, res) -> + res.writeHead(200, {'Content-Type': 'text/plain'}) + res.end('hello') + server.close() + server.listen 0, '127.0.0.1', -> + {port} = server.address() + url = "http://127.0.0.1:#{port}" + job = new protocol.RequestHttpJob({url}) + handler = remote.createFunctionWithReturnValue job + protocol.registerProtocol 'atom-http-job', handler + + $.ajax + url: 'atom-http-job://fake-host' + success: (data) -> + assert.equal data, 'hello' + protocol.unregisterProtocol 'atom-http-job' + done() + error: (xhr, errorType, error) -> + assert false, 'Got error: ' + errorType + ' ' + error + protocol.unregisterProtocol 'atom-http-job' + it 'returns RequestBufferJob should send buffer', (done) -> data = new Buffer("hello") job = new protocol.RequestBufferJob(data: data)