]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
api: share apiServer* code across auth, recursor
authorChristian Hofstaedtler <christian@hofstaedtler.name>
Thu, 30 Jan 2014 22:31:30 +0000 (23:31 +0100)
committerChristian Hofstaedtler <christian@hofstaedtler.name>
Mon, 3 Feb 2014 14:06:19 +0000 (15:06 +0100)
Gives us "free" implementations of /servers, /servers/localhost,
/servers/localhost/{config, search-log, statistics} for the recursor.

Move JSON helper functions from ws-auth.cc to json.cc; turn
parseJsonBody into HttpRequest::json().

12 files changed:
pdns/Makefile-recursor
pdns/Makefile.am
pdns/dist-recursor
pdns/json.cc
pdns/json.hh
pdns/webserver.cc
pdns/webserver.hh
pdns/ws-api.cc [new file with mode: 0644]
pdns/ws-api.hh [new file with mode: 0644]
pdns/ws-auth.cc
pdns/ws-recursor.cc
pdns/ws-recursor.hh

index 171e21cc55b8acd79b4b16142ecbd5dca563cd6c..d3eba8f29ffea98046c2a36a13c6da005627cbf7 100644 (file)
@@ -21,8 +21,8 @@ dnswriter.o dnsrecords.o rcpgenerator.o base64.o zoneparser-tng.o \
 rec_channel.o rec_channel_rec.o selectmplexer.o sillyrecords.o \
 dns_random.o ext/polarssl-1.3.2/library/aes.o ext/polarssl-1.3.2/library/padlock.o dnslabeltext.o \
 lua-pdns.o lua-recursor.o randomhelper.o recpacketcache.o dns.o \
-reczones.o base32.o nsecrecords.o json.o ws-recursor.o version.o responsestats.o \
-session.o webserver.o ext/yahttp/yahttp/reqresp.o
+reczones.o base32.o nsecrecords.o json.o ws-recursor.o ws-api.o \
+version.o responsestats.o session.o webserver.o ext/yahttp/yahttp/reqresp.o
 
 REC_CONTROL_OBJECTS=rec_channel.o rec_control.o arguments.o misc.o \
        unix_utility.o logger.o qtype.o
index bea08dd6c2bbd5e214aa55d6d920eb64c893f534..6b0a018ad1458dfd02dc850886156137a294f112 100644 (file)
@@ -44,7 +44,8 @@ qtype.cc logger.cc arguments.cc packethandler.cc tcpreceiver.cc \
 packetcache.cc statbag.cc pdnsexception.hh arguments.hh distributor.hh \
 dns.hh dnsbackend.hh dnsbackend.cc dnspacket.hh dynmessenger.hh lock.hh logger.hh \
 nameserver.hh packetcache.hh packethandler.hh qtype.hh statbag.hh \
-ueberbackend.hh pdns.conf-dist ws-auth.hh ws-auth.cc webserver.cc webserver.hh \
+ueberbackend.hh pdns.conf-dist \
+ws-auth.hh ws-auth.cc ws-api.cc ws-api.hh webserver.cc webserver.hh \
 session.cc session.hh misc.cc misc.hh receiver.cc ueberbackend.cc \
 dynlistener.cc dynlistener.hh  dynhandler.cc dynhandler.hh  \
 resolver.hh resolver.cc slavecommunicator.cc mastercommunicator.cc communicator.cc communicator.hh dnsproxy.cc \
@@ -283,8 +284,9 @@ base64.cc base64.hh zoneparser-tng.cc zoneparser-tng.hh rec_channel.cc rec_chann
 rec_channel_rec.cc selectmplexer.cc epollmplexer.cc sillyrecords.cc htimer.cc htimer.hh \
 dns_random.cc \
 lua-pdns.cc lua-pdns.hh lua-recursor.cc lua-recursor.hh randomhelper.cc  \
-recpacketcache.cc recpacketcache.hh dns.cc nsecrecords.cc base32.cc cachecleaner.hh ws-recursor.cc ws-recursor.hh \
-json.cc json.hh version.hh version.cc responsestats.cc webserver.cc webserver.hh session.cc session.hh
+recpacketcache.cc recpacketcache.hh dns.cc nsecrecords.cc base32.cc cachecleaner.hh \
+ws-recursor.cc ws-recursor.hh ws-api.cc ws-api.hh webserver.cc webserver.hh \
+session.cc session.hh json.cc json.hh version.hh version.cc responsestats.cc
 
 pdns_recursor_LDFLAGS= $(LUA_LIBS)
 pdns_recursor_LDADD= $(POLARSSL_LIBS) $(YAHTTP_LIBS)
index 234212006290e3c5f4f29a0eb4f1f2c5e50f0e0b..ed0a328050ae05a4ec22fa06ae2841964bc52f94 100755 (executable)
@@ -26,6 +26,7 @@ sstuff.hh mtasker.hh mtasker.cc lwres.hh logger.hh pdnsexception.hh \
 mplexer.hh \
 dns_random.hh lua-pdns.hh lua-recursor.hh namespaces.hh \
 recpacketcache.hh base32.hh cachecleaner.hh json.hh version.hh \
+ws-recursor.hh ws-api.hh \
 responsestats.hh webserver.hh session.hh"
 
 CFILES="syncres.cc  misc.cc unix_utility.cc qtype.cc \
@@ -35,7 +36,7 @@ base64.cc  zoneparser-tng.cc  rec_channel.cc rec_channel_rec.cc rec_control.cc \
 selectmplexer.cc epollmplexer.cc kqueuemplexer.cc portsmplexer.cc pdns_hw.cc \
 sillyrecords.cc lua-pdns.cc lua-recursor.cc randomhelper.cc \
 devpollmplexer.cc recpacketcache.cc dns.cc reczones.cc base32.cc nsecrecords.cc \
-dnslabeltext.cc json.cc ws-recursor.cc ws-recursor.hh version.cc dns_random.cc \
+dnslabeltext.cc json.cc ws-recursor.cc ws-api.cc version.cc dns_random.cc \
 responsestats.cc webserver.cc session.cc"
 
 cd docs
index c2132e1a01f902b18cdd67ecf000aa03569cef1f..de37bcf626ef48bdceedaacdd07c996c0574323a 100644 (file)
@@ -1,61 +1,58 @@
 #include "json.hh"
 #include "namespaces.hh"
-#include <stdio.h>
-#include <boost/circular_buffer.hpp>
-#include <boost/tokenizer.hpp>
-#include "namespaces.hh"
 #include "misc.hh"
 #include <boost/foreach.hpp>
 #include "rapidjson/document.h"
 #include "rapidjson/stringbuffer.h"
 #include "rapidjson/writer.h"
-#include "config.h"
-#include <string.h>
-#include <ctype.h>
-#include <sys/types.h>
-
-#ifndef HAVE_STRCASESTR
 
-/*
- * strcasestr() locates the first occurrence in the string s1 of the
- * sequence of characters (excluding the terminating null character)
- * in the string s2, ignoring case.  strcasestr() returns a pointer
- * to the located string, or a null pointer if the string is not found.
- * If s2 is empty, the function returns s1.
- */
+using namespace rapidjson;
 
-static char *
-strcasestr(const char *s1, const char *s2)
+int intFromJson(const Value& container, const char* key)
 {
-        int *cm = __trans_lower;
-        const uchar_t *us1 = (const uchar_t *)s1;
-        const uchar_t *us2 = (const uchar_t *)s2;
-        const uchar_t *tptr;
-        int c;
-
-        if (us2 == NULL || *us2 == '\0')
-                return ((char *)us1);
-
-        c = cm[*us2];
-        while (*us1 != '\0') {
-                if (c == cm[*us1++]) {
-                        tptr = us1;
-                        while (cm[c = *++us2] == cm[*us1++] && c != '\0')
-                                continue;
-                        if (c == '\0')
-                                return ((char *)tptr - 1);
-                        us1 = tptr;
-                        us2 = (const uchar_t *)s2;
-                        c = cm[*us2];
-                }
-        }
+  const Value& val = container[key];
+  if (val.IsInt()) {
+    return val.GetInt();
+  } else if (val.IsString()) {
+    return atoi(val.GetString());
+  } else {
+    throw JsonException("Key '" + string(key) + "' not an Integer or not present");
+  }
+}
 
-        return (NULL);
+int intFromJson(const Value& container, const char* key, const int default_value)
+{
+  const Value& val = container[key];
+  if (val.IsInt()) {
+    return val.GetInt();
+  } else if (val.IsString()) {
+    return atoi(val.GetString());
+  } else {
+    // TODO: check if value really isn't present
+    return default_value;
+  }
 }
 
-#endif // HAVE_STRCASESTR
+string stringFromJson(const Value& container, const char* key)
+{
+  const Value& val = container[key];
+  if (val.IsString()) {
+    return val.GetString();
+  } else {
+    throw JsonException("Key '" + string(key) + "' not present or not a String");
+  }
+}
 
-using namespace rapidjson;                        
+string stringFromJson(const Value& container, const char* key, const string& default_value)
+{
+  const Value& val = container[key];
+  if (val.IsString()) {
+    return val.GetString();
+  } else {
+    // TODO: check if value really isn't present
+    return default_value;
+  }
+}
 
 string makeStringFromDocument(const Document& doc)
 {
@@ -65,7 +62,7 @@ string makeStringFromDocument(const Document& doc)
   return string(output.GetString(), output.Size());
 }
 
-string returnJSONObject(const map<string, string>& items)
+string returnJsonObject(const map<string, string>& items)
 {
   Document doc;
   doc.SetObject();
@@ -76,7 +73,7 @@ string returnJSONObject(const map<string, string>& items)
   return makeStringFromDocument(doc);
 }
 
-string returnJSONError(const string& error)
+string returnJsonError(const string& error)
 {
   Document doc;
   doc.SetObject();
@@ -84,45 +81,3 @@ string returnJSONError(const string& error)
   doc.AddMember("error", jerror, doc.GetAllocator());
   return makeStringFromDocument(doc);
 }
-
-string makeLogGrepJSON(const string& q, const string& fname, const string& prefix)
-{
-  FILE* ptr = fopen(fname.c_str(), "r");
-  if(!ptr) {
-    return "[]";
-  }
-  boost::shared_ptr<FILE> fp(ptr, fclose);
-
-  string line;
-  string needle = q;
-  trim_right(needle);
-
-  boost::replace_all(needle, "%20", " ");  
-  boost::replace_all(needle, "%22", "\"");    
-
-  boost::tokenizer<boost::escaped_list_separator<char> > t(needle, boost::escaped_list_separator<char>("\\", " ", "\""));
-  vector<string> matches(t.begin(), t.end());
-  matches.push_back(prefix);
-  
-  boost::circular_buffer<string> lines(200);
-  while(stringfgets(fp.get(), line)) {
-    vector<string>::const_iterator iter;
-    for(iter = matches.begin(); iter != matches.end(); ++iter) {
-      if(!strcasestr(line.c_str(), iter->c_str()))
-        break;
-    }
-    if(iter == matches.end()) { 
-      trim_right(line);
-      lines.push_front(line);
-    }
-  }
-
-  Document doc;
-  doc.SetArray();
-  if(!lines.empty()) {
-    BOOST_FOREACH(const string& line, lines) {
-      doc.PushBack(line.c_str(), doc.GetAllocator());
-    }
-  }
-  return makeStringFromDocument(doc);
-}
index 3945b05c5bca053a26c05bff55a4603af872ea3c..17021b850532776a315c20bd9ee5306b78358352 100644 (file)
 
 #include <string>
 #include <map>
+#include <stdexcept>
 #include "rapidjson/document.h"
 
-std::string returnJSONObject(const std::map<std::string, std::string>& items);
-std::string returnJSONError(const std::string& error);
-std::string makeLogGrepJSON(const std::string& q, const std::string& fname, const std::string& prefix="");
+std::string returnJsonObject(const std::map<std::string, std::string>& items);
+std::string returnJsonError(const std::string& error);
 std::string makeStringFromDocument(const rapidjson::Document& doc);
+int intFromJson(const rapidjson::Value& container, const char* key);
+int intFromJson(const rapidjson::Value& container, const char* key, const int default_value);
+std::string stringFromJson(const rapidjson::Value& container, const char* key);
+std::string stringFromJson(const rapidjson::Value& container, const char* key, const std::string& default_value);
+
+class JsonException : public std::runtime_error
+{
+public:
+  JsonException(const std::string& what) : std::runtime_error(what) {
+  }
+};
index 1f55c7999ad94b575404ed24d73b8d41c7af1a30..20bf17b5106e519f302f021631b88274fcf59f0d 100644 (file)
@@ -38,6 +38,13 @@ struct connectionThreadData {
   Session client;
 };
 
+void HttpRequest::json(rapidjson::Document& document)
+{
+  if(document.Parse<0>(this->body.c_str()).HasParseError()) {
+    throw HttpBadRequestException();
+  }
+}
+
 int WebServer::B64Decode(const std::string& strInput, std::string& strOutput)
 {
   return ::B64Decode(strInput, strOutput);
@@ -102,8 +109,11 @@ static void apiWrapper(boost::function<void(HttpRequest*,HttpResponse*)> handler
   try {
     handler(req, resp);
   } catch (ApiException &e) {
-    string what = e.what();
-    resp->body = returnJSONError(what);
+    resp->body = returnJsonError(e.what());
+    resp->status = 422;
+    return;
+  } catch (JsonException &e) {
+    resp->body = returnJsonError(e.what());
     resp->status = 422;
     return;
   }
@@ -224,7 +234,7 @@ HttpResponse WebServer::handleRequest(HttpRequest req)
       resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
     } else if (req.accept_json) {
       resp.headers["Content-Type"] = "application/json";
-      resp.body = returnJSONError(what);
+      resp.body = returnJsonError(what);
     } else {
       resp.headers["Content-Type"] = "text/plain; charset=utf-8";
       resp.body = what;
index b6e472f158349940083e325fb92ddcd5223c45b4..8aef0c47bfc33d5ca6d2cf3a49a51cfb9572479b 100644 (file)
 #include <list>
 #include <boost/utility.hpp>
 #include <yahttp/yahttp.hpp>
-
+#include "rapidjson/document.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
 #include "namespaces.hh"
+
 class Server;
 class Session;
 
@@ -38,6 +41,7 @@ public:
   map<string,string> path_parameters;
   bool accept_json;
   bool accept_html;
+  void json(rapidjson::Document& document);
 };
 
 class HttpResponse: public YaHTTP::Response {
diff --git a/pdns/ws-api.cc b/pdns/ws-api.cc
new file mode 100644 (file)
index 0000000..da14b57
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+    Copyright (C) 2002 - 2014  PowerDNS.COM BV
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License version 2
+    as published by the Free Software Foundation
+
+    Additionally, the license of this program contains a special
+    exception which allows to distribute the program in binary form when
+    it is linked against OpenSSL.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#include <boost/foreach.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/circular_buffer.hpp>
+#include "namespaces.hh"
+#include "ws-api.hh"
+#include "json.hh"
+#include "config.h"
+#include "version.hh"
+#include "arguments.hh"
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/types.h>
+
+#ifndef HAVE_STRCASESTR
+
+/*
+ * strcasestr() locates the first occurrence in the string s1 of the
+ * sequence of characters (excluding the terminating null character)
+ * in the string s2, ignoring case.  strcasestr() returns a pointer
+ * to the located string, or a null pointer if the string is not found.
+ * If s2 is empty, the function returns s1.
+ */
+
+static char *
+strcasestr(const char *s1, const char *s2)
+{
+        int *cm = __trans_lower;
+        const uchar_t *us1 = (const uchar_t *)s1;
+        const uchar_t *us2 = (const uchar_t *)s2;
+        const uchar_t *tptr;
+        int c;
+
+        if (us2 == NULL || *us2 == '\0')
+                return ((char *)us1);
+
+        c = cm[*us2];
+        while (*us1 != '\0') {
+                if (c == cm[*us1++]) {
+                        tptr = us1;
+                        while (cm[c = *++us2] == cm[*us1++] && c != '\0')
+                                continue;
+                        if (c == '\0')
+                                return ((char *)tptr - 1);
+                        us1 = tptr;
+                        us2 = (const uchar_t *)s2;
+                        c = cm[*us2];
+                }
+        }
+
+        return (NULL);
+}
+
+#endif // HAVE_STRCASESTR
+
+using namespace rapidjson;
+
+static void fillServerDetail(Value& out, Value::AllocatorType& allocator)
+{
+  Value jdaemonType(productTypeApiType().c_str(), allocator);
+  out.SetObject();
+  out.AddMember("type", "Server", allocator);
+  out.AddMember("id", "localhost", allocator);
+  out.AddMember("url", "/servers/localhost", allocator);
+  out.AddMember("daemon_type", jdaemonType, allocator);
+  out.AddMember("version", VERSION, allocator);
+  out.AddMember("config_url", "/servers/localhost/config{/config_setting}", allocator);
+  out.AddMember("zones_url", "/servers/localhost/zones{/zone}", allocator);
+}
+
+void apiServer(HttpRequest* req, HttpResponse* resp) {
+  if(req->method != "GET")
+    throw HttpMethodNotAllowedException();
+
+  Document doc;
+  doc.SetArray();
+  Value server;
+  fillServerDetail(server, doc.GetAllocator());
+  doc.PushBack(server, doc.GetAllocator());
+  resp->body = makeStringFromDocument(doc);
+}
+
+void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
+  if(req->method != "GET")
+    throw HttpMethodNotAllowedException();
+
+  Document doc;
+  fillServerDetail(doc, doc.GetAllocator());
+  resp->body = makeStringFromDocument(doc);
+}
+
+void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
+  if(req->method != "GET")
+    throw HttpMethodNotAllowedException();
+
+  vector<string> items = ::arg().list();
+  string value;
+  Document doc;
+  doc.SetArray();
+  BOOST_FOREACH(const string& item, items) {
+    Value jitem;
+    jitem.SetObject();
+    jitem.AddMember("type", "ConfigSetting", doc.GetAllocator());
+
+    Value jname(item.c_str(), doc.GetAllocator());
+    jitem.AddMember("name", jname, doc.GetAllocator());
+
+    if(item.find("password") != string::npos)
+      value = "***";
+    else
+      value = ::arg()[item];
+
+    Value jvalue(value.c_str(), doc.GetAllocator());
+    jitem.AddMember("value", jvalue, doc.GetAllocator());
+
+    doc.PushBack(jitem, doc.GetAllocator());
+  }
+  resp->body = makeStringFromDocument(doc);
+}
+
+static string logGrep(const string& q, const string& fname, const string& prefix)
+{
+  FILE* ptr = fopen(fname.c_str(), "r");
+  if(!ptr) {
+    return "[]";
+  }
+  boost::shared_ptr<FILE> fp(ptr, fclose);
+
+  string line;
+  string needle = q;
+  trim_right(needle);
+
+  boost::replace_all(needle, "%20", " ");
+  boost::replace_all(needle, "%22", "\"");
+
+  boost::tokenizer<boost::escaped_list_separator<char> > t(needle, boost::escaped_list_separator<char>("\\", " ", "\""));
+  vector<string> matches(t.begin(), t.end());
+  matches.push_back(prefix);
+
+  boost::circular_buffer<string> lines(200);
+  while(stringfgets(fp.get(), line)) {
+    vector<string>::const_iterator iter;
+    for(iter = matches.begin(); iter != matches.end(); ++iter) {
+      if(!strcasestr(line.c_str(), iter->c_str()))
+        break;
+    }
+    if(iter == matches.end()) {
+      trim_right(line);
+      lines.push_front(line);
+    }
+  }
+
+  Document doc;
+  doc.SetArray();
+  if(!lines.empty()) {
+    BOOST_FOREACH(const string& line, lines) {
+      doc.PushBack(line.c_str(), doc.GetAllocator());
+    }
+  }
+  return makeStringFromDocument(doc);
+}
+
+void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) {
+  if(req->method != "GET")
+    throw HttpMethodNotAllowedException();
+
+  string prefix;
+  switch (versionGetProduct()) {
+  case ProductAuthoritative:
+    prefix = " pdns[";
+    break;
+  case ProductRecursor:
+    prefix = " pdns_recursor[";
+    break;
+  }
+  resp->body = logGrep(req->parameters["q"], ::arg()["experimental-logfile"], prefix);
+}
+
+void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
+  if(req->method != "GET")
+    throw HttpMethodNotAllowedException();
+
+  map<string,string> items;
+  productServerStatisticsFetch(items);
+
+  Document doc;
+  doc.SetArray();
+  typedef map<string, string> items_t;
+   BOOST_FOREACH(const items_t::value_type& item, items) {
+    Value jitem;
+    jitem.SetObject();
+    jitem.AddMember("type", "StatisticItem", doc.GetAllocator());
+
+    Value jname(item.first.c_str(), doc.GetAllocator());
+    jitem.AddMember("name", jname, doc.GetAllocator());
+
+    Value jvalue(item.second.c_str(), doc.GetAllocator());
+    jitem.AddMember("value", jvalue, doc.GetAllocator());
+
+    doc.PushBack(jitem, doc.GetAllocator());
+  }
+
+  resp->body = makeStringFromDocument(doc);
+}
diff --git a/pdns/ws-api.hh b/pdns/ws-api.hh
new file mode 100644 (file)
index 0000000..3f28212
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+    PowerDNS Versatile Database Driven Nameserver
+    Copyright (C) 2002 - 2014  PowerDNS.COM BV
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License version 2
+    as published by the Free Software Foundation
+
+    Additionally, the license of this program contains a special
+    exception which allows to distribute the program in binary form when
+    it is linked against OpenSSL.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+#include <map>
+#include "rapidjson/document.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
+#include "webserver.hh"
+
+void apiServer(HttpRequest* req, HttpResponse* resp);
+void apiServerDetail(HttpRequest* req, HttpResponse* resp);
+void apiServerConfig(HttpRequest* req, HttpResponse* resp);
+void apiServerSearchLog(HttpRequest* req, HttpResponse* resp);
+void apiServerStatistics(HttpRequest* req, HttpResponse* resp);
+
+// To be provided by product code.
+void productServerStatisticsFetch(std::map<string,string>& out);
index d8fc574702a1029ea3410e8f20ec0f0fb9e3b93c..d4a2f4871a7617a3f80a114be1bf558a5ac0697c 100644 (file)
@@ -1,8 +1,8 @@
 /*
-    Copyright (C) 2002 - 2012  PowerDNS.COM BV
+    Copyright (C) 2002 - 2014  PowerDNS.COM BV
 
     This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License version 2 
+    it under the terms of the GNU General Public License version 2
     as published by the Free Software Foundation
 
     Additionally, the license of this program contains a special
 #include "rapidjson/document.h"
 #include "rapidjson/stringbuffer.h"
 #include "rapidjson/writer.h"
+#include "ws-api.hh"
 #include "version.hh"
 
 using namespace rapidjson;
 
 extern StatBag S;
 
-typedef map<string,string> varmap_t;
-
 AuthWebServer::AuthWebServer()
 {
   d_start=time(0);
@@ -272,59 +271,11 @@ void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
   resp->body = ret.str();
 }
 
-static void parseJsonBody(HttpRequest* req, rapidjson::Document& document) {
-  if(document.Parse<0>(req->body.c_str()).HasParseError()) {
-    throw HttpBadRequestException();
-  }
-}
-
-static int intFromJson(const Value& container, const char* key) {
-  const Value& val = container[key];
-  if (val.IsInt()) {
-    return val.GetInt();
-  } else if (val.IsString()) {
-    return atoi(val.GetString());
-  } else {
-    throw ApiException("Key '" + string(key) + "' not an Integer or not present");
-  }
-}
-
-static int intFromJson(const Value& container, const char* key, const int default_value) {
-  const Value& val = container[key];
-  if (val.IsInt()) {
-    return val.GetInt();
-  } else if (val.IsString()) {
-    return atoi(val.GetString());
-  } else {
-    // TODO: check if value really isn't present
-    return default_value;
-  }
-}
-
-static string stringFromJson(const Value& container, const char* key) {
-  const Value& val = container[key];
-  if (val.IsString()) {
-    return val.GetString();
-  } else {
-    throw ApiException("Key '" + string(key) + "' not present or not a String");
-  }
-}
-
-static string stringFromJson(const Value& container, const char* key, const string default_value) {
-  const Value& val = container[key];
-  if (val.IsString()) {
-    return val.GetString();
-  } else {
-    // TODO: check if value really isn't present
-    return default_value;
-  }
-}
-
 static string getZone(const string& zonename) {
   UeberBackend B;
   DomainInfo di;
   if(!B.getDomainInfo(zonename, di))
-    return returnJSONError("Could not find domain '"+zonename+"'");
+    return returnJsonError("Could not find domain '"+zonename+"'");
 
   Document doc;
   doc.SetObject();
@@ -373,115 +324,15 @@ static string getZone(const string& zonename) {
   return makeStringFromDocument(doc);
 }
 
-static void fillServerDetail(Value& out, Value::AllocatorType& allocator) {
-  Value jdaemonType(productTypeApiType().c_str(), allocator);
-  out.SetObject();
-  out.AddMember("type", "Server", allocator);
-  out.AddMember("id", "localhost", allocator);
-  out.AddMember("url", "/servers/localhost", allocator);
-  out.AddMember("daemon_type", jdaemonType, allocator);
-  out.AddMember("version", VERSION, allocator);
-  out.AddMember("config_url", "/servers/localhost/config{/config_setting}", allocator);
-  out.AddMember("zones_url", "/servers/localhost/zones{/zone}", allocator);
-}
-
-static void apiServer(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  vector<string> items = ::arg().list();
-  Document doc;
-  doc.SetArray();
-  Value server;
-  fillServerDetail(server, doc.GetAllocator());
-  doc.PushBack(server, doc.GetAllocator());
-  resp->body = makeStringFromDocument(doc);
-}
-
-static void apiServerDetail(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  vector<string> items = ::arg().list();
-  Document doc;
-  fillServerDetail(doc, doc.GetAllocator());
-  resp->body = makeStringFromDocument(doc);
-}
-
-static void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  vector<string> items = ::arg().list();
-  string value;
-  Document doc;
-  doc.SetArray();
-  BOOST_FOREACH(const string& item, items) {
-    Value jitem;
-    jitem.SetObject();
-    jitem.AddMember("type", "ConfigSetting", doc.GetAllocator());
-
-    Value jname(item.c_str(), doc.GetAllocator());
-    jitem.AddMember("name", jname, doc.GetAllocator());
-
-    if(item.find("password") != string::npos)
-      value = "***";
-    else
-      value = ::arg()[item];
-
-    Value jvalue(value.c_str(), doc.GetAllocator());
-    jitem.AddMember("value", jvalue, doc.GetAllocator());
-
-    doc.PushBack(jitem, doc.GetAllocator());
-  }
-  resp->body = makeStringFromDocument(doc);
-}
-
-static void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
+void productServerStatisticsFetch(map<string,string>& out)
+{
   vector<string> items = S.getEntries();
-  string value;
-  Document doc;
-  doc.SetArray();
   BOOST_FOREACH(const string& item, items) {
-    Value jitem;
-    jitem.SetObject();
-    jitem.AddMember("type", "StatisticItem", doc.GetAllocator());
-
-    Value jname(item.c_str(), doc.GetAllocator());
-    jitem.AddMember("name", jname, doc.GetAllocator());
-
-    value = lexical_cast<string>(S.read(item));
-
-    Value jvalue(value.c_str(), doc.GetAllocator());
-    jitem.AddMember("value", jvalue, doc.GetAllocator());
-
-    doc.PushBack(jitem, doc.GetAllocator());
+    out[item] = lexical_cast<string>(S.read(item));
   }
 
   // add uptime
-  // TODO: this is a hack. should we move this elsewhere?
-  {
-    Value jitem;
-    jitem.SetObject();
-    jitem.AddMember("type", "StatisticItem", doc.GetAllocator());
-    jitem.AddMember("name", "uptime", doc.GetAllocator());
-    value = lexical_cast<string>(time(0) - s_starttime);
-    Value jvalue(value.c_str(), doc.GetAllocator());
-    jitem.AddMember("value", jvalue, doc.GetAllocator());
-    doc.PushBack(jitem, doc.GetAllocator());
-  }
-
-  resp->body = makeStringFromDocument(doc);
-}
-
-static void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) {
-  if(req->method != "GET")
-    throw HttpMethodNotAllowedException();
-
-  resp->body = makeLogGrepJSON(req->parameters["q"], ::arg()["experimental-logfile"], " pdns[");
+  out["uptime"] = lexical_cast<string>(time(0) - s_starttime);
 }
 
 static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
@@ -489,7 +340,7 @@ static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
   if (req->method == "POST") {
     DomainInfo di;
     Document document;
-    parseJsonBody(req, document);
+    req->json(document);
     string zonename = stringFromJson(document, "name");
     // TODO: better validation of zonename
     if(zonename.empty())
@@ -602,7 +453,7 @@ static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
       throw ApiException("Could not find domain '"+zonename+"'");
 
     Document document;
-    parseJsonBody(req, document);
+    req->json(document);
 
     string master;
     const Value &masters = document["masters"];
@@ -655,7 +506,7 @@ static void apiServerZoneRRset(HttpRequest* req, HttpResponse* resp) {
     throw ApiException("Could not find domain '"+zonename+"'");
 
   Document document;
-  parseJsonBody(req, document);
+  req->json(document);
 
   string qname, changetype;
   QType qtype;
@@ -730,7 +581,7 @@ void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
     map<string, string> object;
     object["number"]=lexical_cast<string>(number);
     //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
-    resp->body = returnJSONObject(object);
+    resp->body = returnJsonObject(object);
     return;
   }
   else if(command == "pdns-control") {
@@ -738,11 +589,7 @@ void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
       throw HttpMethodNotAllowedException();
     // cout<<"post: "<<post<<endl;
     rapidjson::Document document;
-    if(document.Parse<0>(req->body.c_str()).HasParseError()) {
-      resp->status = 400;
-      resp->body = returnJSONError("Unable to parse JSON");
-      return;
-    }
+    req->json(document);
     // cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
     vector<string> parameters;
     stringtok(parameters, document["parameters"].GetString(), " \t");
@@ -758,15 +605,17 @@ void AuthWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
       resp->status = 404;
       m["error"]="No such function "+toUpper(parameters[0]);
     }
-    resp->body = returnJSONObject(m);
+    resp->body = returnJsonObject(m);
     return;
   }
   else if(command=="log-grep") {
-    resp->body = makeLogGrepJSON(req->parameters["needle"], ::arg()["experimental-logfile"], " pdns[");
+    // legacy parameter name hack
+    req->parameters["q"] = req->parameters["needle"];
+    apiServerSearchLog(req, resp);
     return;
   }
 
-  resp->body = returnJSONError("No or unknown command given");
+  resp->body = returnJsonError("No or unknown command given");
   resp->status = 404;
   return;
 }
index 139a4dfe8447f2dec50110169697b982de600247..657a04906041d5ed49591fa015a07f491327f1fa 100644 (file)
@@ -3,7 +3,7 @@
     Copyright (C) 2003 - 2012  PowerDNS.COM BV
 
     This program is free software; you can redistribute it and/or modify
-    it under the terms of the GNU General Public License version 2 
+    it under the terms of the GNU General Public License version 2
     as published by the Free Software Foundation
 
     Additionally, the license of this program contains a special
 #include "arguments.hh"
 #include "misc.hh"
 #include "syncres.hh"
-#include "config.h"
 #include "rapidjson/document.h"
 #include "rapidjson/stringbuffer.h"
 #include "rapidjson/writer.h"
 #include "webserver.hh"
+#include "ws-api.hh"
 
 using namespace rapidjson;
 
+void productServerStatisticsFetch(map<string,string>& out)
+{
+  map<string,string> stats = getAllStatsMap();
+  out.swap(stats);
+}
+
 RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
 {
   RecursorControlParser rcp; // inits
@@ -49,6 +55,11 @@ RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
 
   // legacy dispatch
   d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2));
+  d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
+  d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
+  d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
+  d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
+  d_ws->registerApiHandler("/servers", &apiServer);
 
   d_ws->go();
 }
@@ -135,7 +146,7 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
       resp->body = makeStringFromDocument(doc);
       return;
     } else {
-      resp->body = returnJSONError("Could not find domain '"+arg_zone+"'");
+      resp->body = returnJsonError("Could not find domain '"+arg_zone+"'");
       return;
     }
   }
@@ -144,7 +155,7 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
     int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon));
     count+=broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon));
     stats["number"]=lexical_cast<string>(count);
-    resp->body = returnJSONObject(stats);
+    resp->body = returnJsonObject(stats);
     return;
   }
   else if(command == "config") {
@@ -152,19 +163,21 @@ void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
     BOOST_FOREACH(const string& var, items) {
       stats[var] = ::arg()[var];
     }
-    resp->body = returnJSONObject(stats);
+    resp->body = returnJsonObject(stats);
     return;
   }
   else if(command == "log-grep") {
-    resp->body = makeLogGrepJSON(req->parameters["needle"], ::arg()["experimental-logfile"], " pdns_recursor[");
+    // legacy parameter name hack
+    req->parameters["q"] = req->parameters["needle"];
+    apiServerSearchLog(req, resp);
     return;
   }
   else if(command == "stats") {
     stats = getAllStatsMap();
-    resp->body = returnJSONObject(stats);
+    resp->body = returnJsonObject(stats);
     return;
   } else {
     resp->status = 404;
-    resp->body = returnJSONError("Not found");
+    resp->body = returnJsonError("Not found");
   }
 }
index 3f239d30c69414b17b68345f25cb832dd83982a8..f729e6b71bc53c18c8accfb83c60d1bb1847121c 100644 (file)
 #include <boost/utility.hpp> 
 #include "namespaces.hh"
 #include "mplexer.hh"
-#include "webserver.hh"
+
+class AsyncWebServer;
+class HttpRequest;
+class HttpResponse;
 
 class RecursorWebServer : public boost::noncopyable
 {