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