REST сервис на C++: POCO+Angular TODO
POCO — кроссплатформенная open-source библиотека на С++ под Boost Software License: ru.wikipedia.org/wiki/POCO.POCO имеет в своем составе средства для создания веб-сервисов c RESTful API.В данной статье рассмотрено создание такого сервиса на примере TODO.Простейшее приложение TODO — это список задач с возможностью добавить новую или удалить выполненную задачи.Задача реализована в CTodo. Каждой задаче в списке присваиваются уникальный идентификатор (id) и пользовательское описание (text).Список задач реализован в CTodoList. Управление списком задач производится методами CRUD Для хранения списка задач используется std: map.Http сервер бэкенда реализован в TodoServerApp. Данный класс содержит методы CRUD, а также список задач и мьютекс для синхронизации доступа к нему. Метод main вызывается из базового класса POCO ServerApplication.
TodoServerApp.h #pragma once
#include
using namespace Poco; using namespace Poco: Net; using namespace Poco: Util; using namespace std;
/** Todo */ class CTodo { size_t id; string text; public: CTodo (string text): text (text){ } /* getters & setters */ size_t getId (){ return id; } void setId (size_t id){ this→id = id; } string getText (){ return text; } };
/**
Список Todo
*/
class CTodoList {
size_t id;
map
public:
CTodoList (): id (0){}
/* CRUD */
void create (CTodo& todo){ todo.setId (++id); todos.insert (pair
/** Сервер */ class TodoServerApp: public ServerApplication { public: /* CRUD */ static void createTodo (CTodo& todo); static CTodoList& readTodoList (); //static void updateTodo (size_t id, CTodo& todo); static void deleteTodo (size_t id);
protected:
int main (const vector
#include «TodoServerApp.h»
Mutex TodoServerApp: todoLock; CTodoList TodoServerApp: todoList;
ostream& operator<<(ostream& os, CTodo& todo) { os << "{ \"_id\": "<< todo.getId() << ", \"text\": \"" << todo.getText() << "\" }"; return os; }
ostream& operator<<(ostream& os, CTodoList& todoList)
{
map
os << "[";
if(!todos.empty())
{
if(todos.size() == 1)
os << todos.begin()->second;
else
for (map
} os << "]\n";
return os; }
class CTodoHandler: public HTTPRequestHandler { public: void handleRequest (HTTPServerRequest &req, HTTPServerResponse &resp) { URI uri (req.getURI ()); string method = req.getMethod ();
cerr << "URI: " << uri.toString() << endl; cerr << "Method: " << req.getMethod() << endl;
StringTokenizer tokenizer (uri.getPath (),»/», StringTokenizer: TOK_TRIM); HTMLForm form (req, req.stream ());
if (! method.compare («POST»)) { cerr << "Create:" << form.get("text") << endl; CTodo todo(form.get("text")); TodoServerApp::createTodo(todo); } else if(!method.compare("PUT")) { cerr << "Update id:" << *(--tokenizer.end()) << endl; cerr << "Update text:" << form.get("text") << endl; //size_t id=stoull(*(--tokenizer.end())); //TodoServerApp::updateTodo(id, form.get("text")); } else if(!method.compare("DELETE")) { cerr << "Delete id:" << *(--tokenizer.end()) << endl; size_t id=stoull(*(--tokenizer.end())); TodoServerApp::deleteTodo(id); }
resp.setStatus (HTTPResponse: HTTP_OK); resp.setContentType («application/json»); ostream& out = resp.send ();
cerr << TodoServerApp::readTodoList() << endl; out << TodoServerApp::readTodoList() << endl;
out.flush (); } };
#include
class CFileHandler: public HTTPRequestHandler
{
typedef std: map
string getPath (string& path){
if (path == »/»){ path=»/index.html»; }
path.insert (0,»./www»);
return path; }
string getContentType (string& path){
string contentType («text/plain»); Poco: Path p (path);
TStrStrMap: const_iterator i=CONTENT_TYPE.find (p.getExtension ());
if (i!= CONTENT_TYPE.end ()) { /* Found, i→first is f, i→second is ++-- */ contentType = i→second; }
if (contentType.find («text/») != std: string: npos) { contentType+=»; charset=utf-8»; }
cerr << path << " : " << contentType << endl;
return contentType; }
public:
void handleRequest (HTTPServerRequest &req, HTTPServerResponse &resp) { cerr << "Get static page: "; //system("echo -n '1. Current Directory is '; pwd");
URI uri (req.getURI ()); string path (uri.getPath ());
ifstream ifs (getPath (path).c_str (), ifstream: in);
if (ifs) { resp.setStatus (HTTPResponse: HTTP_OK); resp.setContentType (getContentType (path)); ostream& out = resp.send ();
char c = ifs.get ();
while (ifs.good ()) { out << c; c = ifs.get(); }
out.flush (); } else { resp.setStatus (HTTPResponse: HTTP_NOT_FOUND); ostream& out = resp.send ();
out << "File not found" << endl;
out.flush (); }
ifs.close (); } };
class TodoRequestHandlerFactory: public HTTPRequestHandlerFactory { public: virtual HTTPRequestHandler* createRequestHandler (const HTTPServerRequest & request) { if (! request.getURI ().find (»/api/»)) return new CTodoHandler; else return new CFileHandler; } };
void TodoServerApp: createTodo (CTodo& todo)
{
ScopedLock
CTodoList& TodoServerApp: readTodoList ()
{
ScopedLock
void TodoServerApp: deleteTodo (size_t id)
{
ScopedLock
int TodoServerApp: main (const vector
pParams→setMaxQueued (100); pParams→setMaxThreads (16);
HTTPServer s (new TodoRequestHandlerFactory, ServerSocket (8000), pParams);
s.start (); cerr << "Server started" << endl;
waitForTerminationRequest (); // wait for CTRL-C or kill
cerr << "Shutting down..." << endl; s.stop();
return Application: EXIT_OK; } Независимо от типа запроса сервис всегда возвращает текущий список задач в формате JSON.Response.json [ { »_id»: 1, «text»: «First» }, { »_id»: 2, «text»: «Second» } ] Фронтенд загружается из ./www/ в дирректории приложения.Для фронтенда используется AngularJs. Код фронтенда практически полностью взят из статьи scotch.io/tutorials/creating-a-single-page-todo-app-with-node-and-angularВид в index.html состоит из заголовка со счетчиком заданий, списка заданий и формы добавления нового задания.Контроллер js/app.js вызывает GET для списка при инициализации. Далее — POST при добавлении нового задания из формы, либо DELETE при клике на чекбокс задания.index.html
POCO/Angular Todo App {{ todos.length }}
js/app.js // js/app.js var pocoTodo = angular.module ('pocoTodo', []);
function mainController ($scope, $http) { $scope.formData = {}; $http.defaults.headers.post[«Content-Type»] = «application/x-www-form-urlencoded»;
// when landing on the page, get all todos and show them $http.get ('/api/todos') .success (function (data) { $scope.todos = data; console.log (data); }) .error (function (data) { console.log ('Error: ' + data); });
// when submitting the add form, send the text to the node API $scope.createTodo = function () { $http.post ('/api/todos', $.param ($scope.formData)) // $scope.formData) .success (function (data) { $scope.formData = {}; // clear the form so our user is ready to enter another $scope.todos = data; console.log (data); }) .error (function (data) { console.log ('Error: ' + data); }); };
// delete a todo after checking it $scope.deleteTodo = function (id) { $http.delete ('/api/todos/' + id) .success (function (data) { $scope.todos = data; console.log (data); }) .error (function (data) { console.log ('Error: ' + data); }); };
} Репозиторий проекта: github.com/spot62/PocoAngularTodo