1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtHttpServer/qhttpserverrouterrule.h>
5#include <QtHttpServer/qhttpserverresponder.h>
6
7#include <private/qhttpserverrouterrule_p.h>
8#include <private/qhttpserverrequest_p.h>
9
10#include <QtCore/qmetaobject.h>
11#include <QtCore/qloggingcategory.h>
12#include <QtCore/qregularexpression.h>
13#include <QtCore/qstringbuilder.h>
14#include <QtCore/qdebug.h>
15
16QT_BEGIN_NAMESPACE
17
18Q_LOGGING_CATEGORY(lcRouterRule, "qt.httpserver.router.rule")
19
20/*!
21 \class QHttpServerRouterRule
22 \since 6.4
23 \brief The QHttpServerRouterRule is the base class for QHttpServerRouter rules.
24 \inmodule QtHttpServer
25
26 QHttpServerRouterRule expresses the connection between a request path, an
27 HTTP request method, and the respective handler callback. The \l
28 QHttpServerRouter is a collection of such rules from which the handlers are
29 called if the path and request method match the request. The handler
30 callback must provide the response to the request.
31
32 \section1 Path and Patterns
33
34 Every QHttpServerRouterRule contains a path or path pattern which defines
35 the paths for which it can provide a response through its handler. The path
36 can contain placeholders that are forwarded to the rule's handler. The
37 following examples of path patterns are shown with the \l
38 QHttpServer::route convenience method, but can also be provided to the
39 QHttpServerRouterRule constructor.
40
41 In the simplest case the path is a string with a leading "/":
42 \code
43 QHttpServer server;
44 server.route("/user", [] () { return "hello user"; } );
45 \endcode
46 This path pattern creates a rule that forwards all requests with "/user"
47 to the provided hanlder, which in this case is a simple lambda (Note that
48 the handler syntax would look different when using QHttpServerRouterRule
49 directly, see below).
50
51 The path pattern can further contain a trailing "/" to create a rule that
52 addresses a collection of paths with arguments after the trailing "/". The
53 argument will be forwarded to the Rule as a \l QRegularExpressionMatch.
54 Using the QHttpServer::route convenience method the argument is directly
55 forwarded to the lambda:
56 \code
57 server.route("/user/", [] ( qint64 id ) { return "hello user"; } );
58 \endcode
59 This would match the request urls "/user/1", "/user/2" and so on.
60
61 The argument can be posititioned freely with the path pattern by using the
62 "<arg>" placeholder. This keyword further allows multiple placeholder.
63 \code
64 server.route("/user/<arg>/history", [] (qint64 id){ return "hello user"; } );
65 server.route("/user/<arg>/history/", [] (qint64 id, qint64 page){ return "hello user"; } );
66 \endcode
67 This would, for example, match the request url "/user/1/history/2".
68 All types which are registered in \l QHttpServerRouter::converters() can be
69 used in the callback and the respective placeholder.
70
71 \section1 Request Method
72
73 Request method is simply one of \l QHttpServerRequest::Method. If no
74 method is provided to any overload of the Rule construction, the rule will
75 match any request method.
76
77 \section1 Handler Signature
78
79 The handler is a callback with the signature
80 \code
81 void (*)(const QRegularExpressionMatch &, const QHttpServerRequest &, QHttpServerResponder &);
82 \endcode
83
84 The handler callback receives any matched placeholders as its first argument.
85 The second argument contains details about the request and the response has
86 to be written on the last argument by the handler.
87
88 The following code example shows how new rules with the respective handler can be created and
89 added to a \l QHttpServerRouter:
90 \code
91 template<typename ViewHandler>
92 void route(const char *path, const QHttpServerRequest::Methods methods, ViewHandler &&viewHandler)
93 {
94 auto rule = std::make_unique<QHttpServerRouterRule>(
95 path, methods, [this, viewHandler = std::forward<ViewHandler>(viewHandler)]
96 (QRegularExpressionMatch &match,
97 const QHttpServerRequest &request,
98 QHttpServerResponder &responder) mutable {
99 auto boundViewHandler = QHttpServerRouterRule::bindCaptured<ViewHandler>(
100 this, std::move(viewHandler), match);
101 // call viewHandler
102 boundViewHandler();
103 });
104
105 // QHttpServerRouter
106 router.addRule<ViewHandler>(std::move(rule));
107 }
108
109 // Valid:
110 route("/user/", [] (qint64 id) { } ); // "/user/1"
111 // "/user/3"
112 //
113 route("/user/<arg>/history", [] (qint64 id) { } ); // "/user/1/history"
114 // "/user/2/history"
115 //
116 route("/user/<arg>/history/", [] (qint64 id, qint64 page) { } ); // "/user/1/history/1"
117 // "/user/2/history/2"
118 \endcode
119
120 \note This is a low level API, see \l QHttpServer for higher level alternatives.
121
122 \note Regular expressions in the path pattern are not supported, but
123 can be registered (to match a use of "<arg>" to a specific type) using
124 \l QHttpServerRouter::addConverter().
125*/
126
127/*! \fn template <typename Functor, typename ViewTraits = QHttpServerRouterViewTraits<Functor>> static typename ViewTraits::BindableType QHttpServerRouterRule::bindCaptured(QObject *receiver, Functor &&slot, const QRegularExpressionMatch &match) const
128
129 Supplies the \a receiver and \a slot with arguments derived from a URL.
130 Returns the bound function that accepts whatever remaining arguments the
131 handler may take, supplying them to the slot after the URL-derived values.
132 Each match of the regex applied to the URL (as a string) is converted to
133 the type of the handler's parameter at its position, so that it can be
134 passed as \a match.
135
136 \code
137 QHttpServerRouter router;
138
139 auto pageView = [] (const QString &page, const quint32 num) {
140 qDebug("page: %s, num: %d", qPrintable(page), num);
141 };
142 using ViewHandler = decltype(pageView);
143
144 auto rule = std::make_unique<QHttpServerRouterRule>(
145 "/<arg>/<arg>/log",
146 [&router, &pageView] (QRegularExpressionMatch &match,
147 const QHttpServerRequest &request,
148 QHttpServerResponder &&responder) {
149 // Bind and call viewHandler with match's captured string and quint32:
150 QHttpServerRouterRule::bindCaptured(pageView, match)();
151 });
152
153 router.addRule<ViewHandler>(std::move(rule));
154 \endcode
155*/
156
157/*! \fn template <typename Functor> QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern, const QHttpServerRequest::Methods methods, const QObject *receiver, Functor &&slot)
158
159 Constructs a rule for \a pathPattern, \a methods and connects it to \a
160 receiver and \a slot. The \a slot can also be a function pointer,
161 non-mutable lambda, or any other copiable callable with const call
162 operator. In that case the \a receiver will be a context object. The
163 handler will be valid until the receiver object is destroyed.
164
165 The rule accepts any combinations of available HTTP methods.
166
167 \sa QHttpServerRequest::Methods
168*/
169
170/*! \fn template <typename Functor> QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern, const QObject *receiver, Functor &&slot)
171
172 \overload
173
174 Constructs a rule for \a pathPattern, \l
175 QHttpServerRequest::Method::AnyKnown and connects it to \a receiver and \a
176 slot. The \a slot can also be a function pointer, non-mutable lambda, or
177 any other copiable callable with const call operator. In that case the \a
178 receiver will be a context object. The handler will be valid until the
179 receiver object is destroyed.
180*/
181QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern,
182 const QHttpServerRequest::Methods methods,
183 const QObject *context,
184 QtPrivate::QSlotObjectBase *slotObjRaw)
185 : QHttpServerRouterRule(new QHttpServerRouterRulePrivate{
186 .pathPattern: pathPattern, .methods: methods, .routerHandler: QtPrivate::SlotObjUniquePtr(slotObjRaw), .context: QPointer(context), .pathRegexp: {}})
187{
188 Q_ASSERT(slotObjRaw);
189}
190
191/*!
192 \internal
193*/
194QHttpServerRouterRule::QHttpServerRouterRule(QHttpServerRouterRulePrivate *d)
195 : d_ptr(d)
196{
197}
198
199/*!
200 Destroys a QHttpServerRouterRule.
201*/
202QHttpServerRouterRule::~QHttpServerRouterRule()
203{
204}
205
206/*!
207 Returns the context object of this rule. This is the receiver that has to
208 handle the request.
209*/
210const QObject *QHttpServerRouterRule::contextObject() const
211{
212 Q_D(const QHttpServerRouterRule);
213 return d->context;
214}
215
216/*!
217 Returns \c true if the methods is valid
218*/
219bool QHttpServerRouterRule::hasValidMethods() const
220{
221 Q_D(const QHttpServerRouterRule);
222 return d->methods & QHttpServerRequest::Method::AnyKnown;
223}
224
225/*!
226 Executes this rule for the given \a request.
227
228 This function is called by \l QHttpServerRouter when it receives a new
229 request. If the given \a request matches this rule, this function handles
230 the request by delivering a response to the given \a responder, then returns
231 \c true. Otherwise, it returns \c false.
232*/
233bool QHttpServerRouterRule::exec(const QHttpServerRequest &request,
234 QHttpServerResponder &responder) const
235{
236 Q_D(const QHttpServerRouterRule);
237 if (!d->routerHandler)
238 return false;
239
240 QRegularExpressionMatch match;
241 if (!matches(request, match: &match))
242 return false;
243
244 void *args[] = { nullptr, &match, const_cast<QHttpServerRequest *>(&request), &responder };
245 Q_ASSERT(d->routerHandler);
246 d->routerHandler->call(r: nullptr, a: args);
247 return true;
248}
249
250/*!
251 Determines whether a given \a request matches this rule.
252
253 This virtual function is called by exec() to check if \a request matches
254 this rule. If a match is found, it is stored in the object pointed to by
255 \a match (which \e{must not} be \nullptr) and this function returns
256 \c true. Otherwise, it returns \c false.
257*/
258bool QHttpServerRouterRule::matches(const QHttpServerRequest &request,
259 QRegularExpressionMatch *match) const
260{
261 Q_D(const QHttpServerRouterRule);
262
263 if (d->methods && !(d->methods & request.method()))
264 return false;
265
266 *match = d->pathRegexp.match(subject: request.url().path());
267 return (match->hasMatch() && d->pathRegexp.captureCount() == match->lastCapturedIndex());
268}
269
270/*!
271 \internal
272*/
273bool QHttpServerRouterRule::createPathRegexp(std::initializer_list<QMetaType> metaTypes,
274 const QHash<QMetaType, QString> &converters)
275{
276 Q_D(QHttpServerRouterRule);
277
278 QString pathRegexp = d->pathPattern;
279 const QLatin1StringView arg("<arg>");
280 for (auto metaType : metaTypes) {
281 if (metaType.id() >= QMetaType::User
282 && !QMetaType::hasRegisteredConverterFunction(fromType: QMetaType::fromType<QString>(), toType: metaType)) {
283 qCWarning(lcRouterRule,
284 "%s has not registered a converter to QString. "
285 "Use QHttpServerRouter::addConveter<Type>(converter).",
286 metaType.name());
287 return false;
288 }
289
290 auto it = converters.constFind(key: metaType);
291 if (it == converters.end()) {
292 qCWarning(lcRouterRule, "Can not find converter for type: %s", metaType.name());
293 return false;
294 }
295
296 if (it->isEmpty())
297 continue;
298
299 const auto index = pathRegexp.indexOf(s: arg);
300 const QString &regexp = QLatin1Char('(') % *it % QLatin1Char(')');
301 if (index == -1)
302 pathRegexp.append(s: regexp);
303 else
304 pathRegexp.replace(i: index, len: arg.size(), after: regexp);
305 }
306
307 if (pathRegexp.indexOf(s: arg) != -1) {
308 qCWarning(lcRouterRule) << "not enough types or one of the types is not supported, regexp:"
309 << pathRegexp
310 << ", pattern:" << d->pathPattern
311 << ", types:" << metaTypes;
312 return false;
313 }
314
315 if (!pathRegexp.startsWith(c: QLatin1Char('^')))
316 pathRegexp = QLatin1Char('^') % pathRegexp;
317 if (!pathRegexp.endsWith(c: QLatin1Char('$')))
318 pathRegexp += u'$';
319
320 qCDebug(lcRouterRule) << "url pathRegexp:" << pathRegexp;
321
322 d->pathRegexp.setPattern(pathRegexp);
323 d->pathRegexp.optimize();
324 return true;
325}
326
327QT_END_NAMESPACE
328

source code of qthttpserver/src/httpserver/qhttpserverrouterrule.cpp