Commit 4380217c authored by Rajesh Mahindra's avatar Rajesh Mahindra Committed by Commit Bot

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: default avatarRyan Hamilton <rch@chromium.org>
Cr-Commit-Position: refs/heads/master@{#577650}
parent c51b5582
...@@ -3232,6 +3232,10 @@ source_set("simple_quic_tools") { ...@@ -3232,6 +3232,10 @@ source_set("simple_quic_tools") {
"third_party/quic/tools/quic_spdy_client_base.h", "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.cc",
"tools/quic/quic_client_message_loop_network_helper.h", "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.cc",
"tools/quic/quic_simple_client.h", "tools/quic/quic_simple_client.h",
"tools/quic/quic_simple_per_connection_packet_writer.cc", "tools/quic/quic_simple_per_connection_packet_writer.cc",
...@@ -5283,6 +5287,8 @@ test("net_unittests") { ...@@ -5283,6 +5287,8 @@ test("net_unittests") {
"third_party/quic/tools/quic_server_test.cc", "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_session_test.cc",
"third_party/quic/tools/quic_simple_server_stream_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_session_helper_test.cc",
"tools/quic/quic_simple_server_test.cc", "tools/quic/quic_simple_server_test.cc",
] ]
......
// 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
// 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
// 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
// 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
// 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
// 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
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
#include "net/quic/chromium/crypto/proof_source_chromium.h" #include "net/quic/chromium/crypto/proof_source_chromium.h"
#include "net/third_party/quic/core/quic_packets.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_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" #include "net/tools/quic/quic_simple_server.h"
// The port the quic server will listen on. // The port the quic server will listen on.
...@@ -29,6 +31,9 @@ std::string FLAGS_quic_mode = "cache"; ...@@ -29,6 +31,9 @@ std::string FLAGS_quic_mode = "cache";
// construction to seed the cache. Cache directory can be // construction to seed the cache. Cache directory can be
// generated using `wget -p --save-headers <url>` // generated using `wget -p --save-headers <url>`
std::string FLAGS_quic_response_cache_dir = ""; 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( std::unique_ptr<quic::ProofSource> CreateProofSource(
const base::FilePath& cert_path, const base::FilePath& cert_path,
...@@ -58,20 +63,26 @@ int main(int argc, char* argv[]) { ...@@ -58,20 +63,26 @@ int main(int argc, char* argv[]) {
"Options:\n" "Options:\n"
"-h, --help show this help message and exit\n" "-h, --help show this help message and exit\n"
"--port=<port> specify the port to listen on\n" "--port=<port> specify the port to listen on\n"
"--mode=<cache> Default: cache\n" "--mode=<cache|proxy> Specify mode of operation: Proxy will "
" Specify mode of operation: Cache will " "serve response from\n"
"serve it " " a backend server and Cache will serve it "
"from a cache dir\n" "from a cache dir\n"
"--quic_response_cache_dir=<directory>\n" "--quic_response_cache_dir=<directory>\n"
" The directory containing cached response " " The directory containing cached response "
"data to load\n" "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" "--certificate_file=<file> path to the certificate chain\n"
"--key_file=<file> path to the pkcs8 private key\n"; "--key_file=<file> path to the pkcs8 private key\n";
std::cout << help_str; std::cout << help_str;
exit(0); 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")) { if (line->HasSwitch("mode")) {
FLAGS_quic_mode = line->GetSwitchValueASCII("mode"); FLAGS_quic_mode = line->GetSwitchValueASCII("mode");
} }
...@@ -79,13 +90,28 @@ int main(int argc, char* argv[]) { ...@@ -79,13 +90,28 @@ int main(int argc, char* argv[]) {
if (line->HasSwitch("quic_response_cache_dir")) { if (line->HasSwitch("quic_response_cache_dir")) {
FLAGS_quic_response_cache_dir = FLAGS_quic_response_cache_dir =
line->GetSwitchValueASCII("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() || if (FLAGS_quic_response_cache_dir.empty() ||
memory_cache_backend.InitializeBackend( quic_simple_server_backend->InitializeBackend(
FLAGS_quic_response_cache_dir) != true) { FLAGS_quic_response_cache_dir) != true) {
LOG(ERROR) << "--quic_response_cache_dir is not valid !"; LOG(ERROR) << "--quic_response_cache_dir is not valid !";
return 1; 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 { } else {
LOG(ERROR) << "unknown --mode. cache is a valid mode of operation"; LOG(ERROR) << "unknown --mode. cache is a valid mode of operation";
return 1; return 1;
...@@ -115,7 +141,7 @@ int main(int argc, char* argv[]) { ...@@ -115,7 +141,7 @@ int main(int argc, char* argv[]) {
CreateProofSource(line->GetSwitchValuePath("certificate_file"), CreateProofSource(line->GetSwitchValuePath("certificate_file"),
line->GetSwitchValuePath("key_file")), line->GetSwitchValuePath("key_file")),
config, quic::QuicCryptoServerConfig::ConfigOptions(), 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)); int rc = server.Listen(net::IPEndPoint(ip, FLAGS_port));
if (rc < 0) { if (rc < 0) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment