5 #include "application_p.h"
12 #include "request_p.h"
13 #include "controller.h"
14 #include "controller_p.h"
16 #include "response_p.h"
17 #include "dispatchtype.h"
22 #include <QtCore/QDir>
23 #include <QtCore/QStringList>
24 #include <QtCore/QDataStream>
25 #include <QtCore/QCoreApplication>
26 #include <QtCore/QPluginLoader>
27 #include <QtCore/QTranslator>
28 #include <QtCore/QFileInfo>
29 #include <QtCore/QLocale>
31 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER,
"cutelyst.dispatcher", QtWarningMsg)
32 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_PATH,
"cutelyst.dispatcher.path", QtWarningMsg)
33 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_CHAINED,
"cutelyst.dispatcher.chained", QtWarningMsg)
34 Q_LOGGING_CATEGORY(CUTELYST_CONTROLLER,
"cutelyst.controller", QtWarningMsg)
35 Q_LOGGING_CATEGORY(CUTELYST_CORE,
"cutelyst.core", QtWarningMsg)
36 Q_LOGGING_CATEGORY(CUTELYST_ENGINE,
"cutelyst.engine", QtWarningMsg)
37 Q_LOGGING_CATEGORY(CUTELYST_UPLOAD,
"cutelyst.upload", QtWarningMsg)
38 Q_LOGGING_CATEGORY(CUTELYST_MULTIPART,
"cutelyst.multipart", QtWarningMsg)
39 Q_LOGGING_CATEGORY(CUTELYST_VIEW,
"cutelyst.view", QtWarningMsg)
40 Q_LOGGING_CATEGORY(CUTELYST_REQUEST,
"cutelyst.request", QtWarningMsg)
41 Q_LOGGING_CATEGORY(CUTELYST_RESPONSE,
"cutelyst.response", QtWarningMsg)
42 Q_LOGGING_CATEGORY(CUTELYST_STATS,
"cutelyst.stats", QtWarningMsg)
43 Q_LOGGING_CATEGORY(CUTELYST_COMPONENT,
"cutelyst.component", QtWarningMsg)
49 d_ptr(new ApplicationPrivate)
55 qRegisterMetaType<ParamsMultiMap>();
56 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
57 qRegisterMetaTypeStreamOperators<ParamsMultiMap>(
"ParamsMultiMap");
65 Application::~Application()
72 qCDebug(CUTELYST_CORE) <<
"Default Application::init called on pid:" << QCoreApplication::applicationPid();
78 qCDebug(CUTELYST_CORE) <<
"Default Application::postFork called on pid:" << QCoreApplication::applicationPid();
91 d->headers.setHeader(QStringLiteral(
"X_CUTELYST"), QStringLiteral(VERSION));
97 if (d->plugins.contains(
plugin)) {
100 d->plugins.append(
plugin);
107 const auto name = QString::fromLatin1(controller->metaObject()->className());
108 if (d->controllersHash.contains(name)) {
111 d->controllersHash.insert(name, controller);
112 d->controllers.append(controller);
119 if (d->views.contains(
view->
name())) {
120 qCWarning(CUTELYST_CORE) <<
"Not registering View." <<
view->metaObject()->className()
121 <<
"There is already a view with this name:" <<
view->
name();
141 auto it = d->factories.constFind(name);
142 if (it != d->factories.constEnd()) {
151 const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv(
"CUTELYST_PLUGINS_DIR").split(
';');
152 for (
const QByteArray &dir : dirs) {
153 Component *component = d->createComponentPlugin(name, parent, QString::fromLocal8Bit(dir));
158 qCDebug(CUTELYST_CORE) <<
"Did not find plugin" << name <<
"on" << dirs <<
"for" << parent;
171 return d->controllers;
177 return d->views.value(name);
183 return d->views.value(name);
189 auto it = d->config.constFind(key);
190 if (it != d->config.constEnd()) {
199 return d->dispatcher;
205 return d->dispatcher->dispatchers();
222 QDir home =
config(QStringLiteral(
"home")).toString();
223 return home.absoluteFilePath(path);
228 QDir home = config(QStringLiteral(
"home")).toString();
229 return home.absoluteFilePath(path.join(u
'/'));
247 d->config.insert(key, value);
259 d->useStats = CUTELYST_STATS().isDebugEnabled();
268 d->setupChildren(children());
272 QVector<QStringList> tablePlugins;
273 const auto plugins = d->plugins;
275 if (
plugin->objectName().isEmpty()) {
276 plugin->setObjectName(QString::fromLatin1(
plugin->metaObject()->className()));
278 tablePlugins.append({
plugin->objectName() });
283 if (zeroCore && !tablePlugins.isEmpty()) {
284 qCDebug(CUTELYST_CORE) << Utils::buildTable(tablePlugins, QStringList(),
285 QLatin1String(
"Loaded plugins:")).constData();
289 QVector<QStringList> tableDataHandlers;
290 tableDataHandlers.append({ QLatin1String(
"application/x-www-form-urlencoded") });
291 tableDataHandlers.append({ QLatin1String(
"application/json") });
292 tableDataHandlers.append({ QLatin1String(
"multipart/form-data") });
293 qCDebug(CUTELYST_CORE) << Utils::buildTable(tableDataHandlers, QStringList(),
294 QLatin1String(
"Loaded Request Data Handlers:")).constData();
296 qCDebug(CUTELYST_CORE) <<
"Loaded dispatcher" << QString::fromLatin1(d->dispatcher->metaObject()->className());
297 qCDebug(CUTELYST_CORE) <<
"Using engine" << QString::fromLatin1(d->engine->metaObject()->className());
300 QString home = d->config.value(QLatin1String(
"home")).toString();
301 if (home.isEmpty()) {
303 qCDebug(CUTELYST_CORE) <<
"Couldn't find home";
306 QFileInfo homeInfo(home);
307 if (homeInfo.isDir()) {
309 qCDebug(CUTELYST_CORE) <<
"Found home" << home;
313 qCDebug(CUTELYST_CORE) <<
"Home" << home <<
"doesn't exist";
318 QVector<QStringList> table;
319 QStringList controllerNames = d->controllersHash.keys();
320 controllerNames.sort();
321 for (
const QString &controller : controllerNames) {
322 table.append({ controller, QLatin1String(
"Controller")});
325 const auto views = d->views;
328 const QString className = QString::fromLatin1(
view->metaObject()->className()) + QLatin1String(
"->execute");
331 table.append({
view->
reverse(), QLatin1String(
"View")});
334 if (zeroCore && !table.isEmpty()) {
335 qCDebug(CUTELYST_CORE) << Utils::buildTable(table, {
336 QLatin1String(
"Class"), QLatin1String(
"Type")
338 QLatin1String(
"Loaded components:")).constData();
343 controller->d_ptr->init(
this, d->dispatcher);
346 d->dispatcher->setupActions(d->controllers, d->dispatchers, d->engine->workerCore() == 0);
349 qCInfo(CUTELYST_CORE) << qPrintable(QString::fromLatin1(
"%1 powered by Cutelyst %2, Qt %3.")
350 .arg(QCoreApplication::applicationName(),
352 QLatin1String(qVersion())));
369 auto priv =
new ContextPrivate(
this,
engine, d->dispatcher, d->plugins);
373 priv->engineRequest = request;
374 priv->response =
new Response(d->headers, request);
375 priv->request =
new Request(request);
378 priv->stats =
new Stats(request);
382 bool skipMethod =
false;
386 static bool log = CUTELYST_REQUEST().isEnabled(QtDebugMsg);
388 d->logRequest(priv->request);
391 d->dispatcher->prepareAction(c);
395 d->dispatcher->dispatch(c);
397 if (request->
status & EngineRequest::Async) {
417 if (!controller->postFork(
this)) {
430 Q_ASSERT_X(translator,
"add translator to application",
"invalid QTranslator object");
431 auto it = d->translators.find(locale);
432 if (it != d->translators.end()) {
433 it.value().prepend(translator);
435 d->translators.insert(locale, QVector<QTranslator*>(1, translator));
447 Q_ASSERT_X(!translators.empty(),
"add translators to application",
"empty translators vector");
448 auto transIt = d->translators.find(locale);
449 if (transIt != d->translators.end()) {
450 for (
auto it = translators.crbegin(); it != translators.crend(); ++it) {
451 transIt.value().prepend(*it);
454 d->translators.insert(locale, translators);
458 static void replacePercentN(QString *result,
int n)
463 while ((percentPos = result->indexOf(u
'%', percentPos + len)) != -1) {
466 if (result->at(percentPos + len) == u
'L') {
468 fmt = QStringLiteral(
"%L1");
470 fmt = QStringLiteral(
"%1");
472 if (result->at(percentPos + len) == u
'n') {
475 result->replace(percentPos, len, fmt);
482 QString
Application::translate(
const QLocale &locale,
const char *context,
const char *sourceText,
const char *disambiguation,
int n)
const
492 const QVector<QTranslator*> translators = d->translators.value(locale);
493 if (translators.empty()) {
494 result = QString::fromUtf8(sourceText);
495 replacePercentN(&result, n);
499 for (QTranslator *translator : translators) {
500 result = translator->translate(context, sourceText, disambiguation, n);
501 if (!result.isEmpty()) {
506 if (result.isEmpty()) {
507 result = QString::fromUtf8(sourceText);
510 replacePercentN(&result, n);
521 QVector<QLocale> locales;
523 if (Q_LIKELY(!filename.isEmpty())) {
524 const QString _dir = directory.isEmpty() ? QStringLiteral(I18NDIR) : directory;
525 const QDir i18nDir(_dir);
526 if (Q_LIKELY(i18nDir.exists())) {
527 const QString _prefix = prefix.isEmpty() ? QStringLiteral(
".") : prefix;
528 const QString _suffix = suffix.isEmpty() ? QStringLiteral(
".qm") : suffix;
529 const QStringList namesFilter = QStringList({filename + _prefix + u
'*' + _suffix});
531 const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
532 if (Q_LIKELY(!tsFiles.empty())) {
533 locales.reserve(tsFiles.size());
534 for (
const QFileInfo &ts : tsFiles) {
535 const QString fn = ts.fileName();
536 const int prefIdx = fn.indexOf(_prefix);
537 const QString locString = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
538 QLocale loc(locString);
539 if (Q_LIKELY(loc.language() != QLocale::C)) {
540 auto trans =
new QTranslator(
this);
541 if (Q_LIKELY(trans->load(loc, filename, _prefix, _dir))) {
544 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << loc <<
"from" << ts.absoluteFilePath();
547 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << loc <<
"from" << ts.absoluteFilePath();
550 qCWarning(CUTELYST_CORE) <<
"Can not load translations for invalid locale string" << locString;
555 qCWarning(CUTELYST_CORE) <<
"Can not find translation files for" << filename <<
"in directory" << _dir;
558 qCWarning(CUTELYST_CORE) <<
"Can not load translations from not existing directory:" << _dir;
561 qCWarning(CUTELYST_CORE) <<
"Can not load translations for empty file name.";
569 QVector<QLocale> locales;
571 if (Q_LIKELY(!directory.isEmpty() && !filename.isEmpty())) {
572 const QDir dir(directory);
573 if (Q_LIKELY(dir.exists())) {
574 const auto dirs = dir.entryList(QDir::AllDirs);
575 if (Q_LIKELY(!dirs.empty())) {
576 locales.reserve(dirs.size());
577 for (
const QString &subDir : dirs) {
578 const QString relFn = subDir + u
'/' + filename;
579 if (dir.exists(relFn)) {
580 const QLocale l(subDir);
581 if (Q_LIKELY(l.language() != QLocale::C)) {
582 auto trans =
new QTranslator(
this);
583 const QFileInfo fi(dir, relFn);
584 if (Q_LIKELY(trans->load(l, fi.baseName(), QString(), fi.absolutePath(), fi.suffix()))) {
587 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << l <<
"from" << fi.absoluteFilePath();
590 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << l <<
"from" << fi.absoluteFilePath();
593 qCWarning(CUTELYST_CORE) <<
"Can not load translations for invalid locale string:" << subDir;
599 qCWarning(CUTELYST_CORE) <<
"Can not find locale dirs under" << directory;
602 qCWarning(CUTELYST_CORE) <<
"Can not load translations from not existing directory:" << directory;
605 qCWarning(CUTELYST_CORE) <<
"Can not load translations for empty file name or directory name";
611 void Cutelyst::ApplicationPrivate::setupHome()
614 if (!config.contains(QLatin1String(
"home"))) {
615 config.insert(QStringLiteral(
"home"), QDir::currentPath());
618 if (!config.contains(QLatin1String(
"root"))) {
619 QDir home = config.value(QLatin1String(
"home")).toString();
620 config.insert(QStringLiteral(
"root"), home.absoluteFilePath(QLatin1String(
"root")));
624 void ApplicationPrivate::setupChildren(
const QObjectList &children)
627 for (QObject *child : children) {
628 auto controller = qobject_cast<Controller *>(child);
630 q->registerController(controller);
634 auto plugin = qobject_cast<Plugin *>(child);
636 q->registerPlugin(plugin);
640 auto view = qobject_cast<View *>(child);
642 q->registerView(view);
646 auto dispatchType = qobject_cast<DispatchType *>(child);
648 q->registerDispatcher(dispatchType);
654 void Cutelyst::ApplicationPrivate::logRequest(
Request *req)
656 QString path = req->path();
657 if (path.isEmpty()) {
658 path = QStringLiteral(
"/");
660 qCDebug(CUTELYST_REQUEST) << req->method() <<
"request for" << path <<
"from" << req->
addressString();
663 if (!params.isEmpty()) {
664 logRequestParameters(params, QLatin1String(
"Query Parameters are:"));
668 if (!params.isEmpty()) {
669 logRequestParameters(params, QLatin1String(
"Body Parameters are:"));
672 const auto uploads = req->
uploads();
673 if (!uploads.isEmpty()) {
674 logRequestUploads(uploads);
678 void Cutelyst::ApplicationPrivate::logRequestParameters(
const ParamsMultiMap ¶ms,
const QString &title)
680 QVector<QStringList> table;
681 auto it = params.constBegin();
682 while (it != params.constEnd()) {
683 table.append({ it.key(), it.value() });
686 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table, {
687 QLatin1String(
"Parameter"),
688 QLatin1String(
"Value"),
693 void Cutelyst::ApplicationPrivate::logRequestUploads(
const QVector<Cutelyst::Upload *> &uploads)
695 QVector<QStringList> table;
696 for (
Upload *upload : uploads) {
697 table.append({ upload->name(),
699 upload->contentType(),
700 QString::number(upload->size())
703 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table, {
704 QLatin1String(
"Parameter"),
705 QLatin1String(
"Filename"),
706 QLatin1String(
"Type"),
707 QLatin1String(
"Size"),
709 QLatin1String(
"File Uploads are:")).constData();
712 Component *ApplicationPrivate::createComponentPlugin(
const QString &name, QObject *parent,
const QString &directory)
716 QDir pluginsDir(directory);
717 QPluginLoader loader;
719 const auto plugins = pluginsDir.entryList(QDir::Files);
720 for (
const QString &fileName : plugins) {
721 loader.setFileName(pluginsDir.absoluteFilePath(fileName));
722 const QJsonObject json = loader.metaData()[QLatin1String(
"MetaData")].toObject();
723 if (json[QLatin1String(
"name")].toString() == name) {
724 QObject *plugin = loader.instance();
726 factory = qobject_cast<ComponentFactory *>(plugin);
728 qCCritical(CUTELYST_CORE) <<
"Could not create a factory for" << loader.fileName();
734 qCCritical(CUTELYST_CORE) <<
"Could not load plugin" << loader.fileName() << loader.errorString();
740 factories.insert(name, factory);
746 #include "moc_application.cpp"
The Cutelyst Application.
Engine * engine() const noexcept
QVector< Controller * > controllers() const noexcept
void afterDispatch(Cutelyst::Context *c)
bool setup(Engine *engine)
Called by the Engine to setup the internal data.
void handleRequest(Cutelyst::EngineRequest *request)
Called by the Engine to handle a new Request object.
void beforePrepareAction(Cutelyst::Context *c, bool *skipMethod)
Application(QObject *parent=nullptr)
void setConfig(const QString &key, const QVariant &value)
QVariantMap config() const noexcept
Dispatcher * dispatcher() const noexcept
bool registerPlugin(Plugin *plugin)
bool registerController(Controller *controller)
bool registerDispatcher(DispatchType *dispatcher)
QString pathTo(const QString &path) const
Headers & defaultHeaders() noexcept
QString translate(const QLocale &locale, const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
bool inited() const noexcept
void beforeDispatch(Cutelyst::Context *c)
void preForked(Cutelyst::Application *app)
QVector< Plugin * > plugins() const noexcept
bool enginePostFork()
Called by the Engine once post fork happened.
void addTranslator(const QLocale &locale, QTranslator *translator)
bool registerView(View *view)
void addTranslators(const QLocale &locale, const QVector< QTranslator * > &translators)
QVector< DispatchType * > dispatchers() const noexcept
static const char * cutelystVersion() noexcept
QVector< QLocale > loadTranslationsFromDirs(const QString &directory, const QString &filename)
T plugin()
Returns the registered plugin that casts to the template type T.
void addXCutelystVersionHeader()
Component * createComponentPlugin(const QString &name, QObject *parent=nullptr)
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory=QString(), const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
View * view(const QString &name) const
void loadTranslations(const QString &filename, const QString &directory=QString(), const QString &prefix=QString(), const QString &suffix=QString())
void postForked(Cutelyst::Application *app)
virtual Component * createComponent(QObject *parent=nullptr)=0
The Cutelyst Component base class.
void setReverse(const QString &reverse)
Cutelyst Controller base class
Context * context
The Cutelyst::Context of this request.
Status status
Connection status.
int workerCore() const
Each worker process migth have a number of worker cores (threads), a single process with two worker t...
QVariantMap config(const QString &entity) const
user configuration for the application
QString addressString() const
QVector< Upload * > uploads() const
ParamsMultiMap bodyParameters() const
ParamsMultiMap queryParameters() const
Cutelyst Upload handles file upload request
Cutelyst View abstract view component
The Cutelyst namespace holds all public Cutelyst API.
QMultiMap< QString, QString > ParamsMultiMap