From: Christian Hofstaedtler Date: Wed, 29 Jan 2014 21:59:34 +0000 (+0100) Subject: use class Webserver to implement recursor http server X-Git-Tag: rec-3.6.0-rc1~211^2~8 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3ae143b0bbf903710cfe046bc9183275842125f0;p=thirdparty%2Fpdns.git use class Webserver to implement recursor http server --- diff --git a/pdns/Makefile-recursor b/pdns/Makefile-recursor index d6cc1d9749..9ceadfbe85 100644 --- a/pdns/Makefile-recursor +++ b/pdns/Makefile-recursor @@ -4,7 +4,7 @@ BINDIR=/usr/bin/ SYSCONFDIR=/etc/powerdns/ LOCALSTATEDIR=/var/run/ OPTFLAGS?=-O3 -CXXFLAGS:= $(CXXFLAGS) -Iext/rapidjson/include -I$(CURDIR)/ext/polarssl-1.3.2/include -Wall $(OPTFLAGS) $(PROFILEFLAGS) $(ARCHFLAGS) -pthread +CXXFLAGS:= $(CXXFLAGS) -Iext/rapidjson/include -I$(CURDIR)/ext/polarssl-1.3.2/include -Wall $(OPTFLAGS) $(PROFILEFLAGS) $(ARCHFLAGS) -pthread -Iext/yahttp CFLAGS:=$(CFLAGS) -Wall $(OPTFLAGS) $(PROFILEFLAGS) $(ARCHFLAGS) -I$(CURDIR)/ext/polarssl-1.3.2/include -pthread LDFLAGS:=$(LDFLAGS) $(ARCHFLAGS) -pthread @@ -21,7 +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 json_ws.o version.o responsestats.o +reczones.o base32.o nsecrecords.o json.o json_ws.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 @@ -105,7 +106,7 @@ clean: binclean -rm -f dep *~ *.gcda *.gcno optional/*.gcda optional/*.gcno binclean: - -rm -f *.o pdns_recursor rec_control optional/*.o build-stamp ext/polarssl-1.3.2/library/*.o + -rm -f *.o pdns_recursor rec_control optional/*.o build-stamp ext/polarssl-1.3.2/library/*.o ext/yahttp/yahttp/*.o dep: $(CXX) $(CXXFLAGS) -MM -MG *.cc *.hh > $@ diff --git a/pdns/Makefile.am b/pdns/Makefile.am index ac6e56de97..b2d5b206c3 100644 --- a/pdns/Makefile.am +++ b/pdns/Makefile.am @@ -1,6 +1,8 @@ AM_CXXFLAGS=-DSYSCONFDIR=\"@sysconfdir@\" -DLIBDIR=\"@libdir@\" -DLOCALSTATEDIR=\"@socketdir@\" @THREADFLAGS@ $(LUA_CFLAGS) $(SQLITE3_CFLAGS) $(POLARSSL_CFLAGS) -Iext/rapidjson/include -Iext/yahttp +YAHTTP_LIBS = -Lext/yahttp/yahttp -lyahttp + AM_LFLAGS = -i AM_YFLAGS = -d --verbose --debug @@ -63,7 +65,7 @@ version.hh version.cc rfc2136handler.cc responsestats.cc responsestats.hh pdns_server_LDFLAGS=@moduleobjects@ @modulelibs@ @DYNLINKFLAGS@ @LIBDL@ @THREADFLAGS@ $(BOOST_SERIALIZATION_LDFLAGS) -rdynamic -pdns_server_LDADD= $(POLARSSL_LIBS) $(BOOST_SERIALIZATION_LIBS) $(LUA_LIBS) $(SQLITE3_LIBS) -Lext/yahttp/yahttp -lyahttp +pdns_server_LDADD= $(POLARSSL_LIBS) $(BOOST_SERIALIZATION_LIBS) $(LUA_LIBS) $(SQLITE3_LIBS) $(YAHTTP_LIBS) if BOTAN110 pdns_server_SOURCES += botan110signers.cc botansigners.cc @@ -282,10 +284,10 @@ rec_channel_rec.cc selectmplexer.cc epollmplexer.cc sillyrecords.cc htimer.cc ht 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 json_ws.cc json_ws.hh \ -json.cc json.hh version.hh version.cc responsestats.cc +json.cc json.hh version.hh version.cc responsestats.cc webserver.cc webserver.hh session.cc session.hh pdns_recursor_LDFLAGS= $(LUA_LIBS) -pdns_recursor_LDADD= $(POLARSSL_LIBS) +pdns_recursor_LDADD= $(POLARSSL_LIBS) $(YAHTTP_LIBS) pdns_control_SOURCES=dynloader.cc dynmessenger.cc arguments.cc logger.cc statbag.cc \ misc.cc unix_utility.cc qtype.cc diff --git a/pdns/dist-recursor b/pdns/dist-recursor index c0e4c973c5..f7a29c9ae2 100755 --- a/pdns/dist-recursor +++ b/pdns/dist-recursor @@ -26,7 +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 \ -responsestats.hh" +responsestats.hh webserver.hh session.hh" CFILES="syncres.cc misc.cc unix_utility.cc qtype.cc \ logger.cc arguments.cc lwres.cc pdns_recursor.cc \ @@ -36,7 +36,7 @@ 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 json_ws.cc json_ws.hh version.cc dns_random.cc \ -responsestats.cc" +responsestats.cc webserver.cc session.cc" cd docs make pdns_recursor.1 rec_control.1 @@ -61,6 +61,7 @@ mkdir -p $DIRNAME/ext/polarssl-1.3.2/include/polarssl cp -a ext/polarssl-1.3.2/include/polarssl/config.h ext/polarssl-1.3.2/include/polarssl/aes.h ext/polarssl-1.3.2/include/polarssl/padlock.h $DIRNAME/ext/polarssl-1.3.2/include/polarssl mkdir -p $DIRNAME/ext/polarssl-1.3.2/library cp -a ext/polarssl-1.3.2/library/aes.c ext/polarssl-1.3.2/library/padlock.c $DIRNAME/ext/polarssl-1.3.2/library +cp -a ext/yahttp/ $DIRNAME/ext/ mkdir $DIRNAME/rrd cp tools/rrd/{create,update,makegraphs,index.html} $DIRNAME/rrd cp pdns-recursor.init.d $DIRNAME diff --git a/pdns/json_ws.cc b/pdns/json_ws.cc index d51fbb6b69..6204e53295 100644 --- a/pdns/json_ws.cc +++ b/pdns/json_ws.cc @@ -34,108 +34,33 @@ #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" +#include "webserver.hh" using namespace rapidjson; -JWebserver::JWebserver(FDMultiplexer* fdm) : d_fdm(fdm) +JWebserver::JWebserver(FDMultiplexer* fdm) { RecursorControlParser rcp; // inits - d_socket = socket(AF_INET6, SOCK_STREAM, 0); - if(d_socket<0) { - throw PDNSException("Making webserver socket: "+stringerror()); - } - setSocketReusable(d_socket); - ComboAddress local("::", 8082); - if(bind(d_socket, (struct sockaddr*)&local, local.getSocklen())<0) { - throw PDNSException("Binding webserver socket: "+stringerror()); - } - listen(d_socket, 5); - - d_fdm->addReadFD(d_socket, boost::bind(&JWebserver::newConnection, this)); -} -void JWebserver::readRequest(int fd) -{ - char buffer[16384]; - int res = read(fd, buffer, sizeof(buffer)-1); - if(res <= 0) { - d_fdm->removeReadFD(fd); - close(fd); + if(!arg().mustDo("webserver")) return; - } - buffer[res]=0; - - // Note: this code makes it impossible to read the request body. - // We'll at least need to wait for two \r\n sets to arrive, parse the - // headers, and then read the body (using the supplied Content-Length). - char *p = strchr(buffer, '\r'); - if(p) *p = 0; - - vector parts; - string method, uri; - if(strlen(buffer) < 2048) { - stringtok(parts, buffer); - if(parts.size()>1) { - method=parts[0]; - uri=parts[1]; - } - } - string content; + d_ws = new AsyncWebServer(fdm, arg()["webserver-address"], arg().asNum("webserver-port"), arg()["webserver-password"]); - string status = "200 OK"; - string headers = "Date: Wed, 30 Nov 2012 22:01:15 GMT\r\n" - "Server: PowerDNS Recursor/"VERSION"\r\n" - "Connection: keep-alive\r\n"; + // legacy dispatch + d_ws->registerApiHandler("/jsonstat", boost::bind(&JWebserver::jsonstat, this, _1, _2)); - if (method != "GET") { - status = "400 Bad Request"; - content = "Your client sent a request this server could not understand.\n"; - } else { - parts.clear(); - stringtok(parts, uri, "?"); - map varmap; - if(parts.size()>1) { - vector variables; - stringtok(variables, parts[1], "&"); - BOOST_FOREACH(const string& var, variables) { - varmap.insert(splitField(var, '=')); - } - } - - content = handleRequest(method, uri, varmap, headers); - } - - const char *headers_append = "Content-Length: %d\r\n\r\n"; - string reply = "HTTP/1.1 " + status + "\r\n" + headers + - (boost::format(headers_append) % content.length()).str() + - content; - - Utility::setBlocking(fd); - writen2(fd, reply.c_str(), reply.length()); - Utility::setNonBlocking(fd); + d_ws->go(); } -string JWebserver::handleRequest(const string &method, const string &uri, const map &rovarmap, string &headers) +void JWebserver::jsonstat(HttpRequest* req, HttpResponse *resp) { - map varmap = rovarmap; - string callback; - if (varmap.count("callback")) { - callback = varmap["callback"]; - varmap.erase("callback"); - } string command; - if (varmap.count("command")) { - command = varmap["command"]; - varmap.erase("command"); - } - - headers += "Access-Control-Allow-Origin: *\r\n"; - headers += "Content-Type: application/json\r\n"; - string content; - if(!callback.empty()) - content=callback+"("; + if(req->parameters.count("command")) { + command = req->parameters["command"]; + req->parameters.erase("command"); + } map stats; if(command == "domains") { @@ -162,10 +87,12 @@ string JWebserver::handleRequest(const string &method, const string &uri, const doc.PushBack(jzone, doc.GetAllocator()); } - content += makeStringFromDocument(doc); + resp->body = makeStringFromDocument(doc); + return; } else if(command == "zone") { - SyncRes::domainmap_t::const_iterator ret = t_sstorage->domainmap->find(varmap["zone"]); + string arg_zone = req->parameters["zone"]; + SyncRes::domainmap_t::const_iterator ret = t_sstorage->domainmap->find(arg_zone); if (ret != t_sstorage->domainmap->end()) { Document doc; doc.SetObject(); @@ -205,48 +132,39 @@ string JWebserver::handleRequest(const string &method, const string &uri, const root.AddMember("records", records, doc.GetAllocator()); doc.AddMember("zone", root, doc.GetAllocator()); - content += makeStringFromDocument(doc); + resp->body = makeStringFromDocument(doc); + return; } else { - content += returnJSONError("Could not find domain '"+varmap["zone"]+"'"); + resp->body = returnJSONError("Could not find domain '"+arg_zone+"'"); + return; } } else if(command == "flush-cache") { - string canon=toCanonic("", varmap["domain"]); + string canon=toCanonic("", req->parameters["domain"]); int count = broadcastAccFunction(boost::bind(pleaseWipeCache, canon)); count+=broadcastAccFunction(boost::bind(pleaseWipeAndCountNegCache, canon)); stats["number"]=lexical_cast(count); - content += returnJSONObject(stats); + resp->body = returnJSONObject(stats); + return; } else if(command == "config") { vector items = ::arg().list(); BOOST_FOREACH(const string& var, items) { stats[var] = ::arg()[var]; } - content += returnJSONObject(stats); + resp->body = returnJSONObject(stats); + return; } else if(command == "log-grep") { - content += makeLogGrepJSON(varmap["needle"], ::arg()["experimental-logfile"], " pdns_recursor["); + resp->body = makeLogGrepJSON(req->parameters["needle"], ::arg()["experimental-logfile"], " pdns_recursor["); + return; } - else { // if(command == "stats") { + else if(command == "stats") { stats = getAllStatsMap(); - content += returnJSONObject(stats); - } - - if(!callback.empty()) - content += ");"; - - return content; -} - -void JWebserver::newConnection() -{ - ComboAddress remote; - remote.sin4.sin_family=AF_INET6; - socklen_t remlen = remote.getSocklen(); - int sock = accept(d_socket, (struct sockaddr*) &remote, &remlen); - if(sock < 0) + resp->body = returnJSONObject(stats); return; - - Utility::setNonBlocking(sock); - d_fdm->addReadFD(sock, boost::bind(&JWebserver::readRequest, this, _1)); + } else { + resp->status = 404; + resp->body = returnJSONError("Not found"); + } } diff --git a/pdns/json_ws.hh b/pdns/json_ws.hh index 061111ef4a..ed40ee1c32 100644 --- a/pdns/json_ws.hh +++ b/pdns/json_ws.hh @@ -22,17 +22,16 @@ #include #include "namespaces.hh" #include "mplexer.hh" +#include "webserver.hh" class JWebserver : public boost::noncopyable { - public: - explicit JWebserver(FDMultiplexer* fdm); - void newConnection(); - void readRequest(int fd); - string handleRequest(const string &method, const string &uri, const map &varmap, string &headers); - private: - FDMultiplexer* d_fdm; - int d_socket; +public: + explicit JWebserver(FDMultiplexer* fdm); + void jsonstat(HttpRequest* req, HttpResponse *resp); + +private: + AsyncWebServer* d_ws; }; string returnJSONStats(const map& items); diff --git a/pdns/misc.hh b/pdns/misc.hh index 67480765c5..3b149c376d 100644 --- a/pdns/misc.hh +++ b/pdns/misc.hh @@ -462,12 +462,6 @@ inline string toCanonic(const string& zone, const string& domain) return ret; } -inline void setSocketReusable(int fd) -{ - int tmp=1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char*)&tmp, static_cast(sizeof tmp)); -} - string stripDot(const string& dom); void seedRandom(const string& source); string makeRelative(const std::string& fqdn, const std::string& zone); diff --git a/pdns/pdns_recursor.cc b/pdns/pdns_recursor.cc index 373632fcc7..256f5492da 100644 --- a/pdns/pdns_recursor.cc +++ b/pdns/pdns_recursor.cc @@ -2034,6 +2034,10 @@ int main(int argc, char **argv) ::arg().set("config-name","Name of this virtual configuration - will rename the binary image")=""; ::arg().set( "experimental-logfile", "Filename of the log file for JSON parser" )= "/var/log/pdns.log"; ::arg().setSwitch( "experimental-json-interface", "If we should run a JSON webserver") = "no"; + ::arg().setSwitch("webserver", "Start a webserver for monitoring") = "no"; + ::arg().set("webserver-address", "IP Address of webserver to listen on") = "127.0.0.1"; + ::arg().set("webserver-port", "Port of webserver to listen on") = "8082"; + ::arg().set("webserver-password", "Password required for accessing the webserver") = ""; ::arg().set("quiet","Suppress logging of questions and answers")=""; ::arg().set("logging-facility","Facility to log messages as. 0 corresponds to local0")=""; ::arg().set("config-dir","Location of configuration directory (recursor.conf)")=SYSCONFDIR; diff --git a/pdns/session.cc b/pdns/session.cc index 1fe586b6b2..c3ee0a2aa4 100644 --- a/pdns/session.cc +++ b/pdns/session.cc @@ -31,16 +31,14 @@ #include "misc.hh" #include "iputils.hh" -void Session::init() +Session::Session(int s, ComboAddress r) : d_good(true) { - d_good = true; + d_remote=r; + d_socket=s; } -Session::Session(int s, struct sockaddr_in r) +Session::Session() : d_good(false) { - init(); - d_remote=r; - d_socket=s; } int Session::close() @@ -65,10 +63,10 @@ Session::~Session() //! This function makes a deep copy of Session Session::Session(const Session &s) { - init(); - d_socket=s.d_socket; d_remote=s.d_remote; + d_good=s.d_good; + d_timeout=s.d_timeout; } void Session::setTimeout(unsigned int seconds) @@ -76,7 +74,6 @@ void Session::setTimeout(unsigned int seconds) d_timeout=seconds; } - bool Session::put(const string &s) { int length=s.length(); @@ -136,15 +133,15 @@ int Session::getSocket() return d_socket; } -Session *Server::accept() +Session Server::accept() { - struct sockaddr_in d_remote; - Utility::socklen_t len=sizeof(d_remote); + ComboAddress remote; + remote.sin4.sin_family = AF_INET6; + socklen_t remlen = remote.getSocklen(); - int d_socket=-1; + int socket=-1; - - while((d_socket=::accept(s,(struct sockaddr *)(&d_remote),&len))==-1) // repeat until we have a successful connect + while((socket=::accept(s, (struct sockaddr *)&remote, &remlen))==-1) // repeat until we have a successful connect { // L<addReadFD(s, boost::bind(&Server::asyncNewConnection, this)); } Server::Server(const string &localaddress, int port) @@ -165,9 +179,9 @@ Server::Server(const string &localaddress, int port) throw SessionException(string("socket: ")+strerror(errno)); Utility::setCloseOnExec(s); - + int tmp=1; - if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR,(char*)&tmp,sizeof tmp)<0) + if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&tmp, static_cast(sizeof tmp))<0) throw SessionException(string("Setsockopt failed: ")+strerror(errno)); if(bind(s, (sockaddr*)&d_local, d_local.getSocklen())<0) diff --git a/pdns/session.hh b/pdns/session.hh index 81a0ec6437..be854578e4 100644 --- a/pdns/session.hh +++ b/pdns/session.hh @@ -37,6 +37,7 @@ #include "iputils.hh" #include "pdnsexception.hh" +#include "mplexer.hh" class SessionException: public PDNSException { @@ -58,7 +59,7 @@ public: bool good(); size_t read(char* buf, size_t len); - Session(int s, struct sockaddr_in r); //!< Start a session on an existing socket, and inform this class of the remotes name + Session(int s, ComboAddress r); //!< Start a session on an existing socket, and inform this class of the remotes name /** Create a session to a remote host and port. This function reads a timeout value from the ArgvMap class and does a nonblocking connect to support this timeout. It should be noted that nonblocking connects @@ -66,6 +67,7 @@ public: Session(const string &remote, int port, int timeout=0); Session(const Session &s); + Session(); ~Session(); int getSocket(); //!< return the filedescriptor for layering violations @@ -73,8 +75,7 @@ public: void setTimeout(unsigned int seconds); private: int d_socket; - struct sockaddr_in d_remote; - void init(); + ComboAddress d_remote; int d_timeout; bool d_good; }; @@ -84,11 +85,17 @@ class Server { public: Server(const string &localaddress, int port); - Session* accept(); //!< Call accept() in an endless loop to accept new connections ComboAddress d_local; + Session accept(); //!< Call accept() in an endless loop to accept new connections + + typedef boost::function< void(Session) > newconnectioncb_t; + void asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback); + private: int s; + void asyncNewConnection(); + newconnectioncb_t d_asyncNewConnectionCallback; }; #endif /* SESSION_HH */ diff --git a/pdns/webserver.cc b/pdns/webserver.cc index f4bacade62..1f55c7999a 100644 --- a/pdns/webserver.cc +++ b/pdns/webserver.cc @@ -29,10 +29,13 @@ #include "dns.hh" #include "base64.hh" #include "json.hh" +#include "mplexer.hh" + +const char* INVALID_REQUEST_RESPONSE = "HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\nYour Browser sent a request that this server failed to understand.\r\n"; struct connectionThreadData { WebServer* webServer; - Session* client; + Session client; }; int WebServer::B64Decode(const std::string& strInput, std::string& strOutput) @@ -83,6 +86,38 @@ void WebServer::registerHandler(const string& url, HandlerFunction handler) d_handlers.push_back(reg); } +static void apiWrapper(boost::function 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"); + } + + req->parameters.erase("_"); // jQuery cache buster + + try { + handler(req, resp); + } catch (ApiException &e) { + string what = e.what(); + resp->body = returnJSONError(what); + resp->status = 422; + return; + } + + if(!callback.empty()) { + resp->body = callback + "(" + resp->body + ");"; + } +} + +void WebServer::registerApiHandler(const string& url, HandlerFunction handler) { + HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2); + registerHandler(url, f); +} + bool WebServer::route(const std::string& url, std::map& pathArgs, HandlerFunction** handler) { for (std::list::iterator reg=d_handlers.begin(); reg != d_handlers.end(); ++reg) { @@ -128,8 +163,7 @@ static void *WebServerConnectionThreadStart(void *p) { pthread_detach(pthread_self()); data->webServer->serveConnection(data->client); - data->client->close(); - delete data->client; + data->client.close(); delete data; @@ -204,19 +238,19 @@ HttpResponse WebServer::handleRequest(HttpRequest req) return resp; } -void WebServer::serveConnection(Session* client) +void WebServer::serveConnection(Session client) try { HttpRequest req; YaHTTP::AsyncRequestLoader yarl(&req); - client->setTimeout(5); + client.setTimeout(5); bool complete = false; try { - while(client->good()) { + while(client.good()) { int bytes; char buf[1024]; - bytes = client->read(buf, sizeof(buf)); + bytes = client.read(buf, sizeof(buf)); if (bytes) { string data = string(buf, bytes); if (yarl.feed(data)) { @@ -230,14 +264,14 @@ try { } if (!complete) { - client->put("HTTP/1.0 400 Bad Request\r\nConnection: close\r\n\r\nYour Browser sent a request that this server failed to understand.\r\n"); + client.put(INVALID_REQUEST_RESPONSE); return; } HttpResponse resp = WebServer::handleRequest(req); ostringstream ss; resp.write(ss); - client->put(ss.str()); + client.put(ss.str()); } catch(SessionTimeoutException &e) { // L<d_local.toStringWithPort() <accept())) { + while(true) { // will be freed by thread connectionThreadData *data = new connectionThreadData; data->webServer = this; - data->client = client; + data->client = d_server->accept(); pthread_create(&tid, 0, &WebServerConnectionThreadStart, (void *)data); } } @@ -299,3 +332,72 @@ void WebServer::go() exit(1); } + +void AsyncWebServer::go() +{ + if (!d_server) + return; + + d_server->asyncWaitForConnections(d_fdm, boost::bind(&AsyncWebServer::newConnection, this, _1)); +} + +void AsyncWebServer::newConnection(Session session) +{ + int fd = session.getSocket(); + Utility::setNonBlocking(fd); + d_fdm->addReadFD(fd, boost::bind(&AsyncWebServer::serveConnection, this, session)); +} + +void AsyncWebServer::serveConnection(Session session) +{ + int fd = session.getSocket(); + d_fdm->removeReadFD(fd); + + try { + char buffer[16384]; + int res = read(fd, buffer, sizeof(buffer)-1); + if (res <= 0) { + throw PDNSException("Reading from client failed"); + return; + } + buffer[res]=0; + + HttpRequest req; + YaHTTP::AsyncRequestLoader yarl(&req); + + bool complete = false; + string reply; + + try { + if (yarl.feed(buffer)) { + complete = true; + } + } catch (YaHTTP::ParseError &e) { + complete = false; + } + + if (complete) { + HttpResponse resp = handleRequest(req); + ostringstream ss; + resp.write(ss); + reply = ss.str(); + } else { + reply = INVALID_REQUEST_RESPONSE; + } + + Utility::setBlocking(fd); + writen2(fd, reply.c_str(), reply.length()); + Utility::setNonBlocking(fd); + } + catch(PDNSException &e) { + L< #include #include -#include "yahttp/yahttp.hpp" +#include +#include #include "namespaces.hh" class Server; @@ -87,14 +88,20 @@ public: HttpMethodNotAllowedException() : HttpException(405) { }; }; +class ApiException : public runtime_error +{ +public: + ApiException(const string& what) : runtime_error(what) { + } +}; -class WebServer +class WebServer : public boost::noncopyable { public: WebServer(const string &listenaddress, int port, const string &password=""); void go(); - void serveConnection(Session* client); + void serveConnection(Session client); HttpResponse handleRequest(HttpRequest request); typedef boost::function HandlerFunction; @@ -105,8 +112,9 @@ public: }; void registerHandler(const string& url, HandlerFunction handler); + void registerApiHandler(const string& url, HandlerFunction handler); -private: +protected: static char B64Decode1(char cInChar); static int B64Decode(const std::string& strInput, std::string& strOutput); bool route(const std::string& url, std::map& urlArgs, HandlerFunction** handler); @@ -117,4 +125,21 @@ private: string d_password; Server* d_server; }; + +class FDMultiplexer; + +class AsyncWebServer : public WebServer +{ +public: + AsyncWebServer(FDMultiplexer* fdm, const string &listenaddress, int port, const string &password="") : + WebServer(listenaddress, port, password), d_fdm(fdm) { }; + void go(); + +private: + FDMultiplexer* d_fdm; + + void newConnection(Session session); + void serveConnection(Session session); +}; + #endif /* WEBSERVER_HH */ diff --git a/pdns/ws.cc b/pdns/ws.cc index 6c0e2ab0ba..2d2d1958f6 100644 --- a/pdns/ws.cc +++ b/pdns/ws.cc @@ -44,13 +44,6 @@ extern StatBag S; typedef map varmap_t; -class ApiException : public runtime_error -{ -public: - ApiException(const string& what) : runtime_error(what) { - } -}; - StatWebServer::StatWebServer() { d_start=time(0); @@ -778,38 +771,6 @@ void StatWebServer::jsonstat(HttpRequest* req, HttpResponse* resp) return; } -static void apiWrapper(boost::function 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"); - } - - req->parameters.erase("_"); // jQuery cache buster - - try { - handler(req, resp); - } catch (ApiException &e) { - string what = e.what(); - resp->body = returnJSONError(what); - resp->status = 422; - return; - } - - if(!callback.empty()) { - resp->body = callback + "(" + resp->body + ");"; - } -} - -void StatWebServer::registerApiHandler(const string& url, boost::function handler) { - WebServer::HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2); - d_ws->registerHandler(url, f); -} - void StatWebServer::cssfunction(HttpRequest* req, HttpResponse* resp) { resp->headers["Cache-Control"] = "max-age=86400"; @@ -849,16 +810,16 @@ void StatWebServer::launch() { try { if(::arg().mustDo("experimental-json-interface")) { - registerApiHandler("/servers/localhost/config", &apiServerConfig); - registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog); - registerApiHandler("/servers/localhost/statistics", &apiServerStatistics); - registerApiHandler("/servers/localhost/zones//rrset", &apiServerZoneRRset); - registerApiHandler("/servers/localhost/zones/", &apiServerZoneDetail); - registerApiHandler("/servers/localhost/zones", &apiServerZones); - registerApiHandler("/servers/localhost", &apiServerDetail); - registerApiHandler("/servers", &apiServer); + 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/zones//rrset", &apiServerZoneRRset); + d_ws->registerApiHandler("/servers/localhost/zones/", &apiServerZoneDetail); + d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones); + d_ws->registerApiHandler("/servers/localhost", &apiServerDetail); + d_ws->registerApiHandler("/servers", &apiServer); // legacy dispatch - registerApiHandler("/jsonstat", boost::bind(&StatWebServer::jsonstat, this, _1, _2)); + d_ws->registerApiHandler("/jsonstat", boost::bind(&StatWebServer::jsonstat, this, _1, _2)); } d_ws->registerHandler("/style.css", boost::bind(&StatWebServer::cssfunction, this, _1, _2)); d_ws->registerHandler("/", boost::bind(&StatWebServer::indexfunction, this, _1, _2));