void Session::init()
{
- d_bufsize=15049; // why?!
-
- d_verbose=false;
-
- rdbuf=new char[d_bufsize];
- rdoffset=0;
- wroffset=0;
-}
-
-void Session::beVerbose()
-{
- d_verbose=true;
+ d_good = true;
}
Session::Session(int s, struct sockaddr_in r)
{
init();
- remote=r;
- clisock=s;
+ d_remote=r;
+ d_socket=s;
}
int Session::close()
{
int rc=0;
- if(clisock>=0)
- rc=Utility::closesocket(clisock);
+ if(d_socket>=0)
+ rc=Utility::closesocket(d_socket);
- clisock=-1;
+ d_socket=-1;
return rc;
}
Session::~Session()
{
-
/* NOT CLOSING AUTOMATICALLY ANYMORE!
- if(clisock>=0)
- ::close(clisock);
+ if(d_socket>=0)
+ ::close(d_socket);
*/
-
- delete[] rdbuf;
}
//! This function makes a deep copy of Session
Session::Session(const Session &s)
{
- d_bufsize=s.d_bufsize;
-
- init(); // needs d_bufsize, but will reset rdoffset & wroffset
-
- rdoffset=s.rdoffset;
- wroffset=s.wroffset;
- clisock=s.clisock;
- remote=s.remote;
+ init();
- memcpy(rdbuf,s.rdbuf,d_bufsize);
-}
+ d_socket=s.d_socket;
+ d_remote=s.d_remote;
+}
void Session::setTimeout(unsigned int seconds)
{
}
-bool Session::putLine(const string &s)
+bool Session::put(const string &s)
{
int length=s.length();
int written=0;
while(written < length)
{
- err=waitForRWData(clisock, false, d_timeout, 0);
+ err=waitForRWData(d_socket, false, d_timeout, 0);
if(err<=0)
throw SessionException("nonblocking write failed: "+string(strerror(errno)));
- err = send(clisock, s.c_str() + written, length-written, 0);
+ err = send(d_socket, s.c_str() + written, length-written, 0);
if(err < 0)
return false;
return true;
}
-char *strnchr(char *p, char c, int len)
-{
- int n;
- for(n=0;n<len;n++)
- if(p[n]==c)
- return p+n;
- return 0;
-}
-
-string Session::get(unsigned int bytes)
+static int timeoutRead(int s, char *buf, size_t len, int timeout)
{
- string ret;
- if(wroffset - rdoffset >= (int)bytes)
- {
- ret = string(rdbuf + rdoffset, bytes);
- bytes -= ret.length();
- rdoffset += ret.length();
- }
-
- if(bytes) {
- scoped_array<char> buffer(new char[bytes]);
- int err = read(clisock, &buffer[0], bytes); // XXX FIXME should be nonblocking
- if(err < 0)
- throw SessionException("Error reading bytes from client: "+string(strerror(errno)));
- if(err != (int)bytes)
- throw SessionException("Error reading bytes from client: partial read");
- ret.append(&buffer[0], err);
- }
- return ret;
-}
-
-int Session::timeoutRead(int s, char *buf, size_t len)
-{
- int err = waitForRWData(s, true, d_timeout, 0);
+ int err = waitForRWData(s, true, timeout, 0);
if(!err)
throw SessionTimeoutException("timeout reading");
return recv(s,buf,len,0);
}
-bool
-Session::haveLine()
+bool Session::good()
{
- return (wroffset!=rdoffset && (strnchr(rdbuf+rdoffset,'\n',wroffset-rdoffset)!=NULL));
+ return d_good;
}
-
-bool
-Session::getLine(string &line)
+size_t Session::read(char* buf, size_t len)
{
int bytes;
- char *p;
-
- int linelength;
-
- // read data into a buffer
- // find first \n, and return that as string, store how far we were
-
- for(;;)
- {
- if(wroffset==rdoffset)
- {
- wroffset=rdoffset=0;
- }
-
- if(wroffset!=rdoffset && (p=strnchr(rdbuf+rdoffset,'\n',wroffset-rdoffset))) // we have a full line in store, return that
- {
- // from rdbuf+rdoffset to p should become the new line
-
- linelength=p-(rdbuf+rdoffset);
-
- *p=0; // terminate
-
- line=rdbuf+rdoffset;
- line+="\n";
-
- rdoffset+=linelength+1;
-
- return true;
- }
- // we need more data before we can return a line
-
- if(wroffset==d_bufsize) // buffer is full, flush to left
- {
- if(!rdoffset) // line too long!
- {
- // FIXME: do stuff
- close();
- return false;
- }
-
- memmove(rdbuf,rdbuf+rdoffset,wroffset-rdoffset);
- wroffset-=rdoffset;
- rdoffset=0;
- }
- bytes=timeoutRead(clisock,rdbuf+wroffset,d_bufsize-wroffset);
-
- if(bytes<0)
- throw SessionException("error on read from socket: "+string(strerror(errno)));
-
- if(bytes==0)
- throw SessionException("Remote closed connection");
-
- wroffset+=bytes;
- }
- // we never get here
-}
-
-int Session::getSocket()
-{
- return clisock;
-}
+ bytes = timeoutRead(d_socket, buf, len, d_timeout);
-string Session::getRemote ()
-{
- ostringstream o;
- uint32_t rint=htonl(remote.sin_addr.s_addr);
- o<< (rint>>24 & 0xff)<<".";
- o<< (rint>>16 & 0xff)<<".";
- o<< (rint>>8 & 0xff)<<".";
- o<< (rint & 0xff);
- o<<":"<<htons(remote.sin_port);
-
- return o.str();
-}
+ if(bytes<0)
+ throw SessionException("error on read from socket: "+string(strerror(errno)));
-uint32_t Session::getRemoteAddr()
-{
+ if(bytes==0)
+ d_good = false;
- return htonl(remote.sin_addr.s_addr);
+ return bytes;
}
-string Session::getRemoteIP()
+int Session::getSocket()
{
- ostringstream o;
- uint32_t rint=htonl(remote.sin_addr.s_addr);
- o<< (rint>>24 & 0xff)<<".";
- o<< (rint>>16 & 0xff)<<".";
- o<< (rint>>8 & 0xff)<<".";
- o<< (rint & 0xff);
-
- return o.str();
+ return d_socket;
}
-
Session *Server::accept()
{
- struct sockaddr_in remote;
- Utility::socklen_t len=sizeof(remote);
+ struct sockaddr_in d_remote;
+ Utility::socklen_t len=sizeof(d_remote);
- int clisock=-1;
+ int d_socket=-1;
- while((clisock=::accept(s,(struct sockaddr *)(&remote),&len))==-1) // repeat until we have a successful connect
+ while((d_socket=::accept(s,(struct sockaddr *)(&d_remote),&len))==-1) // repeat until we have a successful connect
{
// L<<Logger::Error<<"accept() returned: "<<strerror(errno)<<endl;
if(errno==EMFILE) {
}
- return new Session(clisock, remote);
+ return new Session(d_socket, d_remote);
}
-Server::Server(int port, const string &localaddress)
+Server::Server(const string &localaddress, int port)
{
d_local = ComboAddress(localaddress.empty() ? "0.0.0.0" : localaddress, port);
s = socket(d_local.sin4.sin_family ,SOCK_STREAM,0);
class Session
{
public:
- bool getLine(string &); //!< Read a line from the remote
- bool haveLine(); //!< returns true if a line is available
- bool putLine(const string &s); //!< Write a line to the remote
- bool sendFile(int fd); //!< Send a file out
- int timeoutRead(int s,char *buf, size_t len);
- string get(unsigned int bytes);
+ bool put(const string &s);
+ 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
and does a nonblocking connect to support this timeout. It should be noted that nonblocking connects
suffer from bad portability problems, so look here if you see weird problems on new platforms */
Session(const string &remote, int port, int timeout=0);
- Session(uint32_t ip, int port, int timeout=0);
Session(const Session &s);
~Session();
int getSocket(); //!< return the filedescriptor for layering violations
- string getRemote();
- uint32_t getRemoteAddr();
- string getRemoteIP();
- void beVerbose();
int close(); //!< close and disconnect the connection
void setTimeout(unsigned int seconds);
private:
- void doConnect(uint32_t ip, int port);
- bool d_verbose;
- char *rdbuf;
- int d_bufsize;
- int rdoffset;
- int wroffset;
- int clisock;
- struct sockaddr_in remote;
+ int d_socket;
+ struct sockaddr_in d_remote;
void init();
int d_timeout;
-
+ bool d_good;
};
//! The server class can be used to create listening servers
class Server
{
public:
- Server(int p, const string &localaddress=""); //!< port on which to listen
+ Server(const string &localaddress, int port);
Session* accept(); //!< Call accept() in an endless loop to accept new connections
ComboAddress d_local;
+
private:
int s;
};
connectionThreadData* data = static_cast<connectionThreadData*>(p);
pthread_detach(pthread_self());
data->webServer->serveConnection(data->client);
+
+ data->client->close();
+ delete data->client;
+
delete data;
+
return NULL;
}
-void WebServer::serveConnection(Session* client)
-try {
- HttpRequest req;
+HttpResponse WebServer::handleRequest(HttpRequest req)
+{
+ HttpResponse resp(req);
+ // set default headers
+ resp.headers["Content-Type"] = "text/html; charset=utf-8";
try {
- string line;
- client->setTimeout(5);
- client->getLine(line);
- stripLine(line);
- if(line.empty())
- throw HttpBadRequestException();
- // L<<"page: "<<line<<endl;
-
- vector<string> parts;
- stringtok(parts, line);
-
- if(parts.size()>1) {
- req.method = parts[0];
- req.uri = parts[1];
- }
-
- parts.clear();
- stringtok(parts,req.uri,"?");
- req.path = parts[0];
-
- vector<string> variables;
- if(parts.size()>1) {
- stringtok(variables,parts[1],"&");
- }
-
- for(vector<string>::const_iterator i=variables.begin();
- i!=variables.end();++i) {
-
- parts.clear();
- stringtok(parts,*i,"=");
- if(parts.size()>1)
- req.queryArgs[parts[0]]=parts[1];
- else
- req.queryArgs[parts[0]]="";
+ YaHTTP::strstr_map_t::iterator header;
+
+ if ((header = req.headers.find("accept")) != req.headers.end()) {
+ // json wins over html
+ if (header->second.find("application/json") != std::string::npos) {
+ req.accept_json = true;
+ } else if (header->second.find("text/html") != std::string::npos) {
+ req.accept_html = true;
+ }
}
- bool authOK=0;
- int postlen = 0;
- // read & ignore other lines
- do {
- client->getLine(line);
- stripLine(line);
-
- if(line.empty())
- break;
-
- size_t colon = line.find(":");
- if(colon==std::string::npos)
- throw HttpBadRequestException();
-
- string header = toLower(line.substr(0, colon));
- string value = line.substr(line.find_first_not_of(' ', colon+1));
+ if (!d_password.empty()) {
+ // validate password
+ header = req.headers.find("authorization");
+ bool auth_ok = false;
+ if (header != req.headers.end() && toLower(header->second).find("basic ") == 0) {
+ string cookie = header->second.substr(6);
- if(header == "authorization" && toLower(value).find("basic ") == 0) {
- string cookie=value.substr(6);
string plain;
+ B64Decode(cookie, plain);
- B64Decode(cookie,plain);
- vector<string>cparts;
- stringtok(cparts,plain,":");
- // L<<Logger::Error<<"Entered password: '"<<cparts[1].c_str()<<"', should be '"<<d_password.c_str()<<"'"<<endl;
- if(cparts.size()==2 && !strcmp(cparts[1].c_str(),d_password.c_str())) { // this gets rid of terminating zeros
- authOK=1;
- }
+ vector<string> cparts;
+ stringtok(cparts, plain, ":");
+
+ // this gets rid of terminating zeros
+ auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), d_password.c_str())));
}
- else if(header == "content-length" && req.method=="POST") {
- postlen = atoi(value.c_str());
-// cout<<"Got a post: "<<postlen<<" bytes"<<endl;
+ if (!auth_ok) {
+ throw HttpUnauthorizedException();
}
- else if(header == "accept") {
- // json wins over html
- if(value.find("application/json")!=std::string::npos) {
- req.accept_json=true;
- } else if(value.find("text/html")!=std::string::npos) {
- req.accept_html=true;
- }
- }
- else
- ; // cerr<<"Ignoring line: "<<line<<endl;
-
- } while(true);
-
- if(postlen)
- req.body = client->get(postlen);
-
- if(!d_password.empty() && !authOK)
- throw HttpUnauthorizedException();
+ }
HandlerFunction *handler;
- if (route(req.path, req.pathArgs, &handler)) {
- bool custom=false;
- string ret=(*handler)(&req, &custom);
-
- if(!custom) {
- client->putLine("HTTP/1.1 200 OK\n");
- client->putLine("Connection: close\n");
- client->putLine("Content-Type: text/html; charset=utf-8\n\n");
- }
- client->putLine(ret);
- } else {
+ if (!route(req.url.path, req.path_parameters, &handler)) {
throw HttpNotFoundException();
}
+ (*handler)(&req, &resp);
}
catch(HttpException &e) {
- client->putLine(e.statusLine());
- client->putLine("Connection: close\n");
- client->putLine(e.headers());
+ resp = e.response();
+ string what = YaHTTP::Utility::status2text(resp.status);
if(req.accept_html) {
- client->putLine("Content-Type: text/html; charset=utf-8\n\n");
- client->putLine("<!html><title>" + e.what() + "</title><h1>" + e.what() + "</h1>");
+ resp.headers["Content-Type"] = "text/html; charset=utf-8";
+ resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
} else if (req.accept_json) {
- client->putLine("Content-Type: application/json\n\n");
- client->putLine(returnJSONError(e.what()));
+ resp.headers["Content-Type"] = "application/json";
+ resp.body = returnJSONError(what);
} else {
- client->putLine("Content-Type: text/plain; charset=utf-8\n\n");
- client->putLine(e.what());
+ resp.headers["Content-Type"] = "text/plain; charset=utf-8";
+ resp.body = what;
}
}
- client->close();
- delete client;
- client=0;
+ // always set these headers
+ resp.headers["Server"] = "PowerDNS/"VERSION;
+ resp.headers["Connection"] = "close";
+
+ return resp;
+}
+
+void WebServer::serveConnection(Session* client)
+try {
+ HttpRequest req;
+ YaHTTP::AsyncRequestLoader yarl(&req);
+
+ client->setTimeout(5);
+
+ bool complete = false;
+ try {
+ while(client->good()) {
+ int bytes;
+ char buf[1024];
+ bytes = client->read(buf, sizeof(buf));
+ if (bytes) {
+ string data = string(buf, bytes);
+ if (yarl.feed(data)) {
+ complete = true;
+ break;
+ }
+ }
+ }
+ } catch (YaHTTP::ParseError &e) {
+ complete = false;
+ }
+
+ 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");
+ return;
+ }
+
+ HttpResponse resp = WebServer::handleRequest(req);
+ ostringstream ss;
+ resp.write(ss);
+ client->put(ss.str());
}
catch(SessionTimeoutException &e) {
// L<<Logger::Error<<"Timeout in webserver"<<endl;
- return 0;
}
catch(PDNSException &e) {
L<<Logger::Error<<"Exception in webserver: "<<e.reason<<endl;
- return 0;
}
catch(std::exception &e) {
L<<Logger::Error<<"STL Exception in webserver: "<<e.what()<<endl;
- return 0;
}
catch(...) {
L<<Logger::Error<<"Unknown exception in webserver"<<endl;
- return 0;
}
WebServer::WebServer(const string &listenaddress, int port, const string &password)
d_listenaddress=listenaddress;
d_port=port;
d_password=password;
- d_server = 0; // on exception, this class becomes a NOOP later on
try {
- d_server = new Server(d_port, d_listenaddress);
+ d_server = new Server(d_listenaddress, d_port);
}
catch(SessionException &e) {
L<<Logger::Error<<"Fatal error in webserver: "<<e.reason<<endl;
#include <map>
#include <string>
#include <list>
+#include "yahttp/yahttp.hpp"
#include "namespaces.hh"
class Server;
class Session;
+class HttpRequest : public YaHTTP::Request {
+public:
+ HttpRequest() : YaHTTP::Request(), accept_json(false), accept_html(false) { };
+
+ map<string,string> path_parameters;
+ bool accept_json;
+ bool accept_html;
+};
+
+class HttpResponse: public YaHTTP::Response {
+public:
+ HttpResponse() : YaHTTP::Response() { };
+ HttpResponse(const YaHTTP::Request &req) : YaHTTP::Response(req) { };
+ HttpResponse(const YaHTTP::Response &resp) : YaHTTP::Response(resp) { };
+};
+
+
class HttpException
{
public:
- HttpException(int status_code, const std::string& reason_phrase) :
- d_status_code(status_code), d_reason_phrase(reason_phrase)
+ HttpException(int status) : d_response()
{
+ d_response.status = status;
};
- virtual std::string statusLine() const {
- return "HTTP/1.0 " + lexical_cast<string>(d_status_code) + " " + d_reason_phrase + "\n";
- }
-
- virtual std::string headers() const {
- return "";
- }
-
- virtual std::string what() const {
- return d_reason_phrase;
+ HttpResponse response()
+ {
+ return d_response;
}
-private:
- int d_status_code;
- std::string d_reason_phrase;
+protected:
+ HttpResponse d_response;
};
class HttpBadRequestException : public HttpException {
public:
- HttpBadRequestException() : HttpException(400, "Bad Request") { };
+ HttpBadRequestException() : HttpException(400) { };
};
class HttpUnauthorizedException : public HttpException {
public:
- HttpUnauthorizedException() : HttpException(401, "Unauthorized") { };
-
- std::string headers() const {
- return "WWW-Authenticate: Basic realm=\"PowerDNS\"\n";
+ HttpUnauthorizedException() : HttpException(401)
+ {
+ d_response.headers["WWW-Authenticate"] = "Basic realm=\"PowerDNS\"";
}
};
class HttpNotFoundException : public HttpException {
public:
- HttpNotFoundException() : HttpException(404, "Not Found") { };
+ HttpNotFoundException() : HttpException(404) { };
};
class HttpMethodNotAllowedException : public HttpException {
public:
- HttpMethodNotAllowedException() : HttpException(405, "Method Not Allowed") { };
+ HttpMethodNotAllowedException() : HttpException(405) { };
};
-class HttpRequest {
-public:
- HttpRequest() : accept_json(false), accept_html(false) { };
-
- string method;
- string post;
- string uri;
- string path;
- string body;
- map<string,string> pathArgs;
- map<string,string> queryArgs;
- bool accept_json;
- bool accept_html;
-};
class WebServer
{
void go();
void serveConnection(Session* client);
+ HttpResponse handleRequest(HttpRequest request);
- typedef boost::function<string(HttpRequest* req, bool *custom)> HandlerFunction;
+ typedef boost::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
struct HandlerRegistration {
std::list<string> urlParts;
std::list<string> paramNames;
#include "rapidjson/stringbuffer.h"
#include "rapidjson/writer.h"
#include "version.hh"
-#include "session.hh"
using namespace rapidjson;
return (boost::format("%.01f%%") % val).str();
}
-string StatWebServer::indexfunction(HttpRequest* req, bool *custom)
+void StatWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
{
- if(!req->queryArgs["resetring"].empty()){
- *custom=true;
- S.resetRing(req->queryArgs["resetring"]);
- return "HTTP/1.1 301 Moved Permanently\nLocation: /\nConnection: close\n\n";
+ if(!req->parameters["resetring"].empty()){
+ S.resetRing(req->parameters["resetring"]);
+ resp->status = 301;
+ resp->headers["Location"] = "/";
+ return;
}
- if(!req->queryArgs["resizering"].empty()){
- *custom=true;
- S.resizeRing(req->queryArgs["resizering"], atoi(req->queryArgs["size"].c_str()));
- return "HTTP/1.1 301 Moved Permanently\nLocation: /\nConnection: close\n\n";
+ if(!req->parameters["resizering"].empty()){
+ S.resizeRing(req->parameters["resizering"], atoi(req->parameters["size"].c_str()));
+ resp->status = 301;
+ resp->headers["Location"] = "/";
+ return;
}
ostringstream ret;
"<br>"<<endl;
ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
- if(req->queryArgs["ring"].empty()) {
+ if(req->parameters["ring"].empty()) {
vector<string>entries=S.listRings();
for(vector<string>::const_iterator i=entries.begin();i!=entries.end();++i)
printtable(ret,*i,S.getRingTitle(*i));
printargs(ret);
}
else
- printtable(ret,req->queryArgs["ring"],S.getRingTitle(req->queryArgs["ring"]),100);
+ printtable(ret,req->parameters["ring"],S.getRingTitle(req->parameters["ring"]),100);
ret<<"</div></div>"<<endl;
ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>© 2013 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
ret<<"</body></html>"<<endl;
- return ret.str();
+ resp->body = ret.str();
}
static int intFromJson(const Value& val) {
return getZone(zonename);
}
-static string apiServerConfig(HttpRequest* req) {
+static void apiServerConfig(HttpRequest* req, HttpResponse* resp) {
if(req->method != "GET")
throw HttpMethodNotAllowedException();
kv.PushBack(value, doc.GetAllocator());
doc.PushBack(kv, doc.GetAllocator());
}
- return makeStringFromDocument(doc);
+ resp->body = makeStringFromDocument(doc);
}
-static string apiServerSearchLog(HttpRequest* req) {
+static void apiServerSearchLog(HttpRequest* req, HttpResponse* resp) {
if(req->method != "GET")
throw HttpMethodNotAllowedException();
- return makeLogGrepJSON(req->queryArgs["q"], ::arg()["experimental-logfile"], " pdns[");
+ resp->body = makeLogGrepJSON(req->parameters["q"], ::arg()["experimental-logfile"], " pdns[");
}
-static string apiServerZones(HttpRequest* req) {
+static void apiServerZones(HttpRequest* req, HttpResponse* resp) {
if(req->method != "GET")
throw HttpMethodNotAllowedException();
jdomains.PushBack(jdi, doc.GetAllocator());
}
doc.AddMember("domains", jdomains, doc.GetAllocator());
- return makeStringFromDocument(doc);
+ resp->body = makeStringFromDocument(doc);
}
-static string jsonDispatch(HttpRequest* req, const string& command) {
+void StatWebServer::jsonstat(HttpRequest* req, HttpResponse* resp)
+{
+ string command;
+
+ if(req->parameters.count("command")) {
+ command = req->parameters["command"];
+ req->parameters.erase("command");
+ }
+
if(command=="get") {
- if(req->queryArgs.empty()) {
+ if(req->parameters.empty()) {
vector<string> entries = S.getEntries();
BOOST_FOREACH(string& ent, entries) {
- req->queryArgs[ent];
+ req->parameters[ent];
}
- req->queryArgs["version"];
- req->queryArgs["uptime"];
+ req->parameters["version"];
+ req->parameters["uptime"];
}
string variable, value;
Document doc;
doc.SetObject();
- for(varmap_t::const_iterator iter = req->queryArgs.begin(); iter != req->queryArgs.end() ; ++iter) {
+ for(varmap_t::const_iterator iter = req->parameters.begin(); iter != req->parameters.end() ; ++iter) {
variable = iter->first;
if(variable == "version") {
value = VERSION;
jval.SetString(value.c_str(), value.length(), doc.GetAllocator());
doc.AddMember(variable.c_str(), jval, doc.GetAllocator());
}
- return makeStringFromDocument(doc);
+ resp->body = makeStringFromDocument(doc);
+ return;
}
else if(command=="config") {
- return apiServerConfig(req);
+ apiServerConfig(req, resp);
+ return;
}
else if(command == "flush-cache") {
extern PacketCache PC;
int number;
- if(req->queryArgs["domain"].empty())
+ if(req->parameters["domain"].empty())
number = PC.purge();
else
- number = PC.purge(req->queryArgs["domain"]);
+ number = PC.purge(req->parameters["domain"]);
map<string, string> object;
object["number"]=lexical_cast<string>(number);
- //cerr<<"Flushed cache for '"<<queryArgs["domain"]<<"', cleaned "<<number<<" records"<<endl;
- return returnJSONObject(object);
+ //cerr<<"Flushed cache for '"<<parameters["domain"]<<"', cleaned "<<number<<" records"<<endl;
+ resp->body = returnJSONObject(object);
+ return;
}
else if(command == "pdns-control") {
if(req->method!="POST")
throw HttpMethodNotAllowedException();
// cout<<"post: "<<post<<endl;
rapidjson::Document document;
- if(document.Parse<0>(req->body.c_str()).HasParseError())
- return returnJSONError("Unable to parse JSON");
+ if(document.Parse<0>(req->body.c_str()).HasParseError()) {
+ resp->status = 400;
+ resp->body = returnJSONError("Unable to parse JSON");
+ return;
+ }
// cout<<"Parameters: '"<<document["parameters"].GetString()<<"'\n";
vector<string> parameters;
stringtok(parameters, document["parameters"].GetString(), " \t");
if(ptr) {
m["result"] = (*ptr)(parameters, 0);
} else {
+ resp->status = 404;
m["error"]="No such function "+toUpper(parameters[0]);
}
- return returnJSONObject(m);
+ resp->body = returnJSONObject(m);
+ return;
}
else if(command == "zone-rest") { // http://jsonstat?command=zone-rest&rest=/powerdns.nl/www.powerdns.nl/a
vector<string> parts;
- stringtok(parts, req->queryArgs["rest"], "/");
- if(parts.size() != 3)
- return returnJSONError("Could not parse rest parameter");
+ stringtok(parts, req->parameters["rest"], "/");
+ if(parts.size() != 3) {
+ resp->status = 400;
+ resp->body = returnJSONError("Could not parse rest parameter");
+ return;
+ }
UeberBackend B;
SOAData sd;
sd.db = (DNSBackend*)-1;
- if(!B.getSOA(parts[0], sd) || !sd.db)
- return returnJSONError("Could not find domain '"+parts[0]+"'");
+ if(!B.getSOA(parts[0], sd) || !sd.db) {
+ resp->status = 404;
+ resp->body = returnJSONError("Could not find domain '"+parts[0]+"'");
+ return;
+ }
QType qtype;
qtype=parts[2];
ret+=returnJSONObject(object);
}
ret+="]}";
- return ret;
+ resp->body = ret;
+ return;
}
else if(req->method=="DELETE") {
sd.db->replaceRRSet(sd.domain_id, qname, qtype, vector<DNSResourceRecord>());
}
else if(req->method=="POST") {
rapidjson::Document document;
- if(document.Parse<0>(req->body.c_str()).HasParseError())
- return returnJSONError("Unable to parse JSON");
+ if(document.Parse<0>(req->body.c_str()).HasParseError()) {
+ resp->status = 400;
+ resp->body = returnJSONError("Unable to parse JSON");
+ return;
+ }
DNSResourceRecord rr;
vector<DNSResourceRecord> rrset;
}
catch(std::exception& e)
{
- return returnJSONError("Following record had a problem: "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what());
+ resp->body = returnJSONError("Following record had a problem: "+rr.qname+" IN " +rr.qtype.getName()+ " " + rr.content+": "+e.what());
+ return;
}
}
// but now what
sd.db->startTransaction(qname);
sd.db->replaceRRSet(sd.domain_id, qname, qtype, rrset);
sd.db->commitTransaction();
- return req->body;
+ resp->body = req->body;
+ return;
}
}
else if(command == "zone") {
- string zonename = req->queryArgs["zone"];
- if (zonename.empty())
- return returnJSONError("Must give zone parameter");
+ string zonename = req->parameters["zone"];
+ if (zonename.empty()) {
+ resp->status = 400;
+ resp->body = returnJSONError("Must give zone parameter");
+ }
if(req->method == "GET") {
// get current zone
- return getZone(zonename);
+ resp->body = getZone(zonename);
+ return;
} else if (req->method == "POST") {
// create
- return createOrUpdateZone(zonename, true, req->queryArgs);
+ resp->body = createOrUpdateZone(zonename, true, req->parameters);
+ return;
} else if (req->method == "PUT") {
// update or create
- return createOrUpdateZone(zonename, false, req->queryArgs);
+ resp->body = createOrUpdateZone(zonename, false, req->parameters);
+ return;
} else if (req->method == "DELETE") {
// delete
UeberBackend B;
DomainInfo di;
- if(!B.getDomainInfo(zonename, di))
- return returnJSONError("Deleting domain '"+zonename+"' failed: domain does not exist");
- if(!di.backend->deleteDomain(zonename))
- return returnJSONError("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
+ if(!B.getDomainInfo(zonename, di)) {
+ resp->body = returnJSONError("Deleting domain '"+zonename+"' failed: domain does not exist");
+ return;
+ }
+ if(!di.backend->deleteDomain(zonename)) {
+ resp->body = returnJSONError("Deleting domain '"+zonename+"' failed: backend delete failed/unsupported");
+ return;
+ }
map<string, string> success; // empty success object
- return returnJSONObject(success);
+ resp->body = returnJSONObject(success);
+ return;
} else {
throw HttpMethodNotAllowedException();
}
}
else if(command=="log-grep") {
- return makeLogGrepJSON(req->queryArgs["needle"], ::arg()["experimental-logfile"], " pdns[");
+ resp->body = makeLogGrepJSON(req->parameters["needle"], ::arg()["experimental-logfile"], " pdns[");
+ return;
}
else if(command=="domains") {
- return apiServerZones(req);
+ apiServerZones(req, resp);
+ return;
}
- return returnJSONError("No or unknown command given");
+ resp->body = returnJSONError("No or unknown command given");
+ resp->status = 404;
+ return;
}
-static string apiWrapper(boost::function<string(HttpRequest*)> handler, HttpRequest* req, bool *custom) {
- *custom=1; // indicates we build the response
- string ret="HTTP/1.1 200 OK\r\n"
- "Server: PowerDNS/"VERSION"\r\n"
- "Connection: close\r\n"
- "Access-Control-Allow-Origin: *\r\n"
- "Content-Type: application/json\r\n"
- "\r\n" ;
+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->queryArgs.count("callback")) {
- callback=req->queryArgs["callback"];
- req->queryArgs.erase("callback");
+ if(req->parameters.count("callback")) {
+ callback=req->parameters["callback"];
+ req->parameters.erase("callback");
}
- req->queryArgs.erase("_"); // jQuery cache buster
+ req->parameters.erase("_"); // jQuery cache buster
- if(!callback.empty())
- ret += callback+"(";
-
- ret += handler(req);
+ handler(req, resp);
if(!callback.empty()) {
- ret += ");";
+ resp->body = callback + "(" + resp->body + ");";
}
- return ret;
}
-void StatWebServer::registerApiHandler(const string& url, boost::function<string(HttpRequest*)> handler) {
+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);
}
-string StatWebServer::jsonstat(HttpRequest* req)
+void StatWebServer::cssfunction(HttpRequest* req, HttpResponse* resp)
{
- string command;
+ resp->headers["Cache-Control"] = "max-age=86400";
+ resp->headers["Content-Type"] = "text/css";
- if(req->queryArgs.count("command")) {
- command=req->queryArgs["command"];
- req->queryArgs.erase("command");
- }
-
- return jsonDispatch(req, command);
-}
-
-string StatWebServer::cssfunction(HttpRequest* req, bool *custom)
-{
- *custom=1; // indicates we build the response
ostringstream ret;
- ret<<"HTTP/1.1 200 OK\r\n"
- "Server: PowerDNS/"VERSION"\r\n"
- "Connection: close\r\n"
- "Cache-Control: max-age=86400\r\n"
- "Content-Type: text/css\r\n"
- "\r\n";
-
ret<<"* { box-sizing: border-box; margin: 0; padding: 0; }"<<endl;
ret<<"body { color: black; background: white; margin-top: 1em; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 10pt; position: relative; }"<<endl;
ret<<"a { color: #0959c2; }"<<endl;
ret<<".resetring i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA/klEQVQY01XPP04UUBgE8N/33vd2XZUWEuzYuMZEG4KFCQn2NhA4AIewAOMBPIG2xhNYeAcKGqkNCdmYlVBZGBIT4FHsbuE0U8xk/kAbqm9TOfI/nicfhmwgDNhvylUT58kxCp4l31L8SfH9IetJ2ev6PwyIwyZWsdb11/gbTK55Co+r8rmJaRPTFJcpZil+pTit7C5awMpA+Zpi1sRFE9MqflYOloYCjY2uP8EdYiGU4CVGUBubxKfOOLjrtOBmzvEilbVb/aQWvhRl0unBZVXe4XdnK+bprwqnhoyTsyZ+JG8Wk0apfExxlcp7PFruXH8gdxamWB4cyW2sIO4BG3czIp78jUIAAAAASUVORK5CYII=); width: 10px; height: 10px; margin-right: 2px; display: inline-block; background-repeat: no-repeat; }"<<endl;
ret<<".resetring:hover i { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAA2ElEQVQY013PMUoDcRDF4c+kEzxCsNNCrBQvIGhnlcYm11EkBxAraw8gglgIoiJpAoKIYlBcgrgopsma3c3fwt1k9cHA480M8xvQp/nMjorOWY5ov7IAYlpjQk7aYxcuWBpwFQgJnUcaYk7GhEDIGL5w+MVpKLIRyR2b4JOjvGhUKzHTv2W7iuSN479Dvu9plf1awbQ6y3x1sU5tjpVJcMbakF6Ycoas8Dl5xEHJ160wRdfqzXfa6XQ4PLDlicWUjxHxZfndL/N+RhiwNzl/Q6PDhn/qsl76H7prcApk2B1aAAAAAElFTkSuQmCC);}"<<endl;
ret<<".resizering {float: right;}"<<endl;
- return ret.str();
+ resp->body = ret.str();
}
void StatWebServer::launch()
registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
registerApiHandler("/servers/localhost/zones", &apiServerZones);
// legacy dispatch
- registerApiHandler("/jsonstat", boost::bind(&StatWebServer::jsonstat, this, _1));
+ registerApiHandler("/jsonstat", boost::bind(&StatWebServer::jsonstat, this, _1, _2));
}
d_ws->go();
}
class WebServer;
class HttpRequest;
+class HttpResponse;
class StatWebServer
{
private:
static void *threadHelper(void *);
static void *statThreadHelper(void *p);
- string indexfunction(HttpRequest* req, bool *custom);
- string cssfunction(HttpRequest* req, bool *custom);
- string jsonstat(HttpRequest* req);
- void registerApiHandler(const string& url, boost::function<string(HttpRequest*)> handler);
+ void indexfunction(HttpRequest* req, HttpResponse* resp);
+ void cssfunction(HttpRequest* req, HttpResponse* resp);
+ void jsonstat(HttpRequest* req, HttpResponse* resp);
+ void registerApiHandler(const string& url, boost::function<void(HttpRequest*, HttpResponse*)> handler);
void printvars(ostringstream &ret);
void printargs(ostringstream &ret);
void launch();