C++ web application MVC framework
C++ web MVC framework
Topics covered:
- 2.1Web application framework
- 2.2Web request and response
- 2.2.1URL path segments
- 2.2.2Application request
- 2.2.3Application response
- 2.3Application mapper
- 2.4Application server
- 2.5Model, view, and controller
- 2.5.1Default MVC classes
- 2.5.2Firm MVC classes
- 2.5.3Store MVC classes
- 2.6Website URL routing
- 2.6.1Application router
- 2.6.2Web application service
- 2.6.3Main application service
- 2.7Build CppCMS application
- 2.7.1CMake build setup
- 2.7.2CMake build steps
- 2.8Run CppCMS application
Affiliate links
Setting up a CppCMS C++ web application server requires a VPS with root access. Use our affiliate links to purchase a VPS or cloud server from third-party vendors. The affiliate commissions we earn facilitate, Free website access for everyone.
- Cloudways WordPress VPS hosting
- Linode VPS hosting $100 credit
- Tastytrade invest in US markets
- Vultr VPS hosting $100 credit
The affiliate links are listed in alphabetical order without any favor. Users are encouraged to refer to the Global Webdynamics LLP Terms of Service governing the Third-party vendors.
2.1Web application framework
CppCMS web framework provides HTTP request and response handling, HTTP cookie management, etc., which are features available as part of the web application framework.
The web framework also offers routing, session management, HTML form handling, view templates, cache management, etc., which can be used out of the box, or you may use custom libraries to develop web applications.
Small web applications that depend on all the features offered by a single web framework may be relatively easy to maintain. But, for large web applications with hundreds of modules, tying web applications to one web application framework is risky to maintain and upgrade in the long term.
One word of advice is to choose the best framework and try to use minimal specific features from that framework. The key is to decouple from the framework by substituting with equivalent standalone feature libraries. In the future, if the need arises, replacing a library is effortless than the entire framework.
For example, instead of using a template feature provided by the web framework, use a C++
CTemplate
open-source HTML template library. Likewise, replace URL routing mechanism, session, and cache management features with third-party vendor libraries or develop in-house custom libraries to replace framework features.
2.2Web request and response
The CppCMS C++ web framework provides a routing mechanism to parse and route the static and dynamic URLs. To learn how website URL routing works in CppCMS, refer to the CppCMS examples directory (
examples/url_mapping
).
The
WebApp::main
method manages URL handling, request parsing, and application response. Segregate these responsibilities by creating three new classes:
URL
,
AppRequest
and
AppResponse
.
Create a library (
lib
) directory under the
code/inc
and
code/src
directory.
$ mkdir -p /usr/local/gwinc/web/example/code/{inc,src}/lib
2.2.1URL path segments
Create a
URL
utility class with a single method
URL::path_segments
that extracts the URL path into segments.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/lib/URL.hpp
#ifndef GWINC_URL_HPP
#define GWINC_URL_HPP
#include <string>
#include <vector>
/// Gwinc global namespace.
/// @namespace gwinc
/// Web module.
/// @namespace gwinc::web
namespace gwinc::web
{
using std::string;
using std::vector;
/// URL-parsing interface.
/// @class URL
class URL
{
public:
/// Non-conversion constructor.
/// @param path URL-path to be extracted.
explicit URL(const string& path);
/// Virtual destructor.
virtual ~URL();
/// Extract URL-path into segments.
/// @return Extracted URL-path.
vector<string> path_segments();
private:
// URL-path separator.
static const char PATH_SEPARATOR;
// Interface properties.
string iPath;
};
} // namespace gwinc::web
#endif // GWINC_URL_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/lib/URL.cpp
#include "inc/lib/URL.hpp"
#include <sstream>
namespace gwinc::web
{
using std::istringstream;
const char URL::PATH_SEPARATOR = '/';
URL::URL(const string& path) :
iPath(path)
{
}
URL::~URL()
{
}
vector<string> URL::path_segments()
{
if(this->iPath.empty())
{
return {};
}
vector<string> result;
istringstream inStrStream(this->iPath);
string subStr;
const char delimiter = PATH_SEPARATOR;
while(getline(inStrStream, subStr, delimiter))
{
// In each iteration, subStr will be replaced with newly
// extracted sub-string against delimiter.
result.push_back(subStr);
}
return result;
}
} // namespace gwinc::web
2.2.2Application request
The application request class parses the HTTP URL path and extracts the controller and action segments mapped to the Router and Controller class's methods. If the URL path is mapped to the Store Controller, the class parses the department, category, and item ID.
Create the C++ web application request classes (
inc/lib/AppRequest.hpp
and
src/lib/AppRequest.cpp
) under the
lib
directory.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/lib/AppRequest.hpp
#ifndef GWINC_APPREQUEST_HPP
#define GWINC_APPREQUEST_HPP
#include <string>
#include <cppcms/http_request.h>
/// Gwinc global namespace.
/// @namespace gwinc
/// Web module.
/// @namespace gwinc::web
namespace gwinc::web
{
using std::string;
/// Application request interface.
/// @class AppRequest
class AppRequest
{
public:
/// Virtual destructor.
virtual ~AppRequest();
/// Get an instance of an AppRequest.
/// @return Reference to AppRequest.
static AppRequest& instance();
/// HTTP request.
/// @param val HTTP request.
/// @return Reference to AppRequest.
AppRequest& set_request(cppcms::http::request* val);
/// Parse HTTP url-path.
/// @param path A url-path.
/// @param offset A url-path segment offset.
/// @return Reference to AppRequest.
/// @note Use offset = 0 to start from first url-path segment.
/// @note To start from a specific url-path segment increment offset respectively.
AppRequest& parse_url(const string& path, short offset);
/// Get HTTP request.
/// @return HTTP request.
cppcms::http::request* request();
/// Get url-path.
/// @return A url-path.
string url_path();
/// Get controller mapped from url-path.
/// @return Controller name.
string controller();
/// Get action mapped from url-path.
/// @return Action name.
string action();
/// Get department ID mapped from url-path.
/// @return Department ID if part of the url-path or else zero.
short department_id();
/// Get category ID mapped from url-path.
/// @return Category ID if part of the url-path or else zero.
int category_id();
/// Get item ID mapped from url-path.
/// @return Item ID if part of the url-path or else zero.
long long item_id();
protected:
/// Constructor.
AppRequest();
private:
// Interface properties.
cppcms::http::request* iRequest;
string iUrlPath;
string iController;
string iAction;
short iDepartmentId;
int iCategoryId;
long long iItemId;
/// Extract ID from url-path.
/// @return URL-path ID.
long long parse_url_path_id(const string& pathId);
/// Clear url-path data.
/// Before parsing the new url-path, empty previous url-path data.
void clear_url_path_data();
};
} // namespace gwinc::web
#endif // GWINC_APPREQUEST_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/lib/AppRequest.cpp
#include "inc/lib/AppRequest.hpp"
#include "inc/lib/URL.hpp"
#include <memory>
#include <regex>
namespace gwinc::web
{
using std::make_unique;
using std::stoll;
using std::unique_ptr;
AppRequest::AppRequest()
{
}
AppRequest::~AppRequest()
{
}
AppRequest& AppRequest::instance()
{
static unique_ptr<AppRequest> obj = nullptr;
if(!obj)
{
obj.reset(new AppRequest());
}
return *obj;
}
AppRequest& AppRequest::set_request(cppcms::http::request* val)
{
this->iRequest = val;
return *this;
}
AppRequest& AppRequest::parse_url(const string& path, short offset)
{
this->clear_url_path_data();
if(path.empty() || offset < 0)
{
return *this;
}
this->iUrlPath = path;
unique_ptr<URL> url = make_unique<URL>(this->iUrlPath);
vector<string> pathSegments = url->path_segments();
if(pathSegments.empty())
{
return *this;
}
// By convention in url-path, controller segment starts from index-pos 1
// and action segment starts from index-pos 2. Increment index-pos by
// offset units to advance the url-mapping starting position.
short controllerIndexPos = 1 + offset;
short actionIndexPos = controllerIndexPos + 1;
short departmentIndexPos = actionIndexPos;
short categoryIndexPos = departmentIndexPos + 1;
short itemIndexPos = categoryIndexPos + 1;
size_t pathSegmentsSize = pathSegments.size();
if(pathSegmentsSize > controllerIndexPos)
{
// Controller path exists.
this->iController = pathSegments[controllerIndexPos];
}
if(pathSegmentsSize > actionIndexPos)
{
this->iAction = pathSegments[actionIndexPos];
string departmentPath = this->iAction;
this->iDepartmentId = this->parse_url_path_id(departmentPath);
}
if(pathSegmentsSize > categoryIndexPos)
{
string categoryPath = pathSegments[categoryIndexPos];
this->iCategoryId = this->parse_url_path_id(categoryPath);
}
if(pathSegmentsSize > itemIndexPos)
{
string itemPath = pathSegments[itemIndexPos];
this->iItemId = this->parse_url_path_id(itemPath);
}
return *this;
}
cppcms::http::request* AppRequest::request()
{
return this->iRequest;
}
string AppRequest::url_path()
{
return this->iUrlPath;
}
string AppRequest::controller()
{
return this->iController;
}
string AppRequest::action()
{
return this->iAction;
}
short AppRequest::department_id()
{
return this->iDepartmentId;
}
int AppRequest::category_id()
{
return this->iCategoryId;
}
long long AppRequest::item_id()
{
return this->iItemId;
}
long long AppRequest::parse_url_path_id(const string& pathId)
{
string subject = pathId;
std::smatch match;
std::regex pattern("^[a-z\\-]+([0-9]+)$");
bool isPathIdExists = std::regex_match(subject, match, pattern);
if(!isPathIdExists)
{
return 0;
}
isPathIdExists = match.size() == 2;
if(!isPathIdExists)
{
return 0;
}
return stoll(match[1]);
}
void AppRequest::clear_url_path_data()
{
this->iUrlPath = "";
this->iController = "";
this->iAction = "";
this->iDepartmentId = 0;
this->iCategoryId = 0;
this->iItemId = 0;
}
} // namespace gwinc::web
2.2.3Application response
Like the
AppRequest
class, the
AppResponse
class is a singleton with a global state. The
AppResponse
is responsible for the C++ web application's HTTP response status code and the final HTTP response data.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/lib/AppResponse.hpp
#ifndef GWINC_APPRESPONSE_HPP
#define GWINC_APPRESPONSE_HPP
#include <string>
/// Gwinc global namespace.
/// @namespace gwinc
/// Web module.
/// @namespace gwinc::web
namespace gwinc::web
{
using std::string;
/// Application response interface.
/// @class AppResponse
class AppResponse
{
public:
/// Virtual destructor.
virtual ~AppResponse();
/// Get an instance of an AppResponse.
/// @return Reference to AppResponse.
static AppResponse& instance();
/// Application response status code.
/// @param val Response code.
/// @return Reference to AppResponse.
AppResponse& set_res_code(short val);
/// Final response data.
/// @param val response data.
/// @return Reference to AppResponse.
AppResponse& set_res_data(const string& val);
/// Get Application response status code.
/// @return Response code.
short res_code();
/// Get final response data.
/// @return Response data.
string res_data();
protected:
/// Constructor.
AppResponse();
private:
// Interface properties.
short iResCode;
string iResData;
};
} // namespace gwinc::web
#endif // GWINC_APPRESPONSE_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/lib/AppResponse.cpp
#include "inc/lib/AppResponse.hpp"
#include <memory>
namespace gwinc::web
{
using std::unique_ptr;
AppResponse::AppResponse()
{
}
AppResponse::~AppResponse()
{
}
AppResponse& AppResponse::instance()
{
static unique_ptr<AppResponse> obj = nullptr;
if(!obj)
{
obj.reset(new AppResponse());
}
return *obj;
}
AppResponse& AppResponse::set_res_code(short val)
{
this->iResCode = val;
return *this;
}
AppResponse& AppResponse::set_res_data(const string& val)
{
this->iResData = val;
return *this;
}
short AppResponse::res_code()
{
return this->iResCode;
}
string AppResponse::res_data()
{
return this->iResData;
}
} // namespace gwinc::web
2.2.3-1HTTP status code
The HTTP status class (
HttpStatus
) provides access to the HTTP protocol response status code through descriptive methods.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/lib/HttpStatus.hpp
#ifndef GWINC_HTTPSTATUS_HPP
#define GWINC_HTTPSTATUS_HPP
/// Gwinc global namespace.
/// @namespace gwinc
/// Web module.
/// @namespace gwinc::web
namespace gwinc::web
{
/// HTTP status code interface.
/// @class HttpStatus
class HttpStatus
{
public:
/// Constructor.
HttpStatus();
/// Virtual destructor.
virtual ~HttpStatus();
/// The request is successful.
/// @return Success status code.
static short ok();
/// The requested page moved permanently.
/// @return Moved permanently status code.
static short moved_permanently();
/// The requested page moved temporarily.
/// @return Moved temporarily status code.
static short temporary_redirect();
/// The requested page access is unauthorized.
/// @return Unauthorized status code.
static short unauthorized();
/// The requested page access is marked as forbidden.
/// @return Forbidden status code.
static short forbidden();
/// The requested page not found.
/// @return Page not found status code.
static short not_found();
};
} // namespace gwinc::web
#endif // GWINC_HTTPSTATUS_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/lib/HttpStatus.cpp
#include "inc/lib/HttpStatus.hpp"
namespace gwinc::web
{
static const short OK = 200;
static const short MOVED_PERMANENTLY = 301;
static const short TEMPORARY_REDIRECT = 307;
static const short UNAUTHORIZED = 401;
static const short FORBIDDEN = 403;
static const short NOT_FOUND = 404;
HttpStatus::HttpStatus()
{
}
HttpStatus::~HttpStatus()
{
}
short HttpStatus::ok()
{
return OK;
}
short HttpStatus::moved_permanently()
{
return MOVED_PERMANENTLY;
}
short HttpStatus::temporary_redirect()
{
return TEMPORARY_REDIRECT;
}
short HttpStatus::unauthorized()
{
return UNAUTHORIZED;
}
short HttpStatus::forbidden()
{
return FORBIDDEN;
}
short HttpStatus::not_found()
{
return NOT_FOUND;
}
} // namespace gwinc::web
2.3Application mapper
The application mapper template class (
AppMapper
) iterates over URL segments to match the URL path and calls the respective Router or Controller class methods mapped to URL path segments.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/lib/AppMapper.hpp
#ifndef GWINC_APPMAPPER_HPP
#define GWINC_APPMAPPER_HPP
#include <map>
#include <memory>
#include <string>
#include "inc/lib/AppRequest.hpp"
/// Gwinc global namespace.
/// @namespace gwinc
/// Web module.
/// @namespace gwinc::web
namespace gwinc::web
{
using std::map;
using std::string;
using std::unique_ptr;
using gwinc::web::AppRequest;
/// Application mapper interface.
/// Request url-segment mapper.
/// @class AppMapper
template <typename T>
class AppMapper
{
public:
/// Constructor.
AppMapper();
/// Virtual destructor.
virtual ~AppMapper();
/// Router or controller class function pointer.
typedef void (T::*MethodPtr)();
/// Add method pointer against url-segment.
/// @param urlSegment A url-segment mapped to controller or action.
/// @param methodPtr Method pointer (controller or action).
void add(const string& urlSegment, const MethodPtr methodPtr);
/// Dispatch controller.
/// @return True if url-segment mapped to controller or else false.
bool controller();
/// Dispatch action.
/// @return True if url-segment mapped to action or else false.
bool action();
private:
/// Application request.
/// Used to get url-segment mapped to controller or action.
AppRequest& req;
/// Router or controller class pointer.
T* route;
/// Method pointer map.
/// Holds method pointer (controller or action) against url-segment.
/// @see add
map<string, MethodPtr> methodMap;
/// Dispatch controller or action.
/// @param urlSegment A url-segment mapped to controller or action.
/// @return True if url-segment mapped to method (controller or action) or else false.
bool dispatch(const string& urlSegment);
};
template <typename T>
AppMapper<T>::AppMapper() :
req(AppRequest::instance()), route(new T())
{
}
template <typename T>
AppMapper<T>::~AppMapper()
{
delete this->route;
this->route = nullptr;
}
template <typename T>
void AppMapper<T>::add(const string& urlSegment, const MethodPtr methodPtr)
{
this->methodMap[urlSegment] = methodPtr;
}
template <typename T>
bool AppMapper<T>::controller()
{
return this->dispatch(this->req.controller());
}
template <typename T>
bool AppMapper<T>::action()
{
return this->dispatch(this->req.action());
}
template <typename T>
bool AppMapper<T>::dispatch(const string& urlSegment)
{
typename map<string, MethodPtr>::const_iterator it1;
MethodPtr methodPtr = nullptr;
for(it1 = this->methodMap.begin(); it1 != this->methodMap.end(); it1++)
{
if(urlSegment == it1->first)
{
methodPtr = it1->second;
break;
}
}
if(!methodPtr)
{
// The requested page doesn't exist.
// Request url-segment doesn't map to any method (controller or action).
return false;
}
// Request url-segment mapped to a method.
// Call the respective method (controller or action).
(this->route->*methodPtr)();
return true;
}
} // namespace gwinc::web
#endif // GWINC_APPMAPPER_HPP
2.4Application server
The
AppServer
class directly inherits from the CppCMS web application class
cppcms::application
. The application server class has the option to set the URL offset (
AppServer::set_url_offset
) before calling the
AppServer::parse_url
method.
The application server's
AppServer::main
method initializes the application request, response, and routing and finally calls the web application response method.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/lib/AppServer.hpp
#ifndef GWINC_APPSERVER_HPP
#define GWINC_APPSERVER_HPP
#include <string>
#include <cppcms/application.h>
/// Gwinc global namespace.
/// @namespace gwinc
/// Web module.
/// @namespace gwinc::web
namespace gwinc::web
{
using std::string;
/// Application server interface.
/// @class AppServer
class AppServer : public cppcms::application
{
public:
/// Non-conversion constructor.
/// @param val Web application service.
explicit AppServer(cppcms::service& srv);
/// Virtual destructor.
virtual ~AppServer() = 0;
/// A url-path segment offset from which the controller and action are parsed.
/// @param offset A url-path segment offset.
/// @return Pointer to AppServer.
/// @note By default, the url is parsed from the first url-path segment (offset = 0).
AppServer* set_url_offset(short offset);
/// This is called implicitly by application server.
/// @param url A url-path.
virtual void main(string url) override;
/// Initialize respective web application routing logic.
virtual void init_routing() = 0;
private:
// Interface properties.
short iUrlOffset;
/// Initialize application request.
void init_app_request(const string& url);
/// Initialize application response.
/// For every new application request, initialize the application response.
void init_app_response();
/// Final application response.
void app_response();
};
} // namespace gwinc::web
#endif // GWINC_APPSERVER_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/lib/AppServer.cpp
#include "inc/lib/AppServer.hpp"
#include <cppcms/http_request.h>
#include <cppcms/http_response.h>
#include "inc/lib/AppRequest.hpp"
#include "inc/lib/AppResponse.hpp"
#include "inc/lib/HttpStatus.hpp"
namespace gwinc::web
{
using gwinc::web::AppRequest;
using gwinc::web::AppResponse;
using gwinc::web::HttpStatus;
AppServer::AppServer(cppcms::service& srv) :
cppcms::application(srv),
iUrlOffset(0)
{
}
AppServer::~AppServer()
{
}
AppServer* AppServer::set_url_offset(short offset)
{
this->iUrlOffset = offset;
return this;
}
void AppServer::main(string url)
{
this->init_app_request(url);
this->init_app_response();
this->init_routing();
this->app_response();
}
void AppServer::init_app_request(const string& url)
{
AppRequest::instance().set_request(&this->request());
AppRequest::instance().parse_url(url, this->iUrlOffset);
// Consult 'cppcms/http_request.h' for available HTTP request variables.
// Access HTTP request parameters.
string storeToken = AppRequest::instance().request()->get("storeToken");
}
void AppServer::init_app_response()
{
AppResponse& res = AppResponse::instance();
// By default all application requests are forbidden.
res.set_res_code(HttpStatus::forbidden());
res.set_res_data("PAGE ACCESS IS FORBIDDEN");
}
void AppServer::app_response()
{
AppResponse& res = AppResponse::instance();
short resCode = res.res_code();
string resData = res.res_data();
if(resCode != HttpStatus::ok())
{
// Update HTTP status-header.
this->response().status(resCode);
}
this->response().out() << resData;
}
} // namespace gwinc::web
2.5Model, view, and controller
First, create an application controller class (
AppController
) that will be the base class for all the web application controllers. The base application controller class holds the web application response code and data that can be accessed later by the application server class (
AppServer
).
$ nano -w -c /usr/local/gwinc/web/example/code/inc/lib/AppController.hpp
#ifndef GWINC_APPCONTROLLER_HPP
#define GWINC_APPCONTROLLER_HPP
#include <string>
/// Gwinc global namespace.
/// @namespace gwinc
/// Web module.
/// @namespace gwinc::web
namespace gwinc::web
{
using std::string;
/// Application controller interface.
/// @class AppController
class AppController
{
public:
/// Constructor.
AppController();
/// Virtual destructor.
virtual ~AppController();
/// Application response status code.
/// @param val Response code.
/// @return Pointer to AppController.
AppController* set_res_code(short val);
/// Final response data.
/// @param val response data.
/// @return Pointer to AppController.
AppController* set_res_data(const string& val);
};
} // namespace gwinc::web
#endif // GWINC_APPCONTROLLER_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/lib/AppController.cpp
#include "inc/lib/AppController.hpp"
#include "inc/lib/AppResponse.hpp"
namespace gwinc::web
{
AppController::AppController()
{
}
AppController::~AppController()
{
}
AppController* AppController::set_res_code(short val)
{
AppResponse::instance().set_res_code(val);
return this;
}
AppController* AppController::set_res_data(const string& val)
{
AppResponse::instance().set_res_data(val);
return this;
}
} // namespace gwinc::web
Create a
model
,
view
, and
controller
directories under the
code/inc
and
code/src
directory.
$ mkdir -p /usr/local/gwinc/web/example/code/{inc,src}/{model,view,controller}
Create the Default, Firm, and Store MVC (Model, View, and Controller) classes that constitute the C++ web application.
- Default MVC classes
- Firm MVC classes
- Store MVC classes
2.5.1Default MVC classes
The default controller class (
DefaultController
) has two actions, namely,
index
and
page_not_found
. The
index
action serves the website's home page (
example.com
), and as the name suggests, the
page_not_found
action serves the page not found error page.
2.5.1-1Default model class
The
record
map object can be populated with static data or data from the file, API service, or a database.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/model/DefaultModel.hpp
#ifndef GWINC_DEFAULTMODEL_HPP
#define GWINC_DEFAULTMODEL_HPP
#include <map>
#include <string>
namespace gwinc::model
{
using std::map;
using std::string;
/// Default model.
/// @class DefaultModel
class DefaultModel
{
public:
/// Constructor.
DefaultModel();
/// Virtual destructor.
virtual ~DefaultModel();
// Controller actions.
map<string, string> index();
map<string, string> page_not_found();
};
} // namespace gwinc::model
#endif // GWINC_DEFAULTMODEL_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/model/DefaultModel.cpp
#include "inc/model/DefaultModel.hpp"
namespace gwinc::model
{
DefaultModel::DefaultModel()
{
}
DefaultModel::~DefaultModel()
{
}
map<string, string> DefaultModel::index()
{
map<string, string> record;
record["page_title"] = "Welcome!, C++ web application.";
return record;
}
map<string, string> DefaultModel::page_not_found()
{
map<string, string> record;
record["page_title"] = "Error 404! Page not found.";
return record;
}
} // namespace gwinc::model
2.5.1-2Default view class
The
html
string variable can be assigned static data or content of the HTML template file by replacing template placeholders with the
DefaultModel
's
record
data.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/view/DefaultView.hpp
#ifndef GWINC_DEFAULTVIEW_HPP
#define GWINC_DEFAULTVIEW_HPP
#include <string>
#include "inc/model/DefaultModel.hpp"
namespace gwinc::view
{
using std::string;
using gwinc::model::DefaultModel;
/// Default view.
/// @class DefaultView
class DefaultView
{
public:
/// Non-conversion constructor.
/// @param viewName View name.
explicit DefaultView(const string& viewName);
/// Virtual destructor.
virtual ~DefaultView();
/// Data used to render view.
/// @param Pointer to default model.
/// @return Pointer to default view.
DefaultView* set_data(DefaultModel* val);
// Controller actions.
string index();
string page_not_found();
private:
// Controller properties.
DefaultModel* data;
};
} // namespace gwinc::view
#endif // GWINC_DEFAULTVIEW_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/view/DefaultView.cpp
#include "inc/view/DefaultView.hpp"
#include <map>
namespace gwinc::view
{
using std::map;
DefaultView::DefaultView(const string& viewName)
{
}
DefaultView::~DefaultView()
{
}
DefaultView* DefaultView::set_data(DefaultModel* val)
{
this->data = val;
return this;
}
string DefaultView::index()
{
map<string, string> vData = this->data->index();
string html = "<h3>"+vData["page_title"]+"</h3>\n";
return html;
}
string DefaultView::page_not_found()
{
map<string, string> vData = this->data->page_not_found();
string html = "<h3 style=\"color:red\">"+vData["page_title"]+"</h3>\n";
return html;
}
} // namespace gwinc::view
2.5.1-3Default controller class
$ nano -w -c /usr/local/gwinc/web/example/code/inc/controller/DefaultController.hpp
#ifndef GWINC_DEFAULTCONTROLLER_HPP
#define GWINC_DEFAULTCONTROLLER_HPP
#include "inc/lib/AppController.hpp"
#include "inc/model/DefaultModel.hpp"
#include "inc/view/DefaultView.hpp"
namespace gwinc::controller
{
using gwinc::web::AppController;
using gwinc::model::DefaultModel;
using gwinc::view::DefaultView;
/// Default controller.
/// @class DefaultController
class DefaultController : public AppController
{
public:
/// Constructor.
DefaultController();
/// Virtual destructor.
virtual ~DefaultController();
/// Home page.
void index();
/// Page not found page.
void page_not_found();
private:
/// Pointer to default model.
DefaultModel* data;
/// Pointer to default view.
DefaultView* view;
};
} // namespace gwinc::controller
#endif // GWINC_DEFAULTCONTROLLER_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/controller/DefaultController.cpp
#include "inc/controller/DefaultController.hpp"
#include <exception>
#include <string>
#include "inc/lib/HttpStatus.hpp"
namespace gwinc::controller
{
using std::exception;
using std::string;
using gwinc::web::HttpStatus;
DefaultController::DefaultController() :
data(new DefaultModel()),
view(new DefaultView(__FUNCTION__))
{
}
DefaultController::~DefaultController()
{
delete this->view;
this->view = nullptr;
delete this->data;
this->data = nullptr;
}
void DefaultController::index()
{
string resData;
try
{
this->view->set_data(this->data);
resData = this->view->index();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::ok());
this->set_res_data(resData);
}
void DefaultController::page_not_found()
{
string resData;
try
{
this->view->set_data(this->data);
resData = this->view->page_not_found();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::not_found());
this->set_res_data(resData);
}
} // namespace gwinc::controller
2.5.2Firm MVC classes
The firm controller class (
FirmController
) has two actions, namely,
index
and
contact
. The
index
action serves the firm's about us page, and the
contact
action serves the firm's contact information page.
2.5.2-1Firm model class
The
record
map object can be populated with static data or data from the file, API service, or a database.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/model/FirmModel.hpp
#ifndef GWINC_FIRMMODEL_HPP
#define GWINC_FIRMMODEL_HPP
#include <map>
#include <string>
namespace gwinc::model
{
using std::map;
using std::string;
/// Firm model.
/// @class FirmModel
class FirmModel
{
public:
/// Constructor.
FirmModel();
/// Virtual destructor.
virtual ~FirmModel();
// Controller actions.
map<string, string> index();
map<string, string> contact();
};
} // namespace gwinc::model
#endif // GWINC_FIRMMODEL_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/model/FirmModel.cpp
#include "inc/model/FirmModel.hpp"
namespace gwinc::model
{
FirmModel::FirmModel()
{
}
FirmModel::~FirmModel()
{
}
map<string, string> FirmModel::index()
{
map<string, string> record;
record["page_title"] = "About us page";
record["firm_name"] = "Global Webdynamics";
return record;
}
map<string, string> FirmModel::contact()
{
map<string, string> record;
record["page_title"] = "Contact";
record["email"] = "info@example.com";
return record;
}
} // namespace gwinc::model
2.5.2-2Firm view class
The
html
string variable can be assigned static data or content of the HTML template file by replacing template placeholders with the
FirmModel
's
record
data.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/view/FirmView.hpp
#ifndef GWINC_FIRMVIEW_HPP
#define GWINC_FIRMVIEW_HPP
#include <string>
#include "inc/model/FirmModel.hpp"
namespace gwinc::view
{
using std::string;
using gwinc::model::FirmModel;
/// Firm view.
/// @class FirmView
class FirmView
{
public:
/// Constructor.
/// @param viewName View name.
explicit FirmView(const string& viewName);
/// Virtual destructor.
virtual ~FirmView();
/// Data used to render view.
/// @param val Pointer to firm model.
/// @return Pointer to firm view.
FirmView* set_data(FirmModel* val);
// Controller actions.
string index();
string contact();
private:
// Controller properties.
FirmModel* data;
};
} // namespace gwinc::view
#endif // GWINC_FIRMVIEW_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/view/FirmView.cpp
#include "inc/view/FirmView.hpp"
#include <map>
namespace gwinc::view
{
using std::map;
FirmView::FirmView(const string& viewName)
{
}
FirmView::~FirmView()
{
}
FirmView* FirmView::set_data(FirmModel* val)
{
this->data = val;
return this;
}
string FirmView::index()
{
map<string, string> vData = this->data->index();
string html = "<h2>"+vData["page_title"]+"</h2>\n\n";
html += "<h3>"+vData["firm_name"]+"</h3>\n";
return html;
}
string FirmView::contact()
{
map<string, string> vData = this->data->contact();
string html = "<h2>"+vData["page_title"]+"</h2>\n\n";
html += "<h4>Email ID: "+vData["email"]+"</h4>\n";
return html;
}
} // namespace gwinc::view
2.5.2-3Firm controller class
$ nano -w -c /usr/local/gwinc/web/example/code/inc/controller/FirmController.hpp
#ifndef GWINC_FIRMCONTROLLER_HPP
#define GWINC_FIRMCONTROLLER_HPP
#include "inc/lib/AppController.hpp"
#include "inc/model/FirmModel.hpp"
#include "inc/view/FirmView.hpp"
namespace gwinc::controller
{
using gwinc::web::AppController;
using gwinc::model::FirmModel;
using gwinc::view::FirmView;
/// Firm controller.
/// @class FirmController
class FirmController : public AppController
{
public:
/// Constructor.
FirmController();
/// Virtual destructor.
virtual ~FirmController();
/// About us page.
void index();
/// Contact page.
void contact();
private:
/// Pointer to firm model.
FirmModel* data;
/// Pointer to firm view.
FirmView* view;
};
} // namespace gwinc::controller
#endif // GWINC_FIRMCONTROLLER_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/controller/FirmController.cpp
#include "inc/controller/FirmController.hpp"
#include <exception>
#include <string>
#include "inc/lib/HttpStatus.hpp"
namespace gwinc::controller
{
using std::exception;
using std::string;
using gwinc::web::HttpStatus;
FirmController::FirmController() :
data(new FirmModel()),
view(new FirmView(__FUNCTION__))
{
}
FirmController::~FirmController()
{
delete this->view;
this->view = nullptr;
delete this->data;
this->data = nullptr;
}
void FirmController::index()
{
string resData;
try
{
this->view->set_data(this->data);
resData = this->view->index();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::ok());
this->set_res_data(resData);
}
void FirmController::contact()
{
string resData;
try
{
this->view->set_data(this->data);
resData = this->view->contact();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::ok());
this->set_res_data(resData);
}
} // namespace gwinc::controller
2.5.3Store MVC classes
The store controller class (
StoreController
) has four actions:
index
,
department
,
category
, and
item
.
-
The
index
action serves the store's home page. -
The
department
action serves the product catalog department info and category listing. -
The
category
action serves the product catalog category info and item listing. -
The
item
action serves the item's details page.
2.5.3-1Store model class
The
StoreModel
product catalog methods
record
map object can be populated with static data or data from the product catalog file, product catalog API service, or a product catalog database.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/model/StoreModel.hpp
#ifndef GWINC_STOREMODEL_HPP
#define GWINC_STOREMODEL_HPP
#include <map>
#include <string>
#include <vector>
namespace gwinc::model
{
using std::map;
using std::string;
using std::vector;
/// Store model manager.
/// @class StoreModel
class StoreModel
{
public:
/// Constructor.
StoreModel();
/// Virtual destructor.
virtual ~StoreModel();
// Controller actions.
map<string, string> index();
map<string, string> department();
map<string, string> category();
map<string, string> item();
// Product catalog methods.
/// Set department ID.
/// @param val Department ID.
/// @return Pointer to StoreModel.
StoreModel* set_department_id(short val);
/// Set category ID.
/// @param val Category ID.
/// @return Pointer to StoreModel.
StoreModel* set_category_id(int val);
/// Set item ID.
/// @param val Item ID.
/// @return Pointer to StoreModel.
StoreModel* set_item_id(long long val);
/// Get department record by ID.
/// @return Department record.
map<string, string> get_department_record_by_id();
/// Get all department records.
/// @return Department records.
vector<map<string, string>> get_department_records();
/// Get category record by ID.
/// @return Category record.
map<string, string> get_category_record_by_id();
/// Get all category records by department ID.
/// @return Category records.
vector<map<string, string>> get_category_records_by_department_id();
/// Get all item records by category ID.
/// @return Item records.
vector<map<string, string>> get_item_records_by_category_id();
/// Get item record by ID.
/// @return Item record.
map<string, string> get_item_record_by_id();
private:
// Interface properties.
short iDepartmentId;
int iCategoryId;
long long iItemId;
};
} // namespace gwinc::model
#endif // GWINC_STOREMODEL_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/model/StoreModel.cpp
#include "inc/model/StoreModel.hpp"
#include "inc/lib/AppRequest.hpp"
namespace gwinc::model
{
using gwinc::web::AppRequest;
StoreModel::StoreModel() :
iDepartmentId(0),
iCategoryId(0),
iItemId(0)
{
}
StoreModel::~StoreModel()
{
}
map<string, string> StoreModel::index()
{
map<string, string> record;
record["seo_description"] = "Store SEO description";
record["seo_keywords"] = "Store SEO keywords";
record["seo_title"] = "Store SEO title";
record["store_token"] = AppRequest::instance().request()->get("storeToken");
return record;
}
map<string, string> StoreModel::department()
{
map<string, string> record;
record["seo_description"] = "Department SEO description";
record["seo_keywords"] = "Department SEO keywords";
record["seo_title"] = "Department SEO title";
return record;
}
map<string, string> StoreModel::category()
{
map<string, string> record;
record["seo_description"] = "Category SEO description";
record["seo_keywords"] = "Category SEO keywords";
record["seo_title"] = "Category SEO title";
return record;
}
map<string, string> StoreModel::item()
{
map<string, string> record;
record["seo_description"] = "Item SEO description";
record["seo_keywords"] = "Item SEO keywords";
record["seo_title"] = "Item SEO title";
return record;
}
StoreModel* StoreModel::set_department_id(short val)
{
this->iDepartmentId = val;
return this;
}
StoreModel* StoreModel::set_category_id(int val)
{
this->iCategoryId = val;
return this;
}
StoreModel* StoreModel::set_item_id(long long val)
{
this->iItemId = val;
return this;
}
map<string, string> StoreModel::get_department_record_by_id()
{
map<string, string> record;
if(this->iDepartmentId == 3)
{
record["name"] = "Department name - "+std::to_string(this->iDepartmentId);
}
else
{
record["name"] = "Department does not exist.";
}
return record;
}
vector<map<string, string>> StoreModel::get_department_records()
{
vector<map<string, string>> records;
map<string, string> record1;
record1["name"] = "Department name - 2";
records.push_back(record1);
map<string, string> record2;
record2["name"] = "Department name - 3";
records.push_back(record2);
return records;
}
map<string, string> StoreModel::get_category_record_by_id()
{
map<string, string> record;
if(this->iCategoryId == 5)
{
record["name"] = "Category name - "+std::to_string(this->iCategoryId);
}
else
{
record["name"] = "Category does not exist.";
}
return record;
}
vector<map<string, string>> StoreModel::get_category_records_by_department_id()
{
vector<map<string, string>> records;
if(this->iDepartmentId == 3)
{
map<string, string> record1;
record1["name"] = "Category name - 4";
records.push_back(record1);
map<string, string> record2;
record2["name"] = "Category name - 5";
records.push_back(record2);
}
return records;
}
vector<map<string, string>> StoreModel::get_item_records_by_category_id()
{
vector<map<string, string>> records;
if(this->iCategoryId == 5)
{
map<string, string> record1;
record1["name"] = "Item name - 6";
records.push_back(record1);
map<string, string> record2;
record2["name"] = "Item name - 7";
records.push_back(record2);
}
return records;
}
map<string, string> StoreModel::get_item_record_by_id()
{
map<string, string> record;
if(this->iItemId == 7)
{
record["name"] = "Item name - "+std::to_string(this->iItemId);
record["description"] = "Item description ... ... ...";
}
else
{
record["name"] = "Category does not exist.";
record["description"] = "N/A";
}
return record;
}
} // namespace gwinc::model
2.5.3-2Store view class
The
html
string variable can be assigned content of the HTML template file by replacing template placeholders with the
StoreModel
's
record
data.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/view/StoreView.hpp
#ifndef GWINC_STOREVIEW_HPP
#define GWINC_STOREVIEW_HPP
#include <string>
#include "inc/model/StoreModel.hpp"
namespace gwinc::view
{
using std::string;
using gwinc::model::StoreModel;
/// Store view.
/// @class StoreView
class StoreView
{
public:
/// Non-conversion constructor.
/// @param viewName View name.
explicit StoreView(const string& viewName);
/// Virtual destructor.
virtual ~StoreView();
/// Data used to render view.
/// @param Pointer to store model.
/// @return Pointer to store view.
StoreView* set_data(StoreModel* val);
// Controller actions.
string index();
string department();
string category();
string item();
private:
// Controller properties.
StoreModel* data;
};
} // namespace gwinc::view
#endif // GWINC_STOREVIEW_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/view/StoreView.cpp
#include "inc/view/StoreView.hpp"
#include <map>
#include <vector>
namespace gwinc::view
{
using std::map;
using std::vector;
StoreView::StoreView(const string& viewName)
{
}
StoreView::~StoreView()
{
}
StoreView* StoreView::set_data(StoreModel* val)
{
this->data = val;
return this;
}
string StoreView::index()
{
map<string, string> vData = this->data->index();
string html;
// Store SEO.
html += "<h5>"+vData["seo_description"]+"</h5>\n";
html += "<h5>"+vData["seo_keywords"]+"</h5>\n";
html += "<h5>"+vData["seo_title"]+"</h5>\n\n";
// HTTP get parameter
html += "<h2>HTTP get parameter: storeToken = "+vData["store_token"]+"</h2>\n\n";
// Department records.
vector<map<string, string>>::const_iterator it1;
vector<map<string, string>> dptRecords = this->data->get_department_records();
map<string, string> dptRecord;
for(it1 = dptRecords.begin(); it1 != dptRecords.end(); it1++)
{
dptRecord = *it1;
html += "<h4> -- "+dptRecord["name"]+"</h4>\n";
}
return html;
}
string StoreView::department()
{
map<string, string> vData = this->data->department();
string html;
// Department SEO.
html += "<h5>"+vData["seo_description"]+"</h5>\n";
html += "<h5>"+vData["seo_keywords"]+"</h5>\n";
html += "<h5>"+vData["seo_title"]+"</h5>\n\n";
// Department info.
map<string, string> dptRecord = this->data->get_department_record_by_id();
html += "<h2>"+dptRecord["name"]+"</h2>\n";
// Category records.
vector<map<string, string>>::const_iterator it1;
vector<map<string, string>> ctgRecords = this->data->get_category_records_by_department_id();
map<string, string> ctgRecord;
for(it1 = ctgRecords.begin(); it1 != ctgRecords.end(); it1++)
{
ctgRecord = *it1;
html += "<h4> -- "+ctgRecord["name"]+"</h4>\n";
}
return html;
}
string StoreView::category()
{
map<string, string> vData = this->data->category();
string html;
// Category SEO.
html += "<h5>"+vData["seo_description"]+"</h5>\n";
html += "<h5>"+vData["seo_keywords"]+"</h5>\n";
html += "<h5>"+vData["seo_title"]+"</h5>\n\n";
// Category info.
map<string, string> ctgRecord = this->data->get_category_record_by_id();
html += "<h2>"+ctgRecord["name"]+"</h2>\n";
// Item records.
vector<map<string, string>>::const_iterator it1;
vector<map<string, string>> itmRecords = this->data->get_item_records_by_category_id();
map<string, string> itmRecord;
for(it1 = itmRecords.begin(); it1 != itmRecords.end(); it1++)
{
itmRecord = *it1;
html += "<h4> -- "+itmRecord["name"]+"</h4>\n";
}
return html;
}
string StoreView::item()
{
map<string, string> vData = this->data->item();
string html;
// Item SEO.
html += "<h5>"+vData["seo_description"]+"</h5>\n";
html += "<h5>"+vData["seo_keywords"]+"</h5>\n";
html += "<h5>"+vData["seo_title"]+"</h5>\n\n";
// Item info.
map<string, string> itmRecord = this->data->get_item_record_by_id();
html += "<h2>"+itmRecord["name"]+"</h2>\n";
html += "<h4>"+itmRecord["description"]+"</h4>\n";
return html;
}
} // namespace gwinc::view
2.5.3-3Store controller class
The store controller class (
StoreController
) actions:
department
,
category
, and
item
are passed with
departmentId
,
categoryId
, and
itemId
, respectively, as function parameters.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/controller/StoreController.hpp
#ifndef GWINC_STORECONTROLLER_HPP
#define GWINC_STORECONTROLLER_HPP
#include "inc/lib/AppController.hpp"
#include "inc/model/StoreModel.hpp"
#include "inc/view/StoreView.hpp"
namespace gwinc::controller
{
using gwinc::web::AppController;
using gwinc::model::StoreModel;
using gwinc::view::StoreView;
/// Store controller.
/// @class StoreController
class StoreController : public AppController
{
public:
/// Constructor.
StoreController();
/// Virtual destructor.
virtual ~StoreController();
/// Store home page.
void index();
/// Department page.
/// @param departmentId Department ID.
void department(short departmentId);
/// Category page.
/// @param categoryId Category ID.
void category(int categoryId);
/// Item page.
/// @param itemId Item ID.
void item(long long itemId);
private:
/// Pointer to store model.
StoreModel* data;
/// Pointer to store view.
StoreView* view;
};
} // namespace gwinc::controller
#endif // GWINC_STORECONTROLLER_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/controller/StoreController.cpp
#include "inc/controller/StoreController.hpp"
#include <exception>
#include <string>
#include "inc/lib/HttpStatus.hpp"
namespace gwinc::controller
{
using std::exception;
using std::string;
using gwinc::web::HttpStatus;
StoreController::StoreController() :
data(new StoreModel()),
view(new StoreView(__FUNCTION__))
{
}
StoreController::~StoreController()
{
delete this->view;
this->view = nullptr;
delete this->data;
this->data = nullptr;
}
void StoreController::index()
{
string resData;
try
{
this->view->set_data(this->data);
resData = this->view->index();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::ok());
this->set_res_data(resData);
}
void StoreController::department(short departmentId)
{
string resData;
try
{
this->data->set_department_id(departmentId);
this->view->set_data(this->data);
resData = this->view->department();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::ok());
this->set_res_data(resData);
}
void StoreController::category(int categoryId)
{
string resData;
try
{
this->data->set_category_id(categoryId);
this->view->set_data(this->data);
resData = this->view->category();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::ok());
this->set_res_data(resData);
}
void StoreController::item(long long itemId)
{
string resData;
try
{
this->data->set_item_id(itemId);
this->view->set_data(this->data);
resData = this->view->item();
}
catch(const exception& e)
{
resData = string(e.what());
}
this->set_res_code(HttpStatus::ok());
this->set_res_data(resData);
}
} // namespace gwinc::controller
2.6Website URL routing
We have a routing mechanism through
AppMapper
, utility classes under the
lib
directory, and business logic handled by the MVC classes. We need a First Controller pattern, also known as a Router, that invokes controller actions by website URL matching.
2.6.1Application router
Create a
Router
class that will add a mapping between the website URL segment and controller actions.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/Router.hpp
#ifndef GWINC_ROUTER_HPP
#define GWINC_ROUTER_HPP
namespace gwinc
{
/// Application router.
/// Map URL-path to action.
/// @class Router
class Router
{
public:
/// Constructor.
Router();
/// Virtual destructor.
virtual ~Router();
/// Initialize default controller.
void init_default_controller();
/// Initialize firm controller.
void init_firm_controller();
/// Initialize store controller.
void init_store_controller();
/// Handle final routing.
/// @param pageFound Page found flag.
void route(bool pageFound);
};
} // namespace gwinc
#endif // GWINC_ROUTER_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/Router.cpp
#include "inc/Router.hpp"
#include <memory>
#include <string>
#include "inc/lib/AppMapper.hpp"
#include "inc/lib/AppRequest.hpp"
#include "inc/controller/DefaultController.hpp"
#include "inc/controller/FirmController.hpp"
#include "inc/controller/StoreController.hpp"
namespace gwinc
{
using std::make_unique;
using std::string;
using std::unique_ptr;
using gwinc::web::AppMapper;
using gwinc::web::AppRequest;
using gwinc::controller::DefaultController;
using gwinc::controller::FirmController;
using gwinc::controller::StoreController;
Router::Router()
{
}
Router::~Router()
{
}
void Router::init_default_controller()
{
unique_ptr<DefaultController> action = make_unique<DefaultController>();
action->index();
}
void Router::init_firm_controller()
{
unique_ptr<AppMapper<FirmController>> appMapper = make_unique<AppMapper<FirmController>>();
appMapper->add("", &FirmController::index);
appMapper->add("contact", &FirmController::contact);
this->route(appMapper->action());
}
void Router::init_store_controller()
{
AppRequest& req = AppRequest::instance();
unique_ptr<StoreController> action = make_unique<StoreController>();
if(req.item_id())
{
action->item(req.item_id());
}
else if(req.category_id())
{
action->category(req.category_id());
}
else if(req.department_id())
{
action->department(req.department_id());
}
else
{
action->index();
}
}
void Router::route(bool pageFound)
{
if(!pageFound)
{
unique_ptr<DefaultController> action = make_unique<DefaultController>();
action->page_not_found();
}
}
} // namespace gwinc
2.6.2Web application service
The web application class (
WebApp
) inherits from
AppServer
. The
WebApp::init_routing
method initializes the application routing, thereby initializing the web application controllers by matching website URLs.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/WebApp.hpp
#ifndef GWINC_WEBAPP_HPP
#define GWINC_WEBAPP_HPP
#include <string>
#include "inc/lib/AppServer.hpp"
namespace gwinc
{
using std::string;
using gwinc::web::AppServer;
/// Web application service.
/// @class WebApp
class WebApp : public AppServer
{
public:
/// Non-conversion constructor.
/// @param val Web application service.
explicit WebApp(cppcms::service& srv);
/// Virtual destructor.
virtual ~WebApp();
/// This is called implicitly by CppCMS application.
/// @param url A url-path.
virtual void main(string url) override;
/// Initialize routing.
/// Map url-path to controller.
void init_routing() override;
};
} // namespace gwinc
#endif // GWINC_WEBAPP_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/WebApp.cpp
#include "inc/WebApp.hpp"
#include <memory>
#include "inc/lib/AppMapper.hpp"
#include "inc/Router.hpp"
namespace gwinc
{
using std::make_unique;
using std::unique_ptr;
using gwinc::web::AppMapper;
WebApp::WebApp(cppcms::service& srv) :
AppServer(srv)
{
}
WebApp::~WebApp()
{
}
void WebApp::main(string url)
{
AppServer::main(url);
}
void WebApp::init_routing()
{
unique_ptr<AppMapper<Router>> appMapper = make_unique<AppMapper<Router>>();
appMapper->add("", &Router::init_default_controller);
appMapper->add("gwinc", &Router::init_firm_controller);
appMapper->add("gwinc-store", &Router::init_store_controller);
unique_ptr<Router> router = make_unique<Router>();
router->route(appMapper->controller());
}
} // namespace gwinc
2.6.3Main application service
Create a CppCMS application service to mount the web application class (
WebApp
) and run the C++ application server service that services the request from the Lighttpd web server through FastCGI.
$ nano -w -c /usr/local/gwinc/web/example/code/inc/main.hpp
#ifndef GWINC_MAIN_HPP
#define GWINC_MAIN_HPP
#include <stdexcept>
#include <iostream>
#include <cppcms/applications_pool.h>
#include <cppcms/service.h>
#include "inc/WebApp.hpp"
using std::cout;
using std::endl;
using std::exception;
using gwinc::WebApp;
#endif // GWINC_MAIN_HPP
$ nano -w -c /usr/local/gwinc/web/example/code/src/main.cpp
#include "inc/main.hpp"
int main(int argc, char** argv)
{
cout << endl << "app_name" << " " << "app_version" << endl;
try
{
cppcms::service srv(argc, argv);
srv.applications_pool().mount(cppcms::create_pool<WebApp>());
srv.run();
}
catch(const exception& e)
{
cout << "Web application error:" << endl;
cout << e.what() << endl;
}
}
2.7Build CppCMS application
Build a CppCMS C++ web application with the IDE of your choice, or you can use the
CMake
build system to compile the application.
2.7.1CMake build setup
Create a
bin
and
build
directory and CMake build file
CMakeLists.txt
in the web application directory (
/usr/local/gwinc/web/example
).
$ mkdir -p /usr/local/gwinc/web/example/{bin,build}
$ nano -w -c /usr/local/gwinc/web/example/CMakeLists.txt
# CMAKE EXECUTABLE BUILD
cmake_minimum_required(VERSION 3.2)
## EXECUTABLE INFO
set(GWINC_APP_NAME "example")
cmake_policy(SET CMP0048 NEW)
project(gwinc_${GWINC_APP_NAME} VERSION 1.0.0)
## VERSION
set(GWINC_APP_VER ${PROJECT_VERSION})
set(GWINC_ABI_VER ${PROJECT_VERSION_MAJOR})
set(GWINC_API_VER ${PROJECT_VERSION_MINOR})
set(GWINC_FIX_VER ${PROJECT_VERSION_PATCH})
file(WRITE VERSION ${GWINC_APP_VER})
## COMPILER SETTINGS
set(CMAKE_C_COMPILER "clang")
set(CMAKE_CXX_COMPILER "clang++")
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_BUILD_TYPE "RelWithDebInfo")
## BINARY EXECUTABLE
set(GWINC_BIN_DIR ${CMAKE_SOURCE_DIR}/bin)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${GWINC_BIN_DIR})
add_executable(${GWINC_APP_NAME})
## SOURCE FILES
cmake_policy(SET CMP0076 NEW)
target_sources(${GWINC_APP_NAME} PRIVATE
code/src/lib/AppController.cpp
code/inc/lib/AppMapper.hpp
code/src/lib/AppRequest.cpp
code/src/lib/AppResponse.cpp
code/src/lib/AppServer.cpp
code/src/lib/HttpStatus.cpp
code/src/lib/URL.cpp
code/src/model/DefaultModel.cpp
code/src/model/FirmModel.cpp
code/src/model/StoreModel.cpp
code/src/view/DefaultView.cpp
code/src/view/FirmView.cpp
code/src/view/StoreView.cpp
code/src/controller/DefaultController.cpp
code/src/controller/FirmController.cpp
code/src/controller/StoreController.cpp
code/src/main.cpp
code/src/Router.cpp
code/src/WebApp.cpp
)
## INCLUDE SEARCH DIRECTORY
target_include_directories(${GWINC_APP_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/code
/usr/local/include
/usr/include/c++/v1
)
## COMPILER OPTIONS
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -std=c++14")
target_compile_options(${GWINC_APP_NAME} PRIVATE
-fexceptions -fvisibility=hidden
-Wno-deprecated -Wno-c++17-extensions
)
## LINKER OPTIONS
target_link_options(${GWINC_APP_NAME} PRIVATE -s)
set(LIB_SEARCH_DIR /usr/local/lib)
find_library(LIB_CPPCMS cppcms PATHS ${LIB_SEARCH_DIR} REQUIRED)
find_library(LIB_BOOSTER booster PATHS ${LIB_SEARCH_DIR} REQUIRED)
target_link_libraries(${GWINC_APP_NAME} PRIVATE
${LIB_CPPCMS}
${LIB_BOOSTER}
)
2.7.2CMake build steps
Use the following steps to build the C++ web application using CMake.
$ cd /usr/local/gwinc/web/example/build
$ cmake ..
$ gmake
The
gmake
(GNU make utility) will build and install the C++ web application binary executable (
example
) to the
bin
directory. Copy the application binary file to the C++ web application directory (
/usr/local/gwinc/web/example
).
$ cp /usr/local/gwinc/web/example/bin/example /usr/local/gwinc/web/example
$ chmod 755 /usr/local/gwinc/web/example/example
2.8Run CppCMS application
When the
lighhtpd
service is restarted, the web server will run the CppCMS
example
web application process using FastCGI protocol through a Unix socket and the website can be accessed through the URL
https://example.com
.
$ sudo service lighttpd restart
Access the website
example.com
through any web browser, or for quick testing, use the
wget
network tool to download the website content.
# ---- DefaultController actions ----
$ wget --no-check-certificate -qO - https://example.com
<h3>Welcome!, C++ web application.</h3>
$ wget --no-check-certificate --content-on-error -qO - https://example.com/dummy-path
<h3 style="color:red">Error 404! Page not found.</h3>
# ---- FirmController actions ----
$ wget --no-check-certificate -qO - https://example.com/gwinc
<h2>About us page</h2>
<h3>Global Webdynamics</h3>
$ wget --no-check-certificate -qO - https://example.com/gwinc/contact
<h2>Contact</h2>
<h4>Email ID: info@example.com</h4>
# ---- StoreController actions ----
$ wget --no-check-certificate -qO - https://example.com/gwinc-store
<h5>Store SEO description</h5>
<h5>Store SEO keywords</h5>
<h5>Store SEO title</h5>
<h4> --Department name - 2</h4>
<h4> --Department name - 3</h4>
$ wget --no-check-certificate -qO - https://example.com/gwinc-store?storeToken=54321
<h5>Store SEO description</h5>
<h5>Store SEO keywords</h5>
<h5>Store SEO title</h5>
<h2>HTTP get parameter: storeToken = 54321</h2>
<h4> --Department name - 2</h4>
<h4> --Department name - 3</h4>
$ wget --no-check-certificate -qO - https://example.com/gwinc-store/department-3
<h5>Department SEO description</h5>
<h5>Department SEO keywords</h5>
<h5>Department SEO title</h5>
<h2>Department name - 3</h2>
<h4> --Category name - 4</h4>
<h4> --Category name - 5</h4>
$ wget --no-check-certificate -qO - https://example.com/gwinc-store/department-3/category-5
<h5>Category SEO description</h5>
<h5>Category SEO keywords</h5>
<h5>Category SEO title</h5>
<h2>Category name - 5</h2>
<h4> --Item name - 6</h4>
<h4> --Item name - 7</h4>
$ wget --no-check-certificate -qO - https://example.com/gwinc-store/department-3/category-5/item-7
<h5>Item SEO description</h5>
<h5>Item SEO keywords</h5>
<h5>Item SEO title</h5>
<h2>Item name - 7</h2>
<h4>Item description ... ... ...</h4>
Affiliate links
Setting up a CppCMS C++ web application server requires a VPS with root access. Use our affiliate links to purchase a VPS or cloud server from third-party vendors. The affiliate commissions we earn facilitate, Free website access for everyone.
- Cloudways WordPress VPS hosting
- Linode VPS hosting $100 credit
- Tastytrade invest in US markets
- Vultr VPS hosting $100 credit
The affiliate links are listed in alphabetical order without any favor. Users are encouraged to refer to the Global Webdynamics LLP Terms of Service governing the Third-party vendors.