Skip to content

Commit de129e5

Browse files
mpenickMichael Fero
authored andcommitted
CPP-799 Handle HTTP client request timeouts (#276)
* Expand HTTP client error handling * Handle and verify`cancel()` * Pinned to version=1 * Corrected default metadata service port * Cleaned up deeply nested error handling (in favor of early return)
1 parent 248a359 commit de129e5

8 files changed

Lines changed: 367 additions & 107 deletions

File tree

cpp-driver/gtests/src/unit/http_server.cpp

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ using datastax::internal::core::Task;
2727
String response(int status, const String& body = "", const String& content_type = "") {
2828
OStringStream ss;
2929
ss << "HTTP/1.0 " << status << " " << http_status_str(static_cast<http_status>(status)) << "\r\n";
30-
if (status == 200 && !body.empty()) {
30+
if (!body.empty()) {
3131
ss << "Content-Type: ";
3232
if (content_type.empty()) {
3333
ss << "text/plain";
@@ -63,13 +63,14 @@ bool Server::use_ssl(const String& key, const String& cert, const String& passwo
6363
}
6464

6565
Server::ClientConnection::ClientConnection(internal::ServerConnection* server_connection,
66-
const String& path, const String& content_type,
67-
const String& response_body, bool enable_valid_response)
66+
Server* server)
6867
: internal::ClientConnection(server_connection)
69-
, path_(path)
70-
, content_type_(content_type)
71-
, response_body_(response_body)
72-
, enable_valid_response_(enable_valid_response) {
68+
, path_(server->path())
69+
, content_type_(server->content_type())
70+
, response_body_(server->response_body())
71+
, response_status_code_(server->response_status_code())
72+
, enable_valid_response_(server->enable_valid_response())
73+
, close_connnection_after_request_(server->close_connnection_after_request()) {
7374
http_parser_init(&parser_, HTTP_REQUEST);
7475
http_parser_settings_init(&parser_settings_);
7576

@@ -95,17 +96,26 @@ int Server::ClientConnection::on_url(http_parser* parser, const char* buf, size_
9596

9697
void Server::ClientConnection::handle_url(const char* buf, size_t len) {
9798
String path(buf, len);
98-
if (path == path_) {
99+
if (path.substr(0, path.find("?")) == path_) { // Compare without query parameters
99100
if (enable_valid_response_) {
100101
if (response_body_.empty()) {
101-
write(response(200, request_)); // Echo response
102+
write(response(response_status_code_, request_)); // Echo response
102103
} else {
103-
write(response(200, response_body_, content_type_));
104+
write(response(response_status_code_, response_body_, content_type_));
104105
}
105106
} else {
106107
write("Invalid HTTP server response");
107108
}
108109
} else {
109110
write(response(404));
110111
}
112+
// From the HTTP/1.0 protocol specification:
113+
//
114+
// > When an Entity-Body is included with a message, the length of that body may be determined in
115+
// > one of two ways. If a Content-Length header field is present, its value in bytes represents
116+
// > the length of the Entity-Body. Otherwise, the body length is determined by the closing of the
117+
// > connection by the server.
118+
if (close_connnection_after_request_) {
119+
close();
120+
}
111121
}

cpp-driver/gtests/src/unit/http_server.hpp

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,13 @@ namespace mockssandra { namespace http {
3333
* server.
3434
*/
3535
class Server {
36-
public:
3736
public:
3837
Server()
3938
: path_("/")
4039
, content_type_("text/plain")
40+
, response_status_code_(200)
4141
, enable_valid_response_(true)
42+
, close_connnection_after_request_(true)
4243
, event_loop_group_(1, "HTTP Server")
4344
, factory_(this)
4445
, server_connection_(new internal::ServerConnection(
@@ -47,12 +48,18 @@ class Server {
4748
const String& path() const { return path_; }
4849
const String& content_type() const { return content_type_; }
4950
const String& response_body() const { return response_body_; }
51+
int response_status_code() const { return response_status_code_; }
5052
bool enable_valid_response() { return enable_valid_response_; }
53+
bool close_connnection_after_request() { return close_connnection_after_request_; }
5154

5255
void set_path(const String& path) { path_ = path; }
5356
void set_content_type(const String& content_type) { content_type_ = content_type; }
5457
void set_response_body(const String& response_body) { response_body_ = response_body; }
58+
void set_response_status_code(int status_code) { response_status_code_ = status_code; }
5559
void enable_valid_response(bool enable) { enable_valid_response_ = enable; }
60+
void set_close_connnection_after_request(bool enable) {
61+
close_connnection_after_request_ = enable;
62+
}
5663

5764
bool use_ssl(const String& key, const String& cert, const String& password = "",
5865
const String& client_cert = "");
@@ -63,9 +70,7 @@ class Server {
6370
private:
6471
class ClientConnection : public internal::ClientConnection {
6572
public:
66-
ClientConnection(internal::ServerConnection* server_connection, const String& endpoint,
67-
const String& content_type, const String& response_body,
68-
bool enable_valid_response);
73+
ClientConnection(internal::ServerConnection* server_connection, Server* server);
6974

7075
virtual void on_read(const char* data, size_t len);
7176

@@ -77,7 +82,9 @@ class Server {
7782
String path_;
7883
String content_type_;
7984
String response_body_;
85+
int response_status_code_;
8086
bool enable_valid_response_;
87+
bool close_connnection_after_request_;
8188
String request_;
8289
http_parser parser_;
8390
http_parser_settings parser_settings_;
@@ -90,8 +97,7 @@ class Server {
9097

9198
virtual internal::ClientConnection*
9299
create(internal::ServerConnection* server_connection) const {
93-
return new ClientConnection(server_connection, server_->path(), server_->content_type(),
94-
server_->response_body(), server_->enable_valid_response());
100+
return new ClientConnection(server_connection, server_);
95101
}
96102

97103
private:
@@ -102,7 +108,9 @@ class Server {
102108
String path_;
103109
String content_type_;
104110
String response_body_;
111+
int response_status_code_;
105112
bool enable_valid_response_;
113+
bool close_connnection_after_request_;
106114
SimpleEventLoopGroup event_loop_group_;
107115
ClientConnectionFactory factory_;
108116
internal::ServerConnection::Ptr server_connection_;

cpp-driver/gtests/src/unit/http_test.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,14 @@ class HttpTest : public LoopTest {
3939
server_.set_response_body(response_body);
4040
}
4141

42+
void set_response_status_code(int status_code) { server_.set_response_status_code(status_code); }
43+
4244
void enable_valid_response(bool enable) { server_.enable_valid_response(enable); }
4345

46+
void set_close_connnection_after_request(bool enable) {
47+
server_.set_close_connnection_after_request(enable);
48+
}
49+
4450
void start_http_server() { server_.listen(); }
4551
void stop_http_server() { server_.close(); }
4652

cpp-driver/gtests/src/unit/tests/test_cloud_secure_connect_config.cpp

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
#define SNI_HOST_ID_1 "276b1694-64c4-4ba8-afb4-e33915a02f1e"
5353
#define SNI_HOST_ID_2 "8c29f723-5c1c-4ffd-a4ef-8c683a7fc02b"
5454
#define SNI_HOST_ID_3 "fb91d3ff-47cb-447d-b31d-c5721ca8d7ab"
55+
#define METADATA_SERVICE_PORT 30443
5556

5657
using datastax::String;
5758
using datastax::internal::core::AddressVec;
@@ -404,12 +405,17 @@ class CloudMetadataServerTest : public CloudSecureConnectionConfigTest {
404405

405406
const AddressVec& contact_points = resolver->resolved_contact_points();
406407
ASSERT_EQ(3u, contact_points.size());
407-
EXPECT_EQ(Address(SNI_HOST, CASS_DEFAULT_PORT, SNI_HOST_ID_1), contact_points[0]);
408-
EXPECT_EQ(Address(SNI_HOST, CASS_DEFAULT_PORT, SNI_HOST_ID_2), contact_points[1]);
409-
EXPECT_EQ(Address(SNI_HOST, CASS_DEFAULT_PORT, SNI_HOST_ID_3), contact_points[2]);
408+
EXPECT_EQ(Address(SNI_HOST, METADATA_SERVICE_PORT, SNI_HOST_ID_1), contact_points[0]);
409+
EXPECT_EQ(Address(SNI_HOST, METADATA_SERVICE_PORT, SNI_HOST_ID_2), contact_points[1]);
410+
EXPECT_EQ(Address(SNI_HOST, METADATA_SERVICE_PORT, SNI_HOST_ID_3), contact_points[2]);
410411
}
411412

412413
static void on_resolve_failed(ClusterMetadataResolver* resolver, bool* flag) {
414+
*flag = true;
415+
EXPECT_EQ(0u, resolver->resolved_contact_points().size());
416+
}
417+
418+
static void on_resolve_local_dc_failed(ClusterMetadataResolver* resolver, bool* flag) {
413419
*flag = true;
414420
EXPECT_EQ("", resolver->local_dc());
415421
EXPECT_EQ(0u, resolver->resolved_contact_points().size());
@@ -525,7 +531,8 @@ TEST_F(CloudMetadataServerTest, ResolveV1MissingLocalDcSsl) {
525531

526532
bool is_resolved = false;
527533
AddressVec contact_points;
528-
resolver()->resolve(loop(), contact_points, bind_callback(on_resolve_failed, &is_resolved));
534+
resolver()->resolve(loop(), contact_points,
535+
bind_callback(on_resolve_local_dc_failed, &is_resolved));
529536
uv_run(loop(), UV_RUN_DEFAULT);
530537
EXPECT_TRUE(is_resolved);
531538

@@ -555,4 +562,67 @@ TEST_F(CloudMetadataServerTest, ResolveV1MissingSniProxyAddressSsl) {
555562

556563
stop_http_server();
557564
}
565+
566+
TEST_F(CloudMetadataServerTest, ResolveInvalidJsonResponse) {
567+
add_logging_critera("Unable to configure driver from metadata server: Metadata JSON is invalid");
568+
569+
set_path("/metadata");
570+
set_response_body("[]");
571+
set_content_type("application/json");
572+
HttpTest::start_http_server();
573+
574+
bool is_resolve_failed = false;
575+
AddressVec contact_points;
576+
resolver()->resolve(loop(), contact_points, bind_callback(on_resolve_failed, &is_resolve_failed));
577+
uv_run(loop(), UV_RUN_DEFAULT);
578+
EXPECT_TRUE(is_resolve_failed);
579+
EXPECT_EQ(logging_criteria_count(), 1);
580+
581+
stop_http_server();
582+
}
583+
584+
TEST_F(CloudMetadataServerTest, ResolveErrorResponse) {
585+
add_logging_critera("Unable to configure driver from metadata server: Returned error response "
586+
"code 400: 'Invalid version'");
587+
588+
const char* response_body = "{"
589+
"\"code\": 400,"
590+
"\"message\": \"Invalid version\""
591+
"}";
592+
593+
set_path("/metadata");
594+
set_response_body(response_body);
595+
set_response_status_code(400);
596+
set_content_type("application/json");
597+
HttpTest::start_http_server();
598+
599+
bool is_resolve_failed = false;
600+
AddressVec contact_points;
601+
resolver()->resolve(loop(), contact_points, bind_callback(on_resolve_failed, &is_resolve_failed));
602+
uv_run(loop(), UV_RUN_DEFAULT);
603+
EXPECT_TRUE(is_resolve_failed);
604+
EXPECT_EQ(logging_criteria_count(), 1);
605+
606+
stop_http_server();
607+
}
608+
609+
TEST_F(CloudMetadataServerTest, ResolveInvalidJsonErrorResponse) {
610+
add_logging_critera("Unable to configure driver from metadata server: Returned error response "
611+
"code 400: '[]'");
612+
613+
set_path("/metadata");
614+
set_response_body("[]");
615+
set_response_status_code(400);
616+
set_content_type("application/json");
617+
HttpTest::start_http_server();
618+
619+
bool is_resolve_failed = false;
620+
AddressVec contact_points;
621+
resolver()->resolve(loop(), contact_points, bind_callback(on_resolve_failed, &is_resolve_failed));
622+
uv_run(loop(), UV_RUN_DEFAULT);
623+
EXPECT_TRUE(is_resolve_failed);
624+
EXPECT_EQ(logging_criteria_count(), 1);
625+
626+
stop_http_server();
627+
}
558628
#endif

0 commit comments

Comments
 (0)