blob: 6e65538b03bb3aff8df53919d8ea71cbcccb9d7d [file] [log] [blame]
Paul Stewart188a84a2012-01-20 16:28:15 -08001// Copyright (c) 2012 The Chromium OS 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#include "shill/http_request.h"
6
7#include <string>
8
9#include <base/logging.h>
10#include <base/string_number_conversions.h>
11#include <base/stringprintf.h>
12
13#include "shill/async_connection.h"
14#include "shill/connection.h"
15#include "shill/dns_client.h"
16#include "shill/event_dispatcher.h"
17#include "shill/http_url.h"
18#include "shill/ip_address.h"
19#include "shill/sockets.h"
20
21using base::StringPrintf;
22using std::string;
23
24namespace shill {
25
26const int HTTPRequest::kConnectTimeoutSeconds = 10;
27const int HTTPRequest::kDNSTimeoutSeconds = 5;
28const int HTTPRequest::kInputTimeoutSeconds = 10;
29
30const char HTTPRequest::kHTTPRequestTemplate[] =
31 "GET %s HTTP/1.1\r\n"
32 "Host: %s:%d\r\n"
33 "Connection: Close\r\n\r\n";
34
35HTTPRequest::HTTPRequest(ConnectionRefPtr connection,
36 EventDispatcher *dispatcher,
37 Sockets *sockets)
38 : connection_(connection),
39 dispatcher_(dispatcher),
40 sockets_(sockets),
41 connect_completion_callback_(
42 NewCallback(this, &HTTPRequest::OnConnectCompletion)),
43 dns_client_callback_(NewCallback(this, &HTTPRequest::GetDNSResult)),
44 read_server_callback_(NewCallback(this, &HTTPRequest::ReadFromServer)),
45 write_server_callback_(NewCallback(this, &HTTPRequest::WriteToServer)),
46 result_callback_(NULL),
47 read_event_callback_(NULL),
48 task_factory_(this),
49 idle_timeout_(NULL),
50 dns_client_(
51 new DNSClient(IPAddress::kFamilyIPv4,
52 connection->interface_name(),
53 connection->dns_servers(),
54 kDNSTimeoutSeconds * 1000,
55 dispatcher,
56 dns_client_callback_.get())),
57 server_async_connection_(
58 new AsyncConnection(connection_->interface_name(),
59 dispatcher_, sockets,
60 connect_completion_callback_.get())),
61 server_port_(-1),
62 server_socket_(-1),
63 timeout_result_(kResultUnknown),
64 is_running_(false) { }
65
66HTTPRequest::~HTTPRequest() {
67 Stop();
68}
69
70HTTPRequest::Result HTTPRequest::Start(
71 const HTTPURL &url,
72 Callback1<int>::Type *read_event_callback,
73 Callback1<Result>::Type *result_callback) {
74 VLOG(3) << "In " << __func__;
75
76 DCHECK(!is_running_);
77
78 is_running_ = true;
79 request_data_ = ByteString(StringPrintf(kHTTPRequestTemplate,
80 url.path().c_str(),
81 url.host().c_str(),
82 url.port()), false);
83 server_hostname_ = url.host();
84 server_port_ = url.port();
85 connection_->RequestRouting();
86
87 IPAddress addr(IPAddress::kFamilyIPv4);
88 if (addr.SetAddressFromString(server_hostname_)) {
89 if (!ConnectServer(addr, server_port_)) {
90 LOG(ERROR) << "Connect to "
91 << server_hostname_
92 << " failed synchronously";
93 return kResultConnectionFailure;
94 }
95 } else {
96 VLOG(3) << "Looking up host: " << server_hostname_;
97 if (!dns_client_->Start(server_hostname_)) {
98 LOG(ERROR) << "Failed to start DNS client";
99 Stop();
100 return kResultDNSFailure;
101 }
102 }
103
104 // Only install callbacks after connection succeeds in starting.
105 read_event_callback_ = read_event_callback;
106 result_callback_ = result_callback;
107
108 return kResultInProgress;
109}
110
111void HTTPRequest::Stop() {
112 VLOG(3) << "In " << __func__ << "; running is " << is_running_;
113
114 if (!is_running_) {
115 return;
116 }
117
118 // Clear IO handlers first so that closing the socket doesn't cause
119 // events to fire.
120 write_server_handler_.reset();
121 read_server_handler_.reset();
122
123 connection_->ReleaseRouting();
124 dns_client_->Stop();
125 idle_timeout_ = NULL;
126 is_running_ = false;
127 result_callback_ = NULL;
128 read_event_callback_ = NULL;
129 request_data_.Clear();
130 response_data_.Clear();
131 server_async_connection_->Stop();
132 server_hostname_.clear();
133 server_port_ = -1;
134 if (server_socket_ != -1) {
135 sockets_->Close(server_socket_);
136 server_socket_ = -1;
137 }
138 task_factory_.RevokeAll();
139 timeout_result_ = kResultUnknown;
140}
141
142bool HTTPRequest::ConnectServer(const IPAddress &address, int port) {
143 VLOG(3) << "In " << __func__;
144 if (!server_async_connection_->Start(address, port)) {
145 string address_string("<unknown>");
146 address.ToString(&address_string);
147 LOG(ERROR) << "Could not create socket to connect to server at "
148 << address_string;
149 SendStatus(kResultConnectionFailure);
150 return false;
151 }
152 // Start a connection timeout only if we didn't synchronously connect.
153 if (server_socket_ == -1) {
154 StartIdleTimeout(kConnectTimeoutSeconds, kResultConnectionTimeout);
155 }
156 return true;
157}
158
159// DNSClient callback that fires when the DNS request completes.
160void HTTPRequest::GetDNSResult(bool result) {
161 VLOG(3) << "In " << __func__;
162 if (!result) {
163 const string &error = dns_client_->error();
164 LOG(ERROR) << "Could not resolve hostname "
165 << server_hostname_
166 << ": "
167 << error;
168 if (error == DNSClient::kErrorTimedOut) {
169 SendStatus(kResultDNSTimeout);
170 } else {
171 SendStatus(kResultDNSFailure);
172 }
173 return;
174 }
175 ConnectServer(dns_client_->address(), server_port_);
176}
177
178// AsyncConnection callback routine which fires when the asynchronous Connect()
179// to the remote server completes (or fails).
180void HTTPRequest::OnConnectCompletion(bool success, int fd) {
181 VLOG(3) << "In " << __func__;
182 if (!success) {
183 LOG(ERROR) << "Socket connection delayed failure to "
184 << server_hostname_
185 << ": "
186 << server_async_connection_->error();
187 SendStatus(kResultConnectionFailure);
188 return;
189 }
190 server_socket_ = fd;
191 write_server_handler_.reset(
192 dispatcher_->CreateReadyHandler(server_socket_,
193 IOHandler::kModeOutput,
194 write_server_callback_.get()));
195 StartIdleTimeout(kInputTimeoutSeconds, kResultRequestTimeout);
196}
197
198// IOInputHandler callback which fires when data has been read from the
199// server.
200void HTTPRequest::ReadFromServer(InputData *data) {
201 VLOG(3) << "In " << __func__ << " length " << data->len;
202 if (data->len == 0) {
203 SendStatus(kResultSuccess);
204 return;
205 }
206
207 response_data_.Append(ByteString(data->buf, data->len));
208 if (read_event_callback_) {
209 read_event_callback_->Run(data->len);
210 }
211 StartIdleTimeout(kInputTimeoutSeconds, kResultResponseTimeout);
212}
213
214void HTTPRequest::SendStatus(Result result) {
215 if (result_callback_) {
216 result_callback_->Run(result);
217 }
218 Stop();
219}
220
221// Start a timeout for "the next event".
222void HTTPRequest::StartIdleTimeout(int timeout_seconds, Result timeout_result) {
223 if (idle_timeout_) {
224 idle_timeout_->Cancel();
225 }
226 timeout_result_ = timeout_result;
227 idle_timeout_ = task_factory_.NewRunnableMethod(&HTTPRequest::TimeoutTask);
228 dispatcher_->PostDelayedTask(idle_timeout_, timeout_seconds * 1000);
229}
230
231void HTTPRequest::TimeoutTask() {
232 LOG(ERROR) << "Connection with "
233 << server_hostname_
234 << " timed out";
235 SendStatus(timeout_result_);
236}
237
238// Output ReadyHandler callback which fires when the server socket is
239// ready for data to be sent to it.
240void HTTPRequest::WriteToServer(int fd) {
241 CHECK_EQ(server_socket_, fd);
242 int ret = sockets_->Send(fd, request_data_.GetConstData(),
243 request_data_.GetLength(), 0);
244 CHECK(static_cast<size_t>(ret) <= request_data_.GetLength());
245
246 VLOG(3) << "In " << __func__ << " wrote " << ret << " of " <<
247 request_data_.GetLength();
248
249 if (ret < 0) {
250 LOG(ERROR) << "Client write failed to "
251 << server_hostname_;
252 SendStatus(kResultRequestFailure);
253 return;
254 }
255
256 request_data_ = ByteString(request_data_.GetConstData() + ret,
257 request_data_.GetLength() - ret);
258
259 if (request_data_.IsEmpty()) {
260 write_server_handler_->Stop();
261 read_server_handler_.reset(
262 dispatcher_->CreateInputHandler(server_socket_,
263 read_server_callback_.get()));
264 StartIdleTimeout(kInputTimeoutSeconds, kResultResponseTimeout);
265 } else {
266 StartIdleTimeout(kInputTimeoutSeconds, kResultRequestTimeout);
267 }
268}
269
270} // namespace shill