Skip to content

Commit

Permalink
Improve client connection classes
Browse files Browse the repository at this point in the history
- Add HEAD to client methods.
- Add headers to send() method.
- Add headers to response struct.
- Improve getUri() to allow scheme configuration,
  but implementing a default behavior.
  Also, uri path is protected to allow starting with
  slash '/' or not.
- Allow secure client connections.
- Implement reconnect() method.
- Implement connection asString() method.
- Implement static headersAsString() method to
  be available on ert::http2comm::headersAsString().
  Use it from Http2Headers class (asString() method).
- Connect on send(). ToDo: manage timeout for waitToBeConnected().
  • Loading branch information
testillano committed May 25, 2022
1 parent 03101b9 commit f20a2c5
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 72 deletions.
15 changes: 8 additions & 7 deletions include/ert/http2comm/Http2Client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,14 @@ class Http2Client

enum class Method
{
GET, PUT, POST, DELETE
POST, GET, PUT, DELETE, HEAD
};

struct response
{
std::string body;
int status; //http result code
int statusCode;
nghttp2::asio_http2::header_map headers;
};

private:
Expand Down Expand Up @@ -118,16 +119,16 @@ class Http2Client

virtual void setHttp2Connection(std::shared_ptr<Http2Connection> connection);
virtual std::shared_ptr<Http2Connection> getHttp2Connection();
virtual Http2Client::response send(const Http2Client::Method&,
const std::string& uri_path,
const std::string& json);
virtual Http2Client::response send(const Http2Client::Method &method,
const std::string &uri,
const std::string &body,
const nghttp2::asio_http2::header_map &headers);
private:
std::shared_ptr<Http2Connection> connection_;
const std::chrono::milliseconds request_timeout_;
const std::string host_;
const std::string scheme_;

std::string getUri(const std::string& uri_path);
std::string getUri(const std::string &uri, const std::string &scheme = "" /* http or https by default, but could be forced here */);
};

}
Expand Down
42 changes: 34 additions & 8 deletions include/ert/http2comm/Http2Connection.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ class Http2Connection
CLOSED
};

// Seccion factory with io_service, host, port and secure inputs:
nghttp2::asio_http2::client::session createSession(boost::asio::io_service &ioService, const std::string &host, const std::string &port, bool secure);

// Set on_connect/on_error session callbacks
void configureSession();

public:
/// Class constructors

Expand All @@ -87,8 +93,9 @@ class Http2Connection
*
* \param host Endpoint host
* \param port Endpoint port
* \param secure Secure connection. False by default
*/
Http2Connection(const std::string& host, const std::string& port);
Http2Connection(const std::string& host, const std::string& port, bool secure);

/**
* Copy constructor
Expand Down Expand Up @@ -150,6 +157,13 @@ class Http2Connection
*/
const std::string& getPort() const;

/**
* Returns true for secured connection
*
* \return Boolean about secured connection
*/
bool isSecure() const;

/**
* Returns the connection status
*
Expand Down Expand Up @@ -184,6 +198,17 @@ class Http2Connection
*/
void onClose(connection_callback connection_closed_callback);

/**
* Reconnection procedure
*/
void reconnect();


/**
* Class string representation
*/
std::string asString() const;

private:
/// Internal methods

Expand All @@ -200,20 +225,21 @@ class Http2Connection
private:

/// ASIO attributes
std::unique_ptr<boost::asio::io_service>
io_service_; //non-copyable and non-movable
std::unique_ptr<boost::asio::io_service::work> work_;
nghttp2::asio_http2::client::session session_; //non-copyable
boost::asio::io_service io_service_;
boost::asio::io_service::work work_;
nghttp2::asio_http2::client::session *session_; // session is non-copyable, so we will use this pointer
// to allow recreating the session (reconnect feature).

/// Class attributes
Status status_;
const std::string host_;
const std::string port_;
std::string host_;
std::string port_;
bool secure_;
connection_callback connection_closed_callback_;

/// Concurrency attributes
std::mutex mutex_;
std::thread execution_;
std::thread thread_;
std::condition_variable status_change_cond_var_;


Expand Down
16 changes: 16 additions & 0 deletions include/ert/http2comm/Http2Headers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ namespace ert
namespace http2comm
{

/**
* Prints headers list for traces. For example:
* '[content-length: 200][content-type: application/json; charset=utf-8]'
*
* @param headers nghttp2 headers map
*
* @return sorted query parameters URI part
*/
std::string headersAsString(const nghttp2::asio_http2::header_map &headers);


class Http2Headers
{
nghttp2::asio_http2::header_map headers_{};
Expand Down Expand Up @@ -99,6 +110,11 @@ class Http2Headers
* Gets current built header map
*/
const nghttp2::asio_http2::header_map& getHeaders() const;

/**
* Class string representation
*/
std::string asString() const;
};

}
Expand Down
98 changes: 65 additions & 33 deletions src/Http2Client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,18 @@ SOFTWARE.

#include <ert/tracing/Logger.hpp>

#include <ert/http2comm/Http2Headers.hpp>
#include <ert/http2comm/Http2Client.hpp>
#include <ert/http2comm/Http2Connection.hpp>

namespace
{
std::map<ert::http2comm::Http2Client::Method, std::string> method_to_str = { {
ert::http2comm::Http2Client::Method::GET, "GET"
}, {
ert::http2comm::Http2Client::Method::PUT, "PUT"
}, {
ert::http2comm::Http2Client::Method::POST, "POST"
}, {
ert::http2comm::Http2Client::Method::DELETE, "DELETE"
}
std::map<ert::http2comm::Http2Client::Method, std::string> method_to_str = {
{ ert::http2comm::Http2Client::Method::POST, "POST" },
{ ert::http2comm::Http2Client::Method::GET, "GET" },
{ ert::http2comm::Http2Client::Method::PUT, "PUT" },
{ ert::http2comm::Http2Client::Method::DELETE, "DELETE" },
{ ert::http2comm::Http2Client::Method::HEAD, "HEAD" }
};
}

Expand All @@ -67,14 +65,13 @@ namespace http2comm
Http2Client::Http2Client(std::shared_ptr<Http2Connection>
connection,
const std::chrono::milliseconds& request_timeout) :
connection_(connection), request_timeout_(request_timeout), scheme_(
"http")
connection_(connection), request_timeout_(request_timeout)
{
}

Http2Client::Http2Client(const std::chrono::milliseconds&
request_timeout) :
request_timeout_(request_timeout), scheme_("http")
request_timeout_(request_timeout)
{
}

Expand All @@ -92,40 +89,55 @@ Http2Client::getHttp2Connection()
}

Http2Client::response Http2Client::send(
const Http2Client::Method&
method,
const std::string& uri_path, const std::string& json)
const Http2Client::Method &method,
const std::string &uri,
const std::string &body,
const nghttp2::asio_http2::header_map &headers)
{
// Internal Server Error response if connection not initialized
if (!connection_)
{
LOGINFORMATIONAL(ert::tracing::Logger::informational("There must be a connection instance !", ERT_FILE_LOCATION));
return Http2Client::response{"", 500};
}
if (connection_->getStatus() != Http2Connection::Status::OPEN)
{
LOGINFORMATIONAL(ert::tracing::Logger::informational(ert::tracing::Logger::asString("Connection must be OPEN to send request (%s) !", connection_->asString().c_str()), ERT_FILE_LOCATION));

connection_->reconnect();
connection_->waitToBeConnected();
if (connection_->getStatus() != Http2Connection::Status::OPEN) {
LOGWARNING(ert::tracing::Logger::warning(ert::tracing::Logger::asString("Unable to reconnect '%s'", connection_->asString().c_str()), ERT_FILE_LOCATION));
return Http2Client::response{"", 503};
}
}

auto url = getUri(uri_path);
auto url = getUri(uri);
auto method_str = ::method_to_str[method];
LOGINFORMATIONAL(ert::tracing::Logger::informational(
ert::tracing::Logger::asString("Sending %s request to: %s; Data: %s; server connection status: %d",
method_str.c_str(), url.c_str(), json.c_str(),
static_cast<int>(connection_->getStatus())), ERT_FILE_LOCATION));

LOGINFORMATIONAL(
ert::tracing::Logger::informational(ert::tracing::Logger::asString("Sending %s request to url: %s; body: %s; headers: %s; %s",
method_str.c_str(), url.c_str(), body.c_str(), headersAsString(headers).c_str(), connection_->asString().c_str()), ERT_FILE_LOCATION);
);

auto submit = [&, url](const nghttp2::asio_http2::client::session & sess,
const nghttp2::asio_http2::header_map & headers, boost::system::error_code & ec)
{
return sess.submit(ec, method_str, url, json, headers);
return sess.submit(ec, method_str, url, body, headers);
};

auto& session = connection_->getSession();
auto task = std::make_shared<Http2Client::task>();

session.io_service().post([&, task]
session.io_service().post([&, task, headers]
{
boost::system::error_code ec;

//configure headers
nghttp2::asio_http2::header_value ctValue = {"application/json", 0};
nghttp2::asio_http2::header_value clValue = {std::to_string(json.length()), 0};
nghttp2::asio_http2::header_map headers = { {"content-type", ctValue}, {"content-length", clValue} };
// // example to add headers:
// nghttp2::asio_http2::header_value ctValue = {"application/json", 0};
// nghttp2::asio_http2::header_value clValue = {std::to_string(body.length()), 0};
// headers.emplace("content-type", ctValue);
// headers.emplace("content-length", clValue);

//perform submit
auto req = submit(session, headers, ec);
Expand All @@ -137,15 +149,15 @@ Http2Client::response Http2Client::send(
{
if (len > 0)
{
std::string json (reinterpret_cast<const char*>(data), len);
task->data += json;
std::string body (reinterpret_cast<const char*>(data), len);
task->data += body;
}
else
{
//setting the value on 'response' (promise) will unlock 'done' (future)
task->response.set_value(Http2Client::response {task->data, res.status_code()});
task->response.set_value(Http2Client::response {task->data, res.status_code(), res.header()});
LOGDEBUG(ert::tracing::Logger::debug(ert::tracing::Logger::asString(
"Request has been answered with %d; Data: %s", res.status_code(), task->data.c_str()), ERT_FILE_LOCATION));
"Request has been answered with status code: %d; data: %s; headers: %s", res.status_code(), task->data.c_str(), headersAsString(res.header()).c_str()), ERT_FILE_LOCATION));
}
});
});
Expand All @@ -170,10 +182,30 @@ Http2Client::response Http2Client::send(

}

std::string Http2Client::getUri(const std::string& uri_path)
std::string Http2Client::getUri(const std::string& uri, const std::string &scheme)
{
return scheme_ + "://" + connection_->getHost() + ":"
+ connection_->getPort() + "/" + uri_path;
std::string result{};

if (scheme.empty()) {
result = "http";
if (connection_->isSecure()) {
result += "s";
}
}
else {
result = scheme;
}

result += "://" + connection_->getHost() + ":" + connection_->getPort();
if (uri.empty()) return result;

if (uri[0] != '/') {
result += "/";
}

result += uri;

return result;
}

}
Expand Down
Loading

0 comments on commit f20a2c5

Please sign in to comment.