普通文本  |  306行  |  9.99 KB

//
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#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 {

namespace Logging {
static auto kModuleLogScope = ScopeLogger::kHTTP;
static string ObjectID(Connection* c) { return c->interface_name(); }
}

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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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(connection_.get(), 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