From: Christian Hofstaedtler Date: Thu, 30 Jan 2014 22:31:30 +0000 (+0100) Subject: api: share apiServer* code across auth, recursor X-Git-Tag: rec-3.6.0-rc1~211^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6ec5e728152684454b3358f4daa21f75362e50d6;p=thirdparty%2Fpdns.git api: share apiServer* code across auth, recursor 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(). --- diff --git a/pdns/Makefile-recursor b/pdns/Makefile-recursor index 171e21cc55..d3eba8f29f 100644 --- a/pdns/Makefile-recursor +++ b/pdns/Makefile-recursor @@ -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 diff --git a/pdns/Makefile.am b/pdns/Makefile.am index bea08dd6c2..6b0a018ad1 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -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) diff --git a/pdns/dist-recursor b/pdns/dist-recursor index 2342120062..ed0a328050 100755 --- a/pdns/dist-recursor +++ b/pdns/dist-recursor @@ -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 diff --git a/pdns/json.cc b/pdns/json.cc index c2132e1a01..de37bcf626 100644 --- a/pdns/json.cc +++ b/pdns/json.cc @@ -1,61 +1,58 @@ #include "json.hh" #include "namespaces.hh" -#include -#include -#include -#include "namespaces.hh" #include "misc.hh" #include #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" -#include "config.h" -#include -#include -#include - -#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& items) +string returnJsonObject(const map& items) { Document doc; doc.SetObject(); @@ -76,7 +73,7 @@ string returnJSONObject(const map& 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 fp(ptr, fclose); - - string line; - string needle = q; - trim_right(needle); - - boost::replace_all(needle, "%20", " "); - boost::replace_all(needle, "%22", "\""); - - boost::tokenizer > t(needle, boost::escaped_list_separator("\\", " ", "\"")); - vector matches(t.begin(), t.end()); - matches.push_back(prefix); - - boost::circular_buffer lines(200); - while(stringfgets(fp.get(), line)) { - vector::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); -} diff --git a/pdns/json.hh b/pdns/json.hh index 3945b05c5b..17021b8505 100644 --- a/pdns/json.hh +++ b/pdns/json.hh @@ -23,9 +23,20 @@ #include #include +#include #include "rapidjson/document.h" -std::string returnJSONObject(const std::map& 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& 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) { + } +}; diff --git a/pdns/webserver.cc b/pdns/webserver.cc index 1f55c7999a..20bf17b510 100644 --- a/pdns/webserver.cc +++ b/pdns/webserver.cc @@ -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 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 = "" + what + "

" + what + "

"; } 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; diff --git a/pdns/webserver.hh b/pdns/webserver.hh index b6e472f158..8aef0c47bf 100644 --- a/pdns/webserver.hh +++ b/pdns/webserver.hh @@ -26,8 +26,11 @@ #include #include #include - +#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 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 index 0000000000..da14b57492 --- /dev/null +++ b/pdns/ws-api.cc @@ -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 +#include +#include +#include "namespaces.hh" +#include "ws-api.hh" +#include "json.hh" +#include "config.h" +#include "version.hh" +#include "arguments.hh" +#include +#include +#include +#include + +#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 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 fp(ptr, fclose); + + string line; + string needle = q; + trim_right(needle); + + boost::replace_all(needle, "%20", " "); + boost::replace_all(needle, "%22", "\""); + + boost::tokenizer > t(needle, boost::escaped_list_separator("\\", " ", "\"")); + vector matches(t.begin(), t.end()); + matches.push_back(prefix); + + boost::circular_buffer lines(200); + while(stringfgets(fp.get(), line)) { + vector::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 items; + productServerStatisticsFetch(items); + + Document doc; + doc.SetArray(); + typedef map 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 index 0000000000..3f28212e67 --- /dev/null +++ b/pdns/ws-api.hh @@ -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 +#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& out); diff --git a/pdns/ws-auth.cc b/pdns/ws-auth.cc index d8fc574702..d4a2f4871a 100644 --- a/pdns/ws-auth.cc +++ b/pdns/ws-auth.cc @@ -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 @@ -36,14 +36,13 @@ #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 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 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 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 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& out) +{ vector 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(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(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(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(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 object; object["number"]=lexical_cast(number); //cerr<<"Flushed cache for '"<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: "<(req->body.c_str()).HasParseError()) { - resp->status = 400; - resp->body = returnJSONError("Unable to parse JSON"); - return; - } + req->json(document); // cout<<"Parameters: '"< 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; } diff --git a/pdns/ws-recursor.cc b/pdns/ws-recursor.cc index 139a4dfe84..657a049060 100644 --- a/pdns/ws-recursor.cc +++ b/pdns/ws-recursor.cc @@ -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 @@ -30,14 +30,20 @@ #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& out) +{ + map 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(boost::bind(pleaseWipeCache, canon)); count+=broadcastAccFunction(boost::bind(pleaseWipeAndCountNegCache, canon)); stats["number"]=lexical_cast(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"); } } diff --git a/pdns/ws-recursor.hh b/pdns/ws-recursor.hh index 3f239d30c6..f729e6b71b 100644 --- a/pdns/ws-recursor.hh +++ b/pdns/ws-recursor.hh @@ -22,7 +22,10 @@ #include #include "namespaces.hh" #include "mplexer.hh" -#include "webserver.hh" + +class AsyncWebServer; +class HttpRequest; +class HttpResponse; class RecursorWebServer : public boost::noncopyable {