[Из песочницы] Сайт на с++ (CppCMS). Часть 1

Здравствуй уважаемый %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»:

Полный текст файла ]>

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

namespace Data { //------------------------------------------------------------------------------------- // Dsc: Структура основной информации о странице //------------------------------------------------------------------------------------- struct infoPage { std: string title; // титул страницы std: string description; // описание страницы std: string keywords; // ключевые слова страницы std: map 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() %> <%= page.title %>

<% include page_footer() %>
<% 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(»/», «MAIN»)); tmpl.page.menuList.insert (std: pair(»/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: 12 px/18 px Arial, sans-serif; width: 100%; height: 100%; } .wrapper { width: 800 px; margin: 0 auto; min-height: 100%; height: auto! important; height: 100%; }

/* Header -----------------------------------------------------------------------------*/ .header { height: 50 px; background: #FFE680; }

/* Middle -----------------------------------------------------------------------------*/ .middle { width: 100%; padding: 0 0 50 px; position: relative; } .middle: after { display: table; clear: both; content: ''; } .container { width: 100%; float: left; overflow: hidden; } .content { padding: 0 270 px 0 270 px; }

/* Left Sidebar -----------------------------------------------------------------------------*/ .left-sidebar { float: left; width: 250 px; margin-left: -100%; position: relative; background: #B5E3FF; }

/* Footer -----------------------------------------------------------------------------*/ .footer { width: 800 px; margin: -50 px auto 0; height: 50 px; 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 #include #include #include #include #include

#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(»/», «MASTER»)); tmpl.page.menuList.insert (std: pair(»/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(»/», «MASTER»)); tmpl.page.menuList.insert (std: pair(»/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()); // запускаем 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; }

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

© Habrahabr.ru