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