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.
std::cout << r->content() << std::endl;
} catch (const webcc::Exception& e) {
std::cout << e.what() << std::endl;
} catch (const webcc::Error& error) {
std::cout << error << std::endl;
}
return 0;

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

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

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

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

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

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

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

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

@ -29,7 +29,7 @@ class Client {
public:
Client();
virtual ~Client() = default;
~Client() = default;
Client(const Client&) = delete;
Client& operator=(const Client&) = delete;
@ -52,7 +52,7 @@ public:
}
// 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.
void Close();
@ -61,20 +61,18 @@ public:
bool closed() const { return closed_; }
bool timed_out() const { return timed_out_; }
Error error() const { return error_; }
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 OnTimer(boost::system::error_code ec);
@ -113,10 +111,6 @@ private:
// Deadline timer canceled.
bool timer_canceled_;
// Timeout occurred.
bool timed_out_;
// Error code.
Error error_;
};

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

@ -204,7 +204,7 @@ FormPart::FormPart(const std::string& name, const Path& path,
const std::string& media_type)
: name_(name), media_type_(media_type) {
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.

@ -1,9 +1,8 @@
#include "webcc/globals.h"
#include <iostream>
#include <map>
#include "webcc/version.h"
namespace webcc {
// -----------------------------------------------------------------------------
@ -47,37 +46,13 @@ std::string FromExtension(const std::string& extension,
// -----------------------------------------------------------------------------
const char* DescribeError(Error error) {
switch (error) {
case kSchemaError:
return "Schema error";
case kHostResolveError:
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)";
std::ostream& operator<<(std::ostream& os, const Error& error) {
os << std::to_string(static_cast<int>(error.code()));
os << ": " << error.message();
if (error.timeout()) {
os << " (timeout)";
}
return os;
}
} // namespace webcc

@ -3,6 +3,7 @@
#include <cassert>
#include <exception>
#include <iosfwd>
#include <string>
#include "webcc/config.h"
@ -149,56 +150,49 @@ enum class ContentEncoding {
// -----------------------------------------------------------------------------
// Client side error codes.
enum 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 {
// Error (or exception) for the client.
class Error {
public:
explicit Exception(Error error, const std::string& details = "",
bool timeout = false);
enum Code {
kOK = 0,
kSyntaxError,
kResolveError,
kConnectError,
kHandshakeError,
kSocketReadError,
kSocketWriteError,
kParseError,
kFileError,
};
Error error() const {
return error_;
public:
Error(Code code = kOK, const std::string& message = "")
: code_(code), message_(message), timeout_(false) {
}
// Note that `noexcept` is required by GCC.
const char* what() const WEBCC_NOEXCEPT override{
return msg_.c_str();
}
Code code() const { return code_; }
bool timeout() const {
return timeout_;
const std::string& message() const { return message_; }
void Set(Code code, const std::string& message) {
code_ = code;
message_ = message;
}
private:
Error error_;
bool timeout() const { return timeout_; }
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_;
};
std::ostream& operator<<(std::ostream& os, const Error& error);
} // namespace webcc
#endif // WEBCC_GLOBALS_H_

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

@ -25,7 +25,7 @@ bool RestServiceManager::AddService(RestServicePtr service,
item.url_regex.assign(url, flags);
service_items_.push_back(std::move(item));
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());
return false;
}

Loading…
Cancel
Save