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)
50 , d_ptr(new ApplicationPrivate)
56 qRegisterMetaType<ParamsMultiMap>();
70 qCDebug(CUTELYST_CORE) <<
"Default Application::init called on pid:"
77 qCDebug(CUTELYST_CORE) <<
"Default Application::postFork called on pid:"
91 d->headers.setHeader(
"X-Cutelyst"_ba, QByteArrayLiteral(CUTELYST_VERSION));
97 if (d->plugins.contains(
plugin)) {
100 d->plugins.append(
plugin);
108 if (d->controllersHash.contains(name)) {
111 d->controllersHash.insert(name, controller);
112 d->controllers.append(controller);
119 if (d->views.contains(
view->
name())) {
121 <<
"There is already a view with this name:" <<
view->
name();
142 auto it = d->factories.constFind(name);
143 if (it != d->factories.constEnd()) {
153 qgetenv(
"CUTELYST_PLUGINS_DIR").split(
';');
160 qCDebug(CUTELYST_CORE) <<
"Did not find plugin" << name <<
"on" << dirs <<
"for" <<
parent;
167 return CUTELYST_VERSION;
173 return d->controllers;
179 return d->views.value(name);
185 auto it = d->config.constFind(key);
186 if (it != d->config.constEnd()) {
195 return d->dispatcher;
201 return d->dispatcher->dispatchers();
224 QDir home = config(u
"home"_s).toString();
243 d->config.insert(key, value);
255 d->useStats = CUTELYST_STATS().isDebugEnabled();
269 for (
Plugin *pluginItem : std::as_const(d->plugins)) {
270 if (pluginItem->objectName().isEmpty()) {
271 pluginItem->setObjectName(
274 tablePlugins.
append({pluginItem->objectName()});
276 pluginItem->setup(
this);
279 if (zeroCore && !tablePlugins.
isEmpty()) {
280 qCDebug(CUTELYST_CORE)
281 << Utils::buildTable(tablePlugins, {}, u
"Loaded plugins:"_s).constData();
286 tableDataHandlers.
append({u
"application/x-www-form-urlencoded"_s});
287 tableDataHandlers.
append({u
"application/json"_s});
288 tableDataHandlers.
append({u
"multipart/form-data"_s});
289 qCDebug(CUTELYST_CORE)
290 << Utils::buildTable(tableDataHandlers, {}, u
"Loaded Request Data Handlers:"_s)
293 qCDebug(CUTELYST_CORE) <<
"Loaded dispatcher"
295 qCDebug(CUTELYST_CORE)
299 QString home = d->config.value(u
"home"_s).toString();
302 qCDebug(CUTELYST_CORE) <<
"Couldn't find home";
306 if (homeInfo.
isDir()) {
308 qCDebug(CUTELYST_CORE) <<
"Found home" << home;
312 qCDebug(CUTELYST_CORE) <<
"Home" << home <<
"doesn't exist";
318 QStringList controllerNames = d->controllersHash.keys();
319 controllerNames.
sort();
320 for (
const QString &controller : std::as_const(controllerNames)) {
321 table.
append({controller, u
"Controller"_s});
324 for (
View *viewItem : std::as_const(d->views)) {
325 if (viewItem->reverse().isEmpty()) {
328 viewItem->setReverse(className);
330 table.
append({viewItem->reverse(), u
"View"_s});
333 if (zeroCore && !table.
isEmpty()) {
334 qCDebug(CUTELYST_CORE) << Utils::buildTable(table,
339 u
"Loaded components:"_s)
343 for (
Controller *controller : std::as_const(d->controllers)) {
344 controller->d_ptr->init(
this, d->dispatcher);
347 d->dispatcher->setupActions(d->controllers, d->dispatchers, d->engine->workerCore() == 0);
350 qCInfo(CUTELYST_CORE) << qPrintable(u
"%1 powered by Cutelyst %2, Qt %3."_s.arg(
368 auto priv =
new ContextPrivate(
this, d->engine, d->dispatcher, d->plugins);
372 priv->engineRequest = request;
373 priv->response =
new Response(d->headers, request);
374 priv->request =
new Request(request);
375 priv->locale = d->defaultLocale;
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) {
415 if (!std::ranges::all_of(d->controllers, [
this](
Controller *controller) {
416 return controller->postFork(this);
429 Q_ASSERT_X(translator,
"add translator to application",
"invalid QTranslator object");
430 auto it = d->translators.find(locale);
431 if (it != d->translators.end()) {
432 it.value().prepend(translator);
446 Q_ASSERT_X(!translators.
empty(),
"add translators to application",
"empty translators vector");
447 auto transIt = d->translators.find(locale);
448 if (transIt != d->translators.end()) {
449 for (
auto it = translators.
crbegin(); it != translators.
crend(); ++it) {
450 transIt.value().prepend(*it);
453 d->translators.insert(locale, translators);
458void replacePercentN(
QString *result,
int n)
463 while ((percentPos = result->
indexOf(u
'%', percentPos + len)) != -1) {
466 if (result->
at(percentPos + len) == u
'L') {
472 if (result->
at(percentPos + len) == u
'n') {
475 result->
replace(percentPos, len, fmt);
485 const char *sourceText,
486 const char *disambiguation,
498 if (translators.
empty()) {
500 replacePercentN(&result, n);
505 result = translator->translate(context, sourceText, disambiguation, n);
515 replacePercentN(&result, n);
534 if (Q_LIKELY(!filename.
isEmpty())) {
535 const QString _dir = directory.
isEmpty() ? QStringLiteral(CUTELYST_I18N_DIR) : directory;
536 const QDir i18nDir(_dir);
537 if (Q_LIKELY(i18nDir.
exists())) {
543 if (Q_LIKELY(!tsFiles.empty())) {
544 locales.
reserve(tsFiles.size());
546 const QString fn = ts.fileName();
547 const int prefIdx = fn.
indexOf(_prefix);
554 if (Q_LIKELY(trans->load(loc, filename, _prefix, _dir))) {
557 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << loc <<
"from"
558 << ts.absoluteFilePath();
561 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << loc
562 <<
"from" << ts.absoluteFilePath();
565 qCWarning(CUTELYST_CORE)
566 <<
"Can not load translations for invalid locale string" << locString;
571 qCWarning(CUTELYST_CORE)
572 <<
"Can not find translation files for" << filename <<
"in directory" << _dir;
575 qCWarning(CUTELYST_CORE)
576 <<
"Can not load translations from not existing directory:" << _dir;
579 qCWarning(CUTELYST_CORE) <<
"Can not load translations for empty file name.";
591 const QDir dir(directory);
592 if (Q_LIKELY(dir.
exists())) {
594 if (Q_LIKELY(!dirs.empty())) {
596 for (
const QString &subDir : dirs) {
597 const QString relFn = subDir + u
'/' + filename;
603 if (Q_LIKELY(trans->load(
607 qCDebug(CUTELYST_CORE) <<
"Loaded translations for" << l <<
"from"
611 qCWarning(CUTELYST_CORE) <<
"Can not load translations for" << l
615 qCWarning(CUTELYST_CORE)
616 <<
"Can not load translations for invalid locale string:" << subDir;
622 qCWarning(CUTELYST_CORE) <<
"Can not find locale dirs under" << directory;
625 qCWarning(CUTELYST_CORE)
626 <<
"Can not load translations from not existing directory:" << directory;
629 qCWarning(CUTELYST_CORE)
630 <<
"Can not load translations for empty file name or directory name";
639 return d->defaultLocale;
645 d->defaultLocale = locale;
648void Cutelyst::ApplicationPrivate::setupHome()
651 if (!config.contains(u
"home"_s)) {
655 if (!config.contains(u
"root"_s)) {
656 QDir home = config.value(u
"home"_s).toString();
661void ApplicationPrivate::setupChildren(
const QObjectList &children)
664 for (
QObject *child : children) {
665 auto controller = qobject_cast<Controller *>(child);
667 q->registerController(controller);
671 auto plugin = qobject_cast<Plugin *>(child);
673 q->registerPlugin(plugin);
677 auto view = qobject_cast<View *>(child);
679 q->registerView(view);
683 auto dispatchType = qobject_cast<DispatchType *>(child);
685 q->registerDispatcher(dispatchType);
691void Cutelyst::ApplicationPrivate::logRequest(
const Request *req)
697 qCDebug(CUTELYST_REQUEST) << req->method() <<
"request for" << path <<
"from"
702 logRequestParameters(params, u
"Query Parameters are:"_s);
707 logRequestParameters(params, u
"Body Parameters are:"_s);
710 const auto bodyData = req->bodyData();
713 qCDebug(CUTELYST_REQUEST).noquote() <<
"JSON body:\n"
717 const auto uploads = req->
uploads();
718 if (!uploads.isEmpty()) {
719 logRequestUploads(uploads);
723void Cutelyst::ApplicationPrivate::logRequestParameters(
const ParamsMultiMap ¶ms,
727 for (
const auto &[key, value] : params.asKeyValueRange()) {
728 table.
append({key, value});
730 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table,
742 for (
const Upload *upload : uploads) {
743 table.
append({upload->name(),
748 qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table,
755 u
"File Uploads are:"_s)
766 auto matchMetadata = [name](
const QJsonObject &metadata) {
767 const QJsonObject json = metadata[u
"MetaData"].toObject();
768 qCDebug(CUTELYST_CORE) <<
"Found plugin metadata" << json;
769 return json[u
"name"].toString() == name;
772 auto createComponent = [name, parent, &factory](
QObject *plugin) ->
Component * {
773 factory = qobject_cast<ComponentFactory *>(plugin);
783 if (matchMetadata(plugin.metaData())) {
784 component = createComponent(plugin.instance());
789 qCCritical(CUTELYST_CORE)
790 <<
"Could not create a component for static plugin" << plugin.metaData();
794 if (factory && component) {
795 factories.insert(name, factory);
799 QDir pluginsDir(directory);
801 const auto pluginFiles = pluginsDir.entryList(
QDir::Files);
802 for (
const QString &fileName : pluginFiles) {
803 loader.
setFileName(pluginsDir.absoluteFilePath(fileName));
805 if (matchMetadata(loader.
metaData())) {
806 component = createComponent(loader.
instance());
811 qCCritical(CUTELYST_CORE)
812 <<
"Could not create a component for plugin" << fileName << loader.
metaData();
817 factories.
insert(name, factory);
823#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)
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory={}, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
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={})
void postForked(Cutelyst::Application *app)
virtual Component * createComponent(QObject *parent=nullptr)=0
The Cutelyst Component base class.
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.
The Cutelyst namespace holds all public Cutelyst API.
QString absoluteFilePath(const QString &fileName) const const
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
QStringList entryList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
QString absoluteFilePath() const const
QString absolutePath() const const
QString baseName() const const
QString suffix() const const
QByteArray toJson(QJsonDocument::JsonFormat format) const const
QJsonObject::iterator insert(QLatin1StringView key, const QJsonValue &value)
void append(QList::parameter_type value)
QList::const_reverse_iterator crbegin() const const
QList::const_reverse_iterator crend() const const
bool isEmpty() const const
void reserve(qsizetype size)
T value(qsizetype i) const const
QLocale::Language language() const const
bool isEmpty() const const
const QObjectList & children() const const
QObject * parent() const const
void setFileName(const QString &fileName)
QList< QStaticPlugin > staticPlugins()
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
QString fromLatin1(QByteArrayView str)
QString fromLocal8Bit(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString join(QChar separator) const const
void sort(Qt::CaseSensitivity cs)
QJsonDocument toJsonDocument() const const