From 331969453f1d2f39dbd8980fd82e08ccb7803f10 Mon Sep 17 00:00:00 2001 From: Christian Hofstaedtler Date: Sat, 5 Oct 2013 19:16:17 +0200 Subject: [PATCH] ws: rework exception/error handling HTTP-level errors now cause HttpExceptions, and generate corresponding HTTP status replies. Those replies can be served as HTML, JSON or plain text, depending on what the client wanted. Also, this gets rid of the old "class Exception", and duplicate catch statements, because SessionException is a PDNSException already. --- pdns/session.cc | 2 +- pdns/session.hh | 8 --- pdns/webserver.cc | 123 ++++++++++++++++++++++++---------------------- pdns/webserver.hh | 49 ++++++++++++++++++ pdns/ws.cc | 8 ++- 5 files changed, 118 insertions(+), 72 deletions(-) diff --git a/pdns/session.cc b/pdns/session.cc index 0e338d35a0..716be607db 100644 --- a/pdns/session.cc +++ b/pdns/session.cc @@ -291,7 +291,7 @@ Server::Server(int port, const string &localaddress) s = socket(d_local.sin4.sin_family ,SOCK_STREAM,0); if(s < 0) - throw Exception(string("socket: ")+strerror(errno)); + throw SessionException(string("socket: ")+strerror(errno)); Utility::setCloseOnExec(s); diff --git a/pdns/session.hh b/pdns/session.hh index 9988760178..c2902520a6 100644 --- a/pdns/session.hh +++ b/pdns/session.hh @@ -103,12 +103,4 @@ private: int s; }; -class Exception -{ -public: - Exception(){reason="Unspecified";}; - Exception(string r){reason=r;}; - string reason; -}; - #endif /* SESSION_HH */ diff --git a/pdns/webserver.cc b/pdns/webserver.cc index 79a6cd3147..8e0d8f693f 100644 --- a/pdns/webserver.cc +++ b/pdns/webserver.cc @@ -25,6 +25,7 @@ #include #include "dns.hh" #include "base64.hh" +#include "json.hh" mapWebServer::d_functions; @@ -47,16 +48,19 @@ void WebServer::setCaller(void *that) } void *WebServer::serveConnection(void *p) -{ +try { pthread_detach(pthread_self()); Session *client=static_cast(p); + bool want_html=false; + bool want_json=false; + try { string line; client->setTimeout(5); client->getLine(line); stripLine(line); if(line.empty()) - throw Exception("Invalid web request"); + throw HttpBadRequestException(); // L<<"page: "< parts; @@ -110,27 +114,44 @@ void *WebServer::serveConnection(void *p) client->getLine(line); stripLine(line); - // L<cparts; stringtok(cparts,plain,":"); - // L<putLine("HTTP/1.1 401 OK\n"); - client->putLine("WWW-Authenticate: Basic realm=\"PowerDNS\"\n"); - - client->putLine("Connection: close\n"); - client->putLine("Content-Type: text/html; charset=utf-8\n\n"); - client->putLine("Please enter a valid password!\n"); - client->close(); - delete client; - return 0; - } + if(!d_password.empty() && !authOK) + throw HttpUnauthorizedException(); HandlerFunction *fptr; if(d_functions.count(baseUrl) && (fptr=d_functions[baseUrl])) { @@ -163,44 +175,45 @@ void *WebServer::serveConnection(void *p) client->putLine(ret); } else { - client->putLine("HTTP/1.1 404 Not found\n"); - client->putLine("Connection: close\n"); - client->putLine("Content-Type: text/html; charset=utf-8\n\n"); - // FIXME: CSS problem? - client->putLine("

Did not find file '"+baseUrl+"'\n"); + throw HttpNotFoundException(); } - - client->close(); - delete client; - client=0; - return 0; } - catch(SessionTimeoutException &e) { - // L<close(); - delete client; - client=0; + catch(HttpException &e) { + client->putLine(e.statusLine()); + client->putLine("Connection: close\n"); + client->putLine(e.headers()); + if(want_html) { + client->putLine("Content-Type: text/html; charset=utf-8\n\n"); + client->putLine("" + e.what() + "

" + e.what() + "

"); + } else if (want_json) { + client->putLine("Content-Type: application/json\n\n"); + client->putLine(returnJSONError(e.what())); + } else { + client->putLine("Content-Type: text/plain; charset=utf-8\n\n"); + client->putLine(e.what()); + } } + + client->close(); + delete client; + client=0; + return 0; } +catch(SessionTimeoutException &e) { + // L<(d_status_code) + " " + d_reason_phrase + "\n"; + } + + virtual std::string headers() const { + return ""; + } + + virtual std::string what() const { + return d_reason_phrase; + } + +private: + int d_status_code; + std::string d_reason_phrase; +}; + +class HttpBadRequestException : public HttpException { +public: + HttpBadRequestException() : HttpException(400, "Bad Request") { }; +}; + +class HttpUnauthorizedException : public HttpException { +public: + HttpUnauthorizedException() : HttpException(401, "Unauthorized") { }; + + std::string headers() const { + return "WWW-Authenticate: Basic realm=\"PowerDNS\"\n"; + } +}; + +class HttpNotFoundException : public HttpException { +public: + HttpNotFoundException() : HttpException(404, "Not Found") { }; +}; + +class HttpMethodNotAllowedException : public HttpException { +public: + HttpMethodNotAllowedException() : HttpException(405, "Method Not Allowed") { }; +}; + class WebServer { public: diff --git a/pdns/ws.cc b/pdns/ws.cc index d7ea5cebb9..4b17211f8b 100644 --- a/pdns/ws.cc +++ b/pdns/ws.cc @@ -273,7 +273,7 @@ static int intFromJson(const Value& val) { } else if (val.IsString()) { return atoi(val.GetString()); } else { - throw Exception("Value not an Integer"); + throw PDNSException("Value not an Integer"); } } @@ -429,9 +429,8 @@ static string jsonDispatch(const string& method, const string& post, varmap_t& v return returnJSONObject(object); } else if(command == "pdns-control") { - // TODO: turn this into a 405 if(method!="POST") - return returnJSONError("pdns-control requires a POST"); + throw HttpMethodNotAllowedException(); // cout<<"post: "<(post.c_str()).HasParseError()) @@ -560,8 +559,7 @@ static string jsonDispatch(const string& method, const string& post, varmap_t& v map success; // empty success object return returnJSONObject(success); } else { - // TODO: turn this into a 405 - return returnJSONError("Method not allowed"); + throw HttpMethodNotAllowedException(); } } else if(command=="log-grep") { -- 2.47.2