cutelyst 3.9.1
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
29Context::Context(Application *app)
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);
34 req->body->open(QBuffer::ReadWrite);
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
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
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
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
283QUrl Context::uriFor(Action *action,
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
345void Context::detach(Action *action)
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
365 // ASync might be destroyed at the same stack level it was created
366 // resulting in this method being called while it was caller,
367 // allowing this method to call finished() twice, with
368 // a null context the second time so we check also check
369 // if the action stack is not empty to skip this method
370 if (--d->actionRefCount || !d->stack.isEmpty()) {
371 return;
372 }
373
374 if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
375 qCWarning(CUTELYST_ASYNC) << "Trying to async attach to a finalized request! Skipping...";
376 return;
377 }
378
379 if (d->engineRequest->status & EngineRequest::Async) {
380 while (!d->pendingAsync.isEmpty()) {
381 Component *action = d->pendingAsync.dequeue();
382 const bool ret = execute(action);
383
384 if (d->actionRefCount) {
385 return;
386 }
387
388 if (!ret) {
389 break; // we are finished
390 }
391 }
392
393 Q_EMIT d->app->afterDispatch(this);
394
395 finalize();
396 }
397}
398
400{
401 Q_D(Context);
402 return d->dispatcher->forward(this, action);
403}
404
405bool Context::forward(const QString &action)
406{
407 Q_D(Context);
408 return d->dispatcher->forward(this, action);
409}
410
411Action *Context::getAction(const QString &action, const QString &ns) const
412{
413 Q_D(const Context);
414 return d->dispatcher->getAction(action, ns);
415}
416
418{
419 Q_D(const Context);
420 return d->dispatcher->getActions(action, ns);
421}
422
424{
425 Q_D(const Context);
426 return d->plugins;
427}
428
430{
431 Q_D(Context);
432 Q_ASSERT_X(code, "Context::execute", "trying to execute a null Cutelyst::Component");
433
434 static int recursion =
435 qEnvironmentVariableIsSet("RECURSION") ? qEnvironmentVariableIntValue("RECURSION") : 1000;
436 if (d->stack.size() >= recursion) {
437 QString msg = QStringLiteral("Deep recursion detected (stack size %1) calling %2, %3")
438 .arg(QString::number(d->stack.size()), code->reverse(), code->name());
439 error(msg);
440 setState(false);
441 return false;
442 }
443
444 bool ret;
445 d->stack.push(code);
446
447 if (d->stats) {
448 const QString statsInfo = d->statsStartExecute(code);
449
450 ret = code->execute(this);
451
452 // The request might finalize execution before returning
453 // so it's wise to check for d->stats again
454 if (d->stats && !statsInfo.isEmpty()) {
455 d->statsFinishExecute(statsInfo);
456 }
457 } else {
458 ret = code->execute(this);
459 }
460
461 d->stack.pop();
462
463 return ret;
464}
465
466QLocale Context::locale() const noexcept
467{
468 Q_D(const Context);
469 return d->locale;
470}
471
473{
474 Q_D(Context);
475 d->locale = locale;
476}
477
478QVariant Context::config(const QString &key, const QVariant &defaultValue) const
479{
480 Q_D(const Context);
481 return d->app->config(key, defaultValue);
482}
483
484QVariantMap Context::config() const noexcept
485{
486 Q_D(const Context);
487 return d->app->config();
488}
489
490QString Context::translate(const char *context,
491 const char *sourceText,
492 const char *disambiguation,
493 int n) const
494{
495 Q_D(const Context);
496 return d->app->translate(d->locale, context, sourceText, disambiguation, n);
497}
498
500{
501 Q_D(Context);
502
503 if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
504 qCWarning(CUTELYST_CORE) << "Trying to finalize a finalized request! Skipping...";
505 return;
506 }
507
508 if (d->stats) {
509 qCDebug(CUTELYST_STATS,
510 "Response Code: %d; Content-Type: %s; Content-Length: %s",
511 d->response->status(),
512 qPrintable(d->response->headers().header(QStringLiteral("CONTENT_TYPE"),
513 QStringLiteral("unknown"))),
514 qPrintable(d->response->headers().header(QStringLiteral("CONTENT_LENGTH"),
515 QStringLiteral("unknown"))));
516
517 const double enlapsed = d->engineRequest->elapsed.nsecsElapsed() / 1000000000.0;
518 QString average;
519 if (enlapsed == 0.0) {
520 average = QStringLiteral("??");
521 } else {
522 average = QString::number(1.0 / enlapsed, 'f');
523 average.truncate(average.size() - 3);
524 }
525 qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
526 .arg(QString::number(enlapsed, 'f'),
527 average,
528 QString::fromLatin1(d->stats->report())));
529 delete d->stats;
530 d->stats = nullptr;
531 }
532
533 d->engineRequest->finalize();
534}
535
536QString ContextPrivate::statsStartExecute(Component *code)
537{
538 QString actionName;
539 // Skip internal actions
540 if (code->name().startsWith(u'_')) {
541 return actionName;
542 }
543
544 actionName = code->reverse();
545
546 if (qobject_cast<Action *>(code)) {
547 actionName.prepend(u'/');
548 }
549
550 if (stack.size() > 2) {
551 actionName = u"-> " + actionName;
552 actionName =
553 actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
554 }
555
556 stats->profileStart(actionName);
557
558 return actionName;
559}
560
561void ContextPrivate::statsFinishExecute(const QString &statsInfo)
562{
563 stats->profileEnd(statsInfo);
564}
565
566void Context::stash(const QVariantHash &unite)
567{
568 Q_D(Context);
569 auto it = unite.constBegin();
570 while (it != unite.constEnd()) {
571 d->stash.insert(it.key(), it.value());
572 ++it;
573 }
574}
575
576#include "moc_context.cpp"
577#include "moc_context_p.cpp"
virtual qint8 numberOfCaptures() const noexcept
Definition action.cpp:130
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
QVector< Action * > getActions(const QString &action, const QString &ns={}) const
Definition context.cpp:417
QStringList errors() const noexcept
Returns a list of errors that were defined.
Definition context.cpp:66
bool forward(Component *component)
Definition context.cpp:399
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:423
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:411
QString ns() const noexcept
Definition context.cpp:120
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:466
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:490
void setStash(const QString &key, const QVariant &value)
Definition context.cpp:217
QString actionName() const noexcept
Definition context.cpp:114
void finalize()
finalize the request right away this is automatically called at the end of the actions chain
Definition context.cpp:499
bool stashRemove(const QString &key)
Definition context.cpp:211
Action * action() const noexcept
Definition context.cpp:108
QVariant stashTake(const QString &key)
Definition context.cpp:205
void attachAsync()
attachAsync
Definition context.cpp:361
Request * request() const noexcept
Definition context.cpp:126
void setLocale(const QLocale &locale)
Definition context.cpp:472
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
QString controllerName() const
Definition context.cpp:144
Request * req() const noexcept
Definition context.cpp:132
Controller * controller() const noexcept
Definition context.cpp:150
void detachAsync() noexcept
Definition context.cpp:355
bool execute(Component *code)
Definition context.cpp:429
bool setCustomView(const QString &name)
Definition context.cpp:180
Engine * engine() const noexcept
Definition context.cpp:84
bool state() const noexcept
Definition context.cpp:72
QVariantMap config() const noexcept
Definition context.cpp:484
bool error() const noexcept
Returns true if an error was set.
Definition context.cpp:49
Response * response() const noexcept
Definition context.cpp:96
The Cutelyst Dispatcher.
Definition dispatcher.h:28
Cutelyst View abstract view component
Definition view.h:22
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QMultiMap< QString, QString > ParamsMultiMap
void append(const T &value)
bool isEmpty() const const
int size() const const
T takeFirst()
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, ParsingMode mode)
void setQuery(const QString &query, ParsingMode mode)
void addQueryItem(const QString &key, const QString &value)
QVariant fromValue(const T &value)