Cutelyst  1.11.0
application.cpp
1 /*
2  * Copyright (C) 2013-2017 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 #include "application_p.h"
19 
20 #include "config.h"
21 #include "common.h"
22 #include "context_p.h"
23 #include "enginerequest.h"
24 #include "request.h"
25 #include "request_p.h"
26 #include "controller.h"
27 #include "controller_p.h"
28 #include "response.h"
29 #include "response_p.h"
30 #include "dispatchtype.h"
31 #include "view.h"
32 #include "stats.h"
33 #include "utils.h"
34 
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 
42 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER, "cutelyst.dispatcher")
43 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_PATH, "cutelyst.dispatcher.path")
44 Q_LOGGING_CATEGORY(CUTELYST_DISPATCHER_CHAINED, "cutelyst.dispatcher.chained")
45 Q_LOGGING_CATEGORY(CUTELYST_CONTROLLER, "cutelyst.controller")
46 Q_LOGGING_CATEGORY(CUTELYST_CORE, "cutelyst.core")
47 Q_LOGGING_CATEGORY(CUTELYST_ENGINE, "cutelyst.engine")
48 Q_LOGGING_CATEGORY(CUTELYST_UPLOAD, "cutelyst.upload")
49 Q_LOGGING_CATEGORY(CUTELYST_MULTIPART, "cutelyst.multipart")
50 Q_LOGGING_CATEGORY(CUTELYST_VIEW, "cutelyst.view")
51 Q_LOGGING_CATEGORY(CUTELYST_REQUEST, "cutelyst.request")
52 Q_LOGGING_CATEGORY(CUTELYST_RESPONSE, "cutelyst.response")
53 Q_LOGGING_CATEGORY(CUTELYST_STATS, "cutelyst.stats")
54 Q_LOGGING_CATEGORY(CUTELYST_COMPONENT, "cutelyst.component")
55 
56 using namespace Cutelyst;
57 
58 Application::Application(QObject *parent) :
59  QObject(parent),
60  d_ptr(new ApplicationPrivate)
61 {
62  Q_D(Application);
63 
64  d->q_ptr = this;
65  d->headers.setHeader(QStringLiteral("X_CUTELYST"), QStringLiteral(VERSION));
66 
67  qRegisterMetaType<ParamsMultiMap>();
68  qRegisterMetaTypeStreamOperators<ParamsMultiMap>("ParamsMultiMap");
69 
70  d->dispatcher = new Dispatcher(this);
71 }
72 
73 Application::~Application()
74 {
75  delete d_ptr;
76 }
77 
79 {
80  qCDebug(CUTELYST_CORE) << "Default Application::init called on pid:" << QCoreApplication::applicationPid();
81  return true;
82 }
83 
85 {
86  qCDebug(CUTELYST_CORE) << "Default Application::postFork called on pid:" << QCoreApplication::applicationPid();
87  return true;
88 }
89 
91 {
92  Q_D(Application);
93  return d->headers;
94 }
95 
97 {
98  Q_D(Application);
99  if (d->plugins.contains(plugin)) {
100  return false;
101  }
102  d->plugins.append(plugin);
103  return true;
104 }
105 
107 {
108  Q_D(Application);
109  const auto name = QString::fromLatin1(controller->metaObject()->className());
110  if (d->controllersHash.contains(name)) {
111  return false;
112  }
113  d->controllersHash.insert(name, controller);
114  d->controllers.append(controller);
115  return true;
116 }
117 
119 {
120  Q_D(Application);
121  if (d->views.contains(view->name())) {
122  qCWarning(CUTELYST_CORE) << "Not registering View." << view->metaObject()->className()
123  << "There is already a view with this name:" << view->name();
124  return false;
125  }
126  d->views.insert(view->name(), view);
127  return true;
128 }
129 
131 {
132  Q_D(Application);
133  if (d->dispatchers.contains(dispatcher)) {
134  return false;
135  }
136  d->dispatchers.append(dispatcher);
137  return true;
138 }
139 
140 Component *Application::createComponentPlugin(const QString &name, QObject *parent)
141 {
142  Q_D(Application);
143  auto it = d->factories.constFind(name);
144  if (it != d->factories.constEnd()) {
145  ComponentFactory *factory = it.value();
146  if (factory) {
147  return factory->createComponent(parent);
148  } else {
149  return nullptr;
150  }
151  }
152 
153  const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv("CUTELYST_PLUGINS_DIR").split(';');
154  for (const QByteArray &dir : dirs) {
155  Component *component = d->createComponentPlugin(name, parent, QString::fromLocal8Bit(dir));
156  if (component) {
157  return component;
158  }
159  }
160 
161  return nullptr;
162 }
163 
165 {
166  return VERSION;
167 }
168 
169 QVector<Cutelyst::Controller *> Application::controllers() const
170 {
171  Q_D(const Application);
172  return d->controllers;
173 }
174 
175 View *Application::view(const QString &name) const
176 {
177  Q_D(const Application);
178  return d->views.value(name);
179 }
180 
181 QVariant Application::config(const QString &key, const QVariant &defaultValue) const
182 {
183  Q_D(const Application);
184  auto it = d->config.constFind(key);
185  if (it != d->config.constEnd()) {
186  return it.value();
187  }
188  return defaultValue;
189 }
190 
192 {
193  Q_D(const Application);
194  return d->dispatcher;
195 }
196 
197 QVector<Cutelyst::DispatchType *> Application::dispatchers() const
198 {
199  Q_D(const Application);
200  return d->dispatcher->dispatchers();
201 }
202 
203 QVector<Plugin *> Application::plugins() const
204 {
205  Q_D(const Application);
206  return d->plugins;
207 }
208 
209 QVariantMap Application::config() const
210 {
211  Q_D(const Application);
212  return d->config;
213 }
214 
215 QString Application::pathTo(const QString &path) const
216 {
217  QDir home = config(QStringLiteral("home")).toString();
218  return home.absoluteFilePath(path);
219 }
220 
221 QString Cutelyst::Application::pathTo(const QStringList &path) const
222 {
223  QDir home = config(QStringLiteral("home")).toString();
224  return home.absoluteFilePath(path.join(QLatin1Char('/')));
225 }
226 
228 {
229  Q_D(const Application);
230  return d->init;
231 }
232 
234 {
235  Q_D(const Application);
236  return d->engine;
237 }
238 
239 void Application::setConfig(const QString &key, const QVariant &value)
240 {
241  Q_D(Application);
242  d->config.insert(key, value);
243 }
244 
246 {
247  Q_D(Application);
248 
249  if (d->init) {
250  return true;
251  }
252  d->init = true;
253 
254  d->useStats = CUTELYST_STATS().isDebugEnabled();
255  d->engine = engine;
256  d->config = engine->config(QLatin1String("Cutelyst"));
257 
258  d->setupHome();
259 
260  // Call the virtual application init
261  // to setup Controllers plugins stuff
262  if (init()) {
263  d->setupChildren(children());
264 
265  bool zeroCore = engine->workerCore() == 0;
266 
267  QVector<QStringList> tablePlugins;
268  const auto plugins = d->plugins;
269  for (Plugin *plugin : plugins) {
270  if (plugin->objectName().isEmpty()) {
271  plugin->setObjectName(QString::fromLatin1(plugin->metaObject()->className()));
272  }
273  tablePlugins.append({ plugin->objectName() });
274  // Configure plugins
275  plugin->setup(this);
276  }
277 
278  if (zeroCore && !tablePlugins.isEmpty()) {
279  qCDebug(CUTELYST_CORE) << Utils::buildTable(tablePlugins, QStringList(),
280  QLatin1String("Loaded plugins:")).constData();
281  }
282 
283  if (zeroCore) {
284  QVector<QStringList> tableDataHandlers;
285  tableDataHandlers.append({ QLatin1String("application/x-www-form-urlencoded") });
286  tableDataHandlers.append({ QLatin1String("application/json") });
287  tableDataHandlers.append({ QLatin1String("multipart/form-data") });
288  qCDebug(CUTELYST_CORE) << Utils::buildTable(tableDataHandlers, QStringList(),
289  QLatin1String("Loaded Request Data Handlers:")).constData();
290 
291  qCDebug(CUTELYST_CORE) << "Loaded dispatcher" << QString::fromLatin1(d->dispatcher->metaObject()->className());
292  qCDebug(CUTELYST_CORE) << "Using engine" << QString::fromLatin1(d->engine->metaObject()->className());
293  }
294 
295  QString home = d->config.value(QLatin1String("home")).toString();
296  if (home.isEmpty()) {
297  if (zeroCore) {
298  qCDebug(CUTELYST_CORE) << "Couldn't find home";
299  }
300  } else {
301  QFileInfo homeInfo = home;
302  if (homeInfo.isDir()) {
303  if (zeroCore) {
304  qCDebug(CUTELYST_CORE) << "Found home" << home;
305  }
306  } else {
307  if (zeroCore) {
308  qCDebug(CUTELYST_CORE) << "Home" << home << "doesn't exist";
309  }
310  }
311  }
312 
313  QVector<QStringList> table;
314  QStringList controllerNames = d->controllersHash.keys();
315  controllerNames.sort();
316  for (const QString &controller : controllerNames) {
317  table.append({ controller, QLatin1String("Controller")});
318  }
319 
320  const auto views = d->views;
321  for (View *view : views) {
322  if (view->reverse().isEmpty()) {
323  const QString className = QString::fromLatin1(view->metaObject()->className()) + QLatin1String("->execute");
324  view->setReverse(className);
325  }
326  table.append({ view->reverse(), QLatin1String("View")});
327  }
328 
329  if (zeroCore && !table.isEmpty()) {
330  qCDebug(CUTELYST_CORE) << Utils::buildTable(table, {
331  QLatin1String("Class"), QLatin1String("Type")
332  },
333  QLatin1String("Loaded components:")).constData();
334  }
335 
336  const auto controllers = d->controllers;
337  for (Controller *controller : controllers) {
338  controller->d_ptr->init(this, d->dispatcher);
339  }
340 
341  d->dispatcher->setupActions(d->controllers, d->dispatchers, d->engine->workerCore() == 0);
342 
343  if (zeroCore) {
344  qCInfo(CUTELYST_CORE) << QString::fromLatin1("%1 powered by Cutelyst %2, Qt %3.")
345  .arg(QCoreApplication::applicationName(), QLatin1String(Application::cutelystVersion()), QLatin1String(qVersion()))
346  .toLatin1().constData();
347  }
348 
349  Q_EMIT preForked(this);
350 
351  return true;
352  }
353 
354  return false;
355 }
356 
358 {
359  delete handleRequest2(req);
360 }
361 
363 {
364  Q_D(Application);
365 
366  Engine *engine = d->engine;
367  EngineRequest *engineRequest = req->d_ptr->engineRequest;
368  auto priv = new ContextPrivate(this, engine, d->dispatcher, d->plugins);
369  auto c = new Context(priv);
370  priv->response = new Response(c, engineRequest, d->headers);
371  priv->request = req;
372  priv->engineRequest = engineRequest;
373  req->setParent(c);
374 
375  Stats *stats = nullptr;
376  if (d->useStats) {
377  stats = new Stats(this);
378  priv->stats = stats;
379  }
380 
381  // Process request
382  bool skipMethod = false;
383  Q_EMIT beforePrepareAction(c, &skipMethod);
384 
385  if (!skipMethod) {
386  static bool log = CUTELYST_REQUEST().isEnabled(QtDebugMsg);
387  if (log) {
388  d->logRequest(req);
389  }
390 
391  d->dispatcher->prepareAction(c);
392 
393  Q_EMIT beforeDispatch(c);
394 
395  d->dispatcher->dispatch(c);
396 
397  Q_EMIT afterDispatch(c);
398  }
399 
400  engineRequest->finalize(c);
401 
402  if (stats) {
403  qCDebug(CUTELYST_STATS, "Response Code: %d; Content-Type: %s; Content-Length: %s",
404  c->response()->status(),
405  c->response()->headers().header(QStringLiteral("CONTENT_TYPE"), QStringLiteral("unknown")).toLatin1().data(),
406  c->response()->headers().header(QStringLiteral("CONTENT_LENGTH"), QStringLiteral("unknown")).toLatin1().data());
407 
408  quint64 endOfRequest = engine->time();
409  double enlapsed = (endOfRequest - engineRequest->startOfRequest) / 1000000.0;
410  QString average;
411  if (enlapsed == 0.0) {
412  average = QStringLiteral("??");
413  } else {
414  average = QString::number(1.0 / enlapsed, 'f');
415  average.truncate(average.size() - 3);
416  }
417  qCInfo(CUTELYST_STATS) << QStringLiteral("Request took: %1s (%2/s)\n%3")
418  .arg(QString::number(enlapsed, 'f'), average, QString::fromLatin1(stats->report()))
419  .toLatin1().constData();
420  delete stats;
421  }
422 
423  return c;
424 }
425 
427 {
428  Q_D(Application);
429 
430  if (!postFork()) {
431  return false;
432  }
433 
434  const auto controllers = d->controllers;
435  for (Controller *controller : controllers) {
436  if (!controller->postFork(this)) {
437  return false;
438  }
439  }
440 
441  Q_EMIT postForked(this);
442 
443  return true;
444 }
445 
446 void Application::addTranslator(const QLocale &locale, QTranslator *translator)
447 {
448  Q_D(Application);
449  Q_ASSERT_X(translator, "add translator to application", "invalid QTranslator object");
450  auto it = d->translators.find(locale);
451  if (it != d->translators.end()) {
452  it.value().prepend(translator);
453  } else {
454  d->translators.insert(locale, QVector<QTranslator*>(1, translator));
455  }
456 }
457 
458 void Application::addTranslator(const QString &locale, QTranslator *translator)
459 {
460  addTranslator(QLocale(locale), translator);
461 }
462 
463 void Application::addTranslators(const QLocale &locale, const QVector<QTranslator *> &translators)
464 {
465  Q_D(Application);
466  Q_ASSERT_X(!translators.empty(), "add translators to application", "empty translators vector");
467  auto transIt = d->translators.find(locale);
468  if (transIt != d->translators.end()) {
469  for (auto it = translators.crbegin(); it != translators.crend(); ++it) {
470  transIt.value().prepend(*it);
471  }
472  } else {
473  d->translators.insert(locale, translators);
474  }
475 }
476 
477 static void replacePercentN(QString *result, int n)
478 {
479  if (n >= 0) {
480  auto percentPos = 0;
481  auto len = 0;
482  while ((percentPos = result->indexOf(QLatin1Char('%'), percentPos + len)) != -1) {
483  len = 1;
484  QString fmt;
485  if (result->at(percentPos + len) == QLatin1Char('L')) {
486  ++len;
487  fmt = QStringLiteral("%L1");
488  } else {
489  fmt = QStringLiteral("%1");
490  }
491  if (result->at(percentPos + len) == QLatin1Char('n')) {
492  fmt = fmt.arg(n);
493  ++len;
494  result->replace(percentPos, len, fmt);
495  len = fmt.length();
496  }
497  }
498  }
499 }
500 
501 QString Application::translate(const QLocale &locale, const char *context, const char *sourceText, const char *disambiguation, int n) const
502 {
503  QString result;
504 
505  if (!sourceText) {
506  return result;
507  }
508 
509  Q_D(const Application);
510 
511  const QVector<QTranslator*> translators = d->translators.value(locale);
512  if (translators.empty()) {
513  result = QString::fromUtf8(sourceText);
514  replacePercentN(&result, n);
515  return result;
516  }
517 
518  for (QTranslator *translator : translators) {
519  result = translator->translate(context, sourceText, disambiguation, n);
520  if (!result.isEmpty()) {
521  break;
522  }
523  }
524 
525  if (result.isEmpty()) {
526  result = QString::fromUtf8(sourceText);
527  }
528 
529  replacePercentN(&result, n);
530  return result;
531 }
532 
533 
534 void Cutelyst::ApplicationPrivate::setupHome()
535 {
536  // Hook the current directory in config if "home" is not set
537  if (!config.contains(QLatin1String("home"))) {
538  config.insert(QStringLiteral("home"), QDir::currentPath());
539  }
540 
541  if (!config.contains(QLatin1String("root"))) {
542  QDir home = config.value(QLatin1String("home")).toString();
543  config.insert(QStringLiteral("root"), home.absoluteFilePath(QLatin1String("root")));
544  }
545 }
546 
547 void ApplicationPrivate::setupChildren(const QObjectList &children)
548 {
549  Q_Q(Application);
550  for (QObject *child : children) {
551  auto controller = qobject_cast<Controller *>(child);
552  if (controller) {
553  q->registerController(controller);
554  continue;
555  }
556 
557  auto plugin = qobject_cast<Plugin *>(child);
558  if (plugin) {
559  q->registerPlugin(plugin);
560  continue;
561  }
562 
563  auto view = qobject_cast<View *>(child);
564  if (view) {
565  q->registerView(view);
566  continue;
567  }
568 
569  auto dispatchType = qobject_cast<DispatchType *>(child);
570  if (dispatchType) {
571  q->registerDispatcher(dispatchType);
572  continue;
573  }
574  }
575 }
576 
577 void Cutelyst::ApplicationPrivate::logRequest(Request *req)
578 {
579  QString path = req->path();
580  if (path.isEmpty()) {
581  path = QStringLiteral("/");
582  }
583  qCDebug(CUTELYST_REQUEST) << req->method() << "request for" << path << "from" << req->addressString();
584 
585  ParamsMultiMap params = req->queryParameters();
586  if (!params.isEmpty()) {
587  logRequestParameters(params, QStringLiteral("Query Parameters are:"));
588  }
589 
590  params = req->bodyParameters();
591  if (!params.isEmpty()) {
592  logRequestParameters(params, QStringLiteral("Body Parameters are:"));
593  }
594 
595  const auto uploads = req->uploads();
596  if (!uploads.isEmpty()) {
597  logRequestUploads(uploads);
598  }
599 }
600 
601 void Cutelyst::ApplicationPrivate::logRequestParameters(const ParamsMultiMap &params, const QString &title)
602 {
603  QVector<QStringList> table;
604  auto it = params.constBegin();
605  while (it != params.constEnd()) {
606  table.append({ it.key(), it.value() });
607  ++it;
608  }
609  qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table, {
610  QStringLiteral("Parameter"),
611  QStringLiteral("Value"),
612  },
613  title).constData();
614 }
615 
616 void Cutelyst::ApplicationPrivate::logRequestUploads(const QVector<Cutelyst::Upload *> &uploads)
617 {
618  QVector<QStringList> table;
619  for (Upload *upload : uploads) {
620  table.append({ upload->name(),
621  upload->filename(),
622  upload->contentType(),
623  QString::number(upload->size())
624  });
625  }
626  qCDebug(CUTELYST_REQUEST) << Utils::buildTable(table, {
627  QStringLiteral("Parameter"),
628  QStringLiteral("Filename"),
629  QStringLiteral("Type"),
630  QStringLiteral("Size"),
631  },
632  QStringLiteral("File Uploads are:")).constData();
633 }
634 
635 Component *ApplicationPrivate::createComponentPlugin(const QString &name, QObject *parent, const QString &directory)
636 {
637  Component *component = nullptr;
638 
639  QDir pluginsDir(directory);
640  QPluginLoader loader;
641  ComponentFactory *factory = nullptr;
642  const auto plugins = pluginsDir.entryList(QDir::Files);
643  for (const QString &fileName : plugins) {
644  loader.setFileName(pluginsDir.absoluteFilePath(fileName));
645  const QJsonObject json = loader.metaData()[QLatin1String("MetaData")].toObject();
646  if (json[QLatin1String("name")].toString() == name) {
647  QObject *plugin = loader.instance();
648  if (plugin) {
649  factory = qobject_cast<ComponentFactory *>(plugin);
650  if (!factory) {
651  qCCritical(CUTELYST_CORE) << "Could not create a factory for" << loader.fileName();
652  } else {
653  component = factory->createComponent(parent);
654  }
655  break;
656  } else {
657  qCCritical(CUTELYST_CORE) << "Could not load plugin" << loader.fileName() << loader.errorString();
658  }
659  }
660  }
661 
662  if (factory) {
663  factories.insert(name, factory);
664  }
665 
666  return component;
667 }
668 
669 #include "moc_application.cpp"
QMap< QString, QString > ParamsMultiMap
void setConfig(const QString &key, const QVariant &value)
QVector< DispatchType * > dispatchers() const
QVector< Plugin * > plugins() const
QVector< Controller * > controllers() const
Engine * engine() const
void afterDispatch(Context *c)
bool inited() const
Context * handleRequest2(Request *req)
Called by the Engine to handle a new Request object.
bool registerView(View *view)
T plugin()
Returns the registered plugin that casts to the template type T.
Definition: application.h:114
bool setup(Engine *engine)
Called by the Engine to setup the internal data.
The Cutelyst Component base class.
Definition: component.h:38
ParamsMultiMap bodyParameters() const
Definition: request.cpp:213
ParamsMultiMap queryParameters() const
Definition: request.cpp:236
int workerCore() const
Each worker process migth have a number of worker cores (threads), a single process with two worker t...
Definition: engine.cpp:162
Cutelyst Upload handles file upload request
Definition: upload.h:35
QString pathTo(const QString &path) const
Headers & defaultHeaders()
Definition: application.cpp:90
The Cutelyst Context.
Definition: context.h:50
Cutelyst Controller base class
Definition: controller.h:102
bool registerController(Controller *controller)
virtual bool init()
Definition: application.cpp:78
Component * createComponentPlugin(const QString &name, QObject *parent=nullptr)
Dispatcher * dispatcher() const
QVariantMap config() const
void addTranslator(const QLocale &locale, QTranslator *translator)
Headers headers
The request headers.
QString translate(const QLocale &locale, const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void beforePrepareAction(Context *c, bool *skipMethod)
void beforeDispatch(Context *c)
void finalize(Context *c)
Called by Application to deal with finalizing cookies, headers and body.
bool registerDispatcher(DispatchType *dispatcher)
QString addressString() const
Definition: request.cpp:47
View * view(const QString &name=QString()) const
void postForked(Application *app)
bool enginePostFork()
Called by the Engine once post fork happened.
void preForked(Application *app)
virtual QByteArray report()
Definition: stats.cpp:62
virtual bool postFork()
Definition: application.cpp:84
QVector< Upload * > uploads() const
Definition: request.cpp:316
Q_DECL_DEPRECATED void handleRequest(Request *req)
Called by the Engine to handle a new Request object.
Cutelyst View abstract view component
Definition: view.h:33
void addTranslators(const QLocale &locale, const QVector< QTranslator * > &translators)
virtual quint64 time()
Definition: engine.cpp:199
The Cutelyst Application.
Definition: application.h:54
QString reverse() const
Definition: component.h:141
quint64 startOfRequest
The timestamp of the start of headers.
bool registerPlugin(Plugin *plugin)
Definition: application.cpp:96
virtual Component * createComponent(QObject *parent=nullptr)=0
The Cutelyst Engine.
Definition: engine.h:33
QString name() const
Definition: view.cpp:40
The Cutelyst Dispatcher.
Definition: dispatcher.h:40
static const char * cutelystVersion()
QVariantMap config(const QString &entity) const
user configuration for the application
Definition: engine.cpp:383
void setReverse(const QString &reverse)
Definition: component.h:144