// Copyright 2013 Google Inc. All Rights Reserved. #include "http_socket.h" #include #include #include #include #include #include "log.h" namespace wvcdm { HttpSocket::HttpSocket() : socket_fd_(-1), timeout_enabled_(false) {} HttpSocket::~HttpSocket() { CloseSocket(); } void HttpSocket::CloseSocket() { if (socket_fd_ != -1) { close(socket_fd_); socket_fd_ = -1; } } // Extracts the domain name and resource path from the input url parameter. // The results are put in domain_name and resource_path respectively. // The format of the url can begin with :://domain server/... // or dowmain server/resource_path void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url, std::string& domain_name, std::string& resource_path) { domain_name.clear(); resource_path.clear(); size_t start = url.find("//"); size_t end = url.npos; if (start != url.npos) { end = url.find("/", start + 2); if (end != url.npos) { domain_name.assign(url, start + 2, end - start - 2); resource_path.assign(url, end + 1, url.npos); } else { domain_name.assign(url, start + 2, url.npos); } } else { // no scheme/protocol in url end = url.find("/"); if (end != url.npos) { domain_name.assign(url, 0, end); resource_path.assign(url, end + 1, url.npos); } else { domain_name.assign(url); } } // strips port number if present, e.g. https://www.domain.com:8888/... end = domain_name.find(":"); if (end != domain_name.npos) { domain_name.erase(end); } } bool HttpSocket::Connect(const char* url, const std::string& port, bool enable_timeout) { GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_); socket_fd_ = socket(AF_INET, SOCK_STREAM, 0); if (socket_fd_ < 0) { LOGE("cannot open socket %d", errno); return false; } int reuse = 1; if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) { CloseSocket(); LOGE("setsockopt error %d", errno); return false; } struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; struct addrinfo* addr_info = NULL; bool status = true; int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info); if (ret != 0) { CloseSocket(); LOGE("getaddrinfo failed with %d", ret); status = false; } else { if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) { CloseSocket(); LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(), errno); status = false; } } timeout_enabled_ = enable_timeout; if (addr_info != NULL) { freeaddrinfo(addr_info); } return status; } int HttpSocket::Read(char* data, int len) { return(Read(data, len, 0)); } // makes non-blocking mode only during read, it supports timeout for read // returns -1 for error, number of bytes read for success int HttpSocket::Read(char* data, int len, int timeout_in_ms) { bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0)); int original_flags = 0; if (use_timeout) { original_flags = fcntl(socket_fd_, F_GETFL, 0); if (original_flags == -1) { LOGE("fcntl error %d", errno); return -1; } if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) { LOGE("fcntl error %d", errno); return -1; } } int total_read = 0; int read = 0; int to_read = len; while (to_read > 0) { if (use_timeout) { fd_set read_fds; struct timeval tv; tv.tv_sec = timeout_in_ms / 1000; tv.tv_usec = (timeout_in_ms % 1000) * 1000; FD_ZERO(&read_fds); FD_SET(socket_fd_, &read_fds); if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) { LOGE("select failed"); break; } if (!FD_ISSET(socket_fd_, &read_fds)) { LOGE("socket read timeout"); break; } } read = recv(socket_fd_, data, to_read, 0); if (read > 0) { to_read -= read; data += read; total_read += read; } else if (read == 0) { // in blocking mode, zero read mean's peer closed. // in non-blocking mode, select said that there is data. so it should not happen break; } else { LOGE("recv returned %d, error = %d", read, errno); break; } } if (use_timeout) { fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again } return total_read; } int HttpSocket::Write(const char* data, int len) { int total_sent = 0; int sent = 0; int to_send = len; while (to_send > 0) { sent = send(socket_fd_, data, to_send, 0); if (sent > 0) { to_send -= sent; data += sent; total_sent += sent; } else if (sent == 0) { usleep(10); // retry later } else { LOGE("send returned error %d", errno); } } return total_sent; } } // namespace wvcdm