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