]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
webserver: Allow specifying supported method
authorAki Tuomi <cmouse@cmouse.fi>
Fri, 18 Aug 2023 07:09:56 +0000 (10:09 +0300)
committerAki Tuomi <cmouse@cmouse.fi>
Fri, 15 Dec 2023 07:59:57 +0000 (09:59 +0200)
If method is not empty and it does not match the request, throw
exception.

ext/yahttp/yahttp/router.cpp
ext/yahttp/yahttp/router.hpp
pdns/webserver.cc
pdns/webserver.hh

index 90612ad8ec62d80f0824895d1204ce8e6de0756d..83465e318a30439f532a1c12f2fd33ceac243fa4 100644 (file)
@@ -24,10 +24,11 @@ namespace YaHTTP {
     routes.push_back(funcptr::make_tuple(method2, url, handler, name));
   };
 
-  bool Router::route(Request *req, THandlerFunction& handler) {
+  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
@@ -36,8 +37,8 @@ namespace YaHTTP {
       std::string pname;
       std::string method, url;
       funcptr::tie(method, url, handler, rname) = *i;
-    
-      if (method.empty() == false && req->method != method) continue; // no match on method
+      matched = false;
+
       // see if we can't match the url
       params.clear();
       // simple matcher func
@@ -53,6 +54,7 @@ namespace YaHTTP {
             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();
@@ -78,10 +80,23 @@ namespace YaHTTP {
         matched = false;
       else
         matched = true;
+
+      if (matched && method.empty() == false && req->method != method) {
+         // method did not match, record it though so we can return correct result
+         matched = false;
+         seen = true;
+         continue;
+      }
+    }
+
+    if (!matched) {
+      if (seen)
+        return RouteNoMethod;
+      // no route
+      return RouteNotFound;
     }
 
-    if (!matched) { return false; } // no route
-    req->parameters.clear();    
+    req->parameters.clear();
 
     for(std::map<std::string, TDelim>::iterator i = params.begin(); i != params.end(); i++) {
       int p1,p2;
@@ -93,7 +108,7 @@ namespace YaHTTP {
 
     req->routeName = std::move(rname);
 
-    return true;
+    return RouteFound;
   };
 
   void Router::printRoutes(std::ostream &os) {
index 205119c7d41ecb86074bc0d0ef0299605fda3b07..0261bd4cd5dca8c2f44d6918d3ffbeb9b4b09db3 100644 (file)
@@ -25,6 +25,12 @@ namespace funcptr = boost;
 #include <utility>
 
 namespace YaHTTP {
+  enum RoutingResult {
+    RouteFound = 1,
+    RouteNotFound = 0,
+    RouteNoMethod = -1,
+  };
+
   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
@@ -44,7 +50,7 @@ is consumed but not stored. Note that only path is matched, scheme, host and url
     static Router router; //<! Singleton instance of Router
   public:
     void map(const std::string& method, const std::string& url, THandlerFunction handler, const std::string& name); //<! Instance method for mapping urls
-    bool route(Request *req, THandlerFunction& handler); //<! Instance method for performing routing
+    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
 
@@ -59,7 +65,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 Route(Request *req, THandlerFunction& handler) { return router.route(req, handler); }; //<! Performs routing based on req->url.path 
+    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
 
     static std::pair<std::string, std::string> URLFor(const std::string &name, const strstr_map_t& arguments) { return router.urlFor(name,arguments); }; //<! Generates url from named route and arguments. Missing arguments are assumed empty
index 98431a07d84d3225e94e5ecd806f481ad0b6b4db..700de7e71978ab75336248b576ae193744b42b6d 100644 (file)
@@ -142,10 +142,10 @@ static void bareHandlerWrapper(const WebServer::HandlerFunction& handler, YaHTTP
   handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
 }
 
-void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler)
+void WebServer::registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method)
 {
   YaHTTP::THandlerFunction f = [=](YaHTTP::Request* req, YaHTTP::Response* resp){return bareHandlerWrapper(handler, req, resp);};
-  YaHTTP::Router::Any(url, std::move(f));
+  YaHTTP::Router::Map(method, url, std::move(f));
 }
 
 static bool optionsHandler(HttpRequest* req, HttpResponse* resp) {
@@ -215,9 +215,9 @@ void WebServer::apiWrapper(const WebServer::HandlerFunction& handler, HttpReques
   }
 }
 
-void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword) {
+void WebServer::registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method, bool allowPassword) {
   auto f = [=](HttpRequest *req, HttpResponse* resp){apiWrapper(handler, req, resp, allowPassword);};
-  registerBareHandler(url, f);
+  registerBareHandler(url, f, method);
 }
 
 void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpRequest* req, HttpResponse* resp) {
@@ -233,9 +233,9 @@ void WebServer::webWrapper(const WebServer::HandlerFunction& handler, HttpReques
   handler(req, resp);
 }
 
-void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler) {
+void WebServer::registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method) {
   auto f = [=](HttpRequest *req, HttpResponse *resp){webWrapper(handler, req, resp);};
-  registerBareHandler(url, f);
+  registerBareHandler(url, f, method);
 }
 
 static void *WebServerConnectionThreadStart(const WebServer* webServer, std::shared_ptr<Socket> client) {
@@ -293,11 +293,16 @@ void WebServer::handleRequest(HttpRequest& req, HttpResponse& resp) const
     }
 
     YaHTTP::THandlerFunction handler;
-    if (!YaHTTP::Router::Route(&req, handler)) {
+    YaHTTP::RoutingResult res = YaHTTP::Router::Route(&req, handler);
+
+    if (res == YaHTTP::RouteNotFound) {
       SLOG(g_log<<Logger::Debug<<req.logprefix<<"No route found for \"" << req.url.path << "\"" << endl,
            log->info(Logr::Debug, "No route found"));
       throw HttpNotFoundException();
     }
+    if (res == YaHTTP::RouteNoMethod) {
+      throw HttpMethodNotAllowedException();
+    }
 
     const string msg = "HTTP ISE Exception";
     try {
index 6f9c59e8497bb67a32d3425a55b6b5578d026e33..28d901dbd52df329d3be601f0bd4d48c98aaaf5b 100644 (file)
@@ -225,8 +225,8 @@ public:
   void handleRequest(HttpRequest& request, HttpResponse& resp) const;
 
   typedef std::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
-  void registerApiHandler(const string& url, const HandlerFunction& handler, bool allowPassword=false);
-  void registerWebHandler(const string& url, const HandlerFunction& handler);
+  void registerApiHandler(const string& url, const HandlerFunction& handler, const std::string& method = "", bool allowPassword=false);
+  void registerWebHandler(const string& url, const HandlerFunction& handler, const std::string& method = "");
 
   enum class LogLevel : uint8_t {
     None = 0,                // No logs from requests at all
@@ -266,7 +266,7 @@ public:
 #endif
 
 protected:
-  void registerBareHandler(const string& url, const HandlerFunction& handler);
+  static void registerBareHandler(const string& url, const HandlerFunction& handler, const std::string& method);
   void logRequest(const HttpRequest& req, const ComboAddress& remote) const;
   void logResponse(const HttpResponse& resp, const ComboAddress& remote, const string& logprefix) const;