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
15QT_BEGIN_NAMESPACE
16
17Q_LOGGING_CATEGORY(lcRouter, "qt.httpserver.router")
18
19using namespace Qt::StringLiterals;
20
21/*!
22 \internal
23*/
24static 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
174QHttpServerRouterPrivate::QHttpServerRouterPrivate()
175 : converters(defaultConverters)
176{}
177
178/*!
179 Creates a QHttpServerRouter object with default converters.
180
181 \sa converters()
182*/
183QHttpServerRouter::QHttpServerRouter()
184 : d_ptr(new QHttpServerRouterPrivate)
185{}
186
187/*!
188 Destroys a QHttpServerRouter.
189*/
190QHttpServerRouter::~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*/
199void 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*/
208void 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*/
221void 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*/
247const QHash<QMetaType, QString> &QHttpServerRouter::converters() const
248{
249 Q_D(const QHttpServerRouter);
250 return d->converters;
251}
252
253bool 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*/
273bool 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
285QT_END_NAMESPACE
286

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