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 
21 using namespace Cutelyst;
22 
24  : QObject(parent)
25  , d_ptr(new DispatcherPrivate(this))
26 {
29 }
30 
31 Dispatcher::~Dispatcher()
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;
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 
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 
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 
141 bool 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 
173 void 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 
206 Action *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 
237 ActionList 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 
265 QString 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 
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 
299 QString 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 
323 QString 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 
347 void 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 
375 ActionList 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 
394 Action *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 
406 Action *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 
440 QString 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"
bool dispatch(Context *c)
Definition: dispatcher.cpp:117
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString uriForAction(Action *action, const QStringList &captures) const
Definition: dispatcher.cpp:265
bool dispatch(Context *c)
Definition: action.h:81
void setupActions(const QVector< Controller *> &controllers, const QVector< DispatchType *> &dispatchers, bool printActions)
Definition: dispatcher.cpp:36
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:49
QString & prepend(QChar ch)
qsizetype size() const const
bool forward(Context *c, Component *component)
Definition: dispatcher.cpp:133
QString join(QChar separator) const const
void prepareAction(Context *c)
Definition: dispatcher.cpp:154
reverse_iterator rbegin()
QVector< DispatchType * > dispatchers() const
Definition: dispatcher.cpp:293
The Cutelyst Component base class.
Definition: component.h:25
ActionList getActions(const QString &name, const QString &nameSpace) const
Definition: dispatcher.cpp:237
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) 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:38
Cutelyst Controller base class
Definition: controller.h:87
QMap< QString, Controller * > controllers() const
Definition: dispatcher.cpp:259
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString name() const
Definition: component.cpp:33
bool isEmpty() const const
void resize(qsizetype newSize, QChar fillChar)
bool isEmpty() const const
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:490
bool _DISPATCH(Context *c)
Definition: controller.cpp:162
Controller * controller() const
Definition: action.cpp:92
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
bool execute(Component *code)
Definition: context.cpp:429
reverse_iterator rend()
qsizetype count() const const
void prepend(parameter_type value)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QStack< Component * > stack() const noexcept
Definition: context.cpp:229
QString mid(qsizetype position, qsizetype n) const const
Action * expandAction(const Context *c, Action *action) const
Definition: dispatcher.cpp:281
void append(QList< T > &&value)
const QChar at(qsizetype position) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
Action * getAction(const QString &name, const QString &nameSpace=QString()) const
Definition: dispatcher.cpp:206
Action * getActionByPath(const QString &path) const
Definition: dispatcher.cpp:223
QObject * parent() const const
Dispatcher(QObject *parent=nullptr)
Definition: dispatcher.cpp:23
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
The Cutelyst Dispatcher.
Definition: dispatcher.h:27
QString arg(Args &&... args) const const
QString className() const
Definition: action.cpp:86