cutelyst 4.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;
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);
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
55void Context::appendError(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
66QStringList Context::errors() const noexcept
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 noexcept
145{
146 Q_D(const Context);
147 return d->action->className();
148}
149
150Controller *Context::controller() const noexcept
151{
152 Q_D(const Context);
153 return d->action->controller();
154}
155
156Controller *Context::controller(QStringView name) const
157{
158 Q_D(const Context);
159 return d->dispatcher->controller(name);
160}
161
162View *Context::customView() const noexcept
163{
164 Q_D(const Context);
165 return d->view;
166}
167
168View *Context::view(QStringView name) const
169{
170 Q_D(const Context);
171 return d->app->view(name);
172}
173
174bool Context::setCustomView(QStringView name)
175{
176 Q_D(Context);
177 d->view = d->app->view(name);
178 return d->view;
179}
180
181QVariantHash &Context::stash()
182{
183 Q_D(Context);
184 return d->stash;
185}
186
187QVariant Context::stash(const QString &key) const
188{
189 Q_D(const Context);
190 return d->stash.value(key);
191}
192
193QVariant Context::stash(const QString &key, const QVariant &defaultValue) const
194{
195 Q_D(const Context);
196 return d->stash.value(key, defaultValue);
197}
198
199QVariant Context::stashTake(const QString &key)
200{
201 Q_D(Context);
202 return d->stash.take(key);
203}
204
205bool Context::stashRemove(const QString &key)
206{
207 Q_D(Context);
208 return d->stash.remove(key);
209}
210
211void Context::setStash(const QString &key, const QVariant &value)
212{
213 Q_D(Context);
214 d->stash.insert(key, value);
215}
216
217void Context::setStash(const QString &key, const ParamsMultiMap &map)
218{
219 Q_D(Context);
220 d->stash.insert(key, QVariant::fromValue(map));
221}
222
223QStack<Component *> Context::stack() const noexcept
224{
225 Q_D(const Context);
226 return d->stack;
227}
228
229QUrl Context::uriFor(const QString &path,
230 const QStringList &args,
231 const ParamsMultiMap &queryValues) const
232{
233 Q_D(const Context);
234
235 QUrl uri = d->request->uri();
236
237 QString _path;
238 if (path.isEmpty()) {
239 // ns must NOT return a leading slash
240 const QString controllerNS = d->action->controller()->ns();
241 if (!controllerNS.isEmpty()) {
242 _path.prepend(controllerNS);
243 }
244 } else {
245 _path = path;
246 }
247
248 if (!args.isEmpty()) {
249 if (_path.compare(u"/") == 0) {
250 _path += args.join(u'/');
251 } else {
252 _path = _path + u'/' + args.join(u'/');
253 }
254 }
255
256 if (!_path.startsWith(u'/')) {
257 _path.prepend(u'/');
258 }
259 uri.setPath(_path, QUrl::DecodedMode);
260
261 QUrlQuery query;
262 if (!queryValues.isEmpty()) {
263 // Avoid a trailing '?'
264 if (queryValues.size()) {
265 auto it = queryValues.constEnd();
266 while (it != queryValues.constBegin()) {
267 --it;
268 query.addQueryItem(it.key(), it.value());
269 }
270 }
271 }
272 uri.setQuery(query);
273
274 return uri;
275}
276
278 const QStringList &captures,
279 const QStringList &args,
280 const ParamsMultiMap &queryValues) const
281{
282 Q_D(const Context);
283
284 QUrl uri;
285 if (action == nullptr) {
286 action = d->action;
287 }
288
289 QStringList localArgs = args;
290 QStringList localCaptures = captures;
291
292 Action *expandedAction = d->dispatcher->expandAction(this, action);
293 if (expandedAction->numberOfCaptures() > 0) {
294 while (localCaptures.size() < expandedAction->numberOfCaptures() && localArgs.size()) {
295 localCaptures.append(localArgs.takeFirst());
296 }
297 } else {
298 QStringList localCapturesAux = localCaptures;
299 localCapturesAux.append(localArgs);
300 localArgs = localCapturesAux;
301 localCaptures = QStringList();
302 }
303
304 const QString path = d->dispatcher->uriForAction(action, localCaptures);
305 if (path.isEmpty()) {
306 qCWarning(CUTELYST_CORE) << "Can not find action for" << action << localCaptures;
307 return uri;
308 }
309
310 uri = uriFor(path, localArgs, queryValues);
311 return uri;
312}
313
314QUrl Context::uriForAction(QStringView path,
315 const QStringList &captures,
316 const QStringList &args,
317 const ParamsMultiMap &queryValues) const
318{
319 Q_D(const Context);
320
321 QUrl uri;
322 Action *action = d->dispatcher->getActionByPath(path);
323 if (!action) {
324 qCWarning(CUTELYST_CORE) << "Can not find action for" << path;
325 return uri;
326 }
327
328 uri = uriFor(action, captures, args, queryValues);
329 return uri;
330}
331
332bool Context::detached() const noexcept
333{
334 Q_D(const Context);
335 return d->detached;
336}
337
339{
340 Q_D(Context);
341 if (action) {
342 d->dispatcher->forward(this, action);
343 } else {
344 d->detached = true;
345 }
346}
347
348void Context::detachAsync() noexcept
349{
350 Q_D(Context);
351 ++d->actionRefCount;
352}
353
355{
356 Q_D(Context);
357 if (--d->actionRefCount) {
358 return;
359 }
360
361 if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
362 qCWarning(CUTELYST_ASYNC) << "Trying to async attach to a finalized request! Skipping...";
363 return;
364 }
365
366 if (d->engineRequest->status & EngineRequest::Async) {
367 while (d->asyncAction < d->pendingAsync.size()) {
368 Component *action = d->pendingAsync[d->asyncAction++];
369 const bool ret = execute(action);
370
371 if (d->actionRefCount) {
372 return;
373 }
374
375 if (!ret) {
376 break; // we are finished
377 }
378 }
379
380 Q_EMIT d->app->afterDispatch(this);
381
382 finalize();
383 }
384}
385
387{
388 Q_D(Context);
389 return d->dispatcher->forward(this, action);
390}
391
392bool Context::forward(QStringView action)
393{
394 Q_D(Context);
395 return d->dispatcher->forward(this, action);
396}
397
398Action *Context::getAction(QStringView action, QStringView ns) const
399{
400 Q_D(const Context);
401 return d->dispatcher->getAction(action, ns);
402}
403
404QVector<Action *> Context::getActions(QStringView action, QStringView ns) const
405{
406 Q_D(const Context);
407 return d->dispatcher->getActions(action, ns);
408}
409
410QVector<Cutelyst::Plugin *> Context::plugins() const
411{
412 Q_D(const Context);
413 return d->plugins;
414}
415
417{
418 Q_D(Context);
419 Q_ASSERT_X(code, "Context::execute", "trying to execute a null Cutelyst::Component");
420
421 static int recursion =
422 qEnvironmentVariableIsSet("RECURSION") ? qEnvironmentVariableIntValue("RECURSION") : 1000;
423 if (d->stack.size() >= recursion) {
424 QString msg = QStringLiteral("Deep recursion detected (stack size %1) calling %2, %3")
425 .arg(QString::number(d->stack.size()), code->reverse(), code->name());
426 appendError(msg);
427 setState(false);
428 return false;
429 }
430
431 bool ret;
432 d->stack.push(code);
433
434 if (d->stats) {
435 const QString statsInfo = d->statsStartExecute(code);
436
437 ret = code->execute(this);
438
439 // The request might finalize execution before returning
440 // so it's wise to check for d->stats again
441 if (d->stats && !statsInfo.isEmpty()) {
442 d->statsFinishExecute(statsInfo);
443 }
444 } else {
445 ret = code->execute(this);
446 }
447
448 d->stack.pop();
449
450 return ret;
451}
452
453QLocale Context::locale() const noexcept
454{
455 Q_D(const Context);
456 return d->locale;
457}
458
459void Context::setLocale(const QLocale &locale)
460{
461 Q_D(Context);
462 d->locale = locale;
463}
464
465QVariant Context::config(const QString &key, const QVariant &defaultValue) const
466{
467 Q_D(const Context);
468 return d->app->config(key, defaultValue);
469}
470
471QVariantMap Context::config() const noexcept
472{
473 Q_D(const Context);
474 return d->app->config();
475}
476
477QString Context::translate(const char *context,
478 const char *sourceText,
479 const char *disambiguation,
480 int n) const
481{
482 Q_D(const Context);
483 return d->app->translate(d->locale, context, sourceText, disambiguation, n);
484}
485
487{
488 Q_D(Context);
489
490 if (Q_UNLIKELY(d->engineRequest->status & EngineRequest::Finalized)) {
491 qCWarning(CUTELYST_CORE) << "Trying to finalize a finalized request! Skipping...";
492 return;
493 }
494
495 if (d->stats) {
496 qCDebug(CUTELYST_STATS,
497 "Response Code: %d; Content-Type: %s; Content-Length: %s",
498 d->response->status(),
499 d->response->headers().header("Content-Type"_qba, "unknown"_qba).constData(),
500 d->response->headers().header("Content-Length"_qba, "unknown"_qba).constData());
501
502 const std::chrono::duration<double> duration =
503 std::chrono::steady_clock::now() - d->engineRequest->startOfRequest;
504
505 QString average;
506 if (duration.count() == 0.0) {
507 average = QStringLiteral("??");
508 } else {
509 average = QString::number(1.0 / duration.count(), 'f');
510 average.truncate(average.size() - 3);
511 }
512 qCInfo(CUTELYST_STATS) << qPrintable(QStringLiteral("Request took: %1s (%2/s)\n%3")
513 .arg(QString::number(duration.count(), 'f'),
514 average,
515 QString::fromLatin1(d->stats->report())));
516 delete d->stats;
517 d->stats = nullptr;
518 }
519
520 d->engineRequest->finalize();
521}
522
523QString ContextPrivate::statsStartExecute(Component *code)
524{
525 QString actionName;
526 // Skip internal actions
527 if (code->name().startsWith(u'_')) {
528 return actionName;
529 }
530
531 actionName = code->reverse();
532
533 if (qobject_cast<Action *>(code)) {
534 actionName.prepend(u'/');
535 }
536
537 if (stack.size() > 2) {
538 actionName = u"-> " + actionName;
539 actionName =
540 actionName.rightJustified(actionName.size() + stack.size() - 2, QLatin1Char(' '));
541 }
542
543 stats->profileStart(actionName);
544
545 return actionName;
546}
547
548void ContextPrivate::statsFinishExecute(const QString &statsInfo)
549{
550 stats->profileEnd(statsInfo);
551}
552
553void 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:35
virtual qint8 numberOfCaptures() const
Definition: action.cpp:130
The Cutelyst Application.
Definition: application.h:43
Headers & defaultHeaders() noexcept
Definition: application.cpp:80
The Cutelyst Component base class.
Definition: component.h:26
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:38
QStringList errors() const noexcept
Returns a list of errors that were defined.
Definition: context.cpp:66
bool forward(Component *component)
Definition: context.cpp:386
QVector< Plugin * > plugins() const
Definition: context.cpp:410
Context(Application *app)
Constructs a new DUMMY Context object that is child of Application This currently is experimental to ...
Definition: context.cpp:29
void detach(Action *action=nullptr)
Definition: context.cpp:338
QStack< Component * > stack() const noexcept
Definition: context.cpp:223
QVariantHash & stash()
Definition: context.cpp:181
QLocale locale() const noexcept
Definition: context.cpp:453
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
QUrl uriFor(const QString &path={}, const QStringList &args={}, const ParamsMultiMap &queryValues={}) const
Definition: context.cpp:229
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:477
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:211
void finalize()
finalize the request right away this is automatically called at the end of the actions chain
Definition: context.cpp:486
bool stashRemove(const QString &key)
Definition: context.cpp:205
QVariant stashTake(const QString &key)
Definition: context.cpp:199
void attachAsync()
attachAsync
Definition: context.cpp:354
bool setCustomView(QStringView name)
Definition: context.cpp:174
void setLocale(const QLocale &locale)
Definition: context.cpp:459
View * customView() const noexcept
Definition: context.cpp:162
QVector< Action * > getActions(QStringView action, QStringView ns={}) const
Definition: context.cpp:404
bool detached() const noexcept
Definition: context.cpp:332
Action * getAction(QStringView action, QStringView ns={}) const
Definition: context.cpp:398
Dispatcher * dispatcher() const noexcept
Definition: context.cpp:138
Application * app() const noexcept
Definition: context.cpp:90
View * view(QStringView name={}) const
Definition: context.cpp:168
QUrl uriForAction(QStringView path, const QStringList &captures={}, const QStringList &args={}, const ParamsMultiMap &queryValues={}) const
Definition: context.cpp:314
void detachAsync() noexcept
Definition: context.cpp:348
bool execute(Component *code)
Definition: context.cpp:416
void appendError(const QString &error)
Sets an error string and try to stop.
Definition: context.cpp:55
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:87
The Cutelyst Dispatcher.
Definition: dispatcher.h:27
The Cutelyst Engine.
Definition: engine.h:20
QIODevice * body() const noexcept
Definition: request.cpp:179
Cutelyst View abstract view component
Definition: view.h:21
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
QMultiMap< QString, QString > ParamsMultiMap