introduce string_view

master
Chunting Gu 4 years ago
parent 751a3539ae
commit 26bb6b341a

@ -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_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) if(BUILD_UNITTEST)
enable_testing() enable_testing()
@ -45,7 +46,7 @@ endif()
# C++ standard requirements. # C++ standard requirements.
if(WEBCC_USE_STD_FILESYSTEM) if(WEBCC_USE_STD_FILESYSTEM OR WEBCC_USE_STD_STRING_VIEW)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
else() else()
set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD 11)

@ -20,7 +20,7 @@ static Json::Value StringToJson(const std::string& str) {
Json::Value json; Json::Value json;
Json::CharReaderBuilder builder; Json::CharReaderBuilder builder;
std::stringstream stream(str); std::istringstream stream{ str };
std::string errors; std::string errors;
if (!Json::parseFromStream(builder, stream, &json, &errors)) { if (!Json::parseFromStream(builder, stream, &json, &errors)) {
std::cerr << errors << std::endl; std::cerr << errors << std::endl;

@ -16,7 +16,7 @@ Json::Value StringToJson(const std::string& str) {
Json::Value json; Json::Value json;
Json::CharReaderBuilder builder; Json::CharReaderBuilder builder;
std::stringstream stream(str); std::istringstream stream{ str };
std::string errs; std::string errs;
if (!Json::parseFromStream(builder, stream, &json, &errs)) { if (!Json::parseFromStream(builder, stream, &json, &errs)) {
std::cerr << errs << std::endl; std::cerr << errs << std::endl;

@ -16,7 +16,7 @@ Json::Value StringToJson(const std::string& str) {
Json::Value json; Json::Value json;
Json::CharReaderBuilder builder; Json::CharReaderBuilder builder;
std::stringstream stream(str); std::istringstream stream{ str };
std::string errs; std::string errs;
if (!Json::parseFromStream(builder, stream, &json, &errs)) { if (!Json::parseFromStream(builder, stream, &json, &errs)) {
std::cerr << errs << std::endl; std::cerr << errs << std::endl;

@ -39,11 +39,11 @@ int main(int argc, char* argv[]) {
std::make_shared<BookListView>(), std::make_shared<BookListView>(),
{ "GET", "POST" }); { "GET", "POST" });
server.Route(webcc::R("/books/(\\d+)"), server.Route(webcc::R{ "/books/(\\d+)" },
std::make_shared<BookDetailView>(photo_dir), std::make_shared<BookDetailView>(photo_dir),
{ "GET", "PUT", "DELETE" }); { "GET", "PUT", "DELETE" });
server.Route(webcc::R("/books/(\\d+)/photo"), server.Route(webcc::R{ "/books/(\\d+)/photo" },
std::make_shared<BookPhotoView>(photo_dir), std::make_shared<BookPhotoView>(photo_dir),
{ "GET", "PUT", "DELETE" }); { "GET", "PUT", "DELETE" });

@ -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" << std::endl;
std::cout << " $ ./form_client path/to/webcc/data/upload " std::cout << " $ ./form_client path/to/webcc/data/upload "
<< "http://httpbin.org/post" << std::endl; << "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 " std::cout << " $ ./form_client path/to/webcc/data/upload "
"http://localhost:8080/upload" "http://localhost:8080/upload"
<< std::endl; << std::endl;
@ -43,8 +43,7 @@ int main(int argc, char* argv[]) {
webcc::ClientSession session; webcc::ClientSession session;
try { try {
auto r = session.Send(webcc::RequestBuilder{} auto r = session.Send(WEBCC_POST(url)
.Post(url)
.FormFile("file", upload_dir / "remember.txt") .FormFile("file", upload_dir / "remember.txt")
.FormData("json", "{}", "application/json")()); .FormData("json", "{}", "application/json")());

@ -20,7 +20,7 @@ Json::Value StringToJson(const std::string& str) {
Json::Value json; Json::Value json;
Json::CharReaderBuilder builder; Json::CharReaderBuilder builder;
std::stringstream stream(str); std::istringstream stream{ str };
std::string errors; std::string errors;
if (!Json::parseFromStream(builder, stream, &json, &errors)) { if (!Json::parseFromStream(builder, stream, &json, &errors)) {
std::cerr << errors << std::endl; std::cerr << errors << std::endl;

@ -17,8 +17,7 @@ public:
TEST(RouterTest, URL_RegexBasic) { TEST(RouterTest, URL_RegexBasic) {
webcc::Router router; webcc::Router router;
router.Route(webcc::R("/instance/(\\d+)"), router.Route(webcc::R{ "/instance/(\\d+)" }, std::make_shared<MyView>());
std::make_shared<MyView>());
std::string url = "/instance/12345"; std::string url = "/instance/12345";
webcc::UrlArgs args; webcc::UrlArgs args;
@ -40,7 +39,7 @@ TEST(RouterTest, URL_RegexBasic) {
TEST(RouterTest, URL_RegexMultiple) { TEST(RouterTest, URL_RegexMultiple) {
webcc::Router router; 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<MyView>()); std::make_shared<MyView>());
std::string url = "/study/1/series/2/instance/3"; std::string url = "/study/1/series/2/instance/3";

@ -6,8 +6,36 @@
#include "webcc/string.h" #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) { TEST(StringTest, Split) {
std::vector<boost::string_view> parts; std::vector<webcc::string_view> parts;
webcc::Split("GET /path/to HTTP/1.1", ' ', false, &parts); webcc::Split("GET /path/to HTTP/1.1", ' ', false, &parts);
EXPECT_EQ(3, parts.size()); EXPECT_EQ(3, parts.size());
@ -18,12 +46,11 @@ TEST(StringTest, Split) {
TEST(StringTest, Split_TokenCompressOff) { TEST(StringTest, Split_TokenCompressOff) {
std::string str = "one,two,,three,,"; std::string str = "one,two,,three,,";
std::vector<boost::string_view> parts; std::vector<webcc::string_view> parts;
// Same as: // Same as:
// boost::split(parts, str, boost::is_any_of(","), // boost::split(parts, str, boost::is_any_of(","),
// boost::token_compress_off); // boost::token_compress_off);
webcc::Split(str, ',', false, &parts); webcc::Split(str, ',', false, &parts);
EXPECT_EQ(6, parts.size()); EXPECT_EQ(6, parts.size());
@ -37,7 +64,7 @@ TEST(StringTest, Split_TokenCompressOff) {
TEST(StringTest, Split_TokenCompressOn) { TEST(StringTest, Split_TokenCompressOn) {
std::string str = "one,two,,three,,"; std::string str = "one,two,,three,,";
std::vector<boost::string_view> parts; std::vector<webcc::string_view> parts;
// Same as: // Same as:
// boost::split(parts, str, boost::is_any_of(","), // boost::split(parts, str, boost::is_any_of(","),
@ -51,8 +78,28 @@ TEST(StringTest, Split_TokenCompressOn) {
EXPECT_EQ("", parts[3]); EXPECT_EQ("", parts[3]);
} }
TEST(StringTest, Split_TokensOnly) {
std::string str = ",,,,,";
std::vector<webcc::string_view> 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) { TEST(StringTest, Split_NewLine) {
std::vector<boost::string_view> lines; std::vector<webcc::string_view> lines;
webcc::Split("line one\nline two\nline 3", '\n', false, &lines); webcc::Split("line one\nline two\nline 3", '\n', false, &lines);
EXPECT_EQ(3, lines.size()); EXPECT_EQ(3, lines.size());
@ -64,8 +111,8 @@ TEST(StringTest, Split_NewLine) {
TEST(StringTest, SplitKV) { TEST(StringTest, SplitKV) {
const std::string str = "key=value"; const std::string str = "key=value";
std::string key; webcc::string_view key;
std::string value; webcc::string_view value;
bool ok = webcc::SplitKV(str, '=', true, &key, &value); bool ok = webcc::SplitKV(str, '=', true, &key, &value);
EXPECT_EQ(true, ok); EXPECT_EQ(true, ok);
@ -76,8 +123,8 @@ TEST(StringTest, SplitKV) {
TEST(StringTest, SplitKV_OtherDelim) { TEST(StringTest, SplitKV_OtherDelim) {
const std::string str = "key:value"; const std::string str = "key:value";
std::string key; webcc::string_view key;
std::string value; webcc::string_view value;
bool ok = webcc::SplitKV(str, ':', true, &key, &value); bool ok = webcc::SplitKV(str, ':', true, &key, &value);
EXPECT_TRUE(ok); EXPECT_TRUE(ok);
@ -89,8 +136,8 @@ TEST(StringTest, SplitKV_OtherDelim) {
TEST(StringTest, SplitKV_Spaces) { TEST(StringTest, SplitKV_Spaces) {
const std::string str = " key = value "; const std::string str = " key = value ";
std::string key; webcc::string_view key;
std::string value; webcc::string_view value;
bool ok = webcc::SplitKV(str, '=', true, &key, &value); bool ok = webcc::SplitKV(str, '=', true, &key, &value);
EXPECT_TRUE(ok); EXPECT_TRUE(ok);
@ -102,8 +149,8 @@ TEST(StringTest, SplitKV_Spaces) {
TEST(StringTest, SplitKV_SpacesNoTrim) { TEST(StringTest, SplitKV_SpacesNoTrim) {
const std::string str = " key = value "; const std::string str = " key = value ";
std::string key; webcc::string_view key;
std::string value; webcc::string_view value;
bool ok = webcc::SplitKV(str, '=', false, &key, &value); bool ok = webcc::SplitKV(str, '=', false, &key, &value);
EXPECT_TRUE(ok); EXPECT_TRUE(ok);
@ -115,8 +162,8 @@ TEST(StringTest, SplitKV_SpacesNoTrim) {
TEST(StringTest, SplitKV_NoKey) { TEST(StringTest, SplitKV_NoKey) {
const std::string str = "=value"; const std::string str = "=value";
std::string key; webcc::string_view key;
std::string value; webcc::string_view value;
bool ok = webcc::SplitKV(str, '=', true, &key, &value); bool ok = webcc::SplitKV(str, '=', true, &key, &value);
EXPECT_TRUE(ok); EXPECT_TRUE(ok);
@ -128,8 +175,8 @@ TEST(StringTest, SplitKV_NoKey) {
TEST(StringTest, SplitKV_NoValue) { TEST(StringTest, SplitKV_NoValue) {
const std::string str = "key="; const std::string str = "key=";
std::string key; webcc::string_view key;
std::string value; webcc::string_view value;
bool ok = webcc::SplitKV(str, '=', true, &key, &value); bool ok = webcc::SplitKV(str, '=', true, &key, &value);
EXPECT_TRUE(ok); EXPECT_TRUE(ok);
@ -141,8 +188,8 @@ TEST(StringTest, SplitKV_NoValue) {
TEST(StringTest, SplitKV_NoKeyNoValue) { TEST(StringTest, SplitKV_NoKeyNoValue) {
const std::string str = "="; const std::string str = "=";
std::string key; webcc::string_view key;
std::string value; webcc::string_view value;
bool ok = webcc::SplitKV(str, '=', true, &key, &value); bool ok = webcc::SplitKV(str, '=', true, &key, &value);
EXPECT_TRUE(ok); EXPECT_TRUE(ok);

@ -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(); std::string port = request_->port();
if (port.empty()) { if (port.empty()) {
port = default_port; port = ToString(default_port);
} }
LOG_VERB("Resolve host (%s)", request_->host().c_str()); LOG_VERB("Resolve host (%s)", request_->host().c_str());

@ -89,7 +89,7 @@ private:
void AsyncConnect(); void AsyncConnect();
void AsyncResolve(const std::string& default_port); void AsyncResolve(string_view default_port);
void OnResolve(boost::system::error_code ec, void OnResolve(boost::system::error_code ec,
boost::asio::ip::tcp::resolver::results_type endpoints); boost::asio::ip::tcp::resolver::results_type endpoints);

@ -128,7 +128,7 @@ void ClientSession::Stop() {
started_ = false; started_ = false;
} }
void ClientSession::Accept(const std::string& content_types) { void ClientSession::Accept(string_view content_types) {
if (!content_types.empty()) { if (!content_types.empty()) {
headers_.Set(headers::kAccept, content_types); headers_.Set(headers::kAccept, content_types);
} }
@ -168,18 +168,18 @@ void ClientSession::AcceptGzip(bool gzip) {
#endif // WEBCC_ENABLE_GZIP #endif // WEBCC_ENABLE_GZIP
void ClientSession::Auth(const std::string& type, void ClientSession::Auth(string_view type, string_view credentials) {
const std::string& credentials) { headers_.Set(headers::kAuthorization,
headers_.Set(headers::kAuthorization, type + " " + credentials); ToString(type) + " " + ToString(credentials));
} }
void ClientSession::AuthBasic(const std::string& login, void ClientSession::AuthBasic(string_view login, string_view password) {
const std::string& password) { auto credentials =
auto credentials = Base64Encode(login + ":" + password); Base64Encode(ToString(login) + ":" + ToString(password));
return Auth("Basic", credentials); return Auth("Basic", credentials);
} }
void ClientSession::AuthToken(const std::string& token) { void ClientSession::AuthToken(string_view token) {
return Auth("Token", token); return Auth("Token", token);
} }

@ -52,7 +52,7 @@ public:
buffer_size_ = buffer_size; 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); headers_.Set(key, value);
} }
@ -60,14 +60,13 @@ public:
// Only applied when: // Only applied when:
// - the request to send has no `Content-Type` header, and // - the request to send has no `Content-Type` header, and
// - the request has a body. // - the request has a body.
void SetContentType(const std::string& media_type, void SetContentType(string_view media_type, string_view charset = "") {
const std::string& charset = "") { media_type_ = ToString(media_type);
media_type_ = media_type; charset_ = ToString(charset);
charset_ = charset;
} }
// Set content types to accept. // Set content types to accept.
void Accept(const std::string& content_types); void Accept(string_view content_types);
#if WEBCC_ENABLE_GZIP #if WEBCC_ENABLE_GZIP
@ -77,13 +76,13 @@ public:
#endif // WEBCC_ENABLE_GZIP #endif // WEBCC_ENABLE_GZIP
// Set authorization. // Set authorization.
void Auth(const std::string& type, const std::string& credentials); void Auth(string_view type, string_view credentials);
// Set Basic authorization. // Set Basic authorization.
void AuthBasic(const std::string& login, const std::string& password); void AuthBasic(string_view login, string_view password);
// Set Token authorization. // Set Token authorization.
void AuthToken(const std::string& token); void AuthToken(string_view token);
// Send a request. // Send a request.
// Please use RequestBuilder to build the request. // Please use RequestBuilder to build the request.

@ -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()) { if (value.empty()) {
return false; return false;
} }
auto it = Find(key); auto it = Find(key);
if (it != headers_.end()) { if (it != headers_.end()) {
it->second = value; it->second = ToString(value);
} else { } else {
headers_.push_back({ key, value }); headers_.push_back({ ToString(key), ToString(value) });
} }
return true; return true;
} }
bool Headers::Set(std::string&& key, std::string&& value) { bool Headers::Has(string_view key) const {
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 {
return const_cast<Headers*>(this)->Find(key) != headers_.end(); return const_cast<Headers*>(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<Headers*>(this)->Find(key); auto it = const_cast<Headers*>(this)->Find(key);
if (existed != nullptr) { if (existed != nullptr) {
@ -61,7 +46,7 @@ const std::string& Headers::Get(const std::string& key, bool* existed) const {
return s_no_value; return s_no_value;
} }
std::vector<Header>::iterator Headers::Find(const std::string& key) { std::vector<Header>::iterator Headers::Find(string_view key) {
auto it = headers_.begin(); auto it = headers_.begin();
for (; it != headers_.end(); ++it) { for (; it != headers_.end(); ++it) {
if (boost::iequals(it->first, key)) { if (boost::iequals(it->first, key)) {
@ -73,25 +58,23 @@ std::vector<Header>::iterator Headers::Find(const std::string& key) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static bool ParseValue(const std::string& str, const std::string& expected_key, static bool ParseValue(const std::string& str, const char* expected_key,
std::string* value) { string_view* value) {
std::string key; string_view key;
if (!SplitKV(str, '=', true, &key, value)) { if (!SplitKV(str, '=', true, &key, value)) {
return false; return false;
} }
if (key != expected_key) { if (key != expected_key) {
return false; return false;
} }
return !value->empty(); return !value->empty();
} }
ContentType::ContentType(const std::string& str) { ContentType::ContentType(string_view str) {
Init(str); Init(str);
} }
void ContentType::Parse(const std::string& str) { void ContentType::Parse(string_view str) {
Reset(); Reset();
Init(str); Init(str);
} }
@ -114,15 +97,15 @@ bool ContentType::Valid() const {
return true; return true;
} }
void ContentType::Init(const std::string& str) { void ContentType::Init(string_view str) {
std::string other; std::string other;
std::size_t pos = str.find(';'); std::size_t pos = str.find(';');
if (pos == std::string::npos) { if (pos == str.npos) {
media_type_ = str; media_type_ = ToString(str);
} else { } else {
media_type_ = str.substr(0, pos); media_type_ = ToString(str.substr(0, pos));
other = str.substr(pos + 1); other = ToString(str.substr(pos + 1));
} }
boost::trim(media_type_); boost::trim(media_type_);
@ -130,26 +113,31 @@ void ContentType::Init(const std::string& str) {
if (media_type_ == "multipart/form-data") { if (media_type_ == "multipart/form-data") {
multipart_ = true; multipart_ = true;
if (!ParseValue(other, "boundary", &additional_)) { string_view boundary;
LOG_ERRO("Invalid 'multipart/form-data' content-type (no boundary)."); if (ParseValue(other, "boundary", &boundary)) {
additional_ = ToString(boundary);
LOG_INFO("Content-type multipart boundary: %s", additional_.c_str());
} else { } else {
LOG_INFO("Content-type multipart boundary: %s.", additional_.c_str()); LOG_ERRO("Invalid 'multipart/form-data' content-type (no boundary)");
} }
} else { } else {
if (ParseValue(other, "charset", &additional_)) { string_view charset;
LOG_INFO("Content-type charset: %s.", additional_.c_str()); 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) { static inline void Unquote(std::string& str) {
boost::trim_if(str, boost::is_any_of("\"")); boost::trim_if(str, boost::is_any_of("\""));
} }
bool ContentDisposition::Init(const std::string& str) { bool ContentDisposition::Init(string_view str) {
std::vector<boost::string_view> parts; std::vector<string_view> parts;
Split(str, ';', false, &parts); Split(str, ';', false, &parts);
if (parts.empty()) { if (parts.empty()) {
@ -160,18 +148,18 @@ bool ContentDisposition::Init(const std::string& str) {
return false; return false;
} }
std::string key; string_view key;
std::string value; string_view value;
for (std::size_t i = 1; i < parts.size(); ++i) { 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; return false;
} }
if (key == "name") { if (key == "name") {
name_ = value; name_ = ToString(value);
Unquote(name_); Unquote(name_);
} else if (key == "filename") { } else if (key == "filename") {
file_name_ = value; file_name_ = ToString(value);
Unquote(file_name_); Unquote(file_name_);
} }
} }
@ -181,24 +169,24 @@ bool ContentDisposition::Init(const std::string& str) {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
FormPartPtr FormPart::New(const std::string& name, std::string&& data, FormPartPtr FormPart::New(string_view name, std::string&& data,
const std::string& media_type) { string_view media_type) {
auto form_part = std::make_shared<FormPart>(); auto form_part = std::make_shared<FormPart>();
form_part->name_ = name; form_part->name_ = ToString(name);
form_part->data_ = std::move(data); form_part->data_ = std::move(data);
form_part->media_type_ = media_type; form_part->media_type_ = ToString(media_type);
return form_part; return form_part;
} }
FormPartPtr FormPart::NewFile(const std::string& name, const fs::path& path, FormPartPtr FormPart::NewFile(string_view name, const fs::path& path,
const std::string& media_type) { string_view media_type) {
auto form_part = std::make_shared<FormPart>(); auto form_part = std::make_shared<FormPart>();
form_part->name_ = name; form_part->name_ = ToString(name);
form_part->path_ = path; form_part->path_ = path;
form_part->media_type_ = media_type; form_part->media_type_ = ToString(media_type);
// Determine file name from file path. // Determine file name from file path.
// TODO: encoding // TODO: encoding
@ -288,7 +276,7 @@ std::size_t FormPart::GetDataSize() {
return size; 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()) { for (auto& h : headers_.data()) {
os << prefix << h.first << ": " << h.second << std::endl; os << prefix << h.first << ": " << h.second << std::endl;
} }

@ -29,11 +29,9 @@ public:
return headers_; 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(string_view key) const;
bool Has(const std::string& key) const;
// Get header by index. // Get header by index.
const Header& Get(std::size_t index) const { const Header& Get(std::size_t index) const {
@ -44,14 +42,14 @@ public:
// Get header value by key. // Get header value by key.
// If there's no such header with the given key, besides return empty, the // If there's no such header with the given key, besides return empty, the
// optional |existed| parameter will be set to false. // 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() { void Clear() {
headers_.clear(); headers_.clear();
} }
private: private:
std::vector<Header>::iterator Find(const std::string& key); std::vector<Header>::iterator Find(string_view key);
std::vector<Header> headers_; std::vector<Header> headers_;
}; };
@ -64,9 +62,9 @@ private:
// Content-Type: multipart/form-data; boundary=something // Content-Type: multipart/form-data; boundary=something
class ContentType { class ContentType {
public: 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(); void Reset();
@ -91,7 +89,7 @@ public:
} }
private: private:
void Init(const std::string& str); void Init(string_view str);
private: private:
std::string media_type_; std::string media_type_;
@ -109,7 +107,7 @@ private:
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
class ContentDisposition { class ContentDisposition {
public: public:
explicit ContentDisposition(const std::string& str) { explicit ContentDisposition(string_view str) {
valid_ = Init(str); valid_ = Init(str);
} }
@ -126,7 +124,7 @@ public:
} }
private: private:
bool Init(const std::string& str); bool Init(string_view str);
private: private:
std::string name_; std::string name_;
@ -151,14 +149,14 @@ public:
// The data will be moved, no file name is needed. // 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 // The media type is optional. If the data is a JSON string, you can specify
// media type as "application/json". // media type as "application/json".
static FormPartPtr New(const std::string& name, std::string&& data, static FormPartPtr New(string_view name, std::string&& data,
const std::string& media_type = ""); string_view media_type = "");
// Construct a file part. // Construct a file part.
// The file name will be extracted from path. // The file name will be extracted from path.
// The media type, if not provided, will be inferred from file extension. // The media type, if not provided, will be inferred from file extension.
static FormPartPtr NewFile(const std::string& name, const fs::path& path, static FormPartPtr NewFile(string_view name, const fs::path& path,
const std::string& media_type = ""); string_view media_type = "");
// API: SERVER // API: SERVER
const std::string& name() const { const std::string& name() const {
@ -214,7 +212,7 @@ public:
std::size_t GetDataSize(); std::size_t GetDataSize();
// Dump to output stream for logging purpose. // 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: private:
// Generate headers from properties. // Generate headers from properties.

@ -17,7 +17,10 @@
// Set 1/0 to enable/disable GZIP compression. // Set 1/0 to enable/disable GZIP compression.
#define WEBCC_ENABLE_GZIP 0 #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 #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_ #endif // WEBCC_CONFIG_H_

@ -19,7 +19,10 @@
// Set 1/0 to enable/disable GZIP compression. // Set 1/0 to enable/disable GZIP compression.
#define WEBCC_ENABLE_GZIP @WEBCC_ENABLE_GZIP@ #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@ #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_ #endif // WEBCC_CONFIG_H_

@ -24,10 +24,11 @@ void Connection::Start() {
boost::system::error_code ec; boost::system::error_code ec;
auto endpoint = socket_.remote_endpoint(ec); auto endpoint = socket_.remote_endpoint(ec);
if (!ec) { if (!ec) {
request_->set_ip(endpoint.address().to_string()); request_->set_address(endpoint.address().to_string());
} }
request_parser_.Init(request_.get(), view_matcher_); request_parser_.Init(request_.get(), view_matcher_);
AsyncRead(); AsyncRead();
} }

@ -12,10 +12,32 @@
#include "webcc/config.h" #include "webcc/config.h"
#if WEBCC_USE_STD_STRING_VIEW
#include <string_view>
#else
#include "boost/utility/string_view.hpp"
#endif // WEBCC_USE_STD_STRING_VIEW
namespace webcc { 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<std::string>; using Strings = std::vector<std::string>;
// Regex sub-matches of the URL (usually resource ID's). // Regex sub-matches of the URL (usually resource ID's).
@ -169,7 +191,7 @@ public:
}; };
public: public:
explicit Error(Code code = kOK, const std::string& message = "") explicit Error(Code code = kOK, string_view message = "")
: code_(code), message_(message) { : code_(code), message_(message) {
} }
@ -186,9 +208,9 @@ public:
return message_; return message_;
} }
void Set(Code code, const std::string& message) { void Set(Code code, string_view message) {
code_ = code; code_ = code;
message_ = message; message_ = ToString(message);
} }
bool timeout() const { bool timeout() const {

@ -135,7 +135,7 @@ static const bool g_terminal_has_color = []() {
static std::string DoGetThreadID() { static std::string DoGetThreadID() {
#if (defined(_WIN32) || defined(_WIN64)) #if (defined(_WIN32) || defined(_WIN64))
auto thread_id = std::this_thread::get_id(); auto thread_id = std::this_thread::get_id();
std::stringstream ss; std::ostringstream ss;
ss << thread_id; ss << thread_id;
return ss.str(); return ss.str();
#else #else
@ -184,7 +184,7 @@ static std::string GetTimestamp() {
auto now = system_clock::now(); auto now = system_clock::now();
std::time_t t = system_clock::to_time_t(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"); ss << std::put_time(std::localtime(&t), "%Y-%m-%d %H:%M:%S");
milliseconds milli_seconds = milliseconds milli_seconds =

@ -31,6 +31,7 @@ void Message::SetBody(BodyPtr body, bool set_length) {
const std::string& Message::data() const { const std::string& Message::data() const {
static const std::string kEmptyData; static const std::string kEmptyData;
auto string_body = std::dynamic_pointer_cast<StringBody>(body_); auto string_body = std::dynamic_pointer_cast<StringBody>(body_);
if (!string_body) { if (!string_body) {
return kEmptyData; return kEmptyData;
@ -43,54 +44,42 @@ std::shared_ptr<FileBody> Message::file_body() const {
} }
bool Message::IsConnectionKeepAlive() const { bool Message::IsConnectionKeepAlive() const {
using headers::kConnection;
bool existed = false; bool existed = false;
const std::string& connection = GetHeader(kConnection, &existed); const auto& connection = GetHeader(headers::kConnection, &existed);
if (!existed) { if (!existed) {
// Keep-Alive is by default for HTTP/1.1. // Keep-Alive is by default for HTTP/1.1.
return true; return true;
} }
if (boost::iequals(connection, "Keep-Alive")) { return boost::iequals(connection, "Keep-Alive");
return true;
}
return false;
} }
ContentEncoding Message::GetContentEncoding() const { ContentEncoding Message::GetContentEncoding() const {
using headers::kContentEncoding; const auto& encoding = GetHeader(headers::kContentEncoding);
const std::string& encoding = GetHeader(kContentEncoding);
if (encoding == "gzip") { if (encoding == "gzip") {
return ContentEncoding::kGzip; return ContentEncoding::kGzip;
} } else if (encoding == "deflate") {
if (encoding == "deflate") {
return ContentEncoding::kDeflate; return ContentEncoding::kDeflate;
} else {
return ContentEncoding::kUnknown;
} }
return ContentEncoding::kUnknown;
} }
bool Message::AcceptEncodingGzip() const { bool Message::AcceptEncodingGzip() const {
using headers::kAcceptEncoding; return GetHeader(headers::kAcceptEncoding).find("gzip") != std::string::npos;
return GetHeader(kAcceptEncoding).find("gzip") != std::string::npos;
} }
void Message::SetContentType(const std::string& media_type, void Message::SetContentType(string_view media_type, string_view charset) {
const std::string& charset) {
using headers::kContentType;
if (!media_type.empty()) { if (!media_type.empty()) {
if (charset.empty()) { if (charset.empty()) {
SetHeader(kContentType, media_type); SetHeader(headers::kContentType, media_type);
} else { } 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 { 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()) { 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::string Message::Dump() const {
std::stringstream ss; std::ostringstream ss;
Dump(ss); Dump(ss);
return ss.str(); return ss.str();
} }

@ -21,51 +21,30 @@ public:
virtual ~Message() = default; virtual ~Message() = default;
// --------------------------------------------------------------------------- const std::string& start_line() const {
return start_line_;
void SetBody(BodyPtr body, bool set_length);
BodyPtr body() const {
return body_;
} }
// Get the data from the (string) body. void set_start_line(string_view start_line) {
// Return empty string if the body is not a StringBody. start_line_ = ToString(start_line);
const std::string& data() const; }
// Get the body as a FileBody.
// Return null if the body is not a FileBody.
std::shared_ptr<FileBody> file_body() const;
// ---------------------------------------------------------------------------
void SetHeader(Header&& header) { void SetHeader(Header&& header) {
headers_.Set(std::move(header.first), std::move(header.second)); 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); headers_.Set(key, value);
} }
const std::string& GetHeader(const std::string& key, const std::string& GetHeader(string_view key, bool* existed = nullptr) const {
bool* existed = nullptr) const {
return headers_.Get(key, existed); return headers_.Get(key, existed);
} }
bool HasHeader(const std::string& key) const { bool HasHeader(string_view key) const {
return headers_.Has(key); 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 { std::size_t content_length() const {
return content_length_; return content_length_;
} }
@ -74,7 +53,19 @@ public:
content_length_ = content_length; 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<FileBody> file_body() const;
// Check `Connection` header to see if it's "Keep-Alive". // Check `Connection` header to see if it's "Keep-Alive".
bool IsConnectionKeepAlive() const; bool IsConnectionKeepAlive() const;
@ -88,16 +79,13 @@ public:
// Set `Content-Type` header. E.g., // Set `Content-Type` header. E.g.,
// SetContentType("application/json; charset=utf-8") // SetContentType("application/json; charset=utf-8")
void SetContentType(const std::string& content_type) { void SetContentType(string_view content_type) {
SetHeader(headers::kContentType, content_type); SetHeader(headers::kContentType, content_type);
} }
// Set `Content-Type` header. E.g., // Set `Content-Type` header. E.g.,
// SetContentType("application/json", "utf-8") // SetContentType("application/json", "utf-8")
void SetContentType(const std::string& media_type, void SetContentType(string_view media_type, string_view charset);
const std::string& charset);
// ---------------------------------------------------------------------------
// Make the message complete in order to be sent. // Make the message complete in order to be sent.
virtual void Prepare() = 0; virtual void Prepare() = 0;
@ -106,8 +94,6 @@ public:
// This doesn't include the payload(s) of the body! // This doesn't include the payload(s) of the body!
Payload GetPayload() const; Payload GetPayload() const;
// ---------------------------------------------------------------------------
// Dump to output stream for logging purpose. // Dump to output stream for logging purpose.
void Dump(std::ostream& os) const; void Dump(std::ostream& os) const;

@ -23,8 +23,8 @@ public:
return method_; return method_;
} }
void set_method(const std::string& method) { void set_method(string_view method) {
method_ = method; method_ = ToString(method);
} }
const Url& url() const { const Url& url() const {
@ -44,23 +44,23 @@ public:
} }
UrlQuery query() const { UrlQuery query() const {
return UrlQuery(url_.query()); return UrlQuery{ url_.query() };
} }
const UrlArgs& args() const { const UrlArgs& args() const {
return args_; return args_;
} }
void set_args(const UrlArgs& args) { void set_args(UrlArgs&& args) {
args_ = args; args_ = std::move(args);
} }
const std::string& ip() const { const std::string& address() const {
return ip_; return address_;
} }
void set_ip(const std::string& ip) { void set_address(std::string&& address) {
ip_ = ip; address_ = std::move(address);
} }
// Check if the body is a multi-part form data. // Check if the body is a multi-part form data.
@ -83,7 +83,7 @@ private:
UrlArgs args_; UrlArgs args_;
// Client IP address. // Client IP address.
std::string ip_; std::string address_;
}; };
using RequestPtr = std::shared_ptr<Request>; using RequestPtr = std::shared_ptr<Request>;

@ -19,7 +19,7 @@ RequestPtr RequestBuilder::operator()() {
request->set_url(std::move(url_)); request->set_url(std::move(url_));
for (std::size_t i = 1; i < headers_.size(); i += 2) { 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". // If no Keep-Alive, explicitly set `Connection` to "Close".
@ -34,7 +34,7 @@ RequestPtr RequestBuilder::operator()() {
if (gzip_ && body_->Compress()) { if (gzip_ && body_->Compress()) {
request->SetHeader(headers::kContentEncoding, "gzip"); request->SetHeader(headers::kContentEncoding, "gzip");
} }
#endif #endif // WEBCC_ENABLE_GZIP
} else if (!form_parts_.empty()) { } else if (!form_parts_.empty()) {
// Another choice to generate the boundary is like what Apache does. // Another choice to generate the boundary is like what Apache does.
// See: https://stackoverflow.com/a/5686863 // See: https://stackoverflow.com/a/5686863
@ -77,41 +77,41 @@ RequestBuilder& RequestBuilder::File(const fs::path& path,
return *this; return *this;
} }
RequestBuilder& RequestBuilder::FormFile(const std::string& name, RequestBuilder& RequestBuilder::FormFile(string_view name,
const fs::path& path, const fs::path& path,
const std::string& media_type) { string_view media_type) {
assert(!name.empty()); assert(!name.empty());
return Form(FormPart::NewFile(name, path, media_type)); return Form(FormPart::NewFile(name, path, media_type));
} }
RequestBuilder& RequestBuilder::FormData(const std::string& name, RequestBuilder& RequestBuilder::FormData(string_view name,
std::string&& data, std::string&& data,
const std::string& media_type) { string_view media_type) {
assert(!name.empty()); assert(!name.empty());
return Form(FormPart::New(name, std::move(data), media_type)); return Form(FormPart::New(name, std::move(data), media_type));
} }
RequestBuilder& RequestBuilder::Header(const std::string& key, RequestBuilder& RequestBuilder::Header(string_view key, string_view value) {
const std::string& value) { headers_.push_back(ToString(key));
headers_.push_back(key); headers_.push_back(ToString(value));
headers_.push_back(value);
return *this; return *this;
} }
RequestBuilder& RequestBuilder::Auth(const std::string& type, RequestBuilder& RequestBuilder::Auth(string_view type,
const std::string& credentials) { string_view credentials) {
headers_.push_back(headers::kAuthorization); headers_.push_back(headers::kAuthorization);
headers_.push_back(type + " " + credentials); headers_.push_back(ToString(type) + " " + ToString(credentials));
return *this; return *this;
} }
RequestBuilder& RequestBuilder::AuthBasic(const std::string& login, RequestBuilder& RequestBuilder::AuthBasic(string_view login,
const std::string& password) { string_view password) {
auto credentials = Base64Encode(login + ":" + password); auto credentials =
Base64Encode(ToString(login) + ":" + ToString(password));
return Auth("Basic", credentials); return Auth("Basic", credentials);
} }
RequestBuilder& RequestBuilder::AuthToken(const std::string& token) { RequestBuilder& RequestBuilder::AuthToken(string_view token) {
return Auth("Token", token); return Auth("Token", token);
} }

@ -39,44 +39,44 @@ public:
RequestBuilder(const RequestBuilder&) = delete; RequestBuilder(const RequestBuilder&) = delete;
RequestBuilder& operator=(const RequestBuilder&) = delete; RequestBuilder& operator=(const RequestBuilder&) = delete;
// Build // Build and return the request object.
RequestPtr operator()(); RequestPtr operator()();
RequestBuilder& Method(const std::string& method) { RequestBuilder& Method(string_view method) {
method_ = method; method_ = ToString(method);
return *this; 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); 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); 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); 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); 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); 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); 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 }; url_ = webcc::Url{ url, encode };
return *this; return *this;
} }
RequestBuilder& Port(const std::string& port) { RequestBuilder& Port(string_view port) {
url_.set_port(port); url_.set_port(port);
return *this; return *this;
} }
@ -87,25 +87,25 @@ public:
} }
// Append a piece to the path. // 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); url_.AppendPath(path, encode);
return *this; return *this;
} }
// Append a parameter to the query. // 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) { bool encode = false) {
url_.AppendQuery(key, value, encode); url_.AppendQuery(key, value, encode);
return *this; return *this;
} }
RequestBuilder& MediaType(const std::string& media_type) { RequestBuilder& MediaType(string_view media_type) {
media_type_ = media_type; media_type_ = ToString(media_type);
return *this; return *this;
} }
RequestBuilder& Charset(const std::string& charset) { RequestBuilder& Charset(string_view charset) {
charset_ = charset; charset_ = ToString(charset);
return *this; return *this;
} }
@ -123,7 +123,7 @@ public:
// Set (comma separated) content types to accept. // Set (comma separated) content types to accept.
// E.g., "application/json", "text/html, application/xhtml+xml". // 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); return Header(headers::kAccept, content_types);
} }
@ -156,26 +156,25 @@ public:
} }
// Add a form part of file. // Add a form part of file.
RequestBuilder& FormFile(const std::string& name, const fs::path& path, RequestBuilder& FormFile(string_view name, const fs::path& path,
const std::string& media_type = ""); string_view media_type = "");
// Add a form part of string data. // Add a form part of string data.
RequestBuilder& FormData(const std::string& name, std::string&& data, RequestBuilder& FormData(string_view name, std::string&& data,
const std::string& media_type = ""); 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) { RequestBuilder& KeepAlive(bool keep_alive = true) {
keep_alive_ = keep_alive; keep_alive_ = keep_alive;
return *this; 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, RequestBuilder& AuthBasic(string_view login, string_view password);
const std::string& password);
RequestBuilder& AuthToken(const std::string& token); RequestBuilder& AuthToken(string_view token);
// Add the `Date` header to the request. // Add the `Date` header to the request.
RequestBuilder& Date(); RequestBuilder& Date();

@ -36,15 +36,15 @@ bool RequestParser::OnHeadersEnd() {
} }
bool RequestParser::ParseStartLine(const std::string& line) { bool RequestParser::ParseStartLine(const std::string& line) {
std::vector<boost::string_view> parts; std::vector<string_view> parts;
Split(line, ' ', true, &parts); Split(line, ' ', true, &parts);
if (parts.size() != 3) { if (parts.size() != 3) {
return false; return false;
} }
request_->set_method(parts[0].to_string()); request_->set_method(parts[0]);
request_->set_url(Url{ parts[1].to_string() }); request_->set_url(Url{ parts[1] });
// HTTP version is ignored. // HTTP version is ignored.
@ -198,7 +198,7 @@ bool RequestParser::ParsePartHeaders(bool* need_more_data) {
// Parse Content-Disposition. // Parse Content-Disposition.
if (boost::iequals(header.first, headers::kContentDisposition)) { if (boost::iequals(header.first, headers::kContentDisposition)) {
ContentDisposition content_disposition(header.second); ContentDisposition content_disposition{ header.second };
if (!content_disposition.valid()) { if (!content_disposition.valid()) {
LOG_ERRO("Invalid content-disposition header: %s", LOG_ERRO("Invalid content-disposition header: %s",
header.second.c_str()); header.second.c_str());

@ -42,21 +42,22 @@ private:
std::size_t count, bool* end = nullptr) const; std::size_t count, bool* end = nullptr) const;
private: private:
Request* request_; // The result request message.
Request* request_ = nullptr;
// A function for matching view once the headers of a request has been // 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. // received. The parsing will stop and fail if no view can be matched.
ViewMatcher view_matcher_; ViewMatcher view_matcher_;
// Form data parsing steps. // Form data parsing steps.
enum Step { enum class Step {
kStart, kStart,
kBoundaryParsed, kBoundaryParsed,
kHeadersParsed, kHeadersParsed,
kEnded, kEnded,
}; };
Step step_ = kStart; Step step_ = Step::kStart;
// The current form part being parsed. // The current form part being parsed.
FormPartPtr part_; FormPartPtr part_;

@ -55,6 +55,12 @@ ResponseBuilder& ResponseBuilder::File(const fs::path& path,
return *this; 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() { ResponseBuilder& ResponseBuilder::Date() {
headers_.push_back(headers::kDate); headers_.push_back(headers::kDate);
headers_.push_back(utility::HttpDate()); headers_.push_back(utility::HttpDate());

@ -61,13 +61,13 @@ public:
return *this; return *this;
} }
ResponseBuilder& MediaType(const std::string& media_type) { ResponseBuilder& MediaType(string_view media_type) {
media_type_ = media_type; media_type_ = ToString(media_type);
return *this; return *this;
} }
ResponseBuilder& Charset(const std::string& charset) { ResponseBuilder& Charset(string_view charset) {
charset_ = charset; charset_ = ToString(charset);
return *this; return *this;
} }
@ -98,11 +98,7 @@ public:
ResponseBuilder& File(const fs::path& path, bool infer_media_type = true, ResponseBuilder& File(const fs::path& path, bool infer_media_type = true,
std::size_t chunk_size = 1024); std::size_t chunk_size = 1024);
ResponseBuilder& Header(const std::string& key, const std::string& value) { ResponseBuilder& Header(string_view key, string_view value);
headers_.push_back(key);
headers_.push_back(value);
return *this;
}
// Add the `Date` header to the response. // Add the `Date` header to the response.
ResponseBuilder& Date(); ResponseBuilder& Date();

@ -8,13 +8,12 @@
namespace webcc { namespace webcc {
bool Router::Route(const std::string& url, ViewPtr view, bool Router::Route(string_view url, ViewPtr view, const Strings& methods) {
const Strings& methods) {
assert(view); assert(view);
// TODO: More error check // TODO: More error check
routes_.push_back({ url, {}, view, methods }); routes_.push_back({ ToString(url), {}, view, methods });
return true; return true;
} }
@ -26,7 +25,6 @@ bool Router::Route(const UrlRegex& regex_url, ViewPtr view,
// TODO: More error check // TODO: More error check
try { try {
routes_.push_back({ "", regex_url(), view, methods }); routes_.push_back({ "", regex_url(), view, methods });
} catch (const std::regex_error& e) { } catch (const std::regex_error& e) {

@ -15,7 +15,7 @@ public:
// Route a URL to a view. // Route a URL to a view.
// The URL should start with "/". E.g., "/instances". // 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" }); const Strings& methods = { "GET" });
// Route a URL (as regular expression) to a view. // Route a URL (as regular expression) to a view.

@ -300,7 +300,7 @@ void Server::Handle(ConnectionPtr connection) {
} }
// Save the (regex matched) URL args to request object. // 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. // Ask the matched view to process the request.
ResponsePtr response = view->Handle(request); ResponsePtr response = view->Handle(request);

@ -36,19 +36,19 @@ bool ToSizeT(const std::string& str, int base, std::size_t* size) {
return true; return true;
} }
void Split(boost::string_view input, char delim, bool compress_token, void Split(string_view input, char delim, bool compress_token,
std::vector<boost::string_view>* output) { std::vector<string_view>* output) {
std::size_t i = 0; std::size_t i = 0;
std::size_t p = 0; std::size_t p = 0;
i = input.find(delim); i = input.find(delim);
while (i != boost::string_view::npos) { while (i != string_view::npos) {
output->emplace_back(input.substr(p, i - p)); output->emplace_back(input.substr(p, i - p));
p = i + 1; p = i + 1;
if (compress_token) { if (compress_token) {
while (input[p] == delim) { while (p < input.size() && input[p] == delim) {
++p; ++p;
} }
} }
@ -59,10 +59,19 @@ void Split(boost::string_view input, char delim, bool compress_token,
output->emplace_back(input.substr(p, i - p)); output->emplace_back(input.substr(p, i - p));
} }
bool SplitKV(const std::string& input, char delim, bool trim_spaces, void Trim(string_view& sv, const char* spaces) {
std::string* key, std::string* value) { 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); std::size_t pos = input.find(delim);
if (pos == std::string::npos) { if (pos == input.npos) {
return false; return false;
} }
@ -70,11 +79,23 @@ bool SplitKV(const std::string& input, char delim, bool trim_spaces,
*value = input.substr(pos + 1); *value = input.substr(pos + 1);
if (trim_spaces) { if (trim_spaces) {
boost::trim(*key); Trim(*key);
boost::trim(*value); Trim(*value);
} }
return true; 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 } // namespace webcc

@ -4,7 +4,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "boost/utility/string_view.hpp" #include "webcc/globals.h" // for string_view
namespace webcc { namespace webcc {
@ -15,15 +15,21 @@ std::string RandomString(std::size_t length);
// Just a wrapper of std::stoul. // Just a wrapper of std::stoul.
bool ToSizeT(const std::string& str, int base, std::size_t* size); bool ToSizeT(const std::string& str, int base, std::size_t* size);
void Trim(string_view& sv, const char* spaces = " ");
// Split string without copy. // Split string without copy.
// |compress_token| is the same as boost::token_compress_on for boost::split. // |compress_token| is the same as boost::token_compress_on for boost::split.
void Split(boost::string_view input, char delim, bool compress_token, void Split(string_view input, char delim, bool compress_token,
std::vector<boost::string_view>* output); std::vector<string_view>* 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. // Split a key-value string.
// E.g., split "Connection: Keep-Alive". // E.g., split "Connection: Keep-Alive".
// TODO: Use string_view (blocked by trim) bool SplitKV(string_view input, char delim, bool trim_spaces,
bool SplitKV(const std::string& input, char delim, bool trim_spaces,
std::string* key, std::string* value); std::string* key, std::string* value);
} // namespace webcc } // namespace webcc

@ -29,7 +29,7 @@ bool HexToDecimal(char hex, int* decimal) {
return true; 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) { for (auto iter = encoded.begin(); iter != encoded.end(); ++iter) {
if (*iter == '%') { if (*iter == '%') {
if (++iter == encoded.end()) { if (++iter == encoded.end()) {
@ -67,16 +67,16 @@ bool Decode(const std::string& encoded, std::string* raw) {
// Unsafe decode. // Unsafe decode.
// Return the original string on failure. // Return the original string on failure.
std::string DecodeUnsafe(const std::string& encoded) { std::string DecodeUnsafe(string_view encoded) {
std::string raw; std::string raw;
if (Decode(encoded, &raw)) { if (Decode(encoded, &raw)) {
return raw; return raw;
} }
return encoded; return ToString(encoded);
} }
// Encode all characters which should be 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<bool(int)> should_encode) { std::function<bool(int)> should_encode) {
const char kHex[] = "0123456789ABCDEF"; 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 // Our own implementation of alpha numeric instead of std::isalnum to avoid
// taking locale into account. // taking locale into account.
inline bool IsAlNum(int c) { 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 // 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 // are called unreserved. These include uppercase and lowercase letters, decimal
// digits, hyphen, period, underscore, and tilde. // digits, hyphen, period, underscore, and tilde.
inline bool IsUnreserved(int c) { 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. // 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; }); 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 EncodeImpl(utf8_str, [](int c) -> bool {
return !IsPathChar(c) || c == '%' || c == '+'; 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 EncodeImpl(utf8_str, [](int c) -> bool {
return !IsQueryChar(c) || c == '%' || c == '+'; 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 EncodeImpl(utf8_str, [](int c) -> bool {
return !IsUnreserved(c) && !IsReserved(c); 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) { if (encode) {
Parse(Url::EncodeFull(str)); Parse(Url::EncodeFull(str));
} else { } 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 == "/") { if (piece.empty() || piece == "/") {
return; return;
} }
@ -222,24 +224,23 @@ void Url::AppendPath(const std::string& piece, bool encode) {
if (encode) { if (encode) {
path_.append(Url::EncodePath(piece)); path_.append(Url::EncodePath(piece));
} else { } else {
path_.append(piece); path_.append(ToString(piece));
} }
} }
void Url::AppendQuery(const std::string& key, const std::string& value, void Url::AppendQuery(string_view key, string_view value, bool encode) {
bool encode) {
if (!query_.empty()) { if (!query_.empty()) {
query_ += "&"; query_ += "&";
} }
if (encode) { if (encode) {
query_ += Url::EncodeQuery(key) + "=" + Url::EncodeQuery(value); query_ += Url::EncodeQuery(key) + "=" + Url::EncodeQuery(value);
} else { } else {
query_ += key + "=" + value; query_ += ToString(key) + "=" + ToString(value);
} }
} }
void Url::Parse(const std::string& str) { void Url::Parse(string_view str) {
std::string tmp = str; std::string tmp = ToString(str);
boost::trim_left(tmp); boost::trim_left(tmp);
std::size_t p = std::string::npos; std::size_t p = std::string::npos;
@ -314,8 +315,8 @@ UrlQuery::UrlQuery(const std::string& encoded_str) {
i = j + 1; i = j + 1;
} }
std::string key; string_view key;
std::string value; string_view value;
if (SplitKV(kv, '=', false, &key, &value)) { if (SplitKV(kv, '=', false, &key, &value)) {
parameters_.push_back({ DecodeUnsafe(key), DecodeUnsafe(value) }); parameters_.push_back({ DecodeUnsafe(key), DecodeUnsafe(value) });
} }

@ -17,15 +17,15 @@ namespace webcc {
class Url { class Url {
public: public:
// Encode URL different components. // Encode URL different components.
static std::string EncodeHost(const std::string& utf8_str); static std::string EncodeHost(string_view utf8_str);
static std::string EncodePath(const std::string& utf8_str); static std::string EncodePath(string_view utf8_str);
static std::string EncodeQuery(const std::string& utf8_str); static std::string EncodeQuery(string_view utf8_str);
static std::string EncodeFull(const std::string& utf8_str); static std::string EncodeFull(string_view utf8_str);
public: public:
Url() = default; Url() = default;
explicit Url(const std::string& str, bool encode = false); explicit Url(string_view str, bool encode = false);
const std::string& scheme() const { const std::string& scheme() const {
return scheme_; return scheme_;
@ -47,19 +47,18 @@ public:
return query_; return query_;
} }
void set_port(const std::string& port) { void set_port(string_view port) {
port_ = port; port_ = ToString(port);
} }
// Append a piece of path. // 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. // Append a query parameter.
void AppendQuery(const std::string& key, const std::string& value, void AppendQuery(string_view key, string_view value, bool encode = false);
bool encode = false);
private: private:
void Parse(const std::string& str); void Parse(string_view str);
void Clear(); void Clear();
@ -125,12 +124,11 @@ private:
// Used by Server::Route(). // Used by Server::Route().
class UrlRegex { class UrlRegex {
public: public:
explicit UrlRegex(const std::string& url) : url_(url) { explicit UrlRegex(string_view url) : url_(url) {
} }
std::regex operator()() const { std::regex operator()() const {
std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase; std::regex::flag_type flags = std::regex::ECMAScript | std::regex::icase;
return std::regex{ url_, flags }; return std::regex{ url_, flags };
} }

@ -10,6 +10,8 @@
#include "webcc/string.h" #include "webcc/string.h"
#include "webcc/version.h" #include "webcc/version.h"
using boost::asio::ip::tcp;
namespace webcc { namespace webcc {
namespace utility { namespace utility {
@ -22,7 +24,7 @@ std::string HttpDate() {
std::time_t t = std::time(nullptr); std::time_t t = std::time(nullptr);
std::tm gmt = *std::gmtime(&t); std::tm gmt = *std::gmtime(&t);
std::stringstream date; std::ostringstream date;
date.imbue(std::locale::classic()); // Use classic C locale date.imbue(std::locale::classic()); // Use classic C locale
date << std::put_time(&gmt, "%a, %d %b %Y %H:%M:%S GMT"); date << std::put_time(&gmt, "%a, %d %b %Y %H:%M:%S GMT");
return date.str(); return date.str();
@ -54,9 +56,8 @@ bool ReadFile(const fs::path& path, std::string* output) {
return true; return true;
} }
void DumpByLine(const std::string& data, std::ostream& os, void DumpByLine(const std::string& data, std::ostream& os, string_view prefix) {
const std::string& prefix) { std::vector<string_view> lines;
std::vector<boost::string_view> lines;
Split(data, '\n', false, &lines); Split(data, '\n', false, &lines);
std::size_t size = 0; std::size_t size = 0;
@ -75,18 +76,17 @@ void DumpByLine(const std::string& data, std::ostream& os,
} }
} }
void PrintEndpoint(std::ostream& ostream, void PrintEndpoint(std::ostream& ostream, const tcp::endpoint& endpoint) {
const boost::asio::ip::tcp::endpoint& endpoint) {
ostream << endpoint; ostream << endpoint;
if (endpoint.protocol() == boost::asio::ip::tcp::v4()) { if (endpoint.protocol() == tcp::v4()) {
ostream << ", v4"; ostream << ", v4";
} else if (endpoint.protocol() == boost::asio::ip::tcp::v6()) { } else if (endpoint.protocol() == tcp::v6()) {
ostream << ", v6"; ostream << ", v6";
} }
} }
std::string EndpointToString(const boost::asio::ip::tcp::endpoint& endpoint) { std::string EndpointToString(const tcp::endpoint& endpoint) {
std::stringstream ss; std::ostringstream ss;
PrintEndpoint(ss, endpoint); PrintEndpoint(ss, endpoint);
return ss.str(); return ss.str();
} }

@ -29,8 +29,7 @@ bool ReadFile(const fs::path& path, std::string* output);
// Dump the string data line by line to achieve more readability. // Dump the string data line by line to achieve more readability.
// Also limit the maximum size of the data to be dumped. // Also limit the maximum size of the data to be dumped.
void DumpByLine(const std::string& data, std::ostream& os, void DumpByLine(const std::string& data, std::ostream& os, string_view prefix);
const std::string& prefix);
// Print TCP endpoint. // Print TCP endpoint.
// Usage: PrintEndpoint(std::cout, endpoint) // Usage: PrintEndpoint(std::cout, endpoint)

Loading…
Cancel
Save