Adds a --deterministic-fetch flag to headless_shell

Adds a -deterministic-fetch flag to headless_shell which causes network
reuests to complete in the same order they're created in.  This removes
a significant source of network related non-determinism at the cost of
slower page loads.

BUG=546953

Review-Url: https://codereview.chromium.org/2352663003
Cr-Commit-Position: refs/heads/master@{#420657}
diff --git a/headless/BUILD.gn b/headless/BUILD.gn
index 572b116..a8a01a3 100644
--- a/headless/BUILD.gn
+++ b/headless/BUILD.gn
@@ -202,12 +202,16 @@
               "public/util/black_hole_protocol_handler.h",
               "public/util/deterministic_dispatcher.cc",
               "public/util/deterministic_dispatcher.h",
+              "public/util/deterministic_http_protocol_handler.cc",
+              "public/util/deterministic_http_protocol_handler.h",
               "public/util/error_reporter.cc",
               "public/util/error_reporter.h",
               "public/util/expedited_dispatcher.cc",
               "public/util/expedited_dispatcher.h",
               "public/util/generic_url_request_job.cc",
               "public/util/generic_url_request_job.h",
+              "public/util/http_url_fetcher.cc",
+              "public/util/http_url_fetcher.h",
               "public/util/in_memory_protocol_handler.cc",
               "public/util/in_memory_protocol_handler.h",
               "public/util/in_memory_request_job.cc",
diff --git a/headless/app/headless_shell.cc b/headless/app/headless_shell.cc
index 73e4d62..0c9ed16 100644
--- a/headless/app/headless_shell.cc
+++ b/headless/app/headless_shell.cc
@@ -25,6 +25,8 @@
 #include "headless/public/headless_devtools_client.h"
 #include "headless/public/headless_devtools_target.h"
 #include "headless/public/headless_web_contents.h"
+#include "headless/public/util/deterministic_dispatcher.h"
+#include "headless/public/util/deterministic_http_protocol_handler.h"
 #include "net/base/file_stream.h"
 #include "net/base/io_buffer.h"
 #include "net/base/ip_address.h"
@@ -73,7 +75,25 @@
   void OnStart(HeadlessBrowser* browser) {
     browser_ = browser;
 
-    browser_context_ = browser_->CreateBrowserContextBuilder().Build();
+    if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+            headless::switches::kDeterministicFetch)) {
+      deterministic_dispatcher_.reset(
+          new headless::DeterministicDispatcher(browser_->BrowserIOThread()));
+
+      headless::ProtocolHandlerMap protocol_handlers;
+      protocol_handlers[url::kHttpScheme] =
+          base::MakeUnique<headless::DeterministicHttpProtocolHandler>(
+              deterministic_dispatcher_.get(), browser->BrowserIOThread());
+      protocol_handlers[url::kHttpsScheme] =
+          base::MakeUnique<headless::DeterministicHttpProtocolHandler>(
+              deterministic_dispatcher_.get(), browser->BrowserIOThread());
+
+      browser_context_ = browser_->CreateBrowserContextBuilder()
+                             .SetProtocolHandlers(std::move(protocol_handlers))
+                             .Build();
+    } else {
+      browser_context_ = browser_->CreateBrowserContextBuilder().Build();
+    }
 
     HeadlessWebContents::Builder builder(
         browser_context_->CreateWebContentsBuilder());
@@ -335,6 +355,7 @@
   bool processed_page_ready_;
   std::unique_ptr<net::FileStream> screenshot_file_stream_;
   HeadlessBrowserContext* browser_context_;
+  std::unique_ptr<headless::DeterministicDispatcher> deterministic_dispatcher_;
 
   DISALLOW_COPY_AND_ASSIGN(HeadlessShell);
 };
diff --git a/headless/app/headless_shell_switches.cc b/headless/app/headless_shell_switches.cc
index a0afc69..c67b5df 100644
--- a/headless/app/headless_shell_switches.cc
+++ b/headless/app/headless_shell_switches.cc
@@ -7,6 +7,11 @@
 namespace headless {
 namespace switches {
 
+// Instructs headless_shell to cause network fetches to complete in order of
+// creation. This removes a significant source of network related
+// non-determinism at the cost of slower page loads.
+const char kDeterministicFetch[] = "deterministic-fetch";
+
 // Instructs headless_shell to print document.body.innerHTML to stdout.
 const char kDumpDom[] = "dump-dom";
 
diff --git a/headless/app/headless_shell_switches.h b/headless/app/headless_shell_switches.h
index f995cfd..a803c3c 100644
--- a/headless/app/headless_shell_switches.h
+++ b/headless/app/headless_shell_switches.h
@@ -7,6 +7,7 @@
 
 namespace headless {
 namespace switches {
+extern const char kDeterministicFetch[];
 extern const char kDumpDom[];
 extern const char kProxyServer[];
 extern const char kRemoteDebuggingAddress[];
diff --git a/headless/public/util/deterministic_http_protocol_handler.cc b/headless/public/util/deterministic_http_protocol_handler.cc
new file mode 100644
index 0000000..bb2c111
--- /dev/null
+++ b/headless/public/util/deterministic_http_protocol_handler.cc
@@ -0,0 +1,80 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/deterministic_http_protocol_handler.h"
+
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "headless/public/headless_browser_context.h"
+#include "headless/public/util/deterministic_dispatcher.h"
+#include "headless/public/util/generic_url_request_job.h"
+#include "headless/public/util/http_url_fetcher.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+
+namespace headless {
+
+class DeterministicHttpProtocolHandler::NopGenericURLRequestJobDelegate
+    : public GenericURLRequestJob::Delegate {
+ public:
+  NopGenericURLRequestJobDelegate() {}
+  ~NopGenericURLRequestJobDelegate() override {}
+
+  // GenericURLRequestJob::Delegate methods:
+  bool BlockOrRewriteRequest(
+      const GURL& url,
+      const std::string& method,
+      const std::string& referrer,
+      GenericURLRequestJob::RewriteCallback callback) override {
+    return false;
+  }
+
+  const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
+      const GURL& url,
+      const std::string& method,
+      const net::HttpRequestHeaders& request_headers) override {
+    return nullptr;
+  }
+
+  void OnResourceLoadComplete(const GURL& final_url,
+                              const std::string& mime_type,
+                              int http_response_code) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(NopGenericURLRequestJobDelegate);
+};
+
+DeterministicHttpProtocolHandler::DeterministicHttpProtocolHandler(
+    DeterministicDispatcher* deterministic_dispatcher,
+    scoped_refptr<base::SingleThreadTaskRunner> io_task_runner)
+    : deterministic_dispatcher_(deterministic_dispatcher),
+      io_task_runner_(io_task_runner),
+      nop_delegate_(new NopGenericURLRequestJobDelegate()) {}
+
+DeterministicHttpProtocolHandler::~DeterministicHttpProtocolHandler() {
+  if (url_request_context_)
+    io_task_runner_->DeleteSoon(FROM_HERE, url_request_context_.release());
+  if (url_request_job_factory_)
+    io_task_runner_->DeleteSoon(FROM_HERE, url_request_job_factory_.release());
+}
+
+net::URLRequestJob* DeterministicHttpProtocolHandler::MaybeCreateJob(
+    net::URLRequest* request,
+    net::NetworkDelegate* network_delegate) const {
+  if (!url_request_context_) {
+    DCHECK(io_task_runner_->BelongsToCurrentThread());
+    // Create our own URLRequestContext with an empty URLRequestJobFactoryImpl
+    // which lets us use the default http(s) RequestJobs.
+    url_request_context_.reset(new net::URLRequestContext());
+    url_request_context_->CopyFrom(request->context());
+    url_request_job_factory_.reset(new net::URLRequestJobFactoryImpl());
+    url_request_context_->set_job_factory(url_request_job_factory_.get());
+  }
+  return new GenericURLRequestJob(
+      request, network_delegate, deterministic_dispatcher_,
+      base::MakeUnique<HttpURLFetcher>(url_request_context_.get()),
+      nop_delegate_.get());
+}
+
+}  // namespace headless
diff --git a/headless/public/util/deterministic_http_protocol_handler.h b/headless/public/util/deterministic_http_protocol_handler.h
new file mode 100644
index 0000000..da0a70d
--- /dev/null
+++ b/headless/public/util/deterministic_http_protocol_handler.h
@@ -0,0 +1,59 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_DETERMINISTIC_HTTP_PROTOCOL_HANDLER_H_
+#define HEADLESS_PUBLIC_UTIL_DETERMINISTIC_HTTP_PROTOCOL_HANDLER_H_
+
+#include <memory>
+
+#include "base/single_thread_task_runner.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+class URLRequestContext;
+class URLRequestJobFactory;
+}  // namespace
+
+namespace headless {
+class DeterministicDispatcher;
+class HeadlessBrowserContext;
+
+// A deterministic protocol handler.  Requests made to this protocol handler
+// will return in order of creation, regardless of what order the network
+// returns them in.  This helps remove one large source of network related
+// non determinism at the cost of slower page loads.
+class DeterministicHttpProtocolHandler
+    : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+  // Note |deterministic_dispatcher| is expected to be shared across a number of
+  // protocol handlers, e.g. for http & https protocols.
+  DeterministicHttpProtocolHandler(
+      DeterministicDispatcher* deterministic_dispatcher,
+      scoped_refptr<base::SingleThreadTaskRunner> io_task_runner);
+  ~DeterministicHttpProtocolHandler() override;
+
+  net::URLRequestJob* MaybeCreateJob(
+      net::URLRequest* request,
+      net::NetworkDelegate* network_delegate) const override;
+
+ private:
+  class NopGenericURLRequestJobDelegate;
+
+  DeterministicDispatcher* deterministic_dispatcher_;  // NOT OWNED.
+  scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+  std::unique_ptr<NopGenericURLRequestJobDelegate> nop_delegate_;
+
+  // |url_request_context_| and |url_request_job_factory_| are lazily created on
+  // the IO thread. The URLRequestContext is setup to bypass any user-specified
+  // protocol handlers including this one. This is necessary to actually fetch
+  // http resources.
+  mutable std::unique_ptr<net::URLRequestContext> url_request_context_;
+  mutable std::unique_ptr<net::URLRequestJobFactory> url_request_job_factory_;
+
+  DISALLOW_COPY_AND_ASSIGN(DeterministicHttpProtocolHandler);
+};
+
+}  // namespace headless
+
+#endif  // HEADLESS_PUBLIC_UTIL_DETERMINISTIC_HTTP_PROTOCOL_HANDLER_H_
diff --git a/headless/public/util/generic_url_request_job.cc b/headless/public/util/generic_url_request_job.cc
index 0228ae84..9c7245b 100644
--- a/headless/public/util/generic_url_request_job.cc
+++ b/headless/public/util/generic_url_request_job.cc
@@ -48,12 +48,13 @@
 }
 
 void GenericURLRequestJob::Start() {
-  auto callback = [this](RewriteResult result, const GURL& url) {
+  auto callback = [this](RewriteResult result, const GURL& url,
+                         const std::string& method) {
     switch (result) {
       case RewriteResult::kAllow:
         // Note that we use the rewritten url for selecting cookies.
         // Also, rewriting does not affect the request initiator.
-        PrepareCookies(url, url::Origin(url));
+        PrepareCookies(url, method, url::Origin(url));
         break;
       case RewriteResult::kDeny:
         DispatchStartError(net::ERR_FILE_NOT_FOUND);
@@ -66,14 +67,15 @@
     }
   };
 
-  if (!delegate_->BlockOrRewriteRequest(request_->url(), request_->referrer(),
-                                        callback)) {
-    PrepareCookies(request()->url(),
+  if (!delegate_->BlockOrRewriteRequest(request_->url(), request_->method(),
+                                        request_->referrer(), callback)) {
+    PrepareCookies(request_->url(), request_->method(),
                    url::Origin(request_->first_party_for_cookies()));
   }
 }
 
 void GenericURLRequestJob::PrepareCookies(const GURL& rewritten_url,
+                                          const std::string& method,
                                           const url::Origin& site_for_cookies) {
   net::CookieStore* cookie_store = request_->context()->cookie_store();
   net::CookieOptions options;
@@ -98,11 +100,12 @@
   cookie_store->GetCookieListWithOptionsAsync(
       rewritten_url, options,
       base::Bind(&GenericURLRequestJob::OnCookiesAvailable,
-                 weak_factory_.GetWeakPtr(), rewritten_url));
+                 weak_factory_.GetWeakPtr(), rewritten_url, method));
 }
 
 void GenericURLRequestJob::OnCookiesAvailable(
     const GURL& rewritten_url,
+    const std::string& method,
     const net::CookieList& cookie_list) {
   // TODO(alexclarke): Set user agent.
   // Pass cookies, the referrer and any extra headers into the fetch request.
@@ -114,15 +117,16 @@
                                    request_->referrer());
 
   // The resource may have been supplied in the request.
-  const HttpResponse* matched_resource =
-      delegate_->MaybeMatchResource(rewritten_url, extra_request_headers_);
+  const HttpResponse* matched_resource = delegate_->MaybeMatchResource(
+      rewritten_url, method, extra_request_headers_);
 
   if (matched_resource) {
     OnFetchCompleteExtractHeaders(
         matched_resource->final_url, matched_resource->http_response_code,
         matched_resource->response_data, matched_resource->response_data_size);
   } else {
-    url_fetcher_->StartFetch(rewritten_url, extra_request_headers_, this);
+    url_fetcher_->StartFetch(rewritten_url, method, extra_request_headers_,
+                             this);
   }
 }
 
diff --git a/headless/public/util/generic_url_request_job.h b/headless/public/util/generic_url_request_job.h
index 63e5d8ee..514a5e1 100644
--- a/headless/public/util/generic_url_request_job.h
+++ b/headless/public/util/generic_url_request_job.h
@@ -38,8 +38,8 @@
                              public URLFetcher::ResultListener {
  public:
   enum class RewriteResult { kAllow, kDeny, kFailure };
-  using RewriteCallback =
-      std::function<void(RewriteResult result, const GURL& url)>;
+  using RewriteCallback = std::function<
+      void(RewriteResult result, const GURL& url, const std::string& method)>;
 
   struct HttpResponse {
     GURL final_url;
@@ -58,6 +58,7 @@
     // with the result, or false to indicate that no rewriting is necessary.
     // Called on an arbitrary thread.
     virtual bool BlockOrRewriteRequest(const GURL& url,
+                                       const std::string& method,
                                        const std::string& referrer,
                                        RewriteCallback callback) = 0;
 
@@ -65,6 +66,7 @@
     // Called on an arbitrary thread.
     virtual const HttpResponse* MaybeMatchResource(
         const GURL& url,
+        const std::string& method,
         const net::HttpRequestHeaders& request_headers) = 0;
 
     // Signals that a resource load has finished. Called on an arbitrary thread.
@@ -104,9 +106,11 @@
 
  private:
   void PrepareCookies(const GURL& rewritten_url,
+                      const std::string& method,
                       const url::Origin& site_for_cookies);
 
   void OnCookiesAvailable(const GURL& rewritten_url,
+                          const std::string& method,
                           const net::CookieList& cookie_list);
 
   std::unique_ptr<URLFetcher> url_fetcher_;
diff --git a/headless/public/util/generic_url_request_job_test.cc b/headless/public/util/generic_url_request_job_test.cc
index 474b03ee..989b5f6 100644
--- a/headless/public/util/generic_url_request_job_test.cc
+++ b/headless/public/util/generic_url_request_job_test.cc
@@ -53,10 +53,12 @@
   ~MockFetcher() override {}
 
   void StartFetch(const GURL& url,
+                  const std::string& method,
                   const net::HttpRequestHeaders& request_headers,
                   ResultListener* result_listener) override {
     // Record the request.
     fetch_request_->SetString("url", url.spec());
+    fetch_request_->SetString("method", method);
     std::unique_ptr<base::DictionaryValue> headers(new base::DictionaryValue);
     for (net::HttpRequestHeaders::Iterator it(request_headers); it.GetNext();) {
       headers->SetString(it.name(), it.value());
@@ -183,6 +185,7 @@
 
   std::string expected_request_json =
       "{\"url\": \"https://example.com/\","
+      " \"method\": \"GET\","
       " \"headers\": {"
       "   \"Accept\": \"text/plain\","
       "   \"Cookie\": \"\","
@@ -334,6 +337,7 @@
 
   std::string expected_request_json =
       "{\"url\": \"https://example.com/\","
+      " \"method\": \"GET\","
       " \"headers\": {"
       "   \"Cookie\": \"basic_cookie=1; secure_cookie=2; http_only_cookie=3\","
       "   \"Referer\": \"\""
diff --git a/headless/public/util/http_url_fetcher.cc b/headless/public/util/http_url_fetcher.cc
new file mode 100644
index 0000000..179c338
--- /dev/null
+++ b/headless/public/util/http_url_fetcher.cc
@@ -0,0 +1,187 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "headless/public/util/http_url_fetcher.h"
+
+#include "net/base/io_buffer.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+
+namespace headless {
+
+class HttpUrlFetcher::Delegate : public net::URLRequest::Delegate {
+ public:
+  Delegate(const GURL& rewritten_url,
+           const std::string& method,
+           const net::HttpRequestHeaders& request_headers,
+           const net::URLRequestContext* url_request_context,
+           ResultListener* result_listener);
+  ~Delegate() override;
+
+  // URLRequest::Delegate methods:
+  void OnAuthRequired(net::URLRequest* request,
+                      net::AuthChallengeInfo* auth_info) override;
+  void OnSSLCertificateError(net::URLRequest* request,
+                             const net::SSLInfo& ssl_info,
+                             bool fatal) override;
+  void OnResponseStarted(net::URLRequest* request, int net_error) override;
+  void OnReadCompleted(net::URLRequest* request, int num_bytes) override;
+
+ private:
+  enum { kBufSize = 4096 };
+
+  bool ConsumeBytesRead(net::URLRequest* request, int num_bytes);
+  void ReadBody(net::URLRequest* request);
+  void OnResponseCompleted(net::URLRequest* request, int net_error);
+
+  // Holds the error condition that was hit by the request, or OK.
+  int result_code_;
+
+  // Buffer that URLRequest writes into.
+  scoped_refptr<net::IOBuffer> buf_;
+
+  // The HTTP fetch.
+  std::unique_ptr<net::URLRequest> request_;
+
+  // Holds the bytes read so far.
+  std::string bytes_read_so_far_;
+
+  // The interface kn which to report any results.
+  ResultListener* result_listener_;  // NOT OWNED.
+};
+
+HttpUrlFetcher::Delegate::Delegate(
+    const GURL& rewritten_url,
+    const std::string& method,
+    const net::HttpRequestHeaders& request_headers,
+    const net::URLRequestContext* url_request_context,
+    ResultListener* result_listener)
+    : result_code_(net::OK),
+      buf_(new net::IOBuffer(kBufSize)),
+      request_(url_request_context->CreateRequest(rewritten_url,
+                                                  net::DEFAULT_PRIORITY,
+                                                  this)),
+      result_listener_(result_listener) {
+  request_->set_method(method);
+  request_->SetExtraRequestHeaders(request_headers);
+  request_->Start();
+}
+
+HttpUrlFetcher::Delegate::~Delegate() {}
+
+void HttpUrlFetcher::Delegate::OnAuthRequired(
+    net::URLRequest* request,
+    net::AuthChallengeInfo* auth_info) {
+  DCHECK_EQ(request, request_.get());
+  LOG(WARNING) << "Auth required to fetch URL, aborting.";
+  result_code_ = net::ERR_NOT_IMPLEMENTED;
+  request->CancelAuth();
+}
+
+void HttpUrlFetcher::Delegate::OnSSLCertificateError(
+    net::URLRequest* request,
+    const net::SSLInfo& ssl_info,
+    bool fatal) {
+  DCHECK_EQ(request, request_.get());
+
+  // Revocation check failures are not fatal.
+  if (net::IsCertStatusMinorError(ssl_info.cert_status)) {
+    request->ContinueDespiteLastError();
+    return;
+  }
+  LOG(WARNING) << "SSL certificate error, aborting.";
+
+  // Certificate errors are in same space as net errors.
+  result_code_ = net::MapCertStatusToNetError(ssl_info.cert_status);
+  request->Cancel();
+}
+
+void HttpUrlFetcher::Delegate::OnResponseStarted(net::URLRequest* request,
+                                                 int net_error) {
+  DCHECK_EQ(request, request_.get());
+  DCHECK_NE(net::ERR_IO_PENDING, net_error);
+
+  if (net_error != net::OK) {
+    OnResponseCompleted(request, net_error);
+    return;
+  }
+
+  ReadBody(request);
+}
+
+void HttpUrlFetcher::Delegate::OnReadCompleted(net::URLRequest* request,
+                                               int num_bytes) {
+  DCHECK_EQ(request, request_.get());
+  DCHECK_NE(net::ERR_IO_PENDING, num_bytes);
+
+  if (ConsumeBytesRead(request, num_bytes)) {
+    // Keep reading.
+    ReadBody(request);
+  }
+}
+
+void HttpUrlFetcher::Delegate::ReadBody(net::URLRequest* request) {
+  // Read as many bytes as are available synchronously.
+  while (true) {
+    int num_bytes = request->Read(buf_.get(), kBufSize);
+    if (num_bytes == net::ERR_IO_PENDING)
+      return;
+
+    if (num_bytes < 0) {
+      OnResponseCompleted(request, num_bytes);
+      return;
+    }
+
+    if (!ConsumeBytesRead(request, num_bytes))
+      return;
+  }
+}
+
+bool HttpUrlFetcher::Delegate::ConsumeBytesRead(net::URLRequest* request,
+                                                int num_bytes) {
+  if (num_bytes <= 0) {
+    // Error while reading, or EOF.
+    OnResponseCompleted(request, num_bytes);
+    return false;
+  }
+
+  bytes_read_so_far_.append(buf_->data(), num_bytes);
+  return true;
+}
+
+void HttpUrlFetcher::Delegate::OnResponseCompleted(net::URLRequest* request,
+                                                   int net_error) {
+  DCHECK_EQ(request, request_.get());
+
+  if (result_code_ != net::OK) {
+    result_listener_->OnFetchStartError(static_cast<net::Error>(result_code_));
+    return;
+  }
+
+  if (net_error != net::OK) {
+    result_listener_->OnFetchStartError(static_cast<net::Error>(net_error));
+    return;
+  }
+
+  result_listener_->OnFetchCompleteExtractHeaders(
+      request->url(), request->GetResponseCode(), bytes_read_so_far_.c_str(),
+      bytes_read_so_far_.size());
+}
+
+HttpUrlFetcher::HttpUrlFetcher(
+    const net::URLRequestContext* url_request_context)
+    : url_request_context_(url_request_context) {}
+
+HttpUrlFetcher::~HttpUrlFetcher() {}
+
+void HttpUrlFetcher::StartFetch(const GURL& rewritten_url,
+                                const std::string& method,
+                                const net::HttpRequestHeaders& request_headers,
+                                ResultListener* result_listener) {
+  delagate_.reset(new Delegate(rewritten_url, method, request_headers,
+                               url_request_context_, result_listener));
+}
+
+}  // namespace headless
diff --git a/headless/public/util/http_url_fetcher.h b/headless/public/util/http_url_fetcher.h
new file mode 100644
index 0000000..2b17287
--- /dev/null
+++ b/headless/public/util/http_url_fetcher.h
@@ -0,0 +1,42 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
+#define HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
+
+#include "base/macros.h"
+#include "headless/public/util/url_fetcher.h"
+
+namespace net {
+class URLRequestContext;
+class URLRequestJobFactory;
+}  // namespace
+
+namespace headless {
+
+// A simple URLFetcher that uses a net::URLRequestContext to as a back end for
+// http(s) fetches.
+class HttpURLFetcher : public URLFetcher {
+ public:
+  explicit HttpURLFetcher(const net::URLRequestContext* url_request_context);
+  ~HttpURLFetcher() override;
+
+  // URLFetcher implementation:
+  void StartFetch(const GURL& rewritten_url,
+                  const std::string& method,
+                  const net::HttpRequestHeaders& request_headers,
+                  ResultListener* result_listener) override;
+
+ private:
+  class Delegate;
+
+  const net::URLRequestContext* url_request_context_;
+  std::unique_ptr<Delegate> delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(HttpURLFetcher);
+};
+
+}  // namespace headless
+
+#endif  // HEADLESS_PUBLIC_UTIL_HTTP_URL_FETCHER_H_
diff --git a/headless/public/util/testing/generic_url_request_mocks.cc b/headless/public/util/testing/generic_url_request_mocks.cc
index 4060950..08ff33d 100644
--- a/headless/public/util/testing/generic_url_request_mocks.cc
+++ b/headless/public/util/testing/generic_url_request_mocks.cc
@@ -19,16 +19,18 @@
 
 bool MockGenericURLRequestJobDelegate::BlockOrRewriteRequest(
     const GURL& url,
+    const std::string& method,
     const std::string& referrer,
     GenericURLRequestJob::RewriteCallback callback) {
   if (should_block_)
-    callback(GenericURLRequestJob::RewriteResult::kDeny, GURL());
+    callback(GenericURLRequestJob::RewriteResult::kDeny, GURL(), method);
   return should_block_;
 }
 
 const GenericURLRequestJob::HttpResponse*
 MockGenericURLRequestJobDelegate::MaybeMatchResource(
     const GURL& url,
+    const std::string& method,
     const net::HttpRequestHeaders& request_headers) {
   return nullptr;
 }
diff --git a/headless/public/util/testing/generic_url_request_mocks.h b/headless/public/util/testing/generic_url_request_mocks.h
index 0af68d0..7dc683f 100644
--- a/headless/public/util/testing/generic_url_request_mocks.h
+++ b/headless/public/util/testing/generic_url_request_mocks.h
@@ -33,11 +33,13 @@
 
   bool BlockOrRewriteRequest(
       const GURL& url,
+      const std::string& method,
       const std::string& referrer,
       GenericURLRequestJob::RewriteCallback callback) override;
 
   const GenericURLRequestJob::HttpResponse* MaybeMatchResource(
       const GURL& url,
+      const std::string& method,
       const net::HttpRequestHeaders& request_headers) override;
 
   void OnResourceLoadComplete(const GURL& final_url,
diff --git a/headless/public/util/url_fetcher.h b/headless/public/util/url_fetcher.h
index 115302f..ac4bbc5 100644
--- a/headless/public/util/url_fetcher.h
+++ b/headless/public/util/url_fetcher.h
@@ -22,8 +22,6 @@
 namespace headless {
 
 // An interface for fetching URLs. Note these are only intended to be used once.
-// TODO(alexclarke): Implement a URLFetcher that can backend onto a URLRequest
-// and hook this up in headless_shell under a flag.
 class URLFetcher {
  public:
   URLFetcher() {}
@@ -63,6 +61,7 @@
 
   // Instructs the sub-class to fetch the resource.
   virtual void StartFetch(const GURL& rewritten_url,
+                          const std::string& method,
                           const net::HttpRequestHeaders& request_headers,
                           ResultListener* result_listener) = 0;