cutelyst 4.3.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 d_ptr->locale = app->defaultLocale();
41}
42
44{
45 delete d_ptr->request;
46 delete d_ptr->response;
47 delete d_ptr;
48}
49
50bool Context::error() const noexcept
51{
52 Q_D(const Context);
53 return !d->error.isEmpty();
54}
55
56void Context::appendError(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
68{
69 Q_D(const Context);
70 return d->error;
71}
72
73bool Context::state() const noexcept
74{
75 Q_D(const Context);
76 return d->state;
77}
78
79void Context::setState(bool state) noexcept
80{
81 Q_D(Context);
82 d->state = state;
83}
84
85Engine *Context::engine() const noexcept
86{
87 Q_D(const Context);
88 return d->engine;
89}
90
91Application *Context::app() const noexcept
92{
93 Q_D(const Context);
94 return d->app;
95}
96
97Response *Context::response() const noexcept
98{
99 Q_D(const Context);
100 return d->response;
101}
102
103Response *Context::res() const noexcept
104{
105 Q_D(const Context);
106 return d->response;
107}
108
109Action *Context::action() const noexcept
110{
111 Q_D(const Context);
112 return d->action;
113}
114
116{
117 Q_D(const Context);
118 return d->action->name();
119}
120
121QString Context::ns() const noexcept
122{
123 Q_D(const Context);
124 return d->action->ns();
125}
126
127Request *Context::request() const noexcept
128{
129 Q_D(const Context);
130 return d->request;
131}
132
133Request *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 d->action->className();
149}
150
152{
153 Q_D(const Context);
154 return d->action->controller();
155}
156
158{
159 Q_D(const Context);
160 return d->dispatcher->controller(name);
161}
162
163View *Context::customView() const noexcept
164{
165 Q_D(const Context);
166 return d->view;
167}
168
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
182QVariantHash &Context::stash()
183{
184 Q_D(Context);
185 return d->stash;
186}
187
189{
190 Q_D(const Context);
191 return d->stash.value(key);
192}
193
194QVariant 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
212void Context::setStash(const QString &key, const QVariant &value)
213{
214 Q_D(Context);
215 d->stash.insert(key, value);
216}
217
218void 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
231 const QStringList &args,
232 const ParamsMultiMap &queryValues) const
233{
234 Q_D(const Context);
235
236 QUrl uri = d->request->uri();
237
238 QString _path;
239 if (path.isEmpty()) {
240 // ns must NOT return a leading slash
241 const QString controllerNS = d->action->controller()->ns();
242 if (!controllerNS.isEmpty()) {
243 _path.prepend(controllerNS);
244 }
245 } else {
246 _path = path;
247 }
248
249 if (!args.isEmpty()) {
250 if (_path.compare(u"/") == 0) {
251 _path += args.join(u'/');
252 } else {
253 _path = _path + u'/' + args.join(u'/');
254 }
255 }
256
257 if (!_path.startsWith(u'/')) {
258 _path.prepend(u'/');
259 }
260 uri.setPath(_path, QUrl::DecodedMode);
261
262 QUrlQuery query;
263 if (!queryValues.isEmpty()) {
264 // Avoid a trailing '?'
265 if (queryValues.size()) {
266 auto it = queryValues.constEnd();
267 while (it != queryValues.constBegin()) {
268 --it;
269 query.addQueryItem(it.key(), it.value());
270 }
271 }
272 }
273 uri.setQuery(query);
274
275 return uri;
276}
277
279 const QStringList &captures,
280 const QStringList &args,
281 const ParamsMultiMap &queryValues) const
282{
283 Q_D(const Context);
284
285 QUrl uri;
286 if (action == nullptr) {
287 action = d->action;
288 }
289
290 QStringList localArgs = args;
291 QStringList localCaptures = captures;
292
293 Action *expandedAction = d->dispatcher->expandAction(this, action);
294 if (expandedAction->numberOfCaptures() > 0) {
295 while (localCaptures.size() < expandedAction->numberOfCaptures() && localArgs.size()) {
296 localCaptures.append(localArgs.takeFirst());
297 }
298 } else {
299 QStringList localCapturesAux = localCaptures;
300 localCapturesAux.append(localArgs);
301 localArgs = localCapturesAux;
302 localCaptures = QStringList();
303 }
304
305 const QString path = d->dispatcher->uriForAction(action, localCaptures);
306 if (path.isEmpty()) {
307 qCWarning(CUTELYST_CORE) << "Can not find action for" << action << localCaptures;
308 return uri;
309 }
310
311 uri = uriFor(path, localArgs, queryValues);
312 return uri;
313}
314
316 const QStringList &captures,
317 const QStringList &args,
318 const ParamsMultiMap &queryValues) const
319{
320 Q_D(const Context);
321
322 QUrl uri;
323 Action *action = d->dispatcher->getActionByPath(path);
324 if (!action) {
325 qCWarning(CUTELYST_CORE) << "Can not find action for" << path;
326 return uri;
327 }
328
329 uri = uriFor(action, captures, args, queryValues);
330 return uri;
331}
332
333bool Context::detached() const noexcept
334{
335 Q_D(const Context);
336 return d->detached;
337}
338
340{
341 Q_D(Context);
342 if (action) {
343 d->dispatcher->forward(this, action);
344 } else {
345 d->detached = true;
346 }
347}
348
349void Context::detachAsync() noexcept
350{
351 Q_D(Context);
352 ++d->actionRefCount;
353}
354
356{
357 Q_D(Context);
358
359 // ASync might be destroyed at the same stack level it was created
360 // resulting in this method being called while it was caller,
361 // allowing this method to call finished() twice, with
362 // a null context the second time so we check also check
363 // if the action stack is not empty to skip this method
364 if (--d->actionRefCount || !d->stack.isEmpty()) {
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->pendingAsync.isEmpty()) {
375 Component *action = d->pendingAsync.dequeue();
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
400{
401 Q_D(Context);
402 return d->dispatcher->forward(this, action);
403}
404
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 appendError(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 d->response->headers().header("Content-Type"_qba, "unknown"_qba).constData(),
507 d->response->headers()
508 .header("Content-Length"_qba, QByteArray::number(d->response->size()))
509 .constData());
510
511 const std::chrono::duration<double> duration =
512 std::chrono::steady_clock::now() - d->engineRequest->startOfRequest;
513
514 QString average;
515 if (duration.count() == 0.0) {
516 average = QStringLiteral("??");
517 } else {
518 average = QString::number(1.0 / duration.count(), 'f');
519 average.truncate(average.size() - 3);
520 }
521 qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
522 .arg(QString::number(duration.count(), 'f'),
523 average,
524 QString::fromLatin1(d->stats->report())));
525 delete d->stats;
526 d->stats = nullptr;
527 }
528
529 d->engineRequest->finalize();
530}
531
532QString ContextPrivate::statsStartExecute(Component *code)
533{
534 QString actionName;
535 // Skip internal actions
536 if (code->name().startsWith(u'_')) {
537 return actionName;
538 }
539
540 actionName = code->reverse();
541
542 if (qobject_cast<Action *>(code)) {
543 actionName.prepend(u'/');
544 }
545
546 if (stack.size() > 2) {
547 actionName = u"-> " + actionName;
548 actionName =
549 actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
550 }
551
552 stats->profileStart(actionName);
553
554 return actionName;
555}
556
557void ContextPrivate::statsFinishExecute(const QString &statsInfo)
558{
559 stats->profileEnd(statsInfo);
560}
561
562void Context::stash(const QVariantHash &unite)
563{
564 Q_D(Context);
565 auto it = unite.constBegin();
566 while (it != unite.constEnd()) {
567 d->stash.insert(it.key(), it.value());
568 ++it;
569 }
570}
571
572#include "moc_context.cpp"
573#include "moc_context_p.cpp"
This class represents a Cutelyst Action.
Definition action.h:36
virtual qint8 numberOfCaptures() const
Definition action.cpp:130
The Cutelyst application.
Definition application.h:66
Headers & defaultHeaders() noexcept
QLocale defaultLocale() const noexcept
The Cutelyst Component base class.
Definition component.h:30
QString reverse() const noexcept
Definition component.cpp:45
bool execute(Context *c)
Definition component.cpp:64
QString name() const noexcept
Definition component.cpp:33
The Cutelyst Context.
Definition context.h:42
QStringList errors() const noexcept
Definition context.cpp:67
bool forward(Component *component)
Definition context.cpp:393
QVector< Plugin * > plugins() const
Definition context.cpp:417
Context(Application *app)
Definition context.cpp:29
virtual ~Context()
Definition context.cpp:43
void detach(Action *action=nullptr)
Definition context.cpp:339
QStack< Component * > stack() const noexcept
Definition context.cpp:224
QVariantHash & stash()
Definition context.cpp:182
QLocale locale() const noexcept
Definition context.cpp:460
void setState(bool state) noexcept
Definition context.cpp:79
QUrl uriFor(const QString &path={}, const QStringList &args={}, const ParamsMultiMap &queryValues={}) const
Definition context.cpp:230
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:484
Request * request
Definition context.h:71
void setStash(const QString &key, const QVariant &value)
Definition context.cpp:212
Request * req
Definition context.h:66
bool stashRemove(const QString &key)
Definition context.cpp:206
QVariant stashTake(const QString &key)
Definition context.cpp:200
bool setCustomView(QStringView name)
Definition context.cpp:175
Controller * controller
Definition context.h:75
void setLocale(const QLocale &locale)
Definition context.cpp:466
View * customView() const noexcept
Definition context.cpp:163
QVector< Action * > getActions(QStringView action, QStringView ns={}) const
Definition context.cpp:411
bool detached() const noexcept
Definition context.cpp:333
Action * action
Definition context.h:47
QVariantMap config
Definition context.h:86
Action * getAction(QStringView action, QStringView ns={}) const
Definition context.cpp:405
Dispatcher * dispatcher() const noexcept
Definition context.cpp:139
Application * app() const noexcept
Definition context.cpp:91
View * view(QStringView name={}) const
Definition context.cpp:169
QUrl uriForAction(QStringView path, const QStringList &captures={}, const QStringList &args={}, const ParamsMultiMap &queryValues={}) const
Definition context.cpp:315
void detachAsync() noexcept
Definition context.cpp:349
bool execute(Component *code)
Definition context.cpp:423
QString actionName
Definition context.h:51
void appendError(const QString &error)
Definition context.cpp:56
QString controllerName
Definition context.h:79
Engine * engine() const noexcept
Definition context.cpp:85
bool error() const noexcept
Definition context.cpp:50
Response * response() const noexcept
Definition context.cpp:97
Cutelyst Controller base class.
Definition controller.h:56
The Cutelyst Dispatcher.
Definition dispatcher.h:29
The Cutelyst Engine.
Definition engine.h:20
A request.
Definition request.h:42
QIODevice * body() const noexcept
Definition request.cpp:179
A Cutelyst response.
Definition response.h:29
Abstract View component for Cutelyst.
Definition view.h:25
The Cutelyst namespace holds all public Cutelyst API.
QByteArray number(double n, char format, int precision)
virtual bool open(QIODeviceBase::OpenMode mode)
void append(QList::parameter_type value)
bool isEmpty() const const
qsizetype size() const const
QList::value_type takeFirst()
QMultiMap::const_iterator constBegin() const const
QMultiMap::const_iterator constEnd() const const
bool isEmpty() const const
QMultiMap::size_type size() const const
Q_EMITQ_EMIT
QString arg(Args &&... args) const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
QString rightJustified(qsizetype width, QChar fill, bool truncate) const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
void truncate(qsizetype position)
QString join(QChar 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)