1
0
Fork 0
arangodb/SimpleHttpClient/SimpleHttpConnection.cpp

460 lines
12 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// @brief simple http client
///
/// @file
///
/// DISCLAIMER
///
/// Copyright 2010-2011 triagens GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is triAGENS GmbH, Cologne, Germany
///
/// @author Dr. Frank Celler
/// @author Achim Brandt
/// @author Copyright 2009, triagens GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
#include "SimpleHttpConnection.h"
#include <stdio.h>
#include <string>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include "Basics/StringUtils.h"
using namespace triagens::basics;
using namespace std;
namespace triagens {
namespace httpclient {
// -----------------------------------------------------------------------------
// static
// -----------------------------------------------------------------------------
double SimpleHttpConnection::_minimalConnectionTimeout = 0.2;
double SimpleHttpConnection::_minimalRequestTimeout = 0.2;
// -----------------------------------------------------------------------------
// constructors and destructors
// -----------------------------------------------------------------------------
SimpleHttpConnection::SimpleHttpConnection (string const& hostname, int port, double connTimeout)
: _hostname (hostname), _port (port) {
if (connTimeout > _minimalConnectionTimeout) {
_connectionTimeout = connTimeout;
}
else {
_connectionTimeout = _minimalConnectionTimeout;
}
_isConnected = false;
_socket = -1;
clearErrorMessage();
_readBufferSize = 0;
}
SimpleHttpConnection::~SimpleHttpConnection () {
if (_isConnected) {
::close(_socket);
}
}
bool SimpleHttpConnection::connect () {
if (_isConnected) {
return true;
}
clearErrorMessage();
return connectSocket();
}
bool SimpleHttpConnection::close () {
_readBufferSize = 0;
if (_isConnected) {
::close(_socket);
_isConnected = false;
return true;
}
return false;
}
bool SimpleHttpConnection::write (const string& str, double timeout) {
if (!writeable(timeout)) {
return false;
}
#ifdef __APPLE__
int status = ::send(_socket, str.c_str(), str.size(), 0);
#else
int status = ::send(_socket, str.c_str(), str.size(), MSG_NOSIGNAL);
#endif
if (status == -1) {
setErrorMessage("::send failed with: " + string(strerror(errno)), errno);
return false;
}
else {
return true;
}
}
bool SimpleHttpConnection::readLn (std::string& line, double timeout) {
stringstream result;
if (_readBufferSize > 0) {
char* pos = (char*) memchr(_readBuffer, '\n', _readBufferSize);
if (pos) {
// line end found
size_t len_line = pos - _readBuffer;
line = string(_readBuffer, len_line);
_readBufferSize = _readBufferSize - len_line - 1;
memmove(_readBuffer, pos + 1, _readBufferSize);
return true;
}
result.write(_readBuffer, _readBufferSize);
_readBufferSize = 0;
}
bool ok = false;
double start = now();
double runtime = 0.0;
while (runtime < timeout && readable(timeout - runtime)) {
int len_read = ::read(_socket, _readBuffer, READBUFFER_SIZE - 1);
if (len_read <= 0) {
// error: stop reading
break;
}
char* pos = (char*) memchr(_readBuffer, '\n', len_read);
if (pos) {
// line end found
size_t len_line = pos - _readBuffer;
result.write(_readBuffer, len_line);
_readBufferSize = len_read - len_line - 1;
memmove(_readBuffer, pos + 1, _readBufferSize);
ok = true;
break;
}
result.write(_readBuffer, len_read);
runtime = now() - start;
}
line = result.str();
return ok;
}
size_t SimpleHttpConnection::read (stringstream& stream, size_t contentLength, double timeout) {
size_t toRead = contentLength;
if (_readBufferSize > 0) {
if (_readBufferSize == toRead) {
stream.write(_readBuffer, _readBufferSize);
_readBufferSize = 0;
return contentLength;
}
if (_readBufferSize > toRead) {
stream.write(_readBuffer, toRead);
_readBufferSize = _readBufferSize - toRead;
memmove(_readBuffer, _readBuffer + toRead, _readBufferSize);
return contentLength;
}
toRead -= _readBufferSize;
stream.write(_readBuffer, _readBufferSize);
_readBufferSize = 0;
}
size_t len = 0;
char* buffer = (char*) malloc(toRead + 1);
double start = now();
double runtime = 0.0;
while (runtime < timeout && len < toRead && readable(timeout - runtime)) {
int len_read = ::read(_socket, buffer + len, toRead - len);
if (len_read <= 0) {
// error: stop reading
break;
}
len += len_read;
runtime = now() - start;
}
stream.write(buffer, len);
free(buffer);
return (contentLength - toRead + len);
}
////////////////////////////////////////////////////////////////////////////////
/// @brief returns true if the client is connected
////////////////////////////////////////////////////////////////////////////////
bool SimpleHttpConnection::isConnected () {
return _isConnected;
}
bool SimpleHttpConnection::readable (double timeout) {
fd_set fdset;
struct timeval tv;
FD_ZERO(&fdset);
FD_SET(_socket, &fdset);
int so_error = -1;
if (timeout > _minimalRequestTimeout) {
tv.tv_sec = (uint64_t) timeout;
tv.tv_usec = ((uint64_t) (timeout * 1000000.0)) % 1000000;
}
else {
tv.tv_sec = (uint64_t) _minimalRequestTimeout;
tv.tv_usec = ((uint64_t) (_minimalRequestTimeout * 1000000.0)) % 1000000;
}
if (select(_socket + 1, &fdset, NULL, NULL, &tv) == 1) {
socklen_t len = sizeof so_error;
getsockopt(_socket, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
return true;
}
setErrorMessage("socket not readable. getsockopt() failed with: " + string(strerror(errno)), errno);
}
else {
setErrorMessage("socket not readable. select() failed with: " + string(strerror(errno)), errno);
}
return false;
}
bool SimpleHttpConnection::writeable (double timeout) {
fd_set fdset;
struct timeval tv;
FD_ZERO(&fdset);
FD_SET(_socket, &fdset);
int so_error = -1;
if (timeout > _minimalRequestTimeout) {
tv.tv_sec = (uint64_t) timeout;
tv.tv_usec = ((uint64_t) (timeout * 1000000.0)) % 1000000;
}
else {
tv.tv_sec = (uint64_t) _minimalRequestTimeout;
tv.tv_usec = ((uint64_t) (_minimalRequestTimeout * 1000000.0)) % 1000000;
}
if (select(_socket + 1, NULL, &fdset, NULL, &tv) == 1) {
socklen_t len = sizeof so_error;
getsockopt(_socket, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
return true;
}
setErrorMessage("socket not writable. getsockopt() failed with: " + string(strerror(errno)), errno);
}
else {
setErrorMessage("socket not writable. select() failed with: " + string(strerror(errno)), errno);
}
return false;
}
// -----------------------------------------------------------------------------
// private methods
// -----------------------------------------------------------------------------
bool SimpleHttpConnection::connectSocket () {
_readBufferSize = 0;
_isConnected = false;
_socket = -1;
struct addrinfo *result, *aip;
struct addrinfo hints;
int error;
memset(&hints, 0, sizeof (struct addrinfo));
hints.ai_family = AF_UNSPEC; // Allow IPv4 or IPv6
hints.ai_flags = AI_NUMERICSERV | AI_ALL;
hints.ai_socktype = SOCK_STREAM;
string portString = StringUtils::itoa(_port);
if (_hostname.empty()) {
string localhost = "localhost";
error = getaddrinfo(localhost.c_str(), portString.c_str(), &hints, &result);
}
else {
error = getaddrinfo(_hostname.c_str(), portString.c_str(), &hints, &result);
}
if (error != 0) {
setErrorMessage("socket not connected. getaddrinfo() failed with: " + string(strerror(errno)), errno);
return false;
}
// Try all returned addresses until one works
for (aip = result; aip != NULL; aip = aip->ai_next) {
// try to connect the address info pointer
if (connectSocket(aip)) {
// is connected
break;
}
}
freeaddrinfo(result);
return _isConnected;
}
bool SimpleHttpConnection::connectSocket (struct addrinfo *aip) {
fd_set fdset;
struct timeval tv;
_isConnected = false;
_socket = socket(aip->ai_family, aip->ai_socktype, aip->ai_protocol);
if (_socket == -1) {
return false;
}
if (!setNonBlocking(_socket)) {
setErrorMessage("Socket not connected. Set non blocking failed with: " + string(strerror(errno)), errno);
::close(_socket);
_socket = -1;
return false;
}
if (!setCloseOnExec(_socket)) {
setErrorMessage("Socket not connected. Set close on exec failed with: " + string(strerror(errno)), errno);
::close(_socket);
_socket = -1;
return false;
}
::connect(_socket, (const struct sockaddr *) aip->ai_addr, aip->ai_addrlen);
FD_ZERO(&fdset);
FD_SET(_socket, &fdset);
tv.tv_sec = (uint64_t) _connectionTimeout;
tv.tv_usec = ((uint64_t) (_connectionTimeout * 1000000.0)) % 1000000;
if (select(_socket + 1, NULL, &fdset, NULL, &tv) == 1) {
int so_error;
socklen_t len = sizeof so_error;
getsockopt(_socket, SOL_SOCKET, SO_ERROR, &so_error, &len);
if (so_error == 0) {
// OK
_isConnected = true;
}
else {
setErrorMessage("Could not connect to server. (getsockopt() failed with: " + string(strerror(errno)) + ")", errno);
::close(_socket);
_socket = -1;
}
}
else {
setErrorMessage("Could not connect to server. (select() failed with: " + string(strerror(errno)) + ")", errno);
}
return _isConnected;
}
bool SimpleHttpConnection::setNonBlocking (socket_t fd) {
#ifdef TRI_HAVE_WIN32_NON_BLOCKING
DWORD ul = 1;
return ioctlsocket(fd, FIONBIO, &ul) == SOCKET_ERROR ? false : true;
#else
long flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) {
return false;
}
flags = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
if (flags < 0) {
return false;
}
return true;
#endif
}
bool SimpleHttpConnection::setCloseOnExec (socket_t fd) {
#ifdef TRI_HAVE_WIN32_CLOSE_ON_EXEC
#else
long flags = fcntl(fd, F_GETFD, 0);
if (flags < 0) {
return false;
}
flags = fcntl(fd, F_SETFD, flags | FD_CLOEXEC);
if (flags < 0) {
return false;
}
#endif
return true;
}
double SimpleHttpConnection::now () {
struct timeval tv;
gettimeofday(&tv, 0);
double sec = tv.tv_sec; // seconds
double usc = tv.tv_usec; // microseconds
return sec + usc / 1000000.0;
}
}
}