diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a1810b..4810b04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,7 +31,8 @@ set(WEBCC_ENABLE_GZIP 0 CACHE STRING "Enable gzip compression (need Zlib)? (1:Ye set(WEBCC_LOG_LEVEL 2 CACHE STRING "Log level (0:VERB, 1:INFO, 2:USER, 3:WARN or 4:ERRO)") -set(WEBCC_USE_STD_FILESYSTEM 1 CACHE STRING "Use C++17 filesystem? (1:Yes, 0:No)") +set(WEBCC_USE_STD_FILESYSTEM 1 CACHE STRING "Use std::filesystem? (1:Yes, 0:No)") +set(WEBCC_USE_STD_STRING_VIEW 1 CACHE STRING "Use std::string_view? (1:Yes, 0:No)") if(BUILD_UNITTEST) enable_testing() @@ -45,7 +46,7 @@ endif() # C++ standard requirements. -if(WEBCC_USE_STD_FILESYSTEM) +if(WEBCC_USE_STD_FILESYSTEM OR WEBCC_USE_STD_STRING_VIEW) set(CMAKE_CXX_STANDARD 17) else() set(CMAKE_CXX_STANDARD 11) diff --git a/autotest/client_autotest/client_autotest.cc b/autotest/client_autotest/client_autotest.cc index 400f8e7..4c58dd4 100644 --- a/autotest/client_autotest/client_autotest.cc +++ b/autotest/client_autotest/client_autotest.cc @@ -20,7 +20,7 @@ static Json::Value StringToJson(const std::string& str) { Json::Value json; Json::CharReaderBuilder builder; - std::stringstream stream(str); + std::istringstream stream{ str }; std::string errors; if (!Json::parseFromStream(builder, stream, &json, &errors)) { std::cerr << errors << std::endl; diff --git a/examples/book_client/book_json.cc b/examples/book_client/book_json.cc index e8213b7..8481432 100644 --- a/examples/book_client/book_json.cc +++ b/examples/book_client/book_json.cc @@ -16,7 +16,7 @@ Json::Value StringToJson(const std::string& str) { Json::Value json; Json::CharReaderBuilder builder; - std::stringstream stream(str); + std::istringstream stream{ str }; std::string errs; if (!Json::parseFromStream(builder, stream, &json, &errs)) { std::cerr << errs << std::endl; diff --git a/examples/book_server/book_json.cc b/examples/book_server/book_json.cc index 206983e..d20e111 100644 --- a/examples/book_server/book_json.cc +++ b/examples/book_server/book_json.cc @@ -16,7 +16,7 @@ Json::Value StringToJson(const std::string& str) { Json::Value json; Json::CharReaderBuilder builder; - std::stringstream stream(str); + std::istringstream stream{ str }; std::string errs; if (!Json::parseFromStream(builder, stream, &json, &errs)) { std::cerr << errs << std::endl; diff --git a/examples/book_server/main.cc b/examples/book_server/main.cc index fe07d0a..792d12b 100644 --- a/examples/book_server/main.cc +++ b/examples/book_server/main.cc @@ -39,11 +39,11 @@ int main(int argc, char* argv[]) { std::make_shared(), { "GET", "POST" }); - server.Route(webcc::R("/books/(\\d+)"), + server.Route(webcc::R{ "/books/(\\d+)" }, std::make_shared(photo_dir), { "GET", "PUT", "DELETE" }); - server.Route(webcc::R("/books/(\\d+)/photo"), + server.Route(webcc::R{ "/books/(\\d+)/photo" }, std::make_shared(photo_dir), { "GET", "PUT", "DELETE" }); diff --git a/examples/form_client.cc b/examples/form_client.cc index 614a0c6..a0655bd 100644 --- a/examples/form_client.cc +++ b/examples/form_client.cc @@ -17,7 +17,7 @@ int main(int argc, char* argv[]) { std::cout << " $ ./form_client path/to/webcc/data/upload" << std::endl; std::cout << " $ ./form_client path/to/webcc/data/upload " << "http://httpbin.org/post" << std::endl; - std::cout << "(Post the example 'form_server')" << std::endl; + std::cout << "(Post to the example 'form_server')" << std::endl; std::cout << " $ ./form_client path/to/webcc/data/upload " "http://localhost:8080/upload" << std::endl; @@ -43,8 +43,7 @@ int main(int argc, char* argv[]) { webcc::ClientSession session; try { - auto r = session.Send(webcc::RequestBuilder{} - .Post(url) + auto r = session.Send(WEBCC_POST(url) .FormFile("file", upload_dir / "remember.txt") .FormData("json", "{}", "application/json")()); diff --git a/examples/github_client.cc b/examples/github_client.cc index e2ca213..b04d493 100644 --- a/examples/github_client.cc +++ b/examples/github_client.cc @@ -20,7 +20,7 @@ Json::Value StringToJson(const std::string& str) { Json::Value json; Json::CharReaderBuilder builder; - std::stringstream stream(str); + std::istringstream stream{ str }; std::string errors; if (!Json::parseFromStream(builder, stream, &json, &errors)) { std::cerr << errors << std::endl; diff --git a/unittest/router_unittest.cc b/unittest/router_unittest.cc index bc7dab5..ddc8710 100644 --- a/unittest/router_unittest.cc +++ b/unittest/router_unittest.cc @@ -17,8 +17,7 @@ public: TEST(RouterTest, URL_RegexBasic) { webcc::Router router; - router.Route(webcc::R("/instance/(\\d+)"), - std::make_shared()); + router.Route(webcc::R{ "/instance/(\\d+)" }, std::make_shared()); std::string url = "/instance/12345"; webcc::UrlArgs args; @@ -40,7 +39,7 @@ TEST(RouterTest, URL_RegexBasic) { TEST(RouterTest, URL_RegexMultiple) { webcc::Router router; - router.Route(webcc::R("/study/(\\d+)/series/(\\d+)/instance/(\\d+)"), + router.Route(webcc::R{ "/study/(\\d+)/series/(\\d+)/instance/(\\d+)" }, std::make_shared()); std::string url = "/study/1/series/2/instance/3"; diff --git a/unittest/string_unittest.cc b/unittest/string_unittest.cc index f54ca5a..f8652f0 100644 --- a/unittest/string_unittest.cc +++ b/unittest/string_unittest.cc @@ -6,8 +6,36 @@ #include "webcc/string.h" +TEST(StringTest, Trim) { + std::string str = " trim me "; + webcc::string_view sv = str; + webcc::Trim(sv); + EXPECT_EQ("trim me", sv); +} + +TEST(StringTest, Trim_Left) { + std::string str = " trim me"; + webcc::string_view sv = str; + webcc::Trim(sv); + EXPECT_EQ("trim me", sv); +} + +TEST(StringTest, Trim_Right) { + std::string str = "trim me "; + webcc::string_view sv = str; + webcc::Trim(sv); + EXPECT_EQ("trim me", sv); +} + +TEST(StringTest, Trim_Empty) { + std::string str = ""; + webcc::string_view sv = str; + webcc::Trim(sv); + EXPECT_EQ("", sv); +} + TEST(StringTest, Split) { - std::vector parts; + std::vector parts; webcc::Split("GET /path/to HTTP/1.1", ' ', false, &parts); EXPECT_EQ(3, parts.size()); @@ -18,12 +46,11 @@ TEST(StringTest, Split) { TEST(StringTest, Split_TokenCompressOff) { std::string str = "one,two,,three,,"; - std::vector parts; + std::vector parts; // Same as: // boost::split(parts, str, boost::is_any_of(","), // boost::token_compress_off); - webcc::Split(str, ',', false, &parts); EXPECT_EQ(6, parts.size()); @@ -37,7 +64,7 @@ TEST(StringTest, Split_TokenCompressOff) { TEST(StringTest, Split_TokenCompressOn) { std::string str = "one,two,,three,,"; - std::vector parts; + std::vector parts; // Same as: // boost::split(parts, str, boost::is_any_of(","), @@ -51,8 +78,28 @@ TEST(StringTest, Split_TokenCompressOn) { EXPECT_EQ("", parts[3]); } +TEST(StringTest, Split_TokensOnly) { + std::string str = ",,,,,"; + std::vector parts; + + // Token compress on + webcc::Split(str, ',', true, &parts); + EXPECT_EQ(2, parts.size()); + EXPECT_EQ("", parts[0]); + EXPECT_EQ("", parts[1]); + + parts.clear(); + + // Token compress off + webcc::Split(str, ',', false, &parts); + EXPECT_EQ(6, parts.size()); + EXPECT_EQ("", parts[0]); + EXPECT_EQ("", parts[1]); + EXPECT_EQ("", parts[5]); +} + TEST(StringTest, Split_NewLine) { - std::vector lines; + std::vector lines; webcc::Split("line one\nline two\nline 3", '\n', false, &lines); EXPECT_EQ(3, lines.size()); @@ -64,8 +111,8 @@ TEST(StringTest, Split_NewLine) { TEST(StringTest, SplitKV) { const std::string str = "key=value"; - std::string key; - std::string value; + webcc::string_view key; + webcc::string_view value; bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_EQ(true, ok); @@ -76,8 +123,8 @@ TEST(StringTest, SplitKV) { TEST(StringTest, SplitKV_OtherDelim) { const std::string str = "key:value"; - std::string key; - std::string value; + webcc::string_view key; + webcc::string_view value; bool ok = webcc::SplitKV(str, ':', true, &key, &value); EXPECT_TRUE(ok); @@ -89,8 +136,8 @@ TEST(StringTest, SplitKV_OtherDelim) { TEST(StringTest, SplitKV_Spaces) { const std::string str = " key = value "; - std::string key; - std::string value; + webcc::string_view key; + webcc::string_view value; bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); @@ -102,8 +149,8 @@ TEST(StringTest, SplitKV_Spaces) { TEST(StringTest, SplitKV_SpacesNoTrim) { const std::string str = " key = value "; - std::string key; - std::string value; + webcc::string_view key; + webcc::string_view value; bool ok = webcc::SplitKV(str, '=', false, &key, &value); EXPECT_TRUE(ok); @@ -115,8 +162,8 @@ TEST(StringTest, SplitKV_SpacesNoTrim) { TEST(StringTest, SplitKV_NoKey) { const std::string str = "=value"; - std::string key; - std::string value; + webcc::string_view key; + webcc::string_view value; bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); @@ -128,8 +175,8 @@ TEST(StringTest, SplitKV_NoKey) { TEST(StringTest, SplitKV_NoValue) { const std::string str = "key="; - std::string key; - std::string value; + webcc::string_view key; + webcc::string_view value; bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); @@ -141,8 +188,8 @@ TEST(StringTest, SplitKV_NoValue) { TEST(StringTest, SplitKV_NoKeyNoValue) { const std::string str = "="; - std::string key; - std::string value; + webcc::string_view key; + webcc::string_view value; bool ok = webcc::SplitKV(str, '=', true, &key, &value); EXPECT_TRUE(ok); diff --git a/webcc/client.cc b/webcc/client.cc index 19a1e96..93ae0a0 100644 --- a/webcc/client.cc +++ b/webcc/client.cc @@ -114,10 +114,10 @@ void Client::AsyncConnect() { } } -void Client::AsyncResolve(const std::string& default_port) { +void Client::AsyncResolve(string_view default_port) { std::string port = request_->port(); if (port.empty()) { - port = default_port; + port = ToString(default_port); } LOG_VERB("Resolve host (%s)", request_->host().c_str()); diff --git a/webcc/client.h b/webcc/client.h index 8481aa3..4da2e16 100644 --- a/webcc/client.h +++ b/webcc/client.h @@ -89,7 +89,7 @@ private: void AsyncConnect(); - void AsyncResolve(const std::string& default_port); + void AsyncResolve(string_view default_port); void OnResolve(boost::system::error_code ec, boost::asio::ip::tcp::resolver::results_type endpoints); diff --git a/webcc/client_session.cc b/webcc/client_session.cc index d04ebe9..bddd2a1 100644 --- a/webcc/client_session.cc +++ b/webcc/client_session.cc @@ -128,7 +128,7 @@ void ClientSession::Stop() { started_ = false; } -void ClientSession::Accept(const std::string& content_types) { +void ClientSession::Accept(string_view content_types) { if (!content_types.empty()) { headers_.Set(headers::kAccept, content_types); } @@ -168,18 +168,18 @@ void ClientSession::AcceptGzip(bool gzip) { #endif // WEBCC_ENABLE_GZIP -void ClientSession::Auth(const std::string& type, - const std::string& credentials) { - headers_.Set(headers::kAuthorization, type + " " + credentials); +void ClientSession::Auth(string_view type, string_view credentials) { + headers_.Set(headers::kAuthorization, + ToString(type) + " " + ToString(credentials)); } -void ClientSession::AuthBasic(const std::string& login, - const std::string& password) { - auto credentials = Base64Encode(login + ":" + password); +void ClientSession::AuthBasic(string_view login, string_view password) { + auto credentials = + Base64Encode(ToString(login) + ":" + ToString(password)); return Auth("Basic", credentials); } -void ClientSession::AuthToken(const std::string& token) { +void ClientSession::AuthToken(string_view token) { return Auth("Token", token); } diff --git a/webcc/client_session.h b/webcc/client_session.h index ce8e5f6..3fca3a1 100644 --- a/webcc/client_session.h +++ b/webcc/client_session.h @@ -52,7 +52,7 @@ public: buffer_size_ = buffer_size; } - void SetHeader(const std::string& key, const std::string& value) { + void SetHeader(string_view key, string_view value) { headers_.Set(key, value); } @@ -60,14 +60,13 @@ public: // Only applied when: // - the request to send has no `Content-Type` header, and // - the request has a body. - void SetContentType(const std::string& media_type, - const std::string& charset = "") { - media_type_ = media_type; - charset_ = charset; + void SetContentType(string_view media_type, string_view charset = "") { + media_type_ = ToString(media_type); + charset_ = ToString(charset); } // Set content types to accept. - void Accept(const std::string& content_types); + void Accept(string_view content_types); #if WEBCC_ENABLE_GZIP @@ -77,13 +76,13 @@ public: #endif // WEBCC_ENABLE_GZIP // Set authorization. - void Auth(const std::string& type, const std::string& credentials); + void Auth(string_view type, string_view credentials); // Set Basic authorization. - void AuthBasic(const std::string& login, const std::string& password); + void AuthBasic(string_view login, string_view password); // Set Token authorization. - void AuthToken(const std::string& token); + void AuthToken(string_view token); // Send a request. // Please use RequestBuilder to build the request. diff --git a/webcc/common.cc b/webcc/common.cc index 570c995..ab52254 100644 --- a/webcc/common.cc +++ b/webcc/common.cc @@ -12,41 +12,26 @@ namespace webcc { // ----------------------------------------------------------------------------- -bool Headers::Set(const std::string& key, const std::string& value) { +bool Headers::Set(string_view key, string_view value) { if (value.empty()) { return false; } auto it = Find(key); if (it != headers_.end()) { - it->second = value; + it->second = ToString(value); } else { - headers_.push_back({ key, value }); + headers_.push_back({ ToString(key), ToString(value) }); } return true; } -bool Headers::Set(std::string&& key, std::string&& value) { - if (value.empty()) { - return false; - } - - auto it = Find(key); - if (it != headers_.end()) { - it->second = std::move(value); - } else { - headers_.push_back({ std::move(key), std::move(value) }); - } - - return true; -} - -bool Headers::Has(const std::string& key) const { +bool Headers::Has(string_view key) const { return const_cast(this)->Find(key) != headers_.end(); } -const std::string& Headers::Get(const std::string& key, bool* existed) const { +const std::string& Headers::Get(string_view key, bool* existed) const { auto it = const_cast(this)->Find(key); if (existed != nullptr) { @@ -61,7 +46,7 @@ const std::string& Headers::Get(const std::string& key, bool* existed) const { return s_no_value; } -std::vector
::iterator Headers::Find(const std::string& key) { +std::vector
::iterator Headers::Find(string_view key) { auto it = headers_.begin(); for (; it != headers_.end(); ++it) { if (boost::iequals(it->first, key)) { @@ -73,25 +58,23 @@ std::vector
::iterator Headers::Find(const std::string& key) { // ----------------------------------------------------------------------------- -static bool ParseValue(const std::string& str, const std::string& expected_key, - std::string* value) { - std::string key; +static bool ParseValue(const std::string& str, const char* expected_key, + string_view* value) { + string_view key; if (!SplitKV(str, '=', true, &key, value)) { return false; } - if (key != expected_key) { return false; } - return !value->empty(); } -ContentType::ContentType(const std::string& str) { +ContentType::ContentType(string_view str) { Init(str); } -void ContentType::Parse(const std::string& str) { +void ContentType::Parse(string_view str) { Reset(); Init(str); } @@ -114,15 +97,15 @@ bool ContentType::Valid() const { return true; } -void ContentType::Init(const std::string& str) { +void ContentType::Init(string_view str) { std::string other; std::size_t pos = str.find(';'); - if (pos == std::string::npos) { - media_type_ = str; + if (pos == str.npos) { + media_type_ = ToString(str); } else { - media_type_ = str.substr(0, pos); - other = str.substr(pos + 1); + media_type_ = ToString(str.substr(0, pos)); + other = ToString(str.substr(pos + 1)); } boost::trim(media_type_); @@ -130,26 +113,31 @@ void ContentType::Init(const std::string& str) { if (media_type_ == "multipart/form-data") { multipart_ = true; - if (!ParseValue(other, "boundary", &additional_)) { - LOG_ERRO("Invalid 'multipart/form-data' content-type (no boundary)."); + string_view boundary; + if (ParseValue(other, "boundary", &boundary)) { + additional_ = ToString(boundary); + LOG_INFO("Content-type multipart boundary: %s", additional_.c_str()); } else { - LOG_INFO("Content-type multipart boundary: %s.", additional_.c_str()); + LOG_ERRO("Invalid 'multipart/form-data' content-type (no boundary)"); } } else { - if (ParseValue(other, "charset", &additional_)) { - LOG_INFO("Content-type charset: %s.", additional_.c_str()); + string_view charset; + if (ParseValue(other, "charset", &charset)) { + additional_ = ToString(charset); + LOG_INFO("Content-type charset: %s", additional_.c_str()); } } } // ----------------------------------------------------------------------------- +// TODO: Use string_view static inline void Unquote(std::string& str) { boost::trim_if(str, boost::is_any_of("\"")); } -bool ContentDisposition::Init(const std::string& str) { - std::vector parts; +bool ContentDisposition::Init(string_view str) { + std::vector parts; Split(str, ';', false, &parts); if (parts.empty()) { @@ -160,18 +148,18 @@ bool ContentDisposition::Init(const std::string& str) { return false; } - std::string key; - std::string value; + string_view key; + string_view value; for (std::size_t i = 1; i < parts.size(); ++i) { - if (!SplitKV(parts[i].to_string(), '=', true, &key, &value)) { + if (!SplitKV(parts[i], '=', true, &key, &value)) { return false; } if (key == "name") { - name_ = value; + name_ = ToString(value); Unquote(name_); } else if (key == "filename") { - file_name_ = value; + file_name_ = ToString(value); Unquote(file_name_); } } @@ -181,24 +169,24 @@ bool ContentDisposition::Init(const std::string& str) { // ----------------------------------------------------------------------------- -FormPartPtr FormPart::New(const std::string& name, std::string&& data, - const std::string& media_type) { +FormPartPtr FormPart::New(string_view name, std::string&& data, + string_view media_type) { auto form_part = std::make_shared(); - form_part->name_ = name; + form_part->name_ = ToString(name); form_part->data_ = std::move(data); - form_part->media_type_ = media_type; + form_part->media_type_ = ToString(media_type); return form_part; } -FormPartPtr FormPart::NewFile(const std::string& name, const fs::path& path, - const std::string& media_type) { +FormPartPtr FormPart::NewFile(string_view name, const fs::path& path, + string_view media_type) { auto form_part = std::make_shared(); - form_part->name_ = name; + form_part->name_ = ToString(name); form_part->path_ = path; - form_part->media_type_ = media_type; + form_part->media_type_ = ToString(media_type); // Determine file name from file path. // TODO: encoding @@ -288,7 +276,7 @@ std::size_t FormPart::GetDataSize() { return size; } -void FormPart::Dump(std::ostream& os, const std::string& prefix) const { +void FormPart::Dump(std::ostream& os, string_view prefix) const { for (auto& h : headers_.data()) { os << prefix << h.first << ": " << h.second << std::endl; } diff --git a/webcc/common.h b/webcc/common.h index 5a85905..751035b 100644 --- a/webcc/common.h +++ b/webcc/common.h @@ -29,11 +29,9 @@ public: return headers_; } - bool Set(const std::string& key, const std::string& value); + bool Set(string_view key, string_view value); - bool Set(std::string&& key, std::string&& value); - - bool Has(const std::string& key) const; + bool Has(string_view key) const; // Get header by index. const Header& Get(std::size_t index) const { @@ -44,14 +42,14 @@ public: // Get header value by key. // If there's no such header with the given key, besides return empty, the // optional |existed| parameter will be set to false. - const std::string& Get(const std::string& key, bool* existed = nullptr) const; + const std::string& Get(string_view key, bool* existed = nullptr) const; void Clear() { headers_.clear(); } private: - std::vector
::iterator Find(const std::string& key); + std::vector
::iterator Find(string_view key); std::vector
headers_; }; @@ -64,9 +62,9 @@ private: // Content-Type: multipart/form-data; boundary=something class ContentType { public: - explicit ContentType(const std::string& str = ""); + explicit ContentType(string_view str = ""); - void Parse(const std::string& str); + void Parse(string_view str); void Reset(); @@ -91,7 +89,7 @@ public: } private: - void Init(const std::string& str); + void Init(string_view str); private: std::string media_type_; @@ -109,7 +107,7 @@ private: // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition class ContentDisposition { public: - explicit ContentDisposition(const std::string& str) { + explicit ContentDisposition(string_view str) { valid_ = Init(str); } @@ -126,7 +124,7 @@ public: } private: - bool Init(const std::string& str); + bool Init(string_view str); private: std::string name_; @@ -151,14 +149,14 @@ public: // The data will be moved, no file name is needed. // The media type is optional. If the data is a JSON string, you can specify // media type as "application/json". - static FormPartPtr New(const std::string& name, std::string&& data, - const std::string& media_type = ""); + static FormPartPtr New(string_view name, std::string&& data, + string_view media_type = ""); // Construct a file part. // The file name will be extracted from path. // The media type, if not provided, will be inferred from file extension. - static FormPartPtr NewFile(const std::string& name, const fs::path& path, - const std::string& media_type = ""); + static FormPartPtr NewFile(string_view name, const fs::path& path, + string_view media_type = ""); // API: SERVER const std::string& name() const { @@ -214,7 +212,7 @@ public: std::size_t GetDataSize(); // Dump to output stream for logging purpose. - void Dump(std::ostream& os, const std::string& prefix) const; + void Dump(std::ostream& os, string_view prefix) const; private: // Generate headers from properties. diff --git a/webcc/config.h.example b/webcc/config.h.example index 14ff92c..367a524 100644 --- a/webcc/config.h.example +++ b/webcc/config.h.example @@ -17,7 +17,10 @@ // Set 1/0 to enable/disable GZIP compression. #define WEBCC_ENABLE_GZIP 0 -// Set 1 to use C++17 filesystem or 0 to use Boost filesystem. +// Set 1 to use std::filesystem or 0 to use boost::filesystem. #define WEBCC_USE_STD_FILESYSTEM 1 +// Set 1 to use std::string_view or 0 to use boost::string_view. +#define WEBCC_USE_STD_STRING_VIEW 1 + #endif // WEBCC_CONFIG_H_ diff --git a/webcc/config.h.in b/webcc/config.h.in index 7d3974c..2a9af52 100644 --- a/webcc/config.h.in +++ b/webcc/config.h.in @@ -19,7 +19,10 @@ // Set 1/0 to enable/disable GZIP compression. #define WEBCC_ENABLE_GZIP @WEBCC_ENABLE_GZIP@ -// Set 1 to use C++17 filesystem or 0 to use Boost filesystem. +// Set 1 to use std::filesystem or 0 to use boost::filesystem. #define WEBCC_USE_STD_FILESYSTEM @WEBCC_USE_STD_FILESYSTEM@ +// Set 1 to use std::string_view or 0 to use boost::string_view. +#define WEBCC_USE_STD_STRING_VIEW @WEBCC_USE_STD_STRING_VIEW@ + #endif // WEBCC_CONFIG_H_ diff --git a/webcc/connection.cc b/webcc/connection.cc index 8cb1812..076492e 100644 --- a/webcc/connection.cc +++ b/webcc/connection.cc @@ -24,10 +24,11 @@ void Connection::Start() { boost::system::error_code ec; auto endpoint = socket_.remote_endpoint(ec); if (!ec) { - request_->set_ip(endpoint.address().to_string()); + request_->set_address(endpoint.address().to_string()); } request_parser_.Init(request_.get(), view_matcher_); + AsyncRead(); } diff --git a/webcc/globals.h b/webcc/globals.h index 7ef01aa..6747c72 100644 --- a/webcc/globals.h +++ b/webcc/globals.h @@ -12,10 +12,32 @@ #include "webcc/config.h" +#if WEBCC_USE_STD_STRING_VIEW +#include +#else +#include "boost/utility/string_view.hpp" +#endif // WEBCC_USE_STD_STRING_VIEW + namespace webcc { // ----------------------------------------------------------------------------- +#if WEBCC_USE_STD_STRING_VIEW +using string_view = std::string_view; +#else +using string_view = boost::string_view; +#endif // WEBCC_USE_STD_STRING_VIEW + +inline std::string ToString(string_view sv) { +#if WEBCC_USE_STD_STRING_VIEW + return std::string{ sv.begin(), sv.end() }; +#else + return sv.to_string(); +#endif // WEBCC_USE_STD_STRING_VIEW +} + +// ----------------------------------------------------------------------------- + using Strings = std::vector; // Regex sub-matches of the URL (usually resource ID's). @@ -169,7 +191,7 @@ public: }; public: - explicit Error(Code code = kOK, const std::string& message = "") + explicit Error(Code code = kOK, string_view message = "") : code_(code), message_(message) { } @@ -186,9 +208,9 @@ public: return message_; } - void Set(Code code, const std::string& message) { + void Set(Code code, string_view message) { code_ = code; - message_ = message; + message_ = ToString(message); } bool timeout() const { diff --git a/webcc/logger.cc b/webcc/logger.cc index 715636a..42a2b48 100644 --- a/webcc/logger.cc +++ b/webcc/logger.cc @@ -135,7 +135,7 @@ static const bool g_terminal_has_color = []() { static std::string DoGetThreadID() { #if (defined(_WIN32) || defined(_WIN64)) auto thread_id = std::this_thread::get_id(); - std::stringstream ss; + std::ostringstream ss; ss << thread_id; return ss.str(); #else @@ -184,7 +184,7 @@ static std::string GetTimestamp() { auto now = system_clock::now(); std::time_t t = system_clock::to_time_t(now); - std::stringstream ss; + std::ostringstream ss; ss << std::put_time(std::localtime(&t), "%Y-%m-%d %H:%M:%S"); milliseconds milli_seconds = diff --git a/webcc/message.cc b/webcc/message.cc index 031782f..5305836 100644 --- a/webcc/message.cc +++ b/webcc/message.cc @@ -31,6 +31,7 @@ void Message::SetBody(BodyPtr body, bool set_length) { const std::string& Message::data() const { static const std::string kEmptyData; + auto string_body = std::dynamic_pointer_cast(body_); if (!string_body) { return kEmptyData; @@ -43,54 +44,42 @@ std::shared_ptr Message::file_body() const { } bool Message::IsConnectionKeepAlive() const { - using headers::kConnection; - bool existed = false; - const std::string& connection = GetHeader(kConnection, &existed); + const auto& connection = GetHeader(headers::kConnection, &existed); if (!existed) { // Keep-Alive is by default for HTTP/1.1. return true; } - if (boost::iequals(connection, "Keep-Alive")) { - return true; - } - - return false; + return boost::iequals(connection, "Keep-Alive"); } ContentEncoding Message::GetContentEncoding() const { - using headers::kContentEncoding; - - const std::string& encoding = GetHeader(kContentEncoding); + const auto& encoding = GetHeader(headers::kContentEncoding); if (encoding == "gzip") { return ContentEncoding::kGzip; - } - - if (encoding == "deflate") { + } else if (encoding == "deflate") { return ContentEncoding::kDeflate; + } else { + return ContentEncoding::kUnknown; } - - return ContentEncoding::kUnknown; } bool Message::AcceptEncodingGzip() const { - using headers::kAcceptEncoding; - - return GetHeader(kAcceptEncoding).find("gzip") != std::string::npos; + return GetHeader(headers::kAcceptEncoding).find("gzip") != std::string::npos; } -void Message::SetContentType(const std::string& media_type, - const std::string& charset) { - using headers::kContentType; - +void Message::SetContentType(string_view media_type, string_view charset) { if (!media_type.empty()) { if (charset.empty()) { - SetHeader(kContentType, media_type); + SetHeader(headers::kContentType, media_type); } else { - SetHeader(kContentType, media_type + "; charset=" + charset); + std::string value = ToString(media_type); + value += "; charset="; + value += ToString(charset); + SetHeader(headers::kContentType, value); } } } @@ -116,21 +105,21 @@ Payload Message::GetPayload() const { } void Message::Dump(std::ostream& os) const { - const std::string prefix = " > "; + static const char* const s_prefix = " > "; - os << prefix << start_line_ << std::endl; + os << s_prefix << start_line_ << std::endl; for (const Header& h : headers_.data()) { - os << prefix << h.first << ": " << h.second << std::endl; + os << s_prefix << h.first << ": " << h.second << std::endl; } - os << prefix << std::endl; + os << s_prefix << std::endl; - body_->Dump(os, prefix); + body_->Dump(os, s_prefix); } std::string Message::Dump() const { - std::stringstream ss; + std::ostringstream ss; Dump(ss); return ss.str(); } diff --git a/webcc/message.h b/webcc/message.h index 6728343..def0ddc 100644 --- a/webcc/message.h +++ b/webcc/message.h @@ -21,51 +21,30 @@ public: virtual ~Message() = default; - // --------------------------------------------------------------------------- - - void SetBody(BodyPtr body, bool set_length); - - BodyPtr body() const { - return body_; + const std::string& start_line() const { + return start_line_; } - // Get the data from the (string) body. - // Return empty string if the body is not a StringBody. - const std::string& data() const; - - // Get the body as a FileBody. - // Return null if the body is not a FileBody. - std::shared_ptr file_body() const; - - // --------------------------------------------------------------------------- + void set_start_line(string_view start_line) { + start_line_ = ToString(start_line); + } void SetHeader(Header&& header) { headers_.Set(std::move(header.first), std::move(header.second)); } - void SetHeader(const std::string& key, const std::string& value) { + void SetHeader(string_view key, string_view value) { headers_.Set(key, value); } - const std::string& GetHeader(const std::string& key, - bool* existed = nullptr) const { + const std::string& GetHeader(string_view key, bool* existed = nullptr) const { return headers_.Get(key, existed); } - bool HasHeader(const std::string& key) const { + bool HasHeader(string_view key) const { return headers_.Has(key); } - // --------------------------------------------------------------------------- - - const std::string& start_line() const { - return start_line_; - } - - void set_start_line(const std::string& start_line) { - start_line_ = start_line; - } - std::size_t content_length() const { return content_length_; } @@ -74,7 +53,19 @@ public: content_length_ = content_length; } - // --------------------------------------------------------------------------- + void SetBody(BodyPtr body, bool set_length); + + BodyPtr body() const { + return body_; + } + + // Get the data from the (string) body. + // Return empty string if the body is not a StringBody. + const std::string& data() const; + + // Get the body as a FileBody. + // Return null if the body is not a FileBody. + std::shared_ptr file_body() const; // Check `Connection` header to see if it's "Keep-Alive". bool IsConnectionKeepAlive() const; @@ -88,16 +79,13 @@ public: // Set `Content-Type` header. E.g., // SetContentType("application/json; charset=utf-8") - void SetContentType(const std::string& content_type) { + void SetContentType(string_view content_type) { SetHeader(headers::kContentType, content_type); } // Set `Content-Type` header. E.g., // SetContentType("application/json", "utf-8") - void SetContentType(const std::string& media_type, - const std::string& charset); - - // --------------------------------------------------------------------------- + void SetContentType(string_view media_type, string_view charset); // Make the message complete in order to be sent. virtual void Prepare() = 0; @@ -106,8 +94,6 @@ public: // This doesn't include the payload(s) of the body! Payload GetPayload() const; - // --------------------------------------------------------------------------- - // Dump to output stream for logging purpose. void Dump(std::ostream& os) const; diff --git a/webcc/request.h b/webcc/request.h index dedaacf..4f8aa07 100644 --- a/webcc/request.h +++ b/webcc/request.h @@ -23,8 +23,8 @@ public: return method_; } - void set_method(const std::string& method) { - method_ = method; + void set_method(string_view method) { + method_ = ToString(method); } const Url& url() const { @@ -44,23 +44,23 @@ public: } UrlQuery query() const { - return UrlQuery(url_.query()); + return UrlQuery{ url_.query() }; } const UrlArgs& args() const { return args_; } - void set_args(const UrlArgs& args) { - args_ = args; + void set_args(UrlArgs&& args) { + args_ = std::move(args); } - const std::string& ip() const { - return ip_; + const std::string& address() const { + return address_; } - void set_ip(const std::string& ip) { - ip_ = ip; + void set_address(std::string&& address) { + address_ = std::move(address); } // Check if the body is a multi-part form data. @@ -83,7 +83,7 @@ private: UrlArgs args_; // Client IP address. - std::string ip_; + std::string address_; }; using RequestPtr = std::shared_ptr; diff --git a/webcc/request_builder.cc b/webcc/request_builder.cc index be484e5..d87402e 100644 --- a/webcc/request_builder.cc +++ b/webcc/request_builder.cc @@ -19,7 +19,7 @@ RequestPtr RequestBuilder::operator()() { request->set_url(std::move(url_)); for (std::size_t i = 1; i < headers_.size(); i += 2) { - request->SetHeader(std::move(headers_[i - 1]), std::move(headers_[i])); + request->SetHeader(headers_[i - 1], headers_[i]); } // If no Keep-Alive, explicitly set `Connection` to "Close". @@ -34,7 +34,7 @@ RequestPtr RequestBuilder::operator()() { if (gzip_ && body_->Compress()) { request->SetHeader(headers::kContentEncoding, "gzip"); } -#endif +#endif // WEBCC_ENABLE_GZIP } else if (!form_parts_.empty()) { // Another choice to generate the boundary is like what Apache does. // See: https://stackoverflow.com/a/5686863 @@ -77,41 +77,41 @@ RequestBuilder& RequestBuilder::File(const fs::path& path, return *this; } -RequestBuilder& RequestBuilder::FormFile(const std::string& name, +RequestBuilder& RequestBuilder::FormFile(string_view name, const fs::path& path, - const std::string& media_type) { + string_view media_type) { assert(!name.empty()); return Form(FormPart::NewFile(name, path, media_type)); } -RequestBuilder& RequestBuilder::FormData(const std::string& name, +RequestBuilder& RequestBuilder::FormData(string_view name, std::string&& data, - const std::string& media_type) { + string_view media_type) { assert(!name.empty()); return Form(FormPart::New(name, std::move(data), media_type)); } -RequestBuilder& RequestBuilder::Header(const std::string& key, - const std::string& value) { - headers_.push_back(key); - headers_.push_back(value); +RequestBuilder& RequestBuilder::Header(string_view key, string_view value) { + headers_.push_back(ToString(key)); + headers_.push_back(ToString(value)); return *this; } -RequestBuilder& RequestBuilder::Auth(const std::string& type, - const std::string& credentials) { +RequestBuilder& RequestBuilder::Auth(string_view type, + string_view credentials) { headers_.push_back(headers::kAuthorization); - headers_.push_back(type + " " + credentials); + headers_.push_back(ToString(type) + " " + ToString(credentials)); return *this; } -RequestBuilder& RequestBuilder::AuthBasic(const std::string& login, - const std::string& password) { - auto credentials = Base64Encode(login + ":" + password); +RequestBuilder& RequestBuilder::AuthBasic(string_view login, + string_view password) { + auto credentials = + Base64Encode(ToString(login) + ":" + ToString(password)); return Auth("Basic", credentials); } -RequestBuilder& RequestBuilder::AuthToken(const std::string& token) { +RequestBuilder& RequestBuilder::AuthToken(string_view token) { return Auth("Token", token); } diff --git a/webcc/request_builder.h b/webcc/request_builder.h index 11ceb5c..aacb92b 100644 --- a/webcc/request_builder.h +++ b/webcc/request_builder.h @@ -39,44 +39,44 @@ public: RequestBuilder(const RequestBuilder&) = delete; RequestBuilder& operator=(const RequestBuilder&) = delete; - // Build + // Build and return the request object. RequestPtr operator()(); - RequestBuilder& Method(const std::string& method) { - method_ = method; + RequestBuilder& Method(string_view method) { + method_ = ToString(method); return *this; } - RequestBuilder& Get(const std::string& url, bool encode = false) { + RequestBuilder& Get(string_view url, bool encode = false) { return Method(methods::kGet).Url(url, encode); } - RequestBuilder& Head(const std::string& url, bool encode = false) { + RequestBuilder& Head(string_view url, bool encode = false) { return Method(methods::kHead).Url(url, encode); } - RequestBuilder& Post(const std::string& url, bool encode = false) { + RequestBuilder& Post(string_view url, bool encode = false) { return Method(methods::kPost).Url(url, encode); } - RequestBuilder& Put(const std::string& url, bool encode = false) { + RequestBuilder& Put(string_view url, bool encode = false) { return Method(methods::kPut).Url(url, encode); } - RequestBuilder& Delete(const std::string& url, bool encode = false) { + RequestBuilder& Delete(string_view url, bool encode = false) { return Method(methods::kDelete).Url(url, encode); } - RequestBuilder& Patch(const std::string& url, bool encode = false) { + RequestBuilder& Patch(string_view url, bool encode = false) { return Method(methods::kPatch).Url(url, encode); } - RequestBuilder& Url(const std::string& url, bool encode = false) { + RequestBuilder& Url(string_view url, bool encode = false) { url_ = webcc::Url{ url, encode }; return *this; } - RequestBuilder& Port(const std::string& port) { + RequestBuilder& Port(string_view port) { url_.set_port(port); return *this; } @@ -87,25 +87,25 @@ public: } // Append a piece to the path. - RequestBuilder& Path(const std::string& path, bool encode = false) { + RequestBuilder& Path(string_view path, bool encode = false) { url_.AppendPath(path, encode); return *this; } // Append a parameter to the query. - RequestBuilder& Query(const std::string& key, const std::string& value, + RequestBuilder& Query(string_view key, string_view value, bool encode = false) { url_.AppendQuery(key, value, encode); return *this; } - RequestBuilder& MediaType(const std::string& media_type) { - media_type_ = media_type; + RequestBuilder& MediaType(string_view media_type) { + media_type_ = ToString(media_type); return *this; } - RequestBuilder& Charset(const std::string& charset) { - charset_ = charset; + RequestBuilder& Charset(string_view charset) { + charset_ = ToString(charset); return *this; } @@ -123,7 +123,7 @@ public: // Set (comma separated) content types to accept. // E.g., "application/json", "text/html, application/xhtml+xml". - RequestBuilder& Accept(const std::string& content_types) { + RequestBuilder& Accept(string_view content_types) { return Header(headers::kAccept, content_types); } @@ -156,26 +156,25 @@ public: } // Add a form part of file. - RequestBuilder& FormFile(const std::string& name, const fs::path& path, - const std::string& media_type = ""); + RequestBuilder& FormFile(string_view name, const fs::path& path, + string_view media_type = ""); // Add a form part of string data. - RequestBuilder& FormData(const std::string& name, std::string&& data, - const std::string& media_type = ""); + RequestBuilder& FormData(string_view name, std::string&& data, + string_view media_type = ""); - RequestBuilder& Header(const std::string& key, const std::string& value); + RequestBuilder& Header(string_view key, string_view value); RequestBuilder& KeepAlive(bool keep_alive = true) { keep_alive_ = keep_alive; return *this; } - RequestBuilder& Auth(const std::string& type, const std::string& credentials); + RequestBuilder& Auth(string_view type, string_view credentials); - RequestBuilder& AuthBasic(const std::string& login, - const std::string& password); + RequestBuilder& AuthBasic(string_view login, string_view password); - RequestBuilder& AuthToken(const std::string& token); + RequestBuilder& AuthToken(string_view token); // Add the `Date` header to the request. RequestBuilder& Date(); diff --git a/webcc/request_parser.cc b/webcc/request_parser.cc index e0512d8..daeec34 100644 --- a/webcc/request_parser.cc +++ b/webcc/request_parser.cc @@ -36,15 +36,15 @@ bool RequestParser::OnHeadersEnd() { } bool RequestParser::ParseStartLine(const std::string& line) { - std::vector parts; + std::vector parts; Split(line, ' ', true, &parts); if (parts.size() != 3) { return false; } - request_->set_method(parts[0].to_string()); - request_->set_url(Url{ parts[1].to_string() }); + request_->set_method(parts[0]); + request_->set_url(Url{ parts[1] }); // HTTP version is ignored. @@ -198,7 +198,7 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) { // Parse Content-Disposition. if (boost::iequals(header.first, headers::kContentDisposition)) { - ContentDisposition content_disposition(header.second); + ContentDisposition content_disposition{ header.second }; if (!content_disposition.valid()) { LOG_ERRO("Invalid content-disposition header: %s", header.second.c_str()); diff --git a/webcc/request_parser.h b/webcc/request_parser.h index a753fb2..31493fe 100644 --- a/webcc/request_parser.h +++ b/webcc/request_parser.h @@ -42,21 +42,22 @@ private: std::size_t count, bool* end = nullptr) const; private: - Request* request_; + // The result request message. + Request* request_ = nullptr; // A function for matching view once the headers of a request has been // received. The parsing will stop and fail if no view can be matched. ViewMatcher view_matcher_; // Form data parsing steps. - enum Step { + enum class Step { kStart, kBoundaryParsed, kHeadersParsed, kEnded, }; - Step step_ = kStart; + Step step_ = Step::kStart; // The current form part being parsed. FormPartPtr part_; diff --git a/webcc/response_builder.cc b/webcc/response_builder.cc index 3b488be..3cc1845 100644 --- a/webcc/response_builder.cc +++ b/webcc/response_builder.cc @@ -55,6 +55,12 @@ ResponseBuilder& ResponseBuilder::File(const fs::path& path, return *this; } +ResponseBuilder& ResponseBuilder::Header(string_view key, string_view value) { + headers_.push_back(ToString(key)); + headers_.push_back(ToString(value)); + return *this; +} + ResponseBuilder& ResponseBuilder::Date() { headers_.push_back(headers::kDate); headers_.push_back(utility::HttpDate()); diff --git a/webcc/response_builder.h b/webcc/response_builder.h index 5bacfc5..c5266a8 100644 --- a/webcc/response_builder.h +++ b/webcc/response_builder.h @@ -61,13 +61,13 @@ public: return *this; } - ResponseBuilder& MediaType(const std::string& media_type) { - media_type_ = media_type; + ResponseBuilder& MediaType(string_view media_type) { + media_type_ = ToString(media_type); return *this; } - ResponseBuilder& Charset(const std::string& charset) { - charset_ = charset; + ResponseBuilder& Charset(string_view charset) { + charset_ = ToString(charset); return *this; } @@ -98,11 +98,7 @@ public: ResponseBuilder& File(const fs::path& path, bool infer_media_type = true, std::size_t chunk_size = 1024); - ResponseBuilder& Header(const std::string& key, const std::string& value) { - headers_.push_back(key); - headers_.push_back(value); - return *this; - } + ResponseBuilder& Header(string_view key, string_view value); // Add the `Date` header to the response. ResponseBuilder& Date(); diff --git a/webcc/router.cc b/webcc/router.cc index d16caea..a87d68f 100644 --- a/webcc/router.cc +++ b/webcc/router.cc @@ -8,13 +8,12 @@ namespace webcc { -bool Router::Route(const std::string& url, ViewPtr view, - const Strings& methods) { +bool Router::Route(string_view url, ViewPtr view, const Strings& methods) { assert(view); // TODO: More error check - routes_.push_back({ url, {}, view, methods }); + routes_.push_back({ ToString(url), {}, view, methods }); return true; } @@ -26,7 +25,6 @@ bool Router::Route(const UrlRegex& regex_url, ViewPtr view, // TODO: More error check try { - routes_.push_back({ "", regex_url(), view, methods }); } catch (const std::regex_error& e) { diff --git a/webcc/router.h b/webcc/router.h index 0efb8ae..b4ee47e 100644 --- a/webcc/router.h +++ b/webcc/router.h @@ -15,7 +15,7 @@ public: // Route a URL to a view. // The URL should start with "/". E.g., "/instances". - bool Route(const std::string& url, ViewPtr view, + bool Route(string_view url, ViewPtr view, const Strings& methods = { "GET" }); // Route a URL (as regular expression) to a view. diff --git a/webcc/server.cc b/webcc/server.cc index 927ade4..3cbc13e 100644 --- a/webcc/server.cc +++ b/webcc/server.cc @@ -300,7 +300,7 @@ void Server::Handle(ConnectionPtr connection) { } // Save the (regex matched) URL args to request object. - request->set_args(args); + request->set_args(std::move(args)); // Ask the matched view to process the request. ResponsePtr response = view->Handle(request); diff --git a/webcc/string.cc b/webcc/string.cc index 265fea9..ed630b8 100644 --- a/webcc/string.cc +++ b/webcc/string.cc @@ -36,19 +36,19 @@ bool ToSizeT(const std::string& str, int base, std::size_t* size) { return true; } -void Split(boost::string_view input, char delim, bool compress_token, - std::vector* output) { +void Split(string_view input, char delim, bool compress_token, + std::vector* output) { std::size_t i = 0; std::size_t p = 0; i = input.find(delim); - while (i != boost::string_view::npos) { + while (i != string_view::npos) { output->emplace_back(input.substr(p, i - p)); p = i + 1; if (compress_token) { - while (input[p] == delim) { + while (p < input.size() && input[p] == delim) { ++p; } } @@ -59,10 +59,19 @@ void Split(boost::string_view input, char delim, bool compress_token, output->emplace_back(input.substr(p, i - p)); } -bool SplitKV(const std::string& input, char delim, bool trim_spaces, - std::string* key, std::string* value) { +void Trim(string_view& sv, const char* spaces) { + sv.remove_prefix(std::min(sv.find_first_not_of(spaces), sv.size())); + + std::size_t pos = sv.find_last_not_of(spaces); + if (pos != sv.npos) { + sv.remove_suffix(sv.size() - pos - 1); + } +} + +bool SplitKV(string_view input, char delim, bool trim_spaces, string_view* key, + string_view* value) { std::size_t pos = input.find(delim); - if (pos == std::string::npos) { + if (pos == input.npos) { return false; } @@ -70,11 +79,23 @@ bool SplitKV(const std::string& input, char delim, bool trim_spaces, *value = input.substr(pos + 1); if (trim_spaces) { - boost::trim(*key); - boost::trim(*value); + Trim(*key); + Trim(*value); } return true; } +bool SplitKV(string_view input, char delim, bool trim_spaces, std::string* key, + std::string* value) { + string_view key_view; + string_view value_view; + if (SplitKV(input, delim, trim_spaces, &key_view, &value_view)) { + *key = ToString(key_view); + *value = ToString(value_view); + return true; + } + return false; +} + } // namespace webcc diff --git a/webcc/string.h b/webcc/string.h index 880c628..67e9635 100644 --- a/webcc/string.h +++ b/webcc/string.h @@ -4,7 +4,7 @@ #include #include -#include "boost/utility/string_view.hpp" +#include "webcc/globals.h" // for string_view namespace webcc { @@ -15,15 +15,21 @@ std::string RandomString(std::size_t length); // Just a wrapper of std::stoul. bool ToSizeT(const std::string& str, int base, std::size_t* size); +void Trim(string_view& sv, const char* spaces = " "); + // Split string without copy. // |compress_token| is the same as boost::token_compress_on for boost::split. -void Split(boost::string_view input, char delim, bool compress_token, - std::vector* output); +void Split(string_view input, char delim, bool compress_token, + std::vector* output); + +// Split a key-value string. +// E.g., split "Connection: Keep-Alive". +bool SplitKV(string_view input, char delim, bool trim_spaces, string_view* key, + string_view* value); // Split a key-value string. // E.g., split "Connection: Keep-Alive". -// TODO: Use string_view (blocked by trim) -bool SplitKV(const std::string& input, char delim, bool trim_spaces, +bool SplitKV(string_view input, char delim, bool trim_spaces, std::string* key, std::string* value); } // namespace webcc diff --git a/webcc/url.cc b/webcc/url.cc index 86b5c8c..e7a006f 100644 --- a/webcc/url.cc +++ b/webcc/url.cc @@ -29,7 +29,7 @@ bool HexToDecimal(char hex, int* decimal) { return true; } -bool Decode(const std::string& encoded, std::string* raw) { +bool Decode(string_view encoded, std::string* raw) { for (auto iter = encoded.begin(); iter != encoded.end(); ++iter) { if (*iter == '%') { if (++iter == encoded.end()) { @@ -67,16 +67,16 @@ bool Decode(const std::string& encoded, std::string* raw) { // Unsafe decode. // Return the original string on failure. -std::string DecodeUnsafe(const std::string& encoded) { +std::string DecodeUnsafe(string_view encoded) { std::string raw; if (Decode(encoded, &raw)) { return raw; } - return encoded; + return ToString(encoded); } // Encode all characters which should be encoded. -std::string EncodeImpl(const std::string& raw, // UTF8 +std::string EncodeImpl(string_view raw, // UTF8 std::function should_encode) { const char kHex[] = "0123456789ABCDEF"; @@ -102,14 +102,16 @@ std::string EncodeImpl(const std::string& raw, // UTF8 // Our own implementation of alpha numeric instead of std::isalnum to avoid // taking locale into account. inline bool IsAlNum(int c) { - return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'); } // Characters that are allowed in a URI but do not have a reserved purpose are // are called unreserved. These include uppercase and lowercase letters, decimal // digits, hyphen, period, underscore, and tilde. inline bool IsUnreserved(int c) { - return IsAlNum((unsigned char)c) || c == '-' || c == '.' || c == '_' || c == '~'; + return IsAlNum((unsigned char)c) || c == '-' || c == '.' || c == '_' || + c == '~'; } // General delimiters serve as the delimiters between different uri components. @@ -171,23 +173,23 @@ inline bool IsQueryChar(int c) { // ----------------------------------------------------------------------------- -std::string Url::EncodeHost(const std::string& utf8_str) { +std::string Url::EncodeHost(string_view utf8_str) { return EncodeImpl(utf8_str, [](int c) -> bool { return c > 127; }); } -std::string Url::EncodePath(const std::string& utf8_str) { +std::string Url::EncodePath(string_view utf8_str) { return EncodeImpl(utf8_str, [](int c) -> bool { return !IsPathChar(c) || c == '%' || c == '+'; }); } -std::string Url::EncodeQuery(const std::string& utf8_str) { +std::string Url::EncodeQuery(string_view utf8_str) { return EncodeImpl(utf8_str, [](int c) -> bool { return !IsQueryChar(c) || c == '%' || c == '+'; }); } -std::string Url::EncodeFull(const std::string& utf8_str) { +std::string Url::EncodeFull(string_view utf8_str) { return EncodeImpl(utf8_str, [](int c) -> bool { return !IsUnreserved(c) && !IsReserved(c); }); @@ -195,7 +197,7 @@ std::string Url::EncodeFull(const std::string& utf8_str) { // ----------------------------------------------------------------------------- -Url::Url(const std::string& str, bool encode) { +Url::Url(string_view str, bool encode) { if (encode) { Parse(Url::EncodeFull(str)); } else { @@ -203,7 +205,7 @@ Url::Url(const std::string& str, bool encode) { } } -void Url::AppendPath(const std::string& piece, bool encode) { +void Url::AppendPath(string_view piece, bool encode) { if (piece.empty() || piece == "/") { return; } @@ -222,24 +224,23 @@ void Url::AppendPath(const std::string& piece, bool encode) { if (encode) { path_.append(Url::EncodePath(piece)); } else { - path_.append(piece); + path_.append(ToString(piece)); } } -void Url::AppendQuery(const std::string& key, const std::string& value, - bool encode) { +void Url::AppendQuery(string_view key, string_view value, bool encode) { if (!query_.empty()) { query_ += "&"; } if (encode) { query_ += Url::EncodeQuery(key) + "=" + Url::EncodeQuery(value); } else { - query_ += key + "=" + value; + query_ += ToString(key) + "=" + ToString(value); } } -void Url::Parse(const std::string& str) { - std::string tmp = str; +void Url::Parse(string_view str) { + std::string tmp = ToString(str); boost::trim_left(tmp); std::size_t p = std::string::npos; @@ -314,8 +315,8 @@ UrlQuery::UrlQuery(const std::string& encoded_str) { i = j + 1; } - std::string key; - std::string value; + string_view key; + string_view value; if (SplitKV(kv, '=', false, &key, &value)) { parameters_.push_back({ DecodeUnsafe(key), DecodeUnsafe(value) }); } diff --git a/webcc/url.h b/webcc/url.h index bcd7efb..0f81b07 100644 --- a/webcc/url.h +++ b/webcc/url.h @@ -17,15 +17,15 @@ namespace webcc { class Url { public: // Encode URL different components. - static std::string EncodeHost(const std::string& utf8_str); - static std::string EncodePath(const std::string& utf8_str); - static std::string EncodeQuery(const std::string& utf8_str); - static std::string EncodeFull(const std::string& utf8_str); + static std::string EncodeHost(string_view utf8_str); + static std::string EncodePath(string_view utf8_str); + static std::string EncodeQuery(string_view utf8_str); + static std::string EncodeFull(string_view utf8_str); public: Url() = default; - explicit Url(const std::string& str, bool encode = false); + explicit Url(string_view str, bool encode = false); const std::string& scheme() const { return scheme_; @@ -47,19 +47,18 @@ public: return query_; } - void set_port(const std::string& port) { - port_ = port; + void set_port(string_view port) { + port_ = ToString(port); } // Append a piece of path. - void AppendPath(const std::string& piece, bool encode = false); + void AppendPath(string_view piece, bool encode = false); // Append a query parameter. - void AppendQuery(const std::string& key, const std::string& value, - bool encode = false); + void AppendQuery(string_view key, string_view value, bool encode = false); private: - void Parse(const std::string& str); + void Parse(string_view str); void Clear(); @@ -125,12 +124,11 @@ private: // Used by Server::Route(). class UrlRegex { public: - explicit UrlRegex(const std::string& url) : url_(url) { + explicit UrlRegex(string_view url) : url_(url) { } std::regex operator()() const { std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase; - return std::regex{ url_, flags }; } diff --git a/webcc/utility.cc b/webcc/utility.cc index 0134f8c..8c6ab4b 100644 --- a/webcc/utility.cc +++ b/webcc/utility.cc @@ -10,6 +10,8 @@ #include "webcc/string.h" #include "webcc/version.h" +using boost::asio::ip::tcp; + namespace webcc { namespace utility { @@ -22,7 +24,7 @@ std::string HttpDate() { std::time_t t = std::time(nullptr); std::tm gmt = *std::gmtime(&t); - std::stringstream date; + std::ostringstream date; date.imbue(std::locale::classic()); // Use classic C locale date << std::put_time(&gmt, "%a, %d %b %Y %H:%M:%S GMT"); return date.str(); @@ -54,9 +56,8 @@ bool ReadFile(const fs::path& path, std::string* output) { return true; } -void DumpByLine(const std::string& data, std::ostream& os, - const std::string& prefix) { - std::vector lines; +void DumpByLine(const std::string& data, std::ostream& os, string_view prefix) { + std::vector lines; Split(data, '\n', false, &lines); std::size_t size = 0; @@ -75,18 +76,17 @@ void DumpByLine(const std::string& data, std::ostream& os, } } -void PrintEndpoint(std::ostream& ostream, - const boost::asio::ip::tcp::endpoint& endpoint) { +void PrintEndpoint(std::ostream& ostream, const tcp::endpoint& endpoint) { ostream << endpoint; - if (endpoint.protocol() == boost::asio::ip::tcp::v4()) { + if (endpoint.protocol() == tcp::v4()) { ostream << ", v4"; - } else if (endpoint.protocol() == boost::asio::ip::tcp::v6()) { + } else if (endpoint.protocol() == tcp::v6()) { ostream << ", v6"; } } -std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint) { - std::stringstream ss; +std::string EndpointToString(const tcp::endpoint& endpoint) { + std::ostringstream ss; PrintEndpoint(ss, endpoint); return ss.str(); } diff --git a/webcc/utility.h b/webcc/utility.h index 528c454..4270ade 100644 --- a/webcc/utility.h +++ b/webcc/utility.h @@ -29,8 +29,7 @@ bool ReadFile(const fs::path& path, std::string* output); // Dump the string data line by line to achieve more readability. // Also limit the maximum size of the data to be dumped. -void DumpByLine(const std::string& data, std::ostream& os, - const std::string& prefix); +void DumpByLine(const std::string& data, std::ostream& os, string_view prefix); // Print TCP endpoint. // Usage: PrintEndpoint(std::cout, endpoint)