mirror of
https://gitlab.isc.org/isc-projects/kea
synced 2025-09-01 06:25:34 +00:00
[#3490] Moved the HTTPS variant
This commit is contained in:
committed by
Razvan Becheriu
parent
d84761ca87
commit
9fc12fbe0f
@@ -19,6 +19,7 @@
|
|||||||
#include <http/response_creator_factory.h>
|
#include <http/response_creator_factory.h>
|
||||||
#include <http/response_json.h>
|
#include <http/response_json.h>
|
||||||
#include <http/tests/response_test.h>
|
#include <http/tests/response_test.h>
|
||||||
|
#include <http/testutils/test_http_client.h>
|
||||||
#include <http/url.h>
|
#include <http/url.h>
|
||||||
#include <util/multi_threading_mgr.h>
|
#include <util/multi_threading_mgr.h>
|
||||||
|
|
||||||
@@ -386,247 +387,6 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/// @brief Entity which can connect to the HTTP server endpoint.
|
|
||||||
class TestHttpsClient : public boost::noncopyable {
|
|
||||||
public:
|
|
||||||
|
|
||||||
/// @brief Constructor.
|
|
||||||
///
|
|
||||||
/// This constructor creates new socket instance. It doesn't connect. Call
|
|
||||||
/// connect() to connect to the server.
|
|
||||||
///
|
|
||||||
/// @param io_service IO service to be stopped on error.
|
|
||||||
/// @param tls_context TLS context.
|
|
||||||
TestHttpsClient(const IOServicePtr& io_service, TlsContextPtr tls_context)
|
|
||||||
: io_service_(io_service), stream_(io_service_->getInternalIOService(),
|
|
||||||
tls_context->getContext()), buf_(), response_() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Destructor.
|
|
||||||
///
|
|
||||||
/// Closes the underlying socket if it is open.
|
|
||||||
~TestHttpsClient() {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Send HTTP request specified in textual format.
|
|
||||||
///
|
|
||||||
/// @param request HTTP request in the textual format.
|
|
||||||
void startRequest(const std::string& request) {
|
|
||||||
tcp::endpoint endpoint(address::from_string(SERVER_ADDRESS),
|
|
||||||
SERVER_PORT);
|
|
||||||
stream_.lowest_layer().async_connect(endpoint,
|
|
||||||
[this, request](const boost::system::error_code& ec) {
|
|
||||||
if (ec) {
|
|
||||||
// One would expect that async_connect wouldn't return
|
|
||||||
// EINPROGRESS error code, but simply wait for the connection
|
|
||||||
// to get established before the handler is invoked. It turns out,
|
|
||||||
// however, that on some OSes the connect handler may receive this
|
|
||||||
// error code which doesn't necessarily indicate a problem.
|
|
||||||
// Making an attempt to write and read from this socket will
|
|
||||||
// typically succeed. So, we ignore this error.
|
|
||||||
if (ec.value() != boost::asio::error::in_progress) {
|
|
||||||
ADD_FAILURE() << "error occurred while connecting: "
|
|
||||||
<< ec.message();
|
|
||||||
io_service_->stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream_.async_handshake(roleToImpl(TlsRole::CLIENT),
|
|
||||||
[this, request](const boost::system::error_code& ec) {
|
|
||||||
if (ec) {
|
|
||||||
ADD_FAILURE() << "error occurred during handshake: "
|
|
||||||
<< ec.message();
|
|
||||||
io_service_->stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sendRequest(request);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Send HTTP request.
|
|
||||||
///
|
|
||||||
/// @param request HTTP request in the textual format.
|
|
||||||
void sendRequest(const std::string& request) {
|
|
||||||
sendPartialRequest(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Send part of the HTTP request.
|
|
||||||
///
|
|
||||||
/// @param request part of the HTTP request to be sent.
|
|
||||||
void sendPartialRequest(std::string request) {
|
|
||||||
boost::asio::async_write(stream_,
|
|
||||||
boost::asio::buffer(request.data(), request.size()),
|
|
||||||
[this, request](const boost::system::error_code& ec,
|
|
||||||
std::size_t bytes_transferred) mutable {
|
|
||||||
if (ec) {
|
|
||||||
if (ec.value() == boost::asio::error::operation_aborted) {
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else if ((ec.value() == boost::asio::error::try_again) ||
|
|
||||||
(ec.value() == boost::asio::error::would_block)) {
|
|
||||||
// If we should try again make sure there is no garbage in the
|
|
||||||
// bytes_transferred.
|
|
||||||
bytes_transferred = 0;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
ADD_FAILURE() << "error occurred while connecting: "
|
|
||||||
<< ec.message();
|
|
||||||
io_service_->stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the part of the request which has been sent.
|
|
||||||
if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
|
|
||||||
request.erase(0, bytes_transferred);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Continue sending request data if there are still some data to be
|
|
||||||
// sent.
|
|
||||||
if (!request.empty()) {
|
|
||||||
sendPartialRequest(request);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Request has been sent. Start receiving response.
|
|
||||||
response_.clear();
|
|
||||||
receivePartialResponse();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Receive response from the server.
|
|
||||||
void receivePartialResponse() {
|
|
||||||
stream_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
|
|
||||||
[this](const boost::system::error_code& ec,
|
|
||||||
std::size_t bytes_transferred) {
|
|
||||||
if (ec) {
|
|
||||||
// IO service stopped so simply return.
|
|
||||||
if (ec.value() == boost::asio::error::operation_aborted) {
|
|
||||||
return;
|
|
||||||
|
|
||||||
} else if ((ec.value() == boost::asio::error::try_again) ||
|
|
||||||
(ec.value() == boost::asio::error::would_block)) {
|
|
||||||
// If we should try again, make sure that there is no garbage
|
|
||||||
// in the bytes_transferred.
|
|
||||||
bytes_transferred = 0;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Error occurred, bail...
|
|
||||||
ADD_FAILURE() << "error occurred while receiving HTTP"
|
|
||||||
" response from the server: " << ec.message();
|
|
||||||
io_service_->stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bytes_transferred > 0) {
|
|
||||||
response_.insert(response_.end(), buf_.data(),
|
|
||||||
buf_.data() + bytes_transferred);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Two consecutive new lines end the part of the response we're
|
|
||||||
// expecting.
|
|
||||||
if (response_.find("\r\n\r\n", 0) != std::string::npos) {
|
|
||||||
io_service_->stop();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
receivePartialResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Checks if the TCP connection is still open.
|
|
||||||
///
|
|
||||||
/// Tests the TCP connection by trying to read from the socket.
|
|
||||||
/// Unfortunately expected failure depends on a race between the read
|
|
||||||
/// and the other side close so to check if the connection is closed
|
|
||||||
/// please use @c isConnectionClosed instead.
|
|
||||||
///
|
|
||||||
/// @return true if the TCP connection is open.
|
|
||||||
bool isConnectionAlive() {
|
|
||||||
// Remember the current non blocking setting.
|
|
||||||
const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
|
|
||||||
// Set the socket to non blocking mode. We're going to test if the socket
|
|
||||||
// returns would_block status on the attempt to read from it.
|
|
||||||
stream_.lowest_layer().non_blocking(true);
|
|
||||||
|
|
||||||
// We need to provide a buffer for a call to read.
|
|
||||||
char data[2];
|
|
||||||
boost::system::error_code ec;
|
|
||||||
boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
|
|
||||||
|
|
||||||
// Revert the original non_blocking flag on the socket.
|
|
||||||
stream_.lowest_layer().non_blocking(non_blocking_orig);
|
|
||||||
|
|
||||||
// If the connection is alive we'd typically get would_block status code.
|
|
||||||
// If there are any data that haven't been read we may also get success
|
|
||||||
// status. We're guessing that try_again may also be returned by some
|
|
||||||
// implementations in some situations. Any other error code indicates a
|
|
||||||
// problem with the connection so we assume that the connection has been
|
|
||||||
// closed.
|
|
||||||
return (!ec || (ec.value() == boost::asio::error::try_again) ||
|
|
||||||
(ec.value() == boost::asio::error::would_block));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Checks if the TCP connection is already closed.
|
|
||||||
///
|
|
||||||
/// Tests the TCP connection by trying to read from the socket.
|
|
||||||
/// The read can block so this must be used to check if a connection
|
|
||||||
/// is alive so to check if the connection is alive please always
|
|
||||||
/// use @c isConnectionAlive.
|
|
||||||
///
|
|
||||||
/// @return true if the TCP connection is closed.
|
|
||||||
bool isConnectionClosed() {
|
|
||||||
// Remember the current non blocking setting.
|
|
||||||
const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
|
|
||||||
// Set the socket to blocking mode. We're going to test if the socket
|
|
||||||
// returns eof status on the attempt to read from it.
|
|
||||||
stream_.lowest_layer().non_blocking(false);
|
|
||||||
|
|
||||||
// We need to provide a buffer for a call to read.
|
|
||||||
char data[2];
|
|
||||||
boost::system::error_code ec;
|
|
||||||
boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
|
|
||||||
|
|
||||||
// Revert the original non_blocking flag on the socket.
|
|
||||||
stream_.lowest_layer().non_blocking(non_blocking_orig);
|
|
||||||
|
|
||||||
// If the connection is closed we'd typically get eof or
|
|
||||||
// stream_truncated status code.
|
|
||||||
return ((ec.value() == boost::asio::error::eof) ||
|
|
||||||
(ec.value() == STREAM_TRUNCATED));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @brief Close connection.
|
|
||||||
void close() {
|
|
||||||
stream_.lowest_layer().close();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string getResponse() const {
|
|
||||||
return (response_);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
/// @brief Holds pointer to the IO service.
|
|
||||||
isc::asiolink::IOServicePtr io_service_;
|
|
||||||
|
|
||||||
/// @brief A socket used for the connection.
|
|
||||||
TlsStreamImpl stream_;
|
|
||||||
|
|
||||||
/// @brief Buffer into which response is written.
|
|
||||||
std::array<char, 8192> buf_;
|
|
||||||
|
|
||||||
/// @brief Response in the textual format.
|
|
||||||
std::string response_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// @brief Pointer to the TestHttpsClient.
|
|
||||||
typedef boost::shared_ptr<TestHttpsClient> TestHttpsClientPtr;
|
|
||||||
|
|
||||||
/// @brief Test fixture class for @ref HttpListener.
|
/// @brief Test fixture class for @ref HttpListener.
|
||||||
class HttpsListenerTest : public ::testing::Test {
|
class HttpsListenerTest : public ::testing::Test {
|
||||||
public:
|
public:
|
||||||
|
@@ -19,8 +19,68 @@
|
|||||||
using namespace boost::asio::ip;
|
using namespace boost::asio::ip;
|
||||||
using namespace isc::asiolink;
|
using namespace isc::asiolink;
|
||||||
|
|
||||||
|
/// @brief Common base for test HTTP/HTTPS clients.
|
||||||
|
class BaseTestHttpClient : public boost::noncopyable {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// @brief Destructor.
|
||||||
|
virtual ~BaseTestHttpClient() = default;
|
||||||
|
|
||||||
|
/// @brief Send HTTP request specified in textual format.
|
||||||
|
///
|
||||||
|
/// @param request HTTP request in the textual format.
|
||||||
|
virtual void startRequest(const std::string& request) = 0;
|
||||||
|
|
||||||
|
/// @brief Send HTTP request.
|
||||||
|
///
|
||||||
|
/// @param request HTTP request in the textual format.
|
||||||
|
virtual void sendRequest(const std::string& request) = 0;
|
||||||
|
|
||||||
|
/// @brief Send part of the HTTP request.
|
||||||
|
///
|
||||||
|
/// @param request part of the HTTP request to be sent.
|
||||||
|
virtual void sendPartialRequest(std::string request) = 0;
|
||||||
|
|
||||||
|
/// @brief Receive response from the server.
|
||||||
|
virtual void receivePartialResponse() = 0;
|
||||||
|
|
||||||
|
/// @brief Checks if the TCP connection is still open.
|
||||||
|
///
|
||||||
|
/// Tests the TCP connection by trying to read from the socket.
|
||||||
|
/// Unfortunately expected failure depends on a race between the read
|
||||||
|
/// and the other side close so to check if the connection is closed
|
||||||
|
/// please use @c isConnectionClosed instead.
|
||||||
|
///
|
||||||
|
/// @return true if the TCP connection is open.
|
||||||
|
virtual bool isConnectionAlive() = 0;
|
||||||
|
|
||||||
|
/// @brief Checks if the TCP connection is already closed.
|
||||||
|
///
|
||||||
|
/// Tests the TCP connection by trying to read from the socket.
|
||||||
|
/// The read can block so this must be used to check if a connection
|
||||||
|
/// is alive so to check if the connection is alive please always
|
||||||
|
/// use @c isConnectionAlive.
|
||||||
|
///
|
||||||
|
/// @return true if the TCP connection is closed.
|
||||||
|
virtual bool isConnectionClosed() = 0;
|
||||||
|
|
||||||
|
/// @brief Close connection.
|
||||||
|
virtual void close() = 0;
|
||||||
|
|
||||||
|
/// @brief Returns the HTTP response string.
|
||||||
|
///
|
||||||
|
/// @return string containing the response.
|
||||||
|
virtual std::string getResponse() const = 0;
|
||||||
|
|
||||||
|
/// @brief Returns true if the receive completed without error.
|
||||||
|
///
|
||||||
|
/// @return True if the receive completed successfully, false
|
||||||
|
/// otherwise.
|
||||||
|
virtual bool receiveDone() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
/// @brief Entity which can connect to the HTTP server endpoint.
|
/// @brief Entity which can connect to the HTTP server endpoint.
|
||||||
class TestHttpClient : public boost::noncopyable {
|
class TestHttpClient : public BaseTestHttpClient {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/// @brief Constructor.
|
/// @brief Constructor.
|
||||||
@@ -42,14 +102,14 @@ public:
|
|||||||
/// @brief Destructor.
|
/// @brief Destructor.
|
||||||
///
|
///
|
||||||
/// Closes the underlying socket if it is open.
|
/// Closes the underlying socket if it is open.
|
||||||
~TestHttpClient() {
|
virtual ~TestHttpClient() {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Send HTTP request specified in textual format.
|
/// @brief Send HTTP request specified in textual format.
|
||||||
///
|
///
|
||||||
/// @param request HTTP request in the textual format.
|
/// @param request HTTP request in the textual format.
|
||||||
void startRequest(const std::string& request) {
|
virtual void startRequest(const std::string& request) {
|
||||||
tcp::endpoint endpoint(address::from_string(server_address_), server_port_);
|
tcp::endpoint endpoint(address::from_string(server_address_), server_port_);
|
||||||
socket_.async_connect(endpoint,
|
socket_.async_connect(endpoint,
|
||||||
[this, request](const boost::system::error_code& ec) {
|
[this, request](const boost::system::error_code& ec) {
|
||||||
@@ -76,14 +136,14 @@ public:
|
|||||||
/// @brief Send HTTP request.
|
/// @brief Send HTTP request.
|
||||||
///
|
///
|
||||||
/// @param request HTTP request in the textual format.
|
/// @param request HTTP request in the textual format.
|
||||||
void sendRequest(const std::string& request) {
|
virtual void sendRequest(const std::string& request) {
|
||||||
sendPartialRequest(request);
|
sendPartialRequest(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Send part of the HTTP request.
|
/// @brief Send part of the HTTP request.
|
||||||
///
|
///
|
||||||
/// @param request part of the HTTP request to be sent.
|
/// @param request part of the HTTP request to be sent.
|
||||||
void sendPartialRequest(std::string request) {
|
virtual void sendPartialRequest(std::string request) {
|
||||||
socket_.async_send(boost::asio::buffer(request.data(), request.size()),
|
socket_.async_send(boost::asio::buffer(request.data(), request.size()),
|
||||||
[this, request](const boost::system::error_code& ec,
|
[this, request](const boost::system::error_code& ec,
|
||||||
std::size_t bytes_transferred) mutable {
|
std::size_t bytes_transferred) mutable {
|
||||||
@@ -124,7 +184,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Receive response from the server.
|
/// @brief Receive response from the server.
|
||||||
void receivePartialResponse() {
|
virtual void receivePartialResponse() {
|
||||||
socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
|
socket_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
|
||||||
[this](const boost::system::error_code& ec,
|
[this](const boost::system::error_code& ec,
|
||||||
std::size_t bytes_transferred) {
|
std::size_t bytes_transferred) {
|
||||||
@@ -171,7 +231,7 @@ public:
|
|||||||
/// please use @c isConnectionClosed instead.
|
/// please use @c isConnectionClosed instead.
|
||||||
///
|
///
|
||||||
/// @return true if the TCP connection is open.
|
/// @return true if the TCP connection is open.
|
||||||
bool isConnectionAlive() {
|
virtual bool isConnectionAlive() {
|
||||||
// Remember the current non blocking setting.
|
// Remember the current non blocking setting.
|
||||||
const bool non_blocking_orig = socket_.non_blocking();
|
const bool non_blocking_orig = socket_.non_blocking();
|
||||||
// Set the socket to non blocking mode. We're going to test if the socket
|
// Set the socket to non blocking mode. We're going to test if the socket
|
||||||
@@ -204,7 +264,7 @@ public:
|
|||||||
/// use @c isConnectionAlive.
|
/// use @c isConnectionAlive.
|
||||||
///
|
///
|
||||||
/// @return true if the TCP connection is closed.
|
/// @return true if the TCP connection is closed.
|
||||||
bool isConnectionClosed() {
|
virtual bool isConnectionClosed() {
|
||||||
// Remember the current non blocking setting.
|
// Remember the current non blocking setting.
|
||||||
const bool non_blocking_orig = socket_.non_blocking();
|
const bool non_blocking_orig = socket_.non_blocking();
|
||||||
// Set the socket to blocking mode. We're going to test if the socket
|
// Set the socket to blocking mode. We're going to test if the socket
|
||||||
@@ -224,14 +284,14 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Close connection.
|
/// @brief Close connection.
|
||||||
void close() {
|
virtual void close() {
|
||||||
socket_.close();
|
socket_.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// @brief Returns the HTTP response string.
|
/// @brief Returns the HTTP response string.
|
||||||
///
|
///
|
||||||
/// @return string containing the response.
|
/// @return string containing the response.
|
||||||
std::string getResponse() const {
|
virtual std::string getResponse() const {
|
||||||
return (response_);
|
return (response_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,7 +299,7 @@ public:
|
|||||||
///
|
///
|
||||||
/// @return True if the receive completed successfully, false
|
/// @return True if the receive completed successfully, false
|
||||||
/// otherwise.
|
/// otherwise.
|
||||||
bool receiveDone() {
|
virtual bool receiveDone() const {
|
||||||
return (receive_done_);
|
return (receive_done_);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -270,4 +330,268 @@ private:
|
|||||||
/// @brief Pointer to the TestHttpClient.
|
/// @brief Pointer to the TestHttpClient.
|
||||||
typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
|
typedef boost::shared_ptr<TestHttpClient> TestHttpClientPtr;
|
||||||
|
|
||||||
|
/// @brief Entity which can connect to the HTTPS server endpoint.
|
||||||
|
class TestHttpsClient : public boost::noncopyable {
|
||||||
|
public:
|
||||||
|
|
||||||
|
/// @brief Constructor.
|
||||||
|
///
|
||||||
|
/// This constructor creates new socket instance. It doesn't connect. Call
|
||||||
|
/// connect() to connect to the server.
|
||||||
|
///
|
||||||
|
/// @param io_service IO service to be stopped on error.
|
||||||
|
/// @param tls_context TLS context.
|
||||||
|
/// @param server_address string containing the IP address of the server.
|
||||||
|
/// @param port port number of the server.
|
||||||
|
TestHttpsClient(const IOServicePtr& io_service, TlsContextPtr tls_context,
|
||||||
|
const std::string& server_address = "127.0.0.1",
|
||||||
|
uint16_t port = 18123)
|
||||||
|
: io_service_(io_service), stream_(io_service_->getInternalIOService(),
|
||||||
|
tls_context->getContext()), buf_(), response_(),
|
||||||
|
server_address_(server_address), server_port_(port),
|
||||||
|
receive_done_(false) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Destructor.
|
||||||
|
///
|
||||||
|
/// Closes the underlying socket if it is open.
|
||||||
|
virtual ~TestHttpsClient() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Send HTTP request specified in textual format.
|
||||||
|
///
|
||||||
|
/// @param request HTTP request in the textual format.
|
||||||
|
virtual void startRequest(const std::string& request) {
|
||||||
|
tcp::endpoint endpoint(address::from_string(server_address_),
|
||||||
|
server_port_);
|
||||||
|
stream_.lowest_layer().async_connect(endpoint,
|
||||||
|
[this, request](const boost::system::error_code& ec) {
|
||||||
|
receive_done_ = false;
|
||||||
|
if (ec) {
|
||||||
|
// One would expect that async_connect wouldn't return
|
||||||
|
// EINPROGRESS error code, but simply wait for the connection
|
||||||
|
// to get established before the handler is invoked. It turns out,
|
||||||
|
// however, that on some OSes the connect handler may receive this
|
||||||
|
// error code which doesn't necessarily indicate a problem.
|
||||||
|
// Making an attempt to write and read from this socket will
|
||||||
|
// typically succeed. So, we ignore this error.
|
||||||
|
if (ec.value() != boost::asio::error::in_progress) {
|
||||||
|
ADD_FAILURE() << "error occurred while connecting: "
|
||||||
|
<< ec.message();
|
||||||
|
io_service_->stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream_.async_handshake(roleToImpl(TlsRole::CLIENT),
|
||||||
|
[this, request](const boost::system::error_code& ec) {
|
||||||
|
if (ec) {
|
||||||
|
ADD_FAILURE() << "error occurred during handshake: "
|
||||||
|
<< ec.message();
|
||||||
|
io_service_->stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
sendRequest(request);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Send HTTP request.
|
||||||
|
///
|
||||||
|
/// @param request HTTP request in the textual format.
|
||||||
|
virtual void sendRequest(const std::string& request) {
|
||||||
|
sendPartialRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Send part of the HTTP request.
|
||||||
|
///
|
||||||
|
/// @param request part of the HTTP request to be sent.
|
||||||
|
virtual void sendPartialRequest(std::string request) {
|
||||||
|
boost::asio::async_write(stream_,
|
||||||
|
boost::asio::buffer(request.data(), request.size()),
|
||||||
|
[this, request](const boost::system::error_code& ec,
|
||||||
|
std::size_t bytes_transferred) mutable {
|
||||||
|
if (ec) {
|
||||||
|
if (ec.value() == boost::asio::error::operation_aborted) {
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else if ((ec.value() == boost::asio::error::try_again) ||
|
||||||
|
(ec.value() == boost::asio::error::would_block)) {
|
||||||
|
// If we should try again make sure there is no garbage in the
|
||||||
|
// bytes_transferred.
|
||||||
|
bytes_transferred = 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
ADD_FAILURE() << "error occurred while connecting: "
|
||||||
|
<< ec.message();
|
||||||
|
io_service_->stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the part of the request which has been sent.
|
||||||
|
if (bytes_transferred > 0 && (request.size() <= bytes_transferred)) {
|
||||||
|
request.erase(0, bytes_transferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue sending request data if there are still some data to be
|
||||||
|
// sent.
|
||||||
|
if (!request.empty()) {
|
||||||
|
sendPartialRequest(request);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Request has been sent. Start receiving response.
|
||||||
|
response_.clear();
|
||||||
|
receivePartialResponse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Receive response from the server.
|
||||||
|
virtual void receivePartialResponse() {
|
||||||
|
stream_.async_read_some(boost::asio::buffer(buf_.data(), buf_.size()),
|
||||||
|
[this](const boost::system::error_code& ec,
|
||||||
|
std::size_t bytes_transferred) {
|
||||||
|
if (ec) {
|
||||||
|
// IO service stopped so simply return.
|
||||||
|
if (ec.value() == boost::asio::error::operation_aborted) {
|
||||||
|
return;
|
||||||
|
|
||||||
|
} else if ((ec.value() == boost::asio::error::try_again) ||
|
||||||
|
(ec.value() == boost::asio::error::would_block)) {
|
||||||
|
// If we should try again, make sure that there is no garbage
|
||||||
|
// in the bytes_transferred.
|
||||||
|
bytes_transferred = 0;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Error occurred, bail...
|
||||||
|
ADD_FAILURE() << "error occurred while receiving HTTP"
|
||||||
|
" response from the server: " << ec.message();
|
||||||
|
io_service_->stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_transferred > 0) {
|
||||||
|
response_.insert(response_.end(), buf_.data(),
|
||||||
|
buf_.data() + bytes_transferred);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two consecutive new lines end the part of the response we're
|
||||||
|
// expecting.
|
||||||
|
if (response_.find("\r\n\r\n", 0) != std::string::npos) {
|
||||||
|
receive_done_ = true;
|
||||||
|
io_service_->stop();
|
||||||
|
} else {
|
||||||
|
receivePartialResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Checks if the TCP connection is still open.
|
||||||
|
///
|
||||||
|
/// Tests the TCP connection by trying to read from the socket.
|
||||||
|
/// Unfortunately expected failure depends on a race between the read
|
||||||
|
/// and the other side close so to check if the connection is closed
|
||||||
|
/// please use @c isConnectionClosed instead.
|
||||||
|
///
|
||||||
|
/// @return true if the TCP connection is open.
|
||||||
|
virtual bool isConnectionAlive() {
|
||||||
|
// Remember the current non blocking setting.
|
||||||
|
const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
|
||||||
|
// Set the socket to non blocking mode. We're going to test if the socket
|
||||||
|
// returns would_block status on the attempt to read from it.
|
||||||
|
stream_.lowest_layer().non_blocking(true);
|
||||||
|
|
||||||
|
// We need to provide a buffer for a call to read.
|
||||||
|
char data[2];
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
|
||||||
|
|
||||||
|
// Revert the original non_blocking flag on the socket.
|
||||||
|
stream_.lowest_layer().non_blocking(non_blocking_orig);
|
||||||
|
|
||||||
|
// If the connection is alive we'd typically get would_block status code.
|
||||||
|
// If there are any data that haven't been read we may also get success
|
||||||
|
// status. We're guessing that try_again may also be returned by some
|
||||||
|
// implementations in some situations. Any other error code indicates a
|
||||||
|
// problem with the connection so we assume that the connection has been
|
||||||
|
// closed.
|
||||||
|
return (!ec || (ec.value() == boost::asio::error::try_again) ||
|
||||||
|
(ec.value() == boost::asio::error::would_block));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Checks if the TCP connection is already closed.
|
||||||
|
///
|
||||||
|
/// Tests the TCP connection by trying to read from the socket.
|
||||||
|
/// The read can block so this must be used to check if a connection
|
||||||
|
/// is alive so to check if the connection is alive please always
|
||||||
|
/// use @c isConnectionAlive.
|
||||||
|
///
|
||||||
|
/// @return true if the TCP connection is closed.
|
||||||
|
virtual bool isConnectionClosed() {
|
||||||
|
// Remember the current non blocking setting.
|
||||||
|
const bool non_blocking_orig = stream_.lowest_layer().non_blocking();
|
||||||
|
// Set the socket to blocking mode. We're going to test if the socket
|
||||||
|
// returns eof status on the attempt to read from it.
|
||||||
|
stream_.lowest_layer().non_blocking(false);
|
||||||
|
|
||||||
|
// We need to provide a buffer for a call to read.
|
||||||
|
char data[2];
|
||||||
|
boost::system::error_code ec;
|
||||||
|
boost::asio::read(stream_, boost::asio::buffer(data, sizeof(data)), ec);
|
||||||
|
|
||||||
|
// Revert the original non_blocking flag on the socket.
|
||||||
|
stream_.lowest_layer().non_blocking(non_blocking_orig);
|
||||||
|
|
||||||
|
// If the connection is closed we'd typically get eof or
|
||||||
|
// stream_truncated status code.
|
||||||
|
return ((ec.value() == boost::asio::error::eof) ||
|
||||||
|
(ec.value() == STREAM_TRUNCATED));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Close connection.
|
||||||
|
virtual void close() {
|
||||||
|
stream_.lowest_layer().close();
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual std::string getResponse() const {
|
||||||
|
return (response_);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief Returns true if the receive completed without error.
|
||||||
|
///
|
||||||
|
/// @return True if the receive completed successfully, false
|
||||||
|
/// otherwise.
|
||||||
|
virtual bool receiveDone() const {
|
||||||
|
return (receive_done_);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/// @brief Holds pointer to the IO service.
|
||||||
|
isc::asiolink::IOServicePtr io_service_;
|
||||||
|
|
||||||
|
/// @brief A socket used for the connection.
|
||||||
|
TlsStreamImpl stream_;
|
||||||
|
|
||||||
|
/// @brief Buffer into which response is written.
|
||||||
|
std::array<char, 8192> buf_;
|
||||||
|
|
||||||
|
/// @brief Response in the textual format.
|
||||||
|
std::string response_;
|
||||||
|
|
||||||
|
/// @brief IP address of the server.
|
||||||
|
std::string server_address_;
|
||||||
|
|
||||||
|
/// @brief IP port of the server.
|
||||||
|
uint16_t server_port_;
|
||||||
|
|
||||||
|
/// @brief Set to true when the receive has completed successfully.
|
||||||
|
bool receive_done_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// @brief Pointer to the TestHttpsClient.
|
||||||
|
typedef boost::shared_ptr<TestHttpsClient> TestHttpsClientPtr;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Reference in New Issue
Block a user