cutelyst 3.9.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
22using namespace Cutelyst;
23
24Context::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
42Context::~Context()
43{
44 delete d_ptr->request;
45 delete d_ptr->response;
46 delete d_ptr;
47}
48
49bool Context::error() const noexcept
50{
51 Q_D(const Context);
52 return !d->error.isEmpty();
53}
54
55void 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
67{
68 Q_D(const Context);
69 return d->error;
70}
71
72bool Context::state() const noexcept
73{
74 Q_D(const Context);
75 return d->state;
76}
77
78void Context::setState(bool state) noexcept
79{
80 Q_D(Context);
81 d->state = state;
82}
83
84Engine *Context::engine() const noexcept
85{
86 Q_D(const Context);
87 return d->engine;
88}
89
90Application *Context::app() const noexcept
91{
92 Q_D(const Context);
93 return d->app;
94}
95
96Response *Context::response() const noexcept
97{
98 Q_D(const Context);
99 return d->response;
100}
101
102Response *Context::res() const noexcept
103{
104 Q_D(const Context);
105 return d->response;
106}
107
108Action *Context::action() const noexcept
109{
110 Q_D(const Context);
111 return d->action;
112}
113
114QString Context::actionName() const noexcept
115{
116 Q_D(const Context);
117 return d->action->name();
118}
119
120QString Context::ns() const noexcept
121{
122 Q_D(const Context);
123 return d->action->ns();
124}
125
126Request *Context::request() const noexcept
127{
128 Q_D(const Context);
129 return d->request;
130}
131
132Request *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
144QString Cutelyst::Context::controllerName() const
145{
146 Q_D(const Context);
147 return QString::fromLatin1(d->action->controller()->metaObject()->className());
148}
149
150Controller *Context::controller() const noexcept
151{
152 Q_D(const Context);
153 return d->action->controller();
154}
155
156Controller *Context::controller(const QString &name) const
157{
158 Q_D(const Context);
159 return d->dispatcher->controllers().value(name);
160}
161
162View *Context::customView() const noexcept
163{
164 Q_D(const Context);
165 return d->view;
166}
167
168View *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
187QVariantHash &Context::stash()
188{
189 Q_D(Context);
190 return d->stash;
191}
192
194{
195 Q_D(const Context);
196 return d->stash.value(key);
197}
198
199QVariant 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
217void Context::setStash(const QString &key, const QVariant &value)
218{
219 Q_D(Context);
220 d->stash.insert(key, value);
221}
222
223void 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
339bool Context::detached() const noexcept
340{
341 Q_D(const Context);
342 return d->detached;
343}
344
346{
347 Q_D(Context);
348 if (action) {
349 d->dispatcher->forward(this, action);
350 } else {
351 d->detached = true;
352 }
353}
354
355void Context::detachAsync() noexcept
356{
357 Q_D(Context);
358 ++d->actionRefCount;
359}
360
362{
363 Q_D(Context);
364 if (--d->actionRefCount) {
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->asyncAction < d->pendingAsync.size()) {
375 Component *action = d->pendingAsync[d->asyncAction++];
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
399bool Context::forward(const QString &action)
400{
401 Q_D(Context);
402 return d->dispatcher->forward(this, action);
403}
404
405Action *Context::getAction(const QString &action, const QString &ns) const
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 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
460QLocale Context::locale() const noexcept
461{
462 Q_D(const Context);
463 return d->locale;
464}
465
466void Context::setLocale(const QLocale &locale)
467{
468 Q_D(Context);
469 d->locale = locale;
470}
471
472QVariant Context::config(const QString &key, const QVariant &defaultValue) const
473{
474 Q_D(const Context);
475 return d->app->config(key, defaultValue);
476}
477
478QVariantMap Context::config() const noexcept
479{
480 Q_D(const Context);
481 return d->app->config();
482}
483
484QString 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 qPrintable(d->response->headers().header(QStringLiteral("CONTENT_TYPE"),
507 QStringLiteral("unknown"))),
508 qPrintable(d->response->headers().header(QStringLiteral("CONTENT_LENGTH"),
509 QStringLiteral("unknown"))));
510
511 const double enlapsed = d->engineRequest->elapsed.nsecsElapsed() / 1000000000.0;
512 QString average;
513 if (enlapsed == 0.0) {
514 average = QStringLiteral("??");
515 } else {
516 average = QString::number(1.0 / enlapsed, 'f');
517 average.truncate(average.size() - 3);
518 }
519 qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
520 .arg(QString::number(enlapsed, 'f'),
521 average,
522 QString::fromLatin1(d->stats->report())));
523 delete d->stats;
524 d->stats = nullptr;
525 }
526
527 d->engineRequest->finalize();
528}
529
530QString ContextPrivate::statsStartExecute(Component *code)
531{
532 QString actionName;
533 // Skip internal actions
534 if (code->name().startsWith(u'_')) {
535 return actionName;
536 }
537
538 actionName = code->reverse();
539
540 if (qobject_cast<Action *>(code)) {
541 actionName.prepend(u'/');
542 }
543
544 if (stack.size() > 2) {
545 actionName = u"-> " + actionName;
546 actionName =
547 actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
548 }
549
550 stats->profileStart(actionName);
551
552 return actionName;
553}
554
555void ContextPrivate::statsFinishExecute(const QString &statsInfo)
556{
557 stats->profileEnd(statsInfo);
558}
559
560void Context::stash(const QVariantHash &unite)
561{
562 Q_D(Context);
563 auto it = unite.constBegin();
564 while (it != unite.constEnd()) {
565 d->stash.insert(it.key(), it.value());
566 ++it;
567 }
568}
569
570#include "moc_context.cpp"
571#include "moc_context_p.cpp"
This class represents a Cutelyst Action.
Definition: action.h:35
virtual qint8 numberOfCaptures() const noexcept
Definition: action.cpp:130
The Cutelyst Application.
Definition: application.h:43
Headers & defaultHeaders() noexcept
Definition: application.cpp:83
The Cutelyst Component base class.
Definition: component.h:26
bool execute(Context *c)
Definition: component.cpp:64
QString name() const
Definition: component.cpp:33
QString reverse() const
Definition: component.cpp:45
The Cutelyst Context.
Definition: context.h:39
QVector< Action * > getActions(const QString &action, const QString &ns={}) const
Definition: context.cpp:411
QStringList errors() const noexcept
Returns a list of errors that were defined.
Definition: context.cpp:66
bool forward(Component *component)
Definition: context.cpp:393
QUrl uriFor(const QString &path=QString(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:235
QVector< Plugin * > plugins() const
Definition: context.cpp:417
Context(Application *app)
Constructs a new DUMMY Context object that is child of Application This currently is experimental to ...
Definition: context.cpp:29
Action * getAction(const QString &action, const QString &ns={}) const
Definition: context.cpp:405
void detach(Action *action=nullptr)
Definition: context.cpp:345
QStack< Component * > stack() const noexcept
Definition: context.cpp:229
QVariantHash & stash()
Definition: context.cpp:187
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:78
Response * res() const noexcept
Definition: context.cpp:102
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:217
void finalize()
finalize the request right away this is automatically called at the end of the actions chain
Definition: context.cpp:493
bool stashRemove(const QString &key)
Definition: context.cpp:211
QVariant stashTake(const QString &key)
Definition: context.cpp:205
void attachAsync()
attachAsync
Definition: context.cpp:361
void setLocale(const QLocale &locale)
Definition: context.cpp:466
View * customView() const noexcept
Definition: context.cpp:162
bool detached() const noexcept
Definition: context.cpp:339
View * view(const QString &name) const
Definition: context.cpp:168
QUrl uriForAction(const QString &path, const QStringList &captures=QStringList(), const QStringList &args=QStringList(), const ParamsMultiMap &queryValues=ParamsMultiMap()) const
Definition: context.cpp:321
Dispatcher * dispatcher() const noexcept
Definition: context.cpp:138
Application * app() const noexcept
Definition: context.cpp:90
void detachAsync() noexcept
Definition: context.cpp:355
bool execute(Component *code)
Definition: context.cpp:423
bool setCustomView(const QString &name)
Definition: context.cpp:180
Engine * engine() const noexcept
Definition: context.cpp:84
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:49
Response * response() const noexcept
Definition: context.cpp:96
Cutelyst Controller base class
Definition: controller.h:88
The Cutelyst Dispatcher.
Definition: dispatcher.h:28
The Cutelyst Engine.
Definition: engine.h:21
QIODevice * body() const
Definition: request.cpp:181
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)