mirror of https://gitee.com/bigwinds/arangodb
414 lines
13 KiB
C++
414 lines
13 KiB
C++
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief simple http client
|
|
///
|
|
/// @file
|
|
///
|
|
/// DISCLAIMER
|
|
///
|
|
/// Copyright by triAGENS GmbH - All rights reserved.
|
|
///
|
|
/// The Programs (which include both the software and documentation)
|
|
/// contain proprietary information of triAGENS GmbH; they are
|
|
/// provided under a license agreement containing restrictions on use and
|
|
/// disclosure and are also protected by copyright, patent and other
|
|
/// intellectual and industrial property laws. Reverse engineering,
|
|
/// disassembly or decompilation of the Programs, except to the extent
|
|
/// required to obtain interoperability with other independently created
|
|
/// software or as specified by law, is prohibited.
|
|
///
|
|
/// The Programs are not intended for use in any nuclear, aviation, mass
|
|
/// transit, medical, or other inherently dangerous applications. It shall
|
|
/// be the licensee's responsibility to take all appropriate fail-safe,
|
|
/// backup, redundancy, and other measures to ensure the safe use of such
|
|
/// applications if the Programs are used for such purposes, and triAGENS
|
|
/// GmbH disclaims liability for any damages caused by such use of
|
|
/// the Programs.
|
|
///
|
|
/// This software is the confidential and proprietary information of
|
|
/// triAGENS GmbH. You shall not disclose such confidential and
|
|
/// proprietary information and shall use it only in accordance with the
|
|
/// terms of the license agreement you entered into with triAGENS GmbH.
|
|
///
|
|
/// Copyright holder is triAGENS GmbH, Cologne, Germany
|
|
///
|
|
/// @author Dr. Frank Celler
|
|
/// @author Achim Brandt
|
|
/// @author Copyright 2008-2011, triagens GmbH, Cologne, Germany
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include "SimpleHttpClient.h"
|
|
#include "SimpleHttpResult.h"
|
|
#include "SimpleHttpConnection.h"
|
|
|
|
#include "Basics/StringUtils.h"
|
|
#include "Logger/Logger.h"
|
|
|
|
using namespace triagens::basics;
|
|
using namespace std;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
/// constructor and destructor
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace triagens {
|
|
namespace httpclient {
|
|
|
|
SimpleHttpClient::SimpleHttpClient (
|
|
const std::string& hostname,
|
|
int port,
|
|
double requestTimeout,
|
|
size_t retries,
|
|
double connectionTimeout) :
|
|
_requestTimeout(requestTimeout),
|
|
_retries(retries) {
|
|
|
|
_connection = new SimpleHttpConnection(hostname, port, connectionTimeout);
|
|
|
|
}
|
|
|
|
SimpleHttpClient::~SimpleHttpClient () {
|
|
if (_connection) {
|
|
delete _connection;
|
|
}
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// public methods
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
SimpleHttpResult* SimpleHttpClient::request (
|
|
int method,
|
|
const std::string& location,
|
|
const char* body,
|
|
size_t bodyLength,
|
|
const map<string, string>& headerFields) {
|
|
|
|
SimpleHttpResult* result = new SimpleHttpResult();
|
|
SimpleHttpResult::resultTypes type = SimpleHttpResult::UNKNOWN;
|
|
|
|
size_t retries = 0;
|
|
|
|
// build request
|
|
stringstream requestBuffer;
|
|
fillRequestBuffer(requestBuffer, method, location, body, bodyLength, headerFields);
|
|
LOGGER_TRACE << "Request: " << requestBuffer.str();
|
|
|
|
double start = now();
|
|
double runtime = 0.0;
|
|
|
|
// TODO requestTimeout
|
|
while (++retries < _retries && runtime < _requestTimeout) {
|
|
|
|
// check connection
|
|
if (!checkConnection()) {
|
|
type = SimpleHttpResult::COULD_NOT_CONNECT;
|
|
// LOGGER_WARNING << "could not connect to '" << _connection->getHostname() << ":" << _connection->getPort() << "'";
|
|
}
|
|
|
|
// write
|
|
else if (!write(requestBuffer, _requestTimeout - runtime)) {
|
|
closeConnection();
|
|
type = SimpleHttpResult::WRITE_ERROR;
|
|
// LOGGER_WARNING << "write error";
|
|
}
|
|
|
|
// read
|
|
else if (!read(result, _requestTimeout - runtime)) {
|
|
closeConnection();
|
|
type = SimpleHttpResult::READ_ERROR;
|
|
// LOGGER_WARNING << "read error";
|
|
}
|
|
else {
|
|
type = SimpleHttpResult::COMPLETE;
|
|
break;
|
|
}
|
|
|
|
runtime = now() - start;
|
|
}
|
|
|
|
result->setResultType(type);
|
|
|
|
return result;
|
|
}
|
|
|
|
const string& SimpleHttpClient::getErrorMessage () {
|
|
static const string nc = "not connected";
|
|
|
|
if (!_connection) {
|
|
return nc;
|
|
}
|
|
|
|
return _connection->getErrorMessage();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the hostname
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const std::string& SimpleHttpClient::getHostname () {
|
|
return _connection->getHostname();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief get the port
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
int SimpleHttpClient::getPort () {
|
|
return _connection->getPort();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief returns true if the client is connected
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SimpleHttpClient::isConnected () {
|
|
if (!_connection) {
|
|
return false;
|
|
}
|
|
return _connection->isConnected();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// @brief sets username and password
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
void SimpleHttpClient::setUserNamePassword (
|
|
const string& prefix,
|
|
const string& username,
|
|
const string& password) {
|
|
|
|
string value = triagens::basics::StringUtils::encodeBase64(username + ":" + password);
|
|
|
|
_pathToBasicAuth.push_back(make_pair(prefix, value));
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
/// private methods
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
bool SimpleHttpClient::read (SimpleHttpResult* httpResult, double timeout) {
|
|
httpResult->clear();
|
|
|
|
double start = now();
|
|
|
|
// read the http header
|
|
if (!readHeader(httpResult, timeout)) {
|
|
return false;
|
|
}
|
|
|
|
double runtime = now() - start;
|
|
|
|
if (runtime > timeout) {
|
|
LOGGER_WARNING << "read timeout";
|
|
return false;
|
|
}
|
|
|
|
// read the body
|
|
return readBody(httpResult, timeout - runtime);
|
|
}
|
|
|
|
bool SimpleHttpClient::readHeader (SimpleHttpResult* httpResult, double timeout) {
|
|
string line;
|
|
|
|
// TODO: timeout
|
|
while (_connection->readLn(line, timeout)) {
|
|
LOGGER_TRACE << "read header line: " << line;
|
|
|
|
if (line == "\r\n" || line == "\n") {
|
|
// end of header found
|
|
break;
|
|
}
|
|
|
|
httpResult->addHeaderField(line);
|
|
}
|
|
|
|
return httpResult->getHttpReturnCode() > 0;
|
|
}
|
|
|
|
|
|
bool SimpleHttpClient::readBody (SimpleHttpResult* result, double timeout) {
|
|
if (result->isChunked()) {
|
|
return readBodyChunked(result, timeout);
|
|
}
|
|
else {
|
|
return readBodyContentLength(result, timeout);
|
|
}
|
|
}
|
|
|
|
bool SimpleHttpClient::readBodyContentLength (SimpleHttpResult* result, double timeout) {
|
|
int contentLength = result->getContenLength();
|
|
|
|
if (contentLength <= 0) {
|
|
// nothing to read
|
|
//_logger->TraceLogger("Result has no content.");
|
|
return true;
|
|
}
|
|
|
|
int len_read = _connection->read(result->getBody(), contentLength, timeout);
|
|
LOGGER_TRACE << "body: " << result->getBody().str();
|
|
|
|
if (len_read == contentLength) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool SimpleHttpClient::readBodyChunked (SimpleHttpResult* result, double timeout) {
|
|
string line;
|
|
|
|
double start = now();
|
|
double runtime = 0.0;
|
|
|
|
while (runtime < timeout && _connection->readLn(line, timeout)) {
|
|
|
|
if (line == "\r\n" || line == "\n") {
|
|
// ignore empty line
|
|
continue;
|
|
}
|
|
|
|
string trimed = StringUtils::trim(line);
|
|
|
|
uint32_t contentLength;
|
|
sscanf(trimed.c_str(), "%x", &contentLength);
|
|
|
|
if (contentLength == 0) {
|
|
// OK: last content length found
|
|
LOGGER_TRACE << "body chunked: " << result->getBody().str();
|
|
return true;
|
|
}
|
|
|
|
if (contentLength > 1000000) {
|
|
// failed: too many bytes
|
|
LOGGER_WARNING << "read body chunked faild: too many bytes.";
|
|
return false;
|
|
}
|
|
|
|
runtime = now() - start;
|
|
|
|
if (runtime > timeout) {
|
|
// failed: timeout
|
|
LOGGER_WARNING << "read body chunked faild: timeout.";
|
|
return false;
|
|
}
|
|
|
|
size_t len_read = _connection->read(result->getBody(), contentLength, timeout - runtime);
|
|
|
|
if (len_read < contentLength) {
|
|
// failed: could not read all data
|
|
LOGGER_WARNING << "read body chunked faild: not enough bytes.";
|
|
return false;
|
|
}
|
|
|
|
runtime = now() - start;
|
|
}
|
|
|
|
LOGGER_WARNING << "readln faild.";
|
|
return false;
|
|
}
|
|
|
|
bool SimpleHttpClient::write (stringstream &buffer, double timeout) {
|
|
return _connection->write(buffer.str(), timeout);
|
|
}
|
|
|
|
void SimpleHttpClient::fillRequestBuffer (stringstream &requestBuffer,
|
|
int method,
|
|
const std::string& location,
|
|
const char* body,
|
|
size_t bodyLength,
|
|
const map<string, string>& headerFields) {
|
|
|
|
switch (method) {
|
|
case GET:
|
|
requestBuffer << "GET ";
|
|
break;
|
|
case POST:
|
|
requestBuffer << "POST ";
|
|
break;
|
|
case PUT:
|
|
requestBuffer << "PUT ";
|
|
break;
|
|
case DELETE:
|
|
requestBuffer << "DELETE ";
|
|
break;
|
|
case HEAD:
|
|
requestBuffer << "HEAD ";
|
|
break;
|
|
default:
|
|
requestBuffer << "POST ";
|
|
break;
|
|
}
|
|
|
|
string l = location;
|
|
if (location.length() == 0 || location[0] != '/') {
|
|
l = "/" + location;
|
|
}
|
|
|
|
requestBuffer << l << " HTTP/1.1\r\n";
|
|
|
|
requestBuffer << "Host: ";
|
|
if (_connection->getHostname().find(':') != string::npos) {
|
|
requestBuffer << "[" << _connection->getHostname() << "]";
|
|
}
|
|
else {
|
|
requestBuffer << _connection->getHostname();
|
|
}
|
|
requestBuffer << ':' << _connection->getPort() << "\r\n";
|
|
requestBuffer << "Connection: Keep-Alive\r\n";
|
|
requestBuffer << "User-Agent: VOC-Client/1.0\r\n";
|
|
requestBuffer << "Accept: application/json\r\n";
|
|
|
|
if (bodyLength > 0) {
|
|
requestBuffer << "Content-Type: application/json; charset=utf-8\r\n";
|
|
}
|
|
|
|
// do basic authorization
|
|
if (_pathToBasicAuth.size() > 0) {
|
|
string foundPrefix;
|
|
string foundValue;
|
|
std::vector< std::pair<std::string, std::string> >::iterator i = _pathToBasicAuth.begin();
|
|
for (; i != _pathToBasicAuth.end(); ++i) {
|
|
string f = i->first;
|
|
if (l.find(f) == 0) {
|
|
// f is prefix of l
|
|
if ( f.length() > foundPrefix.length() ) {
|
|
foundPrefix = f;
|
|
foundValue = i->second;
|
|
}
|
|
}
|
|
}
|
|
if (foundValue.length() > 0) {
|
|
requestBuffer << "Authorization: Basic " << foundValue << "\r\n";
|
|
}
|
|
}
|
|
|
|
for (map<string, string>::const_iterator i = headerFields.begin(); i != headerFields.end(); ++i) {
|
|
requestBuffer << i->first << ": " << i->second << "\r\n";
|
|
}
|
|
|
|
requestBuffer << "Content-Length: " << bodyLength << "\r\n\r\n";
|
|
requestBuffer.write(body, bodyLength);
|
|
}
|
|
|
|
bool SimpleHttpClient::checkConnection () {
|
|
return _connection->connect();
|
|
}
|
|
|
|
void SimpleHttpClient::closeConnection () {
|
|
_connection->close();
|
|
}
|
|
|
|
double SimpleHttpClient::now () {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, 0);
|
|
|
|
double sec = tv.tv_sec; // seconds
|
|
double usc = tv.tv_usec; // microseconds
|
|
|
|
return sec + usc / 1000000.0;
|
|
}
|
|
|
|
|
|
}
|
|
}
|