Cutelyst  3.4.0
context.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 "context_p.h"
19 
20 #include "common.h"
21 #include "request.h"
22 #include "response.h"
23 #include "action.h"
24 #include "dispatcher.h"
25 #include "controller.h"
26 #include "application.h"
27 #include "stats.h"
28 #include "enginerequest.h"
29 
30 #include "config.h"
31 
32 #include <QUrl>
33 #include <QUrlQuery>
34 #include <QCoreApplication>
35 #include <QBuffer>
36 
37 using namespace Cutelyst;
38 
39 Context::Context(ContextPrivate *priv) : d_ptr(priv)
40 {
41 }
42 
44  d_ptr(new ContextPrivate(app, app->engine(), app->dispatcher(), app->plugins()))
45 {
46  auto req = new DummyRequest(this);
47  req->body = new QBuffer(this);
49  req->context = this;
50 
51  d_ptr->response = new Response(app->defaultHeaders(), req);
52  d_ptr->request = new Request(req);
53  d_ptr->request->d_ptr->engine = d_ptr->engine;
54 }
55 
56 Context::~Context()
57 {
58  delete d_ptr->request;
59  delete d_ptr->response;
60  delete d_ptr;
61 }
62 
63 bool Context::error() const noexcept
64 {
65  Q_D(const Context);
66  return !d->error.isEmpty();
67 }
68 
69 void Context::error(const QString &error)
70 {
71  Q_D(Context);
72  if (error.isEmpty()) {
73  d->error.clear();
74  } else {
75  d->error << error;
76  qCCritical(CUTELYST_CORE) << error;
77  }
78 }
79 
80 QStringList Context::errors() const noexcept
81 {
82  Q_D(const Context);
83  return d->error;
84 }
85 
86 bool Context::state() const noexcept
87 {
88  Q_D(const Context);
89  return d->state;
90 }
91 
92 void Context::setState(bool state) noexcept
93 {
94  Q_D(Context);
95  d->state = state;
96 }
97 
98 Engine *Context::engine() const noexcept
99 {
100  Q_D(const Context);
101  return d->engine;
102 }
103 
104 Application *Context::app() const noexcept
105 {
106  Q_D(const Context);
107  return d->app;
108 }
109 
110 Response *Context::response() const noexcept
111 {
112  Q_D(const Context);
113  return d->response;
114 }
115 
116 Response *Context::res() const noexcept
117 {
118  Q_D(const Context);
119  return d->response;
120 }
121 
122 Action *Context::action() const noexcept
123 {
124  Q_D(const Context);
125  return d->action;
126 }
127 
128 QString Context::actionName() const noexcept
129 {
130  Q_D(const Context);
131  return d->action->name();
132 }
133 
134 QString Context::ns() const noexcept
135 {
136  Q_D(const Context);
137  return d->action->ns();
138 }
139 
140 Request *Context::request() const noexcept
141 {
142  Q_D(const Context);
143  return d->request;
144 }
145 
146 Request *Context::req() const noexcept
147 {
148  Q_D(const Context);
149  return d->request;
150 }
151 
153 {
154  Q_D(const Context);
155  return d->dispatcher;
156 }
157 
158 QString Cutelyst::Context::controllerName() const
159 {
160  Q_D(const Context);
161  return QString::fromLatin1(d->action->controller()->metaObject()->className());
162 }
163 
164 Controller *Context::controller() const noexcept
165 {
166  Q_D(const Context);
167  return d->action->controller();
168 }
169 
170 Controller *Context::controller(const QString &name) const
171 {
172  Q_D(const Context);
173  return d->dispatcher->controllers().value(name);
174 }
175 
176 View *Context::customView() const noexcept
177 {
178  Q_D(const Context);
179  return d->view;
180 }
181 
182 View *Context::view(const QString &name) const
183 {
184  Q_D(const Context);
185  return d->app->view(name);
186 }
187 
189 {
190  Q_D(Context);
191  d->view = d->app->view(name);
192  return d->view;
193 }
194 
195 QVariantHash &Context::stash()
196 {
197  Q_D(Context);
198  return d->stash;
199 }
200 
201 QVariant Context::stash(const QString &key) const
202 {
203  Q_D(const Context);
204  return d->stash.value(key);
205 }
206 
207 QVariant Context::stash(const QString &key, const QVariant &defaultValue) const
208 {
209  Q_D(const Context);
210  return d->stash.value(key, defaultValue);
211 }
212 
214 {
215  Q_D(Context);
216  return d->stash.take(key);
217 }
218 
220 {
221  Q_D(Context);
222  return d->stash.remove(key);
223 }
224 
225 void Context::setStash(const QString &key, const QVariant &value)
226 {
227  Q_D(Context);
228  d->stash.insert(key, value);
229 }
230 
231 void Context::setStash(const QString &key, const ParamsMultiMap &map)
232 {
233  Q_D(Context);
234  d->stash.insert(key, QVariant::fromValue(map));
235 }
236 
238 {
239  Q_D(const Context);
240  return d->stack;
241 }
242 
243 QUrl Context::uriFor(const QString &path, const QStringList &args, const ParamsMultiMap &queryValues) const
244 {
245  Q_D(const Context);
246 
247  QUrl uri = d->request->uri();
248 
249  QString _path;
250  if (path.isEmpty()) {
251  // ns must NOT return a leading slash
252  const QString controllerNS = d->action->controller()->ns();
253  if (!controllerNS.isEmpty()) {
254  _path.prepend(controllerNS);
255  }
256  } else {
257  _path = path;
258  }
259 
260  if (!args.isEmpty()) {
261  if (_path.compare(u"/") == 0) {
262  _path += args.join(QLatin1Char('/'));
263  } else {
264  _path = _path + QLatin1Char('/') + args.join(QLatin1Char('/'));
265  }
266  }
267 
268  if (!_path.startsWith(QLatin1Char('/'))) {
269  _path.prepend(QLatin1Char('/'));
270  }
271  uri.setPath(_path, QUrl::DecodedMode);
272 
273  QUrlQuery query;
274  if (!queryValues.isEmpty()) {
275  // Avoid a trailing '?'
276  if (queryValues.size()) {
277  auto it = queryValues.constEnd();
278  while (it != queryValues.constBegin()) {
279  --it;
280  query.addQueryItem(it.key(), it.value());
281  }
282  }
283  }
284  uri.setQuery(query);
285 
286  return uri;
287 }
288 
289 QUrl Context::uriFor(Action *action, const QStringList &captures, const QStringList &args, const ParamsMultiMap &queryValues) const
290 {
291  Q_D(const Context);
292 
293  QUrl uri;
294  Action *localAction = action;
295  if (!localAction) {
296  localAction = d->action;
297  }
298 
299  QStringList localArgs = args;
300  QStringList localCaptures = captures;
301 
302  Action *expandedAction = d->dispatcher->expandAction(this, action);
303  if (expandedAction->numberOfCaptures() > 0) {
304  while (localCaptures.size() < expandedAction->numberOfCaptures()
305  && localArgs.size()) {
306  localCaptures.append(localArgs.takeFirst());
307  }
308  } else {
309  QStringList localCapturesAux = localCaptures;
310  localCapturesAux.append(localArgs);
311  localArgs = localCapturesAux;
312  localCaptures = QStringList();
313  }
314 
315  const QString path = d->dispatcher->uriForAction(localAction, localCaptures);
316  if (path.isEmpty()) {
317  qCWarning(CUTELYST_CORE) << "Can not find action for" << localAction << localCaptures;
318  return uri;
319  }
320 
321  uri = uriFor(path, localArgs, queryValues);
322  return uri;
323 }
324 
325 QUrl Context::uriForAction(const QString &path, const QStringList &captures, const QStringList &args, const ParamsMultiMap &queryValues) const
326 {
327  Q_D(const Context);
328 
329  QUrl uri;
330  Action *action = d->dispatcher->getActionByPath(path);
331  if (!action) {
332  qCWarning(CUTELYST_CORE) << "Can not find action for" << path;
333  return uri;
334  }
335 
336  uri = uriFor(action, captures, args, queryValues);
337  return uri;
338 }
339 
340 bool Context::detached() const noexcept
341 {
342  Q_D(const Context);
343  return d->detached;
344 }
345 
346 void Context::detach(Action *action)
347 {
348  Q_D(Context);
349  if (action) {
350  d->dispatcher->forward(this, action);
351  } else {
352  d->detached = true;
353  }
354 }
355 
356 void Context::detachAsync() noexcept
357 {
358  Q_D(Context);
359  ++d->asyncDetached;
360 }
361 
363 {
364  Q_D(Context);
365  if (--d->asyncDetached) {
366  return;
367  }
368 
369  if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
370  qCWarning(CUTELYST_ASYNC) << "Trying to async attach to a finalized request! Skipping...";
371  return;
372  }
373 
374  if (d->engineRequest->status & EngineRequest::Async) {
375  while (d->asyncAction < d->pendingAsync.size()) {
376  Component *action = d->pendingAsync[d->asyncAction++];
377  const bool ret = execute(action);
378 
379  if (d->asyncDetached) {
380  return;
381  }
382 
383  if (!ret) {
384  break; // we are finished
385  }
386  }
387 
388  Q_EMIT d->app->afterDispatch(this);
389 
390  finalize();
391  }
392 }
393 
395 {
396  Q_D(Context);
397  return d->dispatcher->forward(this, action);
398 }
399 
400 bool Context::forward(const QString &action)
401 {
402  Q_D(Context);
403  return d->dispatcher->forward(this, action);
404 }
405 
406 Action *Context::getAction(const QString &action, const QString &ns) const
407 {
408  Q_D(const Context);
409  return d->dispatcher->getAction(action, ns);
410 }
411 
412 QVector<Action *> Context::getActions(const QString &action, const QString &ns) const
413 {
414  Q_D(const Context);
415  return d->dispatcher->getActions(action, ns);
416 }
417 
419 {
420  Q_D(const Context);
421  return d->plugins;
422 }
423 
425 {
426  Q_D(Context);
427  Q_ASSERT_X(code, "Context::execute", "trying to execute a null Cutelyst::Component");
428 
429  static int recursion = 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  error(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, const char *sourceText, const char *disambiguation, int n) const
485 {
486  Q_D(const Context);
487  return d->app->translate(d->locale, context, sourceText, disambiguation, n);
488 }
489 
491 {
492  Q_D(Context);
493 
494  if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
495  qCWarning(CUTELYST_CORE) << "Trying to finalize a finalized request! Skipping...";
496  return;
497  }
498 
499  if (d->stats) {
500  qCDebug(CUTELYST_STATS, "Response Code: %d; Content-Type: %s; Content-Length: %s",
501  d->response->status(),
502  qPrintable(d->response->headers().header(QStringLiteral("CONTENT_TYPE"), QStringLiteral("unknown"))),
503  qPrintable(d->response->headers().header(QStringLiteral("CONTENT_LENGTH"), QStringLiteral("unknown"))));
504 
505  const double enlapsed = d->engineRequest->elapsed.nsecsElapsed() / 1000000000.0;
506  QString average;
507  if (enlapsed == 0.0) {
508  average = QStringLiteral("??");
509  } else {
510  average = QString::number(1.0 / enlapsed, 'f');
511  average.truncate(average.size() - 3);
512  }
513  qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
514  .arg(QString::number(enlapsed, 'f'),
515  average,
516  QString::fromLatin1(d->stats->report())));
517  delete d->stats;
518  d->stats = nullptr;
519  }
520 
521  d->engineRequest->finalize();
522 }
523 
524 QString ContextPrivate::statsStartExecute(Component *code)
525 {
526  QString actionName;
527  // Skip internal actions
528  if (code->name().startsWith(QLatin1Char('_'))) {
529  return actionName;
530  }
531 
532  actionName = code->reverse();
533 
534  if (qobject_cast<Action *>(code)) {
535  actionName.prepend(QLatin1Char('/'));
536  }
537 
538  if (stack.size() > 2) {
539  actionName = QLatin1String("-> ") + actionName;
540  actionName = actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
541  }
542 
543  stats->profileStart(actionName);
544 
545  return actionName;
546 }
547 
548 void ContextPrivate::statsFinishExecute(const QString &statsInfo)
549 {
550  stats->profileEnd(statsInfo);
551 }
552 
553 void Context::stash(const QVariantHash &unite)
554 {
555  Q_D(Context);
556  auto it = unite.constBegin();
557  while (it != unite.constEnd()) {
558  d->stash.insert(it.key(), it.value());
559  ++it;
560  }
561 }
562 
563 #include "moc_context.cpp"
564 #include "moc_context_p.cpp"
This class represents a Cutelyst Action.
Definition: action.h:48
virtual qint8 numberOfCaptures() const noexcept
Definition: action.cpp:141
The Cutelyst Application.
Definition: application.h:56
Headers & defaultHeaders() noexcept
Definition: application.cpp:95
The Cutelyst Component base class.
Definition: component.h:39
bool execute(Context *c)
Definition: component.cpp:75
QString name() const
Definition: component.cpp:44
QString reverse() const
Definition: component.cpp:56
The Cutelyst Context.
Definition: context.h:52
QStringList errors() const noexcept
Returns a list of errors that were defined.
Definition: context.cpp:80
bool forward(Component *component)
Definition: context.cpp:394
QUrl uriFor(const QString &path=QString(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:243
QVector< Plugin * > plugins() const
Definition: context.cpp:418
Context(Application *app)
Constructs a new DUMMY Context object that is child of Application This currently is experimental to ...
Definition: context.cpp:43
void detach(Action *action=nullptr)
Definition: context.cpp:346
QStack< Component * > stack() const noexcept
Definition: context.cpp:237
QVector< Action * > getActions(const QString &action, const QString &ns=QString()) const
Definition: context.cpp:412
QVariantHash & stash()
Definition: context.cpp:195
QLocale locale() const noexcept
Definition: context.cpp:460
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:92
Response * res() const noexcept
Definition: context.cpp:116
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:484
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:225
void finalize()
finalize the request right away this is automatically called at the end of the actions chain
Definition: context.cpp:490
bool stashRemove(const QString &key)
Definition: context.cpp:219
QVariant stashTake(const QString &key)
Definition: context.cpp:213
void attachAsync()
attachAsync
Definition: context.cpp:362
void setLocale(const QLocale &locale)
Definition: context.cpp:466
View * customView() const noexcept
Definition: context.cpp:176
bool detached() const noexcept
Definition: context.cpp:340
QUrl uriForAction(const QString &path, const QStringList &captures=QStringList(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:325
Dispatcher * dispatcher() const noexcept
Definition: context.cpp:152
Application * app() const noexcept
Definition: context.cpp:104
void detachAsync() noexcept
Definition: context.cpp:356
bool execute(Component *code)
Definition: context.cpp:424
bool setCustomView(const QString &name)
Definition: context.cpp:188
Engine * engine() const noexcept
Definition: context.cpp:98
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:63
View * view(const QString &name=QString()) const
Definition: context.cpp:182
Response * response() const noexcept
Definition: context.cpp:110
Action * getAction(const QString &action, const QString &ns=QString()) const
Definition: context.cpp:406
Cutelyst Controller base class
Definition: controller.h:103
The Cutelyst Dispatcher.
Definition: dispatcher.h:41
The Cutelyst Engine.
Definition: engine.h:34
QIODevice * body() const
Definition: request.cpp:192
Cutelyst View abstract view component
Definition: view.h:35
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)