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