Enabling quic_server to act as a HTTP proxy serving QUIC clients.
To add the functionality of a reverse proxy to the QUIC server, 2 classes
were added:
1. QuicHttpProxyBackend: Implements the interface: QuicSimpleServerBackend
to fetch the response from a backend server. Creates a proxy thread and manages an instance of
net::URLRequestContext within that thread to make HTTP calls to a backend
server.
2. QuicHttpProxyBackendStream: Created on a per-stream basis, manages an
instance of the class net::URLRequest to make a single HTTP call to the
backend server using the context created by QuicHttpProxyBackend.
Run As a Proxy
To run the quic_server as a reverse proxy, run
with --mode=proxy. The param --quic_proxy_backend_url specifies the
backend server from which the response is fetched. For instance,
./out/Default/quic_server \
--certificate_file=net/tools/quic/certs/out/leaf_cert.pem \
--key_file=net/tools/quic/certs/out/leaf_cert.pkcs8 \
--mode=proxy \
--quic_proxy_backend_url=http://localhost
R=rch@chromium.org, zhongyi@chromium.org
Change-Id: I79b7d66eb6bba9628d99181def004c0a5ea2e214
Reviewed-on: https://chromium-review.googlesource.com/1128323
Commit-Queue: Ryan Hamilton <rch@chromium.org>
Reviewed-by: Ryan Hamilton <rch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577650}
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 9c8dee6..957272b 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -3232,6 +3232,10 @@
"third_party/quic/tools/quic_spdy_client_base.h",
"tools/quic/quic_client_message_loop_network_helper.cc",
"tools/quic/quic_client_message_loop_network_helper.h",
+ "tools/quic/quic_http_proxy_backend.cc",
+ "tools/quic/quic_http_proxy_backend.h",
+ "tools/quic/quic_http_proxy_backend_stream.cc",
+ "tools/quic/quic_http_proxy_backend_stream.h",
"tools/quic/quic_simple_client.cc",
"tools/quic/quic_simple_client.h",
"tools/quic/quic_simple_per_connection_packet_writer.cc",
@@ -5283,6 +5287,8 @@
"third_party/quic/tools/quic_server_test.cc",
"third_party/quic/tools/quic_simple_server_session_test.cc",
"third_party/quic/tools/quic_simple_server_stream_test.cc",
+ "tools/quic/quic_http_proxy_backend_stream_test.cc",
+ "tools/quic/quic_http_proxy_backend_test.cc",
"tools/quic/quic_simple_server_session_helper_test.cc",
"tools/quic/quic_simple_server_test.cc",
]
diff --git a/net/tools/quic/quic_http_proxy_backend.cc b/net/tools/quic/quic_http_proxy_backend.cc
new file mode 100644
index 0000000..610c98f
--- /dev/null
+++ b/net/tools/quic/quic_http_proxy_backend.cc
@@ -0,0 +1,191 @@
+// Copyright 2018 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 <limits.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <iostream>
+#include <limits>
+#include <map>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "build/build_config.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/url_util.h"
+#include "net/cert/cert_verifier.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/proxy_resolution/proxy_config_service_fixed.h"
+#include "net/ssl/channel_id_service.h"
+#include "net/tools/quic/quic_http_proxy_backend.h"
+#include "net/tools/quic/quic_http_proxy_backend_stream.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+#include "net/url_request/url_request_interceptor.h"
+
+namespace net {
+
+QuicHttpProxyBackend::QuicHttpProxyBackend()
+ : context_(nullptr), thread_initialized_(false), proxy_thread_(nullptr) {}
+
+QuicHttpProxyBackend::~QuicHttpProxyBackend() {
+ backend_stream_map_.clear();
+ thread_initialized_ = false;
+ proxy_task_runner_->DeleteSoon(FROM_HERE, context_.release());
+ if (proxy_thread_ != nullptr) {
+ LOG(INFO) << "QUIC Proxy thread: " << proxy_thread_->thread_name()
+ << " has stopped !";
+ proxy_thread_.reset();
+ }
+}
+
+bool QuicHttpProxyBackend::InitializeBackend(const std::string& backend_url) {
+ if (!ValidateBackendUrl(backend_url)) {
+ return false;
+ }
+ if (proxy_thread_ == nullptr) {
+ proxy_thread_ = std::make_unique<base::Thread>("quic proxy thread");
+ base::Thread::Options options;
+ options.message_loop_type = base::MessageLoop::TYPE_IO;
+ bool result = proxy_thread_->StartWithOptions(options);
+ proxy_task_runner_ = proxy_thread_->task_runner();
+ CHECK(result);
+ }
+ thread_initialized_ = true;
+ return true;
+}
+
+bool QuicHttpProxyBackend::ValidateBackendUrl(const std::string& backend_url) {
+ backend_url_ = GURL(backend_url);
+ // Only Http(s) backend supported
+ if (!backend_url_.is_valid() || !backend_url_.SchemeIsHTTPOrHTTPS()) {
+ LOG(ERROR) << "QUIC Proxy Backend URL '" << backend_url
+ << "' is not valid !";
+ return false;
+ }
+
+ LOG(INFO)
+ << "Successfully configured to run as a QUIC Proxy with Backend URL: "
+ << backend_url_.spec();
+ return true;
+}
+
+bool QuicHttpProxyBackend::IsBackendInitialized() const {
+ return thread_initialized_;
+}
+
+void QuicHttpProxyBackend::FetchResponseFromBackend(
+ const spdy::SpdyHeaderBlock& request_headers,
+ const std::string& incoming_body,
+ QuicSimpleServerBackend::RequestHandler* quic_server_stream) {
+ QuicHttpProxyBackendStream* proxy_backend_stream =
+ InitializeQuicProxyBackendStream(quic_server_stream);
+
+ LOG(INFO) << " Forwarding QUIC request to the Backend Thread Asynchronously.";
+ if (proxy_backend_stream == nullptr ||
+ proxy_backend_stream->SendRequestToBackend(&request_headers,
+ incoming_body) != true) {
+ std::list<quic::QuicBackendResponse::ServerPushInfo> empty_resources;
+ quic_server_stream->OnResponseBackendComplete(nullptr, empty_resources);
+ }
+}
+
+QuicHttpProxyBackendStream*
+QuicHttpProxyBackend::InitializeQuicProxyBackendStream(
+ QuicSimpleServerBackend::RequestHandler* quic_server_stream) {
+ if (!thread_initialized_) {
+ return nullptr;
+ }
+ QuicHttpProxyBackendStream* proxy_backend_stream =
+ new QuicHttpProxyBackendStream(this);
+ proxy_backend_stream->set_delegate(quic_server_stream);
+ proxy_backend_stream->Initialize(quic_server_stream->connection_id(),
+ quic_server_stream->stream_id(),
+ quic_server_stream->peer_host());
+ {
+ // Aquire write lock for this scope
+ base::AutoLock lock(backend_stream_mutex_);
+
+ auto inserted = backend_stream_map_.insert(std::make_pair(
+ quic_server_stream, base::WrapUnique(proxy_backend_stream)));
+ DCHECK(inserted.second);
+ }
+ return proxy_backend_stream;
+}
+
+void QuicHttpProxyBackend::CloseBackendResponseStream(
+ QuicSimpleServerBackend::RequestHandler* quic_server_stream) {
+ // Clean close of the backend stream handler
+ if (quic_server_stream == nullptr) {
+ return;
+ }
+ // Cleanup the handler on the proxy thread, since it owns the url_request
+ if (!proxy_task_runner_->BelongsToCurrentThread()) {
+ proxy_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&QuicHttpProxyBackend::CloseBackendResponseStream,
+ base::Unretained(this), quic_server_stream));
+ } else {
+ // Aquire write lock for this scope and cancel if the request is still
+ // pending
+ base::AutoLock lock(backend_stream_mutex_);
+ QuicHttpProxyBackendStream* proxy_backend_stream = nullptr;
+
+ ProxyBackendStreamMap::iterator it =
+ backend_stream_map_.find(quic_server_stream);
+ if (it != backend_stream_map_.end()) {
+ proxy_backend_stream = it->second.get();
+ proxy_backend_stream->CancelRequest();
+ proxy_backend_stream->reset_delegate();
+ LOG(INFO) << " Quic Proxy cleaned-up backend handler on context/main "
+ "thread for quic_conn_id: "
+ << proxy_backend_stream->quic_connection_id()
+ << " quic_stream_id: "
+ << proxy_backend_stream->quic_stream_id();
+ size_t erased = backend_stream_map_.erase(quic_server_stream);
+ DCHECK_EQ(1u, erased);
+ }
+ }
+}
+
+void QuicHttpProxyBackend::InitializeURLRequestContext() {
+ DCHECK(context_ == nullptr);
+ net::URLRequestContextBuilder context_builder;
+ // Quic reverse proxy does not cache HTTP objects
+ context_builder.DisableHttpCache();
+ // Enable HTTP2, but disable QUIC on the backend
+ context_builder.SetSpdyAndQuicEnabled(true /* http2 */, false /* quic */);
+
+#if defined(OS_LINUX)
+ // On Linux, use a fixed ProxyConfigService, since the default one
+ // depends on glib.
+ context_builder.set_proxy_config_service(
+ std::make_unique<ProxyConfigServiceFixed>(
+ ProxyConfigWithAnnotation::CreateDirect()));
+#endif
+
+ // Disable net::CookieStore and net::ChannelIDService.
+ context_builder.SetCookieAndChannelIdStores(nullptr, nullptr);
+ context_ = context_builder.Build();
+}
+
+net::URLRequestContext* QuicHttpProxyBackend::GetURLRequestContext() {
+ // Access to URLRequestContext is only available on Backend Thread
+ DCHECK(proxy_task_runner_->BelongsToCurrentThread());
+ if (context_ == nullptr) {
+ InitializeURLRequestContext();
+ }
+ return context_.get();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+QuicHttpProxyBackend::GetProxyTaskRunner() const {
+ return proxy_task_runner_;
+}
+
+} // namespace net
diff --git a/net/tools/quic/quic_http_proxy_backend.h b/net/tools/quic/quic_http_proxy_backend.h
new file mode 100644
index 0000000..c5811e6
--- /dev/null
+++ b/net/tools/quic/quic_http_proxy_backend.h
@@ -0,0 +1,110 @@
+// Copyright 2018 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.
+//
+// The proxy functionality is implemented as a separate thread namely
+// “quic proxy thread”, managed by an instance of the QuicHttpProxyBackend
+// class. The QuicHttpProxyBackend instance also manages an instance of the
+// class net::URLRequestContext, that manages a single context for all the
+// HTTP calls made to the backend server. Finally, the QuicHttpProxyBackend
+// instance owns (creates/ destroys) the instances of QuicHttpProxyBackendStream
+// to avoid orphan pointers of QuicHttpProxyBackendStream when the corresponding
+// QUIC connection is destroyed on the main thread due to several reasons. The
+// QUIC connection management and protocol parsing is performed by the main/quic
+// thread, in the same way as the toy QUIC server.
+//
+// quic_http_proxy_backend_stream.h has a description of threads, the flow
+// of packets in QUIC proxy in the forward and reverse directions.
+
+#ifndef NET_TOOLS_QUIC_QUIC_HTTP_PROXY_BACKEND_H_
+#define NET_TOOLS_QUIC_QUIC_HTTP_PROXY_BACKEND_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <queue>
+
+#include "base/base64.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "net/third_party/quic/tools/quic_simple_server_backend.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_builder.h"
+#include "url/gurl.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace quic {
+class QuicSimpleServerBackend;
+} // namespace quic
+
+namespace net {
+class QuicHttpProxyBackendStream;
+
+// Manages the context to proxy HTTP requests to the backend server
+// Owns instance of net::URLRequestContext.
+class QuicHttpProxyBackend : public quic::QuicSimpleServerBackend {
+ public:
+ explicit QuicHttpProxyBackend();
+ ~QuicHttpProxyBackend() override;
+
+ // Must be called from the backend thread of the quic proxy
+ net::URLRequestContext* GetURLRequestContext();
+ scoped_refptr<base::SingleThreadTaskRunner> GetProxyTaskRunner() const;
+
+ using ProxyBackendStreamMap =
+ std::unordered_map<quic::QuicSimpleServerBackend::RequestHandler*,
+ std::unique_ptr<QuicHttpProxyBackendStream>>;
+ const ProxyBackendStreamMap* proxy_backend_streams_map() const {
+ return &backend_stream_map_;
+ }
+
+ GURL backend_url() const { return backend_url_; }
+
+ // Implements the functions for interface quic::QuicSimpleServerBackend
+ bool InitializeBackend(const std::string& backend_url) override;
+ bool IsBackendInitialized() const override;
+ void FetchResponseFromBackend(
+ const spdy::SpdyHeaderBlock& request_headers,
+ const std::string& incoming_body,
+ quic::QuicSimpleServerBackend::RequestHandler* quic_stream) override;
+ void CloseBackendResponseStream(
+ quic::QuicSimpleServerBackend::RequestHandler* quic_stream) override;
+
+ private:
+ // Maps quic streams in the frontend to the corresponding http streams
+ // managed by |this|
+ ProxyBackendStreamMap backend_stream_map_;
+
+ bool ValidateBackendUrl(const std::string& backend_url);
+ void InitializeURLRequestContext();
+ QuicHttpProxyBackendStream* InitializeQuicProxyBackendStream(
+ quic::QuicSimpleServerBackend::RequestHandler* quic_server_stream);
+
+ // URLRequestContext to make URL requests to the backend
+ std::unique_ptr<net::URLRequestContext> context_; // owned by this
+
+ bool thread_initialized_;
+ // <scheme://hostname:port/ for the backend HTTP server
+ GURL backend_url_;
+
+ // Backend thread is owned by |this|
+ std::unique_ptr<base::Thread> proxy_thread_;
+ scoped_refptr<base::SingleThreadTaskRunner> proxy_task_runner_;
+
+ // Protects against concurrent access from quic (main) and proxy
+ // threads for adding and clearing a backend request handler
+ base::Lock backend_stream_mutex_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicHttpProxyBackend);
+};
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_HTTP_PROXY_BACKEND_H_
\ No newline at end of file
diff --git a/net/tools/quic/quic_http_proxy_backend_stream.cc b/net/tools/quic/quic_http_proxy_backend_stream.cc
new file mode 100644
index 0000000..419ce5ba
--- /dev/null
+++ b/net/tools/quic/quic_http_proxy_backend_stream.cc
@@ -0,0 +1,401 @@
+// Copyright 2018 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 "base/strings/string_number_conversions.h"
+#include "net/base/elements_upload_data_stream.h"
+#include "net/base/net_errors.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/cert/cert_status_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_util.h"
+#include "net/ssl/ssl_info.h"
+#include "net/url_request/redirect_info.h"
+#include "net/url_request/url_request_context.h"
+
+#include "net/tools/quic/quic_http_proxy_backend_stream.h"
+
+namespace net {
+
+// This is the Size of the buffer that consumes the response from the Backend
+// The response is consumed upto 64KB at a time to avoid a large response
+// from hogging resources from smaller responses.
+const int QuicHttpProxyBackendStream::kBufferSize = 64000;
+/*502 Bad Gateway
+ The server was acting as a gateway or proxy and received an
+ invalid response from the upstream server.*/
+const int QuicHttpProxyBackendStream::kProxyHttpBackendError = 502;
+// Hop-by-hop headers (small-caps). These are removed when sent to the backend.
+// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
+// not Trailers per URL above;
+// http://www.rfc-editor.org/errata_search.php?eid=4522
+const std::set<std::string> QuicHttpProxyBackendStream::kHopHeaders = {
+ "connection",
+ "proxy-connection", // non-standard but still sent by libcurl and rejected
+ // by e.g. google
+ "keep-alive", "proxy-authenticate", "proxy-authorization",
+ "te", // canonicalized version of "TE"
+ "trailer", // not Trailers per URL above;
+ // http://www.rfc-editor.org/errata_search.php?eid=4522
+ "transfer-encoding", "upgrade",
+};
+const std::string QuicHttpProxyBackendStream::kDefaultQuicPeerIP = "Unknown";
+
+QuicHttpProxyBackendStream::QuicHttpProxyBackendStream(
+ QuicHttpProxyBackend* proxy_context)
+ : proxy_context_(proxy_context),
+ delegate_(nullptr),
+ quic_peer_ip_(kDefaultQuicPeerIP),
+ url_request_(nullptr),
+ buf_(new IOBuffer(kBufferSize)),
+ response_completed_(false),
+ headers_set_(false),
+ quic_response_(new quic::QuicBackendResponse()),
+ weak_factory_(this) {}
+
+QuicHttpProxyBackendStream::~QuicHttpProxyBackendStream() {}
+
+void QuicHttpProxyBackendStream::Initialize(
+ quic::QuicConnectionId quic_connection_id,
+ quic::QuicStreamId quic_stream_id,
+ std::string quic_peer_ip) {
+ quic_connection_id_ = quic_connection_id;
+ quic_stream_id_ = quic_stream_id;
+ quic_peer_ip_ = quic_peer_ip;
+ if (!quic_proxy_task_runner_.get()) {
+ quic_proxy_task_runner_ = proxy_context_->GetProxyTaskRunner();
+ } else {
+ DCHECK_EQ(quic_proxy_task_runner_, proxy_context_->GetProxyTaskRunner());
+ }
+
+ quic_response_->set_response_type(
+ quic::QuicBackendResponse::BACKEND_ERR_RESPONSE);
+}
+
+void QuicHttpProxyBackendStream::set_delegate(
+ quic::QuicSimpleServerBackend::RequestHandler* delegate) {
+ delegate_ = delegate;
+ delegate_task_runner_ = base::SequencedTaskRunnerHandle::Get();
+}
+
+bool QuicHttpProxyBackendStream::SendRequestToBackend(
+ const spdy::SpdyHeaderBlock* incoming_request_headers,
+ const std::string& incoming_body) {
+ DCHECK(proxy_context_->IsBackendInitialized())
+ << " The quic-backend-proxy-context should be initialized";
+
+ // Get Path From the Incoming Header Block
+ spdy::SpdyHeaderBlock::const_iterator it =
+ incoming_request_headers->find(":path");
+
+ GURL url = proxy_context_->backend_url();
+ std::string backend_spec = url.spec();
+ if (it != incoming_request_headers->end()) {
+ if (url.path().compare("/") == 0) {
+ backend_spec.pop_back();
+ }
+ backend_spec.append(it->second.as_string());
+ }
+
+ url_ = GURL(backend_spec.c_str());
+ if (!url_.is_valid()) {
+ LOG(ERROR) << "Invalid URL received from QUIC client " << backend_spec;
+ return false;
+ }
+ LOG(INFO) << "QUIC Proxy Making a request to the Backed URL: " + url_.spec();
+
+ // Set the Method From the Incoming Header Block
+ std::string method = "";
+ it = incoming_request_headers->find(":method");
+ if (it != incoming_request_headers->end()) {
+ method.append(it->second.as_string());
+ }
+ if (ValidateHttpMethod(method) != true) {
+ LOG(INFO) << "Unknown Request Type received from QUIC client " << method;
+ return false;
+ }
+ CopyHeaders(incoming_request_headers);
+ if (method_type_ == "POST" || method_type_ == "PUT" ||
+ method_type_ == "PATCH") {
+ // Upload content must be set
+ if (!incoming_body.empty()) {
+ std::unique_ptr<UploadElementReader> reader(new UploadBytesElementReader(
+ incoming_body.data(), incoming_body.size()));
+ SetUpload(
+ ElementsUploadDataStream::CreateWithReader(std::move(reader), 0));
+ }
+ }
+ // Start the request on the backend thread
+ bool posted = quic_proxy_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&QuicHttpProxyBackendStream::SendRequestOnBackendThread,
+ weak_factory_.GetWeakPtr()));
+ return posted;
+}
+
+void QuicHttpProxyBackendStream::CopyHeaders(
+ const spdy::SpdyHeaderBlock* incoming_request_headers) {
+ // Set all the request headers
+ // Add or append the X-Forwarded-For Header and X-Real-IP
+ for (spdy::SpdyHeaderBlock::const_iterator it =
+ incoming_request_headers->begin();
+ it != incoming_request_headers->end(); ++it) {
+ std::string key = it->first.as_string();
+ std::string value = it->second.as_string();
+ // Ignore the spdy headers
+ if (!key.empty() && key[0] != ':') {
+ // Remove hop-by-hop headers
+ if (base::ContainsKey(kHopHeaders, key)) {
+ LOG(INFO) << "QUIC Proxy Ignoring Hop-by-hop Request Header: " << key
+ << ":" << value;
+ } else {
+ LOG(INFO) << "QUIC Proxy Copying to backend Request Header: " << key
+ << ":" << value;
+ AddRequestHeader(key, value);
+ }
+ }
+ }
+ // ToDo append proxy ip when x_forwarded_for header already present
+ AddRequestHeader("X-Forwarded-For", quic_peer_ip_);
+}
+
+bool QuicHttpProxyBackendStream::ValidateHttpMethod(std::string method) {
+ // Http method is a token, just as header name.
+ if (!net::HttpUtil::IsValidHeaderName(method))
+ return false;
+ method_type_ = method;
+ return true;
+}
+
+bool QuicHttpProxyBackendStream::AddRequestHeader(std::string name,
+ std::string value) {
+ if (!net::HttpUtil::IsValidHeaderName(name) ||
+ !net::HttpUtil::IsValidHeaderValue(value)) {
+ return false;
+ }
+ request_headers_.SetHeader(name, value);
+ return true;
+}
+
+void QuicHttpProxyBackendStream::SetUpload(
+ std::unique_ptr<net::UploadDataStream> upload) {
+ DCHECK(!upload_);
+ upload_ = std::move(upload);
+}
+
+void QuicHttpProxyBackendStream::SendRequestOnBackendThread() {
+ DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
+ url_request_ = proxy_context_->GetURLRequestContext()->CreateRequest(
+ url_, net::DEFAULT_PRIORITY, this);
+ url_request_->set_method(method_type_);
+ url_request_->SetExtraRequestHeaders(request_headers_);
+ if (upload_) {
+ url_request_->set_upload(std::move(upload_));
+ }
+ url_request_->Start();
+ VLOG(1) << "Quic Proxy Sending Request to Backend for quic_conn_id: "
+ << quic_connection_id_ << " quic_stream_id: " << quic_stream_id_
+ << " backend_req_id: " << url_request_->identifier()
+ << " url: " << url_;
+}
+
+void QuicHttpProxyBackendStream::OnReceivedRedirect(
+ net::URLRequest* request,
+ const net::RedirectInfo& redirect_info,
+ bool* defer_redirect) {
+ DCHECK_EQ(request, url_request_.get());
+ DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
+ // Do not defer redirect, retry again from the proxy with the new url
+ *defer_redirect = false;
+ LOG(ERROR) << "Received Redirect from Backend "
+ << " BackendReqId: " << request->identifier() << " redirectUrl: "
+ << redirect_info.new_url.possibly_invalid_spec().c_str()
+ << " RespCode " << request->GetResponseCode();
+}
+
+void QuicHttpProxyBackendStream::OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_info) {
+ DCHECK_EQ(request, url_request_.get());
+ DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
+ // Continue the SSL handshake without a client certificate.
+ request->ContinueWithCertificate(nullptr, nullptr);
+}
+
+void QuicHttpProxyBackendStream::OnSSLCertificateError(
+ net::URLRequest* request,
+ const net::SSLInfo& ssl_info,
+ bool fatal) {
+ request->Cancel();
+ OnResponseCompleted();
+}
+
+void QuicHttpProxyBackendStream::OnResponseStarted(net::URLRequest* request,
+ int net_error) {
+ DCHECK_EQ(request, url_request_.get());
+ DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
+ // It doesn't make sense for the request to have IO pending at this point.
+ DCHECK_NE(net::ERR_IO_PENDING, net_error);
+ if (net_error != net::OK) {
+ LOG(ERROR) << "OnResponseStarted Error from Backend "
+ << url_request_->identifier() << " url: "
+ << url_request_->url().possibly_invalid_spec().c_str()
+ << " RespError " << net::ErrorToString(net_error);
+ OnResponseCompleted();
+ return;
+ }
+ // Initialite the first read
+ ReadOnceTask();
+}
+
+void QuicHttpProxyBackendStream::ReadOnceTask() {
+ // Initiate a read for a max of kBufferSize
+ // This avoids a request with a large response from starving
+ // requests with smaller responses
+ int bytes_read = url_request_->Read(buf_.get(), kBufferSize);
+ OnReadCompleted(url_request_.get(), bytes_read);
+}
+
+// In the case of net::ERR_IO_PENDING,
+// OnReadCompleted callback will be called by URLRequest
+void QuicHttpProxyBackendStream::OnReadCompleted(net::URLRequest* unused,
+ int bytes_read) {
+ DCHECK_EQ(url_request_.get(), unused);
+ LOG(INFO) << "OnReadCompleted Backend with"
+ << " ReqId: " << url_request_->identifier() << " RespCode "
+ << url_request_->GetResponseCode() << " RcvdBytesCount "
+ << bytes_read << " RcvdTotalBytes " << data_received_.size();
+
+ if (bytes_read > 0) {
+ data_received_.append(buf_->data(), bytes_read);
+ // More data to be read, send a task to self
+ quic_proxy_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&QuicHttpProxyBackendStream::ReadOnceTask,
+ weak_factory_.GetWeakPtr()));
+ } else if (bytes_read != net::ERR_IO_PENDING) {
+ quic_response_->set_response_type(
+ quic::QuicBackendResponse::REGULAR_RESPONSE);
+ OnResponseCompleted();
+ }
+}
+
+/* Response from Backend complete, send the last chunk of data with fin=true to
+ * the corresponding quic stream */
+void QuicHttpProxyBackendStream::OnResponseCompleted() {
+ DCHECK(!response_completed_);
+ LOG(INFO) << "Quic Proxy Received Response from Backend for quic_conn_id: "
+ << quic_connection_id_ << " quic_stream_id: " << quic_stream_id_
+ << " backend_req_id: " << url_request_->identifier()
+ << " url: " << url_;
+
+ // ToDo Stream the response
+ spdy::SpdyHeaderBlock response_headers;
+ if (quic_response_->response_type() !=
+ quic::QuicBackendResponse::BACKEND_ERR_RESPONSE) {
+ response_headers = getAsQuicHeaders(url_request_->response_headers(),
+ url_request_->GetResponseCode(),
+ data_received_.size());
+ quic_response_->set_headers(std::move(response_headers));
+ quic_response_->set_body(std::move(data_received_));
+ } else {
+ response_headers =
+ getAsQuicHeaders(url_request_->response_headers(),
+ kProxyHttpBackendError, data_received_.size());
+ quic_response_->set_headers(std::move(response_headers));
+ }
+ response_completed_ = true;
+ ReleaseRequest();
+
+ // Send the response back to the quic client on the quic/main thread
+ if (delegate_ != nullptr) {
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &QuicHttpProxyBackendStream::SendResponseOnDelegateThread,
+ base::Unretained(this)));
+ }
+}
+
+void QuicHttpProxyBackendStream::SendResponseOnDelegateThread() {
+ DCHECK(delegate_ != nullptr);
+ // Proxy currently does not support push resources
+ std::list<quic::QuicBackendResponse::ServerPushInfo> empty_resources;
+ delegate_->OnResponseBackendComplete(quic_response_.get(), empty_resources);
+}
+
+void QuicHttpProxyBackendStream::CancelRequest() {
+ DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
+ if (quic_proxy_task_runner_.get())
+ DCHECK(quic_proxy_task_runner_->BelongsToCurrentThread());
+ delegate_ = nullptr;
+ if (url_request_.get()) {
+ url_request_->CancelWithError(ERR_ABORTED);
+ ReleaseRequest();
+ }
+}
+
+void QuicHttpProxyBackendStream::ReleaseRequest() {
+ url_request_.reset();
+ buf_ = nullptr;
+}
+
+quic::QuicBackendResponse* QuicHttpProxyBackendStream::GetBackendResponse()
+ const {
+ return quic_response_.get();
+}
+
+// Copy Backend Response headers to Quic response headers
+spdy::SpdyHeaderBlock QuicHttpProxyBackendStream::getAsQuicHeaders(
+ net::HttpResponseHeaders* resp_headers,
+ int response_code,
+ uint64_t response_decoded_body_size) {
+ DCHECK(!headers_set_);
+ bool response_body_encoded = false;
+ spdy::SpdyHeaderBlock quic_response_headers;
+ // Add spdy headers: Status, version need : before the header
+ quic_response_headers[":status"] = base::NumberToString(response_code);
+ headers_set_ = true;
+ // Returns an empty array if |headers| is nullptr.
+ if (resp_headers != nullptr) {
+ size_t iter = 0;
+ std::string header_name;
+ std::string header_value;
+ while (resp_headers->EnumerateHeaderLines(&iter, &header_name,
+ &header_value)) {
+ header_name = base::ToLowerASCII(header_name);
+ // Do not copy status again since status needs a ":" before the header
+ // name
+ if (header_name.compare("status") != 0) {
+ if (header_name.compare("content-encoding") != 0) {
+ // Remove hop-by-hop headers
+ if (base::ContainsKey(kHopHeaders, header_name)) {
+ LOG(INFO) << "Quic Proxy Ignoring Hop-by-hop Response Header: "
+ << header_name << ":" << header_value;
+ } else {
+ LOG(INFO) << " Quic Proxy Copying Response Header: " << header_name
+ << ":" << header_value;
+ quic_response_headers.AppendValueOrAddHeader(header_name,
+ header_value);
+ }
+ } else {
+ response_body_encoded = true;
+ }
+ }
+ } // while
+ // Currently URLRequest class does not support ability to disable decoding,
+ // response body (gzip, deflate etc. )
+ // Instead of re-encoding the body, we send decode body to the quic client
+ // and re-write the content length to the original body size
+ if (response_body_encoded) {
+ LOG(INFO) << " Quic Proxy Rewriting the Content-Length Header since "
+ "the response was encoded : "
+ << response_decoded_body_size;
+ quic_response_headers["content-length"] =
+ base::NumberToString(response_decoded_body_size);
+ }
+ }
+ return quic_response_headers;
+}
+} // namespace net
\ No newline at end of file
diff --git a/net/tools/quic/quic_http_proxy_backend_stream.h b/net/tools/quic/quic_http_proxy_backend_stream.h
new file mode 100644
index 0000000..a231433
--- /dev/null
+++ b/net/tools/quic/quic_http_proxy_backend_stream.h
@@ -0,0 +1,162 @@
+// Copyright 2018 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.
+//
+// The QuicHttpProxyBackendStream instance manages an instance of
+// net::URLRequest to initiate a single HTTP call to the backend. It also
+// implements the callbacks of net::URLRequest to receive the response. It is
+// instantiated by a delegate (for instance, the QuicSimpleServerStream class)
+// when a complete HTTP request is received within a single QUIC stream.
+// However, the instance is owned by QuicHttpProxyBackend, that destroys it
+// safely on the quic proxy thread. Upon receiving a response (success or
+// failed), the response headers and body are posted back to the main thread. In
+// the main thread, the QuicHttpProxyBackendStream instance calls the interface,
+// that is implemented by the delegate to return the response headers and body.
+// In addition to managing the HTTP request/response to the backend, it
+// translates the quic_spdy headers to/from HTTP headers for the backend.
+//
+
+#ifndef NET_TOOLS_QUIC_QUIC_HTTP_PROXY_BACKEND_STREAM_H_
+#define NET_TOOLS_QUIC_QUIC_HTTP_PROXY_BACKEND_STREAM_H_
+
+#include <memory>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_data_stream.h"
+#include "net/url_request/url_request.h"
+
+#include "net/third_party/quic/tools/quic_backend_response.h"
+#include "net/third_party/quic/tools/quic_simple_server_backend.h"
+#include "net/third_party/spdy/core/spdy_header_block.h"
+#include "net/tools/quic/quic_http_proxy_backend.h"
+
+namespace base {
+class SequencedTaskRunner;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace quic {
+class QuicBackendResponse;
+class QuicSimpleServerBackend;
+} // namespace quic
+
+namespace net {
+
+class HttpRequestHeaders;
+class SSLCertRequestInfo;
+class SSLInfo;
+class UploadDataStream;
+
+class QuicHttpProxyBackend;
+
+// An adapter for making HTTP requests to net::URLRequest.
+class QuicHttpProxyBackendStream : public net::URLRequest::Delegate {
+ public:
+ QuicHttpProxyBackendStream(QuicHttpProxyBackend* context);
+ ~QuicHttpProxyBackendStream() override;
+
+ static const std::set<std::string> kHopHeaders;
+ static const int kBufferSize;
+ static const int kProxyHttpBackendError;
+ static const std::string kDefaultQuicPeerIP;
+
+ // Set callbacks to be called from this to the main (quic) thread.
+ // A |delegate| may be NULL.
+ // If set_delegate() is called multiple times, only the last delegate will be
+ // used.
+ void set_delegate(quic::QuicSimpleServerBackend::RequestHandler* delegate);
+ void reset_delegate() { delegate_ = nullptr; }
+
+ void Initialize(quic::QuicConnectionId quic_connection_id,
+ quic::QuicStreamId quic_stream_id,
+ std::string quic_peer_ip);
+
+ virtual bool SendRequestToBackend(
+ const spdy::SpdyHeaderBlock* incoming_request_headers,
+ const std::string& incoming_body);
+
+ quic::QuicConnectionId quic_connection_id() const {
+ return quic_connection_id_;
+ }
+ quic::QuicStreamId quic_stream_id() const { return quic_stream_id_; }
+
+ const net::HttpRequestHeaders& request_headers() const {
+ return request_headers_;
+ }
+ // Releases all resources for the request and deletes the object itself.
+ virtual void CancelRequest();
+
+ // net::URLRequest::Delegate implementations:
+ void OnReceivedRedirect(net::URLRequest* request,
+ const net::RedirectInfo& redirect_info,
+ bool* defer_redirect) override;
+ void OnCertificateRequested(
+ net::URLRequest* request,
+ net::SSLCertRequestInfo* cert_request_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 bytes_read) override;
+
+ bool ResponseIsCompleted() const { return response_completed_; }
+ quic::QuicBackendResponse* GetBackendResponse() const;
+
+ private:
+ void StartOnBackendThread();
+ void SendRequestOnBackendThread();
+ void ReadOnceTask();
+ void OnResponseCompleted();
+ void CopyHeaders(const spdy::SpdyHeaderBlock* incoming_request_headers);
+ bool ValidateHttpMethod(std::string method);
+ bool AddRequestHeader(std::string name, std::string value);
+ // Adds a request body to the request before it starts.
+ void SetUpload(std::unique_ptr<net::UploadDataStream> upload);
+ void SendResponseOnDelegateThread();
+ void ReleaseRequest();
+ spdy::SpdyHeaderBlock getAsQuicHeaders(net::HttpResponseHeaders* resp_headers,
+ int response_code,
+ uint64_t response_decoded_body_size);
+
+ // The quic proxy backend context
+ QuicHttpProxyBackend* proxy_context_;
+ // Send back the response from the backend to |delegate_|
+ quic::QuicSimpleServerBackend::RequestHandler* delegate_;
+ // Task runner for interacting with the delegate
+ scoped_refptr<base::SequencedTaskRunner> delegate_task_runner_;
+ // Task runner for the proxy network operations.
+ scoped_refptr<base::SingleThreadTaskRunner> quic_proxy_task_runner_;
+
+ // The corresponding QUIC conn/client/stream
+ quic::QuicConnectionId quic_connection_id_;
+ quic::QuicStreamId quic_stream_id_;
+ std::string quic_peer_ip_;
+
+ // Url, method and spec for making a http request to the Backend
+ GURL url_;
+ std::string method_type_;
+ net::HttpRequestHeaders request_headers_;
+ std::unique_ptr<net::UploadDataStream> upload_;
+ std::unique_ptr<net::URLRequest> url_request_;
+
+ // Buffers that holds the response body
+ scoped_refptr<IOBuffer> buf_;
+ std::string data_received_;
+ bool response_completed_;
+ // Response and push resources received from the backend
+ bool headers_set_;
+ std::unique_ptr<quic::QuicBackendResponse> quic_response_;
+
+ base::WeakPtrFactory<QuicHttpProxyBackendStream> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(QuicHttpProxyBackendStream);
+};
+
+} // namespace net
+
+#endif // NET_TOOLS_QUIC_QUIC_HTTP_PROXY_BACKEND_STREAM_H_
\ No newline at end of file
diff --git a/net/tools/quic/quic_http_proxy_backend_stream_test.cc b/net/tools/quic/quic_http_proxy_backend_stream_test.cc
new file mode 100644
index 0000000..ef2d507f
--- /dev/null
+++ b/net/tools/quic/quic_http_proxy_backend_stream_test.cc
@@ -0,0 +1,482 @@
+// Copyright 2018 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 "net/tools/quic/quic_http_proxy_backend_stream.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/path_service.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/scoped_task_environment.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/test/embedded_test_server/request_handler_util.h"
+#include "net/third_party/quic/platform/api/quic_test.h"
+#include "net/third_party/quic/tools/quic_backend_response.h"
+#include "net/tools/quic/quic_http_proxy_backend.h"
+
+namespace net {
+namespace test {
+
+// Test server path and response body for the default URL used by many of the
+// tests.
+const char kDefaultResponsePath[] = "/defaultresponse";
+const char kDefaultResponseBody[] =
+ "Default response given for path: /defaultresponse";
+std::string kLargeResponseBody(
+ "Default response given for path: /defaultresponselarge");
+const char* const kHttp2StatusHeader = ":status";
+
+// To test uploading the contents of a file
+base::FilePath GetUploadFileTestPath() {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.Append(
+ FILE_PATH_LITERAL("net/data/url_request_unittest/BullRunSpeech.txt"));
+}
+
+// /defaultresponselarge
+// Returns a valid 10 MB response.
+std::unique_ptr<test_server::HttpResponse> HandleDefaultResponseLarge(
+ const test_server::HttpRequest& request) {
+ std::unique_ptr<test_server::BasicHttpResponse> http_response(
+ new test_server::BasicHttpResponse);
+ http_response->set_content_type("text/html");
+ // return 10 MB
+ for (int i = 0; i < 200000; ++i)
+ kLargeResponseBody += "01234567890123456789012345678901234567890123456789";
+ http_response->set_content(kLargeResponseBody);
+
+ return std::move(http_response);
+}
+
+int ParseHeaderStatusCode(const spdy::SpdyHeaderBlock& header) {
+ int status_code;
+ spdy::SpdyHeaderBlock::const_iterator it = header.find(kHttp2StatusHeader);
+ if (it == header.end()) {
+ return -1;
+ }
+ const base::StringPiece status(it->second);
+ if (status.size() != 3) {
+ return -1;
+ }
+ // First character must be an integer in range [1,5].
+ if (status[0] < '1' || status[0] > '5') {
+ return -1;
+ }
+ // The remaining two characters must be integers.
+ if (!isdigit(status[1]) || !isdigit(status[2])) {
+ return -1;
+ }
+ if (base::StringToInt(status, &status_code)) {
+ return status_code;
+ } else {
+ return -1;
+ }
+}
+
+class TestQuicServerStreamDelegate
+ : public quic::QuicSimpleServerBackend::RequestHandler {
+ public:
+ TestQuicServerStreamDelegate()
+ : send_success_(false),
+ did_complete_(false),
+ quic_backend_stream_(nullptr) {}
+
+ ~TestQuicServerStreamDelegate() override {}
+
+ void CreateProxyBackendResponseStreamForTest(
+ QuicHttpProxyBackend* proxy_backend) {
+ quic_backend_stream_ =
+ std::make_unique<QuicHttpProxyBackendStream>(proxy_backend);
+ quic_backend_stream_->set_delegate(this);
+ quic_backend_stream_->Initialize(connection_id(), stream_id(), peer_host());
+ }
+
+ QuicHttpProxyBackendStream* get_proxy_backend_stream() const {
+ return quic_backend_stream_.get();
+ }
+
+ const net::HttpRequestHeaders& get_request_headers() const {
+ return quic_backend_stream_->request_headers();
+ }
+
+ void StartHttpRequestToBackendAndWait(
+ spdy::SpdyHeaderBlock* incoming_request_headers,
+ const std::string& incoming_body) {
+ send_success_ = quic_backend_stream_->SendRequestToBackend(
+ incoming_request_headers, incoming_body);
+ EXPECT_TRUE(send_success_);
+ WaitForComplete();
+ }
+
+ void WaitForComplete() {
+ EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
+ run_loop_.Run();
+ }
+
+ quic::QuicConnectionId connection_id() const override { return 123; };
+ quic::QuicStreamId stream_id() const override { return 5; };
+ std::string peer_host() const override { return "127.0.0.1"; };
+
+ void OnResponseBackendComplete(
+ const quic::QuicBackendResponse* response,
+ std::list<quic::QuicBackendResponse::ServerPushInfo> resources) override {
+ EXPECT_TRUE(task_runner_->BelongsToCurrentThread());
+ EXPECT_FALSE(did_complete_);
+ EXPECT_TRUE(quic_backend_stream_);
+ did_complete_ = true;
+ task_runner_->PostTask(FROM_HERE, run_loop_.QuitClosure());
+ }
+
+ private:
+ bool send_success_;
+ bool did_complete_;
+ std::unique_ptr<QuicHttpProxyBackendStream> quic_backend_stream_;
+ base::test::ScopedTaskEnvironment scoped_task_environment;
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_ =
+ base::ThreadTaskRunnerHandle::Get();
+ base::RunLoop run_loop_;
+};
+
+class QuicHttpProxyBackendStreamTest : public QuicTest {
+ public:
+ QuicHttpProxyBackendStreamTest() {}
+
+ ~QuicHttpProxyBackendStreamTest() override {}
+
+ // testing::Test:
+ void SetUp() override {
+ SetUpServer();
+ ASSERT_TRUE(test_server_->Start());
+
+ backend_url_ = base::StringPrintf("http://127.0.0.1:%d",
+ test_server_->host_port_pair().port());
+ CreateTestBackendProxy();
+ CreateTestBackendProxyToTestFailure();
+ }
+
+ void CreateTestBackendProxy() {
+ ASSERT_TRUE(GURL(backend_url_).is_valid());
+ proxy_backend_ = std::make_unique<QuicHttpProxyBackend>();
+ proxy_backend_->InitializeBackend(backend_url_);
+ }
+
+ void CreateTestBackendProxyToTestFailure() {
+ // To test against a non-running backend http server
+ std::string backend_fail_url =
+ base::StringPrintf("http://127.0.0.1:%d", 52);
+ ASSERT_TRUE(GURL(backend_fail_url).is_valid());
+ proxy_backend_fail_ = std::make_unique<QuicHttpProxyBackend>();
+ proxy_backend_fail_->InitializeBackend(backend_fail_url);
+ }
+
+ // Initializes |test_server_| without starting it. Allows subclasses to use
+ // their own server configuration.
+ void SetUpServer() {
+ test_server_.reset(new EmbeddedTestServer);
+ test_server_->AddDefaultHandlers(base::FilePath());
+ test_server_->RegisterDefaultHandler(base::BindRepeating(
+ &net::test_server::HandlePrefixedRequest, "/defaultresponselarge",
+ base::BindRepeating(&HandleDefaultResponseLarge)));
+ }
+
+ protected:
+ std::string backend_url_;
+ std::unique_ptr<QuicHttpProxyBackend> proxy_backend_;
+ std::unique_ptr<QuicHttpProxyBackend> proxy_backend_fail_;
+ std::unique_ptr<EmbeddedTestServer> test_server_;
+};
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendGetDefault) {
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = kDefaultResponsePath;
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/1.1";
+ request_headers[":method"] = "GET";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ EXPECT_EQ(kDefaultResponseBody, quic_response->body());
+}
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendGetLarge) {
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = "/defaultresponselarge";
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/1.1";
+ request_headers[":method"] = "GET";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ // kLargeResponseBody should be populated with huge response
+ // already in HandleDefaultResponseLarge()
+ EXPECT_EQ(kLargeResponseBody, quic_response->body());
+}
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostBody) {
+ const char kUploadData[] = "bobsyeruncle";
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = "/echo";
+ request_headers[":version"] = "HTTP/2.0";
+ request_headers[":version"] = "HTTP/1.1";
+ request_headers[":method"] = "POST";
+ request_headers["content-length"] = "12";
+ request_headers["content-type"] = "application/x-www-form-urlencoded";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ EXPECT_EQ(kUploadData, quic_response->body());
+}
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostEmptyString) {
+ const char kUploadData[] = "";
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = "/echo";
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/2.0";
+ request_headers[":method"] = "POST";
+ request_headers["content-length"] = "0";
+ request_headers["content-type"] = "application/x-www-form-urlencoded";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ EXPECT_EQ(kUploadData, quic_response->body());
+}
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendPostFile) {
+ std::string kUploadData;
+ base::FilePath upload_path = GetUploadFileTestPath();
+ ASSERT_TRUE(base::ReadFileToString(upload_path, &kUploadData));
+
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = "/echo";
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/2.0";
+ request_headers[":method"] = "POST";
+ request_headers["content-type"] = "application/x-www-form-urlencoded";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ EXPECT_EQ(kUploadData, quic_response->body());
+}
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendResponse500) {
+ const char kUploadData[] = "bobsyeruncle";
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = "/echo?status=500";
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/2.0";
+ request_headers[":method"] = "POST";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(500, ParseHeaderStatusCode(quic_response->headers()));
+}
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendFail) {
+ const char kUploadData[] = "bobsyeruncle";
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = "/echo";
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/2.0";
+ request_headers[":method"] = "POST";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_fail_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, kUploadData);
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(quic::QuicBackendResponse::BACKEND_ERR_RESPONSE,
+ quic_response->response_type());
+}
+
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendOnRedirect) {
+ const std::string kRedirectTarget = backend_url_.append("/echo");
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = std::string("/server-redirect?") + kRedirectTarget;
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/2.0";
+ request_headers[":method"] = "GET";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+}
+
+// Ensure that the proxy rewrites the content-length when receiving a Gzipped
+// response
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendHandleGzip) {
+ const char kGzipData[] =
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!!";
+ uint64_t rawBodyLength = strlen(kGzipData);
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = std::string("/gzip-body?") + kGzipData;
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":version"] = "HTTP/2.0";
+ request_headers[":method"] = "GET";
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(quic::QuicBackendResponse::REGULAR_RESPONSE,
+ quic_response->response_type());
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ EXPECT_EQ(kGzipData, quic_response->body());
+ spdy::SpdyHeaderBlock quic_response_headers =
+ quic_response->headers().Clone();
+
+ // Ensure that the content length is set to the raw body size (unencoded)
+ auto responseLength = quic_response_headers.find("content-length");
+ uint64_t response_header_content_length = 0;
+ if (responseLength != quic_response_headers.end()) {
+ base::StringToUint64(responseLength->second,
+ &response_header_content_length);
+ }
+ EXPECT_EQ(rawBodyLength, response_header_content_length);
+
+ // Ensure the content-encoding header is removed for the quic response
+ EXPECT_EQ(quic_response_headers.end(),
+ quic_response_headers.find("content-encoding"));
+}
+
+// Ensure cookies are not saved/updated at the proxy
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendCookiesNotSaved) {
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":method"] = "GET";
+
+ {
+ TestQuicServerStreamDelegate delegate;
+ request_headers[":path"] =
+ "/set-cookie?CookieToNotSave=1&CookieToNotUpdate=1";
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ spdy::SpdyHeaderBlock quic_response_headers =
+ quic_response->headers().Clone();
+ EXPECT_TRUE(quic_response_headers.end() !=
+ quic_response_headers.find("set-cookie"));
+ auto cookie = quic_response_headers.find("set-cookie");
+ EXPECT_TRUE(cookie->second.find("CookieToNotSave=1") != std::string::npos);
+ EXPECT_TRUE(cookie->second.find("CookieToNotUpdate=1") !=
+ std::string::npos);
+ }
+ {
+ TestQuicServerStreamDelegate delegate;
+ request_headers[":path"] = "/echoheader?Cookie";
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ EXPECT_TRUE(quic_response->body().find("CookieToNotSave=1") ==
+ std::string::npos);
+ EXPECT_TRUE(quic_response->body().find("CookieToNotUpdate=1") ==
+ std::string::npos);
+ }
+}
+
+// Ensure hop-by-hop headers are removed from the request and response to the
+// backend
+TEST_F(QuicHttpProxyBackendStreamTest, SendRequestToBackendHopHeaders) {
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":path"] = "/echoall";
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":method"] = "GET";
+ std::set<std::string>::iterator it;
+ for (it = QuicHttpProxyBackendStream::kHopHeaders.begin();
+ it != QuicHttpProxyBackendStream::kHopHeaders.end(); ++it) {
+ request_headers[*it] = "SomeString";
+ }
+
+ TestQuicServerStreamDelegate delegate;
+ delegate.CreateProxyBackendResponseStreamForTest(proxy_backend_.get());
+ delegate.StartHttpRequestToBackendAndWait(&request_headers, "");
+ const net::HttpRequestHeaders& actual_request_headers =
+ delegate.get_request_headers();
+ for (it = QuicHttpProxyBackendStream::kHopHeaders.begin();
+ it != QuicHttpProxyBackendStream::kHopHeaders.end(); ++it) {
+ EXPECT_FALSE(actual_request_headers.HasHeader(*it));
+ }
+
+ quic::QuicBackendResponse* quic_response =
+ delegate.get_proxy_backend_stream()->GetBackendResponse();
+ EXPECT_EQ(200, ParseHeaderStatusCode(quic_response->headers()));
+ spdy::SpdyHeaderBlock quic_response_headers =
+ quic_response->headers().Clone();
+ for (it = QuicHttpProxyBackendStream::kHopHeaders.begin();
+ it != QuicHttpProxyBackendStream::kHopHeaders.end(); ++it) {
+ EXPECT_EQ(quic_response_headers.end(), quic_response_headers.find(*it));
+ }
+}
+
+} // namespace test
+} // namespace net
\ No newline at end of file
diff --git a/net/tools/quic/quic_http_proxy_backend_test.cc b/net/tools/quic/quic_http_proxy_backend_test.cc
new file mode 100644
index 0000000..7aa833b
--- /dev/null
+++ b/net/tools/quic/quic_http_proxy_backend_test.cc
@@ -0,0 +1,139 @@
+// Copyright 2018 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 "base/files/file_path.h"
+#include "base/path_service.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_simple_task_runner.h"
+#include "net/base/url_util.h"
+#include "net/third_party/quic/platform/api/quic_test.h"
+
+#include "net/tools/quic/quic_http_proxy_backend.h"
+#include "net/tools/quic/quic_http_proxy_backend_stream.h"
+
+namespace net {
+namespace test {
+
+class TestQuicServerStream
+ : public quic::QuicSimpleServerBackend::RequestHandler {
+ public:
+ TestQuicServerStream() : did_complete_(false) {}
+
+ ~TestQuicServerStream() override {}
+
+ quic::QuicConnectionId connection_id() const override { return 123; };
+ quic::QuicStreamId stream_id() const override { return 5; };
+ std::string peer_host() const override { return "127.0.0.1"; };
+
+ void OnResponseBackendComplete(
+ const quic::QuicBackendResponse* response,
+ std::list<quic::QuicBackendResponse::ServerPushInfo> resources) override {
+ EXPECT_FALSE(did_complete_);
+ did_complete_ = true;
+ task_runner_->PostTask(FROM_HERE, run_loop_.QuitClosure());
+ }
+
+ base::RunLoop* run_loop() { return &run_loop_; }
+
+ private:
+ bool did_complete_;
+ base::test::ScopedTaskEnvironment scoped_task_environment;
+ const scoped_refptr<base::SingleThreadTaskRunner> task_runner_ =
+ base::ThreadTaskRunnerHandle::Get();
+ base::RunLoop run_loop_;
+};
+
+class QuicHttpProxyBackendTest : public QuicTest {
+ public:
+ QuicHttpProxyBackendTest() {
+ proxy_stream_map_ = http_proxy_.proxy_backend_streams_map();
+ }
+
+ ~QuicHttpProxyBackendTest() override {
+ EXPECT_EQ(true, proxy_stream_map_->empty());
+ }
+
+ void SendRequestOverBackend(TestQuicServerStream* quic_stream) {
+ quic_proxy_backend_url_ = "http://www.google.com:80";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+
+ spdy::SpdyHeaderBlock request_headers;
+ request_headers[":authority"] = "www.example.org";
+ request_headers[":method"] = "GET";
+ std::string body = "Test Body";
+ http_proxy_.FetchResponseFromBackend(request_headers, body, quic_stream);
+ quic_stream->run_loop()->Run();
+ }
+
+ protected:
+ std::string quic_proxy_backend_url_;
+ QuicHttpProxyBackend http_proxy_;
+ const QuicHttpProxyBackend::ProxyBackendStreamMap* proxy_stream_map_;
+};
+
+TEST_F(QuicHttpProxyBackendTest, InitializeQuicHttpProxyBackend) {
+ // Test incorrect URLs
+ quic_proxy_backend_url_ = "http://www.google.com:80--";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_EQ(false, http_proxy_.IsBackendInitialized());
+ EXPECT_EQ(nullptr, http_proxy_.GetProxyTaskRunner());
+
+ quic_proxy_backend_url_ = "http://192.168.239.257:80";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_EQ(false, http_proxy_.IsBackendInitialized());
+ EXPECT_EQ(nullptr, http_proxy_.GetProxyTaskRunner());
+
+ quic_proxy_backend_url_ = "http://2555.168.239:80";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_EQ(false, http_proxy_.IsBackendInitialized());
+ EXPECT_EQ(nullptr, http_proxy_.GetProxyTaskRunner());
+
+ quic_proxy_backend_url_ = "http://192.168.239.237:65537";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_EQ(false, http_proxy_.IsBackendInitialized());
+ EXPECT_EQ(nullptr, http_proxy_.GetProxyTaskRunner());
+
+ quic_proxy_backend_url_ = "ftp://www.google.com:80";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_EQ(false, http_proxy_.IsBackendInitialized());
+ EXPECT_EQ(nullptr, http_proxy_.GetProxyTaskRunner());
+
+ // Test initialization with correct URL
+ quic_proxy_backend_url_ = "http://www.google.com:80";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_NE(nullptr, http_proxy_.GetProxyTaskRunner());
+ EXPECT_EQ("http://www.google.com/", http_proxy_.backend_url());
+ EXPECT_EQ(true, http_proxy_.IsBackendInitialized());
+}
+
+TEST_F(QuicHttpProxyBackendTest, CheckProxyStreamManager) {
+ TestQuicServerStream quic_stream;
+ SendRequestOverBackend(&quic_stream);
+ QuicHttpProxyBackend::ProxyBackendStreamMap::const_iterator it_find_success =
+ proxy_stream_map_->find(&quic_stream);
+ EXPECT_NE(it_find_success, proxy_stream_map_->end());
+
+ http_proxy_.CloseBackendResponseStream(&quic_stream);
+
+ /*EXPECT_EQ(true, proxy_stream_map_->empty());
+ QuicHttpProxyBackend::ProxyBackendStreamMap::const_iterator it_find_fail =
+ proxy_stream_map_->find(&quic_stream);
+ EXPECT_EQ(it_find_fail, proxy_stream_map_->end());*/
+}
+
+TEST_F(QuicHttpProxyBackendTest, CheckIsOnBackendThread) {
+ quic_proxy_backend_url_ = "http://www.google.com:80";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_EQ(false, http_proxy_.GetProxyTaskRunner()->BelongsToCurrentThread());
+}
+
+TEST_F(QuicHttpProxyBackendTest, CheckGetBackendTaskRunner) {
+ EXPECT_EQ(nullptr, http_proxy_.GetProxyTaskRunner());
+ quic_proxy_backend_url_ = "http://www.google.com:80";
+ http_proxy_.InitializeBackend(quic_proxy_backend_url_);
+ EXPECT_NE(nullptr, http_proxy_.GetProxyTaskRunner());
+}
+
+} // namespace test
+} // namespace net
\ No newline at end of file
diff --git a/net/tools/quic/quic_simple_server_bin.cc b/net/tools/quic/quic_simple_server_bin.cc
index 42cc3ca..14f2375 100644
--- a/net/tools/quic/quic_simple_server_bin.cc
+++ b/net/tools/quic/quic_simple_server_bin.cc
@@ -19,6 +19,8 @@
#include "net/quic/chromium/crypto/proof_source_chromium.h"
#include "net/third_party/quic/core/quic_packets.h"
#include "net/third_party/quic/tools/quic_memory_cache_backend.h"
+#include "net/third_party/quic/tools/quic_simple_server_backend.h"
+#include "net/tools/quic/quic_http_proxy_backend.h"
#include "net/tools/quic/quic_simple_server.h"
// The port the quic server will listen on.
@@ -29,6 +31,9 @@
// construction to seed the cache. Cache directory can be
// generated using `wget -p --save-headers <url>`
std::string FLAGS_quic_response_cache_dir = "";
+// URL with http/https, IP address or host name and the port number of the
+// backend server
+std::string FLAGS_quic_proxy_backend_url = "";
std::unique_ptr<quic::ProofSource> CreateProofSource(
const base::FilePath& cert_path,
@@ -58,20 +63,26 @@
"Options:\n"
"-h, --help show this help message and exit\n"
"--port=<port> specify the port to listen on\n"
- "--mode=<cache> Default: cache\n"
- " Specify mode of operation: Cache will "
- "serve it "
+ "--mode=<cache|proxy> Specify mode of operation: Proxy will "
+ "serve response from\n"
+ " a backend server and Cache will serve it "
"from a cache dir\n"
"--quic_response_cache_dir=<directory>\n"
" The directory containing cached response "
"data to load\n"
+ "--quic_proxy_backend_url=<http/https>://<hostname_ip>:<port_number> \n"
+ " The URL for the single backend server "
+ "hostname \n"
+ " For example, \"http://xyz.com:80\"\n"
"--certificate_file=<file> path to the certificate chain\n"
"--key_file=<file> path to the pkcs8 private key\n";
std::cout << help_str;
exit(0);
}
- quic::QuicMemoryCacheBackend memory_cache_backend;
+ // Serve the HTTP response from backend: memory cache or http proxy
+ std::unique_ptr<quic::QuicSimpleServerBackend> quic_simple_server_backend;
+
if (line->HasSwitch("mode")) {
FLAGS_quic_mode = line->GetSwitchValueASCII("mode");
}
@@ -79,13 +90,28 @@
if (line->HasSwitch("quic_response_cache_dir")) {
FLAGS_quic_response_cache_dir =
line->GetSwitchValueASCII("quic_response_cache_dir");
+ quic_simple_server_backend =
+ std::make_unique<quic::QuicMemoryCacheBackend>();
if (FLAGS_quic_response_cache_dir.empty() ||
- memory_cache_backend.InitializeBackend(
+ quic_simple_server_backend->InitializeBackend(
FLAGS_quic_response_cache_dir) != true) {
LOG(ERROR) << "--quic_response_cache_dir is not valid !";
return 1;
}
}
+ } else if (FLAGS_quic_mode.compare("proxy") == 0) {
+ if (line->HasSwitch("quic_proxy_backend_url")) {
+ FLAGS_quic_proxy_backend_url =
+ line->GetSwitchValueASCII("quic_proxy_backend_url");
+ quic_simple_server_backend =
+ std::make_unique<net::QuicHttpProxyBackend>();
+ if (quic_simple_server_backend->InitializeBackend(
+ FLAGS_quic_proxy_backend_url) != true) {
+ LOG(ERROR) << "--quic_proxy_backend_url "
+ << FLAGS_quic_proxy_backend_url << " is not valid !";
+ return 1;
+ }
+ }
} else {
LOG(ERROR) << "unknown --mode. cache is a valid mode of operation";
return 1;
@@ -115,7 +141,7 @@
CreateProofSource(line->GetSwitchValuePath("certificate_file"),
line->GetSwitchValuePath("key_file")),
config, quic::QuicCryptoServerConfig::ConfigOptions(),
- quic::AllSupportedVersions(), &memory_cache_backend);
+ quic::AllSupportedVersions(), quic_simple_server_backend.get());
int rc = server.Listen(net::IPEndPoint(ip, FLAGS_port));
if (rc < 0) {