]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/ws-recursor.cc
Include config.h only in .cc files
[thirdparty/pdns.git] / pdns / ws-recursor.cc
CommitLineData
a7a902fb
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
825fa717 3 Copyright (C) 2003 - 2014 PowerDNS.COM BV
a7a902fb
BH
4
5 This program is free software; you can redistribute it and/or modify
6ec5e728 6 it under the terms of the GNU General Public License version 2
a7a902fb
BH
7 as published by the Free Software Foundation
8
f782fe38
MH
9 Additionally, the license of this program contains a special
10 exception which allows to distribute the program in binary form when
11 it is linked against OpenSSL.
12
a7a902fb
BH
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21*/
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
2470b36e 25#include "ws-recursor.hh"
a7a902fb
BH
26#include "json.hh"
27#include <boost/foreach.hpp>
28#include <string>
29#include "namespaces.hh"
30#include <iostream>
31#include "iputils.hh"
32#include "rec_channel.hh"
33#include "arguments.hh"
34#include "misc.hh"
8465487d 35#include "syncres.hh"
c89f8cd0 36#include "dnsparser.hh"
fb4b38f1
CH
37#include "rapidjson/document.h"
38#include "rapidjson/stringbuffer.h"
39#include "rapidjson/writer.h"
3ae143b0 40#include "webserver.hh"
6ec5e728 41#include "ws-api.hh"
41942bb3 42#include "logger.hh"
fb4b38f1 43
825fa717
CH
44extern __thread FDMultiplexer* t_fdm;
45
fb4b38f1 46using namespace rapidjson;
a7a902fb 47
6ec5e728
CH
48void productServerStatisticsFetch(map<string,string>& out)
49{
50 map<string,string> stats = getAllStatsMap();
51 out.swap(stats);
52}
53
c348c0c8
CH
54static void apiWriteConfigFile(const string& filebasename, const string& content)
55{
56 if (::arg()["experimental-api-config-dir"].empty()) {
57 throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
58 }
59
60 string filename = ::arg()["experimental-api-config-dir"] + "/" + filebasename + ".conf";
61 ofstream ofconf(filename.c_str());
62 if (!ofconf) {
63 throw ApiException("Could not open config fragment file '"+filename+"' for writing: "+stringerror());
64 }
65 ofconf << "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
66 ofconf << content << endl;
67 ofconf.close();
68}
69
41942bb3
CH
70static void apiServerConfigAllowFrom(HttpRequest* req, HttpResponse* resp)
71{
b4ae7322 72 if (req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
41942bb3
CH
73 Document document;
74 req->json(document);
bd0320fe 75 const Value &jlist = document["value"];
41942bb3 76
bd0320fe
CH
77 if (!document.IsObject()) {
78 throw ApiException("Supplied JSON must be an object");
79 }
80
81 if (!jlist.IsArray()) {
82 throw ApiException("'value' must be an array");
41942bb3
CH
83 }
84
faa0f891
CH
85 for (SizeType i = 0; i < jlist.Size(); ++i) {
86 try {
87 Netmask(jlist[i].GetString());
88 } catch (NetmaskException &e) {
89 throw ApiException(e.reason);
90 }
91 }
92
41942bb3
CH
93 ostringstream ss;
94
95 // Clear allow-from-file if set, so our changes take effect
96 ss << "allow-from-file=" << endl;
97
98 // Clear allow-from, and provide a "parent" value
99 ss << "allow-from=" << endl;
bd0320fe
CH
100 for (SizeType i = 0; i < jlist.Size(); ++i) {
101 ss << "allow-from+=" << jlist[i].GetString() << endl;
41942bb3
CH
102 }
103
104 apiWriteConfigFile("allow-from", ss.str());
105
106 parseACLs();
107
108 // fall through to GET
109 } else if (req->method != "GET") {
110 throw HttpMethodNotAllowedException();
111 }
112
113 // Return currently configured ACLs
114 Document document;
bd0320fe
CH
115 document.SetObject();
116
117 Value jlist;
118 jlist.SetArray();
41942bb3
CH
119
120 vector<string> entries;
121 t_allowFrom->toStringVector(&entries);
122
123 BOOST_FOREACH(const string& entry, entries) {
124 Value jentry(entry.c_str(), document.GetAllocator()); // copy
bd0320fe 125 jlist.PushBack(jentry, document.GetAllocator());
41942bb3
CH
126 }
127
bd0320fe
CH
128 document.AddMember("name", "allow-from", document.GetAllocator());
129 document.AddMember("value", jlist, document.GetAllocator());
130
41942bb3
CH
131 resp->setBody(document);
132}
133
02945d9a
CH
134static void fillZone(const string& zonename, HttpResponse* resp)
135{
136 SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename);
137 if (iter == t_sstorage->domainmap->end())
138 throw ApiException("Could not find domain '"+zonename+"'");
139
140 Document doc;
141 doc.SetObject();
142
143 const SyncRes::AuthDomain& zone = iter->second;
144
145 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
146 string zoneId = apiZoneNameToId(iter->first);
7bdd945a
CH
147 Value jzoneid(zoneId.c_str(), doc.GetAllocator()); // copy
148 doc.AddMember("id", jzoneid, doc.GetAllocator());
02945d9a
CH
149 string url = "/servers/localhost/zones/" + zoneId;
150 Value jurl(url.c_str(), doc.GetAllocator()); // copy
151 doc.AddMember("url", jurl, doc.GetAllocator());
152 doc.AddMember("name", iter->first.c_str(), doc.GetAllocator());
153 doc.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator());
154 Value servers;
155 servers.SetArray();
156 BOOST_FOREACH(const ComboAddress& server, zone.d_servers) {
157 Value value(server.toStringWithPort().c_str(), doc.GetAllocator());
158 servers.PushBack(value, doc.GetAllocator());
159 }
160 doc.AddMember("servers", servers, doc.GetAllocator());
161 bool rd = zone.d_servers.empty() ? false : zone.d_rdForward;
162 doc.AddMember("recursion_desired", rd, doc.GetAllocator());
163
164 Value records;
165 records.SetArray();
166 BOOST_FOREACH(const SyncRes::AuthDomain::records_t::value_type& rr, zone.d_records) {
167 Value object;
168 object.SetObject();
169 Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
170 object.AddMember("name", jname, doc.GetAllocator());
171 Value jtype(rr.qtype.getName().c_str(), doc.GetAllocator()); // copy
172 object.AddMember("type", jtype, doc.GetAllocator());
173 object.AddMember("ttl", rr.ttl, doc.GetAllocator());
02945d9a
CH
174 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
175 object.AddMember("content", jcontent, doc.GetAllocator());
176 records.PushBack(object, doc.GetAllocator());
177 }
178 doc.AddMember("records", records, doc.GetAllocator());
179
180 resp->setBody(doc);
181}
182
183static void doCreateZone(const Value& document)
184{
185 if (::arg()["experimental-api-config-dir"].empty()) {
186 throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
187 }
188
189 string zonename = stringFromJson(document, "name");
d9081fac 190 // TODO: better validation of zonename - apiZoneNameToId takes care of escaping / however
02945d9a
CH
191 if(zonename.empty())
192 throw ApiException("Zone name empty");
193
194 if (zonename[zonename.size()-1] != '.') {
195 zonename += ".";
196 }
197
d9081fac 198 string singleIPTarget = stringFromJson(document, "single_target_ip", "");
02945d9a
CH
199 string kind = toUpper(stringFromJson(document, "kind"));
200 bool rd = boolFromJson(document, "recursion_desired");
201 string confbasename = "zone-" + apiZoneNameToId(zonename);
202
203 if (kind == "NATIVE") {
204 if (rd)
205 throw ApiException("kind=Native and recursion_desired are mutually exclusive");
10e69330 206 if(!singleIPTarget.empty()) {
207 try {
208 ComboAddress rem(singleIPTarget);
209 if(rem.sin4.sin_family != AF_INET)
210 throw ApiException("");
211 singleIPTarget = rem.toString();
212 }
213 catch(...) {
214 throw ApiException("Single IP target '"+singleIPTarget+"' is invalid");
215 }
216 }
02945d9a
CH
217 string zonefilename = ::arg()["experimental-api-config-dir"] + "/" + confbasename + ".zone";
218 ofstream ofzone(zonefilename.c_str());
219 if (!ofzone) {
220 throw ApiException("Could not open '"+zonefilename+"' for writing: "+stringerror());
221 }
222 ofzone << "; Generated by pdns-recursor REST API, DO NOT EDIT" << endl;
223 ofzone << zonename << "\tIN\tSOA\tlocal.zone.\thostmaster."<<zonename<<" 1 1 1 1 1" << endl;
7f643957 224 if(!singleIPTarget.empty()) {
10e69330 225 ofzone <<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl;
226 ofzone <<"*."<<zonename << "\t3600\tIN\tA\t"<<singleIPTarget<<endl;
7f643957 227 }
02945d9a
CH
228 ofzone.close();
229
230 apiWriteConfigFile(confbasename, "auth-zones+=" + zonename + "=" + zonefilename);
231 } else if (kind == "FORWARDED") {
232 const Value &servers = document["servers"];
233 if (kind == "FORWARDED" && (!servers.IsArray() || servers.Size() == 0))
234 throw ApiException("Need at least one upstream server when forwarding");
235
236 string serverlist;
237 if (servers.IsArray()) {
238 for (SizeType i = 0; i < servers.Size(); ++i) {
239 if (!serverlist.empty()) {
240 serverlist += ";";
241 }
242 serverlist += servers[i].GetString();
243 }
244 }
245
246 if (rd) {
247 apiWriteConfigFile(confbasename, "forward-zones-recurse+=" + zonename + "=" + serverlist);
248 } else {
249 apiWriteConfigFile(confbasename, "forward-zones+=" + zonename + "=" + serverlist);
250 }
251 } else {
252 throw ApiException("invalid kind");
253 }
254}
255
256static bool doDeleteZone(const string& zonename)
257{
258 if (::arg()["experimental-api-config-dir"].empty()) {
259 throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
260 }
261
262 string filename;
263
264 // this one must exist
265 filename = ::arg()["experimental-api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".conf";
266 if (unlink(filename.c_str()) != 0) {
267 return false;
268 }
269
270 // .zone file is optional
271 filename = ::arg()["experimental-api-config-dir"] + "/zone-" + apiZoneNameToId(zonename) + ".zone";
272 unlink(filename.c_str());
273
274 return true;
275}
276
277static void apiServerZones(HttpRequest* req, HttpResponse* resp)
278{
b4ae7322 279 if (req->method == "POST" && !::arg().mustDo("experimental-api-readonly")) {
02945d9a
CH
280 if (::arg()["experimental-api-config-dir"].empty()) {
281 throw ApiException("Config Option \"experimental-api-config-dir\" must be set");
282 }
283
284 Document document;
285 req->json(document);
286
287 string zonename = stringFromJson(document, "name");
288 if (zonename[zonename.size()-1] != '.') {
289 zonename += ".";
290 }
291
292 SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename);
293 if (iter != t_sstorage->domainmap->end())
294 throw ApiException("Zone already exists");
295
296 doCreateZone(document);
297 reloadAuthAndForwards();
298 fillZone(zonename, resp);
64a36f0d 299 resp->status = 201;
02945d9a
CH
300 return;
301 }
302
303 if(req->method != "GET")
304 throw HttpMethodNotAllowedException();
305
306 Document doc;
307 doc.SetArray();
308
309 BOOST_FOREACH(const SyncRes::domainmap_t::value_type& val, *t_sstorage->domainmap) {
310 const SyncRes::AuthDomain& zone = val.second;
311 Value jdi;
312 jdi.SetObject();
313 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
314 string zoneId = apiZoneNameToId(val.first);
7bdd945a
CH
315 Value jzoneid(zoneId.c_str(), doc.GetAllocator()); // copy
316 jdi.AddMember("id", jzoneid, doc.GetAllocator());
02945d9a
CH
317 string url = "/servers/localhost/zones/" + zoneId;
318 Value jurl(url.c_str(), doc.GetAllocator()); // copy
319 jdi.AddMember("url", jurl, doc.GetAllocator());
320 jdi.AddMember("name", val.first.c_str(), doc.GetAllocator());
321 jdi.AddMember("kind", zone.d_servers.empty() ? "Native" : "Forwarded", doc.GetAllocator());
322 Value servers;
323 servers.SetArray();
324 BOOST_FOREACH(const ComboAddress& server, zone.d_servers) {
325 Value value(server.toStringWithPort().c_str(), doc.GetAllocator());
326 servers.PushBack(value, doc.GetAllocator());
327 }
328 jdi.AddMember("servers", servers, doc.GetAllocator());
329 bool rd = zone.d_servers.empty() ? false : zone.d_rdForward;
330 jdi.AddMember("recursion_desired", rd, doc.GetAllocator());
331 doc.PushBack(jdi, doc.GetAllocator());
332 }
333 resp->setBody(doc);
334}
335
336static void apiServerZoneDetail(HttpRequest* req, HttpResponse* resp)
337{
583ea80d 338 string zonename = apiZoneIdToName(req->parameters["id"]);
02945d9a
CH
339 zonename += ".";
340
341 SyncRes::domainmap_t::const_iterator iter = t_sstorage->domainmap->find(zonename);
342 if (iter == t_sstorage->domainmap->end())
343 throw ApiException("Could not find domain '"+zonename+"'");
344
b4ae7322 345 if(req->method == "PUT" && !::arg().mustDo("experimental-api-readonly")) {
02945d9a
CH
346 Document document;
347 req->json(document);
348
349 doDeleteZone(zonename);
350 doCreateZone(document);
351 reloadAuthAndForwards();
e2367534 352 fillZone(stringFromJson(document, "name"), resp);
02945d9a 353 }
b4ae7322 354 else if(req->method == "DELETE" && !::arg().mustDo("experimental-api-readonly")) {
02945d9a
CH
355 if (!doDeleteZone(zonename)) {
356 throw ApiException("Deleting domain failed");
357 }
358
359 reloadAuthAndForwards();
360 // empty body on success
361 resp->body = "";
37663c3b 362 resp->status = 204; // No Content: declare that the zone is gone now
02945d9a
CH
363 } else if(req->method == "GET") {
364 fillZone(zonename, resp);
365 } else {
366 throw HttpMethodNotAllowedException();
367 }
368}
369
37bc3d01
CH
370static void apiServerSearchData(HttpRequest* req, HttpResponse* resp) {
371 if(req->method != "GET")
372 throw HttpMethodNotAllowedException();
373
583ea80d 374 string q = req->getvars["q"];
37bc3d01
CH
375 if (q.empty())
376 throw ApiException("Query q can't be blank");
377
378 Document doc;
379 doc.SetArray();
380
381 BOOST_FOREACH(const SyncRes::domainmap_t::value_type& val, *t_sstorage->domainmap) {
382 string zoneId = apiZoneNameToId(val.first);
383 if (pdns_ci_find(val.first, q) != string::npos) {
384 Value object;
385 object.SetObject();
386 object.AddMember("type", "zone", doc.GetAllocator());
387 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
388 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
389 Value jzoneName(val.first.c_str(), doc.GetAllocator()); // copy
390 object.AddMember("name", jzoneName, doc.GetAllocator());
391 doc.PushBack(object, doc.GetAllocator());
392 }
393
394 // if zone name is an exact match, don't bother with returning all records/comments in it
395 if (val.first == q) {
396 continue;
397 }
398
399 const SyncRes::AuthDomain& zone = val.second;
400
401 BOOST_FOREACH(const SyncRes::AuthDomain::records_t::value_type& rr, zone.d_records) {
402 if (pdns_ci_find(rr.qname, q) == string::npos && pdns_ci_find(rr.content, q) == string::npos)
403 continue;
404
405 Value object;
406 object.SetObject();
407 object.AddMember("type", "record", doc.GetAllocator());
408 Value jzoneId(zoneId.c_str(), doc.GetAllocator()); // copy
409 object.AddMember("zone_id", jzoneId, doc.GetAllocator());
410 Value jzoneName(val.first.c_str(), doc.GetAllocator()); // copy
411 object.AddMember("zone_name", jzoneName, doc.GetAllocator());
412 Value jname(rr.qname.c_str(), doc.GetAllocator()); // copy
413 object.AddMember("name", jname, doc.GetAllocator());
414 Value jcontent(rr.content.c_str(), doc.GetAllocator()); // copy
415 object.AddMember("content", jcontent, doc.GetAllocator());
416
417 doc.PushBack(object, doc.GetAllocator());
418 }
419 }
420 resp->setBody(doc);
421}
422
a426cb89
CH
423static void apiServerFlushCache(HttpRequest* req, HttpResponse* resp) {
424 if(req->method != "PUT")
425 throw HttpMethodNotAllowedException();
426
427 string canon = toCanonic("", req->getvars["domain"]);
428 int count = broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeCache, canon));
429 count += broadcastAccFunction<uint64_t>(boost::bind(pleaseWipeAndCountNegCache, canon));
430 map<string, string> object;
431 object["count"] = lexical_cast<string>(count);
432 object["result"] = "Flushed cache.";
433 resp->body = returnJsonObject(object);
434}
435
1ce57618 436RecursorWebServer::RecursorWebServer(FDMultiplexer* fdm)
a7a902fb
BH
437{
438 RecursorControlParser rcp; // inits
a7a902fb 439
bbef8f04 440 d_ws = new AsyncWebServer(fdm, arg()["experimental-webserver-address"], arg().asNum("experimental-webserver-port"));
825fa717 441 d_ws->bind();
867f6abc 442
3ae143b0 443 // legacy dispatch
1ce57618 444 d_ws->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat, this, _1, _2));
a426cb89 445 d_ws->registerApiHandler("/servers/localhost/flush-cache", &apiServerFlushCache);
41942bb3 446 d_ws->registerApiHandler("/servers/localhost/config/allow-from", &apiServerConfigAllowFrom);
6ec5e728
CH
447 d_ws->registerApiHandler("/servers/localhost/config", &apiServerConfig);
448 d_ws->registerApiHandler("/servers/localhost/search-log", &apiServerSearchLog);
37bc3d01 449 d_ws->registerApiHandler("/servers/localhost/search-data", &apiServerSearchData);
6ec5e728 450 d_ws->registerApiHandler("/servers/localhost/statistics", &apiServerStatistics);
02945d9a
CH
451 d_ws->registerApiHandler("/servers/localhost/zones/<id>", &apiServerZoneDetail);
452 d_ws->registerApiHandler("/servers/localhost/zones", &apiServerZones);
6ec5e728
CH
453 d_ws->registerApiHandler("/servers/localhost", &apiServerDetail);
454 d_ws->registerApiHandler("/servers", &apiServer);
867f6abc 455
3ae143b0 456 d_ws->go();
867f6abc
CH
457}
458
1ce57618 459void RecursorWebServer::jsonstat(HttpRequest* req, HttpResponse *resp)
867f6abc 460{
e2897a7d 461 string command;
a7a902fb 462
583ea80d
CH
463 if(req->getvars.count("command")) {
464 command = req->getvars["command"];
465 req->getvars.erase("command");
3ae143b0 466 }
a7a902fb
BH
467
468 map<string, string> stats;
a426cb89 469 if(command == "get-query-ring") {
c89f8cd0 470 typedef pair<string,uint16_t> query_t;
471 vector<query_t> queries;
509196af 472 bool filter=!req->getvars["public-filtered"].empty();
473
c89f8cd0 474 if(req->getvars["name"]=="servfail-queries")
475 queries=broadcastAccFunction<vector<query_t> >(pleaseGetServfailQueryRing);
476 else if(req->getvars["name"]=="queries")
477 queries=broadcastAccFunction<vector<query_t> >(pleaseGetQueryRing);
478
479 typedef map<query_t,unsigned int> counts_t;
480 counts_t counts;
481 unsigned int total=0;
482 BOOST_FOREACH(const query_t& q, queries) {
483 total++;
509196af 484 if(filter)
485 counts[make_pair(getRegisteredName(toLower(q.first)), q.second)]++;
486 else
487 counts[make_pair(toLower(q.first), q.second)]++;
c89f8cd0 488 }
489
490 typedef std::multimap<int, query_t> rcounts_t;
491 rcounts_t rcounts;
492
493 for(counts_t::const_iterator i=counts.begin(); i != counts.end(); ++i)
494 rcounts.insert(make_pair(-i->second, i->first));
495
c89f8cd0 496 Document doc;
497 doc.SetObject();
498 Value entries;
499 entries.SetArray();
509196af 500 unsigned int tot=0, totIncluded=0;
c89f8cd0 501 BOOST_FOREACH(const rcounts_t::value_type& q, rcounts) {
502 Value arr;
503
504 arr.SetArray();
509196af 505 totIncluded-=q.first;
c89f8cd0 506 arr.PushBack(-q.first, doc.GetAllocator());
507 arr.PushBack(q.second.first.c_str(), doc.GetAllocator());
508 arr.PushBack(DNSRecordContent::NumberToType(q.second.second).c_str(), doc.GetAllocator());
509 entries.PushBack(arr, doc.GetAllocator());
510 if(tot++>=100)
511 break;
512 }
509196af 513 if(queries.size() != totIncluded) {
514 Value arr;
515 arr.SetArray();
e258fd11 516 arr.PushBack((unsigned int)(queries.size()-totIncluded), doc.GetAllocator());
509196af 517 arr.PushBack("", doc.GetAllocator());
518 arr.PushBack("", doc.GetAllocator());
519 entries.PushBack(arr, doc.GetAllocator());
520 }
521 doc.AddMember("entries", entries, doc.GetAllocator());
522 resp->setBody(doc);
523 return;
524 }
525 else if(command == "get-remote-ring") {
526 vector<ComboAddress> queries;
527 if(req->getvars["name"]=="remotes")
528 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetRemotes);
529 else if(req->getvars["name"]=="servfail-remotes")
530 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetServfailRemotes);
531 else if(req->getvars["name"]=="large-answer-remotes")
532 queries=broadcastAccFunction<vector<ComboAddress> >(pleaseGetLargeAnswerRemotes);
533
534 typedef map<ComboAddress,unsigned int,ComboAddress::addressOnlyLessThan> counts_t;
535 counts_t counts;
536 unsigned int total=0;
537 BOOST_FOREACH(const ComboAddress& q, queries) {
538 total++;
539 counts[q]++;
540 }
541
542 typedef std::multimap<int, ComboAddress> rcounts_t;
543 rcounts_t rcounts;
544
545 for(counts_t::const_iterator i=counts.begin(); i != counts.end(); ++i)
546 rcounts.insert(make_pair(-i->second, i->first));
547
548
549 Document doc;
550 doc.SetObject();
551 Value entries;
552 entries.SetArray();
553 unsigned int tot=0, totIncluded=0;
554 BOOST_FOREACH(const rcounts_t::value_type& q, rcounts) {
555 totIncluded-=q.first;
556 Value arr;
557
558 arr.SetArray();
559 arr.PushBack(-q.first, doc.GetAllocator());
560 Value jname(q.second.toString().c_str(), doc.GetAllocator()); // copy
561 arr.PushBack(jname, doc.GetAllocator());
562 entries.PushBack(arr, doc.GetAllocator());
563 if(tot++>=100)
564 break;
565 }
566 if(queries.size() != totIncluded) {
567 Value arr;
568 arr.SetArray();
59a24ed2 569 arr.PushBack((unsigned int)(queries.size()-totIncluded), doc.GetAllocator());
509196af 570 arr.PushBack("", doc.GetAllocator());
571 entries.PushBack(arr, doc.GetAllocator());
572 }
573
c89f8cd0 574 doc.AddMember("entries", entries, doc.GetAllocator());
575 resp->setBody(doc);
576 return;
3ae143b0
CH
577 } else {
578 resp->status = 404;
c89f8cd0 579 resp->body = returnJsonError("Command '"+command+"' not found");
3ae143b0 580 }
a7a902fb 581}
825fa717
CH
582
583
584void AsyncServerNewConnectionMT(void *p) {
585 AsyncServer *server = (AsyncServer*)p;
586 try {
587 Socket* socket = server->accept();
588 server->d_asyncNewConnectionCallback(socket);
589 delete socket;
590 } catch (NetworkError &e) {
591 // we're running in a shared process/thread, so can't just terminate/abort.
592 return;
593 }
594}
595
596void AsyncServer::asyncWaitForConnections(FDMultiplexer* fdm, const newconnectioncb_t& callback)
597{
598 d_asyncNewConnectionCallback = callback;
599 fdm->addReadFD(d_server_socket.getHandle(), boost::bind(&AsyncServer::newConnection, this));
600}
601
602void AsyncServer::newConnection()
603{
604 MT->makeThread(&AsyncServerNewConnectionMT, this);
605}
606
607
608void AsyncWebServer::serveConnection(Socket *client)
609{
610 HttpRequest req;
583ea80d
CH
611 YaHTTP::AsyncRequestLoader yarl;
612 yarl.initialize(&req);
825fa717 613 client->setNonBlocking();
c89f8cd0 614
825fa717
CH
615 string data;
616 try {
617 while(!req.complete) {
f0b2bab0 618 data.clear();
825fa717
CH
619 int bytes = arecvtcp(data, 16384, client, true);
620 if (bytes > 0) {
621 req.complete = yarl.feed(data);
622 } else {
623 // read error OR EOF
624 break;
625 }
626 }
583ea80d 627 yarl.finalize();
825fa717
CH
628 } catch (YaHTTP::ParseError &e) {
629 // request stays incomplete
630 }
631
632 HttpResponse resp = handleRequest(req);
633 ostringstream ss;
634 resp.write(ss);
635 data = ss.str();
636
637 // now send the reply
638 if (asendtcp(data, client) == -1 || data.empty()) {
639 L<<Logger::Error<<"Failed sending reply to HTTP client"<<endl;
640 }
641}
642
643void AsyncWebServer::go() {
644 if (!d_server)
645 return;
646 ((AsyncServer*)d_server)->asyncWaitForConnections(d_fdm, boost::bind(&AsyncWebServer::serveConnection, this, _1));
647}