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