diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b2869e..dbc83a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,10 +123,10 @@ if(WIN32) # For including CMake generated zconf.h. include_directories(${PROJECT_BINARY_DIR}/third_party/src/zlib) else() - find_package(ZLIB REQUIRED) - if(ZLIB_FOUND) - include_directories(${ZLIB_INCLUDE_DIRS}) - endif() + find_package(ZLIB REQUIRED) + if(ZLIB_FOUND) + include_directories(${ZLIB_INCLUDE_DIRS}) + endif() endif() # SOAP support needs pugixml to parse and create XML. diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a260e69..094c293 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -4,33 +4,33 @@ set(EXAMPLE_LIBS webcc ${Boost_LIBRARIES} "${CMAKE_THREAD_LIBS_INIT}") if(WEBCC_ENABLE_SSL) - set(EXAMPLE_LIBS ${EXAMPLE_LIBS} ${OPENSSL_LIBRARIES}) + set(EXAMPLE_LIBS ${EXAMPLE_LIBS} ${OPENSSL_LIBRARIES}) endif() if(WIN32) - set(EXAMPLE_LIBS ${EXAMPLE_LIBS} zlibstatic crypt32) + set(EXAMPLE_LIBS ${EXAMPLE_LIBS} zlibstatic crypt32) else() - set(EXAMPLE_LIBS ${EXAMPLE_LIBS} ${ZLIB_LIBRARIES}) + set(EXAMPLE_LIBS ${EXAMPLE_LIBS} ${ZLIB_LIBRARIES}) endif() if(UNIX) - # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". - set(EXAMPLE_LIBS ${EXAMPLE_LIBS} ${CMAKE_DL_LIBS}) + # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". + set(EXAMPLE_LIBS ${EXAMPLE_LIBS} ${CMAKE_DL_LIBS}) endif() set(REST_BOOK_SRCS - common/book.cc - common/book.h - common/book_json.cc - common/book_json.h - ) + common/book.cc + common/book.h + common/book_json.cc + common/book_json.h + ) add_executable(http_client http_client.cc) target_link_libraries(http_client ${EXAMPLE_LIBS}) if(WEBCC_ENABLE_SSL) - add_executable(github_client github_client.cc) - target_link_libraries(github_client ${EXAMPLE_LIBS} jsoncpp) + add_executable(github_client github_client.cc) + target_link_libraries(github_client ${EXAMPLE_LIBS} jsoncpp) endif() add_executable(file_upload_client file_upload_client.cc) @@ -46,24 +46,24 @@ add_executable(rest_book_client rest_book_client.cc ${REST_BOOK_SRCS}) target_link_libraries(rest_book_client ${EXAMPLE_LIBS} jsoncpp) if(WEBCC_ENABLE_SOAP) - add_executable(soap_calc_server soap_calc_server.cc) - add_executable(soap_calc_client soap_calc_client.cc) - add_executable(soap_calc_client_parasoft soap_calc_client_parasoft.cc) - - target_link_libraries(soap_calc_server ${EXAMPLE_LIBS} pugixml) - target_link_libraries(soap_calc_client ${EXAMPLE_LIBS} pugixml) - target_link_libraries(soap_calc_client_parasoft ${EXAMPLE_LIBS} pugixml) - - set(SOAP_BOOK_SRCS - common/book.cc - common/book.h - common/book_xml.cc - common/book_xml.h - ) - - add_executable(soap_book_server soap_book_server.cc ${SOAP_BOOK_SRCS}) - add_executable(soap_book_client soap_book_client.cc ${SOAP_BOOK_SRCS}) - - target_link_libraries(soap_book_server ${EXAMPLE_LIBS} pugixml) - target_link_libraries(soap_book_client ${EXAMPLE_LIBS} pugixml) + add_executable(soap_calc_server soap_calc_server.cc) + add_executable(soap_calc_client soap_calc_client.cc) + add_executable(soap_calc_client_parasoft soap_calc_client_parasoft.cc) + + target_link_libraries(soap_calc_server ${EXAMPLE_LIBS} pugixml) + target_link_libraries(soap_calc_client ${EXAMPLE_LIBS} pugixml) + target_link_libraries(soap_calc_client_parasoft ${EXAMPLE_LIBS} pugixml) + + set(SOAP_BOOK_SRCS + common/book.cc + common/book.h + common/book_xml.cc + common/book_xml.h + ) + + add_executable(soap_book_server soap_book_server.cc ${SOAP_BOOK_SRCS}) + add_executable(soap_book_client soap_book_client.cc ${SOAP_BOOK_SRCS}) + + target_link_libraries(soap_book_server ${EXAMPLE_LIBS} pugixml) + target_link_libraries(soap_book_client ${EXAMPLE_LIBS} pugixml) endif() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9be4576..4fb5dca 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -4,18 +4,18 @@ set(TEST_LIBS webcc ${Boost_LIBRARIES} "${CMAKE_THREAD_LIBS_INIT}") if(WEBCC_ENABLE_SSL) - set(TEST_LIBS ${TEST_LIBS} ${OPENSSL_LIBRARIES}) + set(TEST_LIBS ${TEST_LIBS} ${OPENSSL_LIBRARIES}) endif() if(WIN32) - set(TEST_LIBS ${TEST_LIBS} zlibstatic crypt32) + set(TEST_LIBS ${TEST_LIBS} zlibstatic crypt32) else() - set(TEST_LIBS ${TEST_LIBS} ${ZLIB_LIBRARIES}) + set(TEST_LIBS ${TEST_LIBS} ${ZLIB_LIBRARIES}) endif() if(UNIX) - # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". - set(TEST_LIBS ${TEST_LIBS} ${CMAKE_DL_LIBS}) + # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". + set(TEST_LIBS ${TEST_LIBS} ${CMAKE_DL_LIBS}) endif() add_executable(test_logger test_logger.cc) diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index 4e2b963..13138de 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -1,14 +1,32 @@ # Unit test set(UT_SRCS - base64_test.cc - rest_service_manager_test.cc + base64_test.cc + http_parser_test.cc + rest_service_manager_test.cc url_test.cc ) set(UT_TARGET_NAME webcc_unittest) +# Common libraries to link. +set(TEST_LIBS webcc ${Boost_LIBRARIES} "${CMAKE_THREAD_LIBS_INIT}") + +if(WEBCC_ENABLE_SSL) + set(TEST_LIBS ${TEST_LIBS} ${OPENSSL_LIBRARIES}) +endif() + +if(WIN32) + set(TEST_LIBS ${TEST_LIBS} zlibstatic crypt32) +else() + set(TEST_LIBS ${TEST_LIBS} ${ZLIB_LIBRARIES}) +endif() + +if(UNIX) + # Add `-ldl` for Linux to avoid "undefined reference to `dlopen'". + set(TEST_LIBS ${TEST_LIBS} ${CMAKE_DL_LIBS}) +endif() + add_executable(${UT_TARGET_NAME} ${UT_SRCS}) -target_link_libraries(${UT_TARGET_NAME} webcc gtest ${Boost_LIBRARIES}) -target_link_libraries(${UT_TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") +target_link_libraries(${UT_TARGET_NAME} webcc gtest ${TEST_LIBS}) add_test(${UT_TARGET_NAME} ${UT_TARGET_NAME}) diff --git a/unittest/http_parser_test.cc b/unittest/http_parser_test.cc new file mode 100644 index 0000000..8b90913 --- /dev/null +++ b/unittest/http_parser_test.cc @@ -0,0 +1,147 @@ +#include "gtest/gtest.h" + +#include "webcc/http_request.h" +#include "webcc/http_request_parser.h" +#include "webcc/http_response.h" +#include "webcc/http_response_parser.h" + +#include + +// ----------------------------------------------------------------------------- + +// HTTP GET request parser test fixture. +class GetRequestParserTest : public testing::Test { +protected: + GetRequestParserTest() : parser_(&request_) { + } + + void SetUp() override { + payload_ = + "GET /get HTTP/1.1\r\n" + "Accept: application/json\r\n" + "Connection: Close\r\n" + "Host: httpbin.org\r\n\r\n"; + } + + void CheckResult() { + EXPECT_EQ("GET", request_.method()); + EXPECT_EQ("httpbin.org", request_.GetHeader("Host")); + EXPECT_EQ("application/json", request_.GetHeader("Accept")); + EXPECT_EQ("Close", request_.GetHeader("Connection")); + + EXPECT_EQ("", request_.content()); + EXPECT_EQ(0, request_.content_length()); + } + + std::string payload_; + + webcc::HttpRequest request_; + webcc::HttpRequestParser parser_; +}; + +TEST_F(GetRequestParserTest, ParseFullDataOnce) { + bool ok = parser_.Parse(payload_.data(), payload_.size()); + + EXPECT_TRUE(ok); + EXPECT_TRUE(parser_.finished()); + + CheckResult(); +} + +// Parse byte by byte. +TEST_F(GetRequestParserTest, ParseByteWise) { + for (std::size_t i = 0; i < payload_.size(); ++i) { + bool ok = parser_.Parse(payload_.data() + i, 1); + EXPECT_TRUE(ok); + } + + EXPECT_TRUE(parser_.finished()); + + CheckResult(); +} + +// Parse line by line. +TEST_F(GetRequestParserTest, ParseLineWise) { + for (std::size_t i = 0; i < payload_.size();) { + std::size_t j = payload_.find('\n', i); + + if (j != std::string::npos) { + bool ok = parser_.Parse(payload_.data() + i, j - i + 1); + EXPECT_TRUE(ok); + } else { + break; + } + + i = j + 1; + } + + EXPECT_TRUE(parser_.finished()); + + CheckResult(); +} + +// ----------------------------------------------------------------------------- + +// HTTP POST request parser test fixture. +class PostRequestParserTest : public testing::Test { +protected: + PostRequestParserTest() : parser_(&request_) { + } + + void SetUp() override { + data_ = + "{\n" + " 'note': 'Webcc test',\n" + " 'scopes': ['public_repo', 'repo', 'repo:status', 'user']\n" + "}"; + + payload_ = + "POST /authorizations HTTP/1.1\r\n" + "Content-Type: application/json; charset=utf-8\r\n" + "Content-Length: " + std::to_string(data_.size()) + "\r\n" + "Accept: application/json\r\n" + "Connection: Close\r\n" + "Host: api.github.com\r\n\r\n"; + + payload_ += data_; + } + + void CheckResult() { + EXPECT_EQ("POST", request_.method()); + EXPECT_EQ("api.github.com", request_.GetHeader("Host")); + EXPECT_EQ("application/json", request_.GetHeader("Accept")); + EXPECT_EQ("Close", request_.GetHeader("Connection")); + EXPECT_EQ("application/json; charset=utf-8", request_.GetHeader("Content-Type")); + EXPECT_EQ(std::to_string(data_.size()), request_.GetHeader("Content-Length")); + + EXPECT_EQ(data_, request_.content()); + EXPECT_EQ(data_.size(), request_.content_length()); + } + + std::string payload_; + std::string data_; + + webcc::HttpRequest request_; + webcc::HttpRequestParser parser_; +}; + +TEST_F(PostRequestParserTest, ParseFullDataOnce) { + bool ok = parser_.Parse(payload_.data(), payload_.size()); + + EXPECT_TRUE(ok); + EXPECT_TRUE(parser_.finished()); + + CheckResult(); +} + +// Parse byte by byte. +TEST_F(PostRequestParserTest, ParseByteWise) { + for (std::size_t i = 0; i < payload_.size(); ++i) { + bool ok = parser_.Parse(payload_.data() + i, 1); + EXPECT_TRUE(ok); + } + + EXPECT_TRUE(parser_.finished()); + + CheckResult(); +} diff --git a/webcc/http_message.cc b/webcc/http_message.cc index 25c8063..1c8deab 100644 --- a/webcc/http_message.cc +++ b/webcc/http_message.cc @@ -28,9 +28,10 @@ std::ostream& operator<<(std::ostream& os, const HttpMessage& message) { // ----------------------------------------------------------------------------- bool HttpMessage::IsConnectionKeepAlive() const { + using http::headers::kConnection; + bool existed = false; - const std::string& connection = - GetHeader(http::headers::kConnection, &existed); + const std::string& connection = GetHeader(kConnection, &existed); if (!existed) { // Keep-Alive is by default for HTTP/1.1. @@ -45,7 +46,9 @@ bool HttpMessage::IsConnectionKeepAlive() const { } http::ContentEncoding HttpMessage::GetContentEncoding() const { - const std::string& encoding = GetHeader(http::headers::kContentEncoding); + using http::headers::kContentEncoding; + + const std::string& encoding = GetHeader(kContentEncoding); if (encoding == "gzip") { return http::ContentEncoding::kGzip; } @@ -64,11 +67,12 @@ bool HttpMessage::AcceptEncodingGzip() const { // See: https://tools.ietf.org/html/rfc7231#section-3.1.1.1 void HttpMessage::SetContentType(const std::string& media_type, const std::string& charset) { + using http::headers::kContentType; + if (charset.empty()) { - SetHeader(http::headers::kContentType, media_type); + SetHeader(kContentType, media_type); } else { - SetHeader(http::headers::kContentType, - media_type + ";charset=" + charset); + SetHeader(kContentType, media_type + ";charset=" + charset); } } @@ -103,6 +107,18 @@ void HttpMessage::Prepare() { } } +void HttpMessage::CopyPayload(std::ostream& os) const { + for (const boost::asio::const_buffer& b : payload_) { + os.write(static_cast(b.data()), b.size()); + } +} + +void HttpMessage::CopyPayload(std::string* str) const { + std::stringstream ss; + CopyPayload(ss); + *str = ss.str(); +} + void HttpMessage::Dump(std::ostream& os, std::size_t indent, const std::string& prefix) const { std::string indent_str; diff --git a/webcc/http_message.h b/webcc/http_message.h index 5a066d5..ddc6d8c 100644 --- a/webcc/http_message.h +++ b/webcc/http_message.h @@ -88,6 +88,12 @@ public: return payload_; } + // Copy the exact payload to the given output stream. + void CopyPayload(std::ostream& os) const; + + // Copy the exact payload to the given string. + void CopyPayload(std::string* str) const; + // Dump to output stream. void Dump(std::ostream& os, std::size_t indent = 0, const std::string& prefix = "") const; diff --git a/webcc/http_parser.cc b/webcc/http_parser.cc index d8b11eb..49c01c6 100644 --- a/webcc/http_parser.cc +++ b/webcc/http_parser.cc @@ -152,8 +152,8 @@ bool HttpParser::ParseHeaderLine(const std::string& line) { LOG_INFO("Content length: %u.", content_length_); + // Reserve memory to avoid frequent reallocation when append. try { - // Reserve memory to avoid frequent reallocation when append. content_.reserve(content_length_); } catch (const std::exception& e) { LOG_ERRO("Failed to reserve content memory: %s.", e.what());