cutelyst  3.9.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
context.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "action.h"
6 #include "application.h"
7 #include "common.h"
8 #include "config.h"
9 #include "context_p.h"
10 #include "controller.h"
11 #include "dispatcher.h"
12 #include "enginerequest.h"
13 #include "request.h"
14 #include "response.h"
15 #include "stats.h"
16 
17 #include <QBuffer>
18 #include <QCoreApplication>
19 #include <QUrl>
20 #include <QUrlQuery>
21 
22 using namespace Cutelyst;
23 
24 Context::Context(ContextPrivate *priv)
25  : d_ptr(priv)
26 {
27 }
28 
30  : d_ptr(new ContextPrivate(app, app->engine(), app->dispatcher(), app->plugins()))
31 {
32  auto req = new DummyRequest(this);
33  req->body = new QBuffer(this);
35  req->context = this;
36 
37  d_ptr->response = new Response(app->defaultHeaders(), req);
38  d_ptr->request = new Request(req);
39  d_ptr->request->d_ptr->engine = d_ptr->engine;
40 }
41 
42 Context::~Context()
43 {
44  delete d_ptr->request;
45  delete d_ptr->response;
46  delete d_ptr;
47 }
48 
49 bool Context::error() const noexcept
50 {
51  Q_D(const Context);
52  return !d->error.isEmpty();
53 }
54 
55 void Context::error(const QString &error)
56 {
57  Q_D(Context);
58  if (error.isEmpty()) {
59  d->error.clear();
60  } else {
61  d->error << error;
62  qCCritical(CUTELYST_CORE) << error;
63  }
64 }
65 
66 QStringList Context::errors() const noexcept
67 {
68  Q_D(const Context);
69  return d->error;
70 }
71 
72 bool Context::state() const noexcept
73 {
74  Q_D(const Context);
75  return d->state;
76 }
77 
78 void Context::setState(bool state) noexcept
79 {
80  Q_D(Context);
81  d->state = state;
82 }
83 
84 Engine *Context::engine() const noexcept
85 {
86  Q_D(const Context);
87  return d->engine;
88 }
89 
90 Application *Context::app() const noexcept
91 {
92  Q_D(const Context);
93  return d->app;
94 }
95 
96 Response *Context::response() const noexcept
97 {
98  Q_D(const Context);
99  return d->response;
100 }
101 
102 Response *Context::res() const noexcept
103 {
104  Q_D(const Context);
105  return d->response;
106 }
107 
108 Action *Context::action() const noexcept
109 {
110  Q_D(const Context);
111  return d->action;
112 }
113 
114 QString Context::actionName() const noexcept
115 {
116  Q_D(const Context);
117  return d->action->name();
118 }
119 
120 QString Context::ns() const noexcept
121 {
122  Q_D(const Context);
123  return d->action->ns();
124 }
125 
126 Request *Context::request() const noexcept
127 {
128  Q_D(const Context);
129  return d->request;
130 }
131 
132 Request *Context::req() const noexcept
133 {
134  Q_D(const Context);
135  return d->request;
136 }
137 
139 {
140  Q_D(const Context);
141  return d->dispatcher;
142 }
143 
145 {
146  Q_D(const Context);
147  return QString::fromLatin1(d->action->controller()->metaObject()->className());
148 }
149 
150 Controller *Context::controller() const noexcept
151 {
152  Q_D(const Context);
153  return d->action->controller();
154 }
155 
157 {
158  Q_D(const Context);
159  return d->dispatcher->controllers().value(name);
160 }
161 
162 View *Context::customView() const noexcept
163 {
164  Q_D(const Context);
165  return d->view;
166 }
167 
168 View *Context::view(const QString &name) const
169 {
170  Q_D(const Context);
171  return d->app->view(name);
172 }
173 
175 {
176  Q_D(const Context);
177  return d->app->view(name);
178 }
179 
181 {
182  Q_D(Context);
183  d->view = d->app->view(name);
184  return d->view;
185 }
186 
187 QVariantHash &Context::stash()
188 {
189  Q_D(Context);
190  return d->stash;
191 }
192 
193 QVariant Context::stash(const QString &key) const
194 {
195  Q_D(const Context);
196  return d->stash.value(key);
197 }
198 
199 QVariant Context::stash(const QString &key, const QVariant &defaultValue) const
200 {
201  Q_D(const Context);
202  return d->stash.value(key, defaultValue);
203 }
204 
206 {
207  Q_D(Context);
208  return d->stash.take(key);
209 }
210 
212 {
213  Q_D(Context);
214  return d->stash.remove(key);
215 }
216 
217 void Context::setStash(const QString &key, const QVariant &value)
218 {
219  Q_D(Context);
220  d->stash.insert(key, value);
221 }
222 
223 void Context::setStash(const QString &key, const ParamsMultiMap &map)
224 {
225  Q_D(Context);
226  d->stash.insert(key, QVariant::fromValue(map));
227 }
228 
230 {
231  Q_D(const Context);
232  return d->stack;
233 }
234 
236  const QStringList &args,
237  const ParamsMultiMap &queryValues) const
238 {
239  Q_D(const Context);
240 
241  QUrl uri = d->request->uri();
242 
243  QString _path;
244  if (path.isEmpty()) {
245  // ns must NOT return a leading slash
246  const QString controllerNS = d->action->controller()->ns();
247  if (!controllerNS.isEmpty()) {
248  _path.prepend(controllerNS);
249  }
250  } else {
251  _path = path;
252  }
253 
254  if (!args.isEmpty()) {
255  if (_path.compare(u"/") == 0) {
256  _path += args.join(u'/');
257  } else {
258  _path = _path + u'/' + args.join(u'/');
259  }
260  }
261 
262  if (!_path.startsWith(u'/')) {
263  _path.prepend(u'/');
264  }
265  uri.setPath(_path, QUrl::DecodedMode);
266 
267  QUrlQuery query;
268  if (!queryValues.isEmpty()) {
269  // Avoid a trailing '?'
270  if (queryValues.size()) {
271  auto it = queryValues.constEnd();
272  while (it != queryValues.constBegin()) {
273  --it;
274  query.addQueryItem(it.key(), it.value());
275  }
276  }
277  }
278  uri.setQuery(query);
279 
280  return uri;
281 }
282 
284  const QStringList &captures,
285  const QStringList &args,
286  const ParamsMultiMap &queryValues) const
287 {
288  Q_D(const Context);
289 
290  QUrl uri;
291  Action *localAction = action;
292  if (!localAction) {
293  localAction = d->action;
294  }
295 
296  QStringList localArgs = args;
297  QStringList localCaptures = captures;
298 
299  Action *expandedAction = d->dispatcher->expandAction(this, action);
300  if (expandedAction->numberOfCaptures() > 0) {
301  while (localCaptures.size() < expandedAction->numberOfCaptures() && localArgs.size()) {
302  localCaptures.append(localArgs.takeFirst());
303  }
304  } else {
305  QStringList localCapturesAux = localCaptures;
306  localCapturesAux.append(localArgs);
307  localArgs = localCapturesAux;
308  localCaptures = QStringList();
309  }
310 
311  const QString path = d->dispatcher->uriForAction(localAction, localCaptures);
312  if (path.isEmpty()) {
313  qCWarning(CUTELYST_CORE) << "Can not find action for" << localAction << localCaptures;
314  return uri;
315  }
316 
317  uri = uriFor(path, localArgs, queryValues);
318  return uri;
319 }
320 
322  const QStringList &captures,
323  const QStringList &args,
324  const ParamsMultiMap &queryValues) const
325 {
326  Q_D(const Context);
327 
328  QUrl uri;
329  Action *action = d->dispatcher->getActionByPath(path);
330  if (!action) {
331  qCWarning(CUTELYST_CORE) << "Can not find action for" << path;
332  return uri;
333  }
334 
335  uri = uriFor(action, captures, args, queryValues);
336  return uri;
337 }
338 
339 bool Context::detached() const noexcept
340 {
341  Q_D(const Context);
342  return d->detached;
343 }
344 
345 void Context::detach(Action *action)
346 {
347  Q_D(Context);
348  if (action) {
349  d->dispatcher->forward(this, action);
350  } else {
351  d->detached = true;
352  }
353 }
354 
355 void Context::detachAsync() noexcept
356 {
357  Q_D(Context);
358  ++d->actionRefCount;
359 }
360 
362 {
363  Q_D(Context);
364 
365  // ASync might be destroyed at the same stack level it was created
366  // resulting in this method being called while it was caller,
367  // allowing this method to call finished() twice, with
368  // a null context the second time so we check also check
369  // if the action stack is not empty to skip this method
370  if (--d->actionRefCount || !d->stack.isEmpty()) {
371  return;
372  }
373 
374  if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
375  qCWarning(CUTELYST_ASYNC) << "Trying to async attach to a finalized request! Skipping...";
376  return;
377  }
378 
379  if (d->engineRequest->status & EngineRequest::Async) {
380  while (!d->pendingAsync.isEmpty()) {
381  Component *action = d->pendingAsync.dequeue();
382  const bool ret = execute(action);
383 
384  if (d->actionRefCount) {
385  return;
386  }
387 
388  if (!ret) {
389  break; // we are finished
390  }
391  }
392 
393  Q_EMIT d->app->afterDispatch(this);
394 
395  finalize();
396  }
397 }
398 
400 {
401  Q_D(Context);
402  return d->dispatcher->forward(this, action);
403 }
404 
405 bool Context::forward(const QString &action)
406 {
407  Q_D(Context);
408  return d->dispatcher->forward(this, action);
409 }
410 
411 Action *Context::getAction(const QString &action, const QString &ns) const
412 {
413  Q_D(const Context);
414  return d->dispatcher->getAction(action, ns);
415 }
416 
417 QVector<Action *> Context::getActions(const QString &action, const QString &ns) const
418 {
419  Q_D(const Context);
420  return d->dispatcher->getActions(action, ns);
421 }
422 
424 {
425  Q_D(const Context);
426  return d->plugins;
427 }
428 
430 {
431  Q_D(Context);
432  Q_ASSERT_X(code, "Context::execute", "trying to execute a null Cutelyst::Component");
433 
434  static int recursion =
435  qEnvironmentVariableIsSet("RECURSION") ? qEnvironmentVariableIntValue("RECURSION") : 1000;
436  if (d->stack.size() >= recursion) {
437  QString msg = QStringLiteral("Deep recursion detected (stack size %1) calling %2, %3")
438  .arg(QString::number(d->stack.size()), code->reverse(), code->name());
439  error(msg);
440  setState(false);
441  return false;
442  }
443 
444  bool ret;
445  d->stack.push(code);
446 
447  if (d->stats) {
448  const QString statsInfo = d->statsStartExecute(code);
449 
450  ret = code->execute(this);
451 
452  // The request might finalize execution before returning
453  // so it's wise to check for d->stats again
454  if (d->stats && !statsInfo.isEmpty()) {
455  d->statsFinishExecute(statsInfo);
456  }
457  } else {
458  ret = code->execute(this);
459  }
460 
461  d->stack.pop();
462 
463  return ret;
464 }
465 
466 QLocale Context::locale() const noexcept
467 {
468  Q_D(const Context);
469  return d->locale;
470 }
471 
472 void Context::setLocale(const QLocale &locale)
473 {
474  Q_D(Context);
475  d->locale = locale;
476 }
477 
478 QVariant Context::config(const QString &key, const QVariant &defaultValue) const
479 {
480  Q_D(const Context);
481  return d->app->config(key, defaultValue);
482 }
483 
484 QVariantMap Context::config() const noexcept
485 {
486  Q_D(const Context);
487  return d->app->config();
488 }
489 
490 QString Context::translate(const char *context,
491  const char *sourceText,
492  const char *disambiguation,
493  int n) const
494 {
495  Q_D(const Context);
496  return d->app->translate(d->locale, context, sourceText, disambiguation, n);
497 }
498 
500 {
501  Q_D(Context);
502 
503  if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
504  qCWarning(CUTELYST_CORE) << "Trying to finalize a finalized request! Skipping...";
505  return;
506  }
507 
508  if (d->stats) {
509  qCDebug(CUTELYST_STATS,
510  "Response Code: %d; Content-Type: %s; Content-Length: %s",
511  d->response->status(),
512  qPrintable(d->response->headers().header(QStringLiteral("CONTENT_TYPE"),
513  QStringLiteral("unknown"))),
514  qPrintable(d->response->headers().header(QStringLiteral("CONTENT_LENGTH"),
515  QStringLiteral("unknown"))));
516 
517  const double enlapsed = d->engineRequest->elapsed.nsecsElapsed() / 1000000000.0;
518  QString average;
519  if (enlapsed == 0.0) {
520  average = QStringLiteral("??");
521  } else {
522  average = QString::number(1.0 / enlapsed, 'f');
523  average.truncate(average.size() - 3);
524  }
525  qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
526  .arg(QString::number(enlapsed, 'f'),
527  average,
528  QString::fromLatin1(d->stats->report())));
529  delete d->stats;
530  d->stats = nullptr;
531  }
532 
533  d->engineRequest->finalize();
534 }
535 
536 QString ContextPrivate::statsStartExecute(Component *code)
537 {
538  QString actionName;
539  // Skip internal actions
540  if (code->name().startsWith(u'_')) {
541  return actionName;
542  }
543 
544  actionName = code->reverse();
545 
546  if (qobject_cast<Action *>(code)) {
547  actionName.prepend(u'/');
548  }
549 
550  if (stack.size() > 2) {
551  actionName = u"-> " + actionName;
552  actionName =
553  actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
554  }
555 
556  stats->profileStart(actionName);
557 
558  return actionName;
559 }
560 
561 void ContextPrivate::statsFinishExecute(const QString &statsInfo)
562 {
563  stats->profileEnd(statsInfo);
564 }
565 
566 void Context::stash(const QVariantHash &unite)
567 {
568  Q_D(Context);
569  auto it = unite.constBegin();
570  while (it != unite.constEnd()) {
571  d->stash.insert(it.key(), it.value());
572  ++it;
573  }
574 }
575 
576 #include "moc_context.cpp"
577 #include "moc_context_p.cpp"
Context(Application *app)
Constructs a new DUMMY Context object that is child of Application This currently is experimental to ...
Definition: context.cpp:29
bool execute(Context *c)
Definition: component.cpp:64
QIODevice * body() const
Definition: request.cpp:181
void truncate(qsizetype position)
bool state() const noexcept
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:49
const_iterator constEnd() const const
bool setCustomView(const QString &name)
Definition: context.cpp:180
QString & prepend(QChar ch)
QVariant fromValue(T &&value)
Response * res() const noexcept
Definition: context.cpp:102
bool isEmpty() const const
qsizetype size() const const
size_type size() const const
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:217
QString ns() const noexcept
void detach(Action *action=nullptr)
Definition: context.cpp:345
QString join(QChar separator) const const
View * view(const QString &name) const
Definition: context.cpp:168
Request * req() const noexcept
QUrl uriFor(const QString &path=QString(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:235
virtual bool open(QIODeviceBase::OpenMode mode)
The Cutelyst Component base class.
Definition: component.h:25
QString actionName() const noexcept
qsizetype size() const const
This class represents a Cutelyst Action.
Definition: action.h:34
Headers & defaultHeaders() noexcept
Definition: application.cpp:83
QUrl uriForAction(const QString &path, const QStringList &captures=QStringList(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:321
void setPath(const QString &path, ParsingMode mode)
const_iterator constBegin() const const
void addQueryItem(const QString &key, const QString &value)
The Cutelyst Context.
Definition: context.h:38
bool forward(Component *component)
Definition: context.cpp:399
QString number(double n, char format, int precision)
Cutelyst Controller base class
Definition: controller.h:87
QString rightJustified(qsizetype width, QChar fill, bool truncate) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString name() const
Definition: component.cpp:33
Action * action() const noexcept
bool detached() const noexcept
Definition: context.cpp:339
bool isEmpty() const const
bool isEmpty() const const
QVector< Action * > getActions(const QString &action, const QString &ns={}) const
Definition: context.cpp:417
Request * request() const noexcept
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:490
QVariantMap config() const noexcept
virtual qint8 numberOfCaptures() const noexcept
Definition: action.cpp:130
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void detachAsync() noexcept
Definition: context.cpp:355
QString reverse() const
Definition: component.cpp:45
QLocale locale() const noexcept
Definition: context.cpp:466
bool execute(Component *code)
Definition: context.cpp:429
QVector< Plugin * > plugins() const
Definition: context.cpp:423
QVariantHash & stash()
Definition: context.cpp:187
Action * getAction(const QString &action, const QString &ns={}) const
Definition: context.cpp:411
void setLocale(const QLocale &locale)
Definition: context.cpp:472
Application * app() const noexcept
Definition: context.cpp:90
QString fromLatin1(QByteArrayView str)
QStack< Component * > stack() const noexcept
Definition: context.cpp:229
QVariant stashTake(const QString &key)
Definition: context.cpp:205
value_type takeFirst()
void append(QList< T > &&value)
Engine * engine() const noexcept
Definition: context.cpp:84
Cutelyst View abstract view component
Definition: view.h:21
Controller * controller() const noexcept
void setQuery(const QString &query, ParsingMode mode)
The Cutelyst Application.
Definition: application.h:42
bool stashRemove(const QString &key)
Definition: context.cpp:211
void setState(bool state) noexcept
Sets the state of the current executed action, setting to false will make the dispatcher skip non pro...
Definition: context.cpp:78
Response * response() const noexcept
Definition: context.cpp:96
The Cutelyst Engine
Definition: engine.h:20
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
The Cutelyst Dispatcher.
Definition: dispatcher.h:27
QString arg(Args &&... args) const const
void finalize()
finalize the request right away this is automatically called at the end of the actions chain ...
Definition: context.cpp:499
View * customView() const noexcept
Definition: context.cpp:162
QStringList errors() const noexcept
Returns a list of errors that were defined.
Definition: context.cpp:66
Q_EMITQ_EMIT
QString controllerName() const
void attachAsync()
attachAsync
Definition: context.cpp:361
Dispatcher * dispatcher() const noexcept
Definition: context.cpp:138