Cutelyst  3.5.0
context.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2013-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "context_p.h"
6 
7 #include "common.h"
8 #include "request.h"
9 #include "response.h"
10 #include "action.h"
11 #include "dispatcher.h"
12 #include "controller.h"
13 #include "application.h"
14 #include "stats.h"
15 #include "enginerequest.h"
16 
17 #include "config.h"
18 
19 #include <QUrl>
20 #include <QUrlQuery>
21 #include <QCoreApplication>
22 #include <QBuffer>
23 
24 using namespace Cutelyst;
25 
26 Context::Context(ContextPrivate *priv) : d_ptr(priv)
27 {
28 }
29 
31  d_ptr(new ContextPrivate(app, app->engine(), app->dispatcher(), app->plugins()))
32 {
33  auto req = new DummyRequest(this);
34  req->body = new QBuffer(this);
35  req->body->open(QBuffer::ReadWrite);
36  req->context = this;
37 
38  d_ptr->response = new Response(app->defaultHeaders(), req);
39  d_ptr->request = new Request(req);
40  d_ptr->request->d_ptr->engine = d_ptr->engine;
41 }
42 
43 Context::~Context()
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::error(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 QString::fromLatin1(d->action->controller()->metaObject()->className());
149 }
150 
151 Controller *Context::controller() const noexcept
152 {
153  Q_D(const Context);
154  return d->action->controller();
155 }
156 
158 {
159  Q_D(const Context);
160  return d->dispatcher->controllers().value(name);
161 }
162 
163 View *Context::customView() const noexcept
164 {
165  Q_D(const Context);
166  return d->view;
167 }
168 
169 View *Context::view(const QString &name) const
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 
230 QUrl Context::uriFor(const QString &path, const QStringList &args, const ParamsMultiMap &queryValues) const
231 {
232  Q_D(const Context);
233 
234  QUrl uri = d->request->uri();
235 
236  QString _path;
237  if (path.isEmpty()) {
238  // ns must NOT return a leading slash
239  const QString controllerNS = d->action->controller()->ns();
240  if (!controllerNS.isEmpty()) {
241  _path.prepend(controllerNS);
242  }
243  } else {
244  _path = path;
245  }
246 
247  if (!args.isEmpty()) {
248  if (_path.compare(u"/") == 0) {
249  _path += args.join(QLatin1Char('/'));
250  } else {
251  _path = _path + QLatin1Char('/') + args.join(QLatin1Char('/'));
252  }
253  }
254 
255  if (!_path.startsWith(QLatin1Char('/'))) {
256  _path.prepend(QLatin1Char('/'));
257  }
258  uri.setPath(_path, QUrl::DecodedMode);
259 
260  QUrlQuery query;
261  if (!queryValues.isEmpty()) {
262  // Avoid a trailing '?'
263  if (queryValues.size()) {
264  auto it = queryValues.constEnd();
265  while (it != queryValues.constBegin()) {
266  --it;
267  query.addQueryItem(it.key(), it.value());
268  }
269  }
270  }
271  uri.setQuery(query);
272 
273  return uri;
274 }
275 
276 QUrl Context::uriFor(Action *action, const QStringList &captures, const QStringList &args, const ParamsMultiMap &queryValues) const
277 {
278  Q_D(const Context);
279 
280  QUrl uri;
281  Action *localAction = action;
282  if (!localAction) {
283  localAction = d->action;
284  }
285 
286  QStringList localArgs = args;
287  QStringList localCaptures = captures;
288 
289  Action *expandedAction = d->dispatcher->expandAction(this, action);
290  if (expandedAction->numberOfCaptures() > 0) {
291  while (localCaptures.size() < expandedAction->numberOfCaptures()
292  && localArgs.size()) {
293  localCaptures.append(localArgs.takeFirst());
294  }
295  } else {
296  QStringList localCapturesAux = localCaptures;
297  localCapturesAux.append(localArgs);
298  localArgs = localCapturesAux;
299  localCaptures = QStringList();
300  }
301 
302  const QString path = d->dispatcher->uriForAction(localAction, localCaptures);
303  if (path.isEmpty()) {
304  qCWarning(CUTELYST_CORE) << "Can not find action for" << localAction << localCaptures;
305  return uri;
306  }
307 
308  uri = uriFor(path, localArgs, queryValues);
309  return uri;
310 }
311 
312 QUrl Context::uriForAction(const QString &path, const QStringList &captures, const QStringList &args, const ParamsMultiMap &queryValues) const
313 {
314  Q_D(const Context);
315 
316  QUrl uri;
317  Action *action = d->dispatcher->getActionByPath(path);
318  if (!action) {
319  qCWarning(CUTELYST_CORE) << "Can not find action for" << path;
320  return uri;
321  }
322 
323  uri = uriFor(action, captures, args, queryValues);
324  return uri;
325 }
326 
327 bool Context::detached() const noexcept
328 {
329  Q_D(const Context);
330  return d->detached;
331 }
332 
333 void Context::detach(Action *action)
334 {
335  Q_D(Context);
336  if (action) {
337  d->dispatcher->forward(this, action);
338  } else {
339  d->detached = true;
340  }
341 }
342 
343 void Context::detachAsync() noexcept
344 {
345  Q_D(Context);
346  ++d->asyncDetached;
347 }
348 
350 {
351  Q_D(Context);
352  if (--d->asyncDetached) {
353  return;
354  }
355 
356  if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
357  qCWarning(CUTELYST_ASYNC) << "Trying to async attach to a finalized request! Skipping...";
358  return;
359  }
360 
361  if (d->engineRequest->status & EngineRequest::Async) {
362  while (d->asyncAction < d->pendingAsync.size()) {
363  Component *action = d->pendingAsync[d->asyncAction++];
364  const bool ret = execute(action);
365 
366  if (d->asyncDetached) {
367  return;
368  }
369 
370  if (!ret) {
371  break; // we are finished
372  }
373  }
374 
375  Q_EMIT d->app->afterDispatch(this);
376 
377  finalize();
378  }
379 }
380 
382 {
383  Q_D(Context);
384  return d->dispatcher->forward(this, action);
385 }
386 
387 bool Context::forward(const QString &action)
388 {
389  Q_D(Context);
390  return d->dispatcher->forward(this, action);
391 }
392 
393 Action *Context::getAction(const QString &action, const QString &ns) const
394 {
395  Q_D(const Context);
396  return d->dispatcher->getAction(action, ns);
397 }
398 
399 QVector<Action *> Context::getActions(const QString &action, const QString &ns) const
400 {
401  Q_D(const Context);
402  return d->dispatcher->getActions(action, ns);
403 }
404 
406 {
407  Q_D(const Context);
408  return d->plugins;
409 }
410 
412 {
413  Q_D(Context);
414  Q_ASSERT_X(code, "Context::execute", "trying to execute a null Cutelyst::Component");
415 
416  static int recursion = qEnvironmentVariableIsSet("RECURSION") ? qEnvironmentVariableIntValue("RECURSION") : 1000;
417  if (d->stack.size() >= recursion) {
418  QString msg = QStringLiteral("Deep recursion detected (stack size %1) calling %2, %3")
419  .arg(QString::number(d->stack.size()), code->reverse(), code->name());
420  error(msg);
421  setState(false);
422  return false;
423  }
424 
425  bool ret;
426  d->stack.push(code);
427 
428  if (d->stats) {
429  const QString statsInfo = d->statsStartExecute(code);
430 
431  ret = code->execute(this);
432 
433  // The request might finalize execution before returning
434  // so it's wise to check for d->stats again
435  if (d->stats && !statsInfo.isEmpty()) {
436  d->statsFinishExecute(statsInfo);
437  }
438  } else {
439  ret = code->execute(this);
440  }
441 
442  d->stack.pop();
443 
444  return ret;
445 }
446 
447 QLocale Context::locale() const noexcept
448 {
449  Q_D(const Context);
450  return d->locale;
451 }
452 
453 void Context::setLocale(const QLocale &locale)
454 {
455  Q_D(Context);
456  d->locale = locale;
457 }
458 
459 QVariant Context::config(const QString &key, const QVariant &defaultValue) const
460 {
461  Q_D(const Context);
462  return d->app->config(key, defaultValue);
463 }
464 
465 QVariantMap Context::config() const noexcept
466 {
467  Q_D(const Context);
468  return d->app->config();
469 }
470 
471 QString Context::translate(const char *context, const char *sourceText, const char *disambiguation, int n) const
472 {
473  Q_D(const Context);
474  return d->app->translate(d->locale, context, sourceText, disambiguation, n);
475 }
476 
478 {
479  Q_D(Context);
480 
481  if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
482  qCWarning(CUTELYST_CORE) << "Trying to finalize a finalized request! Skipping...";
483  return;
484  }
485 
486  if (d->stats) {
487  qCDebug(CUTELYST_STATS, "Response Code: %d; Content-Type: %s; Content-Length: %s",
488  d->response->status(),
489  qPrintable(d->response->headers().header(QStringLiteral("CONTENT_TYPE"), QStringLiteral("unknown"))),
490  qPrintable(d->response->headers().header(QStringLiteral("CONTENT_LENGTH"), QStringLiteral("unknown"))));
491 
492  const double enlapsed = d->engineRequest->elapsed.nsecsElapsed() / 1000000000.0;
493  QString average;
494  if (enlapsed == 0.0) {
495  average = QStringLiteral("??");
496  } else {
497  average = QString::number(1.0 / enlapsed, 'f');
498  average.truncate(average.size() - 3);
499  }
500  qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
501  .arg(QString::number(enlapsed, 'f'),
502  average,
503  QString::fromLatin1(d->stats->report())));
504  delete d->stats;
505  d->stats = nullptr;
506  }
507 
508  d->engineRequest->finalize();
509 }
510 
511 QString ContextPrivate::statsStartExecute(Component *code)
512 {
513  QString actionName;
514  // Skip internal actions
515  if (code->name().startsWith(QLatin1Char('_'))) {
516  return actionName;
517  }
518 
519  actionName = code->reverse();
520 
521  if (qobject_cast<Action *>(code)) {
522  actionName.prepend(QLatin1Char('/'));
523  }
524 
525  if (stack.size() > 2) {
526  actionName = QLatin1String("-> ") + actionName;
527  actionName = actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
528  }
529 
530  stats->profileStart(actionName);
531 
532  return actionName;
533 }
534 
535 void ContextPrivate::statsFinishExecute(const QString &statsInfo)
536 {
537  stats->profileEnd(statsInfo);
538 }
539 
540 void Context::stash(const QVariantHash &unite)
541 {
542  Q_D(Context);
543  auto it = unite.constBegin();
544  while (it != unite.constEnd()) {
545  d->stash.insert(it.key(), it.value());
546  ++it;
547  }
548 }
549 
550 #include "moc_context.cpp"
551 #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:30
bool execute(Context *c)
Definition: component.cpp:62
QIODevice * body() const
Definition: request.cpp:179
void truncate(int position)
bool state() const noexcept
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:50
bool setCustomView(const QString &name)
Definition: context.cpp:175
QString & prepend(QChar ch)
Response * res() const noexcept
Definition: context.cpp:103
QMap::const_iterator constBegin() const const
int size() const const
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:212
QString ns() const noexcept
void detach(Action *action=nullptr)
Definition: context.cpp:333
QString join(const QString &separator) const const
Request * req() const noexcept
QUrl uriFor(const QString &path=QString(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:230
virtual bool open(QIODevice::OpenMode mode)
The Cutelyst Component base class.
Definition: component.h:25
QString actionName() const noexcept
int size() const const
This class represents a Cutelyst Action.
Definition: action.h:34
QVector< Action * > getActions(const QString &action, const QString &ns=QString()) const
Definition: context.cpp:399
Headers & defaultHeaders() noexcept
Definition: application.cpp:82
QUrl uriForAction(const QString &path, const QStringList &captures=QStringList(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:312
void setPath(const QString &path, QUrl::ParsingMode mode)
void addQueryItem(const QString &key, const QString &value)
The Cutelyst Context.
Definition: context.h:38
bool forward(Component *component)
Definition: context.cpp:381
QString number(int n, int base)
Cutelyst Controller base class
Definition: controller.h:89
void append(const T &value)
QString rightJustified(int width, QChar fill, bool truncate) const const
QString name() const
Definition: component.cpp:31
Action * action() const noexcept
bool detached() const noexcept
Definition: context.cpp:327
bool isEmpty() const const
bool isEmpty() const const
QMap::const_iterator constEnd() const const
Request * request() const noexcept
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:471
View * view(const QString &name=QString()) const
Definition: context.cpp:169
QVariantMap config() const noexcept
virtual qint8 numberOfCaptures() const noexcept
Definition: action.cpp:128
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void detachAsync() noexcept
Definition: context.cpp:343
QString reverse() const
Definition: component.cpp:43
QLocale locale() const noexcept
Definition: context.cpp:447
Action * getAction(const QString &action, const QString &ns=QString()) const
Definition: context.cpp:393
bool execute(Component *code)
Definition: context.cpp:411
QVector< Plugin * > plugins() const
Definition: context.cpp:405
QVariantHash & stash()
Definition: context.cpp:182
QVariant fromValue(const T &value)
void setLocale(const QLocale &locale)
Definition: context.cpp:453
Application * app() const noexcept
Definition: context.cpp:91
QStack< Component * > stack() const noexcept
Definition: context.cpp:224
QVariant stashTake(const QString &key)
Definition: context.cpp:200
T takeFirst()
Engine * engine() const noexcept
Definition: context.cpp:85
Cutelyst View abstract view component
Definition: view.h:21
Controller * controller() const noexcept
void setQuery(const QString &query, QUrl::ParsingMode mode)
QString fromLatin1(const char *str, int size)
The Cutelyst Application.
Definition: application.h:42
bool stashRemove(const QString &key)
Definition: context.cpp:206
bool isEmpty() const const
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:79
Response * response() const noexcept
Definition: context.cpp:97
The Cutelyst Engine
Definition: engine.h:20
int compare(const QString &other, Qt::CaseSensitivity cs) const const
The Cutelyst Dispatcher.
Definition: dispatcher.h:27
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void finalize()
finalize the request right away this is automatically called at the end of the actions chain ...
Definition: context.cpp:477
View * customView() const noexcept
Definition: context.cpp:163
int size() const const
QStringList errors() const noexcept
Returns a list of errors that were defined.
Definition: context.cpp:67
QString controllerName() const
void attachAsync()
attachAsync
Definition: context.cpp:349
Dispatcher * dispatcher() const noexcept
Definition: context.cpp:139