Refactor RestService interfaces.

master
Adam Gu 7 years ago
parent e9096d4e53
commit a863b379d0

@ -5,26 +5,8 @@
#include "webcc/logger.h" #include "webcc/logger.h"
#include "webcc/rest_async_client.h" #include "webcc/rest_async_client.h"
// ----------------------------------------------------------------------------- #include "example/common/book.h"
#include "example/common/book_json.h"
// Write a JSON object to string.
static std::string JsonToString(const Json::Value& json) {
Json::StreamWriterBuilder builder;
return Json::writeString(builder, json);
}
static Json::Value StringToJson(const std::string& str) {
Json::Value json;
Json::CharReaderBuilder builder;
std::stringstream stream(str);
std::string errs;
if (!Json::parseFromStream(builder, stream, &json, &errs)) {
std::cerr << errs << std::endl;
}
return json;
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

@ -23,26 +23,6 @@
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
static std::string JsonToString(const Json::Value& json) {
Json::StreamWriterBuilder builder;
return Json::writeString(builder, json);
}
static Json::Value StringToJson(const std::string& str) {
Json::Value json;
Json::CharReaderBuilder builder;
std::stringstream stream(str);
std::string errs;
if (!Json::parseFromStream(builder, stream, &json, &errs)) {
std::cerr << errs << std::endl;
}
return json;
}
// -----------------------------------------------------------------------------
class BookClientBase { class BookClientBase {
public: public:
BookClientBase(const std::string& host, const std::string& port, BookClientBase(const std::string& host, const std::string& port,
@ -54,18 +34,18 @@ class BookClientBase {
virtual ~BookClientBase() = default; virtual ~BookClientBase() = default;
protected: protected:
void PrintSeparateLine() { void LogError() {
std::cout << "--------------------------------";
std::cout << "--------------------------------";
std::cout << std::endl;
}
void PrintError() {
std::cout << webcc::DescribeError(rest_client_.error());
if (rest_client_.timed_out()) { if (rest_client_.timed_out()) {
std::cout << " (timed out)"; LOG_ERRO("%s (timed out)", webcc::DescribeError(rest_client_.error()));
} else {
LOG_ERRO(webcc::DescribeError(rest_client_.error()));
} }
std::cout << std::endl;
//std::cout << webcc::DescribeError(rest_client_.error());
//if (rest_client_.timed_out()) {
// std::cout << " (timed out)";
//}
//std::cout << std::endl;
} }
webcc::RestClient rest_client_; webcc::RestClient rest_client_;
@ -81,11 +61,8 @@ public:
} }
bool ListBooks() { bool ListBooks() {
PrintSeparateLine();
std::cout << "ListBooks" << std::endl;
if (!rest_client_.Get("/books")) { if (!rest_client_.Get("/books")) {
PrintError(); LogError();
return false; return false;
} }
@ -94,20 +71,15 @@ public:
} }
bool CreateBook(const std::string& title, double price, std::string* id) { bool CreateBook(const std::string& title, double price, std::string* id) {
PrintSeparateLine(); Json::Value req_json;
std::cout << "CreateBook: " << title << ", " << price << std::endl;
Json::Value req_json(Json::objectValue);
req_json["title"] = title; req_json["title"] = title;
req_json["price"] = price; req_json["price"] = price;
if (!rest_client_.Post("/books", JsonToString(req_json))) { if (!rest_client_.Post("/books", JsonToString(req_json))) {
PrintError(); LogError();
return false; return false;
} }
std::cout << rest_client_.response_status() << std::endl;
Json::Value rsp_json = StringToJson(rest_client_.response_content()); Json::Value rsp_json = StringToJson(rest_client_.response_content());
*id = rsp_json["id"].asString(); *id = rsp_json["id"].asString();
@ -125,11 +97,8 @@ public:
} }
bool GetBook(const std::string& id, Book* book) { bool GetBook(const std::string& id, Book* book) {
PrintSeparateLine();
std::cout << "GetBook: " << id << std::endl;
if (!rest_client_.Get("/books/" + id)) { if (!rest_client_.Get("/books/" + id)) {
PrintError(); LogError();
return false; return false;
} }
@ -138,40 +107,47 @@ public:
bool UpdateBook(const std::string& id, const std::string& title, bool UpdateBook(const std::string& id, const std::string& title,
double price) { double price) {
PrintSeparateLine();
std::cout << "UpdateBook: " << id << ", " << title << ", " << price
<< std::endl;
// NOTE: ID is already in the URL. // NOTE: ID is already in the URL.
Json::Value json(Json::objectValue); Json::Value json;
json["title"] = title; json["title"] = title;
json["price"] = price; json["price"] = price;
if (!rest_client_.Put("/books/" + id, JsonToString(json))) { if (!rest_client_.Put("/books/" + id, JsonToString(json))) {
PrintError(); LogError();
return false;
}
int status = rest_client_.response_status();
if (status != webcc::HttpStatus::kOK) {
LOG_ERRO("Failed to update book (status: %d).", status);
return false; return false;
} }
std::cout << rest_client_.response_status() << std::endl;
return true; return true;
} }
bool DeleteBook(const std::string& id) { bool DeleteBook(const std::string& id) {
PrintSeparateLine(); if (!rest_client_.Delete("/books/0" /*+ id*/)) {
std::cout << "DeleteBook: " << id << std::endl; LogError();
return false;
}
if (!rest_client_.Delete("/books/" + id)) { int status = rest_client_.response_status();
PrintError(); if (status != webcc::HttpStatus::kOK) {
LOG_ERRO("Failed to delete book (status: %d).", status);
return false; return false;
} }
std::cout << rest_client_.response_status() << std::endl;
return true; return true;
} }
}; };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void PrintSeparator() {
std::cout << std::string(80, '-') << std::endl;
}
void Help(const char* argv0) { void Help(const char* argv0) {
std::cout << "Usage: " << argv0 << " <host> <port> [timeout]" << std::endl; std::cout << "Usage: " << argv0 << " <host> <port> [timeout]" << std::endl;
std::cout << " E.g.," << std::endl; std::cout << " E.g.," << std::endl;
@ -198,21 +174,40 @@ int main(int argc, char* argv[]) {
BookListClient list_client(host, port, timeout_seconds); BookListClient list_client(host, port, timeout_seconds);
BookDetailClient detail_client(host, port, timeout_seconds); BookDetailClient detail_client(host, port, timeout_seconds);
PrintSeparator();
list_client.ListBooks(); list_client.ListBooks();
PrintSeparator();
std::string id; std::string id;
list_client.CreateBook("1984", 12.3, &id); if (!list_client.CreateBook("1984", 12.3, &id)) {
return 1;
}
PrintSeparator();
Book book; Book book;
if (detail_client.GetBook(id, &book)) { if (detail_client.GetBook(id, &book)) {
std::cout << "Book " << id << ": " << book << std::endl; std::cout << "Book: " << book << std::endl;
} }
PrintSeparator();
detail_client.UpdateBook(id, "1Q84", 32.1); detail_client.UpdateBook(id, "1Q84", 32.1);
detail_client.GetBook(id, &book);
PrintSeparator();
if (detail_client.GetBook(id, &book)) {
std::cout << "Book " << book << std::endl;
}
PrintSeparator();
detail_client.DeleteBook(id); detail_client.DeleteBook(id);
PrintSeparator();
list_client.ListBooks(); list_client.ListBooks();
return 0; return 0;

@ -18,8 +18,8 @@ static BookStore g_book_store;
// Return all books as a JSON array. // Return all books as a JSON array.
// TODO: Support query parameters. // TODO: Support query parameters.
bool BookListService::Get(const webcc::UrlQuery& /*query*/, void BookListService::Get(const webcc::UrlQuery& /*query*/,
std::string* response_content) { webcc::RestResponse* response) {
if (sleep_seconds_ > 0) { if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_); LOG_INFO("Sleep %d seconds...", sleep_seconds_);
std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_)); std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
@ -30,14 +30,12 @@ bool BookListService::Get(const webcc::UrlQuery& /*query*/,
json.append(BookToJson(book)); json.append(BookToJson(book));
} }
*response_content = JsonToString(json); response->content = JsonToString(json);
response->status = webcc::HttpStatus::kOK;
return true;
} }
// Add a new book. void BookListService::Post(const std::string& request_content,
bool BookListService::Post(const std::string& request_content, webcc::RestResponse* response) {
std::string* response_content) {
if (sleep_seconds_ > 0) { if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_); LOG_INFO("Sleep %d seconds...", sleep_seconds_);
std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_)); std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
@ -50,75 +48,91 @@ bool BookListService::Post(const std::string& request_content,
Json::Value json; Json::Value json;
json["id"] = id; json["id"] = id;
*response_content = JsonToString(json); response->content = JsonToString(json);
response->status = webcc::HttpStatus::kCreated;
return true; } else {
// Invalid JSON
response->status = webcc::HttpStatus::kBadRequest;
} }
return false;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool BookDetailService::Get(const std::vector<std::string>& url_sub_matches, void BookDetailService::Get(const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query, const webcc::UrlQuery& query,
std::string* response_content) { webcc::RestResponse* response) {
if (sleep_seconds_ > 0) { if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_); LOG_INFO("Sleep %d seconds...", sleep_seconds_);
std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_)); std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
} }
if (url_sub_matches.size() != 1) { if (url_sub_matches.size() != 1) {
return false; // TODO: kNotFound?
response->status = webcc::HttpStatus::kBadRequest;
return;
} }
const std::string& book_id = url_sub_matches[0]; const std::string& book_id = url_sub_matches[0];
const Book& book = g_book_store.GetBook(book_id); const Book& book = g_book_store.GetBook(book_id);
if (!book.IsNull()) { if (book.IsNull()) {
*response_content = BookToJsonString(book); response->status = webcc::HttpStatus::kNotFound;
return true; return;
} }
return false; response->content = BookToJsonString(book);
response->status = webcc::HttpStatus::kOK;
} }
// Update a book. // Update a book.
bool BookDetailService::Put(const std::vector<std::string>& url_sub_matches, void BookDetailService::Put(const std::vector<std::string>& url_sub_matches,
const std::string& request_content, const std::string& request_content,
std::string* response_content) { webcc::RestResponse* response) {
if (sleep_seconds_ > 0) { if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_); LOG_INFO("Sleep %d seconds...", sleep_seconds_);
std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_)); std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
} }
if (url_sub_matches.size() != 1) { if (url_sub_matches.size() != 1) {
return false; // TODO: kNotFound?
response->status = webcc::HttpStatus::kBadRequest;
return;
} }
const std::string& book_id = url_sub_matches[0]; const std::string& book_id = url_sub_matches[0];
Book book; Book book;
if (JsonStringToBook(request_content, &book)) { if (!JsonStringToBook(request_content, &book)) {
book.id = book_id; response->status = webcc::HttpStatus::kBadRequest;
return g_book_store.UpdateBook(book); return;
} }
return false; book.id = book_id;
g_book_store.UpdateBook(book);
response->status = webcc::HttpStatus::kOK;
} }
bool BookDetailService::Delete( void BookDetailService::Delete(
const std::vector<std::string>& url_sub_matches) { const std::vector<std::string>& url_sub_matches,
webcc::RestResponse* response) {
if (sleep_seconds_ > 0) { if (sleep_seconds_ > 0) {
LOG_INFO("Sleep %d seconds...", sleep_seconds_); LOG_INFO("Sleep %d seconds...", sleep_seconds_);
std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_)); std::this_thread::sleep_for(std::chrono::seconds(sleep_seconds_));
} }
if (url_sub_matches.size() != 1) { if (url_sub_matches.size() != 1) {
return false; // TODO: kNotFound?
response->status = webcc::HttpStatus::kBadRequest;
return;
} }
const std::string& book_id = url_sub_matches[0]; const std::string& book_id = url_sub_matches[0];
return g_book_store.DeleteBook(book_id); if (!g_book_store.DeleteBook(book_id)) {
response->status = webcc::HttpStatus::kNotFound;
return;
}
response->status = webcc::HttpStatus::kOK;
} }

@ -16,24 +16,25 @@
// The query parameters could be regular expressions. // The query parameters could be regular expressions.
class BookListService : public webcc::RestListService { class BookListService : public webcc::RestListService {
public: public:
explicit BookListService(int sleep_seconds) : sleep_seconds_(sleep_seconds) { explicit BookListService(int sleep_seconds)
: sleep_seconds_(sleep_seconds) {
} }
protected: protected:
// Return a list of books based on query parameters. // Get a list of books based on query parameters.
// URL examples: // URL examples:
// - /books // - /books
// - /books?name={BookName} // - /books?name={BookName}
bool Get(const webcc::UrlQuery& query, void Get(const webcc::UrlQuery& query,
std::string* response_content) override; webcc::RestResponse* response) final;
// Create a new book. // Create a new book.
bool Post(const std::string& request_content, void Post(const std::string& request_content,
std::string* response_content) override; webcc::RestResponse* response) final;
private: private:
// Sleep for the client to test timeout control. // Sleep for the client to test timeout control.
int sleep_seconds_ = 0; int sleep_seconds_;
}; };
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -47,19 +48,20 @@ class BookDetailService : public webcc::RestDetailService {
} }
protected: protected:
bool Get(const std::vector<std::string>& url_sub_matches, void Get(const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query, const webcc::UrlQuery& query,
std::string* response_content) override; webcc::RestResponse* response) final;
bool Put(const std::vector<std::string>& url_sub_matches, void Put(const std::vector<std::string>& url_sub_matches,
const std::string& request_content, const std::string& request_content,
std::string* response_content) override; webcc::RestResponse* response) final;
bool Delete(const std::vector<std::string>& url_sub_matches) override; void Delete(const std::vector<std::string>& url_sub_matches,
webcc::RestResponse* response) final;
private: private:
// Sleep for the client to test timeout control. // Sleep for the client to test timeout control.
int sleep_seconds_ = 0; int sleep_seconds_;
}; };
#endif // EXAMPLE_REST_BOOK_SERVER_SERVICES_H_ #endif // EXAMPLE_REST_BOOK_SERVER_SERVICES_H_

@ -3,12 +3,9 @@
class TestRestService : public webcc::RestService { class TestRestService : public webcc::RestService {
public: public:
bool Handle(const std::string& http_method, void Handle(const webcc::RestRequest& request,
const std::vector<std::string>& url_sub_matches, webcc::RestResponse* response) final {
const webcc::UrlQuery& query, response->status = webcc::HttpStatus::kOK;
const std::string& request_content,
std::string* response_content) override {
return true;
} }
}; };

@ -14,47 +14,39 @@ bool RestRequestHandler::Bind(RestServicePtr service, const std::string& url,
} }
void RestRequestHandler::HandleConnection(HttpConnectionPtr connection) { void RestRequestHandler::HandleConnection(HttpConnectionPtr connection) {
const HttpRequest& request = connection->request(); const HttpRequest& http_request = connection->request();
Url url(request.url(), true); Url url(http_request.url(), /*decode*/true);
if (!url.IsValid()) { if (!url.IsValid()) {
connection->SendResponse(HttpStatus::kBadRequest); connection->SendResponse(HttpStatus::kBadRequest);
return; return;
} }
std::vector<std::string> sub_matches; RestRequest rest_request{
RestServicePtr service = service_manager_.GetService(url.path(), http_request.method(), http_request.content(), url.query()
&sub_matches); };
if (!service) {
LOG_WARN("No service matches the URL: %s", url.path().c_str());
connection->SendResponse(HttpStatus::kBadRequest);
return;
}
UrlQuery query; // Get service by URL path.
if (request.method() == kHttpGet) { RestServicePtr service = service_manager_.GetService(
// Suppose URL query is only available for HTTP GET. url.path(), &rest_request.url_sub_matches);
Url::SplitQuery(url.query(), &query);
}
std::string content; if (!service) {
bool ok = service->Handle(request.method(), sub_matches, query, LOG_WARN("No service matches the URL path: %s", url.path().c_str());
request.content(), &content); connection->SendResponse(HttpStatus::kNotFound);
if (!ok) {
connection->SendResponse(HttpStatus::kBadRequest);
return; return;
} }
if (!content.empty()) { RestResponse rest_response;
connection->SetResponseContent(std::move(content), kAppJsonUtf8); service->Handle(rest_request, &rest_response);
}
if (request.method() == kHttpPost) { if (!rest_response.content.empty()) {
connection->SendResponse(HttpStatus::kCreated); connection->SetResponseContent(std::move(rest_response.content),
} else { kAppJsonUtf8);
connection->SendResponse(HttpStatus::kOK);
} }
// Send response back to client.
connection->SendResponse(rest_response.status);
} }
} // namespace webcc } // namespace webcc

@ -6,51 +6,34 @@ namespace webcc {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool RestListService::Handle(const std::string& http_method, void RestListService::Handle(const RestRequest& request,
const std::vector<std::string>& url_sub_matches, RestResponse* response) {
const UrlQuery& query, if (request.method == kHttpGet) {
const std::string& request_content, Get(UrlQuery(request.url_query_str), response);
std::string* response_content) { } else if (request.method == kHttpPost) {
if (http_method == kHttpGet) { Post(request.content, response);
return Get(query, response_content); } else {
LOG_ERRO("RestListService doesn't support '%s' method.",
request.method.c_str());
} }
if (http_method == kHttpPost) {
return Post(request_content, response_content);
}
LOG_ERRO("RestListService doesn't support '%s' method.", http_method.c_str());
return false;
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool RestDetailService::Handle(const std::string& http_method, void RestDetailService::Handle(const RestRequest& request,
const std::vector<std::string>& url_sub_matches, RestResponse* response) {
const UrlQuery& query, if (request.method == kHttpGet) {
const std::string& request_content, Get(request.url_sub_matches, UrlQuery(request.url_query_str), response);
std::string* response_content) { } else if (request.method == kHttpPut) {
if (http_method == kHttpGet) { Put(request.url_sub_matches, request.content, response);
return Get(url_sub_matches, query, response_content); } else if (request.method == kHttpPatch) {
} Patch(request.url_sub_matches, request.content, response);
} else if (request.method == kHttpDelete) {
if (http_method == kHttpPut) { Delete(request.url_sub_matches, response);
return Put(url_sub_matches, request_content, response_content); } else {
LOG_ERRO("RestDetailService doesn't support '%s' method.",
request.method.c_str());
} }
if (http_method == kHttpPatch) {
return Patch(url_sub_matches, request_content, response_content);
}
if (http_method == kHttpDelete) {
return Delete(url_sub_matches);
}
LOG_ERRO("RestDetailService doesn't support '%s' method.",
http_method.c_str());
return false;
} }
} // namespace webcc } // namespace webcc

@ -14,28 +14,40 @@
#include <vector> #include <vector>
#include "webcc/globals.h" #include "webcc/globals.h"
#include "webcc/url.h"
namespace webcc { namespace webcc {
class UrlQuery; // -----------------------------------------------------------------------------
struct RestRequest {
// HTTP method (GET, POST, etc.).
const std::string& method;
// Request content (JSON string).
const std::string& content;
// Query string of the URL (only for GET).
const std::string& url_query_str;
// Regex sub-matches of the URL (usually resource ID's).
std::vector<std::string> url_sub_matches;
};
struct RestResponse {
HttpStatus::Enum status;
std::string content;
};
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Base class for your REST service. // Base class for your REST service.
class RestService { class RestService {
public: public:
virtual ~RestService() { virtual ~RestService() = default;
}
// Handle REST request, output the response. // Handle REST request, output response.
// The regex sub-matches of the URL (usually resource IDs) were stored in virtual void Handle(const RestRequest& request, RestResponse* response) = 0;
// |url_sub_matches|. The |query| part of the URL is normally only for GET
// request. Both the request and response contents are JSON strings.
virtual bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) = 0;
}; };
typedef std::shared_ptr<RestService> RestServicePtr; typedef std::shared_ptr<RestService> RestServicePtr;
@ -44,22 +56,16 @@ typedef std::shared_ptr<RestService> RestServicePtr;
class RestListService : public RestService { class RestListService : public RestService {
public: public:
bool Handle(const std::string& http_method, void Handle(const RestRequest& request, RestResponse* response) final;
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) final;
protected: protected:
RestListService() = default; RestListService() = default;
virtual bool Get(const UrlQuery& query, std::string* response_content) { virtual void Get(const UrlQuery& query, RestResponse* response) {
return false;
} }
virtual bool Post(const std::string& request_content, virtual void Post(const std::string& request_content,
std::string* response_content) { RestResponse* response) {
return false;
} }
}; };
@ -67,33 +73,26 @@ class RestListService : public RestService {
class RestDetailService : public RestService { class RestDetailService : public RestService {
public: public:
bool Handle(const std::string& http_method, void Handle(const RestRequest& request, RestResponse* response) final;
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) final;
protected: protected:
virtual bool Get(const std::vector<std::string>& url_sub_matches, virtual void Get(const std::vector<std::string>& url_sub_matches,
const UrlQuery& query, const UrlQuery& query,
std::string* response_content) { RestResponse* response) {
return false;
} }
virtual bool Put(const std::vector<std::string>& url_sub_matches, virtual void Put(const std::vector<std::string>& url_sub_matches,
const std::string& request_content, const std::string& request_content,
std::string* response_content) { RestResponse* response) {
return false;
} }
virtual bool Patch(const std::vector<std::string>& url_sub_matches, virtual void Patch(const std::vector<std::string>& url_sub_matches,
const std::string& request_content, const std::string& request_content,
std::string* response_content) { RestResponse* response) {
return false;
} }
virtual bool Delete(const std::vector<std::string>& url_sub_matches) { virtual void Delete(const std::vector<std::string>& url_sub_matches,
return false; RestResponse* response) {
} }
}; };

@ -166,6 +166,30 @@ bool SplitKeyValue(const std::string& kv, std::string* key,
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
UrlQuery::UrlQuery(const std::string& str) {
if (!str.empty()) {
// Split into key value pairs separated by '&'.
for (std::size_t i = 0; i != std::string::npos;) {
std::size_t j = str.find_first_of('&', i);
std::string kv;
if (j == std::string::npos) {
kv = str.substr(i);
i = std::string::npos;
} else {
kv = str.substr(i, j - i);
i = j + 1;
}
std::string key;
std::string value;
if (SplitKeyValue(kv, &key, &value)) {
Add(std::move(key), std::move(value));
}
}
}
}
UrlQuery::UrlQuery(const std::map<std::string, std::string>& map) { UrlQuery::UrlQuery(const std::map<std::string, std::string>& map) {
for (auto& pair : map) { for (auto& pair : map) {
Add(pair.first, pair.second); Add(pair.first, pair.second);
@ -256,32 +280,6 @@ std::vector<std::string> Url::SplitPath(const std::string& path) {
return results; return results;
} }
// static
void Url::SplitQuery(const std::string& str, UrlQuery* query) {
const std::size_t NPOS = std::string::npos;
// Split into key value pairs separated by '&'.
std::size_t i = 0;
while (i != NPOS) {
std::size_t j = str.find_first_of('&', i);
std::string kv;
if (j == NPOS) {
kv = str.substr(i);
i = NPOS;
} else {
kv = str.substr(i, j - i);
i = j + 1;
}
std::string key;
std::string value;
if (SplitKeyValue(kv, &key, &value)) {
query->Add(std::move(key), std::move(value));
}
}
}
void Url::Init(const std::string& str) { void Url::Init(const std::string& str) {
std::size_t pos = str.find('?'); std::size_t pos = str.find('?');
if (pos == std::string::npos) { if (pos == std::string::npos) {

@ -24,6 +24,9 @@ class UrlQuery {
UrlQuery() = default; UrlQuery() = default;
// The query string should be key value pairs separated by '&'.
explicit UrlQuery(const std::string& str);
// Construct from key-value pairs. // Construct from key-value pairs.
explicit UrlQuery(const std::map<std::string, std::string>& map); explicit UrlQuery(const std::map<std::string, std::string>& map);
@ -84,9 +87,6 @@ class Url {
// Split a path into its hierarchical components. // Split a path into its hierarchical components.
static std::vector<std::string> SplitPath(const std::string& path); static std::vector<std::string> SplitPath(const std::string& path);
// Split query string into key-value parameters.
static void SplitQuery(const std::string& str, UrlQuery* query);
private: private:
void Init(const std::string& str); void Init(const std::string& str);

Loading…
Cancel
Save