Fix URL encoding issues; remove request shortcuts.
This commit is contained in:
+19
-42
@@ -51,22 +51,6 @@ TEST(ClientTest, Head) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ClientTest, Head_Shortcut) {
|
|
||||||
webcc::ClientSession session;
|
|
||||||
|
|
||||||
try {
|
|
||||||
auto r = session.Head("http://httpbin.org/get");
|
|
||||||
|
|
||||||
EXPECT_EQ(webcc::Status::kOK, r->status());
|
|
||||||
EXPECT_EQ("OK", r->reason());
|
|
||||||
|
|
||||||
EXPECT_EQ("", r->data());
|
|
||||||
|
|
||||||
} catch (const webcc::Error& error) {
|
|
||||||
std::cerr << error << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Force Accept-Encoding to be "identity" so that HttpBin.org will include
|
// Force Accept-Encoding to be "identity" so that HttpBin.org will include
|
||||||
// a Content-Length header in the response.
|
// a Content-Length header in the response.
|
||||||
// This tests that the response with Content-Length while no body could be
|
// This tests that the response with Content-Length while no body could be
|
||||||
@@ -136,15 +120,25 @@ TEST(ClientTest, Get) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ClientTest, Get_Shortcut) {
|
// Test the space in the query string could be encoded.
|
||||||
|
TEST(ClientTest, Get_QueryEncode) {
|
||||||
webcc::ClientSession session;
|
webcc::ClientSession session;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto r = session.Get("http://httpbin.org/get",
|
auto r = session.Request(webcc::RequestBuilder{}.
|
||||||
{ "key1", "value1", "key2", "value2" },
|
Get("http://httpbin.org/get").
|
||||||
{ "Accept", "application/json" });
|
Query("name", "Chunting Gu", true)
|
||||||
|
());
|
||||||
|
|
||||||
AssertGet(r);
|
EXPECT_EQ(webcc::Status::kOK, r->status());
|
||||||
|
EXPECT_EQ("OK", r->reason());
|
||||||
|
|
||||||
|
Json::Value json = StringToJson(r->data());
|
||||||
|
|
||||||
|
Json::Value args = json["args"];
|
||||||
|
|
||||||
|
EXPECT_EQ(1, args.size());
|
||||||
|
EXPECT_EQ("Chunting Gu", args["name"].asString());
|
||||||
|
|
||||||
} catch (const webcc::Error& error) {
|
} catch (const webcc::Error& error) {
|
||||||
std::cerr << error << std::endl;
|
std::cerr << error << std::endl;
|
||||||
@@ -323,26 +317,6 @@ TEST(ClientTest, Post) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(ClientTest, Post_Shortcut) {
|
|
||||||
webcc::ClientSession session;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const std::string data = "{'name'='Adam', 'age'=20}";
|
|
||||||
|
|
||||||
auto r = session.Post("http://httpbin.org/post", std::string(data), true);
|
|
||||||
|
|
||||||
EXPECT_EQ(webcc::Status::kOK, r->status());
|
|
||||||
EXPECT_EQ("OK", r->reason());
|
|
||||||
|
|
||||||
Json::Value json = StringToJson(r->data());
|
|
||||||
|
|
||||||
EXPECT_EQ(data, json["data"].asString());
|
|
||||||
|
|
||||||
} catch (const webcc::Error& error) {
|
|
||||||
std::cerr << error << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bfs::path GenerateTempFile(const std::string& data) {
|
static bfs::path GenerateTempFile(const std::string& data) {
|
||||||
try {
|
try {
|
||||||
bfs::path path = bfs::temp_directory_path() / bfs::unique_path();
|
bfs::path path = bfs::temp_directory_path() / bfs::unique_path();
|
||||||
@@ -424,7 +398,10 @@ TEST(ClientTest, Post_Gzip) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Use Boost.org home page as the POST data.
|
// Use Boost.org home page as the POST data.
|
||||||
auto r1 = session.Get("https://www.boost.org/");
|
auto r1 = session.Request(webcc::RequestBuilder{}.
|
||||||
|
Get("https://www.boost.org/")
|
||||||
|
());
|
||||||
|
|
||||||
const std::string& data = r1->data();
|
const std::string& data = r1->data();
|
||||||
|
|
||||||
auto r2 = session.Request(webcc::RequestBuilder{}.
|
auto r2 = session.Request(webcc::RequestBuilder{}.
|
||||||
|
|||||||
@@ -14,22 +14,14 @@ int main() {
|
|||||||
try {
|
try {
|
||||||
r = session.Request(webcc::RequestBuilder{}.
|
r = session.Request(webcc::RequestBuilder{}.
|
||||||
Get("http://httpbin.org/get").
|
Get("http://httpbin.org/get").
|
||||||
Query("key1", "value1").
|
Query("name", "Adam Gu", /*encode*/true).
|
||||||
Query("key2", "value2").
|
Header("Accept", "application/json").
|
||||||
Date().
|
Date()
|
||||||
Header("Accept", "application/json")
|
|
||||||
());
|
());
|
||||||
|
|
||||||
assert(r->status() == webcc::Status::kOK);
|
assert(r->status() == webcc::Status::kOK);
|
||||||
assert(!r->data().empty());
|
assert(!r->data().empty());
|
||||||
|
|
||||||
r = session.Get("http://httpbin.org/get",
|
|
||||||
{ "key1", "value1", "key2", "value2" },
|
|
||||||
{ "Accept", "application/json" });
|
|
||||||
|
|
||||||
assert(r->status() == webcc::Status::kOK);
|
|
||||||
assert(!r->data().empty());
|
|
||||||
|
|
||||||
r = session.Request(webcc::RequestBuilder{}.
|
r = session.Request(webcc::RequestBuilder{}.
|
||||||
Post("http://httpbin.org/post").
|
Post("http://httpbin.org/post").
|
||||||
Body("{'name'='Adam', 'age'=20}").
|
Body("{'name'='Adam', 'age'=20}").
|
||||||
@@ -41,7 +33,9 @@ int main() {
|
|||||||
|
|
||||||
#if WEBCC_ENABLE_SSL
|
#if WEBCC_ENABLE_SSL
|
||||||
|
|
||||||
r = session.Get("https://httpbin.org/get");
|
r = session.Request(webcc::RequestBuilder{}.
|
||||||
|
Get("https://httpbin.org/get")
|
||||||
|
());
|
||||||
|
|
||||||
assert(r->status() == webcc::Status::kOK);
|
assert(r->status() == webcc::Status::kOK);
|
||||||
assert(!r->data().empty());
|
assert(!r->data().empty());
|
||||||
@@ -51,9 +45,6 @@ int main() {
|
|||||||
} catch (const webcc::Error& error) {
|
} catch (const webcc::Error& error) {
|
||||||
std::cerr << error << std::endl;
|
std::cerr << error << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
} catch (const std::exception& e) {
|
|
||||||
std::cerr << e.what() << std::endl;
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ void PrettyPrintJsonString(const std::string& str) {
|
|||||||
// List public events.
|
// List public events.
|
||||||
void ListEvents(webcc::ClientSession& session) {
|
void ListEvents(webcc::ClientSession& session) {
|
||||||
try {
|
try {
|
||||||
auto r = session.Get(kUrlRoot + "/events");
|
auto r = session.Request(webcc::RequestBuilder{}.
|
||||||
|
Get(kUrlRoot).Path("events")
|
||||||
|
());
|
||||||
|
|
||||||
PRINT_JSON_STRING(r->data());
|
PRINT_JSON_STRING(r->data());
|
||||||
|
|
||||||
@@ -66,10 +68,13 @@ void ListEvents(webcc::ClientSession& session) {
|
|||||||
|
|
||||||
// List the followers of the given user.
|
// List the followers of the given user.
|
||||||
// Example:
|
// Example:
|
||||||
// ListUserFollowers(session, "<login>")
|
// ListUserFollowers(session, "<user>")
|
||||||
void ListUserFollowers(webcc::ClientSession& session, const std::string& user) {
|
void ListUserFollowers(webcc::ClientSession& session, const std::string& user) {
|
||||||
try {
|
try {
|
||||||
auto r = session.Get(kUrlRoot + "/users/" + user + "/followers");
|
auto r = session.Request(webcc::RequestBuilder{}.
|
||||||
|
Get(kUrlRoot).Path("users").Path(user).
|
||||||
|
Path("followers")
|
||||||
|
());
|
||||||
|
|
||||||
PRINT_JSON_STRING(r->data());
|
PRINT_JSON_STRING(r->data());
|
||||||
|
|
||||||
@@ -86,7 +91,7 @@ void ListAuthUserFollowers(webcc::ClientSession& session,
|
|||||||
const std::string& password) {
|
const std::string& password) {
|
||||||
try {
|
try {
|
||||||
auto r = session.Request(webcc::RequestBuilder{}.
|
auto r = session.Request(webcc::RequestBuilder{}.
|
||||||
Get(kUrlRoot + "/user/followers").
|
Get(kUrlRoot).Path("user/followers").
|
||||||
AuthBasic(login, password)
|
AuthBasic(login, password)
|
||||||
());
|
());
|
||||||
|
|
||||||
@@ -108,7 +113,7 @@ void CreateAuthorization(webcc::ClientSession& session,
|
|||||||
"}";
|
"}";
|
||||||
|
|
||||||
auto r = session.Request(webcc::RequestBuilder{}.
|
auto r = session.Request(webcc::RequestBuilder{}.
|
||||||
Post(kUrlRoot + "/authorizations").
|
Post(kUrlRoot).Path("authorizations").
|
||||||
Body(std::move(data)).
|
Body(std::move(data)).
|
||||||
Json().Utf8().
|
Json().Utf8().
|
||||||
AuthBasic(login, password)
|
AuthBasic(login, password)
|
||||||
@@ -130,5 +135,8 @@ int main() {
|
|||||||
|
|
||||||
ListEvents(session);
|
ListEvents(session);
|
||||||
|
|
||||||
|
//ListUserFollowers(session, "sprinfall");
|
||||||
|
//ListAuthUserFollowers(session, "sprinfall@gmail.com", "<password>");
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ BookClient::BookClient(const std::string& url, int timeout)
|
|||||||
|
|
||||||
bool BookClient::ListBooks(std::list<Book>* books) {
|
bool BookClient::ListBooks(std::list<Book>* books) {
|
||||||
try {
|
try {
|
||||||
auto r = session_.Get(url_ + "/books");
|
auto r = session_.Request(WEBCC_GET(url_).Path("books")());
|
||||||
|
|
||||||
if (!CheckStatus(r, webcc::Status::kOK)) {
|
if (!CheckStatus(r, webcc::Status::kOK)) {
|
||||||
// Response HTTP status error.
|
// Response HTTP status error.
|
||||||
@@ -89,7 +89,9 @@ bool BookClient::CreateBook(const std::string& title, double price,
|
|||||||
req_json["price"] = price;
|
req_json["price"] = price;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto r = session_.Post(url_ + "/books", JsonToString(req_json), true);
|
auto r = session_.Request(WEBCC_POST(url_).Path("books").
|
||||||
|
Body(JsonToString(req_json))
|
||||||
|
());
|
||||||
|
|
||||||
if (!CheckStatus(r, webcc::Status::kCreated)) {
|
if (!CheckStatus(r, webcc::Status::kCreated)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -108,7 +110,7 @@ bool BookClient::CreateBook(const std::string& title, double price,
|
|||||||
|
|
||||||
bool BookClient::GetBook(const std::string& id, Book* book) {
|
bool BookClient::GetBook(const std::string& id, Book* book) {
|
||||||
try {
|
try {
|
||||||
auto r = session_.Get(url_ + "/books/" + id);
|
auto r = session_.Request(WEBCC_GET(url_).Path("books").Path(id)());
|
||||||
|
|
||||||
if (!CheckStatus(r, webcc::Status::kOK)) {
|
if (!CheckStatus(r, webcc::Status::kOK)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -129,7 +131,9 @@ bool BookClient::UpdateBook(const std::string& id, const std::string& title,
|
|||||||
json["price"] = price;
|
json["price"] = price;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto r = session_.Put(url_ + "/books/" + id, JsonToString(json), true);
|
auto r = session_.Request(WEBCC_PUT(url_).Path("books").Path(id).
|
||||||
|
Body(JsonToString(json))
|
||||||
|
());
|
||||||
|
|
||||||
if (!CheckStatus(r, webcc::Status::kOK)) {
|
if (!CheckStatus(r, webcc::Status::kOK)) {
|
||||||
return false;
|
return false;
|
||||||
@@ -145,7 +149,7 @@ bool BookClient::UpdateBook(const std::string& id, const std::string& title,
|
|||||||
|
|
||||||
bool BookClient::DeleteBook(const std::string& id) {
|
bool BookClient::DeleteBook(const std::string& id) {
|
||||||
try {
|
try {
|
||||||
auto r = session_.Delete(url_ + "/books/" + id);
|
auto r = session_.Request(WEBCC_DELETE(url_).Path("books").Path(id)());
|
||||||
|
|
||||||
if (!CheckStatus(r, webcc::Status::kOK)) {
|
if (!CheckStatus(r, webcc::Status::kOK)) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
#include "webcc/url.h"
|
#include "webcc/url.h"
|
||||||
|
|
||||||
TEST(UrlTest, Basic) {
|
TEST(UrlTest, Basic) {
|
||||||
webcc::Url url("http://example.com/path", false);
|
webcc::Url url("http://example.com/path");
|
||||||
|
|
||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
@@ -13,7 +13,7 @@ TEST(UrlTest, Basic) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(UrlTest, NoPath) {
|
TEST(UrlTest, NoPath) {
|
||||||
webcc::Url url("http://example.com", false);
|
webcc::Url url("http://example.com");
|
||||||
|
|
||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
@@ -23,7 +23,7 @@ TEST(UrlTest, NoPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(UrlTest, NoPath2) {
|
TEST(UrlTest, NoPath2) {
|
||||||
webcc::Url url("http://example.com/", false);
|
webcc::Url url("http://example.com/");
|
||||||
|
|
||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
@@ -33,7 +33,7 @@ TEST(UrlTest, NoPath2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(UrlTest, NoPath3) {
|
TEST(UrlTest, NoPath3) {
|
||||||
webcc::Url url("http://example.com?key=value", false);
|
webcc::Url url("http://example.com?key=value");
|
||||||
|
|
||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
@@ -43,7 +43,7 @@ TEST(UrlTest, NoPath3) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(UrlTest, NoPath4) {
|
TEST(UrlTest, NoPath4) {
|
||||||
webcc::Url url("http://example.com/?key=value", false);
|
webcc::Url url("http://example.com/?key=value");
|
||||||
|
|
||||||
EXPECT_EQ("http", url.scheme());
|
EXPECT_EQ("http", url.scheme());
|
||||||
EXPECT_EQ("example.com", url.host());
|
EXPECT_EQ("example.com", url.host());
|
||||||
@@ -53,7 +53,7 @@ TEST(UrlTest, NoPath4) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(UrlTest, NoScheme) {
|
TEST(UrlTest, NoScheme) {
|
||||||
webcc::Url url("/path/to", false);
|
webcc::Url url("/path/to");
|
||||||
|
|
||||||
EXPECT_EQ("", url.scheme());
|
EXPECT_EQ("", url.scheme());
|
||||||
EXPECT_EQ("", url.host());
|
EXPECT_EQ("", url.host());
|
||||||
@@ -63,7 +63,7 @@ TEST(UrlTest, NoScheme) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(UrlTest, NoScheme2) {
|
TEST(UrlTest, NoScheme2) {
|
||||||
webcc::Url url("/path/to?key=value", false);
|
webcc::Url url("/path/to?key=value");
|
||||||
|
|
||||||
EXPECT_EQ("", url.scheme());
|
EXPECT_EQ("", url.scheme());
|
||||||
EXPECT_EQ("", url.host());
|
EXPECT_EQ("", url.host());
|
||||||
@@ -73,7 +73,7 @@ TEST(UrlTest, NoScheme2) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST(UrlTest, Full) {
|
TEST(UrlTest, Full) {
|
||||||
webcc::Url url("https://localhost:3000/path/to?key=value", false);
|
webcc::Url url("https://localhost:3000/path/to?key=value");
|
||||||
|
|
||||||
EXPECT_EQ("https", url.scheme());
|
EXPECT_EQ("https", url.scheme());
|
||||||
EXPECT_EQ("localhost", url.host());
|
EXPECT_EQ("localhost", url.host());
|
||||||
|
|||||||
@@ -3,14 +3,92 @@
|
|||||||
#include "webcc/utility.h"
|
#include "webcc/utility.h"
|
||||||
|
|
||||||
TEST(UtilityTest, SplitKV) {
|
TEST(UtilityTest, SplitKV) {
|
||||||
const std::string str = "Connection: Keep-Alive";
|
const std::string str = "key=value";
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
bool ok = webcc::utility::SplitKV(str, '=', &key, &value);
|
||||||
|
|
||||||
|
EXPECT_EQ(true, ok);
|
||||||
|
EXPECT_EQ("key", key);
|
||||||
|
EXPECT_EQ("value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilityTest, SplitKV_OtherDelim) {
|
||||||
|
const std::string str = "key:value";
|
||||||
|
|
||||||
std::string key;
|
std::string key;
|
||||||
std::string value;
|
std::string value;
|
||||||
|
|
||||||
bool ok = webcc::utility::SplitKV(str, ':', &key, &value);
|
bool ok = webcc::utility::SplitKV(str, ':', &key, &value);
|
||||||
|
EXPECT_TRUE(ok);
|
||||||
|
|
||||||
EXPECT_EQ(true, ok);
|
EXPECT_EQ("key", key);
|
||||||
EXPECT_EQ("Connection", key);
|
EXPECT_EQ("value", value);
|
||||||
EXPECT_EQ("Keep-Alive", value);
|
}
|
||||||
|
|
||||||
|
TEST(UtilityTest, SplitKV_Spaces) {
|
||||||
|
const std::string str = " key = value ";
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
bool ok = webcc::utility::SplitKV(str, '=', &key, &value);
|
||||||
|
EXPECT_TRUE(ok);
|
||||||
|
|
||||||
|
EXPECT_EQ("key", key);
|
||||||
|
EXPECT_EQ("value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilityTest, SplitKV_SpacesNoTrim) {
|
||||||
|
const std::string str = " key = value ";
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
bool ok = webcc::utility::SplitKV(str, '=', &key, &value, false);
|
||||||
|
EXPECT_TRUE(ok);
|
||||||
|
|
||||||
|
EXPECT_EQ(" key ", key);
|
||||||
|
EXPECT_EQ(" value ", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilityTest, SplitKV_NoKey) {
|
||||||
|
const std::string str = "=value";
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
bool ok = webcc::utility::SplitKV(str, '=', &key, &value);
|
||||||
|
EXPECT_TRUE(ok);
|
||||||
|
|
||||||
|
EXPECT_EQ("", key);
|
||||||
|
EXPECT_EQ("value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilityTest, SplitKV_NoValue) {
|
||||||
|
const std::string str = "key=";
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
bool ok = webcc::utility::SplitKV(str, '=', &key, &value);
|
||||||
|
EXPECT_TRUE(ok);
|
||||||
|
|
||||||
|
EXPECT_EQ("key", key);
|
||||||
|
EXPECT_EQ("", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(UtilityTest, SplitKV_NoKeyNoValue) {
|
||||||
|
const std::string str = "=";
|
||||||
|
|
||||||
|
std::string key;
|
||||||
|
std::string value;
|
||||||
|
|
||||||
|
bool ok = webcc::utility::SplitKV(str, '=', &key, &value);
|
||||||
|
EXPECT_TRUE(ok);
|
||||||
|
|
||||||
|
EXPECT_EQ("", key);
|
||||||
|
EXPECT_EQ("", value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,104 +47,6 @@ ResponsePtr ClientSession::Request(RequestPtr request, bool stream) {
|
|||||||
return Send(request, stream);
|
return Send(request, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetHeaders(const Strings& headers, RequestBuilder* builder) {
|
|
||||||
assert(headers.size() % 2 == 0);
|
|
||||||
|
|
||||||
for (std::size_t i = 1; i < headers.size(); i += 2) {
|
|
||||||
builder->Header(headers[i - 1], headers[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponsePtr ClientSession::Get(const std::string& url,
|
|
||||||
const Strings& parameters,
|
|
||||||
const Strings& headers) {
|
|
||||||
RequestBuilder builder;
|
|
||||||
builder.Get(url);
|
|
||||||
|
|
||||||
assert(parameters.size() % 2 == 0);
|
|
||||||
for (std::size_t i = 1; i < parameters.size(); i += 2) {
|
|
||||||
builder.Query(parameters[i - 1], parameters[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetHeaders(headers, &builder);
|
|
||||||
|
|
||||||
return Request(builder());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponsePtr ClientSession::Head(const std::string& url,
|
|
||||||
const Strings& parameters,
|
|
||||||
const Strings& headers) {
|
|
||||||
RequestBuilder builder;
|
|
||||||
builder.Head(url);
|
|
||||||
|
|
||||||
assert(parameters.size() % 2 == 0);
|
|
||||||
for (std::size_t i = 1; i < parameters.size(); i += 2) {
|
|
||||||
builder.Query(parameters[i - 1], parameters[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetHeaders(headers, &builder);
|
|
||||||
|
|
||||||
return Request(builder());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponsePtr ClientSession::Post(const std::string& url, std::string&& data,
|
|
||||||
bool json, const Strings& headers) {
|
|
||||||
RequestBuilder builder;
|
|
||||||
builder.Post(url);
|
|
||||||
|
|
||||||
SetHeaders(headers, &builder);
|
|
||||||
|
|
||||||
builder.Body(std::move(data));
|
|
||||||
|
|
||||||
if (json) {
|
|
||||||
builder.Json();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Request(builder());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponsePtr ClientSession::Put(const std::string& url, std::string&& data,
|
|
||||||
bool json, const Strings& headers) {
|
|
||||||
RequestBuilder builder;
|
|
||||||
builder.Put(url);
|
|
||||||
|
|
||||||
SetHeaders(headers, &builder);
|
|
||||||
|
|
||||||
builder.Body(std::move(data));
|
|
||||||
|
|
||||||
if (json) {
|
|
||||||
builder.Json();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Request(builder());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponsePtr ClientSession::Delete(const std::string& url,
|
|
||||||
const Strings& headers) {
|
|
||||||
RequestBuilder builder;
|
|
||||||
builder.Delete(url);
|
|
||||||
|
|
||||||
SetHeaders(headers, &builder);
|
|
||||||
|
|
||||||
return Request(builder());
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponsePtr ClientSession::Patch(const std::string& url, std::string&& data,
|
|
||||||
bool json, const Strings& headers) {
|
|
||||||
RequestBuilder builder;
|
|
||||||
builder.Patch(url);
|
|
||||||
|
|
||||||
SetHeaders(headers, &builder);
|
|
||||||
|
|
||||||
builder.Body(std::move(data));
|
|
||||||
|
|
||||||
if (json) {
|
|
||||||
builder.Json();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Request(builder());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientSession::InitHeaders() {
|
void ClientSession::InitHeaders() {
|
||||||
using namespace headers;
|
using namespace headers;
|
||||||
|
|
||||||
|
|||||||
+1
-24
@@ -59,33 +59,10 @@ public:
|
|||||||
// Please use RequestBuilder to build the request.
|
// Please use RequestBuilder to build the request.
|
||||||
// If |stream| is true, the response data will be written into a temp file,
|
// If |stream| is true, the response data will be written into a temp file,
|
||||||
// the response body will be FileBody, and you can easily move the temp file
|
// the response body will be FileBody, and you can easily move the temp file
|
||||||
// to another path with FileBody::Move(). So |stream| is useful for
|
// to another path with FileBody::Move(). So, |stream| is really useful for
|
||||||
// downloading files (JPEG, etc.) or saving memory for huge data responses.
|
// downloading files (JPEG, etc.) or saving memory for huge data responses.
|
||||||
ResponsePtr Request(RequestPtr request, bool stream = false);
|
ResponsePtr Request(RequestPtr request, bool stream = false);
|
||||||
|
|
||||||
// Shortcut for GET request.
|
|
||||||
ResponsePtr Get(const std::string& url, const Strings& parameters = {},
|
|
||||||
const Strings& headers = {});
|
|
||||||
|
|
||||||
// Shortcut for HEAD request.
|
|
||||||
ResponsePtr Head(const std::string& url, const Strings& parameters = {},
|
|
||||||
const Strings& headers = {});
|
|
||||||
|
|
||||||
// Shortcut for POST request.
|
|
||||||
ResponsePtr Post(const std::string& url, std::string&& data, bool json,
|
|
||||||
const Strings& headers = {});
|
|
||||||
|
|
||||||
// Shortcut for PUT request.
|
|
||||||
ResponsePtr Put(const std::string& url, std::string&& data, bool json,
|
|
||||||
const Strings& headers = {});
|
|
||||||
|
|
||||||
// Shortcut for DELETE request.
|
|
||||||
ResponsePtr Delete(const std::string& url, const Strings& headers = {});
|
|
||||||
|
|
||||||
// Shortcut for PATCH request.
|
|
||||||
ResponsePtr Patch(const std::string& url, std::string&& data, bool json,
|
|
||||||
const Strings& headers = {});
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void InitHeaders();
|
void InitHeaders();
|
||||||
|
|
||||||
|
|||||||
+1
-4
@@ -31,10 +31,7 @@ void Request::Prepare() {
|
|||||||
target += url_.query();
|
target += url_.query();
|
||||||
}
|
}
|
||||||
|
|
||||||
start_line_ = method_;
|
start_line_ = method_ + " " + target + " HTTP/1.1";
|
||||||
start_line_ += " ";
|
|
||||||
start_line_ += target;
|
|
||||||
start_line_ += " HTTP/1.1";
|
|
||||||
|
|
||||||
if (url_.port().empty()) {
|
if (url_.port().empty()) {
|
||||||
SetHeader(headers::kHost, url_.host());
|
SetHeader(headers::kHost, url_.host());
|
||||||
|
|||||||
@@ -51,7 +51,25 @@ RequestPtr RequestBuilder::operator()() {
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& RequestBuilder::File(const Path& path, bool infer_media_type,
|
RequestBuilder& RequestBuilder::Url(const std::string& url, bool encode) {
|
||||||
|
url_ = webcc::Url{ url, encode };
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestBuilder& RequestBuilder::Path(const std::string& path, bool encode) {
|
||||||
|
url_.AppendPath(path, encode);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestBuilder& RequestBuilder::Query(const std::string& key,
|
||||||
|
const std::string& value,
|
||||||
|
bool encode) {
|
||||||
|
url_.AppendQuery(key, value, encode);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestBuilder& RequestBuilder::File(const webcc::Path& path,
|
||||||
|
bool infer_media_type,
|
||||||
std::size_t chunk_size) {
|
std::size_t chunk_size) {
|
||||||
body_.reset(new FileBody{ path, chunk_size });
|
body_.reset(new FileBody{ path, chunk_size });
|
||||||
|
|
||||||
@@ -63,7 +81,7 @@ RequestBuilder& RequestBuilder::File(const Path& path, bool infer_media_type,
|
|||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& RequestBuilder::FormFile(const std::string& name,
|
RequestBuilder& RequestBuilder::FormFile(const std::string& name,
|
||||||
const Path& path,
|
const webcc::Path& path,
|
||||||
const std::string& media_type) {
|
const std::string& media_type) {
|
||||||
assert(!name.empty());
|
assert(!name.empty());
|
||||||
return Form(FormPart::NewFile(name, path, media_type));
|
return Form(FormPart::NewFile(name, path, media_type));
|
||||||
|
|||||||
+40
-28
@@ -7,6 +7,24 @@
|
|||||||
#include "webcc/request.h"
|
#include "webcc/request.h"
|
||||||
#include "webcc/url.h"
|
#include "webcc/url.h"
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Handy macros for creating a RequestBuilder.
|
||||||
|
|
||||||
|
#define WEBCC_GET(url) webcc::RequestBuilder{}.Get(url, false)
|
||||||
|
#define WEBCC_GET_ENC(url) webcc::RequestBuilder{}.Get(url, true)
|
||||||
|
#define WEBCC_HEAD(url) webcc::RequestBuilder{}.Head(url, false)
|
||||||
|
#define WEBCC_HEAD_ENC(url) webcc::RequestBuilder{}.Head(url, true)
|
||||||
|
#define WEBCC_POST(url) webcc::RequestBuilder{}.Post(url, false)
|
||||||
|
#define WEBCC_POST_ENC(url) webcc::RequestBuilder{}.Post(url, true)
|
||||||
|
#define WEBCC_PUT(url) webcc::RequestBuilder{}.Put(url, false)
|
||||||
|
#define WEBCC_PUT_ENC(url) webcc::RequestBuilder{}.Put(url, true)
|
||||||
|
#define WEBCC_DELETE(url) webcc::RequestBuilder{}.Delete(url, false)
|
||||||
|
#define WEBCC_DELETE_ENC(url) webcc::RequestBuilder{}.Delete(url, true)
|
||||||
|
#define WEBCC_PATCH(url) webcc::RequestBuilder{}.Patch(url, false)
|
||||||
|
#define WEBCC_PATCH_ENC(url) webcc::RequestBuilder{}.Patch(url, true)
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
class RequestBuilder {
|
class RequestBuilder {
|
||||||
@@ -16,52 +34,46 @@ public:
|
|||||||
RequestBuilder(const RequestBuilder&) = delete;
|
RequestBuilder(const RequestBuilder&) = delete;
|
||||||
RequestBuilder& operator=(const RequestBuilder&) = delete;
|
RequestBuilder& operator=(const RequestBuilder&) = delete;
|
||||||
|
|
||||||
// Build the request.
|
// Build
|
||||||
RequestPtr operator()();
|
RequestPtr operator()();
|
||||||
|
|
||||||
// NOTE:
|
|
||||||
// The naming convention doesn't follow Google C++ Style for
|
|
||||||
// consistency and simplicity.
|
|
||||||
|
|
||||||
RequestBuilder& Method(const std::string& method) {
|
RequestBuilder& Method(const std::string& method) {
|
||||||
method_ = method;
|
method_ = method;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& Url(const std::string& url) {
|
RequestBuilder& Get(const std::string& url, bool encode = false) {
|
||||||
url_.Init(url);
|
return Method(methods::kGet).Url(url, encode);
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& Get(const std::string& url) {
|
RequestBuilder& Head(const std::string& url, bool encode = false) {
|
||||||
return Method(methods::kGet).Url(url);
|
return Method(methods::kHead).Url(url, encode);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& Head(const std::string& url) {
|
RequestBuilder& Post(const std::string& url, bool encode = false) {
|
||||||
return Method(methods::kHead).Url(url);
|
return Method(methods::kPost).Url(url, encode);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& Post(const std::string& url) {
|
RequestBuilder& Put(const std::string& url, bool encode = false) {
|
||||||
return Method(methods::kPost).Url(url);
|
return Method(methods::kPut).Url(url, encode);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& Put(const std::string& url) {
|
RequestBuilder& Delete(const std::string& url, bool encode = false) {
|
||||||
return Method(methods::kPut).Url(url);
|
return Method(methods::kDelete).Url(url, encode);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& Delete(const std::string& url) {
|
RequestBuilder& Patch(const std::string& url, bool encode = false) {
|
||||||
return Method(methods::kDelete).Url(url);
|
return Method(methods::kPatch).Url(url, encode);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBuilder& Patch(const std::string& url) {
|
RequestBuilder& Url(const std::string& url, bool encode = false);
|
||||||
return Method(methods::kPatch).Url(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a query parameter.
|
// Append a piece to the path.
|
||||||
RequestBuilder& Query(const std::string& key, const std::string& value) {
|
RequestBuilder& Path(const std::string& path, bool encode = false);
|
||||||
url_.AddQuery(key, value);
|
|
||||||
return *this;
|
// Append a parameter to the query.
|
||||||
}
|
RequestBuilder& Query(const std::string& key, const std::string& value,
|
||||||
|
bool encode = false);
|
||||||
|
|
||||||
RequestBuilder& MediaType(const std::string& media_type) {
|
RequestBuilder& MediaType(const std::string& media_type) {
|
||||||
media_type_ = media_type;
|
media_type_ = media_type;
|
||||||
@@ -97,7 +109,7 @@ public:
|
|||||||
|
|
||||||
// Use the file content as body.
|
// Use the file content as body.
|
||||||
// NOTE: Error::kFileError might be thrown.
|
// NOTE: Error::kFileError might be thrown.
|
||||||
RequestBuilder& File(const Path& path, bool infer_media_type = true,
|
RequestBuilder& File(const webcc::Path& path, bool infer_media_type = true,
|
||||||
std::size_t chunk_size = 1024);
|
std::size_t chunk_size = 1024);
|
||||||
|
|
||||||
// Add a form part.
|
// Add a form part.
|
||||||
@@ -107,7 +119,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add a form part of file.
|
// Add a form part of file.
|
||||||
RequestBuilder& FormFile(const std::string& name, const Path& path,
|
RequestBuilder& FormFile(const std::string& name, const webcc::Path& path,
|
||||||
const std::string& media_type = "");
|
const std::string& media_type = "");
|
||||||
|
|
||||||
// Add a form part of string data.
|
// Add a form part of string data.
|
||||||
|
|||||||
@@ -22,13 +22,9 @@ public:
|
|||||||
ResponseBuilder(const ResponseBuilder&) = delete;
|
ResponseBuilder(const ResponseBuilder&) = delete;
|
||||||
ResponseBuilder& operator=(const ResponseBuilder&) = delete;
|
ResponseBuilder& operator=(const ResponseBuilder&) = delete;
|
||||||
|
|
||||||
// Build the response.
|
// Build
|
||||||
ResponsePtr operator()();
|
ResponsePtr operator()();
|
||||||
|
|
||||||
// NOTE:
|
|
||||||
// The naming convention doesn't follow Google C++ Style for
|
|
||||||
// consistency and simplicity.
|
|
||||||
|
|
||||||
// Some shortcuts for different status codes:
|
// Some shortcuts for different status codes:
|
||||||
|
|
||||||
ResponseBuilder& OK() {
|
ResponseBuilder& OK() {
|
||||||
|
|||||||
+125
-93
@@ -1,15 +1,16 @@
|
|||||||
#include "webcc/url.h"
|
#include "webcc/url.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "boost/algorithm/string.hpp"
|
#include "boost/algorithm/string/trim.hpp"
|
||||||
|
|
||||||
|
#include "webcc/utility.h"
|
||||||
|
|
||||||
namespace webcc {
|
namespace webcc {
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// Helper functions to decode URL string.
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
@@ -63,58 +64,59 @@ bool Decode(const std::string& encoded, std::string* raw) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encodes all characters not in given set determined by given function.
|
// Unsafe decode.
|
||||||
std::string EncodeImpl(const std::string& raw,
|
// Return the original string on failure.
|
||||||
|
std::string DecodeUnsafe(const std::string& encoded) {
|
||||||
|
std::string raw;
|
||||||
|
if (Decode(encoded, &raw)) {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode all characters which should be encoded.
|
||||||
|
std::string EncodeImpl(const std::string& raw, // UTF8
|
||||||
std::function<bool(int)> should_encode) {
|
std::function<bool(int)> should_encode) {
|
||||||
const char* const hex = "0123456789ABCDEF";
|
const char kHex[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
std::string encoded;
|
std::string encoded;
|
||||||
|
|
||||||
for (auto iter = raw.begin(); iter != raw.end(); ++iter) {
|
for (auto i = raw.begin(); i != raw.end(); ++i) {
|
||||||
// For UTF8 encoded string, char ASCII can be greater than 127.
|
// For UTF8 encoded string, char ASCII can be greater than 127.
|
||||||
int ch = static_cast<unsigned char>(*iter);
|
int c = static_cast<int>(*i);
|
||||||
|
|
||||||
// |ch| should be the same under both UTF8 and UTF16.
|
if (should_encode(c)) {
|
||||||
if (should_encode(ch)) {
|
|
||||||
encoded.push_back('%');
|
encoded.push_back('%');
|
||||||
encoded.push_back(hex[(ch >> 4) & 0xF]);
|
encoded.push_back(kHex[(c >> 4) & 0xF]);
|
||||||
encoded.push_back(hex[ch & 0xF]);
|
encoded.push_back(kHex[c & 0xF]);
|
||||||
} else {
|
} else {
|
||||||
// ASCII doesn't need to be encoded, it should be the same under both
|
encoded.push_back(static_cast<char>(c));
|
||||||
// UTF8 and UTF16.
|
|
||||||
encoded.push_back(static_cast<char>(ch));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return encoded;
|
return encoded;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Our own implementation of alpha numeric instead of std::isalnum to avoid
|
// Characters that are allowed in a URI but do not have a reserved purpose are
|
||||||
// taking global lock for performance reasons.
|
// are called unreserved. These include uppercase and lowercase letters, decimal
|
||||||
inline bool IsAlphaNumeric(char c) {
|
// digits, hyphen, period, underscore, and tilde.
|
||||||
return (c >= '0' && c <= '9') ||
|
inline bool IsUnreserved(int c) {
|
||||||
(c >= 'A' && c <= 'Z') ||
|
return std::isalnum(c) || c == '-' || c == '.' || c == '_' || c == '~';
|
||||||
(c >= 'a' && c <= 'z');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unreserved characters are those that are allowed in a URL/URI but do not have
|
// General delimiters serve as the delimiters between different uri components.
|
||||||
// a reserved purpose. They include:
|
// General delimiters include:
|
||||||
// - A-Z
|
// - All of these :/?#[]@
|
||||||
// - a-z
|
inline bool IsGenDelim(int c) {
|
||||||
// - 0-9
|
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' ||
|
||||||
// - '-' (hyphen)
|
c == '@';
|
||||||
// - '.' (period)
|
|
||||||
// - '_' (underscore)
|
|
||||||
// - '~' (tilde)
|
|
||||||
inline bool IsUnreserved(int c) {
|
|
||||||
return IsAlphaNumeric((char)c) ||
|
|
||||||
c == '-' || c == '.' || c == '_' || c == '~';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub-delimiters are those characters that may have a defined meaning within
|
// Sub-delimiters are those characters that may have a defined meaning within
|
||||||
// component of a URL/URI for a particular scheme. They do not serve as
|
// component of a URL/URI for a particular scheme. They do not serve as
|
||||||
// delimiters in any case between URL/URI segments. Sub-delimiters include:
|
// delimiters in any case between URL/URI segments. Sub-delimiters include:
|
||||||
// - All of these !$&'()*+,;=
|
// - All of these !$&'()*+,;=
|
||||||
inline bool SubDelimiter(int c) {
|
bool IsSubDelim(int c) {
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '!':
|
case '!':
|
||||||
case '$':
|
case '$':
|
||||||
@@ -133,8 +135,20 @@ inline bool SubDelimiter(int c) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reserved characters includes the general delimiters and sub delimiters. Some
|
||||||
|
// characters are neither reserved nor unreserved, and must be percent-encoded.
|
||||||
|
inline bool IsReserved(int c) {
|
||||||
|
return IsGenDelim(c) || IsSubDelim(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legal characters in the path portion include:
|
||||||
|
// - Any unreserved character
|
||||||
|
// - The percent character ('%'), and thus any percent-encoded octet
|
||||||
|
// - The sub-delimiters
|
||||||
|
// - ':' (colon)
|
||||||
|
// - '@' (at sign)
|
||||||
inline bool IsPathChar(int c) {
|
inline bool IsPathChar(int c) {
|
||||||
return IsUnreserved(c) || SubDelimiter(c) ||
|
return IsUnreserved(c) || IsSubDelim(c) ||
|
||||||
c == '%' || c == '/' || c == ':' || c == '@';
|
c == '%' || c == '/' || c == ':' || c == '@';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,57 +159,75 @@ inline bool IsQueryChar(int c) {
|
|||||||
return IsPathChar(c) || c == '?';
|
return IsPathChar(c) || c == '?';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Encode the URL query string.
|
|
||||||
inline std::string EncodeQuery(const std::string& query) {
|
|
||||||
return EncodeImpl(query, [](int c) {
|
|
||||||
return !IsQueryChar(c) || c == '%' || c == '+';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SplitKeyValue(const std::string& kv, std::string* key,
|
|
||||||
std::string* value) {
|
|
||||||
std::size_t i = kv.find_first_of('=');
|
|
||||||
if (i == std::string::npos || i == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
*key = kv.substr(0, i);
|
|
||||||
*value = kv.substr(i + 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
Url::Url(const std::string& str, bool decode) {
|
std::string Url::EncodeHost(const std::string& utf8_str) {
|
||||||
Init(str, decode);
|
return EncodeImpl(utf8_str, [](int c) -> bool { return c > 127; });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Url::Init(const std::string& str, bool decode, bool clear) {
|
std::string Url::EncodePath(const std::string& utf8_str) {
|
||||||
if (clear) {
|
return EncodeImpl(utf8_str, [](int c) -> bool {
|
||||||
Clear();
|
return !IsPathChar(c) || c == '%' || c == '+';
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!decode || str.find('%') == std::string::npos) {
|
std::string Url::EncodeQuery(const std::string& utf8_str) {
|
||||||
|
return EncodeImpl(utf8_str, [](int c) -> bool {
|
||||||
|
return !IsQueryChar(c) || c == '%' || c == '+';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string Url::EncodeFull(const std::string& utf8_str) {
|
||||||
|
return EncodeImpl(utf8_str, [](int c) -> bool {
|
||||||
|
return !IsUnreserved(c) && !IsReserved(c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Url::Url(const std::string& str, bool encode) {
|
||||||
|
if (encode) {
|
||||||
|
Parse(Url::EncodeFull(str));
|
||||||
|
} else {
|
||||||
Parse(str);
|
Parse(str);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Url::AppendPath(const std::string& piece, bool encode) {
|
||||||
|
if (piece.empty() || piece == "/") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string decoded;
|
if (path_.empty() || path_ == "/") {
|
||||||
if (Decode(str, &decoded)) {
|
path_.clear();
|
||||||
Parse(decoded);
|
if (piece.front() != '/') {
|
||||||
|
path_.push_back('/');
|
||||||
|
}
|
||||||
|
} else if (path_.back() == '/' && piece.front() == '/') {
|
||||||
|
path_.pop_back();
|
||||||
|
} else if (path_.back() != '/' && piece.front() != '/') {
|
||||||
|
path_.push_back('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (encode) {
|
||||||
|
path_.append(Url::EncodePath(piece));
|
||||||
} else {
|
} else {
|
||||||
// TODO: Exception?
|
path_.append(piece);
|
||||||
Parse(str);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Url::AddQuery(const std::string& key, const std::string& value) {
|
void Url::AppendQuery(const std::string& key, const std::string& value,
|
||||||
|
bool encode) {
|
||||||
if (!query_.empty()) {
|
if (!query_.empty()) {
|
||||||
query_ += "&";
|
query_ += "&";
|
||||||
}
|
}
|
||||||
query_ += key + "=" + value;
|
if (encode) {
|
||||||
|
query_ += Url::EncodeQuery(key) + "=" + Url::EncodeQuery(value);
|
||||||
|
} else {
|
||||||
|
query_ += key + "=" + value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Url::Parse(const std::string& str) {
|
void Url::Parse(const std::string& str) {
|
||||||
@@ -253,34 +285,43 @@ void Url::Clear() {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
UrlQuery::UrlQuery(const std::string& str) {
|
UrlQuery::UrlQuery(const std::string& encoded_str) {
|
||||||
if (!str.empty()) {
|
if (!encoded_str.empty()) {
|
||||||
// Split into key value pairs separated by '&'.
|
// Split into key value pairs separated by '&'.
|
||||||
for (std::size_t i = 0; i != std::string::npos;) {
|
for (std::size_t i = 0; i != std::string::npos;) {
|
||||||
std::size_t j = str.find_first_of('&', i);
|
std::size_t j = encoded_str.find_first_of('&', i);
|
||||||
|
|
||||||
std::string kv;
|
std::string kv;
|
||||||
if (j == std::string::npos) {
|
if (j == std::string::npos) {
|
||||||
kv = str.substr(i);
|
kv = encoded_str.substr(i);
|
||||||
i = std::string::npos;
|
i = std::string::npos;
|
||||||
} else {
|
} else {
|
||||||
kv = str.substr(i, j - i);
|
kv = encoded_str.substr(i, j - i);
|
||||||
i = j + 1;
|
i = j + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string key;
|
std::string key;
|
||||||
std::string value;
|
std::string value;
|
||||||
if (SplitKeyValue(kv, &key, &value)) {
|
if (utility::SplitKV(kv, '=', &key, &value, false)) {
|
||||||
Add(std::move(key), std::move(value));
|
parameters_.push_back({ DecodeUnsafe(key), DecodeUnsafe(value) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UrlQuery::Add(std::string&& key, std::string&& value) {
|
const std::string& UrlQuery::Get(const std::string& key) const {
|
||||||
if (!Has(key)) {
|
auto it = Find(key);
|
||||||
parameters_.push_back({ std::move(key), std::move(value) });
|
if (it != parameters_.end()) {
|
||||||
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const std::string kEmptyValue;
|
||||||
|
return kEmptyValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UrlQuery::Parameter& UrlQuery::Get(std::size_t index) const {
|
||||||
|
assert(index < Size());
|
||||||
|
return parameters_[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void UrlQuery::Add(const std::string& key, const std::string& value) {
|
void UrlQuery::Add(const std::string& key, const std::string& value) {
|
||||||
@@ -296,30 +337,21 @@ void UrlQuery::Remove(const std::string& key) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& UrlQuery::Get(const std::string& key) const {
|
|
||||||
auto it = Find(key);
|
|
||||||
if (it != parameters_.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const std::string kEmptyValue;
|
|
||||||
return kEmptyValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string UrlQuery::ToString() const {
|
std::string UrlQuery::ToString() const {
|
||||||
if (parameters_.empty()) {
|
if (parameters_.empty()) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string str = parameters_[0].first + "=" + parameters_[0].second;
|
std::string str;
|
||||||
|
|
||||||
for (std::size_t i = 1; i < parameters_.size(); ++i) {
|
for (std::size_t i = 0; i < parameters_.size(); ++i) {
|
||||||
str += "&";
|
if (i != 0) {
|
||||||
|
str += "&";
|
||||||
|
}
|
||||||
str += parameters_[i].first + "=" + parameters_[i].second;
|
str += parameters_[i].first + "=" + parameters_[i].second;
|
||||||
}
|
}
|
||||||
|
|
||||||
str = EncodeQuery(str);
|
return Url::EncodeQuery(str);
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const {
|
UrlQuery::ConstIterator UrlQuery::Find(const std::string& key) const {
|
||||||
|
|||||||
+37
-24
@@ -12,12 +12,20 @@ namespace webcc {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// A simplified implementation of URL (or URI).
|
// A simple implementation of URL (or URI).
|
||||||
|
// TODO: Encoding of path
|
||||||
class Url {
|
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);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Url() = default;
|
Url() = default;
|
||||||
|
|
||||||
explicit Url(const std::string& str, bool decode = true);
|
explicit Url(const std::string& str, bool encode = false);
|
||||||
|
|
||||||
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN
|
#if WEBCC_DEFAULT_MOVE_COPY_ASSIGN
|
||||||
|
|
||||||
@@ -47,8 +55,6 @@ public:
|
|||||||
|
|
||||||
#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN
|
#endif // WEBCC_DEFAULT_MOVE_COPY_ASSIGN
|
||||||
|
|
||||||
void Init(const std::string& str, bool decode = true, bool clear = true);
|
|
||||||
|
|
||||||
const std::string& scheme() const {
|
const std::string& scheme() const {
|
||||||
return scheme_;
|
return scheme_;
|
||||||
}
|
}
|
||||||
@@ -69,15 +75,19 @@ public:
|
|||||||
return query_;
|
return query_;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a query parameter.
|
// Append a piece of path.
|
||||||
void AddQuery(const std::string& key, const std::string& value);
|
void AppendPath(const std::string& piece, bool encode = false);
|
||||||
|
|
||||||
|
// Append a query parameter.
|
||||||
|
void AppendQuery(const std::string& key, const std::string& value,
|
||||||
|
bool encode = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Parse(const std::string& str);
|
void Parse(const std::string& str);
|
||||||
|
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
// TODO: Support auth & fragment.
|
private:
|
||||||
std::string scheme_;
|
std::string scheme_;
|
||||||
std::string host_;
|
std::string host_;
|
||||||
std::string port_;
|
std::string port_;
|
||||||
@@ -87,35 +97,38 @@ private:
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// URL query parameters.
|
// For accessing URL query parameters.
|
||||||
class UrlQuery {
|
class UrlQuery {
|
||||||
public:
|
public:
|
||||||
using Parameter = std::pair<std::string, std::string>;
|
using Parameter = std::pair<std::string, std::string>;
|
||||||
|
|
||||||
UrlQuery() = default;
|
// The query string should be key-value pairs separated by '&'.
|
||||||
|
explicit UrlQuery(const std::string& encoded_str);
|
||||||
|
|
||||||
// The query string should be key value pairs separated by '&'.
|
bool Empty() const {
|
||||||
explicit UrlQuery(const std::string& str);
|
return parameters_.empty();
|
||||||
|
}
|
||||||
|
|
||||||
void Add(const std::string& key, const std::string& value);
|
std::size_t Size() const {
|
||||||
|
return parameters_.size();
|
||||||
void Add(std::string&& key, std::string&& value);
|
}
|
||||||
|
|
||||||
void Remove(const std::string& key);
|
|
||||||
|
|
||||||
// Get a value by key.
|
|
||||||
// Return empty string if the key doesn't exist.
|
|
||||||
const std::string& Get(const std::string& key) const;
|
|
||||||
|
|
||||||
bool Has(const std::string& key) const {
|
bool Has(const std::string& key) const {
|
||||||
return Find(key) != parameters_.end();
|
return Find(key) != parameters_.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsEmpty() const {
|
// Get a value by key.
|
||||||
return parameters_.empty();
|
// Return empty string if the key doesn't exist.
|
||||||
}
|
const std::string& Get(const std::string& key) const;
|
||||||
|
|
||||||
// Return key-value pairs concatenated by '&'.
|
// Get a key-value pair by index.
|
||||||
|
const Parameter& Get(std::size_t index) const;
|
||||||
|
|
||||||
|
void Add(const std::string& key, const std::string& value);
|
||||||
|
|
||||||
|
void Remove(const std::string& key);
|
||||||
|
|
||||||
|
// Return encoded query string joined with '&'.
|
||||||
// E.g., "item=12731&color=blue&size=large".
|
// E.g., "item=12731&color=blue&size=large".
|
||||||
std::string ToString() const;
|
std::string ToString() const;
|
||||||
|
|
||||||
|
|||||||
+8
-6
@@ -35,18 +35,20 @@ std::string GetTimestamp() {
|
|||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SplitKV(const std::string& str, char delimiter,
|
bool SplitKV(const std::string& str, char delimiter, std::string* key,
|
||||||
std::string* part1, std::string* part2) {
|
std::string* value, bool trim) {
|
||||||
std::size_t pos = str.find(delimiter);
|
std::size_t pos = str.find(delimiter);
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*part1 = str.substr(0, pos);
|
*key = str.substr(0, pos);
|
||||||
*part2 = str.substr(pos + 1);
|
*value = str.substr(pos + 1);
|
||||||
|
|
||||||
boost::trim(*part1);
|
if (trim) {
|
||||||
boost::trim(*part2);
|
boost::trim(*key);
|
||||||
|
boost::trim(*value);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -21,8 +21,8 @@ std::string GetTimestamp();
|
|||||||
|
|
||||||
// Split a key-value string.
|
// Split a key-value string.
|
||||||
// E.g., split "Connection: Keep-Alive".
|
// E.g., split "Connection: Keep-Alive".
|
||||||
bool SplitKV(const std::string& str, char delimiter,
|
bool SplitKV(const std::string& str, char delimiter, std::string* key,
|
||||||
std::string* key, std::string* value);
|
std::string* value, bool trim = true);
|
||||||
|
|
||||||
// Convert string to size_t.
|
// Convert string to size_t.
|
||||||
bool ToSize(const std::string& str, int base, std::size_t* size);
|
bool ToSize(const std::string& str, int base, std::size_t* size);
|
||||||
|
|||||||
Reference in New Issue
Block a user