📜 ⬆️ ⬇️

C ++ REST service: POCO + Angular TODO

POCO - cross-platform open-source C ++ library under Boost Software License: ru.wikipedia.org/wiki/POCO .
POCO incorporates tools for creating web services with a RESTful API .
This article describes the creation of such a service on the example of TODO.


The simplest TODO application is a task list with the ability to add a new or delete a completed task.

The task is implemented in CTodo. Each task in the list is assigned a unique identifier (id) and a custom description (text).
The task list is implemented in CTodoList. The task list is managed by CRUD methods. To store the task list, use std :: map.
Http backend server is implemented in TodoServerApp. This class contains CRUD methods, as well as a task list and mutex for synchronizing access to it. The main method is called from the base class 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; }; 


When the main method is called in the TodoServerApp class, an Http server is created with the specified parameters (port 8000, etc.). Also, the server passes the TodoRequestHandlerFactory request processing factory.
After server initialization, an infinite loop starts.
In this case, the TodoRequestHandlerFactory factory has only two handlers: CFileHandler for returning statics and CTodoHandler for the REST API itself.
The CTDoHandler REST API handler determines the type of request (GET / POST / PUT / DELETE). For PUT / DELETE methods, the identifier is calculated by URI. In accordance with the type of request, the necessary actions are performed with the list data.
Since this application is very simple, the PUT method is not used. GET information on the specific task id is also not implemented.
Next, a response is formed into the output stream of the server. For this purpose, the << operator is overloaded for the CTodo task and the CTodoList task list.
')
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; } 


Regardless of the type of request, the service always returns the current task list in JSON format .

Response.json
 [ { "_id": 1, "text": "First" }, { "_id": 2, "text": "Second" } ] 


The frontend is loaded from ./www/ in the application directory.
For frontend used AngularJs . The frontend code is almost entirely taken from the article scotch.io/tutorials/creating-a-single-page-todo-app-with-node-and -angular
The view in index.html consists of a header with a task counter, a list of tasks and a form for adding a new task.
The js / app.js controller calls the GET for the list during initialization. Next - POST when adding a new task from the form, or DELETE when clicking on the task checkbox.

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); }); }; } 


Project repository: github.com/spot62/PocoAngularTodo

Source: https://habr.com/ru/post/252519/


All Articles