| Index: net/tools/quic/quic_client_bin.cc
|
| diff --git a/net/tools/quic/quic_client_bin.cc b/net/tools/quic/quic_client_bin.cc
|
| index 6fcde96fa6be57c79221bf914dc413853fab46b0..dcebd39a9848a6894bc008384565a5dc936a4340 100644
|
| --- a/net/tools/quic/quic_client_bin.cc
|
| +++ b/net/tools/quic/quic_client_bin.cc
|
| @@ -3,11 +3,40 @@
|
| // found in the LICENSE file.
|
|
|
| // A binary wrapper for QuicClient.
|
| -// Connects to a host using QUIC, and sends requests to the provided URLS.
|
| +// Connects to a host using QUIC, sends a request to the provided URL, and
|
| +// displays the response.
|
| //
|
| -// Example usage:
|
| -// quic_client --address=127.0.0.1 --port=6122 --hostname=www.google.com
|
| -// http://www.google.com/index.html http://www.google.com/favicon.ico
|
| +// Some usage examples:
|
| +//
|
| +// TODO(rtenneti): make --host optional by getting IP Address of URL's host.
|
| +//
|
| +// Get IP address of the www.google.com
|
| +// IP=`dig www.google.com +short | head -1`
|
| +//
|
| +// Standard request/response:
|
| +// quic_client http://www.google.com --host=${IP}
|
| +// quic_client http://www.google.com --quiet --host=${IP}
|
| +// quic_client https://www.google.com --port=443 --host=${IP}
|
| +//
|
| +// Use a specific version:
|
| +// quic_client http://www.google.com --version=23 --host=${IP}
|
| +//
|
| +// Send a POST instead of a GET:
|
| +// quic_client http://www.google.com --body="this is a POST body" --host=${IP}
|
| +//
|
| +// Append additional headers to the request:
|
| +// quic_client http://www.google.com --host=${IP}
|
| +// --headers="Header-A: 1234; Header-B: 5678"
|
| +//
|
| +// Connect to a host different to the URL being requested:
|
| +// Get IP address of the www.google.com
|
| +// IP=`dig www.google.com +short | head -1`
|
| +// quic_client mail.google.com --host=${IP}
|
| +//
|
| +// Try to connect to a host which does not speak QUIC:
|
| +// Get IP address of the www.example.com
|
| +// IP=`dig www.example.com +short | head -1`
|
| +// quic_client http://www.example.com --host=${IP}
|
|
|
| #include <iostream>
|
|
|
| @@ -15,25 +44,45 @@
|
| #include "base/command_line.h"
|
| #include "base/logging.h"
|
| #include "base/strings/string_number_conversions.h"
|
| +#include "base/strings/string_split.h"
|
| +#include "base/strings/string_util.h"
|
| #include "net/base/ip_endpoint.h"
|
| #include "net/base/privacy_mode.h"
|
| #include "net/quic/quic_protocol.h"
|
| #include "net/quic/quic_server_id.h"
|
| +#include "net/quic/quic_utils.h"
|
| #include "net/tools/epoll_server/epoll_server.h"
|
| #include "net/tools/quic/quic_client.h"
|
| +#include "net/tools/quic/spdy_utils.h"
|
| +#include "url/gurl.h"
|
| +
|
| +using base::StringPiece;
|
| +using std::cout;
|
| +using std::cerr;
|
| +using std::map;
|
| +using std::string;
|
| +using std::vector;
|
| +using std::endl;
|
|
|
| -std::string FLAGS_address = "127.0.0.1";
|
| // The IP or hostname the quic client will connect to.
|
| -std::string FLAGS_hostname = "localhost";
|
| -// The port the quic client will connect to.
|
| -int32 FLAGS_port = 6121;
|
| -// Check the certificates using proof verifier.
|
| -bool FLAGS_secure = false;
|
| -// QUIC version to speak, e.g. 21. Default value of 0 means 'use the latest
|
| -// version'.
|
| -int32 FLAGS_quic_version = 0;
|
| -// Size of flow control receive window to advertize to the peer.
|
| -int32 FLAGS_flow_control_window_bytes = 10 * 1024 * 1024; // 10 Mb
|
| +string FLAGS_host = "";
|
| +// The port to connect to.
|
| +int32 FLAGS_port = 80;
|
| +// If set, send a POST with this body.
|
| +string FLAGS_body = "";
|
| +// A semicolon separated list of key:value pairs to add to request headers.
|
| +string FLAGS_headers = "";
|
| +// Set to true for a quieter output experience.
|
| +bool FLAGS_quiet = false;
|
| +// QUIC version to speak, e.g. 21. If not set, then all available versions are
|
| +// offered in the handshake.
|
| +int32 FLAGS_quic_version = -1;
|
| +// If true, a version mismatch in the handshake is not considered a failure.
|
| +// Useful for probing a server to determine if it speaks any version of QUIC.
|
| +bool FLAGS_version_mismatch_ok = false;
|
| +// If true, an HTTP response code of 3xx is considered to be a successful
|
| +// response, otherwise a failure.
|
| +bool FLAGS_redirect_is_success = true;
|
|
|
| int main(int argc, char *argv[]) {
|
| base::CommandLine::Init(argc, argv);
|
| @@ -46,36 +95,43 @@ int main(int argc, char *argv[]) {
|
|
|
| if (line->HasSwitch("h") || line->HasSwitch("help") || urls.empty()) {
|
| const char* help_str =
|
| - "Usage: quic_client [options] <url> ...\n"
|
| + "Usage: quic_client [options] <url>\n"
|
| "\n"
|
| - "At least one <url> with scheme must be provided "
|
| - "(e.g. http://www.google.com/)\n\n"
|
| + "<url> with scheme must be provided (e.g. http://www.google.com)\n\n"
|
| "Options:\n"
|
| "-h, --help show this help message and exit\n"
|
| + "--host=<host> specify the IP address of the hostname to "
|
| + "connect to\n"
|
| "--port=<port> specify the port to connect to\n"
|
| - "--address=<address> specify the IP address to connect to\n"
|
| - "--host=<host> specify the SNI hostname to use\n"
|
| - "--secure check certificates\n"
|
| + "--body=<body> specify the body to post\n"
|
| + "--headers=<headers> specify a semicolon separated list of "
|
| + "key:value pairs to add to request headers\n"
|
| + "--quiet specify for a quieter output experience\n"
|
| "--quic-version=<quic version> specify QUIC version to speak\n"
|
| - "--flow-control-window-bytes=<bytes> specify size of flow control "
|
| - "receive window to advertize to the peer\n";
|
| - std::cout << help_str;
|
| + "--version_mismatch_ok if specified a version mismatch in the "
|
| + "handshake is not considered a failure\n"
|
| + "--redirect_is_success if specified an HTTP response code of 3xx "
|
| + "is considered to be a successful response, otherwise a failure\n";
|
| + cout << help_str;
|
| exit(0);
|
| }
|
| + if (line->HasSwitch("host")) {
|
| + FLAGS_host = line->GetSwitchValueASCII("host");
|
| + }
|
| if (line->HasSwitch("port")) {
|
| int port;
|
| if (base::StringToInt(line->GetSwitchValueASCII("port"), &port)) {
|
| FLAGS_port = port;
|
| }
|
| }
|
| - if (line->HasSwitch("address")) {
|
| - FLAGS_address = line->GetSwitchValueASCII("address");
|
| + if (line->HasSwitch("body")) {
|
| + FLAGS_body = line->GetSwitchValueASCII("body");
|
| }
|
| - if (line->HasSwitch("hostname")) {
|
| - FLAGS_hostname = line->GetSwitchValueASCII("hostname");
|
| + if (line->HasSwitch("headers")) {
|
| + FLAGS_headers = line->GetSwitchValueASCII("headers");
|
| }
|
| - if (line->HasSwitch("secure")) {
|
| - FLAGS_secure = true;
|
| + if (line->HasSwitch("quiet")) {
|
| + FLAGS_quiet = true;
|
| }
|
| if (line->HasSwitch("quic-version")) {
|
| int quic_version;
|
| @@ -84,55 +140,124 @@ int main(int argc, char *argv[]) {
|
| FLAGS_quic_version = quic_version;
|
| }
|
| }
|
| - if (line->HasSwitch("flow-control-window-bytes")) {
|
| - int flow_control_window_bytes;
|
| - if (base::StringToInt(
|
| - line->GetSwitchValueASCII("flow-control-window-bytes"),
|
| - &flow_control_window_bytes)) {
|
| - FLAGS_flow_control_window_bytes = flow_control_window_bytes;
|
| - }
|
| + if (line->HasSwitch("version_mismatch_ok")) {
|
| + FLAGS_version_mismatch_ok = true;
|
| + }
|
| + if (line->HasSwitch("redirect_is_success")) {
|
| + FLAGS_redirect_is_success = true;
|
| }
|
| - VLOG(1) << "server port: " << FLAGS_port
|
| - << " address: " << FLAGS_address
|
| - << " hostname: " << FLAGS_hostname
|
| - << " secure: " << FLAGS_secure
|
| - << " quic-version: " << FLAGS_quic_version;
|
| +
|
| + VLOG(1) << "server host: " << FLAGS_host << " port: " << FLAGS_port
|
| + << " body: " << FLAGS_body << " headers: " << FLAGS_headers
|
| + << " quiet: " << FLAGS_quiet
|
| + << " quic-version: " << FLAGS_quic_version
|
| + << " version_mismatch_ok: " << FLAGS_version_mismatch_ok
|
| + << " redirect_is_success: " << FLAGS_redirect_is_success;
|
|
|
| base::AtExitManager exit_manager;
|
|
|
| // Determine IP address to connect to from supplied hostname.
|
| - net::IPAddressNumber addr;
|
| - CHECK(net::ParseIPLiteralToNumber(FLAGS_address, &addr));
|
| + net::IPAddressNumber ip_addr;
|
|
|
| - // Populate version vector with all versions if none specified.
|
| - net::QuicVersionVector versions;
|
| - if (FLAGS_quic_version == 0) {
|
| - versions = net::QuicSupportedVersions();
|
| - } else {
|
| - versions.push_back(static_cast<net::QuicVersion>(FLAGS_quic_version));
|
| - }
|
| + // TODO(rtenneti): GURL's doesn't support default_protocol argument, thus
|
| + // protocol is required in the URL.
|
| + GURL url(urls[0]);
|
| + string host = FLAGS_host;
|
| + // TODO(rtenneti): get ip_addr from hostname by doing host resolution.
|
| + CHECK(!host.empty());
|
| + net::ParseIPLiteralToNumber(host, &ip_addr);
|
| +
|
| + string host_port = net::IPAddressToStringWithPort(ip_addr, FLAGS_port);
|
| + VLOG(1) << "Resolved " << host << " to " << host_port << endl;
|
|
|
| // Build the client, and try to connect.
|
| - VLOG(1) << "Conecting to " << FLAGS_hostname << ":" << FLAGS_port
|
| - << " with supported versions "
|
| - << QuicVersionVectorToString(versions);
|
| net::EpollServer epoll_server;
|
| + net::QuicServerId server_id(host, FLAGS_port, /*is_https=*/false,
|
| + net::PRIVACY_MODE_DISABLED);
|
| + net::QuicVersionVector versions = net::QuicSupportedVersions();
|
| + if (FLAGS_quic_version != -1) {
|
| + versions.clear();
|
| + versions.push_back(static_cast<net::QuicVersion>(FLAGS_quic_version));
|
| + }
|
| + net::tools::QuicClient client(net::IPEndPoint(ip_addr, FLAGS_port), server_id,
|
| + versions, &epoll_server);
|
| + if (!client.Initialize()) {
|
| + cerr << "Failed to initialize client." << endl;
|
| + return 1;
|
| + }
|
| + if (!client.Connect()) {
|
| + net::QuicErrorCode error = client.session()->error();
|
| + if (FLAGS_version_mismatch_ok && error == net::QUIC_INVALID_VERSION) {
|
| + cout << "Server talks QUIC, but none of the versions supoorted by "
|
| + << "this client: " << QuicVersionVectorToString(versions) << endl;
|
| + // Version mismatch is not deemed a failure.
|
| + return 0;
|
| + }
|
| + cerr << "Failed to connect to " << host_port
|
| + << ". Error: " << net::QuicUtils::ErrorToString(error) << endl;
|
| + return 1;
|
| + }
|
| + cout << "Connected to " << host_port << endl;
|
| +
|
| + // Construct a GET or POST request for supplied URL.
|
| + net::BalsaHeaders headers;
|
| + headers.SetRequestFirstlineFromStringPieces(
|
| + FLAGS_body.empty() ? "GET" : "POST", url.spec(), "HTTP/1.1");
|
| +
|
| + // Append any additional headers supplied on the command line.
|
| + vector<string> headers_tokenized;
|
| + Tokenize(FLAGS_headers, ";", &headers_tokenized);
|
| + for (size_t i = 0; i < headers_tokenized.size(); ++i) {
|
| + string sp;
|
| + base::TrimWhitespaceASCII(headers_tokenized[i], base::TRIM_ALL, &sp);
|
| + if (sp.empty()) {
|
| + continue;
|
| + }
|
| + vector<string> kv;
|
| + base::SplitString(sp, ':', &kv);
|
| + CHECK_EQ(2u, kv.size());
|
| + string key;
|
| + base::TrimWhitespaceASCII(kv[0], base::TRIM_ALL, &key);
|
| + string value;
|
| + base::TrimWhitespaceASCII(kv[1], base::TRIM_ALL, &value);
|
| + headers.AppendHeader(key, value);
|
| + }
|
|
|
| - net::tools::QuicClient client(
|
| - net::IPEndPoint(addr, FLAGS_port),
|
| - net::QuicServerId(FLAGS_hostname, FLAGS_port, FLAGS_secure,
|
| - net::PRIVACY_MODE_DISABLED),
|
| - versions, true, &epoll_server);
|
| + // Make sure to store the response, for later output.
|
| + client.set_store_response(true);
|
|
|
| - client.Initialize();
|
| + // Send the request.
|
| + map<string, string> header_block =
|
| + net::tools::SpdyUtils::RequestHeadersToSpdy4Headers(headers);
|
| + client.SendRequestAndWaitForResponse(headers, FLAGS_body, /*fin=*/true);
|
|
|
| - if (!client.Connect()) {
|
| - LOG(ERROR) << "Client failed to connect to host: "
|
| - << FLAGS_hostname << ":" << FLAGS_port;
|
| - return 1;
|
| + // Print request and response details.
|
| + if (!FLAGS_quiet) {
|
| + cout << "Request:" << endl;
|
| + cout << "headers:" << endl;
|
| + for (const std::pair<string, string>& kv : header_block) {
|
| + cout << " " << kv.first << ": " << kv.second << endl;
|
| + }
|
| + cout << "body: " << FLAGS_body << endl;
|
| + cout << endl << "Response:";
|
| + cout << "headers: " << client.latest_response_headers() << endl;
|
| + cout << "body: " << client.latest_response_body() << endl;
|
| }
|
|
|
| - // Send a GET request for each supplied url.
|
| - client.SendRequestsAndWaitForResponse(urls);
|
| - return 0;
|
| + size_t response_code = client.latest_response_code();
|
| + if (response_code >= 200 && response_code < 300) {
|
| + cout << "Request succeeded (" << response_code << ")." << endl;
|
| + return 0;
|
| + } else if (response_code >= 300 && response_code < 400) {
|
| + if (FLAGS_redirect_is_success) {
|
| + cout << "Request succeeded (redirect " << response_code << ")." << endl;
|
| + return 0;
|
| + } else {
|
| + cout << "Request failed (redirect " << response_code << ")." << endl;
|
| + return 1;
|
| + }
|
| + } else {
|
| + cerr << "Request failed (" << response_code << ")." << endl;
|
| + return 1;
|
| + }
|
| }
|
|
|