]> granicus.if.org Git - pdns/commitdiff
use class Webserver to implement recursor http server
authorChristian Hofstaedtler <christian@hofstaedtler.name>
Wed, 29 Jan 2014 21:59:34 +0000 (22:59 +0100)
committerChristian Hofstaedtler <christian@hofstaedtler.name>
Mon, 3 Feb 2014 14:06:08 +0000 (15:06 +0100)
12 files changed:
pdns/Makefile-recursor
pdns/Makefile.am
pdns/dist-recursor
pdns/json_ws.cc
pdns/json_ws.hh
pdns/misc.hh
pdns/pdns_recursor.cc
pdns/session.cc
pdns/session.hh
pdns/webserver.cc
pdns/webserver.hh
pdns/ws.cc

index d6cc1d9749a48bc09738493cfed1aaa29b08fd44..9ceadfbe855e9a75018f0136ee1f8e583a443ec9 100644 (file)
@@ -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 > $@
index ac6e56de97a34f8ccfc437ff23b21a45e2f477ad..b2d5b206c3a775bd11b60537f376207c097c7aef 100644 (file)
@@ -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
index c0e4c973c5f17164ee47ce589c15f0eb5acf99f4..f7a29c9ae23fde2dd74f1b766c3a40476b58c9c7 100755 (executable)
@@ -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
index d51fbb6b6951e8a68a9afb8081729785219e1a94..6204e532952aae492dae4cd8181c677c68d93116 100644 (file)
 #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<string> 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<string, string> varmap;
-    if(parts.size()>1) {
-      vector<string> 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<string,string> &rovarmap, string &headers)
+void JWebserver::jsonstat(HttpRequest* req, HttpResponse *resp)
 {
-  map<string,string> 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<string, string> 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<uint64_t>(boost::bind(pleaseWipeCache, canon));
     count+=broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon));
     stats["number"]=lexical_cast<string>(count);
-    content += returnJSONObject(stats);  
+    resp->body = returnJSONObject(stats);
+    return;
   }
   else if(command == "config") {
     vector<string> 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");
+  }
 }
index 061111ef4a6b5872e86d179dad3fcd35e42525e2..ed40ee1c32948b1a3a1da892e714ddd0b018d763 100644 (file)
 #include <boost/utility.hpp> 
 #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<string,string> &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<string, string>& items);
index 67480765c56a17e3f5ef6dd2ebe47798d60f3212..3b149c376da74d61c451ccd98175de4e07af6bdd 100644 (file)
@@ -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<unsigned>(sizeof tmp));
-}
-
 string stripDot(const string& dom);
 void seedRandom(const string& source);
 string makeRelative(const std::string& fqdn, const std::string& zone);
index 373632fcc72d0c380c779758f3728bd442c398e8..256f5492da228361a4bbe08ca840a4f7f7b85d64 100644 (file)
@@ -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;
index 1fe586b6b22e1bf9c378095af1d6c80fc3b98d4b..c3ee0a2aa4b62600b0099742287e975373e16290 100644 (file)
 #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<<Logger::Error<<"accept() returned: "<<strerror(errno)<<endl;
       if(errno==EMFILE) {
@@ -153,7 +150,24 @@ Session *Server::accept()
 
     }
 
-  return new Session(d_socket, d_remote);
+  Session session(socket, remote);
+  return session;
+}
+
+void Server::asyncNewConnection()
+{
+  try {
+    d_asyncNewConnectionCallback(accept());
+  } catch (SessionException &e) {
+    // we're running in a shared process/thread, so can't just terminate/abort.
+    return;
+  }
+}
+
+void Server::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback)
+{
+  d_asyncNewConnectionCallback = callback;
+  fdm->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<unsigned>(sizeof tmp))<0)
     throw SessionException(string("Setsockopt failed: ")+strerror(errno));
 
   if(bind(s, (sockaddr*)&d_local, d_local.getSocklen())<0)
index 81a0ec643761d19e53466a52cf1141c43733a388..be854578e4ac75c4be2c8ed551e088b35576d0e7 100644 (file)
@@ -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 */
index f4bacade629f1645fc6c8681b0b6995f8cbfbd63..1f55c7999ad94b575404ed24d73b8d41c7af1a30 100644 (file)
 #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<void(HttpRequest*,HttpResponse*)> 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<std::string, std::string>& pathArgs, HandlerFunction** handler)
 {
   for (std::list<HandlerRegistration>::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<<Logger::Error<<"Timeout in webserver"<<endl;
@@ -271,16 +305,15 @@ void WebServer::go()
   if(!d_server)
     return;
   try {
-    Session *client;
     pthread_t tid;
     
     L<<Logger::Error<<"Launched webserver on " << d_server->d_local.toStringWithPort() <<endl;
 
-    while((client=d_server->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<<Logger::Error<<"Exception in webserver: "<<e.reason<<endl;
+  }
+  catch(std::exception &e) {
+    L<<Logger::Error<<"STL Exception in webserver: "<<e.what()<<endl;
+  }
+  catch(...) {
+    L<<Logger::Error<<"Unknown exception in webserver"<<endl;
+  }
+
+  close(fd);
+}
index ef40f6cb2c2b8ce2d6be413048e616e74ecd9d32..b6e472f158349940083e325fb92ddcd5223c45b4 100644 (file)
@@ -24,7 +24,8 @@
 #include <map>
 #include <string>
 #include <list>
-#include "yahttp/yahttp.hpp"
+#include <boost/utility.hpp>
+#include <yahttp/yahttp.hpp>
 
 #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<void(HttpRequest* req, HttpResponse* resp)> 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<std::string, std::string>& 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 */
index 6c0e2ab0ba3259ea7055277f5cd8893d2c90d4a8..2d2d1958f6318124d27b4630452826612e610709 100644 (file)
@@ -44,13 +44,6 @@ extern StatBag S;
 
 typedef map<string,string> 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<void(HttpRequest*,HttpResponse*)> 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<void(HttpRequest*,HttpResponse*)> 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/<id>/rrset", &apiServerZoneRRset);
-      registerApiHandler("/servers/localhost/zones/<id>", &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/<id>/rrset", &apiServerZoneRRset);
+      d_ws->registerApiHandler("/servers/localhost/zones/<id>", &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));