REST сервис на C++: POCO+Angular TODO

POCO — кроссплатформенная open-source библиотека на С++ под Boost Software License: ru.wikipedia.org/wiki/POCO.POCO имеет в своем составе средства для создания веб-сервисов c RESTful API.В данной статье рассмотрено создание такого сервиса на примере TODO.11876b5fa78c4805b3d887b5f10cd0f2.pngПростейшее приложение TODO — это список задач с возможностью добавить новую или удалить выполненную задачи.Задача реализована в CTodo. Каждой задаче в списке присваиваются уникальный идентификатор (id) и пользовательское описание (text).Список задач реализован в CTodoList. Управление списком задач производится методами CRUD Для хранения списка задач используется std: map.Http сервер бэкенда реализован в TodoServerApp. Данный класс содержит методы CRUD, а также список задач и мьютекс для синхронизации доступа к нему. Метод main вызывается из базового класса POCO ServerApplication.

TodoServerApp.h #pragma once

#include #include #include #include #include #include #include #include #include #include #include #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 todos;

public: CTodoList (): id (0){} /* CRUD */ void create (CTodo& todo){ todo.setId (++id); todos.insert (pair(id, todo)); }; map& readList (){ return todos; } void del (size_t id){ todos.erase (id); }; };

/** Сервер */ 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 &); static Mutex todoLock; static CTodoList todoList; }; При вызове метода main в классе TodoServerApp создается Http сервер с заданными параметрами (порт 8000 и т.д.). Также серверу передается фабрика обработки запросов TodoRequestHandlerFactory.После инициализации сервера запускается бесконечный цикл.В данном случае фабрика TodoRequestHandlerFactory имеет всего два обработчика: CFileHandler для отдачи статики и CTodoHandler собственно для самого REST API.Обработчик REST API CTodoHandler определяет тип запроса (GET/POST/PUT/DELETE). Для методов PUT/DELETE вычисляется идентификатор по URI. В соответствие с типом запроса производятся необходимые действия с данными списка.Поскольку данное приложение очень простое, метод PUT не используется. Выдача информации для GET по id конкретного задания также не реализовано.Далее производится формирование ответа в выходной поток сервера. С этой целью для задачи CTodo и списка задач CTodoList перегружен оператор TodoServerApp.cpp #include #include

#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 todos = todoList.readList ();

os << "["; if(!todos.empty()) { if(todos.size() == 1) os << todos.begin()->second; else for (map:: iterator it = todos.begin ();;) { os << it->second; if (++it!= todos.end ()) os << ','; else break; }

} 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 // std: cout #include // std: ifstream #include // std: ifstream

class CFileHandler: public HTTPRequestHandler { typedef std: map TStrStrMap; TStrStrMap CONTENT_TYPE = { #include «MimeTypes.h» };

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 lock (todoLock); todoList.create (todo); }

CTodoList& TodoServerApp: readTodoList () { ScopedLock lock (todoLock); return todoList; }

void TodoServerApp: deleteTodo (size_t id) { ScopedLock lock (todoLock); todoList.del (id); }

int TodoServerApp: main (const vector &) { HTTPServerParams* pParams = new HTTPServerParams;

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

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

© Habrahabr.ru