Dear %username%,
I would like to share with you my first-hand experience in creating a Website on CppCMS (library-template engine on C++). It can also be named as “help for beginners on CppCMS”.
Why Would You Write a Website with C++
There are different pros and cons against this solution. So in order not to provoke a “language war”, I will draw an analogy with cars: “I bought one. I like it and don’t want to sell it!”. As an additional argument I will use the fact that this language is filed-specific for my job.
High Time to Write Something
But before that
Before we write a website, CppCMS should be installed on the working machine. The library requires Boost c++, pcre, crypt, python, icu for its operation. Despite the cross-platform it's easier to set everything up under *nix systems.
Building is quite trite:
mkdir build
cd build
cmake ..
make
make install
There should be no problems with it. Everything is built automatically and it has never let me down.
Looking ahead, I would like to say that development should desirably be able to execute user building steps and have a handily adjusted syntax analyzer. I use QtCreator. I am going to describe all my further steps with reference to the environment mentioned above, as the building by using the command line is well described at the library's website. I would also like to mention that some building actions will be automated by bash scripts (though it would be enough to write a “user step” at the setup stage).
It’s better to add the syntax highlight for QtCreator before we begin. It will help to identify special *.tmpl files which are used as templates. The given file tmpl.xml (slightly modified HTML highlight) should be allocated in the config folder “qtcreator/generic-highlighter/tmpl.xml”:
]>
Now Let’s Start
Depending on the built, the following should be added:
LIBS += -L/usr/local/lib/ -lbooster -lcppcms
INCLUDEPATH += /usr/local/include
DEPENDPATH += /usr/local/include
Create main.cpp file and fill it with the following content:
#include
#include
#include
#include
#include
#include
//-------------------------------------------------------------------------------------
// Dsc: Our class for page rendering, when some address is requested by the user
// First of all he will get here
//-------------------------------------------------------------------------------------
class WebSite : public cppcms::application{
public:
//-------------------------------------------------------------------------------------
// Constructor. Entry point.
//-------------------------------------------------------------------------------------
WebSite(cppcms::service &s) : cppcms::application(s)
{}
//-------------------------------------------------------------------------------------
// Dsc: The function we’ll get into if it’s not indicated otherwise in the constructor.
// (more about it later)
//-------------------------------------------------------------------------------------
virtual void main(std::string path)
{
response().out() << "Hello!";
}
};
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
int main(int argc,char **argv)
{
try {
// create the service
cppcms::service srv(argc,argv);
// define the root
srv.applications_pool().mount(cppcms::applications_factory());
// run
srv.run();
}
catch(std::exception const &e) {
std::cerr << "Failed: " << e.what() << std::endl;
std::cerr << booster::trace(e) << std::endl;
return 1;
}
return 0;
}
If you have already tried to run it, you most likely failed. Configuration file for the server is lacking for full-fledged operation. Its location should be passed at start to the binary file.
WebApp.bin -c config.json
Here is an example of the config file:
{
"WebSite" : {
"root" : "",
"host" : "localhost:8080",
"locdomain" : "localhost",
},
"service" : {
"ip" : "0.0.0.0",
"api" : "http",
"port" : 8080
},
"http" : {
"script" : "/mb.fcgi" ,
"rewrite" : [
{ "regex" : ".*" , "pattern" : "/mb.fcgi$0" }
],
}
}
This should be enough.
You could also write startup options in “configs of the programming environment”.
So add it, then start.
Impressed? No?
Of course you aren’t as we haven’t used template mechanism for this example. We just put the string out. But the example lets us make sure that everything works.
Let's use templates
Let’s write the first template which will be “transformed” by the template engine of the library into *.cpp file.
First of all, we need to add a header file which contains the structure of dynamic data (template data). They will be located in the data folder within the project by default.
data/tmpl_master.h
#ifndef TMPL_MASTER_H
#define TMPL_MASTER_H
#include
namespace Data {
//-------------------------------------------------------------------------------------
// Dsc: Basic page information
//-------------------------------------------------------------------------------------
struct infoPage {
std::string title; // page title
std::string description; // page description
std::string keywords; // page key words
std::map menuList; // list of menu items output (url,desc)
//-------------------------------------------------------------------------------------
// Dsc: Constructor
//-------------------------------------------------------------------------------------
infoPage() :
title (""),
description(""),
keywords (""),
menuList ( )
{}
//-------------------------------------------------------------------------------------
// Dsc: Destructor, doing nothing
//-------------------------------------------------------------------------------------
~infoPage(){}
};
//-------------------------------------------------------------------------------------
// Dsc: Basic content which exists on every page
//-------------------------------------------------------------------------------------
struct Master :public cppcms::base_content {
infoPage page;
//-------------------------------------------------------------------------------------
// Dsc: Page's constructor
//-------------------------------------------------------------------------------------
Master() :
page()
{}
//-------------------------------------------------------------------------------------
// Dsc: Lazy destructor
//-------------------------------------------------------------------------------------
~Master(){}
};
}
#endif
As a rule, the content of the given file isn’t notable for some smart code. They’re just containers for description of the variables, which are used in templates.
Let’s describe the template, then create templates folder and master.tmpl in it with the following content:
<% c++ #include "data/tmpl_master.h" %>
<% skin defskin %>
<% view Master uses Data::Master %>
<% template page_main() %>MAIN TEMPLATE<% end %>
<% template page_footer() %>Все права защищены<% end %>
<% template page_left_sidebar() %>Левая панелька<% end %>
<% template render() %>
<%= page.title %>
<% include page_main() %>
<% end template %>
<% end view %>
<% end skin %>
So what is written here?
In the very first line <% c++ #include «data/tmpl_master.h» %> the header file is written. Data structures will be declared in it.
<% skin defskin %> line defines the current skin name, so you can have different page skins.
<% view Master uses Data::Master %> line defines the current template name as “Master” (it will be further indicated for page filling mechanism) and also creates Data::Master structure inside the wrapper class. In C++ it will look like “Data::Master context;” (if you are interested in details, you can always look at the generated file).
<% template page_main() %>MAIN TEMPLATE<% end %> <% template page_footer() %>All rights reserved<% end %> <% template page_left_sidebar() %>Left panel<% end %> lines define default values which will be derived to the user unless we override them (i.e. they are
virtual const char* page_main(){ return "MAIN TEMPLATE"; }
)Now let’s try to build. Of course C++ compiler won’t handle the entire tmpl file. That’s why we need a utility which will, together with the library, process the template to the needed state.
We’ll create “make_templates.sh” within the project. Inside the file the necessary operations will be located. The given file can be easily replaced either by manual call of this utility or by registering it at the “executed part” of the environment:
#!/bin/bash
INPUT=""
OUTPUT=""
while getopts ":i:o:" opt; do
case $opt in
i)
INPUT=$OPTARG
;;
o)
OUTPUT=$OPTARG
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "Option -$OPTARG requires an argument." >&2
exit 1
;;
esac
done
# copy the configuration file to the build folder
cp $INPUT/config.json $OUTPUT
# write all templates here
TEMPLATES="$INPUT/templates/master.tmpl"
# process templates to срр
cppcms_tmpl_cc $TEMPLATES -o $INPUT/all_tmpl.cpp
# collect templates to the library
g++ -shared -fPIC $INPUT/all_tmpl.cpp -o $OUTPUT/libcpp_defskin.so -lcppcms -lbooster
Now we should add a “user step” in the settings of QtCreator project
Command: "./make_templates.sh"
Working dir: "%{sourceDir}"
Command arguments: "-i %{sourceDir} -o %{buildDir}"
Don’t forget to add “execution” to the file (chmod +x make_template.sh)
If the build went well, libcpp_defskin.so library will appear at the building directory.
The library can be built either statically or dynamically. I made it dynamically and wouldn’t recommend you to do statically as TMPL files should be often changed. Recompiling the project because of that is quite a thankless task.
config.json file should be altered in order to tie templates to the project
{
"WebSite" : {
"root" : "",
"host" : "localhost:8080",
"locdomain" : "localhost",
},
"service" : {
"ip" : "0.0.0.0",
"api" : "http",
"port" : 8080
},
"http" : {
"script" : "/mb.fcgi" ,
"rewrite" : [
{ "regex" : "/media(/.+)", "pattern" : "$1" },
{ "regex" : ".*" , "pattern" : "/mb.fcgi$0" }
],
},
"views" : {
"default_skin" : "defskin" ,
"paths" : [ "./" ],
"skins" : [ "cpp_defskin" ],
},
}
Appropriate changes should be made in main.cpp:
#include "data/tmpl_master.h"
...
WebSite::main(std::string path)
{
Data::Master tmpl;
tmpl.page.title = path;
tmpl.page.description = "description";
tmpl.page.keywords = "keywords";
tmpl.page.menuList.insert(std::pair("/","MAIN"));
tmpl.page.menuList.insert(std::pair("/else","ELSE"));
render("Master",tmpl);
}
We should see the template output when we start the project. Oops, I forgot to say about css and images.
Add one more point to config.json
"file_server" : {
"enable" : true,
"listing" : true,
"document_root" : "./media"
},
I should explain that by the given point we allow the binary file to see the file system.
The rules according to which it performs it are described in http{ «regex»: "/media(/.+)", «pattern»: "$1" } section.
So any request beginning with /media/ should be addressed to the “file server”.
Let's create a «media» folder in our project and also add an appropriate change to make_templates.sh:
# copy the media data to the build folder
cp -R $INPUT/media $OUTPUT
Inside the «media» folder (in the project's source codes directory) create css subfolder and style.css file in it.
/* Eric Meyer's CSS Reset */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* End of Eric Meyer's CSS Reset */
html {
height: 100%;
}
article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary {
display: block;
}
body {
font: 12px/18px Arial, sans-serif;
width: 100%;
height: 100%;
}
.wrapper {
width: 800px;
margin: 0 auto;
min-height: 100%;
height: auto !important;
height: 100%;
}
/* Header
-----------------------------------------------------------------------------*/
.header {
height: 50px;
background: #FFE680;
}
/* Middle
-----------------------------------------------------------------------------*/
.middle {
width: 100%;
padding: 0 0 50px;
position: relative;
}
.middle:after {
display: table;
clear: both;
content: '';
}
.container {
width: 100%;
float: left;
overflow: hidden;
}
.content {
padding: 0 270px 0 270px;
}
/* Left Sidebar
-----------------------------------------------------------------------------*/
.left-sidebar {
float: left;
width: 250px;
margin-left: -100%;
position: relative;
background: #B5E3FF;
}
/* Footer
-----------------------------------------------------------------------------*/
.footer {
width: 800px;
margin: -50px auto 0;
height: 50px;
background: #BFF08E;
position: relative;
}
Let’s try to build it one more time.
Now, when the website looks like a first work of a beginner – we can proceed to the most important part.
Template Inheritance
Template inheritance mechanism is quite simple.
Define from which template we inherit and add redefining of the content output function.
Create tmpl_news.h file in data folder.
#ifndef TMPL_NEWS_H
#define TMPL_NEWS_H
#include "tmpl_master.h"
namespace Data {
//-------------------------------------------------------------------------------------
// Dsc: News content
//-------------------------------------------------------------------------------------
struct News :public Master{
//-------------------------------------------------------------------------------------
// Dsc: Main News
//-------------------------------------------------------------------------------------
std::string mainNews;
//-------------------------------------------------------------------------------------
// Dsc: Page constructor
//-------------------------------------------------------------------------------------
News() :
Master()
{}
//-------------------------------------------------------------------------------------
// Dsc: Lazy destructor
//-------------------------------------------------------------------------------------
~News(){}
};
}
#endif // TMPL_NEWS_H
Also add news.tmpl file to templates folder.
<% c++ #include "data/tmpl_news.h" %>
<% skin defskin %>
<% view News uses Data::News extends Master %>
<% template page_main() %><%= mainNews %><% end %>
<% end view %>
<% end skin %>
Add a path to the file in the build script:
TEMPLATES="$INPUT/templates/master.tmpl"
TEMPLATES="$TEMPLATES $INPUT/templates/news.tmpl"
Change main.cpp file
#include
#include
#include
#include
#include
#include
#include "data/tmpl_master.h"
#include "data/tmpl_news.h"
//-------------------------------------------------------------------------------------
// Dsc: Our class for page rendering, when some address is requested by the user
// First of all he will get here
//-------------------------------------------------------------------------------------
class WebSite : public cppcms::application{
public:
//-------------------------------------------------------------------------------------
// Dsc: Constructor. Entry point.
//-------------------------------------------------------------------------------------
WebSite(cppcms::service &s) : cppcms::application(s)
{
dispatcher().assign("/news(.*)",&WebSite::news,this,1);
mapper().assign("news","/news");
dispatcher().assign("(/?)",&WebSite::master,this,1);
mapper().assign("master","/");
}
//-------------------------------------------------------------------------------------
// Dsc: The function we’ll get into if it’s not indicated otherwise in the constructor.
// (more about it later)
//-------------------------------------------------------------------------------------
virtual void main(std::string path)
{
cppcms::application::main(path);
}
//-------------------------------------------------------------------------------------
// Dsc: Basic content rendering
//-------------------------------------------------------------------------------------
virtual void master(std::string path)
{
Data::Master tmpl;
tmpl.page.title = path;
tmpl.page.description = "description";
tmpl.page.keywords = "keywords";
tmpl.page.menuList.insert(std::pair("/","MASTER"));
tmpl.page.menuList.insert(std::pair("/news","NEWS"));
render("Master",tmpl);
}
//-------------------------------------------------------------------------------------
// Dsc: News rendering
//-------------------------------------------------------------------------------------
virtual void news(std::string path)
{
Data::News tmpl;
tmpl.page.title = path;
tmpl.page.description = "description";
tmpl.page.keywords = "keywords";
tmpl.page.menuList.insert(std::pair("/","MASTER"));
tmpl.page.menuList.insert(std::pair("/news","NEWS"));
tmpl.mainNews = "Sensation! Nothing happened at our website!";
render("News",tmpl);
}
};
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
int main(int argc,char **argv)
{
try {
// create the service
cppcms::service srv(argc,argv);
// define the root
srv.applications_pool().mount(cppcms::applications_factory());
// start
srv.run();
}
catch(std::exception const &e) {
std::cerr << "Failed: " << e.what() << std::endl;
std::cerr << booster::trace(e) << std::endl;
return 1;
}
return 0;
}
}
Major changes of the file occurred in constructor, where we had indicated which function is in charge of which page.
Now these pages can display different templates.
A crucial pint is the order of files list delivery to the template engine (children files should go after parents, otherwise errors will occur.
That’s where I am going to end the first part. Comments are appreciated.
13 comments
habrahabr.ru/post/219899/
Where all can read — what we write!
BTW Habrahabr is russian version of Slashdot. Do you really want to compete with Slashdot? It will not be easy.
Upload image