cutelyst 4.0.0
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
23Dispatcher::Dispatcher(QObject *parent)
24 : QObject(parent)
25 , d_ptr(new DispatcherPrivate(this))
26{
27 new DispatchTypePath(parent);
28 new DispatchTypeChained(parent);
29}
30
31Dispatcher::~Dispatcher()
32{
33 delete d_ptr;
34}
35
36void Dispatcher::setupActions(const QVector<Controller *> &controllers,
37 const QVector<Cutelyst::DispatchType *> &dispatchers,
38 bool printActions)
39{
40 Q_D(Dispatcher);
41
42 d->dispatchers = dispatchers;
43
44 ActionList registeredActions;
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 auto it = d->actionContainer.find(action->ns());
71 if (it != d->actionContainer.end()) {
72 it->actions << action;
73 } else {
74 d->actionContainer.insert(action->ns(), {action->ns(), {action}});
75 }
76
77 registeredActions.append(action);
78 instanceUsed = true;
79 } else {
80 qCDebug(CUTELYST_DISPATCHER)
81 << "The action" << action->name() << "of" << action->controller()->objectName()
82 << "controller was not registered in any dispatcher."
83 " If you still want to access it internally (via actionFor())"
84 " you may make it's method private.";
85 }
86 }
87
88 if (instanceUsed) {
89 d->controllers.insert(controller->objectName(), {controller->objectName(), controller});
90 }
91 }
92
93 if (printActions) {
94 d->printActions();
95 }
96
97 // Cache root actions, BEFORE the controllers set them
98 d->rootActions = d->actionContainer.value(u"").actions;
99
100 for (Controller *controller : controllers) {
101 controller->d_ptr->setupFinished();
102 }
103
104 // Unregister any dispatcher that is not in use
105 int i = 0;
106 while (i < d->dispatchers.size()) {
107 DispatchType *type = d->dispatchers.at(i);
108 if (!type->inUse()) {
109 d->dispatchers.removeAt(i);
110 continue;
111 }
112 ++i;
113 }
114
115 if (printActions) {
116 // List all public actions
117 for (DispatchType *dispatch : dispatchers) {
118 qCDebug(CUTELYST_DISPATCHER) << dispatch->list().constData();
119 }
120 }
121}
122
124{
125 Action *action = c->action();
126 if (action) {
127 return action->controller()->_DISPATCH(c);
128 } else {
129 const QString path = c->req()->path();
130 if (path.isEmpty()) {
131 c->appendError(c->translate("Cutelyst::Dispatcher", "No default action defined"));
132 } else {
133 c->appendError(
134 c->translate("Cutelyst::Dispatcher", "Unknown resource '%1'.").arg(path));
135 }
136 }
137 return false;
138}
139
141{
142 Q_ASSERT(component);
143 // If the component was an Action
144 // the dispatch() would call c->execute
145 return c->execute(component);
146}
147
148bool Dispatcher::forward(Context *c, QStringView opname)
149{
150 Q_D(const Dispatcher);
151
152 Action *action = d->command2Action(c, opname, c->request()->args());
153 if (action) {
154 return action->dispatch(c);
155 }
156
157 qCCritical(CUTELYST_DISPATCHER) << "Action not found" << opname << c->request()->args();
158 return false;
159}
160
162{
163 Q_D(Dispatcher);
164
165 Request *request = c->request();
166 d->prepareAction(c, request->path());
167
168 static const bool log = CUTELYST_DISPATCHER().isDebugEnabled();
169 if (log) {
170 if (!request->match().isEmpty()) {
171 qCDebug(CUTELYST_DISPATCHER) << "Path is" << request->match();
172 }
173
174 if (!request->args().isEmpty()) {
175 qCDebug(CUTELYST_DISPATCHER) << "Arguments are" << request->args().join(u'/');
176 }
177 }
178}
179
180void DispatcherPrivate::prepareAction(Context *c, QStringView path) const
181{
182 QStringList args;
183
184 // "/foo/bar"
185 // "/foo/" skip
186 // "/foo"
187 // "/"
188 Q_FOREVER
189 {
190 // Check out the dispatch types to see if any
191 // will handle the path at this level
192 for (DispatchType *type : dispatchers) {
193 if (type->match(c, path, args) == DispatchType::ExactMatch) {
194 return;
195 }
196 }
197
198 // leave the loop if we are at the root "/"
199 if (path.isEmpty()) {
200 break;
201 }
202
203 int pos = path.lastIndexOf(u'/');
204
205 args.emplaceFront(path.mid(pos + 1).toString());
206
207 path.truncate(pos);
208 }
209}
210
211Action *Dispatcher::getAction(QStringView name, QStringView nameSpace) const
212{
213 Q_D(const Dispatcher);
214
215 if (name.isEmpty()) {
216 return nullptr;
217 }
218
219 if (nameSpace.isEmpty()) {
220 const QString normName = u'/' + name;
221 return d->actions.value(normName).action;
222 }
223
224 return getActionByPath(QString{nameSpace + u'/' + name});
225}
226
227Action *Dispatcher::getActionByPath(QStringView path) const
228{
229 Q_D(const Dispatcher);
230
231 int slashes = path.count(u'/');
232 if (slashes == 0) {
233 return d->actions.value(QString{u'/' + path}).action;
234 } else if (path.startsWith(u'/') && slashes != 1) {
235 return d->actions.value(path.mid(1)).action;
236 }
237 return d->actions.value(path).action;
238}
239
240ActionList Dispatcher::getActions(QStringView name, QStringView nameSpace) const
241{
242 Q_D(const Dispatcher);
243
244 ActionList ret;
245
246 if (name.isEmpty()) {
247 return ret;
248 }
249
250 const ActionList containers = d->getContainers(nameSpace);
251 auto rIt = containers.rbegin();
252 while (rIt != containers.rend()) {
253 if ((*rIt)->name() == name) {
254 ret.append(*rIt);
255 }
256 ++rIt;
257 }
258 return ret;
259}
260
261Controller *Dispatcher::controller(QStringView name) const
262{
263 Q_D(const Dispatcher);
264 return d->controllers.value(name).controller;
265}
266
267QList<Controller *> Dispatcher::controllers() const
268{
269 Q_D(const Dispatcher);
270 QList<Controller *> ret;
271 for (const auto &value : d->controllers) {
272 ret.append(value.controller);
273 }
274 return ret;
275}
276
277QString Dispatcher::uriForAction(Action *action, const QStringList &captures) const
278{
279 Q_D(const Dispatcher);
280 QString ret;
281 if (Q_UNLIKELY(action == nullptr)) {
282 qCCritical(CUTELYST_DISPATCHER) << "Dispatcher::uriForAction called with null action";
283 ret = u"/"_qs;
284 } else {
285 for (DispatchType *dispatch : d->dispatchers) {
286 ret = dispatch->uriForAction(action, captures);
287 if (!ret.isNull()) {
288 if (ret.isEmpty()) {
289 ret = u"/"_qs;
290 }
291 break;
292 }
293 }
294 }
295 return ret;
296}
297
299{
300 Q_D(const Dispatcher);
301 for (DispatchType *dispatch : d->dispatchers) {
302 Action *expandedAction = dispatch->expandAction(c, action);
303 if (expandedAction) {
304 return expandedAction;
305 }
306 }
307 return action;
308}
309
310QVector<DispatchType *> Dispatcher::dispatchers() const
311{
312 Q_D(const Dispatcher);
313 return d->dispatchers;
314}
315
316void DispatcherPrivate::printActions() const
317{
318 QVector<QStringList> table;
319
320 auto keys = actions.keys();
321 std::sort(keys.begin(), keys.end());
322 for (const auto &key : keys) {
323 Action *action = actions.value(key).action;
324 QString path = key.toString();
325 if (!path.startsWith(u'/')) {
326 path.prepend(u'/');
327 }
328
329 QStringList row;
330 row.append(path);
331 row.append(action->className());
332 row.append(action->name());
333 table.append(row);
334 }
335
336 qCDebug(CUTELYST_DISPATCHER) << Utils::buildTable(table,
337 {QLatin1String("Private"),
338 QLatin1String("Class"),
339 QLatin1String("Method")},
340 QLatin1String("Loaded Private actions:"))
341 .constData();
342}
343
344ActionList DispatcherPrivate::getContainers(QStringView ns) const
345{
346 ActionList ret;
347
348 if (ns.compare(u"/") != 0) {
349 int pos = ns.size();
350 // qDebug() << pos << ns.mid(0, pos);
351 while (pos > 0) {
352 // qDebug() << pos << ns.mid(0, pos);
353 ret.append(actionContainer.value(ns.mid(0, pos)).actions);
354 pos = ns.lastIndexOf(u'/', pos - 1);
355 }
356 }
357 // qDebug() << actionContainer.size() << rootActions;
358 ret.append(rootActions);
359
360 return ret;
361}
362
363Action *DispatcherPrivate::command2Action(Context *c,
364 QStringView command,
365 const QStringList &args) const
366{
367 auto it = actions.constFind(command);
368 if (it != actions.constEnd()) {
369 return it.value().action;
370 }
371
372 return invokeAsPath(c, command, args);
373}
374
375Action *DispatcherPrivate::invokeAsPath(Context *c,
376 QStringView relativePath,
377 const QStringList &args) const
378{
379 Q_UNUSED(args);
380 Q_Q(const Dispatcher);
381
382 Action *ret;
383 const QString path = DispatcherPrivate::actionRel2Abs(c, relativePath);
384 QStringView pathView{path};
385
386 int pos = pathView.lastIndexOf(u'/');
387 int lastPos = pathView.size();
388 do {
389 if (pos == -1) {
390 ret = q->getAction(pathView);
391 if (ret) {
392 return ret;
393 }
394 } else {
395 const auto name = pathView.mid(pos + 1, lastPos);
396 pathView = pathView.mid(0, pos);
397 ret = q->getAction(name, pathView);
398 if (ret) {
399 return ret;
400 }
401 }
402
403 lastPos = pos;
404 pos = pathView.indexOf(u'/', pos - 1);
405 } while (pos != -1);
406
407 return nullptr;
408}
409
410QString DispatcherPrivate::actionRel2Abs(Context *c, QStringView path)
411{
412 QString ret;
413 if (path.startsWith(u'/')) {
414 ret = path.mid(1).toString();
415 return ret;
416 }
417
418 const QString ns = qobject_cast<Action *>(c->stack().constLast())->ns();
419 if (ns.isEmpty()) {
420 ret = path.toString();
421 } else {
422 ret = ns + QLatin1Char('/') + path;
423 }
424 return ret;
425}
426
427#include "moc_dispatcher.cpp"
This class represents a Cutelyst Action.
Definition: action.h:35
QString ns() const noexcept
Definition: action.cpp:118
bool dispatch(Context *c)
Definition: action.h:81
QString className() const noexcept
Definition: action.cpp:86
Controller * controller() const noexcept
Definition: action.cpp:92
The Cutelyst Component base class.
Definition: component.h:26
QString name() const noexcept
Definition: component.cpp:33
The Cutelyst Context.
Definition: context.h:38
QStack< Component * > stack() const noexcept
Definition: context.cpp:223
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:477
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
Cutelyst Controller base class
Definition: controller.h:87
ActionList actions() const noexcept
Definition: controller.cpp:46
bool _DISPATCH(Context *c)
Definition: controller.cpp:154
virtual bool inUse()=0
virtual QByteArray list() const =0
list the registered actions To be implemented by subclasses
virtual MatchType match(Context *c, QStringView path, const QStringList &args) const =0
The Cutelyst Dispatcher.
Definition: dispatcher.h:27
Action * getAction(QStringView name, QStringView nameSpace={}) const
Definition: dispatcher.cpp:211
void setupActions(const QVector< Controller * > &controllers, const QVector< DispatchType * > &dispatchers, bool printActions)
Definition: dispatcher.cpp:36
QVector< DispatchType * > dispatchers() const
Definition: dispatcher.cpp:310
QString uriForAction(Action *action, const QStringList &captures) const
Definition: dispatcher.cpp:277
ActionList getActions(QStringView name, QStringView nameSpace) const
Definition: dispatcher.cpp:240
bool forward(Context *c, Component *component)
Definition: dispatcher.cpp:140
Controller * controller(QStringView name) const
Definition: dispatcher.cpp:261
Dispatcher(QObject *parent=nullptr)
Definition: dispatcher.cpp:23
Action * getActionByPath(QStringView path) const
Definition: dispatcher.cpp:227
QList< Controller * > controllers() const
Definition: dispatcher.cpp:267
void prepareAction(Context *c)
Definition: dispatcher.cpp:161
bool dispatch(Context *c)
Definition: dispatcher.cpp:123
Action * expandAction(const Context *c, Action *action) const
Definition: dispatcher.cpp:298
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
QVector< Action * > ActionList
Definition: action.h:154