Пользователь
0,0
рейтинг
7 мая 2014 в 11:56

Разработка → Сайт на с++ (CppCMS). Часть 1 из песочницы tutorial

HTML*, C++*
Здравствуй уважаемый %username%.
Сегодня я хотел бы поделиться с тобой личным опытом в создании Web сайта на CppCMS (библиотека-шаблонизатор на с++). Можно назвать это «помощью начинающему программисту на CppCMS».

Зачем писать сайт на с++


Доводы за и против такого решения могут быть весьма разнообразны и, что бы не спровоцировать войну «языковых школ», я проведу аналогию с автомобилями: «Я купил этот. Нравится. Езжу. Продавать не хочу!».
Из дополнительных аргументов будет то, что данный язык является профильным для моего рабочего места.

Давайте уже что-нибудь напишем


Однако прежде

Перед написанием сайта — придется сначала оное (CppCMS) поставить на рабочую машину. Библиотека самым наглым образом требует для своей работы Boost c++, pcre, crypt, python, icu и, несмотря на кросплатформенность, гораздо приятнее ставится под *nix системами.
Само же построение сводится к банальным:

mkdir build  
cd build 
cmake ..  
make
make install

Проблем возникнуть не должно, все строится в автоматическом режиме, и ни разу еще меня не огорчало.

Забегая вперед хотелось бы сказать что для комфортной работы желательно что бы среда разработки умела выполнять пользовательские шаги построения, а так же имела удобноредактируемый «синтаксический анализатор», мною используется QtCreator.
Все дальнейшие шаги я буду описывать применительно к вышеобозначенной среде, так как построение с использованием командной строки хорошо рассмотрено на сайте самой библиотеки. Так же хочу отметить что некоторые действия сборки будут автоматизированы bash скриптами( хотя было бы достаточно прописания «пользовательского шага» на этапе сборки )

Перед началом работы желательно добавить подсветку синтаксиса для QtCreator, что бы он распознавал особые файлы *.tmpl, которые используются в качестве шаблонов. Данный файл tmpl.xml ( слегка модифицированная подсветка HTML ) должен лежать в папке конфигов «qtcreator/generic-highlighter/tmpl.xml»:

Полный текст файла
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE language SYSTEM "language.dtd"
[
	<!ENTITY name    "[A-Za-z_:][\w.:_-]*">
	<!ENTITY entref  "&(#[0-9]+|#[xX][0-9A-Fa-f]+|&name;);">
]>
<language name="TMPL" version="1" kateversion="2.4" section="Markup" extensions="*.tmpl" mimetype="text/tmpl"  author="Wilbert Berendsen ( original HTML author)(wilbert@kde.nl)" license="LGPL" priority="10">

<highlighting>
<contexts>
  <context name="Start" attribute="Normal Text" lineEndContext="#stay">
    <IncludeRules context="FindHTML" />
  </context>

  <context name="FindHTML" attribute="Normal Text" lineEndContext="#stay">
    <DetectSpaces/>
    <DetectIdentifier/>
    <StringDetect attribute="Comment" context="Comment" String="<!--" beginRegion="comment" />
    <StringDetect attribute="Commenttmpl" context="Commenttmpl" String="<%" beginRegion="commenttmpl" />
    <StringDetect attribute="CDATA" context="CDATA" String="<![CDATA[" beginRegion="cdata" />
    <RegExpr attribute="Doctype" context="Doctype" String="<!DOCTYPE\s+" beginRegion="doctype" />
    <RegExpr attribute="Processing Instruction" context="PI" String="<\?[\w:-]*" beginRegion="pi" />
    <RegExpr attribute="Element" context="CSS" String="<style\b" insensitive="TRUE" beginRegion="style" />
    <RegExpr attribute="Element" context="JS" String="<script\b" insensitive="TRUE" beginRegion="script" />
    <RegExpr attribute="Element" context="El Open" String="<pre\b" insensitive="TRUE" beginRegion="pre" />
    <RegExpr attribute="Element" context="El Open" String="<div\b" insensitive="TRUE" beginRegion="div" />
    <RegExpr attribute="Element" context="El Open" String="<table\b" insensitive="TRUE" beginRegion="table" />
    <RegExpr attribute="Element" context="El Open" String="<ul\b" insensitive="TRUE" beginRegion="ul" />
    <RegExpr attribute="Element" context="El Open" String="<ol\b" insensitive="TRUE" beginRegion="ol" />
    <RegExpr attribute="Element" context="El Open" String="<dl\b" insensitive="TRUE" beginRegion="dl" />
    <RegExpr attribute="Element" context="El Open" String="<&name;" />
    <RegExpr attribute="Element" context="El Close" String="</pre\b" insensitive="TRUE" endRegion="pre" />
    <RegExpr attribute="Element" context="El Close" String="</div\b" insensitive="TRUE" endRegion="div" />
    <RegExpr attribute="Element" context="El Close" String="</table\b" insensitive="TRUE" endRegion="table" />
    <RegExpr attribute="Element" context="El Close" String="</ul\b" insensitive="TRUE" endRegion="ul" />
    <RegExpr attribute="Element" context="El Close" String="</ol\b" insensitive="TRUE" endRegion="ol" />
    <RegExpr attribute="Element" context="El Close" String="</dl\b" insensitive="TRUE" endRegion="dl" />
    <RegExpr attribute="Element" context="El Close" String="</&name;" />
    <!-- as long as kde gives DTDs the text/html mimetype--><IncludeRules context="FindDTDRules" />
    <IncludeRules context="FindEntityRefs" />
  </context>

  <context name="FindEntityRefs" attribute="Other Text" lineEndContext="#stay">
    <StringDetect attribute="Commenttmpl" context="Commenttmpl" String="<%" beginRegion="commenttmpl" />
    <RegExpr attribute="EntityRef" context="#stay" String="&entref;" />
    <AnyChar attribute="Error" context="#stay" String="&<" />
  </context>

  <context name="FindPEntityRefs" attribute="Other Text" lineEndContext="#stay">
    <RegExpr attribute="EntityRef" context="#stay" String="&entref;" />
    <RegExpr attribute="PEntityRef" context="#stay" String="%&name;;" />
    <AnyChar attribute="Error" context="#stay" String="&%" />
  </context>

  <context name="FindAttributes" attribute="Other Text" lineEndContext="#stay">
    <RegExpr attribute="Attribute" context="#stay" String="&name;" column="0"/>
    <RegExpr attribute="Attribute" context="#stay" String="\s+&name;" />
    <DetectChar attribute="Attribute" context="Value" char="=" />
  </context>

  <context name="FindDTDRules" attribute="Other Text" lineEndContext="#stay">
    <RegExpr attribute="Doctype" context="Doctype Markupdecl" String="<!(ELEMENT|ENTITY|ATTLIST|NOTATION)\b" />
  </context>


  <context name="Comment" attribute="Comment" lineEndContext="#stay">
    <DetectSpaces/>
    <IncludeRules context="##Alerts" />
    <DetectIdentifier/>
    <StringDetect attribute="Comment" context="#pop" String="-->" endRegion="comment" />
    <RegExpr attribute="Error" context="#stay" String="-(-(?!->))+" />
  </context>
  
  <context name="Commenttmpl" attribute="Commenttmpl" lineEndContext="#stay">
    <DetectSpaces/>
    <DetectIdentifier/>
    <StringDetect attribute="Commenttmpl" context="#pop" String="%>" endRegion="commenttmpl" />
  </context>

  <context name="CDATA" attribute="Other Text" lineEndContext="#stay">
    <DetectSpaces/>
    <DetectIdentifier/>
    <StringDetect attribute="CDATA" context="#pop" String="]]>" endRegion="cdata" />
    <StringDetect attribute="EntityRef" context="#stay" String="]]&gt;" />
  </context>

  <context name="PI" attribute="Other Text" lineEndContext="#stay">
    <Detect2Chars attribute="Processing Instruction" context="#pop" char="?" char1=">" endRegion="pi" />
  </context>

  <context name="Doctype" attribute="Other Text" lineEndContext="#stay">
    <DetectChar attribute="Doctype" context="#pop" char=">" endRegion="doctype" />
    <DetectChar attribute="Doctype" context="Doctype Internal Subset" char="[" beginRegion="int_subset" />
  </context>

  <context name="Doctype Internal Subset" attribute="Other Text" lineEndContext="#stay">
    <DetectChar attribute="Doctype" context="#pop" char="]" endRegion="int_subset" />
    <IncludeRules context="FindDTDRules" />
    <StringDetect attribute="Comment" context="Comment" String="<!--" beginRegion="comment" />
    <RegExpr attribute="Processing Instruction" context="PI" String="<\?[\w:-]*" beginRegion="pi" />
    <IncludeRules context="FindPEntityRefs" />
  </context>

  <context name="Doctype Markupdecl" attribute="Other Text" lineEndContext="#stay">
    <DetectChar attribute="Doctype" context="#pop" char=">" />
    <DetectChar attribute="Value" context="Doctype Markupdecl DQ" char=""" />
    <DetectChar attribute="Value" context="Doctype Markupdecl SQ" char="'" />
  </context>

  <context name="Doctype Markupdecl DQ" attribute="Value" lineEndContext="#stay">
    <DetectChar attribute="Value" context="#pop" char=""" />
    <IncludeRules context="FindPEntityRefs" />
  </context>

  <context name="Doctype Markupdecl SQ" attribute="Value" lineEndContext="#stay">
    <DetectChar attribute="Value" context="#pop" char="'" />
    <IncludeRules context="FindPEntityRefs" />
  </context>

  <context name="El Open" attribute="Other Text" lineEndContext="#stay">
    <Detect2Chars attribute="Element" context="#pop" char="/" char1=">" />
    <DetectChar attribute="Element" context="#pop" char=">" />
    <IncludeRules context="FindAttributes" />
    <RegExpr attribute="Error" context="#stay" String="\S" />
  </context>

  <context name="El Close" attribute="Other Text" lineEndContext="#stay">
    <DetectChar attribute="Element" context="#pop" char=">" />
    <RegExpr attribute="Error" context="#stay" String="\S" />
  </context>

  <context name="El Close 2" attribute="Other Text" lineEndContext="#stay">
    <DetectChar attribute="Element" context="#pop#pop#pop" char=">" />
    <RegExpr attribute="Error" context="#stay" String="\S" />
  </context>

  <context name="El Close 3" attribute="Other Text" lineEndContext="#stay">
    <DetectChar attribute="Element" context="#pop#pop#pop#pop" char=">" />
    <RegExpr attribute="Error" context="#stay" String="\S" />
  </context>

  <context name="CSS" attribute="Other Text" lineEndContext="#stay">
    <Detect2Chars attribute="Element" context="#pop" char="/" char1=">" endRegion="style" />
    <DetectChar attribute="Element" context="CSS content" char=">" />
    <IncludeRules context="FindAttributes" />
    <RegExpr attribute="Error" context="#stay" String="\S" />
  </context>

  <context name="CSS content" attribute="Other Text" lineEndContext="#stay">
    <RegExpr attribute="Element" context="El Close 2" String="</style\b" insensitive="TRUE" endRegion="style" />
    <IncludeRules context="##CSS" includeAttrib="true"/>
  </context>

  <context name="JS" attribute="Other Text" lineEndContext="#stay">
    <Detect2Chars attribute="Element" context="#pop" char="/" char1=">" endRegion="script" />
    <DetectChar attribute="Element" context="JS content" char=">" />
    <IncludeRules context="FindAttributes" />
    <RegExpr attribute="Error" context="#stay" String="\S" />
  </context>

  <context name="JS content" attribute="Other Text" lineEndContext="#stay">
    <RegExpr attribute="Element" context="El Close 2" String="</script\b" insensitive="TRUE" endRegion="script" />
    <RegExpr attribute="Comment" context="JS comment close" String="//(?=.*</script\b)" insensitive="TRUE" />
    <IncludeRules context="##JavaScript" includeAttrib="true"/>
  </context>

  <context name="JS comment close" attribute="Comment" lineEndContext="#pop">
    <RegExpr attribute="Element" context="El Close 3" String="</script\b" insensitive="TRUE" endRegion="script" />
    <IncludeRules context="##Alerts" />
  </context>

  <context name="Value" attribute="Other Text" lineEndContext="#stay" fallthrough="true" fallthroughContext="Value NQ">
    <DetectChar attribute="Value" context="Value DQ" char=""" />
    <DetectChar attribute="Value" context="Value SQ" char="'" />
    <DetectSpaces />
  </context>

  <context name="Value NQ" attribute="Other Text" lineEndContext="#pop#pop" fallthrough="true" fallthroughContext="#pop#pop">
    <IncludeRules context="FindEntityRefs" />
    <RegExpr attribute="Value" context="#stay" String="/(?!>)" />
    <RegExpr attribute="Value" context="#stay" String="[^/><"'\s]" />
  </context>

  <context name="Value DQ" attribute="Value" lineEndContext="#stay">
    <DetectChar attribute="Value" context="#pop#pop" char=""" />
    <IncludeRules context="FindEntityRefs" />
  </context>

  <context name="Value SQ" attribute="Value" lineEndContext="#stay">
    <DetectChar attribute="Value" context="#pop#pop" char="'" />
    <IncludeRules context="FindEntityRefs" />
  </context>

</contexts>
<itemDatas>
  <itemData name="Normal Text" defStyleNum="dsNormal" />
  <itemData name="Other Text" defStyleNum="dsNormal" spellChecking="false" />
  <itemData name="Comment" defStyleNum="dsComment" />
  <itemData name="Commenttmpl" defStyleNum="dsComment" color="#66f" />
  <itemData name="CDATA" defStyleNum="dsBaseN" bold="1" spellChecking="false" />
  <itemData name="Processing Instruction" defStyleNum="dsKeyword" spellChecking="false" />
  <itemData name="Doctype" defStyleNum="dsDataType" bold="1" spellChecking="false" />
  <itemData name="Element" defStyleNum="dsKeyword" spellChecking="false" />
  <itemData name="Attribute" defStyleNum="dsOthers" spellChecking="false" />
  <itemData name="Value" defStyleNum="dsString" color="#a00" spellChecking="false" />
  <itemData name="EntityRef" defStyleNum="dsDecVal" spellChecking="false" />
  <itemData name="PEntityRef" defStyleNum="dsDecVal" spellChecking="false" />
  <itemData name="Error" defStyleNum="dsError" spellChecking="false" />
</itemDatas>

</highlighting>
<general>
  <comments>
    <comment name="multiLine" start="<!--" end="-->" />
  </comments>
</general>
</language>



Теперь приступим

В зависимости построения следует добавить:

LIBS += -L/usr/local/lib/ -lbooster -lcppcms
INCLUDEPATH += /usr/local/include
DEPENDPATH += /usr/local/include


Создаем файл main.cpp и наполняем его следующим содержимым:

#include <cppcms/applications_pool.h>
#include <cppcms/url_dispatcher.h>
#include <cppcms/http_response.h>
#include <cppcms/application.h>
#include <cppcms/url_mapper.h>
#include <cppcms/service.h>

//-------------------------------------------------------------------------------------
// Dsc: Наш класс отрисовки страниц, при запросе некоторого адреса пользователем
//      В первую очередь он попадет сюда
//-------------------------------------------------------------------------------------
class WebSite : public cppcms::application{
public:
  //-------------------------------------------------------------------------------------
  // Dsc: Конструктор, который будет запушен во время старта программы
  //-------------------------------------------------------------------------------------
  WebSite(cppcms::service &s) : cppcms::application(s)
  {}
  //-------------------------------------------------------------------------------------
  // Dsc: Функция в которую мы попадем, если иного не указано в конструкторе
  //      ( об этом позже )
  //-------------------------------------------------------------------------------------
  virtual void main(std::string path)
  {
    response().out() << "Hello!";
  }
};

//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
int main(int argc,char **argv)
{
 try {
    // создаем сервис
    cppcms::service srv(argc,argv);
    // задаем корень
    srv.applications_pool().mount(cppcms::applications_factory<WebSite>());
    // запускаем
    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;
}


Если вы уже попытались это запустить, то скорее всего ничего не получилось, так как для полноценной работы не хватает конфигурационного файла для сервера, его местоположение нужно передать бинарнику при запуске
WebApp.bin -c config.json
Конфиг может быть следующего содержания:
{	
   "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" }
      ],
   }
}

Этого должно быть достаточно.
Так же хорошим вариантом будет прописать параметра запуска в «конфиги среды программирования»
Итак, дописываем и запускаем и открываем.

Впечатляет? Нет?
Оно и верно, так как для данного примера мы не использовали механизм шаблонов, а ограничились банальным выводом строки. Однако пример позволяет нам убедиться что все работает.

Использование шаблонов


Давайте напишем первый шаблон, который будет «превращен» шаблонизатором библиотеки в файл *.cpp.
Итак, первым делом добавим заголовочный файл, содержащий структуру динамических данных ( данных шаблона ). По умолчанию будут помещаются в папку data внутри проекта.
data/tmpl_master.h
#ifndef TMPL_MASTER_H
#define TMPL_MASTER_H
#include <cppcms/view.h>

namespace Data {
  //-------------------------------------------------------------------------------------
  // Dsc: Структура основной информации о странице
  //-------------------------------------------------------------------------------------
  struct infoPage {
    std::string    title;                         // титул страницы
    std::string    description;                   // описание страницы
    std::string    keywords;                      // ключевые слова страницы
    std::map<std::string,std::string> menuList;   // список выводимых пунктов меню (url,desc)
    //-------------------------------------------------------------------------------------
    // Dsc: Конструктор, инициализирующий переменные
    //-------------------------------------------------------------------------------------
    infoPage() :
      title      (""),
      description(""),
      keywords   (""),
      menuList   (  )
    {}
    //-------------------------------------------------------------------------------------
    // Dsc: Деструктор, ничего не делающий
    //-------------------------------------------------------------------------------------
    ~infoPage(){}
  };
  //-------------------------------------------------------------------------------------
  // Dsc: Базовый контент который есть на каждой странице
  //-------------------------------------------------------------------------------------
  struct Master :public cppcms::base_content {
    infoPage    page;
    //-------------------------------------------------------------------------------------
    // Dsc: Конструктор страницы
    //-------------------------------------------------------------------------------------
    Master() :
      page()
    {}
    //-------------------------------------------------------------------------------------
    // Dsc: Ленивый деструктор
    //-------------------------------------------------------------------------------------
    ~Master(){}
  };
}
#endif


Содержимое данного файла как правило не отличается каким-либо умным кодом и по сути это всего лишь контейнеры для описания переменных, используемых в шаблонах.

Приступим к написанию собственно шаблона, создадим папку templates, в ней файл master.tmpl, следующего содержания:
<% 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() %>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8" />
  <title><%= page.title %></title>
  <meta name="keywords" content="<%= page.keywords %>" />
  <meta name="description" content="<%= page.description %>" />
  <link href="/media/css/style.css" rel="stylesheet">
</head>
<body>
<div class="wrapper">
  <header class="header">
     <div class="nav" >
     <% foreach menuItem in page.menuList %>
        <ul>
           <% item %>
           <li><a href="<%= menuItem.first %>"><%= menuItem.second %></a></li>
           <% end %>
        </ul>
     <% end %>
     </div>
  </header>
  <div class="middle">
    <div class="container">
      <main class="content"><% include page_main() %></main>
    </div>
    <aside class="left-sidebar">
       <div>
           <% include page_left_sidebar() %>
       </div>
    </aside>
  </div>
</div>
<footer class="footer"><% include page_footer() %></footer>
</body>
</html>
<% end template %>
<% end view %>
<% end skin %>

Что же тут понаписано?
В самой первой строчке <% c++ #include "data/tmpl_master.h" %> мы пишем заголовочный файл, в котором будут объявлены наши структуры данных.
Строка <% skin defskin %> определяет название текущего скина, то есть у вас могут быть разные отображения для страниц сайта.
Строка <% view Master uses Data::Master %> определяет название текущего шаблона как «Master» ( В последствии мы будем его указывать для механизма наполнения страниц ), а так же создает структуру Data::Master внутри класса-обертки. Что в переводе на с++ будет выглядеть как «Data::Master context;»( если вас интересуют подробности — то всегда можно посмотреть сгенерированный фал )
Строки <% template page_main() %>MAIN TEMPLATE<% end %>
<% template page_footer() %>Все права защищены<% end %>
<% template page_left_sidebar() %>Левая панелька<% end %>

определяют дефолтные значения, которые будут выведены пользователю в случае если мы их не переопределим( то есть являются
virtual const char* page_main(){ return "MAIN TEMPLATE"; }
, так наверное понятнее. ).
Давайте попробуем собрать. Ясное дело что компилятор с++ не проглотит файл tmpl. Поэтому на помощь должна прийти утилита, собравшаяся вместе с библиотекой, которая переработает шаблон до нужного состояния.
Для этого создадим внутри проекта файл «make_templates.sh», внутрь которого поместим нужные нам операции( Данный файл легко можно заменить или ручным вызовом данной утилиты или прописанием ее в «исполняемую часть» вашей среды ):

#!/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

# копируем файлик конфигурации в папку билда
cp $INPUT/config.json $OUTPUT

# сюда пишем все шаблоны
TEMPLATES="$INPUT/templates/master.tmpl"

# прожевываем шаблоны в срр-шник
cppcms_tmpl_cc $TEMPLATES -o $INPUT/all_tmpl.cpp

# собираем шаблоны в библиотеку
g++ -shared -fPIC $INPUT/all_tmpl.cpp -o $OUTPUT/libcpp_defskin.so -lcppcms -lbooster


Теперь в настройках проекта QtCreator необходимо добавить «пользовательский шаг»
Команда: "./make_templates.sh"
Рабочая директория: "%{sourceDir}"
Аргументы команды: "-i %{sourceDir} -o %{buildDir}"
Не забудьте добавить файлу «исполняемость».

Если сборка прошла удачно, то в директории построения появится кроме прочего библиотека libcpp_defskin.so.
Важно отметить что собрать библиотеку можно статически или динамически. У меня сделано вторым способом, крайне вам не советую использовать первый, так как файлы TMPL приходится менять частенько, а перекомпилировать из-за этого весь проект — весьма неблагодарное занятие.

Так же что бы шаблоны были привязаны к проекту — необходимо дополнить файл config.json
{	
   "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" ],
   },
}


И внести соответствующие изменения в 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<std::string,std::string>("/","MAIN"));
    tmpl.page.menuList.insert(std::pair<std::string,std::string>("/else","ELSE"));
    render("Master",tmpl);
}


Теперь при запуске проекта мы должны увидеть вывод шаблона. Но постойте, совсем забыл про css и изображения, сейчас исправлюсь.
Добавляем в config.json еще один пункт
   "file_server" : {
      "enable" : true,
      "listing" : true,
      "document_root" : "./media"
   },

Здесь определенно нужно пояснить, что данным пунктом мы разрешаем бинарнику смотреть в файловую систему. А правила по которым он это делает описаны в секции http{ "regex" : "/media(/.+)", "pattern" : "$1" },
то есть любой запрос, начинающийся с /media/ следует перенаправлять «файловому серверу».
Создадим в папке проекта папку media, а так же добавим соответствующий пункт в make_templates.sh:
# копируем медиа данные в папку билда
cp -R $INPUT/media $OUTPUT

Внутри папки media ( в директории исходников проекта ) создадим подпапку css, а в ней файл style.css
Скрытый текст
/* 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;
}


Пробуем собрать еще раз.
Теперь, когда сайт похож на первую работу начинающего мастера — можно перейти к самому главному.

Наследование шаблонов


Механизм наследования шаблонов предельно прост. Определяем от какого шаблона наследуемся и дописываем переопределение функции вывода контента.
Создадим файл файл tmpl_news.h в папке data
#ifndef TMPL_NEWS_H
#define TMPL_NEWS_H
#include "tmpl_master.h"

namespace Data {
  //-------------------------------------------------------------------------------------
  // Dsc: Новостной контент
  //-------------------------------------------------------------------------------------
  struct News :public Master{
    //-------------------------------------------------------------------------------------
    // Dsc: Главная новость
    //-------------------------------------------------------------------------------------
    std::string mainNews;
    //-------------------------------------------------------------------------------------
    // Dsc: Конструктор страницы
    //-------------------------------------------------------------------------------------
    News() :
      Master()
    {}
    //-------------------------------------------------------------------------------------
    // Dsc: Ленивый деструктор
    //-------------------------------------------------------------------------------------
    ~News(){}
  };
}


#endif // TMPL_NEWS_H


Так же добавим файл news.tmpl в папку templates
<% c++ #include "data/tmpl_news.h" %>
<% skin defskin %>
<% view News uses Data::News extends Master %>
<% template page_main() %><%= mainNews %><% end %>
<% end view %>
<% end skin %>

Добавим путь до файлика в скрипт сборки ( должно выглядеть как-то так):

TEMPLATES="$INPUT/templates/master.tmpl"
TEMPLATES="$TEMPLATES $INPUT/templates/news.tmpl"


Изменяем файл main.cpp
#include <cppcms/applications_pool.h>
#include <cppcms/url_dispatcher.h>
#include <cppcms/http_response.h>
#include <cppcms/application.h>
#include <cppcms/url_mapper.h>
#include <cppcms/service.h>

#include "data/tmpl_master.h"
#include "data/tmpl_news.h"


//-------------------------------------------------------------------------------------
// Dsc: Наш класс отрисовки страниц, при запросе некоторого адреса пользователем
//      В первую очередь он попадет сюда
//-------------------------------------------------------------------------------------
class WebSite : public cppcms::application{
public:
  //-------------------------------------------------------------------------------------
  // Dsc: Конструктор, который будет запушен во время старта программы
  //-------------------------------------------------------------------------------------
  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: Функция в которую мы попадем, если иного не указано в конструкторе
  //      ( об этом позже )
  //-------------------------------------------------------------------------------------
  virtual void main(std::string path)
  {
    cppcms::application::main(path);
  }
  //-------------------------------------------------------------------------------------
  // Dsc: Рендеринг базового контента
  //-------------------------------------------------------------------------------------
  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<std::string,std::string>("/","MASTER"));
    tmpl.page.menuList.insert(std::pair<std::string,std::string>("/news","NEWS"));
    render("Master",tmpl);
  }
  //-------------------------------------------------------------------------------------
  // Dsc: Рендеринг новостей
  //-------------------------------------------------------------------------------------
  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<std::string,std::string>("/","MASTER"));
    tmpl.page.menuList.insert(std::pair<std::string,std::string>("/news","NEWS"));
    tmpl.mainNews = "Сенсация! У нас на сайте ничего не произошло!";
    render("News",tmpl);
  }
};

//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
int main(int argc,char **argv)
{
  try {
    // создаем сервис
    cppcms::service srv(argc,argv);
    // задаем корень
    srv.applications_pool().mount(cppcms::applications_factory<WebSite>());
    // запускаем
    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;
}

}

Основные изменения в файле произошли в конструкторе, где мы указали какая функция за вывод какой страницы отвечает.
Теперь эти страницы выводят различные шаблоны.
Особо важный момент — порядок подачи списка файлов шаблонизатору ( файлы-потомки должны следовать после родителей, иначе будут сыпаться ошибки ).

На этом я планирую закончить первую часть.
@Drus_K
карма
20,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (36)

  • +35
    Месье знает толк…
    • +2
      В конкретно взятом случае возможно это и извращение но вот подход JS+REST должен и в С++ работать на ура.
  • +3
    Как показывают замеры в разы быстрее чем у языков с сборщиком мусора (даже динамических) не получится.
    Так в чем смысл, кроме доказать что вообще возможно?
    • +3
      Не всегда людям, пишущим на с++ что-то серверное хочется лезть в другие ЯП для реализации конфигурации и мониторинга через веб.
      З.Ы, я в бенчмарке конкретно эту библиотеку не увидел.
      • +3
        ну да, я согласен что если проффесиональный программист на брейнфаке Аде захочет написать домашний сайтик, то зачем тратить 2 недели на вибор оптимального интрумента и его изучения, лучше писатьна том что знаеш, потом еще статью на хабре напишешь…
        одни профити вообщем ;)
        • +6
          А я прямо вижу, как в компании d-link, например, сидит профессиональный программист PHP и ставит на новую железку апач+пхп+зенд-фреймворк, что бы написать на своем любимом языке новую серверную прошивку для управления/конфигурации устройства по http.
          • 0
            Кажется, даже на всеми любимом dir-300 веб-панель была на PHP :) Но я могу ошибаться.
    • +2
      А затраты ресурсов?
      • +4
        сейчас мне снова минусов накидают…
        я не считаю этот инструмент сколь бы то не было сложным.
        тем кто знаком с бустом — вообще дом родной
        • +1
          Я про компьютерные ресурсы. И потом — на порядки там прироста производительности, может, и нет, но 2-3 раза — будет точно.
          Конечно, на C++ писать сложнее. Но долше ли.
          • 0
            Да и ресурсы не факт… Все оно может выглядеть очень красиво и быстро. Пока не придет, то самое внезапно. И сведет на нет все преимущества из-за фрагментации памяти. Впрочем это наверное решаемо и возможно этой проблемой фреймворк не страдает. А если и страдает, то нагрузка должна быть огромной. В любом случае скорее всего База данных отавлится)).
  • +11
    Когда у вас есть только молоток, всё становится похоже на гвоздь.

    Для каждой конкретной задачи следует выбирать наиболее подходящий инструмент, не стесняться отойти от привычного C++.

    Единственный потенциальный плюс, который я вижу — это производительность, но не уверен, что она окупит усложнение разработки и поддержки. В следующей статье, если будете писать, хотелось бы увидеть сравнение производительности с популярными CMS, желательно в том числе с учетом KPHP/HHVM.
  • +9
    Программистов на C++, занимающихся разработкой сайтов или веб-интерфейсов, может также заинтересовать библиотека Wt, о которой есть статья на Хабре. Среди преимуществ над всеми библиотеками можно выделить 1) возможность писать код на чистом C++, без кодогенерации и вкраплений HTML, JS, CSS, SQL и т.д.; 2) единый код обслуживает HTML-only и JavaScript-версии сайта. А ещё кое-чем Wt похож на Qt.
  • +28
    Забавно назвать продукт CppCMS, а потом на самом видном месте писать:
    What is CppCMS? CppCMS is a Free High Performance Web Development Framework (not a CMS) aimed at Rapid Web Application Development.
    • 0
      Я думаю что авторы таким названием заложили перспективы роста. Кроме того CppFramework точно воспринималось бы неоднозначно т.к. никаких ассоциаций с веб'ом не вы вызает. Чего не скажешь о названии CppCMS.
  • 0
    Зачем в infoPage и Master Вы объявляете что-то помимо полей? Все остальные объявления и избыточны.
    В чем смысл передачи string по значению?
    • 0
      Нет времени объяснять, надо писать сайт на C++! =)
  • –6
    image
  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Традиционно собирающая минусы картинка, но, имхо, тут вполне применима.
      • +1
        Я пишу этот ответ не конкретно вам, просто «взгляд на вас клином сошелся».
        Задача в которой это решение применимо — сервер сбора и мониторинга информации. Уже написанный, работающий, ведущий логи и пишущий инфу в бд, но не имеющий какого-либо инструмента для просмотра его текущего внутреннего состояния и удаленного конфигурирования. Что делать? А давайте ка напишем для него гуёвое приложение, разработаем протокол взаимодействия, поборемся с неожиданностями, будем следить что бы у всех заинтересованных была «свежая версия по». Хотя нет, давайте поднимем apache + php…
        Есть же вариант лучше! Пустим в отдельном витке веб морду на cppcms и проблемы решены.
        Ситуация со стаканом пуст/полон… каждому свое
        • 0
          Никто не спорит. Я же и написал — «имхо». Для меня вполне естественно использовать те технологии, которые больше всего подходят именно в этом месте.
          На Perl, к примеру, можно написать 3D движок (используя биндинги), но зачем? Другое дело, если человек ничего не знает, кроме этого ЯП, тогда да — можно. Но это потраченное время.
  • НЛО прилетело и опубликовало эту надпись здесь
    • +3
      эм, библиотека сама есть веб-вервер
      или имеется в виду настройка железа?
      тогда
      wget cmake ... make make install
  • 0
    Код, шаблоны, XML-ки, конфиги и много-много букоф — все было очень здорово, спасибо. Но как это чудо работает и выглядит вживую в виде работающего сайта — намного интереснее. Как насчет посмотреть/пощупать? Если скриншотов нет, так может быть ссылками на Ваше творение поделитесь?..
    • +1
      Оригинальный сайт написан на этом, там же можно посмотреть и проекты, которые якобы данный фрейм используют.
      Мне очень слабо верится что бы кто-то решился на написание полноценного сайта на CppCms.
      А вот жизнеспособность проекта на мелких железяках — вполне вероятна
      • 0
        Оригинальный сайт я уже видел, само собой. Я имел в виду, что интересно как реально работает то, что Вы написали и сделали. Если в Интернете на Ваше творение посмотреть нельзя, то тогда снабдите статью скриншотами, пожалуйста.
  • –1
    А можно с помощью этого сделать динамически обновляемый сайт? Например для realtime показа статистики, что бы каждый раз не тыкать на refresh? А во сколько кода это выльется?
    • +2
      AJAX не?
  • 0
    Основное отличие CppCMS от Wt, про который упоминали выше — в масштабах решаемой проблемы.
    CppCMS — всего лишь шаблонизатор HTML контента и весь скриптинг для динамической работы на стороне клиента нужно писать руками. Данные задачи проектом никак не решаются.
    В Wt-наверное можно, все-таки там многое из «повседневных запросов» уже реализовано
  • 0
    ждем-с с нетерпением второй части, исходники надеюсь предоставите. Очень интересно, спасибо.
  • +1
    Когда то приходилось писать внутрикорпоративный ресурс с long-polling (реалтайм-чат, изменение контента в реальном времени), основная система была на php, но его производительность была недостаточной для этой задачи явно, так что рассматривался вариант cppcms, но в итоге был написан демон на чистом C++ (с boost.asio) — оказалось удобнее и гибче, + минимум оверхеда от сторонних либ.
  • 0
    Будет продолжения? Очень интересно.
    • 0
      скоро выложу, из жизни выпал с АРМ платкой)))

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.