introduce string_view
This commit is contained in:
+3
-2
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -39,11 +39,11 @@ int main(int argc, char* argv[]) {
|
||||
std::make_shared<BookListView>(),
|
||||
{ "GET", "POST" });
|
||||
|
||||
server.Route(webcc::R("/books/(\\d+)"),
|
||||
server.Route(webcc::R{ "/books/(\\d+)" },
|
||||
std::make_shared<BookDetailView>(photo_dir),
|
||||
{ "GET", "PUT", "DELETE" });
|
||||
|
||||
server.Route(webcc::R("/books/(\\d+)/photo"),
|
||||
server.Route(webcc::R{ "/books/(\\d+)/photo" },
|
||||
std::make_shared<BookPhotoView>(photo_dir),
|
||||
{ "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 "
|
||||
<< "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")());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -17,8 +17,7 @@ public:
|
||||
TEST(RouterTest, URL_RegexBasic) {
|
||||
webcc::Router router;
|
||||
|
||||
router.Route(webcc::R("/instance/(\\d+)"),
|
||||
std::make_shared<MyView>());
|
||||
router.Route(webcc::R{ "/instance/(\\d+)" }, std::make_shared<MyView>());
|
||||
|
||||
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<MyView>());
|
||||
|
||||
std::string url = "/study/1/series/2/instance/3";
|
||||
|
||||
+66
-19
@@ -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<boost::string_view> parts;
|
||||
std::vector<webcc::string_view> 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<boost::string_view> parts;
|
||||
std::vector<webcc::string_view> 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<boost::string_view> parts;
|
||||
std::vector<webcc::string_view> 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<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) {
|
||||
std::vector<boost::string_view> lines;
|
||||
std::vector<webcc::string_view> 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);
|
||||
|
||||
+2
-2
@@ -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());
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
+42
-54
@@ -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<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);
|
||||
|
||||
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<Header>::iterator Headers::Find(const std::string& key) {
|
||||
std::vector<Header>::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<Header>::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<boost::string_view> parts;
|
||||
bool ContentDisposition::Init(string_view str) {
|
||||
std::vector<string_view> 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<FormPart>();
|
||||
|
||||
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<FormPart>();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
+14
-16
@@ -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<Header>::iterator Find(const std::string& key);
|
||||
std::vector<Header>::iterator Find(string_view key);
|
||||
|
||||
std::vector<Header> 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.
|
||||
|
||||
@@ -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_
|
||||
|
||||
+4
-1
@@ -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_
|
||||
|
||||
+2
-1
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+25
-3
@@ -12,10 +12,32 @@
|
||||
|
||||
#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 {
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
#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>;
|
||||
|
||||
// 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 {
|
||||
|
||||
+2
-2
@@ -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 =
|
||||
|
||||
+20
-31
@@ -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<StringBody>(body_);
|
||||
if (!string_body) {
|
||||
return kEmptyData;
|
||||
@@ -43,54 +44,42 @@ std::shared_ptr<FileBody> 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;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
+33
-47
@@ -21,7 +21,37 @@ public:
|
||||
|
||||
virtual ~Message() = default;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
const std::string& start_line() const {
|
||||
return start_line_;
|
||||
}
|
||||
|
||||
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(string_view key, string_view value) {
|
||||
headers_.Set(key, value);
|
||||
}
|
||||
|
||||
const std::string& GetHeader(string_view key, bool* existed = nullptr) const {
|
||||
return headers_.Get(key, existed);
|
||||
}
|
||||
|
||||
bool HasHeader(string_view key) const {
|
||||
return headers_.Has(key);
|
||||
}
|
||||
|
||||
std::size_t content_length() const {
|
||||
return content_length_;
|
||||
}
|
||||
|
||||
void set_content_length(std::size_t content_length) {
|
||||
content_length_ = content_length;
|
||||
}
|
||||
|
||||
void SetBody(BodyPtr body, bool set_length);
|
||||
|
||||
@@ -37,45 +67,6 @@ public:
|
||||
// Return null if the body is not a FileBody.
|
||||
std::shared_ptr<FileBody> file_body() const;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void SetHeader(Header&& header) {
|
||||
headers_.Set(std::move(header.first), std::move(header.second));
|
||||
}
|
||||
|
||||
void SetHeader(const std::string& key, const std::string& value) {
|
||||
headers_.Set(key, value);
|
||||
}
|
||||
|
||||
const std::string& GetHeader(const std::string& key,
|
||||
bool* existed = nullptr) const {
|
||||
return headers_.Get(key, existed);
|
||||
}
|
||||
|
||||
bool HasHeader(const std::string& 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_;
|
||||
}
|
||||
|
||||
void set_content_length(std::size_t content_length) {
|
||||
content_length_ = content_length;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
+10
-10
@@ -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<Request>;
|
||||
|
||||
+17
-17
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+26
-27
@@ -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();
|
||||
|
||||
@@ -36,15 +36,15 @@ bool RequestParser::OnHeadersEnd() {
|
||||
}
|
||||
|
||||
bool RequestParser::ParseStartLine(const std::string& line) {
|
||||
std::vector<boost::string_view> parts;
|
||||
std::vector<string_view> 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());
|
||||
|
||||
@@ -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_;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
+2
-4
@@ -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) {
|
||||
|
||||
+1
-1
@@ -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.
|
||||
|
||||
+1
-1
@@ -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);
|
||||
|
||||
+30
-9
@@ -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<boost::string_view>* output) {
|
||||
void Split(string_view input, char delim, bool compress_token,
|
||||
std::vector<string_view>* 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
|
||||
|
||||
+11
-5
@@ -4,7 +4,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<boost::string_view>* output);
|
||||
void Split(string_view input, char delim, bool compress_token,
|
||||
std::vector<string_view>* output);
|
||||
|
||||
// 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, string_view* key,
|
||||
string_view* value);
|
||||
|
||||
// Split a key-value string.
|
||||
// E.g., split "Connection: Keep-Alive".
|
||||
bool SplitKV(string_view input, char delim, bool trim_spaces,
|
||||
std::string* key, std::string* value);
|
||||
|
||||
} // namespace webcc
|
||||
|
||||
+21
-20
@@ -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<bool(int)> 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) });
|
||||
}
|
||||
|
||||
+11
-13
@@ -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 };
|
||||
}
|
||||
|
||||
|
||||
+10
-10
@@ -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<boost::string_view> lines;
|
||||
void DumpByLine(const std::string& data, std::ostream& os, string_view prefix) {
|
||||
std::vector<string_view> 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();
|
||||
}
|
||||
|
||||
+1
-2
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user