5#include "application_p.h"
10#include "controller_p.h"
11#include "dispatchtype.h"
16#include "response_p.h"
21#include <QJsonDocument>
22#include <QtCore/QCoreApplication>
23#include <QtCore/QDataStream>
25#include <QtCore/QFileInfo>
26#include <QtCore/QLocale>
27#include <QtCore/QPluginLoader>
28#include <QtCore/QStringList>
29#include <QtCore/QTranslator>
31Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER,
"cutelyst.dispatcher", QtWarningMsg)
32Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_PATH,
"cutelyst.dispatcher.path", QtWarningMsg)
33Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_CHAINED,
"cutelyst.dispatcher.chained", QtWarningMsg)
34Q_LOGGING_CATEGORY(CUTELYST_CONTROLLER,
"cutelyst.controller", QtWarningMsg)
35Q_LOGGING_CATEGORY(CUTELYST_CORE,
"cutelyst.core", QtWarningMsg)
36Q_LOGGING_CATEGORY(CUTELYST_ENGINE,
"cutelyst.engine", QtWarningMsg)
37Q_LOGGING_CATEGORY(CUTELYST_UPLOAD,
"cutelyst.upload", QtWarningMsg)
38Q_LOGGING_CATEGORY(CUTELYST_MULTIPART,
"cutelyst.multipart", QtWarningMsg)
39Q_LOGGING_CATEGORY(CUTELYST_VIEW,
"cutelyst.view", QtWarningMsg)
40Q_LOGGING_CATEGORY(CUTELYST_REQUEST,
"cutelyst.request", QtWarningMsg)
41Q_LOGGING_CATEGORY(CUTELYST_RESPONSE,
"cutelyst.response", QtWarningMsg)
42Q_LOGGING_CATEGORY(CUTELYST_STATS,
"cutelyst.stats", QtWarningMsg)
43Q_LOGGING_CATEGORY(CUTELYST_COMPONENT,
"cutelyst.component", QtWarningMsg)
49 , d_ptr(new ApplicationPrivate)
55 qRegisterMetaType<ParamsMultiMap>();
69 qCDebug(CUTELYST_CORE) <<
"Default Application::init called on pid:"
70 << QCoreApplication::applicationPid();
76 qCDebug(CUTELYST_CORE) <<
"Default Application::postFork called on pid:"
77 << QCoreApplication::applicationPid();
90 d->headers.setHeader(
"X-Cutelyst"_qba, QByteArrayLiteral(CUTELYST_VERSION));
96 if (d->plugins.contains(
plugin)) {
106 const auto name = QString::fromLatin1(controller->metaObject()->className());
107 if (d->controllersHash.contains(name)) {
110 d->controllersHash.insert(name, controller);
111 d->controllers.append(controller);
118 if (d->views.contains(
view->
name())) {
119 qCWarning(CUTELYST_CORE) <<
"Not registering View." <<
view->metaObject()->className()
120 <<
"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)} +
152 qgetenv(
"CUTELYST_PLUGINS_DIR").split(
';');
153 for (
const QByteArray &dir : dirs) {
154 Component *component = d->createComponentPlugin(name, parent, QString::fromLocal8Bit(dir));
159 qCDebug(CUTELYST_CORE) <<
"Did not find plugin" << name <<
"on" << dirs <<
"for" << parent;
166 return CUTELYST_VERSION;
172 return d->controllers;
178 return d->views.value(name);
184 auto it = d->config.constFind(key);
185 if (it != d->config.constEnd()) {
194 return d->dispatcher;
200 return d->dispatcher->dispatchers();
217 QDir home =
config(u
"home"_qs).toString();
218 return home.absoluteFilePath(path);
223 QDir home = config(u
"home"_qs).toString();
224 return home.absoluteFilePath(path.join(u
'/'));
242 d->config.insert(key, value);
254 d->useStats = CUTELYST_STATS().isDebugEnabled();
263 d->setupChildren(children());
267 QVector<QStringList> tablePlugins;
268 const auto plugins = d->plugins;
270 if (
plugin->objectName().isEmpty()) {
271 plugin->setObjectName(QString::fromLatin1(
plugin->metaObject()->className()));
273 tablePlugins.append({
plugin->objectName()});
278 if (zeroCore && !tablePlugins.isEmpty()) {
279 qCDebug(CUTELYST_CORE)
280 << Utils::buildTable(tablePlugins, QStringList(), QLatin1String(
"Loaded plugins:"))
285 QVector<QStringList> tableDataHandlers;
286 tableDataHandlers.append({u
"application/x-www-form-urlencoded"_qs});
287 tableDataHandlers.append({u
"application/json"_qs});
288 tableDataHandlers.append({u
"multipart/form-data"_qs});
289 qCDebug(CUTELYST_CORE)
290 << Utils::buildTable(tableDataHandlers,
292 QLatin1String(
"Loaded Request Data Handlers:"))
295 qCDebug(CUTELYST_CORE) <<
"Loaded dispatcher"
296 << QString::fromLatin1(d->dispatcher->metaObject()->className());
297 qCDebug(CUTELYST_CORE)
298 <<
"Using engine" << QString::fromLatin1(d->engine->metaObject()->className());
301 QString home = d->config.value(u
"home"_qs).toString();
302 if (home.isEmpty()) {
304 qCDebug(CUTELYST_CORE) <<
"Couldn't find home";
307 QFileInfo homeInfo(home);
308 if (homeInfo.isDir()) {
310 qCDebug(CUTELYST_CORE) <<
"Found home" << home;
314 qCDebug(CUTELYST_CORE) <<
"Home" << home <<
"doesn't exist";
319 QVector<QStringList> table;
320 QStringList controllerNames = d->controllersHash.keys();
321 controllerNames.sort();
322 for (
const QString &controller : controllerNames) {
323 table.append({controller, QLatin1String(
"Controller")});
326 const auto views = d->views;
329 const QString className = QString::fromLatin1(
view->metaObject()->className()) +
330 QLatin1String(
"->execute");
333 table.append({
view->
reverse(), QLatin1String(
"View")});
336 if (zeroCore && !table.isEmpty()) {
337 qCDebug(CUTELYST_CORE)
338 << Utils::buildTable(table,
339 {QLatin1String(
"Class"), QLatin1String(
"Type")},
340 QLatin1String(
"Loaded components:"))
346 controller->d_ptr->init(
this, d->dispatcher);
349 d->dispatcher->setupActions(d->controllers, d->dispatchers, d->engine->workerCore() == 0);
352 qCInfo(CUTELYST_CORE) << qPrintable(
353 QString::fromLatin1(
"%1 powered by Cutelyst %2, Qt %3.")
354 .arg(QCoreApplication::applicationName(),
356 QLatin1String(qVersion())));
373 auto priv =
new ContextPrivate(
this,
engine, d->dispatcher, d->plugins);
377 priv->engineRequest = request;
378 priv->response =
new Response(d->headers, request);
379 priv->request =
new Request(request);
380 priv->locale = d->defaultLocale;
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);
463static void replacePercentN(QString *result,
int n)
468 while ((percentPos = result->indexOf(u
'%', percentPos + len)) != -1) {
471 if (result->at(percentPos + len) == u
'L') {
473 fmt = QStringLiteral(
"%L1");
475 fmt = QStringLiteral(
"%1");
477 if (result->at(percentPos + len) == u
'n') {
480 result->replace(percentPos, len, fmt);
489 const char *sourceText,
490 const char *disambiguation,
501 const QVector<QTranslator *> translators = d->translators.value(locale);
502 if (translators.empty()) {
503 result = QString::fromUtf8(sourceText);
504 replacePercentN(&result, n);
508 for (QTranslator *translator : translators) {
509 result = translator->translate(context, sourceText, disambiguation, n);
510 if (!result.isEmpty()) {
515 if (result.isEmpty()) {
516 result = QString::fromUtf8(sourceText);
519 replacePercentN(&result, n);
524 const QString &directory,
525 const QString &prefix,
526 const QString &suffix)
532 const QString &directory,
533 const QString &prefix,
534 const QString &suffix)
536 QVector<QLocale> locales;
538 if (Q_LIKELY(!filename.isEmpty())) {
539 const QString _dir = directory.isEmpty() ? QStringLiteral(CUTELYST_I18N_DIR) : directory;
540 const QDir i18nDir(_dir);
541 if (Q_LIKELY(i18nDir.exists())) {
542 const QString _prefix = prefix.isEmpty() ? QStringLiteral(
".") : prefix;
543 const QString _suffix = suffix.isEmpty() ? QStringLiteral(
".qm") : suffix;
544 const QStringList namesFilter = QStringList({filename + _prefix + u
'*' + _suffix});
546 const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
547 if (Q_LIKELY(!tsFiles.empty())) {
548 locales.reserve(tsFiles.size());
549 for (
const QFileInfo &ts : tsFiles) {
550 const QString fn = ts.fileName();
551 const int prefIdx = fn.indexOf(_prefix);
552 const QString locString =
553 fn.mid(prefIdx + _prefix.length(),
554 fn.length() - prefIdx - _suffix.length() - _prefix.length());
555 QLocale loc(locString);
556 if (Q_LIKELY(loc.language() != QLocale::C)) {
557 auto trans =
new QTranslator(
this);
558 if (Q_LIKELY(trans->load(loc, filename, _prefix, _dir))) {
561 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << loc <<
"from"
562 << ts.absoluteFilePath();
565 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << loc
566 <<
"from" << ts.absoluteFilePath();
569 qCWarning(CUTELYST_CORE)
570 <<
"Can not load translations for invalid locale string" << locString;
575 qCWarning(CUTELYST_CORE)
576 <<
"Can not find translation files for" << filename <<
"in directory" << _dir;
579 qCWarning(CUTELYST_CORE)
580 <<
"Can not load translations from not existing directory:" << _dir;
583 qCWarning(CUTELYST_CORE) <<
"Can not load translations for empty file name.";
590 const QString &filename)
592 QVector<QLocale> locales;
594 if (Q_LIKELY(!directory.isEmpty() && !filename.isEmpty())) {
595 const QDir dir(directory);
596 if (Q_LIKELY(dir.exists())) {
597 const auto dirs = dir.entryList(QDir::AllDirs);
598 if (Q_LIKELY(!dirs.empty())) {
599 locales.reserve(dirs.size());
600 for (
const QString &subDir : dirs) {
601 const QString relFn = subDir + u
'/' + filename;
602 if (dir.exists(relFn)) {
603 const QLocale l(subDir);
604 if (Q_LIKELY(l.language() != QLocale::C)) {
605 auto trans =
new QTranslator(
this);
606 const QFileInfo fi(dir, relFn);
607 if (Q_LIKELY(trans->load(
608 l, fi.baseName(), QString(), fi.absolutePath(), fi.suffix()))) {
611 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << l <<
"from"
612 << fi.absoluteFilePath();
615 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << l
616 <<
"from" << fi.absoluteFilePath();
619 qCWarning(CUTELYST_CORE)
620 <<
"Can not load translations for invalid locale string:" << subDir;
626 qCWarning(CUTELYST_CORE) <<
"Can not find locale dirs under" << directory;
629 qCWarning(CUTELYST_CORE)
630 <<
"Can not load translations from not existing directory:" << directory;
633 qCWarning(CUTELYST_CORE)
634 <<
"Can not load translations for empty file name or directory name";
643 return d->defaultLocale;
649 d->defaultLocale = locale;
652void Cutelyst::ApplicationPrivate::setupHome()
655 if (!config.contains(QLatin1String(
"home"))) {
656 config.insert(QStringLiteral(
"home"), QDir::currentPath());
659 if (!config.contains(QLatin1String(
"root"))) {
660 QDir home = config.value(QLatin1String(
"home")).toString();
661 config.insert(QStringLiteral(
"root"), home.absoluteFilePath(QLatin1String(
"root")));
665void ApplicationPrivate::setupChildren(
const QObjectList &children)
668 for (QObject *child : children) {
669 auto controller = qobject_cast<Controller *>(child);
671 q->registerController(controller);
675 auto plugin = qobject_cast<Plugin *>(child);
677 q->registerPlugin(plugin);
681 auto view = qobject_cast<View *>(child);
683 q->registerView(view);
687 auto dispatchType = qobject_cast<DispatchType *>(child);
689 q->registerDispatcher(dispatchType);
695void Cutelyst::ApplicationPrivate::logRequest(
Request *req)
697 QString path = req->path();
698 if (path.isEmpty()) {
699 path = QStringLiteral(
"/");
701 qCDebug(CUTELYST_REQUEST) << req->method() <<
"request for" << path <<
"from"
705 if (!params.isEmpty()) {
706 logRequestParameters(params, QLatin1String(
"Query Parameters are:"));
710 if (!params.isEmpty()) {
711 logRequestParameters(params, QLatin1String(
"Body Parameters are:"));
714 const auto bodyData = req->bodyData();
715 if (bodyData.typeId() == QMetaType::QJsonDocument) {
716 const auto doc = bodyData.toJsonDocument();
717 qCDebug(CUTELYST_REQUEST).noquote() <<
"JSON body:\n"
718 << doc.toJson(QJsonDocument::Indented);
721 const auto uploads = req->
uploads();
722 if (!uploads.isEmpty()) {
723 logRequestUploads(uploads);
727void Cutelyst::ApplicationPrivate::logRequestParameters(
const ParamsMultiMap ¶ms,
728 const QString &title)
730 QVector<QStringList> table;
731 auto it = params.constBegin();
732 while (it != params.constEnd()) {
733 table.append({it.key(), it.value()});
736 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table,
738 QLatin1String(
"Parameter"),
739 QLatin1String(
"Value"),
745void Cutelyst::ApplicationPrivate::logRequestUploads(
const QVector<Cutelyst::Upload *> &uploads)
747 QVector<QStringList> table;
748 for (
Upload *upload : uploads) {
749 table.append({upload->name(),
751 QString::fromLatin1(upload->contentType()),
752 QString::number(upload->size())});
754 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table,
756 QLatin1String(
"Parameter"),
757 QLatin1String(
"Filename"),
758 QLatin1String(
"Type"),
759 QLatin1String(
"Size"),
761 QLatin1String(
"File Uploads are:"))
765Component *ApplicationPrivate::createComponentPlugin(
const QString &name,
767 const QString &directory)
772 auto matchMetadata = [name](
const QJsonObject &metadata) {
773 const QJsonObject json = metadata[u
"MetaData"].toObject();
774 qCDebug(CUTELYST_CORE) <<
"Found plugin metadata" << json;
775 return json[u
"name"].toString() == name;
778 auto createComponent = [name, parent, &factory](QObject *plugin) ->
Component * {
779 factory = qobject_cast<ComponentFactory *>(plugin);
787 const QList<QStaticPlugin> &staticPlugins = QPluginLoader::staticPlugins();
788 for (
const QStaticPlugin &plugin : staticPlugins) {
789 if (matchMetadata(plugin.metaData())) {
790 component = createComponent(plugin.instance());
795 qCCritical(CUTELYST_CORE)
796 <<
"Could not create a component for static plugin" << plugin.metaData();
800 if (factory && component) {
801 factories.insert(name, factory);
805 QDir pluginsDir(directory);
806 QPluginLoader loader;
807 const auto plugins = pluginsDir.entryList(QDir::Files);
808 for (
const QString &fileName : plugins) {
809 loader.setFileName(pluginsDir.absoluteFilePath(fileName));
811 if (matchMetadata(loader.metaData())) {
812 component = createComponent(loader.instance());
817 qCCritical(CUTELYST_CORE)
818 <<
"Could not create a component for plugin" << fileName << loader.metaData();
823 factories.insert(name, factory);
829#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)
void handleRequest(Cutelyst::EngineRequest *request)
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
View * view(QStringView name={}) const
bool inited() const noexcept
void beforeDispatch(Cutelyst::Context *c)
void preForked(Cutelyst::Application *app)
void setDefaultLocale(const QLocale &locale)
QVector< Plugin * > plugins() const noexcept
void addTranslator(const QLocale &locale, QTranslator *translator)
QLocale defaultLocale() const noexcept
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)
void addXCutelystVersionHeader()
Component * createComponentPlugin(const QString &name, QObject *parent=nullptr)
void loadTranslations(const QString &filename, const QString &directory={}, const QString &prefix={}, const QString &suffix={})
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory=QString(), const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
void postForked(Cutelyst::Application *app)
virtual Component * createComponent(QObject *parent=nullptr)=0
The Cutelyst Component base class.
void setReverse(const QString &reverse)
QString reverse() const noexcept
QString name() const noexcept
Cutelyst Controller base class.
Abstract class to described a dispatch type.
QVariantMap config(const QString &entity) const
Base class for Cutelyst Plugins.
QString addressString() const
QVector< Upload * > uploads() const
ParamsMultiMap bodyParameters() const
ParamsMultiMap queryParameters() const
Cutelyst Upload handles file upload requests.
Abstract View component for Cutelyst.
QMultiMap< QString, QString > ParamsMultiMap
The Cutelyst namespace holds all public Cutelyst API.