A full-fledged C ++ website and a bit of sofa analytics

    But why?
    There should be a picture about the trolley bus

    It is impolite to answer a question with a question, but: why not? Just because you can.
    Okay, I was joking. To explain the reason, I would like to briefly describe the history of my acquaintance with web development. But, so as not to disrupt the sequence of the narrative, I decided to put it at the end. In general, we will deal with the reasons.

    I think many people are familiar with this kind of web forum like image boards . Yes, yes, you understood correctly - it is on the example of imageboards that I will talk about the experience of creating a site in C ++. What prompted me to engage in such a dubious project benefit? Left heel. In this case, there really were no special reasons. Just woke up one morning and realized - I want. But this is all the lyrics.

    There are enough articles on Habré on websites in C ++: for example, using FastCGI or CppCMS . But all this is HelloWorlds and tutorials. I’ll tell you about a full-fledged (albeit not ideal in terms of architecture and code cleanliness) project, I will try to highlight various subtleties.

    Disclaimer: Method bodies are placed in class declarations to reduce text size.

    Training


    Let's start with the tools we need. It is worth noting immediately that I use Qt for my projects and even wrote a small library that extends the capabilities of this wonderful framework. Qt and this library (called, by the way, BeQt - Beyond Qt) were used by me this time too, but I will not dwell on them, as the article is about something else.

    Web framework


    So, first of all, of course, you need to decide which web framework to use. The choice is small, but it is still there:

    About the first two, I can only say that I somehow did not like them. This does not mean that these frameworks are bad - I did not work with them and I do not know.
    Wt I tried a couple of years ago. Then I was tempted by the architecture similar to Qt and its interesting feature: it was possible, without knowing anything about web applications and HTML, to create a hierarchy of widgets, which then would be rendered into this very HTML without any templates. I was then familiar with the web at the level of "writing in an notebook an HTML page with a title and two paragraphs," that is, you could say I was not familiar at all. However, it quickly became clear that writing something more complicated than the notorious HelloWorld requires the same templates, as well as some third-party scripts (.js) that must be downloaded from the "left" sites. Therefore, acquaintance with Wt ended very soon.
    But after some time, when I already got acquainted with the web, JavaScript, CSS, AJAX-requests, then I decided to once again tackle the topic "website in C ++". Googling again, I came across CppCMS. I liked this framework right away: there is documentation (albeit scarce in places), there are tutorials (and very good ones, examples are very practical, but not far-fetched), cross-platformness is there, templates are there (now I already understood that without them, nowhere). What else is needed for happiness? I downloaded, compiled, connected to the project, checked, rejoiced.

    ORM framework


    Next comes the question of data storage. Qt has a QtSql module , but it assumes a rather low level of abstraction - we manually write queries, manually process the results. I wanted ORM solutions. I googled, apart from various imperfections and abandoned projects, the following (I apologize if I missed some worthwhile framework - no one is omniscient):

    I didn’t like LiteSQL because I didn’t find any documentation on the site, and I just google something with an XML configuration (forgive me, XML wouldn’t be visible to me). I rejected QxOrm by accident and stupidity: I went to the site, saw a screenshot of the graphic link designer and closed the page. Already some time later, after reading an article on Habré, I once again decided to take a closer look at this framework, and I liked it, but you won’t get it done. Hurry, as they say ...
    I liked ODB more or less, although there was no documentation on the classes, but there were quite detailed examples. In addition, ODB supports Qt types such as QString, QDateTime, and others. This is very convenient - no need to convert back and forth. On this framework and chose.
    A moment of humor
    There should be a picture about ODB

    Syntax highlighting


    What I do not like about most large imageboards is that there is no way to really embed code. Either it’s impossible at all (due to the markup, some characters are eaten up, making the font of the text enclosed between them italicized or underlined), or it is possible, but without syntax highlighting. Therefore, even before the start of the project, I decided that I should have such an opportunity. I believe that such a feature is useful on many IT sites.
    The first thing I google - hilite.me - an online service that allows you to convert the text transmitted in the request into HTML, containing the same text, but formatted with syntax highlighting. To work with this site, I first wanted to use QtNetwork , and specifically - QNetworkAccessManager. However, since CppCMS processes requests in separate threads, outside of QThread , this mechanism could not be used (signals and slots outside QEventLoop do not work, but QNetworkReply does not have a blocking API ). I had to cling to new libraries: libcurl (here, as far as I know, no options) and cURLpp - a wrapper over libcurl for C ++ (there was still a variant of curlplusplus , but I chose cURLpp).
    Then I have to run ahead. At first I was glad that the backlight worked as it should, but then I began to notice that for a very long time this whole thing worked: until it connected to the service, while it processed the request, until it sent an answer ... Well, it. And again, I strained Google. Here's what happened this time: Source-highlight . In addition to the console utility, this project also provides a library with the same functionality (the name speaks for itself). Exactly what is needed! (I note, however, that although the library does its job flawlessly, the set of definition files for different languages ​​had to be downloaded separately, and there is no way to load these files into memory - the library can work with them only if they are somewhere in a real file system, and not packaged, say, in an executable file along with other resources.)

    Tell me what your Magic bytes are and I will tell you who you are


    It's about determining the type of file. Imageboards - they are from the word image in the sense of “picture”, and not an image, which means that attaching images to messages lies at their very base. And not only images - recently WebM format is gaining more and more popularity . But which of us has not tried at least once to do something not according to the rules? Sooner or later, it would occur to someone to attach an archive or something else instead of a picture. The information transmitted by the client about the MIME type of the file cannot be trusted either, therefore it is necessary to check its type with some independent tool. Such as libmagic , for example. But pay attention to the date of the last update. Therefore, it is better to use this implementation - it supports more new formats, including the mentioned WebM.
    Of course, Magic bytes do not give any guarantees that the rest of the contents of the file matches the format, but there is nothing to be done. Unless to hire a department of the Chinese (no offense to these laborers be said), which will be checked manually.

    Still connect it all ...


    Here we figured out the choice of tools. And how to cling to the project and use? In Qt, fortunately, there is not the worst (albeit very strange in itself) build system - qmake.
    Here's what the fragment of the project file (.pro / .pri) looks like, which is responsible for connecting libraries:
    Hidden text
    isEmpty(BEQT_PREFIX) {
        mac|unix {
            BEQT_PREFIX=/usr/share/beqt
        } else:win32 {
            BEQT_PREFIX=$$(systemdrive)/PROGRA~1/BeQt
        }
    }
    include($${BEQT_PREFIX}/share/beqt/depend.pri)
    isEmpty(CPPCMS_PREFIX) {
        mac|unix {
            CPPCMS_PREFIX=/usr
        } else:win32 {
            error(CppCMS path is not specified)
        }
    }
    INCLUDEPATH *= $${CPPCMS_PREFIX}/include
    DEPENDPATH *= $${CPPCMS_PREFIX}/include
    LIBS *= -L$${CPPCMS_PREFIX}/lib/ -lcppcms -lbooster
    isEmpty(ODB_PREFIX) {
        mac|unix {
            ODB_PREFIX=/usr
        } else:win32 {
            error(ODB path is not specified)
        }
    }
    INCLUDEPATH *= $${ODB_PREFIX}/include
    DEPENDPATH *= $${ODB_PREFIX}/include
    LIBS *= -L$${ODB_PREFIX}/lib/ -lodb -lodb-sqlite
    !isEmpty(ODB_QT_PREFIX) {
        INCLUDEPATH *= $${ODB_QT_PREFIX}/include
        DEPENDPATH *= $${ODB_QT_PREFIX}/include
        LIBS *= -L$${ODB_QT_PREFIX}/lib/ -lodb-qt
    } else {
        LIBS *= -L$${ODB_PREFIX}/lib/ -lodb-qt
    }
    isEmpty(LIBCURL_PREFIX) {
        mac|unix {
            LIBCURL_PREFIX=/usr
        } else:win32 {
            error(libcurl path is not specified)
        }
    }
    INCLUDEPATH *= $${LIBCURL_PREFIX}/include
    DEPENDPATH *= $${LIBCURL_PREFIX}/include
    LIBS *= -L$${LIBCURL_PREFIX}/lib/ -lcurl
    isEmpty(CURLPP_PREFIX) {
        mac|unix {
            CURLPP_PREFIX=/usr
        } else:win32 {
            error(cURLpp path is not specified)
        }
    }
    INCLUDEPATH *= $${CURLPP_PREFIX}/include
    DEPENDPATH *= $${CURLPP_PREFIX}/include
    LIBS *= -L$${CURLPP_PREFIX}/lib/ -lcurlpp
    isEmpty(BOOST_PREFIX) {
        mac|unix {
            BOOST_PREFIX=/usr
        } else:win32 {
            BOOST_PREFIX=$$(systemdrive)/Boost
        }
    }
    INCLUDEPATH *= $${BOOST_PREFIX}/include
    DEPENDPATH *= $${BOOST_PREFIX}/include
    LIBS *= -L$${BOOST_PREFIX}/lib/ -lboost_regex
    isEmpty(SRCHILITE_PREFIX) {
        mac|unix {
            SRCHILITE_PREFIX=/usr
        } else:win32 {
            error(GNU Source-highlight path is not specified)
        }
    }
    INCLUDEPATH *= $${SRCHILITE_PREFIX}/include
    DEPENDPATH *= $${SRCHILITE_PREFIX}/include
    LIBS *= -L$${SRCHILITE_PREFIX}/lib/ -lsource-highlight
    isEmpty(LIBMAGIC_PREFIX) {
        mac|unix {
            LIBMAGIC_PREFIX=/usr
        } else:win32 {
            error(libmagic path is not specified)
        }
    }
    INCLUDEPATH *= $${LIBMAGIC_PREFIX}/include
    DEPENDPATH *= $${LIBMAGIC_PREFIX}/include
    LIBS *= -L$${LIBMAGIC_PREFIX}/lib/ -lmagic
    isEmpty(SQLITE_PREFIX) {
        mac|unix {
            SQLITE_PREFIX=/usr
        } else:win32 {
            error(SQLite path is not specified)
        }
    }
    INCLUDEPATH *= $${SQLITE_PREFIX}/include
    DEPENDPATH *= $${SQLITE_PREFIX}/include
    LIBS *= -L$${SQLITE_PREFIX}/lib/ -lsqlite3
    mac|unix {
        isEmpty(LORD_PREFIX):LORD_PREFIX=/usr
    } else:win32 {
        isEmpty(LORD_PREFIX):PREFIX=$$(systemdrive)/PROGRA~1/ololord
    }
    


    In addition to the mentioned libraries, you can also notice Boost and SQLite , which are among their dependencies. I will not dwell on these libraries - I did not use them directly. I’ll say briefly about SQLite: this is not the first time I’ve been working with this database, and since I don’t have to host the database on a separate server, I chose it because of its simplicity.
    Of course, everyone chooses tools for themselves, and the set that is described here is not a recommendation. Choose what you like best (if I had the opportunity to return to the past - I would choose QxOrm instead of ODB).

    For work


    The way


    (The Russian “paths” and “routes” somehow do not sound compared to the English “routes”, but what can you do.) In CppCMS, each request is processed by a separate “application” - the cppcms :: application - derived class . Each path is specified by a regular expression that maps a handler function, for example:
    class MyApplication : public cppcms::application
    {
    public:
        explicit MyApplication(cppcms::service &service) :
            cppcms::application(service)
        {
            dispatcher().assign("/file/\\d+", &MyApplication::handleFile, this, 1);
            mapper().assign("/file", "/file/{1}");
        }
        void handleFile(std::string fileNo)
        {
            //тут обрабатываем запрос
        }
    };
    

    cppcms :: service - the thing that is responsible for creating new instances of cppcms :: application, we will come back to it. In the meantime, consider two subtleties: the priority of paths and the situation when the URL ends with a slash or not.
    The imageboard contains a list of boards at "/ [az] +" (simplified for clarity). That is, for example, "site.com/b". And if you type "site.com/b/"? Will everything be alright? No, do not expect anything good. CppCMS does not automatically create alias with a slash at the end. And rightly so. But, nevertheless, sometimes such alias are needed, and you should not forget about them (and add them manually).
    Do not also forget that the paths take precedence in accordance with the order of their addition: the earlier the path is added, the higher its priority. Therefore, if you write like this:
    dispatcher().assign("/.+", &MyApplication::handleAnything, this, 1);
    dispatcher().assign("/page", &MyApplication::handlePage, this);
    

    then the site.com/page page will be unavailable, because its URL matches the regular expression "/.+", and the handler for it is installed with a higher priority. It would be correct to assign the developers in the reverse order. It is important to remember this point.
    Now to a real example. How is work with paths organized with me? First of all, imagine that it was necessary to add some kind of page with its own URL. Edit source? Dismiss We introduce support for factory plugins that create a list of structures, each of which contains a regular expression, the corresponding handler function, the number of arguments (to call the desired method), and priority. If the paths coincide with the default ones, then the paths from the plugins overwrite them. The full code can be found here (carefully, noodles):cppcms :: application descendant class , paths , plugin interface .
    This is where my Qt add-in is used, which allows me to automatically load plug-ins of a certain type (verification is implemented using a separate interface) from several folders: system-wide and user-specific (for example, "/ usr / lib / app / plugins" and "/ home / user / .app / lib / app / plugins "). We will not dwell on this.

    cppcms :: service and configuration


    As mentioned above, cppcms :: service is responsible for creating new instances of cppcms :: application, which already in turn process requests coming to the server. So that cppcms :: service can create instances of your descendant cppcms :: application, you need to register it:
    service.applications_pool().mount(cppcms::applications_factory());
    

    cppcms :: service blocks the current thread and launches new threads as needed, in which cppcms :: application instances work. In order not to interfere with QCoreApplication (the main Qt class in console applications), I run cppcms :: service in a separate thread:
    class OlolordWebAppThread : public QThread
    {
    private:
        const cppcms::json::value Conf;
        cppcms::service *mservice;
        bool mshutdown;
    public:
        explicit OlolordWebAppThread(const cppcms::json::value &conf, QObject *parent = 0) :
             QThread(parent), Conf(conf)
        {
            mshutdown = false;
            mservice = 0;
        }
        void shutdown()
        {
            if (!mservice)
                return;
            mshutdown = true;
            mservice->shutdown();
        }
    protected:
        void run()
        {
            while (!mshutdown) {
                try {
                    cppcms::service service(Conf);
                    mservice = &service;
                    service.applications_pool().mount(cppcms::applications_factory());
                    service.run();
                } catch(std::exception const &e) {
                    qDebug() << e.what();
                }
                mservice = 0;
            }
        }
    };
    

    Note three points: exception handling, the constant const cppcms :: json :: value Conf, and the run method . Unlike Qt, CppCMS uses exceptions everywhere. I do not like exceptions and adhere to the Qt philosophy when instead of
    try {
        int x = doSomething();
    } catch (const Exception &e) {
        //обрабатываем ошибку
    }
    

    is used
    bool ok = false;
    int x = doSomething(&ok);
    if (!ok) {
        //обрабатываем ошибку
    }
    

    Nevertheless, do not forget about exceptions - they are periodically thrown into CppCMS, and they need to be caught and processed on time.
    Why is the overridden run method used instead of the recommended approach with the worker class? Because if you wrap cppcms :: service in a descendant of QObject , put this worker class in a thread and call the slot, which, in turn, calls cppcms :: service :: run , we get only extra wrappers: the cppcms method : : service :: run will still block QEventLoop , because it knows nothing about it and uses the banal for (;;) loop inside. In other words, the code below will not differ from the one above - using the signals and slots or terminating the thread by calling QThread :: quit will not work, since QEventLoop will be blocked.
    class Worker : public QObject
    {
    public:
        cppcms::service service;
    public slots:
        void start()
        {
            //остальной код пропущен для краткости
            service.run();
        }
    }
    int main()
    {
        QThread t;
        Worker *w = new Worker;
        w->moveToThread();
        t.start();
        QMetaObject::invokeMethod(w, "start", Qt::QueuedConnection);
    }
    

    For a better understanding, you can read about the system of signals and slots , as well as about event loops in Qt.
    As for const cppcms :: json :: value Conf , this is a representation of the JSON object, in this case containing the server configuration. Configuration Example:
    {
        "service": {
            "api": "http",
            "port": 80,
            "ip": "0.0.0.0"
        }
    }
    

    “Api” - takes the values ​​“fastcgi”, “scgi”, or “http”, indicates whether the application is stand-alone (with its own HTTP server), or is running * CGI.
    “Port” and “ip” are the port and address that the application listens to. "0.0.0.0" means any address (in other words, the server will respond to requests from all addresses).
    Details are here . Although it is recommended to use * CGI, for my case this would be just an unnecessary complication.
    A few words about how to get cppcms :: json :: value , say, from a file. You need to use the load method , but it takes std :: istream as a parameter , so I wrote a helper function:
    Hidden text
    cppcms::json::value readJsonValue(const QString &fileName, bool *ok)
    {
        bool b = false;
        QString s = BDirTools::readTextFile(fileName, "UTF-8", &b);
        if (!b)
            return cppcms::json::value();
        cppcms::json::value json;
        std::stringstream in(toStd(s));
        if (json.load(in, true))
            return bRet(ok, true, json);
        else
            return bRet(ok, false, cppcms::json::value());
    }
    

    We need all this in order to be able to read the settings not only from the file on disk, but also from the resources built into the application ( Qt Resource System ).

    Now, finally, launch our application:
    int main(int argc, char **argv)
    {
        QCoreApplication app(argc, argv); //главный класс Qt
        cppcms::json::value conf = Tools::readJsonValue("/path/to/conf/file", &ok);
        OlolordWebAppThread t(conf);
        t.start();
        int ret = app.exec(); //запуск QEventLoop
        t.shutdown(); //этот метод является потокобезопасным (thread-safe), проблем нет
        t.wait(10 * BeQt::Second); //ожидание, так как тред может завершиться не моментально, если еще остались необработанные запросы
        return ret;
    }
    


    Controllers and Templates


    So we come to the most interesting part - rendering answers to queries. I will not dwell on less useful examples like
    void MyApplication::main(std::string /*url*/)  
    {  
        response().out() << "\n\n

    Hello World

    \n\n\n"; }

    We want a better site, right? In CppCMS, to render a page, two things are required - a template and a controller associated with it (I began to use the word “controller” arbitrarily, but, it seems to me, it fits here). A template is a file with content in the form of a mixture of HTML and a special CppCMS language. A controller is a C ++ class (or structure) that contains the variables and functions referenced by the template. But fewer words, more examples:
    //page.h
    namespace Content 
    {
    struct Page : public cppcms::base_content
    {
        std::string message;
        std::string pageTitle;
    };
    }
    

    
    <% c++ #include "page.h" %>
    <% skin my_skin %>
    <% view page uses Content::Page %>
    <% template render() %>
    
        
            <%= pageTitle %>

    <%= message %>

    <% end template %> <% end view %> <% end skin %>

    Here we see the Content :: Page controller with the message and pageTitle variables , as well as a template called page using the Content :: Page controller . You can also set various skins (skin), but since I had no business with them, I can’t say anything (and in general I think that it’s better to control the appearance through CSS, although I don’t like this technology).
    Please note that the header file corresponding to the controller must be included with the directive
    <% c++ #include "page.h" %>
    

    This directive allows you to insert arbitrary C ++ code in the place where it appears. To insert the value of the controller variable, a special construction is used
    <%= variableName %>
    

    Line
    <% template render() %>
    

    Declares a function that can then be used in various places:
    <% template hr() %>
    
    <% end template %> <% template render() %> <% include hr() %> <% include hr() %> <% end template %>

    As a result, the contents of the hr function will appear in all places where it was included. Functions can contain an impressive amount of code, which otherwise would have to be copied, which is not good. Functions can also take parameters.
    Templates also support conditional statements, a foreach loop, and some other features. A description of all this would be enough for a separate article, so I will limit myself to a link to the documentation and a couple of comments.
    First, templates can be inherited from each other by overriding functions (they are always declared as virtual). Actually, in the first example, we redefined the render function of the cppcms :: base_content base template .
    Secondly, not so much a remark, rather a small hint: if you need to display a value of a numerical variable increased by some number, this strange code will help (there are no other ways, as I understand it):
    <% c++ out() << (content.variable + 1); %>
    

    I will also show how the template is called from the application:
    void MyApplication::handlePage()
    {
        Content::Page c; //c - потому что controller
        c.pageTitle = "My page";
        c.message = "Yarrr!";
        render("page", c);
    }
    

    Inside, the render function renders (who would think) a template, receives HTML in the form of text, and writes it to response (). Out () .
    And finally, how to start template generation (from a .pro / .pri file):
    CPPCMS_PROCESSING_COMMAND=$${CPPCMS_PREFIX}/bin/cppcms_tmpl_cc
    mac|unix {
        CPPCMS_TEMPLATES=$$files($${PWD}/template/*)
    } else:win32 {
        CPPCMS_TEMPLATES=$$files($${PWD}\\template\\*)
    }
    for(CPPCMS_TEMPLATE, CPPCMS_TEMPLATES) {
        CPPCMS_TEMPLATES_STRING=$${CPPCMS_TEMPLATES_STRING} \"$${CPPCMS_TEMPLATE}\"
    }
    CPPCMS_PROCESSING_COMMAND=$${CPPCMS_PROCESSING_COMMAND} $${CPPCMS_TEMPLATES_STRING} -o \"$${PWD}/compiled_templates.cpp\"
    win32:CPPCMS_PROCESSING_COMMAND=$$replace(CPPCMS_PROCESSING_COMMAND, "/", "\\")
    system(python $${CPPCMS_PROCESSING_COMMAND})
    SOURCES += compiled_templates.cpp
    

    A special utility is used, as well as a Python interpreter (version 2.x), the resulting file is included in the project. It is assumed that the template files are in the subfolder template and have the extension .tmpl.

    Storage


    Almost any website works with data that must somehow be placed in long-term memory (read - on disk). Typically, relational databases are used for this, such as, say, MySQL or SQLite. It is very convenient to use a mechanism that would allow transparent for the developer to turn the objects of the programming language into database data and vice versa. Such a mechanism is called Object-relational Mapping (ORM), and in my case its implementation is used in the form of an ODB framework.
    What does it look like? For example, like this:
    PRAGMA_DB(object table("posts"))
    class Post
    {
    public:
        PRAGMA_DB(id auto)
        quint64 id_;
        PRAGMA_DB(not_null)
        QString board_;
        PRAGMA_DB(not_null)
        quint64 number_;
        PRAGMA_DB(not_null)
        QDateTime dateTime_;
        QString text_;
        PRAGMA_DB(not_null)
        QLazySharedPointer thread_;
    public:
        explicit Post()
        {
            //
        }
    private:
        friend class odb::access;
    };
    

    Here we declare a Post class that represents a post (or post) on a forum (or board). The line PRAGMA_DB (object table ("posts")) says that this object should be stored in the database and loaded from it (there is also an optional parameter table ("posts") that explicitly sets the table name, otherwise the default name would be used "Post"). The PRAGMA_DB macro expands to #pragma db , then its arguments are added. The macro is used to prevent the compiler from issuing warnings when it encounters the unfamiliar #pragma syntax .
    If you add the same macro above the class variable, you can tell ODB additional information about this variable - how it should be stored in the database (for example, that the variable is an identifier and the corresponding field should be declared as PRIMARY KEY ). Alas, there is no way to specify arbitrary constraints, but I would very much like to say, say, for the table something like UNIQUE (fieldOne, fieldTwo, fieldThree) , that is, specify uniqueness across several fields, and not one at a time.
    You can use another class labeled PRAGMA_DB as the variable type . Also, as you noticed, you can specify Qt classes as a variable type. This requires the odb-qt library. Finally, a variable whose type is wrapped inQLazySharedPointer is not initialized immediately when querying the database, but is loaded with a separate query later, if necessary (Lazy fetch).
    You must also declare odb :: access a friendly class.
    And here is the saving and loading of objects (in the case of SQLite):
    try {
        odb::database *db = new odb::sqlite::database("/path/to/db",
            SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE); //создаем соединение с БД
        odb::transaction t = odb::transaction(db.begin()); //начинаем транзакцию
        //создаем схему
        t->execute("PRAGMA foreign_keys=OFF");
        odb::schema_catalog::create_schema(*db);
        t->execute("PRAGMA foreign_keys=ON");
        Post p;
        //инициализируем переменные поста
        db->perist(p); //сохраняем пост в базе
        odb::result r(db->query()); //запрашиваем список всех постов
        for (odb::result_iterator i = r.begin(); i != r.end(); ++i) {
            //делаем что-то с полученным списком постов, например:
            i->dateTime_ = QDateTime::currentDateTimeUtc(); //задаем время
            db->update(*i); //и сохраняем изменения
        }
        t.commit(); //завершаем транзакцию
    } catch (const odb::exception &e) {
        //обрабатываем исключение
    }
    

    In a good way, you still need to take care of deleting the pointer to odb :: database , but I did not overload the code (use a scoped pointer for this).
    It is worth noting that the odb :: schema_catalog :: create_schema function does not check if there are already tables in the database, that is, it performs CREATE TABLE ... instead of CREATE TABLE IF NOT EXISTS ... , so you need to check it manually. Naturally, there is no question of any automatic creation of a circuit - everything is manual. And this is another stone in the ODB garden. However, apart from some crutches, the library copes with its task.
    ODB, like CppCMS, requires its own meta compiler that processes PRAGMA_DB and generates C ++ code. It starts like this:
    mac|unix {
        ODB_PROCESSING_COMMAND=$${ODB_PREFIX}/bin/odb
        ODB_TEMPLATES=$$files($${PWD}/*.h)
    } else:win32 {
        ODB_PROCESSING_COMMAND=$${ODB_PREFIX}/bin/odb.exe
        ODB_TEMPLATES=$$files($${PWD}\\*.h)
    }
    for(ODB_TEMPLATE, ODB_TEMPLATES) {
        ODB_TEMPLATES_STRING=$${ODB_TEMPLATES_STRING} \"$${ODB_TEMPLATE}\"
    }
    ODB_PROCESSING_COMMAND=$${ODB_PROCESSING_COMMAND} -d sqlite --generate-query --generate-schema --profile qt
    ODB_PROCESSING_COMMAND=$${ODB_PROCESSING_COMMAND} -I \"$${QMAKE_INCDIR_QT}\"
    ODB_PROCESSING_COMMAND=$${ODB_PROCESSING_COMMAND} -I \"$${QMAKE_INCDIR_QT}/QtCore\" $${ODB_TEMPLATES_STRING}
    win32:ODB_PROCESSING_COMMAND=$$replace(ODB_PROCESSING_COMMAND, "/", "\\")
    system($${ODB_PROCESSING_COMMAND})
    HEADERS += $$files($${PWD}/*.hxx)
    SOURCES += $$files($${PWD}/*.cxx)
    

    The compiler is supplied separately from the library as a binary. At startup, you need to specify the list of files, the type of database (in my case, SQLite), and also that the Qt profile is used and that you need to generate a schema. Plus, the paths to the Qt header files are indicated. The compiler generates .hxx and .cxx files, adding the suffix "-odb" to the original names.
    Now a few words about my project. Since ODB requires an active transaction for any operation, I decided to wrap the bundle odb :: database and odb :: transaction in one Transaction class , which stores a pointer to the currently active transaction and its corresponding database connection. At the same time, no more than one active transaction can exist in each thread. When creating an instance of the wrapper class (Transaction ), if there is no transaction yet, a new connection is created and the transaction begins, if there is already an active transaction, the internal counter is increased. If Transaction is destroyed, the real odb :: transaction will not be completed until the counter is reset to zero. That is, until we reach the bottom of the stack where the Transaction instance created first is located , we will be inside the same transaction, referring to the same database connection. Very comfortably. Sources here: 1 , 2 . Examples of use: 1 , 2 .
    For the lazy, a trimmed example under the spoiler
    bool createPost(CreateThreadParameters &p, QSharedPointer thread)
    {
        try {
            Transaction t; //этот объект на вершине стека
            Post post(p, thread);
            t->persist(post);
            t.commit();
            return true;
        } catch (const odb::exception &e) {
            qDebug() << e.what();
            return false;
        }
    }
    bool createThread(CreateThreadParameters &p)
    {
        try {
            Transaction t; //этот объект на дне стека
            QSharedPointer thread(new Thread(p));
            t->persist(thread);
            if (!createPost(p, thread))
                return bfalse;
            t.commit();
            return true;
        } catch (const odb::exception &e) {
            qDebug() << e.what();
            return false;
        }
    }
    

    Both Thread objects refer to the same transaction and the same connection to the database.


    Syntax highlighting and file type checking


    There are no special comments, I’ll just give the code:
    QString mimeType(const QByteArray &data, bool *ok)
    {
        if (data.isEmpty())
            return bRet(ok, false, QString());
        magic_t magicMimePredictor;
        magicMimePredictor = magic_open(MAGIC_MIME_TYPE);
        if (!magicMimePredictor)
            return bRet(ok, false, QString());
        if (magic_load(magicMimePredictor, 0)) {
            magic_close(magicMimePredictor);
            return bRet(ok, false, QString());
        }
        QString result = QString::fromLatin1(magic_buffer(magicMimePredictor, (void *) data.data(), data.size()));
        return bRet(ok, !result.isEmpty(), result);
    }
    QString highlight(const QString &code, const QString &lang)
    {
        std::istringstream in(Tools::toStd(code));
        std::ostringstream out;
        try {
            srchilite::SourceHighlight sourceHighlight("html.outlang");
            sourceHighlight.setDataDir("/path/to/definition/files");
            sourceHighlight.highlight(in, out, lang.toLatin1().data() + ".lang");
        } catch (const srchilite::ParserException &e) {
            qDebug() << e.what();
            return "";
        } catch (const srchilite::IOException &e) {
            qDebug() << e.what();
            return;
        } catch (const std::exception &e) {
            qDebug() << e.what();
            return;
        }
        return + QString::fromLocal8Bit(out.str());
    }
    

    When determining the type of file, we play a little with the type of pointers; when highlighting the syntax, we specify the path to the folder with the files for syntax definitions and the source language.

    I would like to tell you a lot more, for example, how I implemented support for different types of captcha and attaching more than one file, processing static content, saving files, caching ... the list goes on. But, I am afraid, within the framework of one article, I can not do this, alas, to me. If it will be interesting to someone, then I will tell you about all of the above and a lot more about that in the next parts.

    Well, now - the promised sofa analytics. At first, I admit, I succumbed to the general opinion that C ++ is not suitable for the web. At the same time, I did not particularly think about why. Since everyone says so, then probably it is. This was a few years ago when I had no idea what JavaScrpt is for, what AJAX is, and why CSS should be used.
    But as time went on, I poked around Django , Ruby on Rails, worked for some time with Java, creating one large website, gained experience, learned new technologies. And I realized that, in fact, no matter what language is used in the backend, the frontend will still be the same HTML, CSS and JavaScript. Anyway, you need to write page templates, create styles, program more complex behavior in JS. And all this has nothing to do with the backend.
    Is there a big difference between, say, Thymeleaf (Java) and CppCMS when working with templates? Not too much. All the same template language, only the syntax is slightly different. Everything is rendered exactly the same, by calling the render function from the code. And the controllers are there, and there, no matter what they are called.
    What about data storage? How is ODB fundamentally different fromHibernate ? Yes, the possibilities are modest in some places, but does this mean that ODB is not suitable for ORM at all? I do not think so.
    Well and so on. The frontend remains the frontend, while in the backend we do the same in any language. So is there any difference? It turns out that no. In Java or Python, working with a database will not be fundamentally different from working with a database in C ++, but this also applies to what can be called "business logic", that is, the main logic of the application. All the same checks, conditional statements, hierarchy of classes / functions, only the syntax of each language has its own.
    Using Python, you cannot get rid of the need to write AJAX requests to JS, or stop accessing the database. There are no such miracles. Someone might say that working with a database on% language_name% is easier than using C ++ and will be partly right, but only partly: wonders, I repeat, do not happen if you need to get an object from the database, for this you need to write something like Object o = db-> query ("..."); - in any language.
    That is, it turns out that the answer to the question “But why?” Remains the same: “Why not?”, Only its meaning changes. These are my observations based on personal experience writing web applications in various languages ​​(C ++, Java, to a lesser extent Python, Ruby). And this is not an appeal to the holivar, but a desire to resolve it in the most peaceful way.

    I take my leave for the sim, and also leave a link to the project sources: github.com/ololoepepe/ololord
    Well, of course, I invite you to discuss it. Have you written web applications in C ++? Have you heard reasoned justification why this is bad? Share your experience.

    Also popular now: