18 #include "application_p.h"
22 #include "context_p.h"
23 #include "enginerequest.h"
25 #include "request_p.h"
26 #include "controller.h"
27 #include "controller_p.h"
29 #include "response_p.h"
30 #include "dispatchtype.h"
35 #include <QtCore/QDir>
36 #include <QtCore/QStringList>
37 #include <QtCore/QDataStream>
38 #include <QtCore/QCoreApplication>
39 #include <QtCore/QPluginLoader>
40 #include <QtCore/QTranslator>
41 #include <QtCore/QFileInfo>
42 #include <QtCore/QLocale>
44 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER,
"cutelyst.dispatcher", QtWarningMsg)
45 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_PATH,
"cutelyst.dispatcher.path", QtWarningMsg)
46 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_CHAINED,
"cutelyst.dispatcher.chained", QtWarningMsg)
47 Q_LOGGING_CATEGORY(CUTELYST_CONTROLLER,
"cutelyst.controller", QtWarningMsg)
48 Q_LOGGING_CATEGORY(CUTELYST_CORE,
"cutelyst.core", QtWarningMsg)
49 Q_LOGGING_CATEGORY(CUTELYST_ENGINE,
"cutelyst.engine", QtWarningMsg)
50 Q_LOGGING_CATEGORY(CUTELYST_UPLOAD,
"cutelyst.upload", QtWarningMsg)
51 Q_LOGGING_CATEGORY(CUTELYST_MULTIPART,
"cutelyst.multipart", QtWarningMsg)
52 Q_LOGGING_CATEGORY(CUTELYST_VIEW,
"cutelyst.view", QtWarningMsg)
53 Q_LOGGING_CATEGORY(CUTELYST_REQUEST,
"cutelyst.request", QtWarningMsg)
54 Q_LOGGING_CATEGORY(CUTELYST_RESPONSE,
"cutelyst.response", QtWarningMsg)
55 Q_LOGGING_CATEGORY(CUTELYST_STATS,
"cutelyst.stats", QtWarningMsg)
56 Q_LOGGING_CATEGORY(CUTELYST_COMPONENT,
"cutelyst.component", QtWarningMsg)
62 d_ptr(new ApplicationPrivate)
68 qRegisterMetaType<ParamsMultiMap>();
69 qRegisterMetaTypeStreamOperators<ParamsMultiMap>(
"ParamsMultiMap");
76 Application::~Application()
83 qCDebug(CUTELYST_CORE) <<
"Default Application::init called on pid:" << QCoreApplication::applicationPid();
89 qCDebug(CUTELYST_CORE) <<
"Default Application::postFork called on pid:" << QCoreApplication::applicationPid();
102 d->headers.setHeader(QStringLiteral(
"X_CUTELYST"), QStringLiteral(VERSION));
108 if (d->plugins.contains(
plugin)) {
111 d->plugins.append(
plugin);
118 const auto name = QString::fromLatin1(controller->metaObject()->className());
119 if (d->controllersHash.contains(name)) {
122 d->controllersHash.insert(name, controller);
123 d->controllers.append(controller);
130 if (d->views.contains(
view->
name())) {
131 qCWarning(CUTELYST_CORE) <<
"Not registering View." <<
view->metaObject()->className()
132 <<
"There is already a view with this name:" <<
view->
name();
152 auto it = d->factories.constFind(name);
153 if (it != d->factories.constEnd()) {
162 const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv(
"CUTELYST_PLUGINS_DIR").split(
';');
163 for (
const QByteArray &dir : dirs) {
164 Component *component = d->createComponentPlugin(name, parent, QString::fromLocal8Bit(dir));
169 qCDebug(CUTELYST_CORE) <<
"Did not find plugin" << name <<
"on" << dirs <<
"for" << parent;
182 return d->controllers;
188 return d->views.value(name);
194 auto it = d->config.constFind(key);
195 if (it != d->config.constEnd()) {
204 return d->dispatcher;
210 return d->dispatcher->dispatchers();
227 QDir home =
config(QStringLiteral(
"home")).toString();
228 return home.absoluteFilePath(path);
233 QDir home = config(QStringLiteral(
"home")).toString();
234 return home.absoluteFilePath(path.join(QLatin1Char(
'/')));
252 d->config.insert(key, value);
264 d->useStats = CUTELYST_STATS().isDebugEnabled();
273 d->setupChildren(children());
277 QVector<QStringList> tablePlugins;
278 const auto plugins = d->plugins;
280 if (
plugin->objectName().isEmpty()) {
281 plugin->setObjectName(QString::fromLatin1(
plugin->metaObject()->className()));
283 tablePlugins.append({
plugin->objectName() });
288 if (zeroCore && !tablePlugins.isEmpty()) {
289 qCDebug(CUTELYST_CORE) << Utils::buildTable(tablePlugins, QStringList(),
290 QLatin1String(
"Loaded plugins:")).constData();
294 QVector<QStringList> tableDataHandlers;
295 tableDataHandlers.append({ QLatin1String(
"application/x-www-form-urlencoded") });
296 tableDataHandlers.append({ QLatin1String(
"application/json") });
297 tableDataHandlers.append({ QLatin1String(
"multipart/form-data") });
298 qCDebug(CUTELYST_CORE) << Utils::buildTable(tableDataHandlers, QStringList(),
299 QLatin1String(
"Loaded Request Data Handlers:")).constData();
301 qCDebug(CUTELYST_CORE) <<
"Loaded dispatcher" << QString::fromLatin1(d->dispatcher->metaObject()->className());
302 qCDebug(CUTELYST_CORE) <<
"Using engine" << QString::fromLatin1(d->engine->metaObject()->className());
305 QString home = d->config.value(QLatin1String(
"home")).toString();
306 if (home.isEmpty()) {
308 qCDebug(CUTELYST_CORE) <<
"Couldn't find home";
311 QFileInfo homeInfo = home;
312 if (homeInfo.isDir()) {
314 qCDebug(CUTELYST_CORE) <<
"Found home" << home;
318 qCDebug(CUTELYST_CORE) <<
"Home" << home <<
"doesn't exist";
323 QVector<QStringList> table;
324 QStringList controllerNames = d->controllersHash.keys();
325 controllerNames.sort();
326 for (
const QString &controller : controllerNames) {
327 table.append({ controller, QLatin1String(
"Controller")});
330 const auto views = d->views;
333 const QString className = QString::fromLatin1(
view->metaObject()->className()) + QLatin1String(
"->execute");
336 table.append({
view->
reverse(), QLatin1String(
"View")});
339 if (zeroCore && !table.isEmpty()) {
340 qCDebug(CUTELYST_CORE) << Utils::buildTable(table, {
341 QLatin1String(
"Class"), QLatin1String(
"Type")
343 QLatin1String(
"Loaded components:")).constData();
348 controller->d_ptr->init(
this, d->dispatcher);
351 d->dispatcher->setupActions(d->controllers, d->dispatchers, d->engine->workerCore() == 0);
354 qCInfo(CUTELYST_CORE) << qPrintable(QString::fromLatin1(
"%1 powered by Cutelyst %2, Qt %3.")
355 .arg(QCoreApplication::applicationName(),
357 QLatin1String(qVersion())));
374 auto priv =
new ContextPrivate(
this,
engine, d->dispatcher, d->plugins);
378 priv->engineRequest = request;
379 priv->response =
new Response(d->headers, request);
380 priv->request =
new Request(request);
383 priv->stats =
new Stats(request);
387 bool skipMethod =
false;
391 static bool log = CUTELYST_REQUEST().isEnabled(QtDebugMsg);
393 d->logRequest(priv->request);
396 d->dispatcher->prepareAction(c);
400 d->dispatcher->dispatch(c);
402 if (request->
status & EngineRequest::Async) {
422 if (!controller->postFork(
this)) {
435 Q_ASSERT_X(translator,
"add translator to application",
"invalid QTranslator object");
436 auto it = d->translators.find(locale);
437 if (it != d->translators.end()) {
438 it.value().prepend(translator);
440 d->translators.insert(locale, QVector<QTranslator*>(1, translator));
452 Q_ASSERT_X(!translators.empty(),
"add translators to application",
"empty translators vector");
453 auto transIt = d->translators.find(locale);
454 if (transIt != d->translators.end()) {
455 for (
auto it = translators.crbegin(); it != translators.crend(); ++it) {
456 transIt.value().prepend(*it);
459 d->translators.insert(locale, translators);
463 static void replacePercentN(QString *result,
int n)
468 while ((percentPos = result->indexOf(QLatin1Char(
'%'), percentPos + len)) != -1) {
471 if (result->at(percentPos + len) == QLatin1Char(
'L')) {
473 fmt = QStringLiteral(
"%L1");
475 fmt = QStringLiteral(
"%1");
477 if (result->at(percentPos + len) == QLatin1Char(
'n')) {
480 result->replace(percentPos, len, fmt);
487 QString
Application::translate(
const QLocale &locale,
const char *context,
const char *sourceText,
const char *disambiguation,
int n)
const
497 const QVector<QTranslator*> translators = d->translators.value(locale);
498 if (translators.empty()) {
499 result = QString::fromUtf8(sourceText);
500 replacePercentN(&result, n);
504 for (QTranslator *translator : translators) {
505 result = translator->translate(context, sourceText, disambiguation, n);
506 if (!result.isEmpty()) {
511 if (result.isEmpty()) {
512 result = QString::fromUtf8(sourceText);
515 replacePercentN(&result, n);
526 QVector<QLocale> locales;
528 if (Q_LIKELY(!filename.isEmpty())) {
529 const QString _dir = directory.isEmpty() ? QStringLiteral(I18NDIR) : directory;
530 const QDir i18nDir(_dir);
531 if (Q_LIKELY(i18nDir.exists())) {
532 const QString _prefix = prefix.isEmpty() ? QStringLiteral(
".") : prefix;
533 const QString _suffix = suffix.isEmpty() ? QStringLiteral(
".qm") : suffix;
534 const QStringList namesFilter = QStringList({filename + _prefix + QLatin1Char(
'*') + _suffix});
536 const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
537 if (Q_LIKELY(!tsFiles.empty())) {
538 locales.reserve(tsFiles.size());
539 for (
const QFileInfo &ts : tsFiles) {
540 const QString fn = ts.fileName();
541 const int prefIdx = fn.indexOf(_prefix);
542 const QString locString = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
543 QLocale loc(locString);
544 if (Q_LIKELY(loc.language() != QLocale::C)) {
545 auto trans =
new QTranslator(
this);
546 if (Q_LIKELY(trans->load(loc, filename, _prefix, _dir))) {
549 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << loc <<
"from" << ts.absoluteFilePath();
552 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << loc <<
"from" << ts.absoluteFilePath();
555 qCWarning(CUTELYST_CORE) <<
"Can not load translations for invalid locale string" << locString;
560 qCWarning(CUTELYST_CORE) <<
"Can not find translation files for" << filename <<
"in directory" << _dir;
563 qCWarning(CUTELYST_CORE) <<
"Can not load translations from not existing directory:" << _dir;
566 qCWarning(CUTELYST_CORE) <<
"Can not load translations for empty file name.";
574 QVector<QLocale> locales;
576 if (Q_LIKELY(!directory.isEmpty() && !filename.isEmpty())) {
577 const QDir dir(directory);
578 if (Q_LIKELY(dir.exists())) {
579 const auto dirs = dir.entryList(QDir::AllDirs);
580 if (Q_LIKELY(!dirs.empty())) {
581 locales.reserve(dirs.size());
582 for (
const QString &subDir : dirs) {
583 const QString relFn = subDir + QLatin1Char(
'/') + filename;
584 if (dir.exists(relFn)) {
585 const QLocale l(subDir);
586 if (Q_LIKELY(l.language() != QLocale::C)) {
587 auto trans =
new QTranslator(
this);
588 const QFileInfo fi(dir, relFn);
589 if (Q_LIKELY(trans->load(l, fi.baseName(), QString(), fi.absolutePath(), fi.suffix()))) {
592 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << l <<
"from" << fi.absoluteFilePath();
595 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << l <<
"from" << fi.absoluteFilePath();
598 qCWarning(CUTELYST_CORE) <<
"Can not load translations for invalid locale string:" << subDir;
604 qCWarning(CUTELYST_CORE) <<
"Can not find locale dirs under" << directory;
607 qCWarning(CUTELYST_CORE) <<
"Can not load translations from not existing directory:" << directory;
610 qCWarning(CUTELYST_CORE) <<
"Can not load translations for empty file name or directory name";
616 void Cutelyst::ApplicationPrivate::setupHome()
619 if (!config.contains(QLatin1String(
"home"))) {
620 config.insert(QStringLiteral(
"home"), QDir::currentPath());
623 if (!config.contains(QLatin1String(
"root"))) {
624 QDir home = config.value(QLatin1String(
"home")).toString();
625 config.insert(QStringLiteral(
"root"), home.absoluteFilePath(QLatin1String(
"root")));
629 void ApplicationPrivate::setupChildren(
const QObjectList &children)
632 for (QObject *child : children) {
633 auto controller = qobject_cast<Controller *>(child);
635 q->registerController(controller);
639 auto plugin = qobject_cast<Plugin *>(child);
641 q->registerPlugin(plugin);
645 auto view = qobject_cast<View *>(child);
647 q->registerView(view);
651 auto dispatchType = qobject_cast<DispatchType *>(child);
653 q->registerDispatcher(dispatchType);
659 void Cutelyst::ApplicationPrivate::logRequest(
Request *req)
661 QString path = req->path();
662 if (path.isEmpty()) {
663 path = QStringLiteral(
"/");
665 qCDebug(CUTELYST_REQUEST) << req->method() <<
"request for" << path <<
"from" << req->
addressString();
668 if (!params.isEmpty()) {
669 logRequestParameters(params, QLatin1String(
"Query Parameters are:"));
673 if (!params.isEmpty()) {
674 logRequestParameters(params, QLatin1String(
"Body Parameters are:"));
677 const auto uploads = req->
uploads();
678 if (!uploads.isEmpty()) {
679 logRequestUploads(uploads);
683 void Cutelyst::ApplicationPrivate::logRequestParameters(
const ParamsMultiMap ¶ms,
const QString &title)
685 QVector<QStringList> table;
686 auto it = params.constBegin();
687 while (it != params.constEnd()) {
688 table.append({ it.key(), it.value() });
691 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table, {
692 QLatin1String(
"Parameter"),
693 QLatin1String(
"Value"),
698 void Cutelyst::ApplicationPrivate::logRequestUploads(
const QVector<Cutelyst::Upload *> &uploads)
700 QVector<QStringList> table;
701 for (
Upload *upload : uploads) {
702 table.append({ upload->name(),
704 upload->contentType(),
705 QString::number(upload->size())
708 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table, {
709 QLatin1String(
"Parameter"),
710 QLatin1String(
"Filename"),
711 QLatin1String(
"Type"),
712 QLatin1String(
"Size"),
714 QLatin1String(
"File Uploads are:")).constData();
717 Component *ApplicationPrivate::createComponentPlugin(
const QString &name, QObject *parent,
const QString &directory)
721 QDir pluginsDir(directory);
722 QPluginLoader loader;
724 const auto plugins = pluginsDir.entryList(QDir::Files);
725 for (
const QString &fileName : plugins) {
726 loader.setFileName(pluginsDir.absoluteFilePath(fileName));
727 const QJsonObject json = loader.metaData()[QLatin1String(
"MetaData")].toObject();
728 if (json[QLatin1String(
"name")].toString() == name) {
729 QObject *plugin = loader.instance();
731 factory = qobject_cast<ComponentFactory *>(plugin);
733 qCCritical(CUTELYST_CORE) <<
"Could not create a factory for" << loader.fileName();
739 qCCritical(CUTELYST_CORE) <<
"Could not load plugin" << loader.fileName() << loader.errorString();
745 factories.insert(name, factory);
751 #include "moc_application.cpp"