ext/common/agents/HelperAgent/AdminServer.h in passenger-5.0.4 vs ext/common/agents/HelperAgent/AdminServer.h in passenger-5.0.5

- old
+ new

@@ -1,8 +1,8 @@ /* * Phusion Passenger - https://www.phusionpassenger.com/ - * Copyright (c) 2013-2014 Phusion + * Copyright (c) 2013-2015 Phusion * * "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,11 @@ * THE SOFTWARE. */ #ifndef _PASSENGER_SERVER_AGENT_ADMIN_SERVER_H_ #define _PASSENGER_SERVER_AGENT_ADMIN_SERVER_H_ +#include <boost/regex.hpp> #include <oxt/thread.hpp> #include <sstream> #include <string> #include <cstring> @@ -73,10 +74,16 @@ private: typedef ServerKit::HttpServer<AdminServer, ServerKit::HttpClient<Request> > ParentClass; typedef ServerKit::HttpClient<Request> Client; typedef ServerKit::HeaderTable HeaderTable; + boost::regex serverConnectionPath; + + bool regex_match(const StaticString &str, const boost::regex &e) const { + return boost::regex_match(str.data(), str.data() + str.size(), e); + } + bool parseAuthorizationHeader(Request *req, string &username, string &password) const { const LString *auth = req->headers.lookup("authorization"); @@ -124,10 +131,23 @@ return auth != NULL && auth->level >= level && constantTimeCompare(password, auth->password); } + int extractThreadNumberFromClientName(const string &clientName) const { + boost::smatch results; + boost::regex re("^([0-9]+)-.*"); + + if (!boost::regex_match(clientName, results, re)) { + return -1; + } + if (results.size() != 2) { + return -1; + } + return stringToUint(results.str(1)); + } + static VariantMap parseQueryString(const StaticString &query) { VariantMap params; const char *pos = query.data(); const char *end = query.data() + query.size(); @@ -153,10 +173,56 @@ } return params; } + static void disconnectClient(RequestHandler *rh, string clientName) { + rh->disconnect(clientName); + } + + void processServerConnectionOperation(Client *client, Request *req) { + if (!authorize(client, req, FULL)) { + respondWith401(client, req); + } else if (req->method == HTTP_DELETE) { + StaticString path = req->getPathWithoutQueryString(); + boost::smatch results; + + boost::regex_match(path.toString(), results, serverConnectionPath); + if (results.size() != 2) { + endAsBadRequest(&client, &req, "Invalid URI"); + return; + } + + int threadNumber = extractThreadNumberFromClientName(results.str(1)); + P_WARN(results.str(1)); + P_WARN(threadNumber); + if (threadNumber < 1 || (unsigned int) threadNumber > requestHandlers.size()) { + HeaderTable headers; + headers.insert(req->pool, "content-type", "application/json"); + writeSimpleResponse(client, 400, &headers, + "{ \"status\": \"error\", \"reason\": \"Invalid thread number\" }"); + if (!req->ended()) { + endRequest(&client, &req); + } + return; + } + + requestHandlers[threadNumber - 1]->getContext()->libev->runLater(boost::bind( + disconnectClient, requestHandlers[threadNumber - 1], results.str(1))); + + HeaderTable headers; + headers.insert(req->pool, "content-type", "application/json"); + writeSimpleResponse(client, 200, &headers, + "{ \"status\": \"ok\" }"); + if (!req->ended()) { + endRequest(&client, &req); + } + } else { + respondWith405(client, req); + } + } + static void inspectRequestHandlerState(RequestHandler *rh, Json::Value *json) { *json = rh->inspectStateAsJson(); } void processServerStatus(Client *client, Request *req) { @@ -394,19 +460,23 @@ respondWith401(client, req); } HeaderTable headers; string logFile = getLogFile(); + string fileDescriptorLogFile = getFileDescriptorLogFile(); headers.insert(req->pool, "content-type", "application/json"); Json::Value doc; requestHandlers[0]->getContext()->libev->runSync(boost::bind( getRequestHandlerConfig, requestHandlers[0], &doc)); doc["log_level"] = getLogLevel(); if (!logFile.empty()) { doc["log_file"] = logFile; } + if (!fileDescriptorLogFile.empty()) { + doc["file_descriptor_log_file"] = fileDescriptorLogFile; + } writeSimpleResponse(client, 200, &headers, psg_pstrdup(req->pool, doc.toStyledString())); if (!req->ended()) { endRequest(&client, &req); @@ -436,15 +506,31 @@ if (json.isMember("log_level")) { setLogLevel(json["log_level"].asInt()); } if (json.isMember("log_file")) { - if (!setLogFile(json["log_file"].asCString())) { - int e = errno; + string logFile = json["log_file"].asString(); + try { + logFile = absolutizePath(logFile); + } catch (const SystemException &e) { unsigned int bufsize = 1024; char *message = (char *) psg_pnalloc(req->pool, bufsize); snprintf(message, bufsize, "{ \"status\": \"error\", " + "\"message\": \"Cannot absolutize log file filename: %s\" }", + e.what()); + writeSimpleResponse(client, 500, &headers, message); + if (!req->ended()) { + endRequest(&client, &req); + } + return; + } + + int e; + if (!setLogFile(logFile, &e)) { + unsigned int bufsize = 1024; + char *message = (char *) psg_pnalloc(req->pool, bufsize); + snprintf(message, bufsize, "{ \"status\": \"error\", " "\"message\": \"Cannot open log file: %s (errno=%d)\" }", strerror(e), e); writeSimpleResponse(client, 500, &headers, message); if (!req->ended()) { endRequest(&client, &req); @@ -466,37 +552,59 @@ void processReopenLogs(Client *client, Request *req) { if (req->method != HTTP_POST) { respondWith405(client, req); } else if (authorize(client, req, FULL)) { + int e; HeaderTable headers; headers.insert(req->pool, "content-type", "application/json"); string logFile = getLogFile(); if (logFile.empty()) { writeSimpleResponse(client, 500, &headers, "{ \"status\": \"error\", " "\"code\": \"NO_LOG_FILE\", " "\"message\": \"" PROGRAM_NAME " was not configured with a log file.\" }\n"); - } else { - if (!setLogFile(logFile.c_str())) { - int e = errno; + if (!req->ended()) { + endRequest(&client, &req); + } + return; + } + + if (!setLogFile(logFile, &e)) { + unsigned int bufsize = 1024; + char *message = (char *) psg_pnalloc(req->pool, bufsize); + snprintf(message, bufsize, "{ \"status\": \"error\", " + "\"code\": \"LOG_FILE_OPEN_ERROR\", " + "\"message\": \"Cannot reopen log file %s: %s (errno=%d)\" }", + logFile.c_str(), strerror(e), e); + writeSimpleResponse(client, 500, &headers, message); + if (!req->ended()) { + endRequest(&client, &req); + } + return; + } + P_NOTICE("Log file reopened."); + + if (hasFileDescriptorLogFile()) { + if (!setFileDescriptorLogFile(getFileDescriptorLogFile(), &e)) { unsigned int bufsize = 1024; char *message = (char *) psg_pnalloc(req->pool, bufsize); snprintf(message, bufsize, "{ \"status\": \"error\", " - "\"code\": \"LOG_FILE_OPEN_ERROR\", " - "\"message\": \"Cannot reopen log file: %s (errno=%d)\" }", - strerror(e), e); + "\"code\": \"FD_LOG_FILE_OPEN_ERROR\", " + "\"message\": \"Cannot reopen file descriptor log file %s: %s (errno=%d)\" }", + getFileDescriptorLogFile().c_str(), strerror(e), e); writeSimpleResponse(client, 500, &headers, message); if (!req->ended()) { endRequest(&client, &req); } return; } - P_NOTICE("Log file reopened."); - writeSimpleResponse(client, 200, &headers, "{ \"status\": \"ok\" }\n"); + P_NOTICE("File descriptor log file reopened."); } + writeSimpleResponse(client, 200, &headers, "{ \"status\": \"ok\" }\n"); + if (!req->ended()) { endRequest(&client, &req); } } else { respondWith401(client, req); @@ -566,10 +674,12 @@ " " << StaticString(req->path.start->data, req->path.size)); try { if (path == P_STATIC_STRING("/server.json")) { processServerStatus(client, req); + } else if (regex_match(path, serverConnectionPath)) { + processServerConnectionOperation(client, req); } else if (path == P_STATIC_STRING("/pool.xml")) { processPoolStatusXml(client, req); } else if (path == P_STATIC_STRING("/pool.txt")) { processPoolStatusTxt(client, req); } else if (path == P_STATIC_STRING("/pool/restart_app_group.json")) { @@ -663,9 +773,10 @@ EventFd *exitEvent; vector<Authorization> authorizations; AdminServer(ServerKit::Context *context) : ParentClass(context), + serverConnectionPath("^/server/(.+)\\.json$"), exitEvent(NULL) { } virtual StaticString getServerName() const { return P_STATIC_STRING("AdminServer");