]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/webserver.cc
Webserver: convert to new yahttp api and router
[thirdparty/pdns.git] / pdns / webserver.cc
CommitLineData
12c86877
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
ac7ba905 3 Copyright (C) 2002-2012 PowerDNS.COM BV
12c86877
BH
4
5 This program is free software; you can redistribute it and/or modify
22dc646a
BH
6 it under the terms of the GNU General Public License version 2
7 as published by the Free Software Foundation
f782fe38
MH
8
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.
12c86877
BH
12
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
06bd9ccf 20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
12c86877 21*/
731f58b8 22#include "utility.hh"
12c86877 23#include "webserver.hh"
12c86877
BH
24#include "misc.hh"
25#include <vector>
26#include "logger.hh"
27#include <stdio.h>
28#include "dns.hh"
2db9c30e 29#include "base64.hh"
33196945 30#include "json.hh"
583ea80d 31#include <yahttp/router.hpp>
12c86877 32
232f0877
CH
33struct connectionThreadData {
34 WebServer* webServer;
825fa717 35 Socket* client;
232f0877 36};
12c86877 37
6ec5e728
CH
38void HttpRequest::json(rapidjson::Document& document)
39{
583ea80d
CH
40 if(this->body.empty()) {
41 L<<Logger::Debug<<"HTTP: JSON document expected in request body, but body was empty" << endl;
42 throw HttpBadRequestException();
43 }
6ec5e728 44 if(document.Parse<0>(this->body.c_str()).HasParseError()) {
583ea80d 45 L<<Logger::Debug<<"HTTP: parsing of JSON document failed" << endl;
6ec5e728
CH
46 throw HttpBadRequestException();
47 }
48}
49
669822d0
CH
50void HttpResponse::setBody(rapidjson::Document& document)
51{
52 this->body = makeStringFromDocument(document);
53}
54
12c86877
BH
55int WebServer::B64Decode(const std::string& strInput, std::string& strOutput)
56{
2db9c30e 57 return ::B64Decode(strInput, strOutput);
12c86877
BH
58}
59
583ea80d 60static void handlerWrapper(WebServer::HandlerFunction handler, YaHTTP::Request* req, YaHTTP::Response* resp)
12c86877 61{
583ea80d
CH
62 // wrapper to convert from YaHTTP::* to our subclasses
63 handler(static_cast<HttpRequest*>(req), static_cast<HttpResponse*>(resp));
64}
232f0877 65
583ea80d
CH
66void WebServer::registerHandler(const string& url, HandlerFunction handler)
67{
68 YaHTTP::THandlerFunction f = boost::bind(&handlerWrapper, handler, _1, _2);
69 YaHTTP::Router::Any(url, f);
232f0877
CH
70}
71
583ea80d 72static void apiWrapper(WebServer::HandlerFunction handler, HttpRequest* req, HttpResponse* resp) {
3ae143b0
CH
73 resp->headers["Access-Control-Allow-Origin"] = "*";
74 resp->headers["Content-Type"] = "application/json";
75
76 string callback;
77
583ea80d
CH
78 if(req->getvars.count("callback")) {
79 callback=req->getvars["callback"];
80 req->getvars.erase("callback");
3ae143b0
CH
81 }
82
583ea80d 83 req->getvars.erase("_"); // jQuery cache buster
3ae143b0
CH
84
85 try {
583ea80d 86 resp->status = 200;
3ae143b0
CH
87 handler(req, resp);
88 } catch (ApiException &e) {
6ec5e728
CH
89 resp->body = returnJsonError(e.what());
90 resp->status = 422;
91 return;
92 } catch (JsonException &e) {
93 resp->body = returnJsonError(e.what());
3ae143b0
CH
94 resp->status = 422;
95 return;
96 }
97
37663c3b
CH
98 if (resp->status == 204) {
99 // No Content -> no Content-Type.
100 resp->headers.erase("Content-Type");
101 }
102
3ae143b0
CH
103 if(!callback.empty()) {
104 resp->body = callback + "(" + resp->body + ");";
105 }
106}
107
108void WebServer::registerApiHandler(const string& url, HandlerFunction handler) {
109 HandlerFunction f = boost::bind(&apiWrapper, handler, _1, _2);
110 registerHandler(url, f);
111}
112
232f0877
CH
113static void *WebServerConnectionThreadStart(void *p) {
114 connectionThreadData* data = static_cast<connectionThreadData*>(p);
6b70b8c7 115 pthread_detach(pthread_self());
232f0877 116 data->webServer->serveConnection(data->client);
80d59cd1 117
825fa717 118 delete data->client; // close socket
232f0877 119 delete data;
80d59cd1 120
232f0877
CH
121 return NULL;
122}
123
80d59cd1
CH
124HttpResponse WebServer::handleRequest(HttpRequest req)
125{
583ea80d 126 HttpResponse resp;
825fa717 127
80d59cd1
CH
128 // set default headers
129 resp.headers["Content-Type"] = "text/html; charset=utf-8";
33196945 130
12c86877 131 try {
825fa717 132 if (!req.complete) {
583ea80d 133 L<<Logger::Debug<<"HTTP: Incomplete request" << endl;
825fa717
CH
134 throw HttpBadRequestException();
135 }
136
137 L<<Logger::Debug<<"HTTP: Handling request \"" << req.url.path << "\"" << endl;
138
80d59cd1
CH
139 YaHTTP::strstr_map_t::iterator header;
140
141 if ((header = req.headers.find("accept")) != req.headers.end()) {
142 // json wins over html
143 if (header->second.find("application/json") != std::string::npos) {
144 req.accept_json = true;
145 } else if (header->second.find("text/html") != std::string::npos) {
146 req.accept_html = true;
147 }
12c86877
BH
148 }
149
80d59cd1
CH
150 if (!d_password.empty()) {
151 // validate password
152 header = req.headers.find("authorization");
153 bool auth_ok = false;
154 if (header != req.headers.end() && toLower(header->second).find("basic ") == 0) {
155 string cookie = header->second.substr(6);
33196945 156
4957a608 157 string plain;
80d59cd1 158 B64Decode(cookie, plain);
4957a608 159
80d59cd1
CH
160 vector<string> cparts;
161 stringtok(cparts, plain, ":");
162
163 // this gets rid of terminating zeros
164 auth_ok = (cparts.size()==2 && (0==strcmp(cparts[1].c_str(), d_password.c_str())));
a2ce158c 165 }
80d59cd1 166 if (!auth_ok) {
51bd0969 167 L<<Logger::Debug<<"HTTP Request \"" << req.url.path << "\": Authentication failed" << endl;
80d59cd1 168 throw HttpUnauthorizedException();
33196945 169 }
80d59cd1 170 }
12c86877 171
583ea80d
CH
172 YaHTTP::THandlerFunction handler;
173 if (!YaHTTP::Router::Route(&req, handler)) {
51bd0969 174 L<<Logger::Debug<<"HTTP: No route found for \"" << req.url.path << "\"" << endl;
33196945 175 throw HttpNotFoundException();
12c86877 176 }
12c86877 177
0f67eeda 178 try {
583ea80d 179 handler(&req, &resp);
51bd0969 180 L<<Logger::Debug<<"HTTP: Result for \"" << req.url.path << "\": " << resp.status << ", body length: " << resp.body.size() << endl;
0f67eeda 181 }
583ea80d
CH
182 catch(HttpException) {
183 throw;
184 }
0f67eeda
CH
185 catch(PDNSException &e) {
186 L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": Exception: " << e.reason << endl;
187 throw HttpInternalServerErrorException();
188 }
189 catch(std::exception &e) {
190 L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": STL Exception: " << e.what() << endl;
191 throw HttpInternalServerErrorException();
192 }
193 catch(...) {
194 L<<Logger::Error<<"HTTP ISE for \""<< req.url.path << "\": Unknown Exception" << endl;
195 throw HttpInternalServerErrorException();
196 }
12c86877 197 }
33196945 198 catch(HttpException &e) {
80d59cd1 199 resp = e.response();
51bd0969 200 L<<Logger::Debug<<"HTTP: Error result for \"" << req.url.path << "\": " << resp.status << endl;
80d59cd1 201 string what = YaHTTP::Utility::status2text(resp.status);
02c04144 202 if(req.accept_html) {
80d59cd1
CH
203 resp.headers["Content-Type"] = "text/html; charset=utf-8";
204 resp.body = "<!html><title>" + what + "</title><h1>" + what + "</h1>";
02c04144 205 } else if (req.accept_json) {
80d59cd1 206 resp.headers["Content-Type"] = "application/json";
6ec5e728 207 resp.body = returnJsonError(what);
33196945 208 } else {
80d59cd1
CH
209 resp.headers["Content-Type"] = "text/plain; charset=utf-8";
210 resp.body = what;
33196945 211 }
12c86877 212 }
33196945 213
80d59cd1
CH
214 // always set these headers
215 resp.headers["Server"] = "PowerDNS/"VERSION;
216 resp.headers["Connection"] = "close";
217
218 return resp;
219}
220
825fa717 221void WebServer::serveConnection(Socket *client)
80d59cd1
CH
222try {
223 HttpRequest req;
583ea80d
CH
224 YaHTTP::AsyncRequestLoader yarl;
225 yarl.initialize(&req);
825fa717
CH
226 int timeout = 5;
227 client->setNonBlocking();
80d59cd1 228
80d59cd1 229 try {
825fa717 230 while(!req.complete) {
80d59cd1
CH
231 int bytes;
232 char buf[1024];
825fa717
CH
233 bytes = client->readWithTimeout(buf, sizeof(buf), timeout);
234 if (bytes > 0) {
80d59cd1 235 string data = string(buf, bytes);
825fa717
CH
236 req.complete = yarl.feed(data);
237 } else {
238 // read error OR EOF
239 break;
80d59cd1
CH
240 }
241 }
583ea80d 242 yarl.finalize();
80d59cd1 243 } catch (YaHTTP::ParseError &e) {
825fa717 244 // request stays incomplete
80d59cd1
CH
245 }
246
247 HttpResponse resp = WebServer::handleRequest(req);
248 ostringstream ss;
249 resp.write(ss);
825fa717
CH
250 string reply = ss.str();
251
252 client->writenWithTimeout(reply.c_str(), reply.size(), timeout);
33196945
CH
253}
254catch(PDNSException &e) {
825fa717 255 L<<Logger::Error<<"HTTP Exception: "<<e.reason<<endl;
33196945
CH
256}
257catch(std::exception &e) {
825fa717 258 L<<Logger::Error<<"HTTP STL Exception: "<<e.what()<<endl;
33196945
CH
259}
260catch(...) {
825fa717 261 L<<Logger::Error<<"HTTP: Unknown exception"<<endl;
33196945
CH
262}
263
0f9f663d 264WebServer::WebServer(const string &listenaddress, int port, const string &password) : d_server(NULL)
12c86877
BH
265{
266 d_listenaddress=listenaddress;
267 d_port=port;
268 d_password=password;
825fa717
CH
269}
270
271void WebServer::bind()
272{
96d299db 273 try {
825fa717
CH
274 d_server = createServer();
275 L<<Logger::Warning<<"Listening for HTTP requests on "<<d_server->d_local.toStringWithPort()<<endl;
96d299db 276 }
825fa717
CH
277 catch(NetworkError &e) {
278 L<<Logger::Error<<"Listening on HTTP socket failed: "<<e.what()<<endl;
8b6348d6 279 d_server = NULL;
96d299db 280 }
12c86877
BH
281}
282
283void WebServer::go()
284{
96d299db
BH
285 if(!d_server)
286 return;
12c86877 287 try {
12c86877 288 pthread_t tid;
12c86877 289
3ae143b0 290 while(true) {
825fa717 291 // data and data->client will be freed by thread
232f0877
CH
292 connectionThreadData *data = new connectionThreadData;
293 data->webServer = this;
3ae143b0 294 data->client = d_server->accept();
232f0877 295 pthread_create(&tid, 0, &WebServerConnectionThreadStart, (void *)data);
12c86877
BH
296 }
297 }
adc10f99 298 catch(std::exception &e) {
12c86877
BH
299 L<<Logger::Error<<"STL Exception in main webserver thread: "<<e.what()<<endl;
300 }
301 catch(...) {
302 L<<Logger::Error<<"Unknown exception in main webserver thread"<<endl;
303 }
304 exit(1);
3ae143b0 305}