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; }