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 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | Q_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 | */ |
88 | QHttpServerRouterRule::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 | */ |
104 | QHttpServerRouterRule::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 | */ |
117 | QHttpServerRouterRule::QHttpServerRouterRule(QHttpServerRouterRulePrivate *d) |
118 | : d_ptr(d) |
119 | { |
120 | } |
121 | |
122 | /*! |
123 | Destroys a QHttpServerRouterRule. |
124 | */ |
125 | QHttpServerRouterRule::~QHttpServerRouterRule() |
126 | { |
127 | } |
128 | |
129 | /*! |
130 | Returns \c true if the methods is valid |
131 | */ |
132 | bool 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 | */ |
146 | bool 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 | */ |
167 | bool 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 | */ |
182 | bool 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 ®exp = 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 | |
236 | QT_END_NAMESPACE |
237 | |