Refactor RestService interfaces.
This commit is contained in:
@@ -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)) {
|
}
|
||||||
PrintError();
|
|
||||||
|
int status = rest_client_.response_status();
|
||||||
|
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);
|
};
|
||||||
|
|
||||||
|
// Get service by URL path.
|
||||||
|
RestServicePtr service = service_manager_.GetService(
|
||||||
|
url.path(), &rest_request.url_sub_matches);
|
||||||
|
|
||||||
if (!service) {
|
if (!service) {
|
||||||
LOG_WARN("No service matches the URL: %s", url.path().c_str());
|
LOG_WARN("No service matches the URL path: %s", url.path().c_str());
|
||||||
connection->SendResponse(HttpStatus::kBadRequest);
|
connection->SendResponse(HttpStatus::kNotFound);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UrlQuery query;
|
RestResponse rest_response;
|
||||||
if (request.method() == kHttpGet) {
|
service->Handle(rest_request, &rest_response);
|
||||||
// Suppose URL query is only available for HTTP GET.
|
|
||||||
Url::SplitQuery(url.query(), &query);
|
if (!rest_response.content.empty()) {
|
||||||
|
connection->SetResponseContent(std::move(rest_response.content),
|
||||||
|
kAppJsonUtf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string content;
|
// Send response back to client.
|
||||||
bool ok = service->Handle(request.method(), sub_matches, query,
|
connection->SendResponse(rest_response.status);
|
||||||
request.content(), &content);
|
|
||||||
if (!ok) {
|
|
||||||
connection->SendResponse(HttpStatus::kBadRequest);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!content.empty()) {
|
|
||||||
connection->SetResponseContent(std::move(content), kAppJsonUtf8);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.method() == kHttpPost) {
|
|
||||||
connection->SendResponse(HttpStatus::kCreated);
|
|
||||||
} else {
|
|
||||||
connection->SendResponse(HttpStatus::kOK);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace webcc
|
} // namespace webcc
|
||||||
|
|||||||
+22
-39
@@ -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) {
|
||||||
|
Delete(request.url_sub_matches, response);
|
||||||
|
} else {
|
||||||
|
LOG_ERRO("RestDetailService doesn't support '%s' method.",
|
||||||
|
request.method.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (http_method == kHttpPut) {
|
|
||||||
return Put(url_sub_matches, request_content, response_content);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
|||||||
+37
-38
@@ -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) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+24
-26
@@ -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) {
|
||||||
|
|||||||
+3
-3
@@ -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);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user