Rework the client error handling

master
Chunting Gu 6 years ago
parent c2a009f68a
commit 8c09189b14

@ -31,8 +31,8 @@ int main() {
// Print the response content data. // Print the response content data.
std::cout << r->content() << std::endl; std::cout << r->content() << std::endl;
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cout << e.what() << std::endl; std::cout << error << std::endl;
} }
return 0; return 0;

@ -73,8 +73,8 @@ TEST(ClientTest, Get_RequestFunc) {
AssertGet(r); AssertGet(r);
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
} }
} }
@ -88,8 +88,8 @@ TEST(ClientTest, Get_Shortcut) {
AssertGet(r); AssertGet(r);
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
} }
} }
@ -110,8 +110,8 @@ TEST(ClientTest, Get_SSL) {
AssertGet(r); AssertGet(r);
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
} }
} }
#endif // WEBCC_ENABLE_SSL #endif // WEBCC_ENABLE_SSL
@ -131,8 +131,8 @@ TEST(ClientTest, Compression_Gzip) {
EXPECT_EQ(true, json["gzipped"].asBool()); EXPECT_EQ(true, json["gzipped"].asBool());
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
} }
} }
@ -147,8 +147,8 @@ TEST(ClientTest, Compression_Deflate) {
EXPECT_EQ(true, json["deflated"].asBool()); EXPECT_EQ(true, json["deflated"].asBool());
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
} }
} }
@ -201,8 +201,8 @@ TEST(ClientTest, KeepAlive) {
EXPECT_TRUE(iequals(r->GetHeader("Connection"), "Keep-alive")); EXPECT_TRUE(iequals(r->GetHeader("Connection"), "Keep-alive"));
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
} }
} }
@ -226,8 +226,8 @@ TEST(ClientTest, GetImageJpeg) {
// TODO: Verify the response is a valid JPEG image. // TODO: Verify the response is a valid JPEG image.
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
} }
} }

@ -58,8 +58,8 @@ int main() {
#endif // WEBCC_ENABLE_SSL #endif // WEBCC_ENABLE_SSL
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cout << "Exception: " << e.what() << std::endl; std::cout << error << std::endl;
} }
return 0; return 0;

@ -58,8 +58,8 @@ int main(int argc, char* argv[]) {
std::cout << r->status() << std::endl; std::cout << r->status() << std::endl;
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cout << "Exception: " << e.what() << std::endl; std::cout << error << std::endl;
} }
return 0; return 0;

@ -55,7 +55,7 @@ int main(int argc, char* argv[]) {
server.Run(); server.Run();
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl; std::cerr << e.what() << std::endl;
return 1; return 1;
} }

@ -67,8 +67,8 @@ void ListEvents(webcc::ClientSession& session) {
PRINT_JSON_STRING(r->content()); PRINT_JSON_STRING(r->content());
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cout << e.what() << std::endl; std::cout << error << std::endl;
} }
} }
@ -81,8 +81,8 @@ void ListUserFollowers(webcc::ClientSession& session, const std::string& user) {
PRINT_JSON_STRING(r->content()); PRINT_JSON_STRING(r->content());
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cout << e.what() << std::endl; std::cout << error << std::endl;
} }
} }
@ -100,8 +100,8 @@ void ListAuthUserFollowers(webcc::ClientSession& session,
PRINT_JSON_STRING(r->content()); PRINT_JSON_STRING(r->content());
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cout << e.what() << std::endl; std::cout << error << std::endl;
} }
} }
@ -124,8 +124,8 @@ void CreateAuthorization(webcc::ClientSession& session,
std::cout << r->content() << std::endl; std::cout << r->content() << std::endl;
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cout << e.what() << std::endl; std::cout << error << std::endl;
} }
} }

@ -74,8 +74,8 @@ public:
return true; return true;
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
return false; return false;
} }
} }
@ -97,8 +97,8 @@ public:
return !id->empty(); return !id->empty();
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
return false; return false;
} }
} }
@ -122,8 +122,8 @@ public:
return JsonStringToBook(r->content(), book); return JsonStringToBook(r->content(), book);
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
return false; return false;
} }
} }
@ -143,8 +143,8 @@ public:
return true; return true;
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
return false; return false;
} }
} }
@ -159,8 +159,8 @@ public:
return true; return true;
} catch (const webcc::Exception& e) { } catch (const webcc::Error& error) {
std::cerr << e.what() << std::endl; std::cerr << error << std::endl;
return false; return false;
} }
} }

@ -238,7 +238,7 @@ int main(int argc, char* argv[]) {
server.Run(); server.Run();
} catch (const std::exception& e) { } catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl; std::cerr << e.what() << std::endl;
return 1; return 1;
} }

@ -12,43 +12,30 @@ Client::Client()
buffer_size_(kBufferSize), buffer_size_(kBufferSize),
timeout_(kMaxReadSeconds), timeout_(kMaxReadSeconds),
closed_(false), closed_(false),
timer_canceled_(false), timer_canceled_(false) {
timed_out_(false),
error_(kNoError) {
} }
bool Client::Request(RequestPtr request, bool connect) { Error Client::Request(RequestPtr request, bool connect) {
io_context_.restart(); Restart();
response_.reset(new Response{});
response_parser_.Init(response_.get());
closed_ = false;
timer_canceled_ = false;
timed_out_ = false;
error_ = kNoError;
if (buffer_.size() != buffer_size_) {
LOG_VERB("Resize buffer: %u -> %u.", buffer_.size(), buffer_size_);
buffer_.resize(buffer_size_);
}
if (connect) { if (connect) {
// No existing socket connection was specified, create a new one. // No existing socket connection was specified, create a new one.
if ((error_ = Connect(request)) != kNoError) { Connect(request);
return false;
if (error_) {
return error_;
} }
} }
if ((error_ = WriteReqeust(request)) != kNoError) { WriteReqeust(request);
return false;
}
if ((error_ = ReadResponse()) != kNoError) { if (error_) {
return false; return error_;
} }
return true; ReadResponse();
return error_;
} }
void Client::Close() { void Client::Close() {
@ -68,22 +55,38 @@ void Client::Close() {
} }
} }
Error Client::Connect(RequestPtr request) { void Client::Restart() {
io_context_.restart();
response_.reset(new Response{});
response_parser_.Init(response_.get());
closed_ = false;
timer_canceled_ = false;
error_ = Error{};
if (buffer_.size() != buffer_size_) {
LOG_VERB("Resize buffer: %u -> %u.", buffer_.size(), buffer_size_);
buffer_.resize(buffer_size_);
}
}
void Client::Connect(RequestPtr request) {
if (request->url().scheme() == "https") { if (request->url().scheme() == "https") {
#if WEBCC_ENABLE_SSL #if WEBCC_ENABLE_SSL
socket_.reset(new SslSocket{ io_context_, ssl_verify_ }); socket_.reset(new SslSocket{ io_context_, ssl_verify_ });
return DoConnect(request, "443"); DoConnect(request, "443");
#else #else
LOG_ERRO("SSL/HTTPS support is not enabled."); LOG_ERRO("SSL/HTTPS support is not enabled.");
return kSchemaError; return kSyntaxError;
#endif // WEBCC_ENABLE_SSL #endif // WEBCC_ENABLE_SSL
} else { } else {
socket_.reset(new Socket{ io_context_ }); socket_.reset(new Socket{ io_context_ });
return DoConnect(request, "80"); DoConnect(request, "80");
} }
} }
Error Client::DoConnect(RequestPtr request, const std::string& default_port) { void Client::DoConnect(RequestPtr request, const std::string& default_port) {
tcp::resolver resolver(io_context_); tcp::resolver resolver(io_context_);
std::string port = request->port(default_port); std::string port = request->port(default_port);
@ -94,7 +97,7 @@ Error Client::DoConnect(RequestPtr request, const std::string& default_port) {
if (ec) { if (ec) {
LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(), LOG_ERRO("Host resolve error (%s): %s, %s.", ec.message().c_str(),
request->host().c_str(), port.c_str()); request->host().c_str(), port.c_str());
return kHostResolveError; error_.Set(Error::kResolveError, "Host resolve error");
} }
LOG_VERB("Connect to server..."); LOG_VERB("Connect to server...");
@ -106,15 +109,14 @@ Error Client::DoConnect(RequestPtr request, const std::string& default_port) {
if (ec) { if (ec) {
LOG_ERRO("Socket connect error (%s).", ec.message().c_str()); LOG_ERRO("Socket connect error (%s).", ec.message().c_str());
Close(); Close();
return kEndpointConnectError; // TODO: Handshake error
error_.Set(Error::kConnectError, "Endpoint connect error");
} }
LOG_VERB("Socket connected."); LOG_VERB("Socket connected.");
return kNoError;
} }
Error Client::WriteReqeust(RequestPtr request) { void Client::WriteReqeust(RequestPtr request) {
LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str()); LOG_VERB("HTTP request:\n%s", request->Dump(4, "> ").c_str());
// NOTE: // NOTE:
@ -130,36 +132,30 @@ Error Client::WriteReqeust(RequestPtr request) {
if (ec) { if (ec) {
LOG_ERRO("Socket write error (%s).", ec.message().c_str()); LOG_ERRO("Socket write error (%s).", ec.message().c_str());
Close(); Close();
return kSocketWriteError; error_.Set(Error::kSocketWriteError, "Socket write error");
} }
LOG_INFO("Request sent."); LOG_INFO("Request sent.");
return kNoError;
} }
Error Client::ReadResponse() { void Client::ReadResponse() {
LOG_VERB("Read response (timeout: %ds)...", timeout_); LOG_VERB("Read response (timeout: %ds)...", timeout_);
timer_.expires_after(std::chrono::seconds(timeout_)); timer_.expires_after(std::chrono::seconds(timeout_));
DoWaitTimer(); DoWaitTimer();
DoReadResponse();
Error error = kNoError; if (!error_) {
DoReadResponse(&error);
if (error == kNoError) {
LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str()); LOG_VERB("HTTP response:\n%s", response_->Dump(4, "> ").c_str());
} }
return error;
} }
void Client::DoReadResponse(Error* error) { void Client::DoReadResponse() {
boost::system::error_code ec = boost::asio::error::would_block; boost::system::error_code ec = boost::asio::error::would_block;
auto handler = [this, &ec, error](boost::system::error_code inner_ec, auto handler = [this, &ec](boost::system::error_code inner_ec,
std::size_t length) { std::size_t length) {
ec = inner_ec; ec = inner_ec;
LOG_VERB("Socket async read handler."); LOG_VERB("Socket async read handler.");
@ -170,7 +166,7 @@ void Client::DoReadResponse(Error* error) {
// TODO: Is it necessary to check `length == 0`? // TODO: Is it necessary to check `length == 0`?
if (ec || length == 0) { if (ec || length == 0) {
Close(); Close();
*error = kSocketReadError; error_.Set(Error::kSocketReadError, "Socket read error");
LOG_ERRO("Socket read error (%s).", ec.message().c_str()); LOG_ERRO("Socket read error (%s).", ec.message().c_str());
return; return;
} }
@ -180,7 +176,7 @@ void Client::DoReadResponse(Error* error) {
// Parse the response piece just read. // Parse the response piece just read.
if (!response_parser_.Parse(buffer_.data(), length)) { if (!response_parser_.Parse(buffer_.data(), length)) {
Close(); Close();
*error = kHttpError; error_.Set(Error::kParseError, "HTTP parse error");
LOG_ERRO("Failed to parse HTTP response."); LOG_ERRO("Failed to parse HTTP response.");
return; return;
} }
@ -203,7 +199,7 @@ void Client::DoReadResponse(Error* error) {
} }
if (!closed_) { if (!closed_) {
DoReadResponse(error); DoReadResponse();
} }
}; };
@ -238,7 +234,7 @@ void Client::OnTimer(boost::system::error_code ec) {
// The deadline has passed. The socket is closed so that any outstanding // The deadline has passed. The socket is closed so that any outstanding
// asynchronous operations are canceled. // asynchronous operations are canceled.
LOG_WARN("HTTP client timed out."); LOG_WARN("HTTP client timed out.");
timed_out_ = true; error_.set_timeout(true);
Close(); Close();
return; return;
} }

@ -29,7 +29,7 @@ class Client {
public: public:
Client(); Client();
virtual ~Client() = default; ~Client() = default;
Client(const Client&) = delete; Client(const Client&) = delete;
Client& operator=(const Client&) = delete; Client& operator=(const Client&) = delete;
@ -52,7 +52,7 @@ public:
} }
// Connect to server, send request, wait until response is received. // Connect to server, send request, wait until response is received.
bool Request(RequestPtr request, bool connect = true); Error Request(RequestPtr request, bool connect = true);
// Close the socket. // Close the socket.
void Close(); void Close();
@ -61,20 +61,18 @@ public:
bool closed() const { return closed_; } bool closed() const { return closed_; }
bool timed_out() const { return timed_out_; }
Error error() const { return error_; }
private: private:
Error Connect(RequestPtr request); void Restart();
Error DoConnect(RequestPtr request, const std::string& default_port); void Connect(RequestPtr request);
Error WriteReqeust(RequestPtr request); void DoConnect(RequestPtr request, const std::string& default_port);
Error ReadResponse(); void WriteReqeust(RequestPtr request);
void DoReadResponse(Error* error); void ReadResponse();
void DoReadResponse();
void DoWaitTimer(); void DoWaitTimer();
void OnTimer(boost::system::error_code ec); void OnTimer(boost::system::error_code ec);
@ -113,10 +111,6 @@ private:
// Deadline timer canceled. // Deadline timer canceled.
bool timer_canceled_; bool timer_canceled_;
// Timeout occurred.
bool timed_out_;
// Error code.
Error error_; Error error_;
}; };

@ -143,11 +143,11 @@ void ClientSession::InitHeaders() {
// content coding. // content coding.
// //
// A note about "deflate": // A note about "deflate":
// (https://www.zlib.net/zlib_faq.html#faq39)
// "gzip" is the gzip format, and "deflate" is the zlib format. They should // "gzip" is the gzip format, and "deflate" is the zlib format. They should
// probably have called the second one "zlib" instead to avoid confusion with // probably have called the second one "zlib" instead to avoid confusion with
// the raw deflate compressed data format. // the raw deflate compressed data format.
// Simply put, "deflate" is not recommended for HTTP 1.1 encoding. // Simply put, "deflate" is not recommended for HTTP 1.1 encoding.
// (https://www.zlib.net/zlib_faq.html#faq39)
#if WEBCC_ENABLE_GZIP #if WEBCC_ENABLE_GZIP
headers_.Set(kAcceptEncoding, "gzip, deflate"); headers_.Set(kAcceptEncoding, "gzip, deflate");
@ -161,7 +161,7 @@ void ClientSession::InitHeaders() {
} }
ResponsePtr ClientSession::Send(RequestPtr request) { ResponsePtr ClientSession::Send(RequestPtr request) {
const ClientPool::Key key{request->url()}; const ClientPool::Key key{ request->url() };
// Reuse a pooled connection. // Reuse a pooled connection.
bool reuse = false; bool reuse = false;
@ -179,22 +179,22 @@ ResponsePtr ClientSession::Send(RequestPtr request) {
client->set_buffer_size(buffer_size_); client->set_buffer_size(buffer_size_);
client->set_timeout(timeout_); client->set_timeout(timeout_);
bool ok = client->Request(request, !reuse); Error error = client->Request(request, !reuse);
if (!ok) { if (error) {
if (reuse && client->error() == kSocketWriteError) { if (reuse && error.code() == Error::kSocketWriteError) {
LOG_WARN("Cannot send request with the reused connection. " LOG_WARN("Cannot send request with the reused connection. "
"The server must have closed it, reconnect and try again."); "The server must have closed it, reconnect and try again.");
ok = client->Request(request, true); error = client->Request(request, true);
} }
} }
if (!ok) { if (error) {
// Remove the failed connection from pool.
if (reuse) { if (reuse) {
// Remove the failed connection from pool.
pool_.Remove(key); pool_.Remove(key);
} }
throw Exception(client->error(), "", client->timed_out()); throw error;
} }
// Update connection pool. // Update connection pool.

@ -204,7 +204,7 @@ FormPart::FormPart(const std::string& name, const Path& path,
const std::string& media_type) const std::string& media_type)
: name_(name), media_type_(media_type) { : name_(name), media_type_(media_type) {
if (!ReadFile(path, &data_)) { if (!ReadFile(path, &data_)) {
throw Exception(kFileIOError, "Cannot read the file."); throw Error{ Error::kFileError, "Cannot read the file." };
} }
// Determine file name from file path. // Determine file name from file path.

@ -1,9 +1,8 @@
#include "webcc/globals.h" #include "webcc/globals.h"
#include <iostream>
#include <map> #include <map>
#include "webcc/version.h"
namespace webcc { namespace webcc {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -47,37 +46,13 @@ std::string FromExtension(const std::string& extension,
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
const char* DescribeError(Error error) { std::ostream& operator<<(std::ostream& os, const Error& error) {
switch (error) { os << std::to_string(static_cast<int>(error.code()));
case kSchemaError: os << ": " << error.message();
return "Schema error"; if (error.timeout()) {
case kHostResolveError: os << " (timeout)";
return "Host resolve error";
case kEndpointConnectError:
return "Endpoint connect error";
case kHandshakeError:
return "Handshake error";
case kSocketReadError:
return "Socket read error";
case kSocketWriteError:
return "Socket write error";
case kHttpError:
return "HTTP error";
case kFileIOError:
return "File IO error";
default:
return "";
}
}
Exception::Exception(Error error, const std::string& details, bool timeout)
: error_(error), msg_(DescribeError(error)), timeout_(timeout) {
if (!details.empty()) {
msg_ += " (" + details + ")";
}
if (timeout) {
msg_ += " (timeout)";
} }
return os;
} }
} // namespace webcc } // namespace webcc

@ -3,6 +3,7 @@
#include <cassert> #include <cassert>
#include <exception> #include <exception>
#include <iosfwd>
#include <string> #include <string>
#include "webcc/config.h" #include "webcc/config.h"
@ -149,56 +150,49 @@ enum class ContentEncoding {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Client side error codes. // Error (or exception) for the client.
enum Error { class Error {
kNoError = 0, // i.e., OK
kSchemaError, // TODO
kHostResolveError,
kEndpointConnectError,
kHandshakeError, // HTTPS handshake
kSocketReadError,
kSocketWriteError,
// HTTP error.
// E.g., failed to parse HTTP response (invalid content length, etc.).
kHttpError,
// File read/write error.
kFileIOError,
};
// Return a descriptive message for the given error code.
const char* DescribeError(Error error);
class Exception : public std::exception {
public: public:
explicit Exception(Error error, const std::string& details = "", enum Code {
bool timeout = false); kOK = 0,
kSyntaxError,
kResolveError,
kConnectError,
kHandshakeError,
kSocketReadError,
kSocketWriteError,
kParseError,
kFileError,
};
Error error() const { public:
return error_; Error(Code code = kOK, const std::string& message = "")
: code_(code), message_(message), timeout_(false) {
} }
// Note that `noexcept` is required by GCC. Code code() const { return code_; }
const char* what() const WEBCC_NOEXCEPT override{
return msg_.c_str();
}
bool timeout() const { const std::string& message() const { return message_; }
return timeout_;
void Set(Code code, const std::string& message) {
code_ = code;
message_ = message;
} }
private: bool timeout() const { return timeout_; }
Error error_;
std::string msg_; void set_timeout(bool timeout) { timeout_ = timeout; }
// If the error was caused by timeout or not. operator bool() const { return code_ != kOK; }
private:
Code code_;
std::string message_;
bool timeout_; bool timeout_;
}; };
std::ostream& operator<<(std::ostream& os, const Error& error);
} // namespace webcc } // namespace webcc
#endif // WEBCC_GLOBALS_H_ #endif // WEBCC_GLOBALS_H_

@ -81,7 +81,7 @@ void Request::CreateStartLine() {
} }
if (url_.host().empty()) { if (url_.host().empty()) {
throw Exception(kSchemaError, "Invalid request: host is missing."); throw Error{ Error::kSyntaxError, "Host is missing" };
} }
std::string target = "/" + url_.path(); std::string target = "/" + url_.path();

@ -25,7 +25,7 @@ bool RestServiceManager::AddService(RestServicePtr service,
item.url_regex.assign(url, flags); item.url_regex.assign(url, flags);
service_items_.push_back(std::move(item)); service_items_.push_back(std::move(item));
return true; return true;
} catch (std::regex_error& e) { } catch (const std::regex_error& e) {
LOG_ERRO("URL is not a valid regular expression: %s", e.what()); LOG_ERRO("URL is not a valid regular expression: %s", e.what());
return false; return false;
} }

Loading…
Cancel
Save