1 | // Copyright (C) 2019 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include "qhttpserverrouter_p.h" |
5 | |
6 | #include <QtHttpServer/qhttpserverrouter.h> |
7 | #include <QtHttpServer/qhttpserverrouterrule.h> |
8 | #include <QtHttpServer/qhttpserverrequest.h> |
9 | |
10 | #include <private/qhttpserverrouterrule_p.h> |
11 | |
12 | #include <QtCore/qloggingcategory.h> |
13 | #include <QtCore/qmetatype.h> |
14 | |
15 | QT_BEGIN_NAMESPACE |
16 | |
17 | Q_LOGGING_CATEGORY(lcRouter, "qt.httpserver.router" ) |
18 | |
19 | using namespace Qt::StringLiterals; |
20 | |
21 | /*! |
22 | \internal |
23 | */ |
24 | static const QHash<QMetaType, QString> defaultConverters = { |
25 | { QMetaType::fromType<int>(), u"[+-]?\\d+"_s }, |
26 | { QMetaType::fromType<long>(), u"[+-]?\\d+"_s }, |
27 | { QMetaType::fromType<long long>(), u"[+-]?\\d+"_s }, |
28 | { QMetaType::fromType<short>(), u"[+-]?\\d+"_s }, |
29 | |
30 | { QMetaType::fromType<unsigned int>(), u"[+]?\\d+"_s }, |
31 | { QMetaType::fromType<unsigned long>(), u"[+]?\\d+"_s }, |
32 | { QMetaType::fromType<unsigned long long>(), u"[+]?\\d+"_s }, |
33 | { QMetaType::fromType<unsigned short>(), u"[+]?\\d+"_s }, |
34 | |
35 | { QMetaType::fromType<double>(), u"[+-]?(?:[0-9]+(?:[.][0-9]*)?|[.][0-9]+)"_s }, |
36 | { QMetaType::fromType<float>(), u"[+-]?(?:[0-9]+(?:[.][0-9]*)?|[.][0-9]+)"_s }, |
37 | |
38 | { QMetaType::fromType<QString>(), u"[^/]+"_s }, |
39 | { QMetaType::fromType<QByteArray>(), u"[^/]+"_s }, |
40 | |
41 | { QMetaType::fromType<QUrl>(), u".*"_s }, |
42 | |
43 | { QMetaType::fromType<void>(), u""_s }, |
44 | }; |
45 | |
46 | /*! |
47 | \class QHttpServerRouter |
48 | \since 6.4 |
49 | \brief Provides functions to bind a URL to a \c ViewHandler. |
50 | \inmodule QtHttpServer |
51 | |
52 | You can register \c ViewHandler as a callback for requests to a specific URL. |
53 | Variable parts in the route can be specified by the arguments in ViewHandler. |
54 | |
55 | \note This is a low-level routing API for an HTTP server. |
56 | |
57 | See the following example: |
58 | |
59 | \code |
60 | auto pageView = [] (const quint64 page) { |
61 | qDebug() << "page" << page; |
62 | }; |
63 | using ViewHandler = decltype(pageView); |
64 | |
65 | QHttpServerRouter router; |
66 | |
67 | // register callback pageView on request "/page/<number>" |
68 | // for example: "/page/10", "/page/15" |
69 | router.addRoute<ViewHandler>( |
70 | new QHttpServerRouterRule("/page/", [=] (QRegularExpressionMatch &match, |
71 | const QHttpServerRequest &, |
72 | QHttpServerResponder &&) { |
73 | auto boundView = router.bindCaptured(pageView, match); |
74 | |
75 | // it calls pageView |
76 | boundView(); |
77 | })); |
78 | \endcode |
79 | */ |
80 | |
81 | /*! \fn template <typename Type> bool QHttpServerRouter::addConverter(QAnyStringView regexp) |
82 | |
83 | Adds a new converter for type \e Type matching regular expression \a regexp, |
84 | and returns \c true if this was successful, otherwise returns \c false. |
85 | |
86 | Automatically try to register an implicit converter from QString to \e Type. |
87 | If there is already a converter of type \e Type, that converter's regexp |
88 | is replaced with \a regexp. |
89 | |
90 | \code |
91 | struct CustomArg { |
92 | int data = 10; |
93 | |
94 | CustomArg() {} ; |
95 | CustomArg(const QString &urlArg) : data(urlArg.toInt()) {} |
96 | }; |
97 | Q_DECLARE_METATYPE(CustomArg); |
98 | |
99 | QHttpServerRouter router; |
100 | router.addConverter<CustomArg>(u"[+-]?\\d+")); |
101 | |
102 | auto pageView = [] (const CustomArg &customArg) { |
103 | qDebug("data: %d", customArg.data); |
104 | }; |
105 | using ViewHandler = decltype(pageView); |
106 | |
107 | auto rule = std::make_unique<QHttpServerRouterRule>( |
108 | "/<arg>/<arg>/log", |
109 | [&router, &pageView] (QRegularExpressionMatch &match, |
110 | const QHttpServerRequest &request, |
111 | QHttpServerResponder &&responder) { |
112 | // Bind and call viewHandler with match's captured string and quint32: |
113 | router.bindCaptured(pageView, match)(); |
114 | }); |
115 | |
116 | router.addRule<ViewHandler>(std::move(rule)); |
117 | \endcode |
118 | */ |
119 | |
120 | /*! \fn template <typename ViewHandler, typename ViewTraits = QHttpServerRouterViewTraits<ViewHandler>> bool QHttpServerRouter::addRule(std::unique_ptr<QHttpServerRouterRule> rule) |
121 | |
122 | Adds a new \a rule and returns \c true if this was successful. |
123 | |
124 | Inside addRule, we determine ViewHandler arguments and generate a list of |
125 | their QMetaType::Type ids. Then we parse the URL and replace each \c <arg> |
126 | with a regexp for its type from the list. |
127 | |
128 | \code |
129 | QHttpServerRouter router; |
130 | |
131 | using ViewHandler = decltype([] (const QString &page, const quint32 num) { }); |
132 | |
133 | auto rule = std::make_unique<QHttpServerRouterRule>( |
134 | "/<arg>/<arg>/log", |
135 | [] (QRegularExpressionMatch &match, |
136 | const QHttpServerRequest &request, |
137 | QHttpServerResponder &&responder) { |
138 | }); |
139 | |
140 | router.addRule<ViewHandler>(std::move(rule)); |
141 | \endcode |
142 | */ |
143 | |
144 | /*! \fn template<typename ViewHandler, typename ViewTraits = QHttpServerRouterViewTraits<ViewHandler>> typename ViewTraits::BindableType QHttpServerRouter::bindCaptured(ViewHandler &&handler, const QRegularExpressionMatch &match) const |
145 | |
146 | Supplies the \a handler with arguments derived from a URL. |
147 | Returns the bound function that accepts whatever remaining arguments the handler may take, |
148 | supplying them to the handler after the URL-derived values. |
149 | Each match of the regex applied to the URL (as a string) is converted to |
150 | the type of the handler's parameter at its position, so that it can be |
151 | passed as \a match. |
152 | |
153 | \code |
154 | QHttpServerRouter router; |
155 | |
156 | auto pageView = [] (const QString &page, const quint32 num) { |
157 | qDebug("page: %s, num: %d", qPrintable(page), num); |
158 | }; |
159 | using ViewHandler = decltype(pageView); |
160 | |
161 | auto rule = std::make_unique<QHttpServerRouterRule>( |
162 | "/<arg>/<arg>/log", |
163 | [&router, &pageView] (QRegularExpressionMatch &match, |
164 | const QHttpServerRequest &request, |
165 | QHttpServerResponder &&responder) { |
166 | // Bind and call viewHandler with match's captured string and quint32: |
167 | router.bindCaptured(pageView, match)(); |
168 | }); |
169 | |
170 | router.addRule<ViewHandler>(std::move(rule)); |
171 | \endcode |
172 | */ |
173 | |
174 | QHttpServerRouterPrivate::QHttpServerRouterPrivate() |
175 | : converters(defaultConverters) |
176 | {} |
177 | |
178 | /*! |
179 | Creates a QHttpServerRouter object with default converters. |
180 | |
181 | \sa converters() |
182 | */ |
183 | QHttpServerRouter::QHttpServerRouter() |
184 | : d_ptr(new QHttpServerRouterPrivate) |
185 | {} |
186 | |
187 | /*! |
188 | Destroys a QHttpServerRouter. |
189 | */ |
190 | QHttpServerRouter::~QHttpServerRouter() |
191 | {} |
192 | |
193 | /*! |
194 | Adds a new converter for type \a metaType matching regular expression \a regexp. |
195 | |
196 | If there is already a converter of type \a metaType, that converter's regexp |
197 | is replaced with \a regexp. |
198 | */ |
199 | void QHttpServerRouter::addConverter(QMetaType metaType, QAnyStringView regexp) |
200 | { |
201 | Q_D(QHttpServerRouter); |
202 | d->converters[metaType] = regexp.toString(); |
203 | } |
204 | |
205 | /*! |
206 | Removes the converter for type \a metaType. |
207 | */ |
208 | void QHttpServerRouter::removeConverter(QMetaType metaType) |
209 | { |
210 | Q_D(QHttpServerRouter); |
211 | d->converters.remove(key: metaType); |
212 | } |
213 | |
214 | /*! |
215 | Removes all converters. |
216 | |
217 | \note clearConverters() does not set up default converters. |
218 | |
219 | \sa converters() |
220 | */ |
221 | void QHttpServerRouter::clearConverters() |
222 | { |
223 | Q_D(QHttpServerRouter); |
224 | d->converters.clear(); |
225 | } |
226 | |
227 | /*! |
228 | Returns a map of converter type and regexp. |
229 | |
230 | The following converters are available by default: |
231 | |
232 | \value QMetaType::Int |
233 | \value QMetaType::Long |
234 | \value QMetaType::LongLong |
235 | \value QMetaType::Short |
236 | \value QMetaType::UInt |
237 | \value QMetaType::ULong |
238 | \value QMetaType::ULongLong |
239 | \value QMetaType::UShort |
240 | \value QMetaType::Double |
241 | \value QMetaType::Float |
242 | \value QMetaType::QString |
243 | \value QMetaType::QByteArray |
244 | \value QMetaType::QUrl |
245 | \value QMetaType::Void An empty converter. |
246 | */ |
247 | const QHash<QMetaType, QString> &QHttpServerRouter::converters() const |
248 | { |
249 | Q_D(const QHttpServerRouter); |
250 | return d->converters; |
251 | } |
252 | |
253 | bool QHttpServerRouter::addRuleImpl(std::unique_ptr<QHttpServerRouterRule> rule, |
254 | std::initializer_list<QMetaType> metaTypes) |
255 | { |
256 | Q_D(QHttpServerRouter); |
257 | |
258 | if (!rule->hasValidMethods() || !rule->createPathRegexp(metaTypes, converters: d->converters)) { |
259 | return false; |
260 | } |
261 | |
262 | d->rules.push_back(x: std::move(rule)); |
263 | return true; |
264 | } |
265 | |
266 | /*! |
267 | Handles each new \a request for the HTTP server using \a responder. |
268 | |
269 | Iterates through the list of rules to find the first that matches, |
270 | then executes this rule, returning \c true. Returns \c false if no rule |
271 | matches the request. |
272 | */ |
273 | bool QHttpServerRouter::handleRequest(const QHttpServerRequest &request, |
274 | QHttpServerResponder &responder) const |
275 | { |
276 | Q_D(const QHttpServerRouter); |
277 | for (const auto &rule : d->rules) { |
278 | if (rule->exec(request, responder)) |
279 | return true; |
280 | } |
281 | |
282 | return false; |
283 | } |
284 | |
285 | QT_END_NAMESPACE |
286 | |