cutelyst  4.4.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 
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 
123 bool Dispatcher::dispatch(Context *c)
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 
181 void 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 
282 QString 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 
321 void DispatcherPrivate::printActions() const
322 {
323  QVector<QStringList> table;
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 
349 ActionList 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 
368 Action *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 
380 Action *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 
415 QString 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:35
bool dispatch(Context *c)
Definition: action.h:88
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
Definition: controller.cpp:269
bool _DISPATCH(Context *c)
Definition: controller.cpp:377
Describes a chained dispatch type.
Describes a path dispatch type.
Abstract class to described a dispatch type.
Definition: dispatchtype.h:25
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
Definition: dispatcher.cpp:216
void setupActions(const QVector< Controller * > &controllers, const QVector< DispatchType * > &dispatchers, bool printActions)
Definition: dispatcher.cpp:36
QVector< DispatchType * > dispatchers() const
Definition: dispatcher.cpp:315
QString uriForAction(Action *action, const QStringList &captures) const
Definition: dispatcher.cpp:282
ActionList getActions(QStringView name, QStringView nameSpace) const
Definition: dispatcher.cpp:245
bool forward(Context *c, Component *component)
Definition: dispatcher.cpp:141
Controller * controller(QStringView name) const
Definition: dispatcher.cpp:266
Dispatcher(QObject *parent=nullptr)
Definition: dispatcher.cpp:23
Action * getActionByPath(QStringView path) const
Definition: dispatcher.cpp:232
QList< Controller * > controllers() const
Definition: dispatcher.cpp:272
void prepareAction(Context *c)
Definition: dispatcher.cpp:162
bool dispatch(Context *c)
Definition: dispatcher.cpp:123
Action * expandAction(const Context *c, Action *action) const
Definition: dispatcher.cpp:303
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)