cutelyst 3.9.1
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
dispatcher.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 "context.h"
9#include "controller.h"
10#include "controller_p.h"
11#include "dispatcher_p.h"
12#include "dispatchtypechained.h"
13#include "dispatchtypepath.h"
14#include "engine.h"
15#include "request_p.h"
16#include "utils.h"
17
18#include <QMetaMethod>
19#include <QUrl>
20
21using namespace Cutelyst;
22
25 , d_ptr(new DispatcherPrivate(this))
26{
29}
30
31Dispatcher::~Dispatcher()
32{
33 delete d_ptr;
34}
35
38 bool printActions)
39{
40 Q_D(Dispatcher);
41
42 d->dispatchers = dispatchers;
43
44 ActionList registeredActions;
45 for (Controller *controller : controllers) {
46 bool instanceUsed = false;
47 const auto actions = controller->actions();
48 for (Action *action : actions) {
49 bool registered = false;
50 if (!d->actions.contains(action->reverse())) {
51 if (!action->attributes().contains(QStringLiteral("Private"))) {
52 // Register the action with each dispatcher
54 if (dispatch->registerAction(action)) {
55 registered = true;
56 }
57 }
58 } else {
59 // We register private actions
60 registered = true;
61 }
62 }
63
64 // The Begin, Auto, End actions are not
65 // registered by Dispatchers but we need them
66 // as private actions anyway
67 if (registered) {
68 const QString name = action->ns() + QLatin1Char('/') + action->name();
69 d->actions.insert(name, {name, action});
70 d->actionContainer[action->ns()] << action;
71 registeredActions.append(action);
72 instanceUsed = true;
73 } else {
74 qCDebug(CUTELYST_DISPATCHER)
75 << "The action" << action->name() << "of" << action->controller()->objectName()
76 << "controller was not registered in any dispatcher."
77 " If you still want to access it internally (via actionFor())"
78 " you may make it's method private.";
79 }
80 }
81
82 if (instanceUsed) {
83 d->controllers.insert(controller->objectName(), controller);
84 }
85 }
86
87 if (printActions) {
88 d->printActions();
89 }
90
91 // Cache root actions, BEFORE the controllers set them
92 d->rootActions = d->actionContainer.value(QLatin1String(""));
93
94 for (Controller *controller : controllers) {
95 controller->d_ptr->setupFinished();
96 }
97
98 // Unregister any dispatcher that is not in use
99 int i = 0;
100 while (i < d->dispatchers.size()) {
101 DispatchType *type = d->dispatchers.at(i);
102 if (!type->inUse()) {
103 d->dispatchers.removeAt(i);
104 continue;
105 }
106 ++i;
107 }
108
109 if (printActions) {
110 // List all public actions
112 qCDebug(CUTELYST_DISPATCHER) << dispatch->list().constData();
113 }
114 }
115}
116
117bool Dispatcher::dispatch(Context *c)
118{
119 Action *action = c->action();
120 if (action) {
121 return action->controller()->_DISPATCH(c);
122 } else {
123 const QString path = c->req()->path();
124 if (path.isEmpty()) {
125 c->error(c->translate("Cutelyst::Dispatcher", "No default action defined"));
126 } else {
127 c->error(c->translate("Cutelyst::Dispatcher", "Unknown resource '%1'.").arg(path));
128 }
129 }
130 return false;
131}
132
133bool Dispatcher::forward(Context *c, Component *component)
134{
135 Q_ASSERT(component);
136 // If the component was an Action
137 // the dispatch() would call c->execute
138 return c->execute(component);
139}
140
141bool Dispatcher::forward(Context *c, const QString &opname)
142{
143 Q_D(const Dispatcher);
144
145 Action *action = d->command2Action(c, opname, c->request()->args());
146 if (action) {
147 return action->dispatch(c);
148 }
149
150 qCCritical(CUTELYST_DISPATCHER) << "Action not found" << opname << c->request()->args();
151 return false;
152}
153
155{
156 Q_D(Dispatcher);
157
158 Request *request = c->request();
159 d->prepareAction(c, request->path());
160
161 static const auto &log = CUTELYST_DISPATCHER();
162 if (log.isDebugEnabled()) {
163 if (!request->match().isEmpty()) {
164 qCDebug(log) << "Path is" << request->match();
165 }
166
167 if (!request->args().isEmpty()) {
168 qCDebug(log) << "Arguments are" << request->args().join(QLatin1Char('/'));
169 }
170 }
171}
172
173void DispatcherPrivate::prepareAction(Context *c, const QString &requestPath) const
174{
175 QString path = normalizePath(requestPath);
176 QStringList args;
177
178 // "foo/bar"
179 // "foo/" skip
180 // "foo"
181 // ""
182 Q_FOREVER
183 {
184 // Check out the dispatch types to see if any
185 // will handle the path at this level
186 for (DispatchType *type : dispatchers) {
187 if (type->match(c, path, args) == DispatchType::ExactMatch) {
188 return;
189 }
190 }
191
192 // leave the loop if we are at the root "/"
193 if (path.isEmpty()) {
194 break;
195 }
196
197 int pos = path.lastIndexOf(u'/');
198
199 const QString arg = path.mid(pos + 1);
200 args.prepend(arg);
201
202 path.resize(pos);
203 }
204}
205
206Action *Dispatcher::getAction(const QString &name, const QString &nameSpace) const
207{
208 Q_D(const Dispatcher);
209
210 if (name.isEmpty()) {
211 return nullptr;
212 }
213
214 if (nameSpace.isEmpty()) {
215 const QString normName = u'/' + name;
216 return d->actions.value(normName).action;
217 }
218
219 const QString ns = DispatcherPrivate::cleanNamespace(nameSpace);
220 return getActionByPath(ns + u'/' + name);
221}
222
224{
225 Q_D(const Dispatcher);
226
227 QString _path = path;
228 int slashes = _path.count(u'/');
229 if (slashes == 0) {
230 _path.prepend(u'/');
231 } else if (_path.startsWith(u'/') && slashes != 1) {
232 _path.remove(0, 1);
233 }
234 return d->actions.value(_path).action;
235}
236
237ActionList Dispatcher::getActions(const QString &name, const QString &nameSpace) const
238{
239 Q_D(const Dispatcher);
240
241 ActionList ret;
242
243 if (name.isEmpty()) {
244 return ret;
245 }
246
247 const QString ns = DispatcherPrivate::cleanNamespace(nameSpace);
248 const ActionList containers = d->getContainers(ns);
249 auto rIt = containers.rbegin();
250 while (rIt != containers.rend()) {
251 if ((*rIt)->name() == name) {
252 ret.append(*rIt);
253 }
254 ++rIt;
255 }
256 return ret;
257}
258
260{
261 Q_D(const Dispatcher);
262 return d->controllers;
263}
264
265QString Dispatcher::uriForAction(Action *action, const QStringList &captures) const
266{
267 Q_D(const Dispatcher);
268 QString ret;
269 for (DispatchType *dispatch : d->dispatchers) {
270 ret = dispatch->uriForAction(action, captures);
271 if (!ret.isNull()) {
272 if (ret.isEmpty()) {
273 ret = QStringLiteral("/");
274 }
275 break;
276 }
277 }
278 return ret;
279}
280
281Action *Dispatcher::expandAction(const Context *c, Action *action) const
282{
283 Q_D(const Dispatcher);
284 for (DispatchType *dispatch : d->dispatchers) {
285 Action *expandedAction = dispatch->expandAction(c, action);
286 if (expandedAction) {
287 return expandedAction;
288 }
289 }
290 return action;
291}
292
294{
295 Q_D(const Dispatcher);
296 return d->dispatchers;
297}
298
299QString DispatcherPrivate::cleanNamespace(const QString &ns)
300{
301 QString ret = ns;
302 bool lastWasSlash = true; // remove initial slash
303 int nsSize = ns.size();
304 for (int i = 0; i < nsSize; ++i) {
305 // Mark if the last char was a slash
306 // so that two or more consecutive slashes
307 // could be converted to just one
308 // "a///b" -> "a/b"
309 if (ret.at(i) == u'/') {
310 if (lastWasSlash) {
311 ret.remove(i, 1);
312 --nsSize;
313 } else {
314 lastWasSlash = true;
315 }
316 } else {
317 lastWasSlash = false;
318 }
319 }
320 return ret;
321}
322
323QString DispatcherPrivate::normalizePath(const QString &path)
324{
325 QString ret = path;
326 bool lastSlash = true;
327 int i = 0;
328 while (i < ret.size()) {
329 if (ret.at(i) == u'/') {
330 if (lastSlash) {
331 ret.remove(i, 1);
332 continue;
333 }
334 lastSlash = true;
335 } else {
336 lastSlash = false;
337 }
338 ++i;
339 }
340
341 if (ret.endsWith(u'/')) {
342 ret.resize(ret.size() - 1);
343 }
344 return ret;
345}
346
347void DispatcherPrivate::printActions() const
348{
349 QVector<QStringList> table;
350
351 auto keys = actions.keys();
352 std::sort(keys.begin(), keys.end());
353 for (const auto &key : keys) {
354 Action *action = actions.value(key).action;
355 QString path = key.toString();
356 if (!path.startsWith(u'/')) {
357 path.prepend(u'/');
358 }
359
360 QStringList row;
361 row.append(path);
362 row.append(action->className());
363 row.append(action->name());
364 table.append(row);
365 }
366
367 qCDebug(CUTELYST_DISPATCHER) << Utils::buildTable(table,
368 {QLatin1String("Private"),
369 QLatin1String("Class"),
370 QLatin1String("Method")},
371 QLatin1String("Loaded Private actions:"))
372 .constData();
373}
374
375ActionList DispatcherPrivate::getContainers(const QString &ns) const
376{
377 ActionList ret;
378
379 if (ns.compare(u"/") != 0) {
380 int pos = ns.size();
381 // qDebug() << pos << ns.mid(0, pos);
382 while (pos > 0) {
383 // qDebug() << pos << ns.mid(0, pos);
384 ret.append(actionContainer.value(ns.mid(0, pos)));
385 pos = ns.lastIndexOf(QLatin1Char('/'), pos - 1);
386 }
387 }
388 // qDebug() << actionContainer.size() << rootActions;
389 ret.append(rootActions);
390
391 return ret;
392}
393
394Action *DispatcherPrivate::command2Action(Context *c,
395 const QString &command,
396 const QStringList &args) const
397{
398 auto it = actions.constFind(command);
399 if (it != actions.constEnd()) {
400 return it.value().action;
401 }
402
403 return invokeAsPath(c, command, args);
404}
405
406Action *DispatcherPrivate::invokeAsPath(Context *c,
407 const QString &relativePath,
408 const QStringList &args) const
409{
410 Q_UNUSED(args);
411 Q_Q(const Dispatcher);
412
413 Action *ret;
414 QString path = DispatcherPrivate::actionRel2Abs(c, relativePath);
415
416 int pos = path.lastIndexOf(QLatin1Char('/'));
417 int lastPos = path.size();
418 do {
419 if (pos == -1) {
420 ret = q->getAction(path, QString());
421 if (ret) {
422 return ret;
423 }
424 } else {
425 const QString name = path.mid(pos + 1, lastPos);
426 path = path.mid(0, pos);
427 ret = q->getAction(name, path);
428 if (ret) {
429 return ret;
430 }
431 }
432
433 lastPos = pos;
434 pos = path.indexOf(QLatin1Char('/'), pos - 1);
435 } while (pos != -1);
436
437 return nullptr;
438}
439
440QString DispatcherPrivate::actionRel2Abs(Context *c, const QString &path)
441{
442 QString ret;
443 if (path.startsWith(QLatin1Char('/'))) {
444 ret = path.mid(1);
445 return ret;
446 }
447
448 const QString ns = qobject_cast<Action *>(c->stack().constLast())->ns();
449 if (ns.isEmpty()) {
450 ret = path;
451 } else {
452 ret = ns + QLatin1Char('/') + path;
453 }
454 return ret;
455}
456
457#include "moc_dispatcher.cpp"
This class represents a Cutelyst Action.
Definition action.h:35
bool dispatch(Context *c)
Definition action.h:81
QString className() const
Definition action.cpp:86
Controller * controller() const
Definition action.cpp:92
The Cutelyst Component base class.
Definition component.h:26
QString name() const
Definition component.cpp:33
The Cutelyst Context.
Definition context.h:39
QStack< Component * > stack() const noexcept
Definition context.cpp:229
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition context.cpp:490
bool execute(Component *code)
Definition context.cpp:429
bool error() const noexcept
Returns true if an error was set.
Definition context.cpp:49
bool _DISPATCH(Context *c)
virtual bool inUse()=0
The Cutelyst Dispatcher.
Definition dispatcher.h:28
QMap< QString, Controller * > controllers() const
void setupActions(const QVector< Controller * > &controllers, const QVector< DispatchType * > &dispatchers, bool printActions)
Action * getAction(const QString &name, const QString &nameSpace=QString()) const
QVector< DispatchType * > dispatchers() const
Action * getActionByPath(const QString &path) const
QString uriForAction(Action *action, const QStringList &captures) const
bool forward(Context *c, Component *component)
Dispatcher(QObject *parent=nullptr)
ActionList getActions(const QString &name, const QString &nameSpace) const
void prepareAction(Context *c)
bool dispatch(Context *c)
Action * expandAction(const Context *c, Action *action) const
The Cutelyst namespace holds all public Cutelyst API.
Definition Mainpage.dox:8
QVector< Action * > ActionList
Definition action.h:154
void append(const T &value)
bool isEmpty() const const
void prepend(const T &value)
QObject(QObject *parent)
QObject * parent() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
const QChar at(int position) const const
int compare(const QString &other, Qt::CaseSensitivity cs) const const
int count() const const
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
int lastIndexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
QString mid(int position, int n) const const
QString & prepend(QChar ch)
QString & remove(int position, int n)
void resize(int size)
int size() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString join(const QString &separator) const const
void append(const T &value)
reverse_iterator rbegin()
reverse_iterator rend()