| // Copyright (c) 2012 The Chromium OS 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 "shill/http_request.h" |
| |
| #include <string> |
| |
| #include <base/bind.h> |
| #include <base/strings/string_number_conversions.h> |
| #include <base/strings/stringprintf.h> |
| |
| #include "shill/async_connection.h" |
| #include "shill/connection.h" |
| #include "shill/dns_client.h" |
| #include "shill/error.h" |
| #include "shill/event_dispatcher.h" |
| #include "shill/http_url.h" |
| #include "shill/logging.h" |
| #include "shill/net/ip_address.h" |
| #include "shill/net/sockets.h" |
| |
| using base::Bind; |
| using base::Callback; |
| using base::StringPrintf; |
| using std::string; |
| |
| namespace shill { |
| |
| const int HTTPRequest::kConnectTimeoutSeconds = 10; |
| const int HTTPRequest::kDNSTimeoutSeconds = 5; |
| const int HTTPRequest::kInputTimeoutSeconds = 10; |
| |
| const char HTTPRequest::kHTTPRequestTemplate[] = |
| "GET %s HTTP/1.1\r\n" |
| "Host: %s:%d\r\n" |
| "Connection: Close\r\n\r\n"; |
| |
| HTTPRequest::HTTPRequest(ConnectionRefPtr connection, |
| EventDispatcher *dispatcher, |
| Sockets *sockets) |
| : connection_(connection), |
| dispatcher_(dispatcher), |
| sockets_(sockets), |
| weak_ptr_factory_(this), |
| connect_completion_callback_( |
| Bind(&HTTPRequest::OnConnectCompletion, |
| weak_ptr_factory_.GetWeakPtr())), |
| dns_client_callback_(Bind(&HTTPRequest::GetDNSResult, |
| weak_ptr_factory_.GetWeakPtr())), |
| read_server_callback_(Bind(&HTTPRequest::ReadFromServer, |
| weak_ptr_factory_.GetWeakPtr())), |
| write_server_callback_(Bind(&HTTPRequest::WriteToServer, |
| weak_ptr_factory_.GetWeakPtr())), |
| dns_client_( |
| new DNSClient(connection->IsIPv6() ? IPAddress::kFamilyIPv6 |
| : IPAddress::kFamilyIPv4, |
| connection->interface_name(), |
| connection->dns_servers(), |
| kDNSTimeoutSeconds * 1000, |
| dispatcher, |
| dns_client_callback_)), |
| server_async_connection_( |
| new AsyncConnection(connection_->interface_name(), |
| dispatcher_, sockets, |
| connect_completion_callback_)), |
| server_port_(-1), |
| server_socket_(-1), |
| timeout_result_(kResultUnknown), |
| is_running_(false) { } |
| |
| HTTPRequest::~HTTPRequest() { |
| Stop(); |
| } |
| |
| HTTPRequest::Result HTTPRequest::Start( |
| const HTTPURL &url, |
| const Callback<void(const ByteString &)> &read_event_callback, |
| const Callback<void(Result, const ByteString &)> &result_callback) { |
| SLOG(HTTP, 3) << "In " << __func__; |
| |
| DCHECK(!is_running_); |
| |
| is_running_ = true; |
| request_data_ = ByteString(StringPrintf(kHTTPRequestTemplate, |
| url.path().c_str(), |
| url.host().c_str(), |
| url.port()), false); |
| server_hostname_ = url.host(); |
| server_port_ = url.port(); |
| connection_->RequestRouting(); |
| |
| IPAddress addr(IPAddress::kFamilyIPv4); |
| if (connection_->IsIPv6()) { |
| addr.set_family(IPAddress::kFamilyIPv6); |
| } |
| if (addr.SetAddressFromString(server_hostname_)) { |
| if (!ConnectServer(addr, server_port_)) { |
| LOG(ERROR) << "Connect to " |
| << server_hostname_ |
| << " failed synchronously"; |
| return kResultConnectionFailure; |
| } |
| } else { |
| SLOG(HTTP, 3) << "Looking up host: " << server_hostname_; |
| Error error; |
| if (!dns_client_->Start(server_hostname_, &error)) { |
| LOG(ERROR) << "Failed to start DNS client: " << error.message(); |
| Stop(); |
| return kResultDNSFailure; |
| } |
| } |
| |
| // Only install callbacks after connection succeeds in starting. |
| read_event_callback_ = read_event_callback; |
| result_callback_ = result_callback; |
| |
| return kResultInProgress; |
| } |
| |
| void HTTPRequest::Stop() { |
| SLOG(HTTP, 3) << "In " << __func__ << "; running is " << is_running_; |
| |
| if (!is_running_) { |
| return; |
| } |
| |
| // Clear IO handlers first so that closing the socket doesn't cause |
| // events to fire. |
| write_server_handler_.reset(); |
| read_server_handler_.reset(); |
| |
| connection_->ReleaseRouting(); |
| dns_client_->Stop(); |
| is_running_ = false; |
| result_callback_.Reset(); |
| read_event_callback_.Reset(); |
| request_data_.Clear(); |
| response_data_.Clear(); |
| server_async_connection_->Stop(); |
| server_hostname_.clear(); |
| server_port_ = -1; |
| if (server_socket_ != -1) { |
| sockets_->Close(server_socket_); |
| server_socket_ = -1; |
| } |
| timeout_closure_.Cancel(); |
| timeout_result_ = kResultUnknown; |
| } |
| |
| bool HTTPRequest::ConnectServer(const IPAddress &address, int port) { |
| SLOG(HTTP, 3) << "In " << __func__; |
| if (!server_async_connection_->Start(address, port)) { |
| LOG(ERROR) << "Could not create socket to connect to server at " |
| << address.ToString(); |
| SendStatus(kResultConnectionFailure); |
| return false; |
| } |
| // Start a connection timeout only if we didn't synchronously connect. |
| if (server_socket_ == -1) { |
| StartIdleTimeout(kConnectTimeoutSeconds, kResultConnectionTimeout); |
| } |
| return true; |
| } |
| |
| // DNSClient callback that fires when the DNS request completes. |
| void HTTPRequest::GetDNSResult(const Error &error, const IPAddress &address) { |
| SLOG(HTTP, 3) << "In " << __func__; |
| if (!error.IsSuccess()) { |
| LOG(ERROR) << "Could not resolve hostname " |
| << server_hostname_ |
| << ": " |
| << error.message(); |
| if (error.message() == DNSClient::kErrorTimedOut) { |
| SendStatus(kResultDNSTimeout); |
| } else { |
| SendStatus(kResultDNSFailure); |
| } |
| return; |
| } |
| ConnectServer(address, server_port_); |
| } |
| |
| // AsyncConnection callback routine which fires when the asynchronous Connect() |
| // to the remote server completes (or fails). |
| void HTTPRequest::OnConnectCompletion(bool success, int fd) { |
| SLOG(HTTP, 3) << "In " << __func__; |
| if (!success) { |
| LOG(ERROR) << "Socket connection delayed failure to " |
| << server_hostname_ |
| << ": " |
| << server_async_connection_->error(); |
| // |this| could be freed as a result of calling SendStatus(). |
| SendStatus(kResultConnectionFailure); |
| return; |
| } |
| server_socket_ = fd; |
| write_server_handler_.reset( |
| dispatcher_->CreateReadyHandler(server_socket_, |
| IOHandler::kModeOutput, |
| write_server_callback_)); |
| StartIdleTimeout(kInputTimeoutSeconds, kResultRequestTimeout); |
| } |
| |
| void HTTPRequest::OnServerReadError(const string &/*error_msg*/) { |
| SendStatus(kResultResponseFailure); |
| } |
| |
| // IOInputHandler callback which fires when data has been read from the |
| // server. |
| void HTTPRequest::ReadFromServer(InputData *data) { |
| SLOG(HTTP, 3) << "In " << __func__ << " length " << data->len; |
| if (data->len == 0) { |
| SendStatus(kResultSuccess); |
| return; |
| } |
| |
| response_data_.Append(ByteString(data->buf, data->len)); |
| StartIdleTimeout(kInputTimeoutSeconds, kResultResponseTimeout); |
| if (!read_event_callback_.is_null()) { |
| read_event_callback_.Run(response_data_); |
| } |
| } |
| |
| void HTTPRequest::SendStatus(Result result) { |
| // Save copies on the stack, since Stop() will remove them. |
| Callback<void(Result, const ByteString &)> result_callback = result_callback_; |
| const ByteString response_data(response_data_); |
| Stop(); |
| |
| // Call the callback last, since it may delete us and |this| may no longer |
| // be valid. |
| if (!result_callback.is_null()) { |
| result_callback.Run(result, response_data); |
| } |
| } |
| |
| // Start a timeout for "the next event". |
| void HTTPRequest::StartIdleTimeout(int timeout_seconds, Result timeout_result) { |
| timeout_result_ = timeout_result; |
| timeout_closure_.Reset( |
| Bind(&HTTPRequest::TimeoutTask, weak_ptr_factory_.GetWeakPtr())); |
| dispatcher_->PostDelayedTask(timeout_closure_.callback(), |
| timeout_seconds * 1000); |
| } |
| |
| void HTTPRequest::TimeoutTask() { |
| LOG(ERROR) << "Connection with " |
| << server_hostname_ |
| << " timed out"; |
| SendStatus(timeout_result_); |
| } |
| |
| // Output ReadyHandler callback which fires when the server socket is |
| // ready for data to be sent to it. |
| void HTTPRequest::WriteToServer(int fd) { |
| CHECK_EQ(server_socket_, fd); |
| int ret = sockets_->Send(fd, request_data_.GetConstData(), |
| request_data_.GetLength(), 0); |
| CHECK(ret < 0 || static_cast<size_t>(ret) <= request_data_.GetLength()); |
| |
| SLOG(HTTP, 3) << "In " << __func__ << " wrote " << ret << " of " |
| << request_data_.GetLength(); |
| |
| if (ret < 0) { |
| LOG(ERROR) << "Client write failed to " |
| << server_hostname_; |
| SendStatus(kResultRequestFailure); |
| return; |
| } |
| |
| request_data_ = ByteString(request_data_.GetConstData() + ret, |
| request_data_.GetLength() - ret); |
| |
| if (request_data_.IsEmpty()) { |
| write_server_handler_->Stop(); |
| read_server_handler_.reset(dispatcher_->CreateInputHandler( |
| server_socket_, |
| read_server_callback_, |
| Bind(&HTTPRequest::OnServerReadError, weak_ptr_factory_.GetWeakPtr()))); |
| StartIdleTimeout(kInputTimeoutSeconds, kResultResponseTimeout); |
| } else { |
| StartIdleTimeout(kInputTimeoutSeconds, kResultRequestTimeout); |
| } |
| } |
| |
| } // namespace shill |