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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | Q_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 | */ |
181 | QHttpServerRouterRule::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 | */ |
194 | QHttpServerRouterRule::QHttpServerRouterRule(QHttpServerRouterRulePrivate *d) |
195 | : d_ptr(d) |
196 | { |
197 | } |
198 | |
199 | /*! |
200 | Destroys a QHttpServerRouterRule. |
201 | */ |
202 | QHttpServerRouterRule::~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 | */ |
210 | const 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 | */ |
219 | bool 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 | */ |
233 | bool 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 | */ |
258 | bool 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 | */ |
273 | bool 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 ®exp = 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 | |
327 | QT_END_NAMESPACE |
328 | |