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 <Poco/Mutex.h> #include <Poco/Net/HTTPServerRequest.h> #include <Poco/Net/HTTPRequestHandler.h> #include <Poco/Net/HTTPServerResponse.h> #include <Poco/Net/HTTPServer.h> #include <Poco/Net/HTTPRequestHandlerFactory.h> #include <Poco/Net/HTMLForm.h> #include <Poco/Path.h> #include <Poco/ScopedLock.h> #include <Poco/StringTokenizer.h> #include <Poco/URI.h> #include <Poco/Util/ServerApplication.h>  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<size_t, CTodo> todos;  public:     CTodoList():id(0){}     /* CRUD */     void create(CTodo& todo){ todo.setId(++id); todos.insert(pair<size_t,CTodo>(id, todo)); };     map<size_t, CTodo>& 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<string> &);     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 <iostream> #include <string>  #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<size_t, CTodo> todos = todoList.readList();      os << "[";     if(!todos.empty())     {         if(todos.size() == 1)             os << todos.begin()->second;         else             for ( map<size_t, CTodo>::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 <iostream>     // std::cout #include <fstream>      // std::ifstream #include <map>          // std::ifstream  class CFileHandler : public HTTPRequestHandler {     typedef std::map<const std::string, const std::string> 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<Mutex> lock(todoLock);     todoList.create(todo); }  CTodoList& TodoServerApp::readTodoList() {     ScopedLock<Mutex> lock(todoLock);     return todoList; }  void TodoServerApp::deleteTodo(size_t id) {     ScopedLock<Mutex> lock(todoLock);     todoList.del(id); }  int TodoServerApp::main(const vector<string> &) {     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

<!-- index.html --> <!doctype html>  <!-- ASSIGN OUR ANGULAR MODULE --> <html ng-app="pocoTodo"> <head>     <!-- META -->     <meta charset="utf-8">     <meta name="viewport" content="width=device-width, initial-scale=1"><!-- Optimize mobile viewport -->      <title>POCO/Angular Todo App</title>      <!-- SCROLLS -->     <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"><!-- load bootstrap -->     <style>         html                    { overflow-y:scroll; }         body                    { padding-top:50px; }         #todo-list              { margin-bottom:30px; }     </style>      <!-- SPELLS -->     <script src="//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script><!-- load jquery -->     <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script><!-- load angular -->     <script src="js/app.js"></script>  </head> <!-- SET THE CONTROLLER AND GET ALL TODOS --> <body ng-controller="mainController">     <div class="container">          <!-- HEADER AND TODO COUNT -->         <div class="jumbotron text-center">             <h2>POCO/Angular Todo App <span class="label label-info">{{ todos.length }}</span></h2>         </div>          <!-- TODO LIST -->         <div id="todo-list" class="row">             <div class="col-sm-4 col-sm-offset-4">                  <!-- LOOP OVER THE TODOS IN $scope.todos -->                 <div class="checkbox" ng-repeat="todo in todos">                     <label>                         <input type="checkbox" ng-click="deleteTodo(todo._id)"> {{ todo.text }}                     </label>                 </div>              </div>         </div>          <!-- FORM TO CREATE TODOS -->         <div id="todo-form" class="row">             <div class="col-sm-8 col-sm-offset-2 text-center">                 <form>                     <div class="form-group">                          <!-- BIND THIS VALUE TO formData.text IN ANGULAR -->                         <input type="text" class="form-control input-lg text-center" placeholder="I want to buy a puppy that will love me forever" ng-model="formData.text">                     </div>                      <!-- createToDo() WILL CREATE NEW TODOS -->                     <button type="submit" class="btn btn-primary btn-lg" ng-click="createTodo()">Add</button>                 </form>             </div>         </div>      </div>  </body> </html> 

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

ссылка на оригинал статьи http://habrahabr.ru/post/252519/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *