| 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 | |