📜 ⬆️ ⬇️

Full C ++ website and some couch analytics

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

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

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

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

Disclaimer: body methods are placed in class declarations to reduce text.
')

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) have been used by me this time too, but I will not dwell on them, since 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 they somehow did not like me. This does not mean that these frameworks are bad - I didn’t work with them and I don’t know.
Wt I tried a couple of years ago. I was then 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 was then rendered to this very HTML without any templates. At that time I was familiar with the web at the level of “writing an HTML page with a title'om and two paragraphs in a notebook,” that is, one might say, was not familiar at all. However, it quickly became clear that for writing something more complicated than the notorious HelloWorld, templates are needed, and also some third-party scripts (.js) that need to be downloaded from the "left" sites. Therefore, acquaintance with Wt very soon ended.
But after a while, when I already got acquainted with the web, JavaScript, CSS, AJAX requests, then I decided to once again address the topic “website in C ++”. Googling again, I stumbled upon CppCMS. I liked this framework right away: documentation (albeit scant in some places) is, tutorials are (and very good, very practical examples, and not far-fetched), cross-platform is, templates are there (now I already understood that without them nowhere). What else do you need for happiness? I downloaded, compiled, connected to the project, checked it, I was glad.

ORM framework


Next comes the issue 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 also wanted an ORM solution. I googled, apart from various shortcomings and abandoned projects, the following (I apologize if I missed some standing framework - no one is omniscient):

I didn’t like LiteSQL because I didn’t find the documentation on the site, but just googled something with an XML configuration (forgive me, but I couldn’t see my XML eyes). I rejected QxOrm by accident and stupidity: I went to the site, saw a screenshot of the graphic designer of communications and closed the page. Already some time later, after reading the article on Habré, I once again decided to look at this framework, and I liked it, but you can’t undo the work. Hurry up, as they say ...
I more or less liked the ODB, although there was no documentation for the classes, but there were quite detailed examples. In addition, ODB supports Qt types such as QString, QDateTime, and others. It is very convenient - no need to convert back and forth. At this framework, and chose.
Moment of humor
There should be a picture of ODB

Syntax highlighting


What I don't like in most large imageboards is that there is no way to really insert the code. Either not at all (due to the peculiarities of the markup, some characters are eaten, making the text between the text enclosed in italics or underlined), or you can, but without syntax highlighting. Therefore, before the start of the project, I decided that I had to have such an opportunity. I believe that this feature is useful on many IT sites.
The first thing I did was google - hilite.me - an online service that allows you to turn 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 streams outside QThread , this mechanism did not work (signals and slots outside QEventLoop do not work, and QNetworkReply does not have a blocking API). I had to cling to new libraries: libcurl (here, as far as I know, with no options) and cURLpp - a wrapper over libcurl for C ++ (there was another option curlplusplus , but I chose cURLpp).
Here I am forced to run ahead. At first I was glad that the backlight was working as it should, but then I began to notice that the whole thing had been working for a very long time: until it connected to the service, until it processed the request, until it sent the answer ... Well, it. And again, I strained Google. This is 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 work without complaints, the set of definition files for different languages ​​had to be downloaded separately, and these files could not be loaded into memory - the library can work with them only if they are somewhere in a real file system, rather than being packed, say, into an executable file along with other resources.)

Tell me what are your magic bytes and i will tell you who you are


Speech on determining the type of file. Imageboards - they are from the word image in the sense of “picture”, and not image, which means that they are based on the attachment of images to messages. And not only images - the WebM format is gaining more and more popularity lately. But who among us has not tried to do something at least once by the rules? Sooner or later, someone would think of attaching an archive or something else instead of a picture. Information transmitted by the client about the MIME type of the file cannot be trusted either, so you need to check its type with some independent tool. Such as libmagic , for example. But pay attention to the last update date. Therefore, it is better to use this implementation - it supports more new formats, including the WebM mentioned.
Of course, Magic bytes do not give any guarantees that the rest of the file contents correspond to the format, but there's nothing you can do. Is that to hire a department of the Chinese (no offense to these trudahyag be told), which will be checked manually.

Another would connect all this ...


Here we are with the choice of tools and figured out. And how to cling to the project and use? In Qt, fortunately, there is not the worst (although very strange in itself) build system - qmake.
Here is what a 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 libraries mentioned, 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: I’m not the first time working with this database, and since I don’t need to host a database on a separate server, I chose it because of its simplicity.
Of course, everyone chooses the tools for themselves, and the set described here is not a recommendation. Choose what you like more (if I had the opportunity to return to the past, I would choose QxOrm instead of ODB).

For work


Paths


(Russian “paths” and “routes” somehow do not sound compared to the English “routes”, but what to do.) In CppCMS, each request is processed by a separate “application” - the heir class cppcms :: application . Each path is defined by a regular expression that matches the 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 is 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 the paths and the situation when the URL ends with a slash or not.
Imageboard contains a list of boards at the address "/ [az] +" (simplified for clarity). That is, for example, "site.com/b". And if you type "site.com/b/"? Will all be well? No, don't expect anything good. CppCMS does not automatically create alias with a slash on 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 forget that the paths take precedence according to the order in which they are added: the earlier the path is added, the higher its priority. Therefore, if you write this:
 dispatcher().assign("/.+", &MyApplication::handleAnything, this, 1); dispatcher().assign("/page", &MyApplication::handlePage, this); 

then the “site.com/page” page will be unavailable, since its URL matches the regular expression “/.+”, and the handler for it is set with a higher priority. It would be correct to assign the workers in the reverse order. It is important to remember this moment.
Now to the real example. How is work with the paths organized by me? First of all, let's imagine that it was necessary to add some of your own page with your URL. Edit the source? Dismiss. We introduce support for plug-in factories 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 those already available by default, then the paths from the plugins overwrite them. The full code can be viewed here (carefully, noodles): the heir class cppcms :: application , paths , plug-in interface .
This is where my add-on over Qt is used, which allows automatically loading plugins of a certain type (the check 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. In order for cppcms :: service to create instances of your descendant of cppcms :: application, it must be registered:
 service.applications_pool().mount(cppcms::applications_factory<MyApplication>()); 

cppcms :: service blocks the current thread and starts new threads as needed, in which instances of cppcms :: application are running. In order not to interfere with the work of 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<OlolordWebApp>()); service.run(); } catch(std::exception const &e) { qDebug() << e.what(); } mservice = 0; } } }; 

Notice three things: exception handling, the const constant cppcms :: json :: value Conf, and the run method. Unlike Qt, CppCMS universally uses exceptions. 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) { //  } 

However, do not forget about the exceptions - they are periodically thrown out in CppCMS, and they need to be intercepted and processed in 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 QObject descendant, put this worker class in a thread and call a slot, which, in turn, will call cppcms :: service :: run , we get only extra wrappers: the cppcms method : : service :: run will still block QEventLoop , because it does not know anything about it and uses a banal for (;;) loop inside. In other words, the code below will not differ in any way from the above — you cannot use signals and slots or interrupt the thread with QThread :: quit , 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 the event loop in Qt.
As for const cppcms :: json :: value Conf , this is a representation of a 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 independent (with its own HTTP server), or is running * CGI.
"Port" and "ip" - 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 - 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()); } 

All this is necessary in order to be able to read the settings not only from the file on disk, but also from the resources embedded in the application ( Qt Resource System ).

Now, finally, run 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


Here we come to the most interesting - rendering answers to requests. I will not dwell on the less useful examples of the type
 void MyApplication::main(std::string /*url*/) { response().out() << "<html>\n<body>\n<h1>Hello World</h1>\n</body>\n</html>\n"; } 

We want the site better, right? In CppCMS, in order 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, as 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 language CppCMS. A controller is a C ++ class (or structure) containing variables and functions that the template refers to. But fewer words, more examples:
 //page.h namespace Content { struct Page : public cppcms::base_content { std::string message; std::string pageTitle; }; } 

 <!-- page.tmlp --> <% c++ #include "page.h" %> <% skin my_skin %> <% view page uses Content::Page %> <% template render() %> <html> <head> <title><%= pageTitle %></title> </head> <body> <h1><%= message %></h1> </body> </html> <% 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 different skins (skin), but since I haven’t dealt with them, I can’t say anything (and in general I think that it's better to control appearance through CSS, even though I don't like this technology).
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 same value of the controller variable, use a special design
 <%= variableName %> 

Line
 <% template render() %> 

Declares a function that can then be used in various places:
 <% template hr() %> <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 that would otherwise have to copy-paste, which is not good. Functions can also take parameters.
Also, templates 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 redefining functions (they are always declared as virtual). Actually, in the first example, we redefined the render function of the base template cppcms :: base_content .
Secondly, not so that the remark, rather a small hint: if you need to display the value of a numeric variable in the template, increased by some number, this strange code will help (there are no other ways, as I understood it):
 <% c++ out() << (content.variable + 1); %> 

I will also show how the template is rendered 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 be able to think) the template, gets the HTML as text and writes it in response (). Out () .
And finally - how to start generating templates (from the .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 a subfolder of the template and have the extension .tmpl.

Storage


Almost any web site works with data that must somehow be placed in long-term memory (read - on disk). Usually, relational DBMS, such as, say, MySQL or SQLite, are used for this. It is very convenient to use a mechanism that would allow the developer to transparently turn programming language objects 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 the ODB framework.
What does this 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> thread_; public: explicit Post() { // } private: friend class odb::access; }; 

Here we declare a class Post , representing the message (or post) on the forum (or blackboard). The fact that this object should be saved to the database and loaded from it is indicated by the string PRAGMA_DB (object table ("posts")) (there is also an optional parameter table ("posts") that explicitly specifies the name of the table, 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 encountering the unfamiliar #pragma syntax.
If you add the same macro over a 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, arbitrary constraints do not indicate the possibility, but I would very much like, say, to declare something like UNIQUE (fieldOne, fieldTwo, fieldThree) for a table, that is, to specify uniqueness for several fields, and not for one.
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, the variable whose type is wrapped in QLazySharedPointer is not initialized immediately upon a query to the database, but is loaded by a separate query later, if necessary (Lazy fetch).
You must also declare odb :: access to be a friendly class.
And this is how saving and loading of objects looks like (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<Post> r(db->query<Post>()); //    for (odb::result_iterator<Post> i = r.begin(); i != r.end(); ++i) { // -    , : i->dateTime_ = QDateTime::currentDateTimeUtc(); //  db->update(*i); //   } t.commit(); //  } catch (const odb::exception &e) { //  } 

In an amicable way, we still have to take care of deleting the pointer to odb :: database , but I did not overload the code (use a scoped pointer for this).
It should be noted that the function odb :: schema_catalog :: create_schema 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, about any automatic creation of the scheme and the speech does not go - all manually. 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, which 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 in the form of a binary. At startup, you need to specify the file list, database type (in my case, SQLite), as well as the fact that the Qt profile is used and that the schema needs to be generated. Plus, the paths to the Qt header files are indicated. The compiler generates .hxx and .cxx files by 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 odb :: database and odb :: transaction bundle into a single Transaction class, which stores a pointer to the currently active transaction and its corresponding database connection. At the same time in each thread there can be no more than one active transaction. When creating an instance of a wrapper class ( Transaction ), if there is no transaction yet, a new connection is created and the transaction begins, if the active transaction already exists, the internal counter is incremented. When a Transaction is destroyed, the real odb :: transaction will not be completed until the counter is reset. That is, until we reach the bottom of the stack, where the first Transaction instance is located, we will be inside the same transaction referring to the same connection to the database. Very comfortably. Sources here: 1 , 2 . Examples of use: 1 , 2 .
For lazy trimmed example under spoiler
 bool createPost(CreateThreadParameters &p, QSharedPointer<Thread> 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> 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 here, 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 defining a file type, just a little is played with the type of pointers; when syntax highlighting, we indicate the path to the folder with the syntax definition files 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, in the framework of one article I will not be able to do this, alas for me. If it is interesting to someone, then I will definitely tell about all of the above and a lot about what else in the following sections.

And now - the promised sofa analyst. First, I confess, I succumbed to the general opinion that C ++ is not suitable for the web. At the same time, I did not particularly wonder why. If everyone says so, then probably it is. It was a few years ago when I had no idea what JavaScrpt was for, what AJAX is and why I should use CSS.
But as time went on, I poked Django , Ruby on Rails , worked for some time with Java, creating one major website, gained experience, learned new technologies. And I realized that, in fact, whatever language was used in the backend, the front end would still be the same HTML, CSS and JavaScript.Anyway, you need to write page templates, create styles, program more complex behavior on JS. And all this has nothing to do with the backend.
Is there a 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 the same way, by calling the render function from the code. And there are controllers there and there, no matter what they are called.
And data storage? How is ODB fundamentally different from Hibernate ? Yes, opportunities are more modest in 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 a front-end; in backend, we do the same in any language. So does it make a difference? It turns out that no. In Java or Python, working with a database will not be something fundamentally different from working with a database in C ++, the same goes for what is called “business logic”, that is, the main logic of the application. All the same checks, conditional operators, class / function hierarchy, only the syntax of each language is different.
It will not work, using Python, to get rid of the need to write JS AJAX requests, or to stop accessing the database. There are no such miracles. Someone might say that working with a database on% name_name% is easier than in C ++, and will be partly right, but only partially: miracles, I repeat, do not happen if you need to get an object from the base, you need to write something likeObject o = db-> query ("..."); - in any language.
That is, it turns out that the answer to the question “But why?” Remains the same: “And why not?”, Only its meaning changes. These are my observations based on personal experience of writing web applications in various languages ​​(C ++, Java, to a lesser extent - Python, Ruby). And this is not a call for a holivar, but a desire to resolve it in the most peaceful way.

Behind this I take my leave, and leave the link to the source code of the project: github.com/ololoepepe/ololord
Well, of course, I invite you to the discussion. Did you write web apps in C ++? Have you heard a reasoned reason why this is bad? Share your experience.

Source: https://habr.com/ru/post/252215/


All Articles