]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Webserver: convert to new yahttp api and router 1465/head
authorChristian Hofstaedtler <christian@hofstaedtler.name>
Mon, 9 Jun 2014 14:35:26 +0000 (16:35 +0200)
committerChristian Hofstaedtler <christian@hofstaedtler.name>
Tue, 24 Jun 2014 11:10:34 +0000 (13:10 +0200)
pdns/webserver.cc
pdns/webserver.hh
pdns/ws-api.cc
pdns/ws-auth.cc
pdns/ws-recursor.cc

index 579b3f8bf670014f7eadfbfd2d508e9798a6ea0e..0e56af580aa90e2c2cdb6b4aa801d6296b762006 100644 (file)
@@ -28,6 +28,7 @@
 #include "dns.hh"
 #include "base64.hh"
 #include "json.hh"
+#include <yahttp/router.hpp>
 
 struct connectionThreadData {
   WebServer* webServer;
@@ -36,7 +37,12 @@ struct connectionThreadData {
 
 void HttpRequest::json(rapidjson::Document& document)
 {
+  if(this->body.empty()) {
+    L<<Logger::Debug<<"HTTP: JSON document expected in request body, but body was empty" << endl;
+    throw HttpBadRequestException();
+  }
   if(document.Parse<0>(this->body.c_str()).HasParseError()) {
+    L<<Logger::Debug<<"HTTP: parsing of JSON document failed" << endl;
     throw HttpBadRequestException();
   }
 }
@@ -51,63 +57,33 @@ int WebServer::B64Decode(const std::string& strInput, std::string& strOutput)
   return ::B64Decode(strInput, strOutput);
 }
 
-// url is supposed to start with a slash.
-// url can contain variable names, marked as <variable>; such variables
-// are parsed out during routing and are put into the "pathArgs" map.
-// route() makes no assumptions about the contents of variables except
-// that the following URL segment can't be part of the variable.
-//
-// Note: ORDER of registration MATTERS:
-// URLs that do a more specific match should come FIRST.
-//
-// Examples:
-//   registerHandler("/foo/<bar>/<baz>", &foobarbaz);
-//   registerHandler("/foo/<bar>", &foobar);
-//   registerHandler("/foo", &foo);
-//   registerHandler("/", &index);
-void WebServer::registerHandler(const string& url, HandlerFunction handler)
+static void handlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Request* req, YaHTTP::Response* resp)
 {
-  std::size_t pos = 0, lastpos = 0;
-
-  HandlerRegistration reg;
-  while ((pos = url.find('<', lastpos)) != std::string::npos) {
-    std::string part = url.substr(lastpos, pos-lastpos);
-    lastpos = pos;
-    pos = url.find('>', pos);
-
-    if (pos == std::string::npos) {
-      throw std::logic_error("invalid url given");
-    }
-
-    std::string paramName = url.substr(lastpos+1, pos-lastpos-1);
-    lastpos = pos+1;
+  // wrapper to convert from YaHTTP::* to our subclasses
+  handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
+}
 
-    reg.urlParts.push_back(part);
-    reg.paramNames.push_back(paramName);
-  }
-  std::string remainder = url.substr(lastpos);
-  if (!remainder.empty()) {
-    reg.urlParts.push_back(remainder);
-    reg.paramNames.push_back("");
-  }
-  reg.handler = handler;
-  d_handlers.push_back(reg);
+void WebServer::registerHandler(const string& url, HandlerFunction handler)
+{
+  YaHTTP::THandlerFunction f = boost::bind(&handlerWrapper, handler, _1, _2);
+  YaHTTP::Router::Any(url, f);
 }
 
-static void apiWrapper(boost::function<void(HttpRequest*,HttpResponse*)> handler, HttpRequest* req, HttpResponse* resp) {
+static void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) {
   resp->headers["Access-Control-Allow-Origin"] = "*";
   resp->headers["Content-Type"] = "application/json";
 
   string callback;
 
-  if(req->parameters.count("callback")) {
-    callback=req->parameters["callback"];
-    req->parameters.erase("callback");
+  if(req->getvars.count("callback")) {
+    callback=req->getvars["callback"];
+    req->getvars.erase("callback");
   }
 
-  req->parameters.erase("_"); // jQuery cache buster
+  req->getvars.erase("_"); // jQuery cache buster
 
   try {
+    resp->status = 200;
     handler(req, resp);
   } catch (ApiException &e) {
     resp->body = returnJsonError(e.what());
@@ -134,46 +110,6 @@ void WebServer::registerApiHandler(const string& url, HandlerFunction handler) {
   registerHandler(url, f);
 }
 
-bool WebServer::route(const std::string& url, std::map<std::string, std::string>& pathArgs, HandlerFunction** handler)
-{
-  for (std::list<HandlerRegistration>::iterator reg=d_handlers.begin(); reg != d_handlers.end(); ++reg) {
-    bool matches = true;
-    size_t lastpos = 0, pos = 0;
-    string lastParam;
-    pathArgs.clear();
-    for (std::list<string>::iterator urlPart = reg->urlParts.begin(), param = reg->paramNames.begin();
-         urlPart != reg->urlParts.end() && param != reg->paramNames.end();
-         urlPart++, param++) {
-      if (!urlPart->empty()) {
-        pos = url.find(*urlPart, lastpos);
-        if (pos == std::string::npos) {
-          matches = false;
-          break;
-        }
-        if (!lastParam.empty()) {
-          // store
-          pathArgs[lastParam] = url.substr(lastpos, pos-lastpos);
-        }
-        lastpos = pos + urlPart->size();
-        lastParam = *param;
-      }
-    }
-    if (matches) {
-      if (!lastParam.empty()) {
-        // store trailing parameter
-        pathArgs[lastParam] = url.substr(lastpos, pos-lastpos);
-      } else if (lastpos != url.size()) {
-        matches = false;
-        continue;
-      }
-
-      *handler = &reg->handler;
-      return true;
-    }
-  }
-  return false;
-}
-
 static void *WebServerConnectionThreadStart(void *p) {
   connectionThreadData* data = static_cast<connectionThreadData*>(p);
   pthread_detach(pthread_self());
@@ -187,13 +123,14 @@ static void *WebServerConnectionThreadStart(void *p) {
 
 HttpResponse WebServer::handleRequest(HttpRequest req)
 {
-  HttpResponse resp(req);
+  HttpResponse resp;
 
   // set default headers
   resp.headers["Content-Type"] = "text/html; charset=utf-8";
 
   try {
     if (!req.complete) {
+      L<<Logger::Debug<<"HTTP: Incomplete request" << endl;
       throw HttpBadRequestException();
     }
 
@@ -232,16 +169,19 @@ HttpResponse WebServer::handleRequest(HttpRequest req)
       }
     }
 
-    HandlerFunction *handler;
-    if (!route(req.url.path, req.path_parameters, &handler)) {
+    YaHTTP::THandlerFunction handler;
+    if (!YaHTTP::Router::Route(&req, handler)) {
       L<<Logger::Debug<<"HTTP: No route found for \"" << req.url.path << "\"" << endl;
       throw HttpNotFoundException();
     }
 
     try {
-      (*handler)(&req, &resp);
+      handler(&req, &resp);
       L<<Logger::Debug<<"HTTP: Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl;
     }
+    catch(HttpException) {
+      throw;
+    }
     catch(PDNSException &e) {
       L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl;
       throw HttpInternalServerErrorException();
@@ -281,7 +221,8 @@ HttpResponse WebServer::handleRequest(HttpRequest req)
 void WebServer::serveConnection(Socket *client)
 try {
   HttpRequest req;
-  YaHTTP::AsyncRequestLoader yarl(&req);
+  YaHTTP::AsyncRequestLoader yarl;
+  yarl.initialize(&req);
   int timeout = 5;
   client->setNonBlocking();
 
@@ -298,6 +239,7 @@ try {
         break;
       }
     }
+    yarl.finalize();
   } catch (YaHTTP::ParseError &e) {
     // request stays incomplete
   }
index e9fabd282563c8e18d90004c839d10a52d46504c..5b3376748d986d6480a930f54e650279eb8319b4 100644 (file)
@@ -36,7 +36,6 @@ class HttpRequest : public YaHTTP::Request {
 public:
   HttpRequest() : YaHTTP::Request(), accept_json(false), accept_html(false), complete(false) { };
 
-  map<string,string> path_parameters;
   bool accept_json;
   bool accept_html;
   bool complete;
@@ -46,7 +45,6 @@ public:
 class HttpResponse: public YaHTTP::Response {
 public:
   HttpResponse() : YaHTTP::Response() { };
-  HttpResponse(const YaHTTP::Request &req) : YaHTTP::Response(req) { };
   HttpResponse(const YaHTTP::Response &resp) : YaHTTP::Response(resp) { };
 
   void setBody(rapidjson::Document& document);
@@ -135,19 +133,12 @@ public:
   HttpResponse handleRequest(HttpRequest request);
 
   typedef boost::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
-  struct HandlerRegistration {
-    std::list<string> urlParts;
-    std::list<string> paramNames;
-    HandlerFunction handler;
-  };
-
   void registerHandler(const string& url, HandlerFunction handler);
   void registerApiHandler(const string& url, HandlerFunction handler);
 
 protected:
   static char B64Decode1(char cInChar);
   static int B64Decode(const std::string& strInput, std::string& strOutput);
-  bool route(const std::string& url, std::map<std::string, std::string>& urlArgs, HandlerFunction** handler);
 
   virtual Server* createServer() {
     return new Server(d_listenaddress, d_port);
@@ -155,7 +146,6 @@ protected:
 
   string d_listenaddress;
   int d_port;
-  std::list<HandlerRegistration> d_handlers;
   string d_password;
   Server* d_server;
 };
index 98d63c8672c188a9ee6c3afcc7090dd0c6b0aee3..44af01b383584b53ddbf2434359d91e5af9b47a3 100644 (file)
@@ -188,7 +188,7 @@ void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) {
     throw HttpMethodNotAllowedException();
 
   string prefix = " " + s_programname + "[";
-  resp->body = logGrep(req->parameters["q"], ::arg()["experimental-logfile"], prefix);
+  resp->body = logGrep(req->getvars["q"], ::arg()["experimental-logfile"], prefix);
 }
 
 void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
index 70238530cb31bbf52c28822f88312f4157520e37..7480a318f96cca5a9ac0b31cbcd1add672fc951d 100644 (file)
@@ -197,17 +197,17 @@ string AuthWebServer::makePercentage(const double& val)
 
 void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
 {
-  if(!req->parameters["resetring"].empty()) {
-    if (S.ringExists(req->parameters["resetring"]))
-      S.resetRing(req->parameters["resetring"]);
+  if(!req->getvars["resetring"].empty()) {
+    if (S.ringExists(req->getvars["resetring"]))
+      S.resetRing(req->getvars["resetring"]);
     resp->status = 301;
     resp->headers["Location"] = "/";
     return;
   }
-  if(!req->parameters["resizering"].empty()){
-    int size=atoi(req->parameters["size"].c_str());
-    if (S.ringExists(req->parameters["resizering"]) && size > 0 && size <= 500000)
-      S.resizeRing(req->parameters["resizering"], atoi(req->parameters["size"].c_str()));
+  if(!req->getvars["resizering"].empty()){
+    int size=atoi(req->getvars["size"].c_str());
+    if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000)
+      S.resizeRing(req->getvars["resizering"], atoi(req->getvars["size"].c_str()));
     resp->status = 301;
     resp->headers["Location"] = "/";
     return;
@@ -264,7 +264,7 @@ void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
     "<br>"<<endl;
 
   ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
-  if(req->parameters["ring"].empty()) {
+  if(req->getvars["ring"].empty()) {
     vector<string>entries=S.listRings();
     for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
       printtable(ret,*i,S.getRingTitle(*i));
@@ -274,7 +274,7 @@ void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
       printargs(ret);
   }
   else
-    printtable(ret,req->parameters["ring"],S.getRingTitle(req->parameters["ring"]),100);
+    printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100);
 
   ret<<"</div></div>"<<endl;
   ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>&copy; 2013 - 2014 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
@@ -478,7 +478,7 @@ static void apiZoneCryptokeys(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "GET")
     throw ApiException("Only GET is implemented");
 
-  string zonename = apiZoneIdToName(req->path_parameters["id"]);
+  string zonename = apiZoneIdToName(req->parameters["id"]);
 
   UeberBackend B;
   DomainInfo di;
@@ -499,8 +499,8 @@ static void apiZoneCryptokeys(HttpRequest* req, HttpResponse* resp) {
   doc.SetArray();
 
   BOOST_FOREACH(DNSSECKeeper::keyset_t::value_type value, keyset) {
-    if (req->path_parameters.count("key_id")) {
-      int keyid = lexical_cast<int>(req->path_parameters["key_id"]);
+    if (req->parameters.count("key_id")) {
+      int keyid = lexical_cast<int>(req->parameters["key_id"]);
       int curid = lexical_cast<int>(value.second.id);
       if (keyid != curid)
         continue;
@@ -513,8 +513,8 @@ static void apiZoneCryptokeys(HttpRequest* req, HttpResponse* resp) {
     key.AddMember("keytype", (value.second.keyOrZone ? "ksk" : "zsk"), doc.GetAllocator());
     Value dnskey(value.first.getDNSKEY().getZoneRepresentation().c_str(), doc.GetAllocator());
     key.AddMember("dnskey", dnskey, doc.GetAllocator());
-    if (req->path_parameters.count("key_id")) {
-      DNSSECPrivateKey dpk=dk.getKeyById(zonename, lexical_cast<int>(req->path_parameters["key_id"]));
+    if (req->parameters.count("key_id")) {
+      DNSSECPrivateKey dpk=dk.getKeyById(zonename, lexical_cast<int>(req->parameters["key_id"]));
       Value content(dpk.getKey()->convertToISC().c_str(), doc.GetAllocator());
       key.AddMember("content", content, doc.GetAllocator());
     }
@@ -708,7 +708,7 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
 }
 
 static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
-  string zonename = apiZoneIdToName(req->path_parameters["id"]);
+  string zonename = apiZoneIdToName(req->parameters["id"]);
 
   if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
     // update domain settings
@@ -761,7 +761,7 @@ static string makeDotted(string in) {
 }
 
 static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
-  string zonename = apiZoneIdToName(req->path_parameters["id"]);
+  string zonename = apiZoneIdToName(req->parameters["id"]);
 
   if(req->method != "GET")
     throw HttpMethodNotAllowedException();
@@ -863,7 +863,7 @@ static void makePtr(const DNSResourceRecord& rr, DNSResourceRecord* ptr) {
 static void patchZone(HttpRequest* req, HttpResponse* resp) {
   UeberBackend B;
   DomainInfo di;
-  string zonename = apiZoneIdToName(req->path_parameters["id"]);
+  string zonename = apiZoneIdToName(req->parameters["id"]);
   if (!B.getDomainInfo(zonename, di))
     throw ApiException("Could not find domain '"+zonename+"'");
 
@@ -1003,7 +1003,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "GET")
     throw HttpMethodNotAllowedException();
 
-  string q = req->parameters["q"];
+  string q = req->getvars["q"];
   if (q.empty())
     throw ApiException("Query q can't be blank");
 
@@ -1088,18 +1088,18 @@ void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
 {
   string command;
 
-  if(req->parameters.count("command")) {
-    command = req->parameters["command"];
-    req->parameters.erase("command");
+  if(req->getvars.count("command")) {
+    command = req->getvars["command"];
+    req->getvars.erase("command");
   }
 
   if(command == "flush-cache") {
     extern PacketCache PC;
     int number; 
-    if(req->parameters["domain"].empty())
+    if(req->getvars["domain"].empty())
       number = PC.purge();
     else
-      number = PC.purge(req->parameters["domain"]);
+      number = PC.purge(req->getvars["domain"]);
       
     map<string, string> object;
     object["number"]=lexical_cast<string>(number);
@@ -1133,7 +1133,7 @@ void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
   }
   else if(command=="log-grep") {
     // legacy parameter name hack
-    req->parameters["q"] = req->parameters["needle"];
+    req->getvars["q"] = req->getvars["needle"];
     apiServerSearchLog(req, resp);
     return;
   }
index 9da97273f3bc3e2a496b4bd3a2e049d18134673c..34afb3376d11aeb82cc0d43a0b31d72c7baf5d30 100644 (file)
@@ -331,7 +331,7 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp)
 
 static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
 {
-  string zonename = apiZoneIdToName(req->path_parameters["id"]);
+  string zonename = apiZoneIdToName(req->parameters["id"]);
   zonename += ".";
 
   SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename);
@@ -367,7 +367,7 @@ static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "GET")
     throw HttpMethodNotAllowedException();
 
-  string q = req->parameters["q"];
+  string q = req->getvars["q"];
   if (q.empty())
     throw ApiException("Query q can't be blank");
 
@@ -442,9 +442,9 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
 {
   string command;
 
-  if(req->parameters.count("command")) {
-    command = req->parameters["command"];
-    req->parameters.erase("command");
+  if(req->getvars.count("command")) {
+    command = req->getvars["command"];
+    req->getvars.erase("command");
   }
 
   map<string, string> stats; 
@@ -476,7 +476,7 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
     return;
   }
   else if(command == "zone") {
-    string arg_zone = req->parameters["zone"];
+    string arg_zone = req->getvars["zone"];
     SyncRes::domainmap_t::const_iterator ret = t_sstorage->domainmap->find(arg_zone);
     if (ret != t_sstorage->domainmap->end()) {
       Document doc;
@@ -525,7 +525,7 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
     }
   }
   else if(command == "flush-cache") {
-    string canon=toCanonic("", req->parameters["domain"]);
+    string canon=toCanonic("", req->getvars["domain"]);
     int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon));
     count+=broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon));
     stats["number"]=lexical_cast<string>(count);
@@ -542,7 +542,7 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
   }
   else if(command == "log-grep") {
     // legacy parameter name hack
-    req->parameters["q"] = req->parameters["needle"];
+    req->getvars["q"] = req->getvars["needle"];
     apiServerSearchLog(req, resp);
     return;
   }
@@ -584,7 +584,8 @@ void AsyncServer::newConnection()
 void AsyncWebServer::serveConnection(Socket *client)
 {
   HttpRequest req;
-  YaHTTP::AsyncRequestLoader yarl(&req);
+  YaHTTP::AsyncRequestLoader yarl;
+  yarl.initialize(&req);
   client->setNonBlocking();
 
   string data;
@@ -599,6 +600,7 @@ void AsyncWebServer::serveConnection(Socket *client)
         break;
       }
     }
+    yarl.finalize();
   } catch (YaHTTP::ParseError &e) {
     // request stays incomplete
   }