diff --git a/example/http_hello_async_client/main.cc b/example/http_hello_async_client/main.cc index 8718939..ce60274 100644 --- a/example/http_hello_async_client/main.cc +++ b/example/http_hello_async_client/main.cc @@ -2,8 +2,8 @@ #include "boost/asio/io_context.hpp" -#include "webcc/logger.h" #include "webcc/async_http_client.h" +#include "webcc/logger.h" // In order to test this client, create a file index.html whose content is // simply "Hello, World!", then start a HTTP server with Python 3: @@ -11,13 +11,12 @@ // The default port number should be 8000. void Test(boost::asio::io_context& ioc) { - std::shared_ptr request(new webcc::HttpRequest()); + webcc::HttpRequestPtr request(new webcc::HttpRequest()); request->set_method(webcc::kHttpGet); request->set_url("/index.html"); request->SetHost("localhost", "8000"); - - request->Build(); + request->UpdateStartLine(); webcc::HttpAsyncClientPtr client(new webcc::AsyncHttpClient(ioc)); diff --git a/example/http_hello_client/main.cc b/example/http_hello_client/main.cc index 81d8267..aefba4d 100644 --- a/example/http_hello_client/main.cc +++ b/example/http_hello_client/main.cc @@ -1,7 +1,7 @@ #include -#include "webcc/logger.h" #include "webcc/http_client.h" +#include "webcc/logger.h" // In order to test this client, create a file index.html whose content is // simply "Hello, World!", then start a HTTP server with Python 3: @@ -10,21 +10,21 @@ void Test() { webcc::HttpRequest request; - request.set_method(webcc::kHttpGet); request.set_url("/index.html"); request.SetHost("localhost", "8000"); - - request.Build(); - - webcc::HttpResponse response; + request.UpdateStartLine(); webcc::HttpClient client; - if (!client.Request(request)) { - return; + if (client.Request(request)) { + std::cout << client.response()->content() << std::endl; + } else { + std::cout << webcc::DescribeError(client.error()); + if (client.timed_out()) { + std::cout << " (timed out)"; + } + std::cout << std::endl; } - - std::cout << response.content() << std::endl; } int main() { diff --git a/example/rest_book_async_client/main.cc b/example/rest_book_async_client/main.cc index 7ee1018..fe8c470 100644 --- a/example/rest_book_async_client/main.cc +++ b/example/rest_book_async_client/main.cc @@ -2,8 +2,8 @@ #include "json/json.h" -#include "webcc/logger.h" #include "webcc/async_rest_client.h" +#include "webcc/logger.h" // ----------------------------------------------------------------------------- @@ -118,7 +118,11 @@ int main(int argc, char* argv[]) { if (error == webcc::kNoError) { std::cout << response->content() << std::endl; } else { - std::cout << webcc::DescribeError(error) << std::endl; + std::cout << webcc::DescribeError(error); + if (timed_out) { + std::cout << " (timed out)"; + } + std::cout << std::endl; } }; diff --git a/webcc/async_rest_client.cc b/webcc/async_rest_client.cc index 98d8319..341e9c6 100644 --- a/webcc/async_rest_client.cc +++ b/webcc/async_rest_client.cc @@ -24,7 +24,7 @@ void AsyncRestClient::Request(const std::string& method, request->SetContent(content); } - request->Build(); + request->UpdateStartLine(); HttpAsyncClientPtr http_client(new AsyncHttpClient(io_context_)); diff --git a/webcc/globals.h b/webcc/globals.h index 8098a0a..970c3cb 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -17,6 +17,8 @@ namespace webcc { // ----------------------------------------------------------------------------- // Constants +const char* const CRLF = "\r\n"; + // Default buffer size for socket reading. const std::size_t kBufferSize = 1024; diff --git a/webcc/http_connection.cc b/webcc/http_connection.cc index f8f6120..f563b85 100644 --- a/webcc/http_connection.cc +++ b/webcc/http_connection.cc @@ -24,8 +24,8 @@ void HttpConnection::Start() { } void HttpConnection::Close() { - boost::system::error_code ec; - socket_.close(ec); + boost::system::error_code ignored_ec; + socket_.close(ignored_ec); } void HttpConnection::SetResponseContent(std::string&& content, @@ -36,6 +36,7 @@ void HttpConnection::SetResponseContent(std::string&& content, void HttpConnection::SendResponse(HttpStatus::Enum status) { response_.set_status(status); + response_.UpdateStartLine(); AsyncWrite(); } @@ -58,6 +59,7 @@ void HttpConnection::ReadHandler(boost::system::error_code ec, if (!request_parser_.Parse(buffer_.data(), length)) { // Bad request. + LOG_ERRO("Failed to parse HTTP request."); response_ = HttpResponse::Fault(HttpStatus::kBadRequest); AsyncWrite(); return; @@ -69,13 +71,16 @@ void HttpConnection::ReadHandler(boost::system::error_code ec, return; } + LOG_VERB("HTTP request:\n%s", request_.Dump(4, "> ").c_str()); + // Enqueue this connection. // Some worker thread will handle it later. - // And DoWrite() will be called in the worker thread. request_handler_->Enqueue(shared_from_this()); } void HttpConnection::AsyncWrite() { + LOG_VERB("HTTP response:\n%s", response_.Dump(4, "> ").c_str()); + boost::asio::async_write(socket_, response_.ToBuffers(), std::bind(&HttpConnection::WriteHandler, @@ -86,8 +91,8 @@ void HttpConnection::AsyncWrite() { // NOTE: // This write handler will be called from main thread (the thread calling -// io_context.run), even though DoWrite() is invoked by worker threads. This is -// ensured by Asio. +// io_context.run), even though AsyncWrite() is invoked by worker threads. +// This is ensured by Asio. void HttpConnection::WriteHandler(boost::system::error_code ec, std::size_t length) { if (!ec) { diff --git a/webcc/http_message.cc b/webcc/http_message.cc index 925dd0b..f7cf252 100644 --- a/webcc/http_message.cc +++ b/webcc/http_message.cc @@ -6,6 +6,17 @@ namespace webcc { +// ----------------------------------------------------------------------------- + +namespace misc_strings { + +const char NAME_VALUE_SEPARATOR[] = { ':', ' ' }; +const char CRLF[] = { '\r', '\n' }; + +} // misc_strings + +// ----------------------------------------------------------------------------- + void HttpMessage::SetHeader(const std::string& name, const std::string& value) { for (HttpHeader& h : headers_) { if (h.name == name) { @@ -17,6 +28,30 @@ void HttpMessage::SetHeader(const std::string& name, const std::string& value) { headers_.push_back({ name, value }); } +// ATTENTION: The buffers don't hold the memory! +std::vector HttpMessage::ToBuffers() const { + assert(!start_line_.empty()); + + std::vector buffers; + + buffers.push_back(boost::asio::buffer(start_line_)); + + for (const HttpHeader& h : headers_) { + buffers.push_back(boost::asio::buffer(h.name)); + buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR)); + buffers.push_back(boost::asio::buffer(h.value)); + buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); + } + + buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); + + if (content_length_ > 0) { + buffers.push_back(boost::asio::buffer(content_)); + } + + return buffers; +} + void HttpMessage::Dump(std::ostream& os, std::size_t indent, const std::string& prefix) const { std::string indent_str; @@ -38,7 +73,7 @@ void HttpMessage::Dump(std::ostream& os, std::size_t indent, os << content_ << std::endl; } else { std::vector splitted; - boost::split(splitted, content_, boost::is_any_of("\r\n")); + boost::split(splitted, content_, boost::is_any_of(CRLF)); for (const std::string& line : splitted) { os << indent_str << line << std::endl; } diff --git a/webcc/http_message.h b/webcc/http_message.h index d2f78a1..c2ae305 100644 --- a/webcc/http_message.h +++ b/webcc/http_message.h @@ -6,6 +6,8 @@ #include // for move() #include +#include "boost/asio/buffer.hpp" // for const_buffer + #include "webcc/globals.h" namespace webcc { @@ -18,6 +20,8 @@ struct HttpHeader { // Base class for HTTP request and response messages. class HttpMessage { public: + HttpMessage() : content_length_(kInvalidLength) {} + virtual ~HttpMessage() = default; const std::string& start_line() const { return start_line_; } @@ -47,6 +51,15 @@ class HttpMessage { SetContentLength(content_.size()); } + // Set start line according to other informations. + // Must be called before ToBuffers()! + virtual void UpdateStartLine() = 0; + + // Convert the message into a vector of buffers. The buffers do not own the + // underlying memory blocks, therefore the message object must remain valid + // and not be changed until the write operation has completed. + std::vector ToBuffers() const; + // Dump to output stream. void Dump(std::ostream& os, std::size_t indent = 0, const std::string& prefix = "") const; @@ -61,10 +74,10 @@ class HttpMessage { SetHeader(kContentLength, std::to_string(content_length)); } - // Start line with trailing "\r\n". + // Start line with trailing CRLF. std::string start_line_; - std::size_t content_length_ = kInvalidLength; + std::size_t content_length_; std::vector headers_; diff --git a/webcc/http_parser.cc b/webcc/http_parser.cc index 990d64a..12bbdac 100644 --- a/webcc/http_parser.cc +++ b/webcc/http_parser.cc @@ -33,7 +33,7 @@ bool HttpParser::Parse(const char* data, std::size_t length) { std::size_t off = 0; while (true) { - std::size_t pos = pending_data_.find("\r\n", off); + std::size_t pos = pending_data_.find(CRLF, off); if (pos == std::string::npos) { break; } @@ -48,6 +48,7 @@ bool HttpParser::Parse(const char* data, std::size_t length) { if (!start_line_parsed_) { start_line_parsed_ = true; + message_->set_start_line(line + CRLF); if (!ParseStartLine(line)) { return false; } @@ -111,7 +112,7 @@ void HttpParser::ParseContentLength(const std::string& line) { try { content_length_ = static_cast(std::stoul(value)); - } catch (const std::exception& e) { + } catch (const std::exception&) { LOG_ERRO("Invalid content length: %s.", value.c_str()); } diff --git a/webcc/http_request.cc b/webcc/http_request.cc index b235245..cdfd017 100644 --- a/webcc/http_request.cc +++ b/webcc/http_request.cc @@ -2,17 +2,6 @@ namespace webcc { -// ----------------------------------------------------------------------------- - -namespace misc_strings { - -const char NAME_VALUE_SEPARATOR[] = { ':', ' ' }; -const char CRLF[] = { '\r', '\n' }; - -} // misc_strings - -// ----------------------------------------------------------------------------- - void HttpRequest::SetHost(const std::string& host, const std::string& port) { host_ = host; port_ = port; @@ -24,37 +13,12 @@ void HttpRequest::SetHost(const std::string& host, const std::string& port) { } } -void HttpRequest::Build() { - if (start_line_.empty()) { - start_line_ = method_; - start_line_ += " "; - start_line_ += url_; - start_line_ += " HTTP/1.1\r\n"; - } -} - -// ATTENTION: The buffers don't hold the memory! -std::vector HttpRequest::ToBuffers() const { - assert(!start_line_.empty()); - - std::vector buffers; - - buffers.push_back(boost::asio::buffer(start_line_)); - - for (const HttpHeader& h : headers_) { - buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR)); - buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); - } - - buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); - - if (content_length_ > 0) { - buffers.push_back(boost::asio::buffer(content_)); - } - - return buffers; +void HttpRequest::UpdateStartLine() { + start_line_ = method_; + start_line_ += " "; + start_line_ += url_; + start_line_ += " HTTP/1.1"; + start_line_ += CRLF; } } // namespace webcc diff --git a/webcc/http_request.h b/webcc/http_request.h index 1ed49ed..7642783 100644 --- a/webcc/http_request.h +++ b/webcc/http_request.h @@ -3,9 +3,6 @@ #include #include -#include - -#include "boost/asio/buffer.hpp" // for const_buffer #include "webcc/http_message.h" @@ -14,9 +11,6 @@ namespace webcc { class HttpRequest : public HttpMessage { public: HttpRequest() = default; - HttpRequest(const HttpRequest&) = default; - HttpRequest& operator=(const HttpRequest&) = default; - virtual ~HttpRequest() = default; const std::string& method() const { return method_; } @@ -33,14 +27,8 @@ class HttpRequest : public HttpMessage { // a numeric number (e.g., "9000") and "80" will be used if it's empty. void SetHost(const std::string& host, const std::string& port); - // Compose start line, etc. - // Must be called before ToBuffers()! - void Build(); - - // Convert the response into a vector of buffers. The buffers do not own the - // underlying memory blocks, therefore the request object must remain valid - // and not be changed until the write operation has completed. - std::vector ToBuffers() const; + // Set start line according to HTTP method, URL, etc. + void UpdateStartLine() override; private: // HTTP method. diff --git a/webcc/http_response.cc b/webcc/http_response.cc index 4914803..64e561f 100644 --- a/webcc/http_response.cc +++ b/webcc/http_response.cc @@ -16,75 +16,47 @@ const std::string INTERNAL_SERVER_ERROR = const std::string NOT_IMPLEMENTED = "HTTP/1.1 501 Not Implemented\r\n"; const std::string SERVICE_UNAVAILABLE = "HTTP/1.1 503 Service Unavailable\r\n"; -boost::asio::const_buffer ToBuffer(int status) { +const std::string& ToString(int status) { switch (status) { case HttpStatus::kOK: - return boost::asio::buffer(OK); + return OK; case HttpStatus::kCreated: - return boost::asio::buffer(CREATED); + return CREATED; case HttpStatus::kAccepted: - return boost::asio::buffer(ACCEPTED); + return ACCEPTED; case HttpStatus::kNoContent: - return boost::asio::buffer(NO_CONTENT); + return NO_CONTENT; case HttpStatus::kNotModified: - return boost::asio::buffer(NOT_MODIFIED); + return NOT_MODIFIED; case HttpStatus::kBadRequest: - return boost::asio::buffer(BAD_REQUEST); + return BAD_REQUEST; case HttpStatus::kNotFound: - return boost::asio::buffer(NOT_FOUND); + return NOT_FOUND; case HttpStatus::InternalServerError: - return boost::asio::buffer(INTERNAL_SERVER_ERROR); + return INTERNAL_SERVER_ERROR; case HttpStatus::kNotImplemented: - return boost::asio::buffer(NOT_IMPLEMENTED); + return NOT_IMPLEMENTED; case HttpStatus::kServiceUnavailable: - return boost::asio::buffer(SERVICE_UNAVAILABLE); + return SERVICE_UNAVAILABLE; default: - return boost::asio::buffer(NOT_IMPLEMENTED); + return NOT_IMPLEMENTED; } } } // namespace status_strings -namespace misc_strings { - -const char NAME_VALUE_SEPARATOR[] = { ':', ' ' }; -const char CRLF[] = { '\r', '\n' }; - -} // misc_strings - -// ATTENTION: The buffers don't hold the memory! -std::vector HttpResponse::ToBuffers() const { - std::vector buffers; - - // Status line - buffers.push_back(status_strings::ToBuffer(status_)); - - // Header fields (optional) - for (const HttpHeader& h : headers_) { - buffers.push_back(boost::asio::buffer(h.name)); - buffers.push_back(boost::asio::buffer(misc_strings::NAME_VALUE_SEPARATOR)); - buffers.push_back(boost::asio::buffer(h.value)); - buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); - } - - buffers.push_back(boost::asio::buffer(misc_strings::CRLF)); - - // Content (optional) - if (!content_.empty()) { - buffers.push_back(boost::asio::buffer(content_)); - } - - return buffers; +void HttpResponse::UpdateStartLine() { + start_line_ = status_strings::ToString(status_); } HttpResponse HttpResponse::Fault(HttpStatus::Enum status) { @@ -92,6 +64,7 @@ HttpResponse HttpResponse::Fault(HttpStatus::Enum status) { HttpResponse response; response.set_status(status); + response.UpdateStartLine(); return response; } diff --git a/webcc/http_response.h b/webcc/http_response.h index 8f94e44..a335630 100644 --- a/webcc/http_response.h +++ b/webcc/http_response.h @@ -3,9 +3,6 @@ #include #include -#include - -#include "boost/asio/buffer.hpp" // for const_buffer #include "webcc/http_message.h" @@ -13,20 +10,19 @@ namespace webcc { class HttpResponse : public HttpMessage { public: - HttpResponse() : status_(HttpStatus::kOK) { - } + HttpResponse() : status_(HttpStatus::kOK) {} ~HttpResponse() override = default; int status() const { return status_; } + void set_status(int status) { status_ = status; } - // Convert the response into a vector of buffers. The buffers do not own the - // underlying memory blocks, therefore the response object must remain valid - // and not be changed until the write operation has completed. - std::vector ToBuffers() const; + // Set start line according to status code. + void UpdateStartLine() override; // Get a fault response when HTTP status is not OK. + // TODO: Avoid copy. static HttpResponse Fault(HttpStatus::Enum status); private: diff --git a/webcc/http_response_parser.cc b/webcc/http_response_parser.cc index f79083a..ff38ee3 100644 --- a/webcc/http_response_parser.cc +++ b/webcc/http_response_parser.cc @@ -10,8 +10,6 @@ HttpResponseParser::HttpResponseParser(HttpResponse* response) } bool HttpResponseParser::ParseStartLine(const std::string& line) { - response_->set_start_line(line + "\r\n"); - std::size_t off = 0; std::size_t pos = line.find(' '); diff --git a/webcc/rest_client.cc b/webcc/rest_client.cc index 466400f..aa96ec8 100644 --- a/webcc/rest_client.cc +++ b/webcc/rest_client.cc @@ -31,7 +31,7 @@ bool RestClient::Request(const std::string& method, request.SetContent(content); } - request.Build(); + request.UpdateStartLine(); HttpClient http_client; diff --git a/webcc/soap_client.cc b/webcc/soap_client.cc index db32bd3..33b8e12 100644 --- a/webcc/soap_client.cc +++ b/webcc/soap_client.cc @@ -44,7 +44,7 @@ Error SoapClient::Call(const std::string& operation, http_request.SetContent(std::move(http_content)); http_request.SetHost(host_, port_); http_request.SetHeader(kSoapAction, operation); - http_request.Build(); + http_request.UpdateStartLine(); HttpResponse http_response;