#include "dns.hh"
#include "base64.hh"
#include "json.hh"
+#include <yahttp/router.hpp>
struct connectionThreadData {
WebServer* webServer;
void HttpRequest::json(rapidjson::Document& document)
{
+ if(this->body.empty()) {
+ L<<Logger::Debug<<"HTTP: JSON document expected in request body, but body was empty" << endl;
+ throw HttpBadRequestException();
+ }
if(document.Parse<0>(this->body.c_str()).HasParseError()) {
+ L<<Logger::Debug<<"HTTP: parsing of JSON document failed" << endl;
throw HttpBadRequestException();
}
}
return ::B64Decode(strInput, strOutput);
}
-// url is supposed to start with a slash.
-// url can contain variable names, marked as <variable>; such variables
-// are parsed out during routing and are put into the "pathArgs" map.
-// route() makes no assumptions about the contents of variables except
-// that the following URL segment can't be part of the variable.
-//
-// Note: ORDER of registration MATTERS:
-// URLs that do a more specific match should come FIRST.
-//
-// Examples:
-// registerHandler("/foo/<bar>/<baz>", &foobarbaz);
-// registerHandler("/foo/<bar>", &foobar);
-// registerHandler("/foo", &foo);
-// registerHandler("/", &index);
-void WebServer::registerHandler(const string& url, HandlerFunction handler)
+static void handlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Request* req, YaHTTP::Response* resp)
{
- std::size_t pos = 0, lastpos = 0;
-
- HandlerRegistration reg;
- while ((pos = url.find('<', lastpos)) != std::string::npos) {
- std::string part = url.substr(lastpos, pos-lastpos);
- lastpos = pos;
- pos = url.find('>', pos);
-
- if (pos == std::string::npos) {
- throw std::logic_error("invalid url given");
- }
-
- std::string paramName = url.substr(lastpos+1, pos-lastpos-1);
- lastpos = pos+1;
+ // wrapper to convert from YaHTTP::* to our subclasses
+ handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
+}
- reg.urlParts.push_back(part);
- reg.paramNames.push_back(paramName);
- }
- std::string remainder = url.substr(lastpos);
- if (!remainder.empty()) {
- reg.urlParts.push_back(remainder);
- reg.paramNames.push_back("");
- }
- reg.handler = handler;
- d_handlers.push_back(reg);
+void WebServer::registerHandler(const string& url, HandlerFunction handler)
+{
+ YaHTTP::THandlerFunction f = boost::bind(&handlerWrapper, handler, _1, _2);
+ YaHTTP::Router::Any(url, f);
}
-static void apiWrapper(boost::function<void(HttpRequest*,HttpResponse*)> handler, HttpRequest* req, HttpResponse* resp) {
+static void apiWrapper(WebServer::HandlerFunction 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");
+ if(req->getvars.count("callback")) {
+ callback=req->getvars["callback"];
+ req->getvars.erase("callback");
}
- req->parameters.erase("_"); // jQuery cache buster
+ req->getvars.erase("_"); // jQuery cache buster
try {
+ resp->status = 200;
handler(req, resp);
} catch (ApiException &e) {
resp->body = returnJsonError(e.what());
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) {
- bool matches = true;
- size_t lastpos = 0, pos = 0;
- string lastParam;
- pathArgs.clear();
- for (std::list<string>::iterator urlPart = reg->urlParts.begin(), param = reg->paramNames.begin();
- urlPart != reg->urlParts.end() && param != reg->paramNames.end();
- urlPart++, param++) {
- if (!urlPart->empty()) {
- pos = url.find(*urlPart, lastpos);
- if (pos == std::string::npos) {
- matches = false;
- break;
- }
- if (!lastParam.empty()) {
- // store
- pathArgs[lastParam] = url.substr(lastpos, pos-lastpos);
- }
- lastpos = pos + urlPart->size();
- lastParam = *param;
- }
- }
- if (matches) {
- if (!lastParam.empty()) {
- // store trailing parameter
- pathArgs[lastParam] = url.substr(lastpos, pos-lastpos);
- } else if (lastpos != url.size()) {
- matches = false;
- continue;
- }
-
- *handler = ®->handler;
- return true;
- }
- }
- return false;
-}
-
static void *WebServerConnectionThreadStart(void *p) {
connectionThreadData* data = static_cast<connectionThreadData*>(p);
pthread_detach(pthread_self());
HttpResponse WebServer::handleRequest(HttpRequest req)
{
- HttpResponse resp(req);
+ HttpResponse resp;
// set default headers
resp.headers["Content-Type"] = "text/html; charset=utf-8";
try {
if (!req.complete) {
+ L<<Logger::Debug<<"HTTP: Incomplete request" << endl;
throw HttpBadRequestException();
}
}
}
- HandlerFunction *handler;
- if (!route(req.url.path, req.path_parameters, &handler)) {
+ YaHTTP::THandlerFunction handler;
+ if (!YaHTTP::Router::Route(&req, handler)) {
L<<Logger::Debug<<"HTTP: No route found for \"" << req.url.path << "\"" << endl;
throw HttpNotFoundException();
}
try {
- (*handler)(&req, &resp);
+ handler(&req, &resp);
L<<Logger::Debug<<"HTTP: Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl;
}
+ catch(HttpException) {
+ throw;
+ }
catch(PDNSException &e) {
L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl;
throw HttpInternalServerErrorException();
void WebServer::serveConnection(Socket *client)
try {
HttpRequest req;
- YaHTTP::AsyncRequestLoader yarl(&req);
+ YaHTTP::AsyncRequestLoader yarl;
+ yarl.initialize(&req);
int timeout = 5;
client->setNonBlocking();
break;
}
}
+ yarl.finalize();
} catch (YaHTTP::ParseError &e) {
// request stays incomplete
}
public:
HttpRequest() : YaHTTP::Request(), accept_json(false), accept_html(false), complete(false) { };
- map<string,string> path_parameters;
bool accept_json;
bool accept_html;
bool complete;
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) { };
void setBody(rapidjson::Document& document);
HttpResponse handleRequest(HttpRequest request);
typedef boost::function<void(HttpRequest* req, HttpResponse* resp)> HandlerFunction;
- struct HandlerRegistration {
- std::list<string> urlParts;
- std::list<string> paramNames;
- HandlerFunction handler;
- };
-
void registerHandler(const string& url, HandlerFunction handler);
void registerApiHandler(const string& url, HandlerFunction handler);
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);
virtual Server* createServer() {
return new Server(d_listenaddress, d_port);
string d_listenaddress;
int d_port;
- std::list<HandlerRegistration> d_handlers;
string d_password;
Server* d_server;
};
throw HttpMethodNotAllowedException();
string prefix = " " + s_programname + "[";
- resp->body = logGrep(req->parameters["q"], ::arg()["experimental-logfile"], prefix);
+ resp->body = logGrep(req->getvars["q"], ::arg()["experimental-logfile"], prefix);
}
void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
void AuthWebServer::indexfunction(HttpRequest* req, HttpResponse* resp)
{
- if(!req->parameters["resetring"].empty()) {
- if (S.ringExists(req->parameters["resetring"]))
- S.resetRing(req->parameters["resetring"]);
+ if(!req->getvars["resetring"].empty()) {
+ if (S.ringExists(req->getvars["resetring"]))
+ S.resetRing(req->getvars["resetring"]);
resp->status = 301;
resp->headers["Location"] = "/";
return;
}
- if(!req->parameters["resizering"].empty()){
- int size=atoi(req->parameters["size"].c_str());
- if (S.ringExists(req->parameters["resizering"]) && size > 0 && size <= 500000)
- S.resizeRing(req->parameters["resizering"], atoi(req->parameters["size"].c_str()));
+ if(!req->getvars["resizering"].empty()){
+ int size=atoi(req->getvars["size"].c_str());
+ if (S.ringExists(req->getvars["resizering"]) && size > 0 && size <= 500000)
+ S.resizeRing(req->getvars["resizering"], atoi(req->getvars["size"].c_str()));
resp->status = 301;
resp->headers["Location"] = "/";
return;
"<br>"<<endl;
ret<<"Total queries: "<<S.read("udp-queries")<<". Question/answer latency: "<<S.read("latency")/1000.0<<"ms</p><br>"<<endl;
- if(req->parameters["ring"].empty()) {
+ if(req->getvars["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->parameters["ring"],S.getRingTitle(req->parameters["ring"]),100);
+ printtable(ret,req->getvars["ring"],S.getRingTitle(req->getvars["ring"]),100);
ret<<"</div></div>"<<endl;
ret<<"<footer class=\"row\">"<<fullVersionString()<<"<br>© 2013 - 2014 <a href=\"http://www.powerdns.com/\">PowerDNS.COM BV</a>.</footer>"<<endl;
if(req->method != "GET")
throw ApiException("Only GET is implemented");
- string zonename = apiZoneIdToName(req->path_parameters["id"]);
+ string zonename = apiZoneIdToName(req->parameters["id"]);
UeberBackend B;
DomainInfo di;
doc.SetArray();
BOOST_FOREACH(DNSSECKeeper::keyset_t::value_type value, keyset) {
- if (req->path_parameters.count("key_id")) {
- int keyid = lexical_cast<int>(req->path_parameters["key_id"]);
+ if (req->parameters.count("key_id")) {
+ int keyid = lexical_cast<int>(req->parameters["key_id"]);
int curid = lexical_cast<int>(value.second.id);
if (keyid != curid)
continue;
key.AddMember("keytype", (value.second.keyOrZone ? "ksk" : "zsk"), doc.GetAllocator());
Value dnskey(value.first.getDNSKEY().getZoneRepresentation().c_str(), doc.GetAllocator());
key.AddMember("dnskey", dnskey, doc.GetAllocator());
- if (req->path_parameters.count("key_id")) {
- DNSSECPrivateKey dpk=dk.getKeyById(zonename, lexical_cast<int>(req->path_parameters["key_id"]));
+ if (req->parameters.count("key_id")) {
+ DNSSECPrivateKey dpk=dk.getKeyById(zonename, lexical_cast<int>(req->parameters["key_id"]));
Value content(dpk.getKey()->convertToISC().c_str(), doc.GetAllocator());
key.AddMember("content", content, doc.GetAllocator());
}
}
static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp) {
- string zonename = apiZoneIdToName(req->path_parameters["id"]);
+ string zonename = apiZoneIdToName(req->parameters["id"]);
if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
// update domain settings
}
static void apiServerZoneExport(HttpRequest* req, HttpResponse* resp) {
- string zonename = apiZoneIdToName(req->path_parameters["id"]);
+ string zonename = apiZoneIdToName(req->parameters["id"]);
if(req->method != "GET")
throw HttpMethodNotAllowedException();
static void patchZone(HttpRequest* req, HttpResponse* resp) {
UeberBackend B;
DomainInfo di;
- string zonename = apiZoneIdToName(req->path_parameters["id"]);
+ string zonename = apiZoneIdToName(req->parameters["id"]);
if (!B.getDomainInfo(zonename, di))
throw ApiException("Could not find domain '"+zonename+"'");
if(req->method != "GET")
throw HttpMethodNotAllowedException();
- string q = req->parameters["q"];
+ string q = req->getvars["q"];
if (q.empty())
throw ApiException("Query q can't be blank");
{
string command;
- if(req->parameters.count("command")) {
- command = req->parameters["command"];
- req->parameters.erase("command");
+ if(req->getvars.count("command")) {
+ command = req->getvars["command"];
+ req->getvars.erase("command");
}
if(command == "flush-cache") {
extern PacketCache PC;
int number;
- if(req->parameters["domain"].empty())
+ if(req->getvars["domain"].empty())
number = PC.purge();
else
- number = PC.purge(req->parameters["domain"]);
+ number = PC.purge(req->getvars["domain"]);
map<string, string> object;
object["number"]=lexical_cast<string>(number);
}
else if(command=="log-grep") {
// legacy parameter name hack
- req->parameters["q"] = req->parameters["needle"];
+ req->getvars["q"] = req->getvars["needle"];
apiServerSearchLog(req, resp);
return;
}
static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
{
- string zonename = apiZoneIdToName(req->path_parameters["id"]);
+ string zonename = apiZoneIdToName(req->parameters["id"]);
zonename += ".";
SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename);
if(req->method != "GET")
throw HttpMethodNotAllowedException();
- string q = req->parameters["q"];
+ string q = req->getvars["q"];
if (q.empty())
throw ApiException("Query q can't be blank");
{
string command;
- if(req->parameters.count("command")) {
- command = req->parameters["command"];
- req->parameters.erase("command");
+ if(req->getvars.count("command")) {
+ command = req->getvars["command"];
+ req->getvars.erase("command");
}
map<string, string> stats;
return;
}
else if(command == "zone") {
- string arg_zone = req->parameters["zone"];
+ string arg_zone = req->getvars["zone"];
SyncRes::domainmap_t::const_iterator ret = t_sstorage->domainmap->find(arg_zone);
if (ret != t_sstorage->domainmap->end()) {
Document doc;
}
}
else if(command == "flush-cache") {
- string canon=toCanonic("", req->parameters["domain"]);
+ string canon=toCanonic("", req->getvars["domain"]);
int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon));
count+=broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon));
stats["number"]=lexical_cast<string>(count);
}
else if(command == "log-grep") {
// legacy parameter name hack
- req->parameters["q"] = req->parameters["needle"];
+ req->getvars["q"] = req->getvars["needle"];
apiServerSearchLog(req, resp);
return;
}
void AsyncWebServer::serveConnection(Socket *client)
{
HttpRequest req;
- YaHTTP::AsyncRequestLoader yarl(&req);
+ YaHTTP::AsyncRequestLoader yarl;
+ yarl.initialize(&req);
client->setNonBlocking();
string data;
break;
}
}
+ yarl.finalize();
} catch (YaHTTP::ParseError &e) {
// request stays incomplete
}