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);
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 
145 QString Cutelyst::Context::controllerName() const
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 
157 Controller *Context::controller(const QString &name) const
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"
This class represents a Cutelyst Action.
Definition: action.h:35
virtual qint8 numberOfCaptures() const noexcept
Definition: action.cpp:128
The Cutelyst Application.
Definition: application.h:43
Headers & defaultHeaders() noexcept
Definition: application.cpp:82
The Cutelyst Component base class.
Definition: component.h:26
bool execute(Context *c)
Definition: component.cpp:62
QString name() const
Definition: component.cpp:31
QString reverse() const
Definition: component.cpp:43
The Cutelyst Context.
Definition: context.h:39
QStringList errors() const noexcept
Returns a list of errors that were defined.
Definition: context.cpp:67
bool forward(Component *component)
Definition: context.cpp:381
QUrl uriFor(const QString &path=QString(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:230
QVector< Plugin * > plugins() const
Definition: context.cpp:405
Context(Application *app)
Constructs a new DUMMY Context object that is child of Application This currently is experimental to ...
Definition: context.cpp:30
void detach(Action *action=nullptr)
Definition: context.cpp:333
QStack< Component * > stack() const noexcept
Definition: context.cpp:224
QVector< Action * > getActions(const QString &action, const QString &ns=QString()) const
Definition: context.cpp:399
QVariantHash & stash()
Definition: context.cpp:182
QLocale locale() const noexcept
Definition: context.cpp:447
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 * 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:471
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:212
void finalize()
finalize the request right away this is automatically called at the end of the actions chain
Definition: context.cpp:477
bool stashRemove(const QString &key)
Definition: context.cpp:206
QVariant stashTake(const QString &key)
Definition: context.cpp:200
void attachAsync()
attachAsync
Definition: context.cpp:349
void setLocale(const QLocale &locale)
Definition: context.cpp:453
View * customView() const noexcept
Definition: context.cpp:163
bool detached() const noexcept
Definition: context.cpp:327
QUrl uriForAction(const QString &path, const QStringList &captures=QStringList(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:312
Dispatcher * dispatcher() const noexcept
Definition: context.cpp:139
Application * app() const noexcept
Definition: context.cpp:91
void detachAsync() noexcept
Definition: context.cpp:343
bool execute(Component *code)
Definition: context.cpp:411
bool setCustomView(const QString &name)
Definition: context.cpp:175
Engine * engine() const noexcept
Definition: context.cpp:85
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:50
View * view(const QString &name=QString()) const
Definition: context.cpp:169
Response * response() const noexcept
Definition: context.cpp:97
Action * getAction(const QString &action, const QString &ns=QString()) const
Definition: context.cpp:393
Cutelyst Controller base class
Definition: controller.h:90
The Cutelyst Dispatcher.
Definition: dispatcher.h:28
The Cutelyst Engine.
Definition: engine.h:21
QIODevice * body() const
Definition: request.cpp:179
Cutelyst View abstract view component
Definition: view.h:22
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
virtual bool open(QIODevice::OpenMode mode)
void append(const T &value)
bool isEmpty() const const
int size() const const
T takeFirst()
QMap::const_iterator constBegin() const const
QMap::const_iterator constEnd() const const
bool isEmpty() const const
int size() const const
Q_EMITQ_EMIT
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
int compare(const QString &other, Qt::CaseSensitivity cs) const const
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
QString number(int n, int base)
QString & prepend(QChar ch)
QString rightJustified(int width, QChar fill, bool truncate) const const
int size() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void truncate(int position)
QString join(const QString &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(const T &value)