SimpleHttpResult* SimpleHttpClient::request (rest::HttpRequest::HttpRequestType method,
            const string& location,
            const char* body,
            size_t bodyLength,
            const map<string, string>& headerFields) {

      assert(_result == 0);

      _result = new SimpleHttpResult;
      _errorMessage = "";

      // set body 
      setRequest(method, rewriteLocation(location), body, bodyLength, headerFields);

      double endTime = now() + _requestTimeout;
      double remainingTime = _requestTimeout;

      while (isWorking() && remainingTime > 0.0) {
        switch (_state) {
          case (IN_CONNECT): {
            handleConnect();
            break;
          }

          case (IN_WRITE): {
            size_t bytesWritten = 0;

            TRI_set_errno(TRI_ERROR_NO_ERROR);
            if (! _connection->handleWrite(remainingTime, (void*) (_writeBuffer.c_str() + _written), _writeBuffer.length() - _written, &bytesWritten)) {
              setErrorMessage(TRI_last_error(), false);
              this->close();
            }
            else {
              _written += bytesWritten;
              if (_written == _writeBuffer.length())  {
                _state = IN_READ_HEADER;
              }
            }
            break;
          }

          case (IN_READ_HEADER):
          case (IN_READ_BODY):
          case (IN_READ_CHUNKED_HEADER):
          case (IN_READ_CHUNKED_BODY): {
            TRI_set_errno(TRI_ERROR_NO_ERROR);

            if (_connection->handleRead(remainingTime, _readBuffer)) {
              switch (_state) {
                case (IN_READ_HEADER):
                  readHeader();
                  break;
                case (IN_READ_BODY):
                  readBody();
                  break;
                case (IN_READ_CHUNKED_HEADER):
                  readChunkedHeader();
                  break;
                case (IN_READ_CHUNKED_BODY):
                  readChunkedBody();
                  break;
                default:
                  break;
              }
            }
            else {
              if (! _result->hasContentLength() && 
                  ! _connection->isConnected() && 
                  _state == IN_READ_BODY) {
                // no content-length header in response, now set the length
                _result->setContentLength(_readBuffer.length());
                readBody();
                break;
              }

              setErrorMessage(TRI_last_error(), false);
              this->close();
            }
            break;
          }
          default:
            break;
        }

        remainingTime = endTime - now();
      }

      if (isWorking() && _errorMessage == "" ) {
        setErrorMessage("Request timeout reached");
      }

      // set result type in getResult()
      SimpleHttpResult* result = getResult();

      _result = 0;

      return result;
    }
    SimpleHttpResult* SimpleHttpClient::request (
      rest::HttpRequest::HttpRequestType method,
      std::string const& location,
      char const* body,
      size_t bodyLength,
      std::map<std::string, std::string> const& headerFields) {
      
      // ensure connection has not yet been invalidated
      TRI_ASSERT(_connection != nullptr);

      // ensure that result is empty
      TRI_ASSERT(_result == nullptr);

      // create a new result
      _result = new SimpleHttpResult;

      // reset error message
      _errorMessage = "";

      // set body
      setRequest(method, rewriteLocation(location), body, bodyLength, headerFields);

      // ensure state
      TRI_ASSERT(_state == IN_CONNECT || _state == IN_WRITE);

      // respect timeout
      double endTime = TRI_microtime() + _requestTimeout;
      double remainingTime = _requestTimeout;

      while (_state < FINISHED && remainingTime > 0.0) {
        // Note that this loop can either be left by timeout or because
        // a connect did not work (which sets the _state to DEAD). In all
        // other error conditions we call close() which resets the state
        // to IN_CONNECT and tries a reconnect. This is important because
        // it is always possible that we are called with a connection that
        // has already been closed by the other side. This leads to the
        // strange effect that the write (if it is small enough) proceeds
        // but the following read runs into an error. In that case we try
        // to reconnect one and then give up if this does not work.
        switch (_state) {
          case (IN_CONNECT): {
            handleConnect();
            // If this goes wrong, _state is set to DEAD
            break;
          }

          case (IN_WRITE): {
            size_t bytesWritten = 0;

            TRI_set_errno(TRI_ERROR_NO_ERROR);

            bool res = _connection->handleWrite(
              remainingTime, 
              static_cast<void const*>(_writeBuffer.c_str() + _written),
              _writeBuffer.length() - _written,
              &bytesWritten);

            if (! res) {
              setErrorMessage("Error writing to '" +
                              _connection->getEndpoint()->getSpecification() +
                              "' '" +
                              _connection->getErrorDetails() +
                              "'");
              this->close(); // this sets _state to IN_CONNECT for a retry
            }
            else {
              _written += bytesWritten;

              if (_written == _writeBuffer.length())  {
                _state = IN_READ_HEADER;
              }
            }

            break;
          }

          case (IN_READ_HEADER):
          case (IN_READ_BODY):
          case (IN_READ_CHUNKED_HEADER):
          case (IN_READ_CHUNKED_BODY): {
            TRI_set_errno(TRI_ERROR_NO_ERROR);

            // we need to notice if the other side has closed the connection:
            bool connectionClosed;

            bool res = _connection->handleRead(remainingTime,
                                               _readBuffer,
                                               connectionClosed);


            // If there was an error, then we are doomed:
            if (! res) {
              setErrorMessage("Error reading from: '" +
                              _connection->getEndpoint()->getSpecification() +
                              "' '" +
                              _connection->getErrorDetails() +
                              "'");
              this->close(); // this sets the state to IN_CONNECT for a retry
              break;
            }

            if (connectionClosed) {
              // write might have succeeded even if the server has closed 
              // the connection, this will then show up here with us being
              // in state IN_READ_HEADER but nothing read.
              if (_state == IN_READ_HEADER && 0 == _readBuffer.length()) {
                this->close(); // sets _state to IN_CONNECT again for a retry
                continue;
              }

              else if (_state == IN_READ_BODY && ! _result->hasContentLength()) {
                // If we are reading the body and no content length was
                // found in the header, then we must read until no more
                // progress is made (but without an error), this then means
                // that the server has closed the connection and we must
                // process the body one more time:
                _result->setContentLength(_readBuffer.length() - _readBufferOffset);
                processBody();

                if (_state != FINISHED) {
                  // If the body was not fully found we give up:
                  this->close(); // this sets the state IN_CONNECT to retry
                }

                break;
              }

              else {
                // In all other cases of closed connection, we are doomed:
                this->close(); // this sets the state to IN_CONNECT retry
                break;
              }
            }

            // the connection is still alive:
            switch (_state) {
              case (IN_READ_HEADER):
                processHeader();
                break;

              case (IN_READ_BODY):
                processBody();
                break;

              case (IN_READ_CHUNKED_HEADER):
                processChunkedHeader();
                break;

              case (IN_READ_CHUNKED_BODY):
                processChunkedBody();
                break;

              default:
                break;
            }

            break;
          }

          default:
            break;
        }

        remainingTime = endTime - TRI_microtime();
      }

      if (_state < FINISHED && _errorMessage.empty()) {
        setErrorMessage("Request timeout reached");
      }

      // set result type in getResult()
      SimpleHttpResult* result = getResult();

      _result = nullptr;

      return result;
    }