cutelyst 5.0.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;
23using namespace Qt::Literals::StringLiterals;
24
25Context::Context(ContextPrivate *priv)
26 : 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 d_ptr->locale = app->defaultLocale();
42}
43
45{
46 delete d_ptr->request;
47 delete d_ptr->response;
48 delete d_ptr;
49}
50
51bool Context::error() const noexcept
52{
53 Q_D(const Context);
54 return !d->error.isEmpty();
55}
56
57void Context::appendError(const QString &error)
58{
59 Q_D(Context);
60 if (error.isEmpty()) {
61 d->error.clear();
62 } else {
63 d->error << error;
64 qCCritical(CUTELYST_CORE) << error;
65 }
66}
67
69{
70 Q_D(const Context);
71 return d->error;
72}
73
74bool Context::state() const noexcept
75{
76 Q_D(const Context);
77 return d->state;
78}
79
80void Context::setState(bool state) noexcept
81{
82 Q_D(Context);
83 d->state = state;
84}
85
86Engine *Context::engine() const noexcept
87{
88 Q_D(const Context);
89 return d->engine;
90}
91
92Application *Context::app() const noexcept
93{
94 Q_D(const Context);
95 return d->app;
96}
97
98Response *Context::response() const noexcept
99{
100 Q_D(const Context);
101 return d->response;
102}
103
104Response *Context::res() const noexcept
105{
106 Q_D(const Context);
107 return d->response;
108}
109
110Action *Context::action() const noexcept
111{
112 Q_D(const Context);
113 return d->action;
114}
115
117{
118 Q_D(const Context);
119 return d->action->name();
120}
121
122QString Context::ns() const noexcept
123{
124 Q_D(const Context);
125 return d->action->ns();
126}
127
128Request *Context::request() const noexcept
129{
130 Q_D(const Context);
131 return d->request;
132}
133
134Request *Context::req() const noexcept
135{
136 Q_D(const Context);
137 return d->request;
138}
139
141{
142 Q_D(const Context);
143 return d->dispatcher;
144}
145
147{
148 Q_D(const Context);
149 return d->action->className();
150}
151
153{
154 Q_D(const Context);
155 return d->action->controller();
156}
157
159{
160 Q_D(const Context);
161 return d->dispatcher->controller(name);
162}
163
164View *Context::customView() const noexcept
165{
166 Q_D(const Context);
167 return d->view;
168}
169
171{
172 Q_D(const Context);
173 return d->app->view(name);
174}
175
177{
178 Q_D(Context);
179 d->view = d->app->view(name);
180 return d->view;
181}
182
183QVariantHash &Context::stash()
184{
185 Q_D(Context);
186 return d->stash;
187}
188
190{
191 Q_D(const Context);
192 return d->stash.value(key);
193}
194
195QVariant Context::stash(const QString &key, const QVariant &defaultValue) const
196{
197 Q_D(const Context);
198 return d->stash.value(key, defaultValue);
199}
200
202{
203 Q_D(Context);
204 return d->stash.take(key);
205}
206
208{
209 Q_D(Context);
210 return d->stash.remove(key);
211}
212
213void Context::setStash(const QString &key, const QVariant &value)
214{
215 Q_D(Context);
216 d->stash.insert(key, value);
217}
218
219void Context::setStash(const QString &key, const ParamsMultiMap &map)
220{
221 Q_D(Context);
222 d->stash.insert(key, QVariant::fromValue(map));
223}
224
226{
227 Q_D(const Context);
228 return d->stack;
229}
230
232 const QStringList &args,
233 const ParamsMultiMap &queryValues) const
234{
235 Q_D(const Context);
236
237 QUrl uri = d->request->uri();
238
239 QString _path;
240 if (path.isEmpty()) {
241 // ns must NOT return a leading slash
242 const QString controllerNS = d->action->controller()->ns();
243 if (!controllerNS.isEmpty()) {
244 _path.prepend(controllerNS);
245 }
246 } else {
247 _path = path;
248 }
249
250 if (!args.isEmpty()) {
251 if (_path.compare(u"/") == 0) {
252 _path += args.join(u'/');
253 } else {
254 _path = _path + u'/' + args.join(u'/');
255 }
256 }
257
258 if (!_path.startsWith(u'/')) {
259 _path.prepend(u'/');
260 }
261 uri.setPath(_path, QUrl::DecodedMode);
262
263 QUrlQuery query;
264 if (!queryValues.isEmpty()) {
265 // Avoid a trailing '?'
266 if (!queryValues.isEmpty()) {
267 auto it = queryValues.constEnd();
268 while (it != queryValues.constBegin()) {
269 --it;
270 query.addQueryItem(it.key(), it.value());
271 }
272 }
273 }
274 uri.setQuery(query);
275
276 return uri;
277}
278
280 const QStringList &captures,
281 const QStringList &args,
282 const ParamsMultiMap &queryValues) const
283{
284 Q_D(const Context);
285
286 QUrl uri;
287 if (action == nullptr) {
288 action = d->action;
289 }
290
291 QStringList localArgs = args;
292 QStringList localCaptures = captures;
293
294 Action *expandedAction = d->dispatcher->expandAction(this, action);
295 if (expandedAction->numberOfCaptures() > 0) {
296 while (localCaptures.size() < expandedAction->numberOfCaptures() && !localArgs.isEmpty()) {
297 localCaptures.append(localArgs.takeFirst());
298 }
299 } else {
300 QStringList localCapturesAux = localCaptures;
301 localCapturesAux.append(localArgs);
302 localArgs = localCapturesAux;
303 localCaptures = QStringList();
304 }
305
306 const QString path = d->dispatcher->uriForAction(action, localCaptures);
307 if (path.isEmpty()) {
308 qCWarning(CUTELYST_CORE) << "Can not find action for" << action << localCaptures;
309 return uri;
310 }
311
312 uri = uriFor(path, localArgs, queryValues);
313 return uri;
314}
315
317 const QStringList &captures,
318 const QStringList &args,
319 const ParamsMultiMap &queryValues) const
320{
321 Q_D(const Context);
322
323 QUrl uri;
324 Action *action = d->dispatcher->getActionByPath(path);
325 if (!action) {
326 qCWarning(CUTELYST_CORE) << "Can not find action for" << path;
327 return uri;
328 }
329
330 uri = uriFor(action, captures, args, queryValues);
331 return uri;
332}
333
334bool Context::detached() const noexcept
335{
336 Q_D(const Context);
337 return d->detached;
338}
339
341{
342 Q_D(Context);
343 if (action) {
344 d->dispatcher->forward(this, action);
345 } else {
346 d->detached = true;
347 }
348}
349
350void Context::detachAsync() noexcept
351{
352 Q_D(Context);
353 ++d->actionRefCount;
354}
355
357{
358 Q_D(Context);
359
360 // ASync might be destroyed at the same stack level it was created
361 // resulting in this method being called while it was caller,
362 // allowing this method to call finished() twice, with
363 // a null context the second time so we check also check
364 // if the action stack is not empty to skip this method
365 if (--d->actionRefCount || !d->stack.isEmpty()) {
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->pendingAsync.isEmpty()) {
376 Component *action = d->pendingAsync.dequeue();
377 const bool ret = execute(action);
378
379 if (d->actionRefCount) {
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
401{
402 Q_D(Context);
403 return d->dispatcher->forward(this, action);
404}
405
407{
408 Q_D(const Context);
409 return d->dispatcher->getAction(action, ns);
410}
411
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 =
430 qEnvironmentVariableIsSet("RECURSION") ? qEnvironmentVariableIntValue("RECURSION") : 1000;
431 if (d->stack.size() >= recursion) {
432 QString msg = QStringLiteral("Deep recursion detected (stack size %1) calling %2, %3")
433 .arg(QString::number(d->stack.size()), code->reverse(), code->name());
434 appendError(msg);
435 setState(false);
436 return false;
437 }
438
439 bool ret;
440 d->stack.push(code);
441
442 if (d->stats) {
443 const QString statsInfo = d->statsStartExecute(code);
444
445 ret = code->execute(this);
446
447 // The request might finalize execution before returning
448 // so it's wise to check for d->stats again
449 if (d->stats && !statsInfo.isEmpty()) {
450 d->statsFinishExecute(statsInfo);
451 }
452 } else {
453 ret = code->execute(this);
454 }
455
456 d->stack.pop();
457
458 return ret;
459}
460
461QLocale Context::locale() const noexcept
462{
463 Q_D(const Context);
464 return d->locale;
465}
466
467void Context::setLocale(const QLocale &locale)
468{
469 Q_D(Context);
470 d->locale = locale;
471}
472
473QVariant Context::config(const QString &key, const QVariant &defaultValue) const
474{
475 Q_D(const Context);
476 return d->app->config(key, defaultValue);
477}
478
479QVariantMap Context::config() const noexcept
480{
481 Q_D(const Context);
482 return d->app->config();
483}
484
485QString Context::translate(const char *context,
486 const char *sourceText,
487 const char *disambiguation,
488 int n) const
489{
490 Q_D(const Context);
491 return d->app->translate(d->locale, context, sourceText, disambiguation, n);
492}
493
495{
496 Q_D(Context);
497
498 if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
499 qCWarning(CUTELYST_CORE) << "Trying to finalize a finalized request! Skipping...";
500 return;
501 }
502
503 if (d->stats) {
504 qCDebug(CUTELYST_STATS,
505 "Response Code: %d; Content-Type: %s; Content-Length: %s",
506 d->response->status(),
507 d->response->headers().header("Content-Type", "unknown"_ba).constData(),
508 d->response->headers()
509 .header("Content-Length", QByteArray::number(d->response->size()))
510 .constData());
511
512 const std::chrono::duration<double> duration =
513 std::chrono::steady_clock::now() - d->engineRequest->startOfRequest;
514
515 QString average;
516 if (duration.count() == 0.0) {
517 average = QStringLiteral("??");
518 } else {
519 average = QString::number(1.0 / duration.count(), 'f');
520 average.truncate(average.size() - 3);
521 }
522 qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
523 .arg(QString::number(duration.count(), 'f'),
524 average,
525 QString::fromLatin1(d->stats->report())));
526 delete d->stats;
527 d->stats = nullptr;
528 }
529
530 d->engineRequest->finalize();
531}
532
533QString ContextPrivate::statsStartExecute(Component *code)
534{
535 QString actionName;
536 // Skip internal actions
537 if (code->name().startsWith(u'_')) {
538 return actionName;
539 }
540
541 actionName = code->reverse();
542
543 if (qobject_cast<Action *>(code)) {
544 actionName.prepend(u'/');
545 }
546
547 if (stack.size() > 2) {
548 actionName = u"-> " + actionName;
549 actionName =
550 actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
551 }
552
553 stats->profileStart(actionName);
554
555 return actionName;
556}
557
558void ContextPrivate::statsFinishExecute(const QString &statsInfo)
559{
560 stats->profileEnd(statsInfo);
561}
562
563void Context::stash(const QVariantHash &unite)
564{
565 Q_D(Context);
566
567 d->stash.insert(unite);
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
Definition action.cpp:131
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:68
bool forward(Component *component)
Definition context.cpp:394
QVector< Plugin * > plugins() const
Definition context.cpp:418
Context(Application *app)
Definition context.cpp:30
virtual ~Context()
Definition context.cpp:44
void detach(Action *action=nullptr)
Definition context.cpp:340
QStack< Component * > stack() const noexcept
Definition context.cpp:225
QVariantHash & stash()
Definition context.cpp:183
QLocale locale() const noexcept
Definition context.cpp:461
void setState(bool state) noexcept
Definition context.cpp:80
QUrl uriFor(const QString &path={}, const QStringList &args={}, const ParamsMultiMap &queryValues={}) const
Definition context.cpp:231
Response * res() const noexcept
Definition context.cpp:104
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition context.cpp:485
Request * request
Definition context.h:71
void setStash(const QString &key, const QVariant &value)
Definition context.cpp:213
Request * req
Definition context.h:66
bool stashRemove(const QString &key)
Definition context.cpp:207
QVariant stashTake(const QString &key)
Definition context.cpp:201
bool setCustomView(QStringView name)
Definition context.cpp:176
Controller * controller
Definition context.h:75
void setLocale(const QLocale &locale)
Definition context.cpp:467
View * customView() const noexcept
Definition context.cpp:164
QVector< Action * > getActions(QStringView action, QStringView ns={}) const
Definition context.cpp:412
bool detached() const noexcept
Definition context.cpp:334
Action * action
Definition context.h:47
QVariantMap config
Definition context.h:86
Action * getAction(QStringView action, QStringView ns={}) const
Definition context.cpp:406
Dispatcher * dispatcher() const noexcept
Definition context.cpp:140
Application * app() const noexcept
Definition context.cpp:92
View * view(QStringView name={}) const
Definition context.cpp:170
QUrl uriForAction(QStringView path, const QStringList &captures={}, const QStringList &args={}, const ParamsMultiMap &queryValues={}) const
Definition context.cpp:316
void detachAsync() noexcept
Definition context.cpp:350
bool execute(Component *code)
Definition context.cpp:424
QString actionName
Definition context.h:51
void appendError(const QString &error)
Definition context.cpp:57
QString controllerName
Definition context.h:79
Engine * engine() const noexcept
Definition context.cpp:86
bool error() const noexcept
Definition context.cpp:51
Response * response() const noexcept
Definition context.cpp:98
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:180
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
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(T &&value)