diff --git a/README.md b/README.md index 7db566b..562e374 100644 --- a/README.md +++ b/README.md @@ -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; diff --git a/autotest/client_autotest.cc b/autotest/client_autotest.cc index ce44193..b884036 100644 --- a/autotest/client_autotest.cc +++ b/autotest/client_autotest.cc @@ -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; } } diff --git a/examples/client_basics.cc b/examples/client_basics.cc index d5a6fb7..ac1a2e8 100644 --- a/examples/client_basics.cc +++ b/examples/client_basics.cc @@ -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; diff --git a/examples/file_upload_client.cc b/examples/file_upload_client.cc index 3380ef0..5cb37f8 100644 --- a/examples/file_upload_client.cc +++ b/examples/file_upload_client.cc @@ -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; diff --git a/examples/file_upload_server.cc b/examples/file_upload_server.cc index f62cb10..5c090d9 100644 --- a/examples/file_upload_server.cc +++ b/examples/file_upload_server.cc @@ -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; } diff --git a/examples/github_client.cc b/examples/github_client.cc index 768a99b..44d648f 100644 --- a/examples/github_client.cc +++ b/examples/github_client.cc @@ -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; } } diff --git a/examples/rest_book_client.cc b/examples/rest_book_client.cc index f764752..2706495 100644 --- a/examples/rest_book_client.cc +++ b/examples/rest_book_client.cc @@ -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; } } diff --git a/examples/rest_book_server.cc b/examples/rest_book_server.cc index 1953a89..b9f5575 100644 --- a/examples/rest_book_server.cc +++ b/examples/rest_book_server.cc @@ -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; } diff --git a/webcc/client.cc b/webcc/client.cc index ce4cbc5..5c42eee 100644 --- a/webcc/client.cc +++ b/webcc/client.cc @@ -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; } diff --git a/webcc/client.h b/webcc/client.h index c1aa1bb..93ab52e 100644 --- a/webcc/client.h +++ b/webcc/client.h @@ -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_; }; diff --git a/webcc/client_session.cc b/webcc/client_session.cc index 5e5fc05..90efed9 100644 --- a/webcc/client_session.cc +++ b/webcc/client_session.cc @@ -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. diff --git a/webcc/common.cc b/webcc/common.cc index f1e23af..324413b 100644 --- a/webcc/common.cc +++ b/webcc/common.cc @@ -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. diff --git a/webcc/globals.cc b/webcc/globals.cc index 7402187..e6cb12a 100644 --- a/webcc/globals.cc +++ b/webcc/globals.cc @@ -1,9 +1,8 @@ #include "webcc/globals.h" +#include #include -#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(error.code())); + os << ": " << error.message(); + if (error.timeout()) { + os << " (timeout)"; } + return os; } } // namespace webcc diff --git a/webcc/globals.h b/webcc/globals.h index 4e0e14b..aee6ccc 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -3,6 +3,7 @@ #include #include +#include #include #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_ diff --git a/webcc/request.cc b/webcc/request.cc index bb143bc..3a735b2 100644 --- a/webcc/request.cc +++ b/webcc/request.cc @@ -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(); diff --git a/webcc/rest_service_manager.cc b/webcc/rest_service_manager.cc index 76229d3..aac4a29 100644 --- a/webcc/rest_service_manager.cc +++ b/webcc/rest_service_manager.cc @@ -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; }