]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
ext/yahttp: Move route matching to separate function
authorAki Tuomi <cmouse@cmouse.fi>
Wed, 23 Aug 2023 17:13:11 +0000 (20:13 +0300)
committerAki Tuomi <cmouse@cmouse.fi>
Fri, 15 Dec 2023 07:59:57 +0000 (09:59 +0200)
ext/yahttp/yahttp/router.cpp
ext/yahttp/yahttp/router.hpp

index 83465e318a30439f532a1c12f2fd33ceac243fa4..e123b38479637a442ab90eb08b85352e6bb1e0a0 100644 (file)
@@ -5,8 +5,6 @@
 #include "router.hpp"
 
 namespace YaHTTP {
-  typedef funcptr::tuple<int,int> TDelim;
-
   // router is defined here.
   YaHTTP::Router Router::router;
 
@@ -24,86 +22,103 @@ namespace YaHTTP {
     routes.push_back(funcptr::make_tuple(method2, url, handler, name));
   };
 
-  RoutingResult Router::route(Request *req, THandlerFunction& handler) {
-    std::map<std::string, TDelim> params;
-    int pos1,pos2;
-    bool matched = false;
-    bool seen = false;
-    std::string rname;
-
-    // iterate routes
-    for(TRouteList::iterator i = routes.begin(); !matched && i != routes.end(); i++) {
-      int k1,k2,k3;
-      std::string pname;
-      std::string method, url;
-      funcptr::tie(method, url, handler, rname) = *i;
-      matched = false;
-
-      // see if we can't match the url
-      params.clear();
-      // simple matcher func
-      for(k1=0, k2=0; k1 < static_cast<int>(url.size()) && k2 < static_cast<int>(req->url.path.size()); ) {
-        if (url[k1] == '<') {
-          pos1 = k2;
-          k3 = k1+1;
+  bool Router::match(const std::string& route, const URL& requrl, std::map<std::string, TDelim> &params) {
+     size_t rpos = 0;
+     size_t upos = 0;
+     size_t npos = 0;
+     size_t nstart = 0;
+     size_t nend = 0;
+     std::string pname;
+     for(; rpos < route.size() && upos < requrl.path.size(); ) {
+        if (route[rpos] == '<') {
+          nstart = upos;
+          npos = rpos+1;
           // start of parameter
-          while(k1 < static_cast<int>(url.size()) && url[k1] != '>') k1++;
-          pname = std::string(url.begin()+k3, url.begin()+k1);
+          while(rpos < route.size() && route[rpos] != '>') {
+            rpos++;
+          }
+          pname = std::string(route.begin()+static_cast<long>(npos), route.begin()+static_cast<long>(rpos));
           // then we also look it on the url
-          if (pname[0]=='*') {
+          if (pname[0] == '*') {
             pname = pname.substr(1);
             // this matches whatever comes after it, basically end of string
-            pos2 = req->url.path.size();
-            matched = true;
-            if (pname != "") 
-              params[pname] = funcptr::tie(pos1,pos2);
-            k1 = url.size();
-            k2 = req->url.path.size();
+            nend = requrl.path.size();
+            if (!pname.empty()) {
+              params[pname] = funcptr::tie(nstart,nend);
+            }
+            rpos = route.size();
+            upos = requrl.path.size();
             break;
           } else { 
-            // match until url[k1]
-            while(k2 < static_cast<int>(req->url.path.size()) && req->url.path[k2] != url[k1+1]) k2++;
-            pos2 = k2;
-            params[pname] = funcptr::tie(pos1,pos2);
+            // match until url[upos] or next / if pattern is at end
+            while (upos < requrl.path.size()) {
+               if (route[rpos+1] == '\0' && requrl.path[upos] == '/') {
+                  break;
+               }
+               if (requrl.path[upos] == route[rpos+1]) {
+                  break;
+               }
+               upos++;
+            }
+            nend = upos;
+            params[pname] = funcptr::tie(nstart, nend);
           }
-          k2--;
+          upos--;
         }
-        else if (url[k1] != req->url.path[k2]) {
+        else if (route[rpos] != requrl.path[upos]) {
           break;
         }
 
-        k1++; k2++;
+        rpos++; upos++;
       }
+      return route[rpos] == requrl.path[upos];
+  }
 
-      // ensure.
-      if (url[k1] != req->url.path[k2]) 
-        matched = false;
-      else
-        matched = true;
+  RoutingResult Router::route(Request *req, THandlerFunction& handler) {
+    std::map<std::string, TDelim> params;
+    bool matched = false;
+    bool seen = false;
+    std::string rname;
+
+    // iterate routes
+    for (auto& route: routes) {
+      std::string method;
+      std::string url;
+      funcptr::tie(method, url, handler, rname) = route;
 
-      if (matched && method.empty() == false && req->method != method) {
+      // see if we can't match the url
+      params.clear();
+      // simple matcher func
+      matched = match(url, req->url, params);
+
+      if (matched && !method.empty() && req->method != method) {
          // method did not match, record it though so we can return correct result
          matched = false;
          seen = true;
          continue;
       }
+      if (matched) {
+        break;
+      }
     }
 
     if (!matched) {
-      if (seen)
+      if (seen) {
         return RouteNoMethod;
+      }
       // no route
       return RouteNotFound;
     }
 
     req->parameters.clear();
 
-    for(std::map<std::string, TDelim>::iterator i = params.begin(); i != params.end(); i++) {
-      int p1,p2;
-      funcptr::tie(p1,p2) = i->second;
-      std::string value(req->url.path.begin() + p1, req->url.path.begin() + p2);
+    for (const auto& param: params) {
+      int nstart = 0;
+      int nend = 0;
+      funcptr::tie(nstart, nend) = param.second;
+      std::string value(req->url.path.begin() + nstart, req->url.path.begin() + nend);
       value = Utility::decodeURL(value);
-      req->parameters[i->first] = std::move(value);
+      req->parameters[param.first] = std::move(value);
     }
 
     req->routeName = std::move(rname);
index 0261bd4cd5dca8c2f44d6918d3ffbeb9b4b09db3..c9332078d0a056ad35120d39d6fa59dc7ccd8793 100644 (file)
@@ -34,6 +34,7 @@ namespace YaHTTP {
   typedef funcptr::function <void(Request* req, Response* resp)> THandlerFunction; //!< Handler function pointer 
   typedef funcptr::tuple<std::string, std::string, THandlerFunction, std::string> TRoute; //!< Route tuple (method, urlmask, handler, name)
   typedef std::vector<TRoute> TRouteList; //!< List of routes in order of evaluation
+  typedef funcptr::tuple<int,int> TDelim;
 
   /*! Implements simple router.
 
@@ -53,6 +54,7 @@ is consumed but not stored. Note that only path is matched, scheme, host and url
     RoutingResult route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
     void printRoutes(std::ostream &os); //<! Instance method for printing routes
     std::pair<std::string, std::string> urlFor(const std::string &name, const strstr_map_t& arguments); //<! Instance method for generating paths
+    static bool match(const std::string& route, const URL& requrl, std::map<std::string, TDelim>& params); //<! Instance method for matching a route
 
 /*! Map an URL.
 If method is left empty, it will match any method. Name is also optional, but needed if you want to find it for making URLs 
@@ -65,6 +67,7 @@ If method is left empty, it will match any method. Name is also optional, but ne
     static void Delete(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("DELETE", url, handler, name); }; //<! Helper for mapping DELETE
     static void Any(const std::string& url, THandlerFunction handler, const std::string& name = "") { router.map("", url, handler, name); }; //<! Helper for mapping any method
 
+    static bool Match(const std::string& route, const URL& requrl, std::map<std::string, TDelim>& params) { return router.match(route, requrl, params); };
     static RoutingResult Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path, returns RouteFound if route is found and method matches, RouteNoMethod if route is seen but method did match, and RouteNotFound if not found.
     static void PrintRoutes(std::ostream &os) { router.printRoutes(os); }; //<! Prints all known routes to given output stream