| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 // A binary wrapper for QuicClient. | |
| 6 // Connects to a host using QUIC, sends a request to the provided URL, and | |
| 7 // displays the response. | |
| 8 // | |
| 9 // Some usage examples: | |
| 10 // | |
| 11 // TODO(rtenneti): make --host optional by getting IP Address of URL's host. | |
| 12 // | |
| 13 // Get IP address of the www.google.com | |
| 14 // IP=`dig www.google.com +short | head -1` | |
| 15 // | |
| 16 // Standard request/response: | |
| 17 // quic_client http://www.google.com --host=${IP} | |
| 18 // quic_client http://www.google.com --quiet --host=${IP} | |
| 19 // quic_client https://www.google.com --port=443 --host=${IP} | |
| 20 // | |
| 21 // Use a specific version: | |
| 22 // quic_client http://www.google.com --version=23 --host=${IP} | |
| 23 // | |
| 24 // Send a POST instead of a GET: | |
| 25 // quic_client http://www.google.com --body="this is a POST body" --host=${IP} | |
| 26 // | |
| 27 // Append additional headers to the request: | |
| 28 // quic_client http://www.google.com --host=${IP} | |
| 29 // --headers="Header-A: 1234; Header-B: 5678" | |
| 30 // | |
| 31 // Connect to a host different to the URL being requested: | |
| 32 // Get IP address of the www.google.com | |
| 33 // IP=`dig www.google.com +short | head -1` | |
| 34 // quic_client mail.google.com --host=${IP} | |
| 35 // | |
| 36 // Try to connect to a host which does not speak QUIC: | |
| 37 // Get IP address of the www.example.com | |
| 38 // IP=`dig www.example.com +short | head -1` | |
| 39 // quic_client http://www.example.com --host=${IP} | |
| 40 | |
| 41 #include <iostream> | |
| 42 | |
| 43 #include "base/at_exit.h" | |
| 44 #include "base/command_line.h" | |
| 45 #include "base/logging.h" | |
| 46 #include "base/strings/string_number_conversions.h" | |
| 47 #include "base/strings/string_split.h" | |
| 48 #include "base/strings/string_util.h" | |
| 49 #include "net/base/ip_endpoint.h" | |
| 50 #include "net/base/privacy_mode.h" | |
| 51 #include "net/cert/cert_verifier.h" | |
| 52 #include "net/http/transport_security_state.h" | |
| 53 #include "net/quic/crypto/proof_verifier_chromium.h" | |
| 54 #include "net/quic/quic_protocol.h" | |
| 55 #include "net/quic/quic_server_id.h" | |
| 56 #include "net/quic/quic_utils.h" | |
| 57 #include "net/tools/epoll_server/epoll_server.h" | |
| 58 #include "net/tools/quic/quic_client.h" | |
| 59 #include "net/tools/quic/spdy_utils.h" | |
| 60 #include "url/gurl.h" | |
| 61 | |
| 62 using base::StringPiece; | |
| 63 using net::CertVerifier; | |
| 64 using net::ProofVerifierChromium; | |
| 65 using net::TransportSecurityState; | |
| 66 using std::cout; | |
| 67 using std::cerr; | |
| 68 using std::map; | |
| 69 using std::string; | |
| 70 using std::vector; | |
| 71 using std::endl; | |
| 72 | |
| 73 // The IP or hostname the quic client will connect to. | |
| 74 string FLAGS_host = ""; | |
| 75 // The port to connect to. | |
| 76 int32 FLAGS_port = 80; | |
| 77 // If set, send a POST with this body. | |
| 78 string FLAGS_body = ""; | |
| 79 // A semicolon separated list of key:value pairs to add to request headers. | |
| 80 string FLAGS_headers = ""; | |
| 81 // Set to true for a quieter output experience. | |
| 82 bool FLAGS_quiet = false; | |
| 83 // QUIC version to speak, e.g. 21. If not set, then all available versions are | |
| 84 // offered in the handshake. | |
| 85 int32 FLAGS_quic_version = -1; | |
| 86 // If true, a version mismatch in the handshake is not considered a failure. | |
| 87 // Useful for probing a server to determine if it speaks any version of QUIC. | |
| 88 bool FLAGS_version_mismatch_ok = false; | |
| 89 // If true, an HTTP response code of 3xx is considered to be a successful | |
| 90 // response, otherwise a failure. | |
| 91 bool FLAGS_redirect_is_success = true; | |
| 92 | |
| 93 int main(int argc, char *argv[]) { | |
| 94 base::CommandLine::Init(argc, argv); | |
| 95 base::CommandLine* line = base::CommandLine::ForCurrentProcess(); | |
| 96 const base::CommandLine::StringVector& urls = line->GetArgs(); | |
| 97 | |
| 98 logging::LoggingSettings settings; | |
| 99 settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; | |
| 100 CHECK(logging::InitLogging(settings)); | |
| 101 | |
| 102 if (line->HasSwitch("h") || line->HasSwitch("help") || urls.empty()) { | |
| 103 const char* help_str = | |
| 104 "Usage: quic_client [options] <url>\n" | |
| 105 "\n" | |
| 106 "<url> with scheme must be provided (e.g. http://www.google.com)\n\n" | |
| 107 "Options:\n" | |
| 108 "-h, --help show this help message and exit\n" | |
| 109 "--host=<host> specify the IP address of the hostname to " | |
| 110 "connect to\n" | |
| 111 "--port=<port> specify the port to connect to\n" | |
| 112 "--body=<body> specify the body to post\n" | |
| 113 "--headers=<headers> specify a semicolon separated list of " | |
| 114 "key:value pairs to add to request headers\n" | |
| 115 "--quiet specify for a quieter output experience\n" | |
| 116 "--quic-version=<quic version> specify QUIC version to speak\n" | |
| 117 "--version_mismatch_ok if specified a version mismatch in the " | |
| 118 "handshake is not considered a failure\n" | |
| 119 "--redirect_is_success if specified an HTTP response code of 3xx " | |
| 120 "is considered to be a successful response, otherwise a failure\n"; | |
| 121 cout << help_str; | |
| 122 exit(0); | |
| 123 } | |
| 124 if (line->HasSwitch("host")) { | |
| 125 FLAGS_host = line->GetSwitchValueASCII("host"); | |
| 126 } | |
| 127 if (line->HasSwitch("port")) { | |
| 128 int port; | |
| 129 if (base::StringToInt(line->GetSwitchValueASCII("port"), &port)) { | |
| 130 FLAGS_port = port; | |
| 131 } | |
| 132 } | |
| 133 if (line->HasSwitch("body")) { | |
| 134 FLAGS_body = line->GetSwitchValueASCII("body"); | |
| 135 } | |
| 136 if (line->HasSwitch("headers")) { | |
| 137 FLAGS_headers = line->GetSwitchValueASCII("headers"); | |
| 138 } | |
| 139 if (line->HasSwitch("quiet")) { | |
| 140 FLAGS_quiet = true; | |
| 141 } | |
| 142 if (line->HasSwitch("quic-version")) { | |
| 143 int quic_version; | |
| 144 if (base::StringToInt(line->GetSwitchValueASCII("quic-version"), | |
| 145 &quic_version)) { | |
| 146 FLAGS_quic_version = quic_version; | |
| 147 } | |
| 148 } | |
| 149 if (line->HasSwitch("version_mismatch_ok")) { | |
| 150 FLAGS_version_mismatch_ok = true; | |
| 151 } | |
| 152 if (line->HasSwitch("redirect_is_success")) { | |
| 153 FLAGS_redirect_is_success = true; | |
| 154 } | |
| 155 | |
| 156 VLOG(1) << "server host: " << FLAGS_host << " port: " << FLAGS_port | |
| 157 << " body: " << FLAGS_body << " headers: " << FLAGS_headers | |
| 158 << " quiet: " << FLAGS_quiet | |
| 159 << " quic-version: " << FLAGS_quic_version | |
| 160 << " version_mismatch_ok: " << FLAGS_version_mismatch_ok | |
| 161 << " redirect_is_success: " << FLAGS_redirect_is_success; | |
| 162 | |
| 163 base::AtExitManager exit_manager; | |
| 164 | |
| 165 // Determine IP address to connect to from supplied hostname. | |
| 166 net::IPAddressNumber ip_addr; | |
| 167 | |
| 168 // TODO(rtenneti): GURL's doesn't support default_protocol argument, thus | |
| 169 // protocol is required in the URL. | |
| 170 GURL url(urls[0]); | |
| 171 string host = FLAGS_host; | |
| 172 // TODO(rtenneti): get ip_addr from hostname by doing host resolution. | |
| 173 CHECK(!host.empty()); | |
| 174 net::ParseIPLiteralToNumber(host, &ip_addr); | |
| 175 | |
| 176 string host_port = net::IPAddressToStringWithPort(ip_addr, FLAGS_port); | |
| 177 VLOG(1) << "Resolved " << host << " to " << host_port << endl; | |
| 178 | |
| 179 // Build the client, and try to connect. | |
| 180 bool is_https = (FLAGS_port == 443); | |
| 181 net::EpollServer epoll_server; | |
| 182 net::QuicServerId server_id(host, FLAGS_port, is_https, | |
| 183 net::PRIVACY_MODE_DISABLED); | |
| 184 net::QuicVersionVector versions = net::QuicSupportedVersions(); | |
| 185 if (FLAGS_quic_version != -1) { | |
| 186 versions.clear(); | |
| 187 versions.push_back(static_cast<net::QuicVersion>(FLAGS_quic_version)); | |
| 188 } | |
| 189 net::tools::QuicClient client(net::IPEndPoint(ip_addr, FLAGS_port), server_id, | |
| 190 versions, &epoll_server); | |
| 191 scoped_ptr<CertVerifier> cert_verifier; | |
| 192 scoped_ptr<TransportSecurityState> transport_security_state; | |
| 193 if (is_https) { | |
| 194 // For secure QUIC we need to verify the cert chain.a | |
| 195 cert_verifier.reset(CertVerifier::CreateDefault()); | |
| 196 transport_security_state.reset(new TransportSecurityState); | |
| 197 // TODO(rtenneti): Fix "Proof invalid: Missing context" error. | |
| 198 client.SetProofVerifier(new ProofVerifierChromium( | |
| 199 cert_verifier.get(), transport_security_state.get())); | |
| 200 } | |
| 201 if (!client.Initialize()) { | |
| 202 cerr << "Failed to initialize client." << endl; | |
| 203 return 1; | |
| 204 } | |
| 205 if (!client.Connect()) { | |
| 206 net::QuicErrorCode error = client.session()->error(); | |
| 207 if (FLAGS_version_mismatch_ok && error == net::QUIC_INVALID_VERSION) { | |
| 208 cout << "Server talks QUIC, but none of the versions supoorted by " | |
| 209 << "this client: " << QuicVersionVectorToString(versions) << endl; | |
| 210 // Version mismatch is not deemed a failure. | |
| 211 return 0; | |
| 212 } | |
| 213 cerr << "Failed to connect to " << host_port | |
| 214 << ". Error: " << net::QuicUtils::ErrorToString(error) << endl; | |
| 215 return 1; | |
| 216 } | |
| 217 cout << "Connected to " << host_port << endl; | |
| 218 | |
| 219 // Construct a GET or POST request for supplied URL. | |
| 220 net::BalsaHeaders headers; | |
| 221 headers.SetRequestFirstlineFromStringPieces( | |
| 222 FLAGS_body.empty() ? "GET" : "POST", url.spec(), "HTTP/1.1"); | |
| 223 | |
| 224 // Append any additional headers supplied on the command line. | |
| 225 vector<string> headers_tokenized; | |
| 226 Tokenize(FLAGS_headers, ";", &headers_tokenized); | |
| 227 for (size_t i = 0; i < headers_tokenized.size(); ++i) { | |
| 228 string sp; | |
| 229 base::TrimWhitespaceASCII(headers_tokenized[i], base::TRIM_ALL, &sp); | |
| 230 if (sp.empty()) { | |
| 231 continue; | |
| 232 } | |
| 233 vector<string> kv; | |
| 234 base::SplitString(sp, ':', &kv); | |
| 235 CHECK_EQ(2u, kv.size()); | |
| 236 string key; | |
| 237 base::TrimWhitespaceASCII(kv[0], base::TRIM_ALL, &key); | |
| 238 string value; | |
| 239 base::TrimWhitespaceASCII(kv[1], base::TRIM_ALL, &value); | |
| 240 headers.AppendHeader(key, value); | |
| 241 } | |
| 242 | |
| 243 // Make sure to store the response, for later output. | |
| 244 client.set_store_response(true); | |
| 245 | |
| 246 // Send the request. | |
| 247 map<string, string> header_block = | |
| 248 net::tools::SpdyUtils::RequestHeadersToSpdy4Headers(headers); | |
| 249 client.SendRequestAndWaitForResponse(headers, FLAGS_body, /*fin=*/true); | |
| 250 | |
| 251 // Print request and response details. | |
| 252 if (!FLAGS_quiet) { | |
| 253 cout << "Request:" << endl; | |
| 254 cout << "headers:" << endl; | |
| 255 for (const std::pair<string, string>& kv : header_block) { | |
| 256 cout << " " << kv.first << ": " << kv.second << endl; | |
| 257 } | |
| 258 cout << "body: " << FLAGS_body << endl; | |
| 259 cout << endl << "Response:"; | |
| 260 cout << "headers: " << client.latest_response_headers() << endl; | |
| 261 cout << "body: " << client.latest_response_body() << endl; | |
| 262 } | |
| 263 | |
| 264 size_t response_code = client.latest_response_code(); | |
| 265 if (response_code >= 200 && response_code < 300) { | |
| 266 cout << "Request succeeded (" << response_code << ")." << endl; | |
| 267 return 0; | |
| 268 } else if (response_code >= 300 && response_code < 400) { | |
| 269 if (FLAGS_redirect_is_success) { | |
| 270 cout << "Request succeeded (redirect " << response_code << ")." << endl; | |
| 271 return 0; | |
| 272 } else { | |
| 273 cout << "Request failed (redirect " << response_code << ")." << endl; | |
| 274 return 1; | |
| 275 } | |
| 276 } else { | |
| 277 cerr << "Request failed (" << response_code << ")." << endl; | |
| 278 return 1; | |
| 279 } | |
| 280 } | |
| OLD | NEW |