Add RestListService and RestDetailService; update README.

master
Chunting Gu 7 years ago
parent 5e6f125f90
commit 189a246221

@ -2,11 +2,95 @@
A lightweight C++ REST and SOAP client and server library based on *Boost.Asio*.
Please see the `doc` folder for tutorials and `example` folder for examples.
## A Quick Look
### REST Server
Suppose you want to create a book server, and provide the following operations with RESTful API:
- Query books based on some criterias.
- Add a new book.
- Get the detailed information of a book.
- Update the information of a book.
- Delete a book.
The first two operations can be implemented by deriving from `webcc::RestListService`:
```cpp
class BookListService : public webcc::RestListService {
protected:
// Query books based on some criterias.
// GET /books?<query>
bool Get(const webcc::UrlQuery& query,
std::string* response_content) final;
// Add a new book.
// POST /books
// The new book's data is attached as request content in JSON format.
bool Post(const std::string& request_content,
std::string* response_content) final;
};
```
The others, derive from `webcc::RestDetailService`:
```cpp
// The URL is like '/book/{BookID}', and the 'url_sub_matches' parameter
// contains the matched book ID.
class BookDetailService : public webcc::RestDetailService {
protected:
// Get the detailed information of a book.
bool Get(const std::vector<std::string>& url_sub_matches,
std::string* response_content) final;
// Update the information of a book.
bool Patch(const std::vector<std::string>& url_sub_matches,
const std::string& request_content,
std::string* response_content) final;
// Delete a book.
bool Delete(const std::vector<std::string>& url_sub_matches) final;
};
```
As you can see, all you have to do is to override the proper virtual functions which are named after HTTP methods.
The detailed implementation is out of the scope of this document, but here is an example:
```cpp
bool BookDetailService::Get(const std::vector<std::string>& url_sub_matches,
std::string* response_content) {
if (url_sub_matches.size() != 1) {
return false;
}
const std::string& book_id = url_sub_matches[0];
// Get the book by ID from database.
// Convert the book details to JSON string.
// Assign JSON string to response_content.
}
```
Last step, register the services and run the server:
```cpp
webcc::RestServer server(8080, 2);
server.RegisterService(std::make_shared<BookListService>(),
"/books");
server.RegisterService(std::make_shared<BookDetailService>(),
"/book/(\\d+)");
server.Run();
```
**Please see the `example` folder for the complete examples (including the client).**
## Build Instructions
A lot of C++11 features are used, e.g., `std::move`. But C++14 is not required. It means that you can still build `webcc` using VS2013.
A lot of C++11 features are used, e.g., `std::move`. But C++14 is not required.
(It means that you can still build `webcc` using VS2013 on Windows.)
[CMake 3.1.0+](https://cmake.org/) is required as the build system. But if you don't use CMake, you can just copy the `src/webcc` folder to your own project then manage it by yourself.
@ -25,3 +109,26 @@ option(WEBCC_BUILD_SOAP_EXAMPLE "Build SOAP example?" ON)
```
If `WEBCC_ENABLE_SOAP` is `ON`, **pugixml** (already included) is used to parse and compose XML strings.
### Build On Linux
Create a build folder under the root (or any other) directory, and `cd` to it:
```bash
mkdir build
cd build
```
Generate Makefiles with the following command:
```bash
cmake -G"Unix Makefiles" \
-DWEBCC_ENABLE_LOG=ON \
-DWEBCC_ENABLE_SOAP=ON \
-DWEBCC_BUILD_UNITTEST=OFF \
-DWEBCC_BUILD_REST_EXAMPLE=ON \
-DWEBCC_BUILD_SOAP_EXAMPLE=OFF \
..
```
Feel free to change the build options (`ON` or `OFF`).
Then make:
```bash
$ make -j4
```

@ -69,6 +69,11 @@
"command": "/usr/bin/c++ -DBOOST_ASIO_NO_DEPRECATED -DWEBCC_ENABLE_LOG -DWEBCC_ENABLE_SOAP -I/home/adam/github/webcc/src -I/home/adam/github/webcc/third_party -std=c++11 -o CMakeFiles/webcc.dir/rest_server.cc.o -c /home/adam/github/webcc/src/webcc/rest_server.cc",
"file": "/home/adam/github/webcc/src/webcc/rest_server.cc"
},
{
"directory": "/home/adam/github/webcc/build/src/webcc",
"command": "/usr/bin/c++ -DBOOST_ASIO_NO_DEPRECATED -DWEBCC_ENABLE_LOG -DWEBCC_ENABLE_SOAP -I/home/adam/github/webcc/src -I/home/adam/github/webcc/third_party -std=c++11 -o CMakeFiles/webcc.dir/rest_service.cc.o -c /home/adam/github/webcc/src/webcc/rest_service.cc",
"file": "/home/adam/github/webcc/src/webcc/rest_service.cc"
},
{
"directory": "/home/adam/github/webcc/build/src/webcc",
"command": "/usr/bin/c++ -DBOOST_ASIO_NO_DEPRECATED -DWEBCC_ENABLE_LOG -DWEBCC_ENABLE_SOAP -I/home/adam/github/webcc/src -I/home/adam/github/webcc/third_party -std=c++11 -o CMakeFiles/webcc.dir/url.cc.o -c /home/adam/github/webcc/src/webcc/url.cc",

@ -92,7 +92,7 @@ public:
bool GetBook(const std::string& id) {
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpGet, "/books/" + id, "", &http_response)) {
if (!Request(webcc::kHttpGet, "/book/" + id, "", &http_response)) {
return false;
}
@ -113,7 +113,7 @@ public:
std::string book_json = Json::writeString(builder, root);
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpPost, "/books/" + id, book_json, &http_response)) {
if (!Request(webcc::kHttpPost, "/book/" + id, book_json, &http_response)) {
return false;
}
@ -124,7 +124,7 @@ public:
bool DeleteBook(const std::string& id) {
webcc::HttpResponse http_response;
if (!Request(webcc::kHttpDelete, "/books/" + id, "", &http_response)) {
if (!Request(webcc::kHttpDelete, "/book/" + id, "", &http_response)) {
return false;
}

@ -115,14 +115,10 @@ static bool BookFromJson(const std::string& json, Book* book) {
return true;
}
bool BookListService::Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query,
const std::string& request_content,
std::string* response_content) {
if (http_method == webcc::kHttpGet) {
// Return all books as a JSON array.
// TODO: Support query parameters.
bool BookListService::Get(const webcc::UrlQuery& /* query */,
std::string* response_content) {
Json::Value root(Json::arrayValue);
for (const Book& book : g_book_store.books()) {
root.append(book.ToJson());
@ -134,8 +130,10 @@ bool BookListService::Handle(const std::string& http_method,
return true;
}
if (http_method == webcc::kHttpPost) {
// Add a new book.
// No response content.
bool BookListService::Post(const std::string& request_content,
std::string* /* response_content */) {
Book book;
if (BookFromJson(request_content, &book)) {
return g_book_store.AddBook(book);
@ -143,15 +141,9 @@ bool BookListService::Handle(const std::string& http_method,
return false;
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
bool BookDetailService::Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query,
const std::string& request_content,
bool BookDetailService::Get(const std::vector<std::string>& url_sub_matches,
std::string* response_content) {
if (url_sub_matches.size() != 1) {
return false;
@ -159,27 +151,41 @@ bool BookDetailService::Handle(const std::string& http_method,
const std::string& book_id = url_sub_matches[0];
if (http_method == webcc::kHttpGet) {
const Book& book = g_book_store.GetBook(book_id);
if (!book.IsNull()) {
Json::StreamWriterBuilder builder;
*response_content = Json::writeString(builder, book.ToJson());
return true;
}
return false;
}
// Update a book partially.
bool BookDetailService::Patch(const std::vector<std::string>& url_sub_matches,
const std::string& request_content,
std::string* response_content) {
if (url_sub_matches.size() != 1) {
return false;
}
const std::string& book_id = url_sub_matches[0];
} else if (http_method == webcc::kHttpPost) {
// Update a book.
Book book;
if (BookFromJson(request_content, &book)) {
book.id = book_id;
return g_book_store.UpdateBook(book);
}
return false;
} else if (http_method == webcc::kHttpDelete) {
return g_book_store.DeleteBook(book_id);
return false;
}
bool BookDetailService::Delete(const std::vector<std::string>& url_sub_matches) {
if (url_sub_matches.size() != 1) {
return false;
}
const std::string& book_id = url_sub_matches[0];
return g_book_store.DeleteBook(book_id);
}

@ -3,48 +3,42 @@
#include "webcc/rest_service.h"
// NOTE:
// XxxListService and XxxDetailService are similar to the XxxListView
// and XxxDetailView in Django (a Python web framework).
////////////////////////////////////////////////////////////////////////////////
// List Service handles the HTTP GET and returns the book list based on
// BookListService handles the HTTP GET and returns the book list based on
// query parameters specified in the URL.
// The URL should be like:
// - /books
// - /books?name={BookName}
// The query parameters could be regular expressions.
class BookListService : public webcc::RestService {
public:
BookListService() = default;
~BookListService() override = default;
bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query,
const std::string& request_content,
std::string* response_content) override;
class BookListService : public webcc::RestListService {
protected:
// Return a list of books based on query parameters.
// URL examples:
// - /books
// - /books?name={BookName}
bool Get(const webcc::UrlQuery& query,
std::string* response_content) final;
// Create a new book.
bool Post(const std::string& request_content,
std::string* response_content) final;
};
////////////////////////////////////////////////////////////////////////////////
// Detail Service handles the following HTTP methods:
// - GET
// - PUT
// - PATCH
// - DELETE
// The URL should be like: /books/{BookID}.
class BookDetailService : public webcc::RestService {
public:
BookDetailService() = default;
~BookDetailService() override = default;
bool Handle(const std::string& http_method,
const std::vector<std::string>& url_sub_matches,
const webcc::UrlQuery& query,
// The URL is like '/books/{BookID}', and the 'url_sub_matches' parameter
// contains the matched book ID.
class BookDetailService : public webcc::RestDetailService {
protected:
bool Get(const std::vector<std::string>& url_sub_matches,
std::string* response_content) final;
bool Patch(const std::vector<std::string>& url_sub_matches,
const std::string& request_content,
std::string* response_content) override;
std::string* response_content) final;
bool Delete(const std::vector<std::string>& url_sub_matches) final;
};
#endif // BOOK_SERVICE_H_

@ -30,7 +30,7 @@ int main(int argc, char* argv[]) {
"/books");
server.RegisterService(std::make_shared<BookDetailService>(),
"/books/(\\d+)");
"/book/(\\d+)");
server.Run();

@ -29,6 +29,7 @@ set(SRCS
queue.h
rest_server.cc
rest_server.h
rest_service.cc
rest_service.h
url.cc
url.h

@ -5,6 +5,8 @@
#if WEBCC_ENABLE_LOG
#include <cstring> // for strrchr()
namespace webcc {
enum LogLevel {
@ -32,7 +34,7 @@ void LogWrite(int level, const char* file, int line, const char* format, ...);
#if (defined(WIN32) || defined(_WIN64))
// See: https://stackoverflow.com/a/8488201
#define __FILENAME__ strrchr("\\" __FILE__, '\\') + 1
#define __FILENAME__ std::strrchr("\\" __FILE__, '\\') + 1
#define LOG_VERB(format, ...) \
webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, format, ##__VA_ARGS__);
@ -52,7 +54,7 @@ void LogWrite(int level, const char* file, int line, const char* format, ...);
#else
// See: https://stackoverflow.com/a/8488201
#define __FILENAME__ strrchr("/" __FILE__, '/') + 1
#define __FILENAME__ std::strrchr("/" __FILE__, '/') + 1
#define LOG_VERB(format, args...) \
webcc::LogWrite(webcc::VERB, __FILENAME__, __LINE__, format, ##args);

@ -0,0 +1,53 @@
#include "webcc/rest_service.h"
#include "webcc/logger.h"
namespace webcc {
bool RestListService::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) {
if (http_method == kHttpGet) {
return Get(query, response_content);
}
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,
const std::vector<std::string>& url_sub_matches,
const UrlQuery& query,
const std::string& request_content,
std::string* response_content) {
if (http_method == kHttpGet) {
return Get(url_sub_matches, response_content);
}
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

@ -1,6 +1,13 @@
#ifndef WEBCC_REST_SERVICE_H_
#define WEBCC_REST_SERVICE_H_
// NOTE:
// The design of RestListService and RestDetailService is very similar to
// XxxListView and XxxDetailView in Django Rest Framework.
// Deriving from them instead of RestService can simplify your own REST services
// a lot. But if you find the filtered parameters cannot meet your needs, feel
// free to derive from RestService directly.
#include <map>
#include <memory>
#include <string>
@ -12,6 +19,8 @@ namespace webcc {
class UrlQuery;
////////////////////////////////////////////////////////////////////////////////
// Base class for your REST service.
class RestService {
public:
@ -34,6 +43,63 @@ public:
typedef std::shared_ptr<RestService> RestServicePtr;
////////////////////////////////////////////////////////////////////////////////
class RestListService : public RestService {
public:
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) final;
protected:
RestListService() = default;
virtual bool Get(const UrlQuery& query,
std::string* response_content) {
return false;
}
virtual bool Post(const std::string& request_content,
std::string* response_content) {
return false;
}
};
////////////////////////////////////////////////////////////////////////////////
class RestDetailService : public RestService {
public:
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) final;
protected:
virtual bool Get(const std::vector<std::string>& url_sub_matches,
std::string* response_content) {
return false;
}
virtual bool Put(const std::vector<std::string>& url_sub_matches,
const std::string& request_content,
std::string* response_content) {
return false;
}
virtual bool Patch(const std::vector<std::string>& url_sub_matches,
const std::string& request_content,
std::string* response_content) {
return false;
}
virtual bool Delete(const std::vector<std::string>& url_sub_matches) {
return false;
}
};
} // namespace webcc
#endif // WEBCC_REST_SERVICE_H_

Loading…
Cancel
Save