cutelyst 4.3.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
24 : QObject(parent)
25 , d_ptr(new DispatcherPrivate(this))
26{
29}
30
32{
33 delete d_ptr;
34}
35
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 //% "No default action defined."
132 c->appendError(c->qtTrId("cutelyst-dispatcher-no-default-act"));
133 } else {
134 //% "Unknown resource '%1'."
135 c->appendError(c->qtTrId("cutelyst-dispatcher-unknown-resource").arg(path));
136 }
137 }
138 return false;
139}
140
142{
143 Q_ASSERT(component);
144 // If the component was an Action
145 // the dispatch() would call c->execute
146 return c->execute(component);
147}
148
150{
151 Q_D(const Dispatcher);
152
153 Action *action = d->command2Action(c, opname, c->request()->args());
154 if (action) {
155 return action->dispatch(c);
156 }
157
158 qCCritical(CUTELYST_DISPATCHER) << "Action not found" << opname << c->request()->args();
159 return false;
160}
161
163{
164 Q_D(Dispatcher);
165
166 Request *request = c->request();
167 d->prepareAction(c, request->path());
168
169 static const bool log = CUTELYST_DISPATCHER().isDebugEnabled();
170 if (log) {
171 if (!request->match().isEmpty()) {
172 qCDebug(CUTELYST_DISPATCHER) << "Path is" << request->match();
173 }
174
175 if (!request->args().isEmpty()) {
176 qCDebug(CUTELYST_DISPATCHER) << "Arguments are" << request->args().join(u'/');
177 }
178 }
179}
180
181void DispatcherPrivate::prepareAction(Context *c, QStringView path) const
182{
183 QStringList args;
184
185 // "/foo/bar"
186 // "/foo/" skip
187 // "/foo"
188 // "/"
189 Q_FOREVER
190 {
191 // Check out the dispatch types to see if any
192 // will handle the path at this level
193 for (DispatchType *type : dispatchers) {
194 if (type->match(c, path, args) == DispatchType::ExactMatch) {
195 return;
196 }
197 }
198
199 // leave the loop if we are at the root "/"
200 if (path.length() == 1) {
201 break;
202 }
203
204 int pos = path.lastIndexOf(u'/');
205
206 args.emplaceFront(path.mid(pos + 1).toString());
207
208 if (pos == 0) {
209 path.truncate(pos + 1);
210 } else {
211 path.truncate(pos);
212 }
213 }
214}
215
217{
218 Q_D(const Dispatcher);
219
220 if (name.isEmpty()) {
221 return nullptr;
222 }
223
224 if (nameSpace.isEmpty()) {
225 const QString normName = u'/' + name;
226 return d->actions.value(normName).action;
227 }
228
229 return getActionByPath(QString{nameSpace + u'/' + name});
230}
231
233{
234 Q_D(const Dispatcher);
235
236 int slashes = path.count(u'/');
237 if (slashes == 0) {
238 return d->actions.value(QString{u'/' + path}).action;
239 } else if (path.startsWith(u'/') && slashes != 1) {
240 return d->actions.value(path.mid(1)).action;
241 }
242 return d->actions.value(path).action;
243}
244
246{
247 Q_D(const Dispatcher);
248
249 ActionList ret;
250
251 if (name.isEmpty()) {
252 return ret;
253 }
254
255 const ActionList containers = d->getContainers(nameSpace);
256 auto rIt = containers.rbegin();
257 while (rIt != containers.rend()) {
258 if ((*rIt)->name() == name) {
259 ret.append(*rIt);
260 }
261 ++rIt;
262 }
263 return ret;
264}
265
267{
268 Q_D(const Dispatcher);
269 return d->controllers.value(name).controller;
270}
271
273{
274 Q_D(const Dispatcher);
276 for (const auto &value : d->controllers) {
277 ret.append(value.controller);
278 }
279 return ret;
280}
281
282QString Dispatcher::uriForAction(Action *action, const QStringList &captures) const
283{
284 Q_D(const Dispatcher);
285 QString ret;
286 if (Q_UNLIKELY(action == nullptr)) {
287 qCCritical(CUTELYST_DISPATCHER) << "Dispatcher::uriForAction called with null action";
288 ret = u"/"_qs;
289 } else {
290 for (DispatchType *dispatch : d->dispatchers) {
291 ret = dispatch->uriForAction(action, captures);
292 if (!ret.isNull()) {
293 if (ret.isEmpty()) {
294 ret = u"/"_qs;
295 }
296 break;
297 }
298 }
299 }
300 return ret;
301}
302
304{
305 Q_D(const Dispatcher);
306 for (DispatchType *dispatch : d->dispatchers) {
307 Action *expandedAction = dispatch->expandAction(c, action);
308 if (expandedAction) {
309 return expandedAction;
310 }
311 }
312 return action;
313}
314
316{
317 Q_D(const Dispatcher);
318 return d->dispatchers;
319}
320
321void DispatcherPrivate::printActions() const
322{
324
325 auto keys = actions.keys();
326 std::sort(keys.begin(), keys.end());
327 for (const auto &key : keys) {
328 Action *action = actions.value(key).action;
329 QString path = key.toString();
330 if (!path.startsWith(u'/')) {
331 path.prepend(u'/');
332 }
333
334 QStringList row;
335 row.append(path);
336 row.append(action->className());
337 row.append(action->name());
338 table.append(row);
339 }
340
341 qCDebug(CUTELYST_DISPATCHER) << Utils::buildTable(table,
342 {QLatin1String("Private"),
343 QLatin1String("Class"),
344 QLatin1String("Method")},
345 QLatin1String("Loaded Private actions:"))
346 .constData();
347}
348
349ActionList DispatcherPrivate::getContainers(QStringView ns) const
350{
351 ActionList ret;
352
353 if (ns.compare(u"/") != 0) {
354 int pos = ns.size();
355 // qDebug() << pos << ns.mid(0, pos);
356 while (pos > 0) {
357 // qDebug() << pos << ns.mid(0, pos);
358 ret.append(actionContainer.value(ns.mid(0, pos)).actions);
359 pos = ns.lastIndexOf(u'/', pos - 1);
360 }
361 }
362 // qDebug() << actionContainer.size() << rootActions;
363 ret.append(rootActions);
364
365 return ret;
366}
367
368Action *DispatcherPrivate::command2Action(Context *c,
369 QStringView command,
370 const QStringList &args) const
371{
372 auto it = actions.constFind(command);
373 if (it != actions.constEnd()) {
374 return it.value().action;
375 }
376
377 return invokeAsPath(c, command, args);
378}
379
380Action *DispatcherPrivate::invokeAsPath(Context *c,
381 QStringView relativePath,
382 const QStringList &args) const
383{
384 Q_UNUSED(args);
385 Q_Q(const Dispatcher);
386
387 Action *ret;
388 const QString path = DispatcherPrivate::actionRel2Abs(c, relativePath);
389 QStringView pathView{path};
390
391 int pos = pathView.lastIndexOf(u'/');
392 int lastPos = pathView.size();
393 do {
394 if (pos == -1) {
395 ret = q->getAction(pathView);
396 if (ret) {
397 return ret;
398 }
399 } else {
400 const auto name = pathView.mid(pos + 1, lastPos);
401 pathView = pathView.mid(0, pos);
402 ret = q->getAction(name, pathView);
403 if (ret) {
404 return ret;
405 }
406 }
407
408 lastPos = pos;
409 pos = pathView.indexOf(u'/', pos - 1);
410 } while (pos != -1);
411
412 return nullptr;
413}
414
415QString DispatcherPrivate::actionRel2Abs(Context *c, QStringView path)
416{
417 QString ret;
418 if (path.startsWith(u'/')) {
419 ret = path.mid(1).toString();
420 return ret;
421 }
422
423 const QString ns = qobject_cast<Action *>(c->stack().constLast())->ns();
424 if (ns.isEmpty()) {
425 ret = path.toString();
426 } else {
427 ret = ns + QLatin1Char('/') + path;
428 }
429 return ret;
430}
431
432#include "moc_dispatcher.cpp"
This class represents a Cutelyst Action.
Definition action.h:36
bool dispatch(Context *c)
Definition action.h:89
QString className() const noexcept
Definition action.cpp:86
Controller * controller() const noexcept
Definition action.cpp:92
The Cutelyst Component base class.
Definition component.h:30
QString name() const noexcept
Definition component.cpp:33
The Cutelyst Context.
Definition context.h:42
QStack< Component * > stack() const noexcept
Definition context.cpp:224
Request * request
Definition context.h:71
Request * req
Definition context.h:66
QString qtTrId(const char *id, int n=-1) const
Definition context.h:656
Action * action
Definition context.h:47
bool execute(Component *code)
Definition context.cpp:423
void appendError(const QString &error)
Definition context.cpp:56
Cutelyst Controller base class.
Definition controller.h:56
ActionList actions() const noexcept
bool _DISPATCH(Context *c)
Describes a chained dispatch type.
Describes a path dispatch type.
Abstract class to described a dispatch type.
virtual bool inUse()=0
virtual QByteArray list() const =0
virtual MatchType match(Context *c, QStringView path, const QStringList &args) const =0
The Cutelyst Dispatcher.
Definition dispatcher.h:29
Action * getAction(QStringView name, QStringView nameSpace={}) const
void setupActions(const QVector< Controller * > &controllers, const QVector< DispatchType * > &dispatchers, bool printActions)
QVector< DispatchType * > dispatchers() const
QString uriForAction(Action *action, const QStringList &captures) const
ActionList getActions(QStringView name, QStringView nameSpace) const
bool forward(Context *c, Component *component)
Controller * controller(QStringView name) const
Dispatcher(QObject *parent=nullptr)
Action * getActionByPath(QStringView path) const
QList< Controller * > controllers() const
void prepareAction(Context *c)
bool dispatch(Context *c)
Action * expandAction(const Context *c, Action *action) const
A request.
Definition request.h:42
The Cutelyst namespace holds all public Cutelyst API.
const char * constData() const const
void append(QList::parameter_type value)
bool isEmpty() const const
QList::reverse_iterator rbegin()
QList::reverse_iterator rend()
QObject * parent() const const
QString arg(Args &&... args) const const
bool isEmpty() const const
bool isNull() const const
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs) const const
QString & prepend(QChar ch)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString join(QChar separator) const const
QStringView mid(qsizetype start, qsizetype length) const const
int compare(QChar ch) const const
qsizetype count(QChar ch, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar c, Qt::CaseSensitivity cs) const const
qsizetype length() const const
qsizetype size() const const
bool startsWith(QChar ch) const const
QString toString() const const
void truncate(qsizetype length)