2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
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.
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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "ws-recursor.hh"
29 #include "namespaces.hh"
32 #include "rec_channel.hh"
33 #include "rec_metrics.hh"
34 #include "arguments.hh"
37 #include "dnsparser.hh"
39 #include "webserver.hh"
42 #include "ext/incbin/incbin.h"
43 #include "rec-lua-conf.hh"
44 #include "rpzloader.hh"
45 #include "uuid-utils.hh"
47 extern thread_local FDMultiplexer
* t_fdm
;
51 void productServerStatisticsFetch(map
<string
,string
>& out
)
53 map
<string
,string
> stats
= getAllStatsMap(StatComponent::API
);
57 boost::optional
<uint64_t> productServerStatisticsFetch(const std::string
& name
)
59 return getStatByName(name
);
62 static void apiWriteConfigFile(const string
& filebasename
, const string
& content
)
64 if (::arg()["api-config-dir"].empty()) {
65 throw ApiException("Config Option \"api-config-dir\" must be set");
68 string filename
= ::arg()["api-config-dir"] + "/" + filebasename
+ ".conf";
69 ofstream
ofconf(filename
.c_str());
71 throw ApiException("Could not open config fragment file '"+filename
+"' for writing: "+stringerror());
73 ofconf
<< "# Generated by pdns-recursor REST API, DO NOT EDIT" << endl
;
74 ofconf
<< content
<< endl
;
78 static void apiServerConfigAllowFrom(HttpRequest
* req
, HttpResponse
* resp
)
80 if (req
->method
== "PUT") {
81 Json document
= req
->json();
83 auto jlist
= document
["value"];
84 if (!jlist
.is_array()) {
85 throw ApiException("'value' must be an array");
89 for (auto value
: jlist
.array_items()) {
91 nmg
.addMask(value
.string_value());
92 } catch (const NetmaskException
&e
) {
93 throw ApiException(e
.reason
);
99 // Clear allow-from-file if set, so our changes take effect
100 ss
<< "allow-from-file=" << endl
;
102 // Clear allow-from, and provide a "parent" value
103 ss
<< "allow-from=" << endl
;
104 ss
<< "allow-from+=" << nmg
.toString() << endl
;
106 apiWriteConfigFile("allow-from", ss
.str());
110 // fall through to GET
111 } else if (req
->method
!= "GET") {
112 throw HttpMethodNotAllowedException();
115 // Return currently configured ACLs
116 vector
<string
> entries
;
117 t_allowFrom
->toStringVector(&entries
);
119 resp
->setBody(Json::object
{
120 { "name", "allow-from" },
121 { "value", entries
},
125 static void fillZone(const DNSName
& zonename
, HttpResponse
* resp
)
127 auto iter
= SyncRes::t_sstorage
.domainmap
->find(zonename
);
128 if (iter
== SyncRes::t_sstorage
.domainmap
->end())
129 throw ApiException("Could not find domain '"+zonename
.toLogString()+"'");
131 const SyncRes::AuthDomain
& zone
= iter
->second
;
134 for(const ComboAddress
& server
: zone
.d_servers
) {
135 servers
.push_back(server
.toStringWithPort());
139 for(const SyncRes::AuthDomain::records_t::value_type
& dr
: zone
.d_records
) {
140 records
.push_back(Json::object
{
141 { "name", dr
.d_name
.toString() },
142 { "type", DNSRecordContent::NumberToType(dr
.d_type
) },
143 { "ttl", (double)dr
.d_ttl
},
144 { "content", dr
.d_content
->getZoneRepresentation() }
148 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
149 string zoneId
= apiZoneNameToId(iter
->first
);
152 { "url", "/api/v1/servers/localhost/zones/" + zoneId
},
153 { "name", iter
->first
.toString() },
154 { "kind", zone
.d_servers
.empty() ? "Native" : "Forwarded" },
155 { "servers", servers
},
156 { "recursion_desired", zone
.d_servers
.empty() ? false : zone
.d_rdForward
},
157 { "records", records
}
163 static void doCreateZone(const Json document
)
165 if (::arg()["api-config-dir"].empty()) {
166 throw ApiException("Config Option \"api-config-dir\" must be set");
169 DNSName zonename
= apiNameToDNSName(stringFromJson(document
, "name"));
170 apiCheckNameAllowedCharacters(zonename
.toString());
172 string singleIPTarget
= document
["single_target_ip"].string_value();
173 string kind
= toUpper(stringFromJson(document
, "kind"));
174 bool rd
= boolFromJson(document
, "recursion_desired");
175 string confbasename
= "zone-" + apiZoneNameToId(zonename
);
177 if (kind
== "NATIVE") {
179 throw ApiException("kind=Native and recursion_desired are mutually exclusive");
180 if(!singleIPTarget
.empty()) {
182 ComboAddress
rem(singleIPTarget
);
183 if(rem
.sin4
.sin_family
!= AF_INET
)
184 throw ApiException("");
185 singleIPTarget
= rem
.toString();
188 throw ApiException("Single IP target '"+singleIPTarget
+"' is invalid");
191 string zonefilename
= ::arg()["api-config-dir"] + "/" + confbasename
+ ".zone";
192 ofstream
ofzone(zonefilename
.c_str());
194 throw ApiException("Could not open '"+zonefilename
+"' for writing: "+stringerror());
196 ofzone
<< "; Generated by pdns-recursor REST API, DO NOT EDIT" << endl
;
197 ofzone
<< zonename
<< "\tIN\tSOA\tlocal.zone.\thostmaster."<<zonename
<<" 1 1 1 1 1" << endl
;
198 if(!singleIPTarget
.empty()) {
199 ofzone
<<zonename
<< "\t3600\tIN\tA\t"<<singleIPTarget
<<endl
;
200 ofzone
<<"*."<<zonename
<< "\t3600\tIN\tA\t"<<singleIPTarget
<<endl
;
204 apiWriteConfigFile(confbasename
, "auth-zones+=" + zonename
.toString() + "=" + zonefilename
);
205 } else if (kind
== "FORWARDED") {
207 for (auto value
: document
["servers"].array_items()) {
208 string server
= value
.string_value();
210 throw ApiException("Forwarded-to server must not be an empty string");
213 ComboAddress ca
= parseIPAndPort(server
, 53);
214 if (!serverlist
.empty()) {
217 serverlist
+= ca
.toStringWithPort();
218 } catch (const PDNSException
&e
) {
219 throw ApiException(e
.reason
);
222 if (serverlist
== "")
223 throw ApiException("Need at least one upstream server when forwarding");
226 apiWriteConfigFile(confbasename
, "forward-zones-recurse+=" + zonename
.toString() + "=" + serverlist
);
228 apiWriteConfigFile(confbasename
, "forward-zones+=" + zonename
.toString() + "=" + serverlist
);
231 throw ApiException("invalid kind");
235 static bool doDeleteZone(const DNSName
& zonename
)
237 if (::arg()["api-config-dir"].empty()) {
238 throw ApiException("Config Option \"api-config-dir\" must be set");
243 // this one must exist
244 filename
= ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename
) + ".conf";
245 if (unlink(filename
.c_str()) != 0) {
249 // .zone file is optional
250 filename
= ::arg()["api-config-dir"] + "/zone-" + apiZoneNameToId(zonename
) + ".zone";
251 unlink(filename
.c_str());
256 static void apiServerZones(HttpRequest
* req
, HttpResponse
* resp
)
258 if (req
->method
== "POST") {
259 if (::arg()["api-config-dir"].empty()) {
260 throw ApiException("Config Option \"api-config-dir\" must be set");
263 Json document
= req
->json();
265 DNSName zonename
= apiNameToDNSName(stringFromJson(document
, "name"));
267 auto iter
= SyncRes::t_sstorage
.domainmap
->find(zonename
);
268 if (iter
!= SyncRes::t_sstorage
.domainmap
->end())
269 throw ApiException("Zone already exists");
271 doCreateZone(document
);
272 reloadAuthAndForwards();
273 fillZone(zonename
, resp
);
278 if(req
->method
!= "GET")
279 throw HttpMethodNotAllowedException();
282 for(const SyncRes::domainmap_t::value_type
& val
: *SyncRes::t_sstorage
.domainmap
) {
283 const SyncRes::AuthDomain
& zone
= val
.second
;
285 for(const ComboAddress
& server
: zone
.d_servers
) {
286 servers
.push_back(server
.toStringWithPort());
288 // id is the canonical lookup key, which doesn't actually match the name (in some cases)
289 string zoneId
= apiZoneNameToId(val
.first
);
290 doc
.push_back(Json::object
{
292 { "url", "/api/v1/servers/localhost/zones/" + zoneId
},
293 { "name", val
.first
.toString() },
294 { "kind", zone
.d_servers
.empty() ? "Native" : "Forwarded" },
295 { "servers", servers
},
296 { "recursion_desired", zone
.d_servers
.empty() ? false : zone
.d_rdForward
}
302 static void apiServerZoneDetail(HttpRequest
* req
, HttpResponse
* resp
)
304 DNSName zonename
= apiZoneIdToName(req
->parameters
["id"]);
306 SyncRes::domainmap_t::const_iterator iter
= SyncRes::t_sstorage
.domainmap
->find(zonename
);
307 if (iter
== SyncRes::t_sstorage
.domainmap
->end())
308 throw ApiException("Could not find domain '"+zonename
.toLogString()+"'");
310 if(req
->method
== "PUT") {
311 Json document
= req
->json();
313 doDeleteZone(zonename
);
314 doCreateZone(document
);
315 reloadAuthAndForwards();
317 resp
->status
= 204; // No Content, but indicate success
319 else if(req
->method
== "DELETE") {
320 if (!doDeleteZone(zonename
)) {
321 throw ApiException("Deleting domain failed");
324 reloadAuthAndForwards();
325 // empty body on success
327 resp
->status
= 204; // No Content: declare that the zone is gone now
328 } else if(req
->method
== "GET") {
329 fillZone(zonename
, resp
);
331 throw HttpMethodNotAllowedException();
335 static void apiServerSearchData(HttpRequest
* req
, HttpResponse
* resp
) {
336 if(req
->method
!= "GET")
337 throw HttpMethodNotAllowedException();
339 string q
= req
->getvars
["q"];
341 throw ApiException("Query q can't be blank");
344 for(const SyncRes::domainmap_t::value_type
& val
: *SyncRes::t_sstorage
.domainmap
) {
345 string zoneId
= apiZoneNameToId(val
.first
);
346 string zoneName
= val
.first
.toString();
347 if (pdns_ci_find(zoneName
, q
) != string::npos
) {
348 doc
.push_back(Json::object
{
350 { "zone_id", zoneId
},
355 // if zone name is an exact match, don't bother with returning all records/comments in it
356 if (val
.first
== DNSName(q
)) {
360 const SyncRes::AuthDomain
& zone
= val
.second
;
362 for(const SyncRes::AuthDomain::records_t::value_type
& rr
: zone
.d_records
) {
363 if (pdns_ci_find(rr
.d_name
.toString(), q
) == string::npos
&& pdns_ci_find(rr
.d_content
->getZoneRepresentation(), q
) == string::npos
)
366 doc
.push_back(Json::object
{
367 { "type", "record" },
368 { "zone_id", zoneId
},
369 { "zone_name", zoneName
},
370 { "name", rr
.d_name
.toString() },
371 { "content", rr
.d_content
->getZoneRepresentation() }
378 static void apiServerCacheFlush(HttpRequest
* req
, HttpResponse
* resp
) {
379 if(req
->method
!= "PUT")
380 throw HttpMethodNotAllowedException();
382 DNSName canon
= apiNameToDNSName(req
->getvars
["domain"]);
383 bool subtree
= (req
->getvars
.count("subtree") > 0 && req
->getvars
["subtree"].compare("true") == 0);
385 int count
= broadcastAccFunction
<uint64_t>(boost::bind(pleaseWipeCache
, canon
, subtree
, 0xffff));
386 count
+= broadcastAccFunction
<uint64_t>(boost::bind(pleaseWipePacketCache
, canon
, subtree
, 0xffff));
387 count
+= broadcastAccFunction
<uint64_t>(boost::bind(pleaseWipeAndCountNegCache
, canon
, subtree
));
388 resp
->setBody(Json::object
{
390 { "result", "Flushed cache." }
394 static void apiServerRPZStats(HttpRequest
* req
, HttpResponse
* resp
) {
395 if(req
->method
!= "GET")
396 throw HttpMethodNotAllowedException();
398 auto luaconf
= g_luaconfs
.getLocal();
399 auto numZones
= luaconf
->dfe
.size();
403 for (size_t i
=0; i
< numZones
; i
++) {
404 auto zone
= luaconf
->dfe
.getZone(i
);
407 auto name
= zone
->getName();
408 auto stats
= getRPZZoneStats(*name
);
409 if (stats
== nullptr)
411 Json::object zoneInfo
= {
412 {"transfers_failed", (double)stats
->d_failedTransfers
},
413 {"transfers_success", (double)stats
->d_successfulTransfers
},
414 {"transfers_full", (double)stats
->d_fullTransfers
},
415 {"records", (double)stats
->d_numberOfRecords
},
416 {"last_update", (double)stats
->d_lastUpdate
},
417 {"serial", (double)stats
->d_serial
},
419 ret
[*name
] = zoneInfo
;
425 static void prometheusMetrics(HttpRequest
*req
, HttpResponse
*resp
) {
426 static MetricDefinitionStorage s_metricDefinitions
;
428 if (req
->method
!= "GET")
429 throw HttpMethodNotAllowedException();
434 std::ostringstream output
;
435 typedef map
<string
, string
> varmap_t
;
436 varmap_t varmap
= getAllStatsMap(
437 StatComponent::API
); // Argument controls blacklisting of any stats. So stats-api-blacklist will be used to block returned stats.
438 for (const auto &tup
: varmap
) {
439 std::string metricName
= tup
.first
;
441 // Prometheus suggest using '_' instead of '-'
442 std::string prometheusMetricName
= "pdns_recursor_" + boost::replace_all_copy(metricName
, "-", "_");
444 MetricDefinition metricDetails
;
446 if (s_metricDefinitions
.getMetricDetails(metricName
, metricDetails
)) {
447 std::string prometheusTypeName
= s_metricDefinitions
.getPrometheusStringMetricType(
448 metricDetails
.prometheusType
);
450 if (prometheusTypeName
.empty()) {
454 // for these we have the help and types encoded in the sources:
455 output
<< "# HELP " << prometheusMetricName
<< " " << metricDetails
.description
<< "\n";
456 output
<< "# TYPE " << prometheusMetricName
<< " " << prometheusTypeName
<< "\n";
458 output
<< prometheusMetricName
<< " " << tup
.second
<< "\n";
461 resp
->body
= output
.str();
462 resp
->headers
["Content-Type"] = "text/plain";
468 #include "htmlfiles.h"
470 static void serveStuff(HttpRequest
* req
, HttpResponse
* resp
)
472 resp
->headers
["Cache-Control"] = "max-age=86400";
474 if(req
->url
.path
== "/")
475 req
->url
.path
= "/index.html";
477 const string charset
= "; charset=utf-8";
478 if(boost::ends_with(req
->url
.path
, ".html"))
479 resp
->headers
["Content-Type"] = "text/html" + charset
;
480 else if(boost::ends_with(req
->url
.path
, ".css"))
481 resp
->headers
["Content-Type"] = "text/css" + charset
;
482 else if(boost::ends_with(req
->url
.path
,".js"))
483 resp
->headers
["Content-Type"] = "application/javascript" + charset
;
484 else if(boost::ends_with(req
->url
.path
, ".png"))
485 resp
->headers
["Content-Type"] = "image/png";
487 resp
->headers
["X-Content-Type-Options"] = "nosniff";
488 resp
->headers
["X-Frame-Options"] = "deny";
489 resp
->headers
["X-Permitted-Cross-Domain-Policies"] = "none";
491 resp
->headers
["X-XSS-Protection"] = "1; mode=block";
492 // resp->headers["Content-Security-Policy"] = "default-src 'self'; style-src 'self' 'unsafe-inline'";
494 resp
->body
= g_urlmap
[req
->url
.path
.c_str()+1];
499 RecursorWebServer::RecursorWebServer(FDMultiplexer
* fdm
)
503 d_ws
= std::unique_ptr
<AsyncWebServer
>(new AsyncWebServer(fdm
, arg()["webserver-address"], arg().asNum("webserver-port")));
504 d_ws
->setApiKey(arg()["api-key"]);
505 d_ws
->setPassword(arg()["webserver-password"]);
506 d_ws
->setLogLevel(arg()["webserver-loglevel"]);
509 acl
.toMasks(::arg()["webserver-allow-from"]);
515 d_ws
->registerApiHandler("/jsonstat", boost::bind(&RecursorWebServer::jsonstat
, this, _1
, _2
), true);
516 d_ws
->registerApiHandler("/api/v1/servers/localhost/cache/flush", &apiServerCacheFlush
);
517 d_ws
->registerApiHandler("/api/v1/servers/localhost/config/allow-from", &apiServerConfigAllowFrom
);
518 d_ws
->registerApiHandler("/api/v1/servers/localhost/config", &apiServerConfig
);
519 d_ws
->registerApiHandler("/api/v1/servers/localhost/rpzstatistics", &apiServerRPZStats
);
520 d_ws
->registerApiHandler("/api/v1/servers/localhost/search-data", &apiServerSearchData
);
521 d_ws
->registerApiHandler("/api/v1/servers/localhost/statistics", &apiServerStatistics
, true);
522 d_ws
->registerApiHandler("/api/v1/servers/localhost/zones/<id>", &apiServerZoneDetail
);
523 d_ws
->registerApiHandler("/api/v1/servers/localhost/zones", &apiServerZones
);
524 d_ws
->registerApiHandler("/api/v1/servers/localhost", &apiServerDetail
, true);
525 d_ws
->registerApiHandler("/api/v1/servers", &apiServer
);
526 d_ws
->registerApiHandler("/api", &apiDiscovery
);
528 for(const auto& u
: g_urlmap
)
529 d_ws
->registerWebHandler("/"+u
.first
, serveStuff
);
530 d_ws
->registerWebHandler("/", serveStuff
);
531 d_ws
->registerWebHandler("/metrics", prometheusMetrics
);
535 void RecursorWebServer::jsonstat(HttpRequest
* req
, HttpResponse
*resp
)
539 if(req
->getvars
.count("command")) {
540 command
= req
->getvars
["command"];
541 req
->getvars
.erase("command");
544 map
<string
, string
> stats
;
545 if(command
== "get-query-ring") {
546 typedef pair
<DNSName
,uint16_t> query_t
;
547 vector
<query_t
> queries
;
548 bool filter
=!req
->getvars
["public-filtered"].empty();
550 if(req
->getvars
["name"]=="servfail-queries")
551 queries
=broadcastAccFunction
<vector
<query_t
> >(pleaseGetServfailQueryRing
);
552 else if(req
->getvars
["name"]=="bogus-queries")
553 queries
=broadcastAccFunction
<vector
<query_t
> >(pleaseGetBogusQueryRing
);
554 else if(req
->getvars
["name"]=="queries")
555 queries
=broadcastAccFunction
<vector
<query_t
> >(pleaseGetQueryRing
);
557 typedef map
<query_t
,unsigned int> counts_t
;
559 unsigned int total
=0;
560 for(const query_t
& q
: queries
) {
563 counts
[make_pair(getRegisteredName(q
.first
), q
.second
)]++;
565 counts
[make_pair(q
.first
, q
.second
)]++;
568 typedef std::multimap
<int, query_t
> rcounts_t
;
571 for(counts_t::const_iterator i
=counts
.begin(); i
!= counts
.end(); ++i
)
572 rcounts
.insert(make_pair(-i
->second
, i
->first
));
575 unsigned int tot
=0, totIncluded
=0;
576 for(const rcounts_t::value_type
& q
: rcounts
) {
577 totIncluded
-=q
.first
;
578 entries
.push_back(Json::array
{
579 -q
.first
, q
.second
.first
.toLogString(), DNSRecordContent::NumberToType(q
.second
.second
)
584 if(queries
.size() != totIncluded
) {
585 entries
.push_back(Json::array
{
586 (int)(queries
.size() - totIncluded
), "", ""
589 resp
->setBody(Json::object
{ { "entries", entries
} });
592 else if(command
== "get-remote-ring") {
593 vector
<ComboAddress
> queries
;
594 if(req
->getvars
["name"]=="remotes")
595 queries
=broadcastAccFunction
<vector
<ComboAddress
> >(pleaseGetRemotes
);
596 else if(req
->getvars
["name"]=="servfail-remotes")
597 queries
=broadcastAccFunction
<vector
<ComboAddress
> >(pleaseGetServfailRemotes
);
598 else if(req
->getvars
["name"]=="bogus-remotes")
599 queries
=broadcastAccFunction
<vector
<ComboAddress
> >(pleaseGetBogusRemotes
);
600 else if(req
->getvars
["name"]=="large-answer-remotes")
601 queries
=broadcastAccFunction
<vector
<ComboAddress
> >(pleaseGetLargeAnswerRemotes
);
602 else if(req
->getvars
["name"]=="timeouts")
603 queries
=broadcastAccFunction
<vector
<ComboAddress
> >(pleaseGetTimeouts
);
605 typedef map
<ComboAddress
,unsigned int,ComboAddress::addressOnlyLessThan
> counts_t
;
607 unsigned int total
=0;
608 for(const ComboAddress
& q
: queries
) {
613 typedef std::multimap
<int, ComboAddress
> rcounts_t
;
616 for(counts_t::const_iterator i
=counts
.begin(); i
!= counts
.end(); ++i
)
617 rcounts
.insert(make_pair(-i
->second
, i
->first
));
620 unsigned int tot
=0, totIncluded
=0;
621 for(const rcounts_t::value_type
& q
: rcounts
) {
622 totIncluded
-=q
.first
;
623 entries
.push_back(Json::array
{
624 -q
.first
, q
.second
.toString()
629 if(queries
.size() != totIncluded
) {
630 entries
.push_back(Json::array
{
631 (int)(queries
.size() - totIncluded
), ""
635 resp
->setBody(Json::object
{ { "entries", entries
} });
638 resp
->setErrorResult("Command '"+command
+"' not found", 404);
643 void AsyncServerNewConnectionMT(void *p
) {
644 AsyncServer
*server
= (AsyncServer
*)p
;
647 auto socket
= server
->accept(); // this is actually a shared_ptr
649 server
->d_asyncNewConnectionCallback(socket
);
651 } catch (NetworkError
&e
) {
652 // we're running in a shared process/thread, so can't just terminate/abort.
653 g_log
<<Logger::Warning
<<"Network error in web thread: "<<e
.what()<<endl
;
657 g_log
<<Logger::Warning
<<"Unknown error in web thread"<<endl
;
664 void AsyncServer::asyncWaitForConnections(FDMultiplexer
* fdm
, const newconnectioncb_t
& callback
)
666 d_asyncNewConnectionCallback
= callback
;
667 fdm
->addReadFD(d_server_socket
.getHandle(), boost::bind(&AsyncServer::newConnection
, this));
670 void AsyncServer::newConnection()
672 getMT()->makeThread(&AsyncServerNewConnectionMT
, this);
675 // This is an entry point from FDM, so it needs to catch everything.
676 void AsyncWebServer::serveConnection(std::shared_ptr
<Socket
> client
) const {
677 const string logprefix
= d_logprefix
+ to_string(getUniqueID()) + " ";
679 HttpRequest
req(logprefix
);
685 YaHTTP::AsyncRequestLoader yarl
;
686 yarl
.initialize(&req
);
687 client
->setNonBlocking();
691 while(!req
.complete
) {
692 int bytes
= arecvtcp(data
, 16384, client
.get(), true);
694 req
.complete
= yarl
.feed(data
);
701 } catch (YaHTTP::ParseError
&e
) {
702 // request stays incomplete
703 g_log
<<Logger::Warning
<<logprefix
<<"Unable to parse request: "<<e
.what()<<endl
;
706 if (d_loglevel
>= WebServer::LogLevel::None
) {
707 client
->getRemote(remote
);
710 logRequest(req
, remote
);
712 WebServer::handleRequest(req
, resp
);
717 logResponse(resp
, remote
, logprefix
);
719 // now send the reply
720 if (asendtcp(reply
, client
.get()) == -1 || reply
.empty()) {
721 g_log
<<Logger::Error
<<logprefix
<<"Failed sending reply to HTTP client"<<endl
;
724 catch(PDNSException
&e
) {
725 g_log
<<Logger::Error
<<logprefix
<<"Exception: "<<e
.reason
<<endl
;
727 catch(std::exception
&e
) {
728 if(strstr(e
.what(), "timeout")==0)
729 g_log
<<Logger::Error
<<logprefix
<<"STL Exception: "<<e
.what()<<endl
;
732 g_log
<<Logger::Error
<<logprefix
<<"Unknown exception"<<endl
;
735 if (d_loglevel
>= WebServer::LogLevel::Normal
) {
736 g_log
<<Logger::Notice
<<logprefix
<<remote
<<" \""<<req
.method
<<" "<<req
.url
.path
<<" HTTP/"<<req
.versionStr(req
.version
)<<"\" "<<resp
.status
<<" "<<reply
.size()<<endl
;
740 void AsyncWebServer::go() {
743 auto server
= std::dynamic_pointer_cast
<AsyncServer
>(d_server
);
746 server
->asyncWaitForConnections(d_fdm
, boost::bind(&AsyncWebServer::serveConnection
, this, _1
));