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
6#include <private/qhttpserverrouterrule_p.h>
7#include <private/qhttpserverrequest_p.h>
8
9#include <QtCore/qmetaobject.h>
10#include <QtCore/qloggingcategory.h>
11#include <QtCore/qregularexpression.h>
12#include <QtCore/qstringbuilder.h>
13#include <QtCore/qdebug.h>
14
15QT_BEGIN_NAMESPACE
16
17Q_LOGGING_CATEGORY(lcRouterRule, "qt.httpserver.router.rule")
18
19/*!
20 \class QHttpServerRouterRule
21 \since 6.4
22 \brief The QHttpServerRouterRule is the base class for QHttpServerRouter rules.
23 \inmodule QtHttpServer
24
25 Use QHttpServerRouterRule to specify expected request parameters:
26
27 \value path QUrl::path()
28 \value HTTP methods QHttpServerRequest::Methods
29 \value callback User-defined response callback
30
31 \note This is a low level API, see QHttpServer for higher level alternatives.
32
33 Example of QHttpServerRouterRule and QHttpServerRouter usage:
34
35 \code
36 template<typename ViewHandler>
37 void route(const char *path, const QHttpServerRequest::Methods methods, ViewHandler &&viewHandler)
38 {
39 auto rule = std::make_unique<QHttpServerRouterRule>(
40 path, methods, [this, viewHandler = std::forward<ViewHandler>(viewHandler)]
41 (QRegularExpressionMatch &match,
42 const QHttpServerRequest &request,
43 QHttpServerResponder &&responder) mutable {
44 auto boundViewHandler = router.bindCaptured<ViewHandler>(
45 std::move(viewHandler), match);
46 // call viewHandler
47 boundViewHandler();
48 });
49
50 // QHttpServerRouter
51 router.addRule<ViewHandler>(std::move(rule));
52 }
53
54 // Valid:
55 route("/user/", [] (qint64 id) { } ); // "/user/1"
56 // "/user/3"
57 //
58 route("/user/<arg>/history", [] (qint64 id) { } ); // "/user/1/history"
59 // "/user/2/history"
60 //
61 route("/user/<arg>/history/", [] (qint64 id, qint64 page) { } ); // "/user/1/history/1"
62 // "/user/2/history/2"
63
64 // Invalid:
65 route("/user/<arg>", [] () { } ); // ERROR: path pattern has <arg>, but ViewHandler does not have any arguments
66 route("/user/\\d+", [] () { } ); // ERROR: path pattern does not support manual regexp
67 \endcode
68
69 \note Regular expressions in the path pattern are not supported, but
70 can be registered (to match a use of "<val>" to a specific type) using
71 QHttpServerRouter::addConverter().
72*/
73
74/*!
75 \typealias QHttpServerRouterRule::RouterHandler
76
77 Type alias for
78 std::function<void(const QRegularExpressionMatch &,const QHttpServerRequest &, QHttpServerResponder &&)>
79 */
80
81/*!
82 Constructs a rule with pathPattern \a pathPattern, and routerHandler \a routerHandler.
83
84 The rule accepts all HTTP methods by default.
85
86 \sa QHttpServerRequest::Methods
87*/
88QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern,
89 RouterHandler routerHandler)
90 : QHttpServerRouterRule(pathPattern,
91 QHttpServerRequest::Method::AnyKnown,
92 std::move(routerHandler))
93{
94}
95
96/*!
97 Constructs a rule with pathPattern \a pathPattern, methods \a methods
98 and routerHandler \a routerHandler.
99
100 The rule accepts any combinations of available HTTP methods.
101
102 \sa QHttpServerRequest::Methods
103*/
104QHttpServerRouterRule::QHttpServerRouterRule(const QString &pathPattern,
105 const QHttpServerRequest::Methods methods,
106 RouterHandler routerHandler)
107 : QHttpServerRouterRule(
108 new QHttpServerRouterRulePrivate{.pathPattern: pathPattern,
109 .methods: methods,
110 .routerHandler: std::move(routerHandler), .pathRegexp: {}})
111{
112}
113
114/*!
115 \internal
116 */
117QHttpServerRouterRule::QHttpServerRouterRule(QHttpServerRouterRulePrivate *d)
118 : d_ptr(d)
119{
120}
121
122/*!
123 Destroys a QHttpServerRouterRule.
124*/
125QHttpServerRouterRule::~QHttpServerRouterRule()
126{
127}
128
129/*!
130 Returns \c true if the methods is valid
131*/
132bool QHttpServerRouterRule::hasValidMethods() const
133{
134 Q_D(const QHttpServerRouterRule);
135 return d->methods & QHttpServerRequest::Method::AnyKnown;
136}
137
138/*!
139 Executes this rule for the given \a request, if it matches.
140
141 This function is called by QHttpServerRouter when it receives a new
142 request. If the given \a request matches this rule, this function handles
143 the request by delivering a response to the given \a responder, then returns
144 \c true. Otherwise, it returns \c false.
145*/
146bool QHttpServerRouterRule::exec(const QHttpServerRequest &request,
147 QHttpServerResponder &responder) const
148{
149 Q_D(const QHttpServerRouterRule);
150
151 QRegularExpressionMatch match;
152 if (!matches(request, match: &match))
153 return false;
154
155 d->routerHandler(match, request, std::move(responder));
156 return true;
157}
158
159/*!
160 Determines whether a given \a request matches this rule.
161
162 This virtual function is called by exec() to check if \a request matches
163 this rule. If a match is found, it is stored in the object pointed to by
164 \a match (which \e{must not} be \nullptr) and this function returns
165 \c true. Otherwise, it returns \c false.
166*/
167bool QHttpServerRouterRule::matches(const QHttpServerRequest &request,
168 QRegularExpressionMatch *match) const
169{
170 Q_D(const QHttpServerRouterRule);
171
172 if (d->methods && !(d->methods & request.method()))
173 return false;
174
175 *match = d->pathRegexp.match(subject: request.url().path());
176 return (match->hasMatch() && d->pathRegexp.captureCount() == match->lastCapturedIndex());
177}
178
179/*!
180 \internal
181*/
182bool QHttpServerRouterRule::createPathRegexp(std::initializer_list<QMetaType> metaTypes,
183 const QHash<QMetaType, QString> &converters)
184{
185 Q_D(QHttpServerRouterRule);
186
187 QString pathRegexp = d->pathPattern;
188 const QLatin1StringView arg("<arg>");
189 for (auto metaType : metaTypes) {
190 if (metaType.id() >= QMetaType::User
191 && !QMetaType::hasRegisteredConverterFunction(fromType: QMetaType::fromType<QString>(), toType: metaType)) {
192 qCWarning(lcRouterRule,
193 "%s has not registered a converter to QString. "
194 "Use QHttpServerRouter::addConveter<Type>(converter).",
195 metaType.name());
196 return false;
197 }
198
199 auto it = converters.constFind(key: metaType);
200 if (it == converters.end()) {
201 qCWarning(lcRouterRule, "Can not find converter for type: %s", metaType.name());
202 return false;
203 }
204
205 if (it->isEmpty())
206 continue;
207
208 const auto index = pathRegexp.indexOf(s: arg);
209 const QString &regexp = QLatin1Char('(') % *it % QLatin1Char(')');
210 if (index == -1)
211 pathRegexp.append(s: regexp);
212 else
213 pathRegexp.replace(i: index, len: arg.size(), after: regexp);
214 }
215
216 if (pathRegexp.indexOf(s: arg) != -1) {
217 qCWarning(lcRouterRule) << "not enough types or one of the types is not supported, regexp:"
218 << pathRegexp
219 << ", pattern:" << d->pathPattern
220 << ", types:" << metaTypes;
221 return false;
222 }
223
224 if (!pathRegexp.startsWith(c: QLatin1Char('^')))
225 pathRegexp = QLatin1Char('^') % pathRegexp;
226 if (!pathRegexp.endsWith(c: QLatin1Char('$')))
227 pathRegexp += u'$';
228
229 qCDebug(lcRouterRule) << "url pathRegexp:" << pathRegexp;
230
231 d->pathRegexp.setPattern(pathRegexp);
232 d->pathRegexp.optimize();
233 return true;
234}
235
236QT_END_NAMESPACE
237

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