17 #include <boost/algorithm/string.hpp>
18 #include <boost/bind.hpp>
19 #include <boost/filesystem.hpp>
20 #include <boost/foreach.hpp>
21 #include <boost/lexical_cast.hpp>
22 #include <boost/mem_fn.hpp>
23 #include <boost/thread/locks.hpp>
24 #include <gutil/strings/substitute.h>
30 #include <mustache/mustache.h>
31 #include <rapidjson/stringbuffer.h>
32 #include <rapidjson/prettywriter.h>
48 using boost::algorithm::is_any_of;
49 using boost::algorithm::split;
50 using boost::algorithm::to_lower;
51 using boost::filesystem::exists;
52 using boost::upgrade_to_unique_lock;
53 using namespace google;
54 using namespace strings;
55 using namespace rapidjson;
56 using namespace mustache;
60 DEFINE_int32(webserver_port, 25000,
"Port to start debug webserver on");
62 "Interface to start debug webserver on. If blank, webserver binds to 0.0.0.0");
64 "Files under <webserver_doc_root>/www are accessible via the debug webserver. "
65 "Defaults to $IMPALA_HOME, or if $IMPALA_HOME is not set, disables the document "
68 "If true, webserver may serve static files from the webserver_doc_root");
71 "The location of the debug webserver's SSL certificate file, in .pem format. If "
72 "empty, webserver SSL support is not enabled");
74 "Domain used for debug webserver authentication");
76 "(Optional) Location of .htpasswd file containing user names and hashed passwords for"
77 " debug webserver authentication");
97 char* impala_home = getenv(
"IMPALA_HOME");
98 if (impala_home == NULL) {
105 string* str =
new string(ss.str());
111 const char* Webserver::ENABLE_RAW_JSON_KEY =
"__raw__";
127 static const string RESPONSE_TEMPLATE =
"HTTP/1.1 $0 $1\r\n"
128 "Content-Type: text/$2\r\n"
129 "Content-Length: %d\r\n"
132 return Substitute(RESPONSE_TEMPLATE, response, response ==
OK ?
"OK" :
"Not found",
133 content_type ==
HTML ?
"html" :
"plain");
136 Webserver::Webserver()
139 "error.tmpl", false)) {
141 FLAGS_webserver_interface.empty() ?
"0.0.0.0" : FLAGS_webserver_interface,
142 FLAGS_webserver_port);
148 "error.tmpl", false)) {
158 document->AddMember(
"version", version, document->GetAllocator());
160 document->AddMember(
"cpu_info", cpu_info, document->GetAllocator());
162 document->AddMember(
"mem_info", mem_info, document->GetAllocator());
164 document->AddMember(
"disk_info", disk_info, document->GetAllocator());
166 document->AddMember(
"os_info", os_info, document->GetAllocator());
168 document->GetAllocator());
169 document->AddMember(
"process_state_info", process_state_info,
170 document->GetAllocator());
174 ArgumentMap::const_iterator it = args.find(
ERROR_KEY);
175 if (it == args.end())
return;
177 Value error(it->second.c_str(), document->GetAllocator());
178 document->AddMember(
"error", error, document->GetAllocator());
182 vector<string> arg_pairs;
183 split(arg_pairs, args, is_any_of(
"&"));
185 BOOST_FOREACH(
const string& arg_pair, arg_pairs) {
186 vector<string> key_value;
187 split(key_value, arg_pair, is_any_of(
"="));
188 if (key_value.empty())
continue;
191 if (!
UrlDecode(key_value[0], &key))
continue;
193 if (!
UrlDecode((key_value.size() >= 2 ? key_value[1] :
""), &value))
continue;
195 (*output)[key] = value;
200 return !FLAGS_webserver_certificate_file.empty();
206 stringstream listening_spec;
210 LOG(INFO) <<
"Webserver: Enabling HTTPS support";
212 listening_spec <<
"s";
214 string listening_str = listening_spec.str();
215 vector<const char*> options;
217 if (!FLAGS_webserver_doc_root.empty() && FLAGS_enable_webserver_doc_root) {
218 LOG(INFO) <<
"Document root: " << FLAGS_webserver_doc_root;
219 options.push_back(
"document_root");
220 options.push_back(FLAGS_webserver_doc_root.c_str());
222 LOG(INFO)<<
"Document root disabled";
226 options.push_back(
"ssl_certificate");
227 options.push_back(FLAGS_webserver_certificate_file.c_str());
230 if (!FLAGS_webserver_authentication_domain.empty()) {
231 options.push_back(
"authentication_domain");
232 options.push_back(FLAGS_webserver_authentication_domain.c_str());
235 if (!FLAGS_webserver_password_file.empty()) {
238 if (!exists(FLAGS_webserver_password_file)) {
240 ss <<
"Webserver: Password file does not exist: " << FLAGS_webserver_password_file;
243 LOG(INFO) <<
"Webserver: Password file is " << FLAGS_webserver_password_file;
244 options.push_back(
"global_auth_file");
245 options.push_back(FLAGS_webserver_password_file.c_str());
248 options.push_back(
"listening_ports");
249 options.push_back(listening_str.c_str());
252 options.push_back(NULL);
257 sighandler_t sig_chld = signal(SIGCHLD, SIG_DFL);
259 sq_callbacks callbacks;
260 memset(&callbacks, 0,
sizeof(callbacks));
268 context_ = sq_start(&callbacks, reinterpret_cast<void*>(
this), &options[0]);
271 signal(SIGCHLD, sig_chld);
274 stringstream error_msg;
275 error_msg <<
"Webserver: Could not start on address " <<
http_address_;
276 return Status(error_msg.str());
284 LOG(INFO) <<
"Webserver started";
296 DCHECK(document != NULL);
297 Value obj(kObjectType);
298 obj.AddMember(
"process-name", google::ProgramInvocationShortName(),
299 document->GetAllocator());
301 Value lst(kArrayType);
302 BOOST_FOREACH(
const UrlHandlerMap::value_type& handler,
url_handlers_) {
303 if (handler.second.is_on_nav_bar()) {
304 Value obj(kObjectType);
305 obj.AddMember(
"link", handler.first.c_str(), document->GetAllocator());
306 obj.AddMember(
"title", handler.first.c_str(), document->GetAllocator());
307 lst.PushBack(obj, document->GetAllocator());
311 obj.AddMember(
"navbar", lst, document->GetAllocator());
316 const char* message) {
317 if (message != NULL) {
318 LOG(INFO) <<
"Webserver: " << message;
324 struct sq_request_info* request_info = sq_get_request_info(connection);
330 struct sq_request_info* request_info) {
331 if (!FLAGS_webserver_doc_root.empty() && FLAGS_enable_webserver_doc_root) {
333 VLOG(2) <<
"HTTP File access: " << request_info->uri;
340 map<string, string> arguments;
341 if (request_info->query_string != NULL) {
346 UrlHandlerMap::const_iterator it =
url_handlers_.find(request_info->uri);
352 arguments[
ERROR_KEY] = Substitute(
"No URI handler for '$0'", request_info->uri);
355 url_handler = &it->second;
362 document.SetObject();
367 bool raw_json = (arguments.find(
"json") != arguments.end());
368 url_handler->
callback()(arguments, &document);
373 PrettyWriter<StringBuffer> writer(strbuf);
374 document.Accept(writer);
375 output << strbuf.GetString();
376 content_type =
PLAIN;
378 if (arguments.find(
"raw") != arguments.end()) {
382 content_type =
PLAIN;
385 const string& full_template_path =
386 Substitute(
"$0/$1/$2", FLAGS_webserver_doc_root,
DOC_FOLDER,
388 ifstream tmpl(full_template_path.c_str());
389 if (!tmpl.is_open()) {
390 output <<
"Could not open template: " << full_template_path;
391 content_type =
PLAIN;
394 buffer << tmpl.rdbuf();
395 RenderTemplate(buffer.str(), Substitute(
"$0/", FLAGS_webserver_doc_root), document,
400 VLOG(3) <<
"Rendering page " << request_info->uri <<
" took "
403 const string& str = output.str();
405 sq_printf(connection, headers.c_str(), (int)str.length());
408 sq_write(connection, str.c_str(), str.length());
413 const string& template_filename,
const UrlCallback& callback,
bool is_on_nav_bar) {
415 upgrade_to_unique_lock<shared_mutex> writer_lock(lock);
417 <<
"Duplicate Url handler for: " << path;
420 make_pair(path,
UrlHandler(callback, template_filename, is_on_nav_bar)));
struct sq_context * context_
Handle to Squeasel context; owned and freed by Squeasel internally.
static const char * ENABLE_RAW_JSON_KEY
string path("/usr/lib/sasl2:/usr/lib64/sasl2:/usr/local/lib/sasl2:/usr/lib/x86_64-linux-gnu/sasl2")
const char * GetDefaultDocumentRoot()
static const int DOC_FOLDER_LEN
boost::function< void(const ArgumentMap &args, rapidjson::Document *json)> UrlCallback
UrlHandler error_handler_
Catch-all handler for error messages.
string GetVersionString(bool compact)
Returns "<program short name> version <GetBuildVersion(compact)>".
static const uint32_t PROCESSING_COMPLETE
DEFINE_string(webserver_interface,"","Interface to start debug webserver on. If blank, webserver binds to 0.0.0.0")
Webserver()
Uses FLAGS_webserver_{port, interface}.
void Stop()
Stops the webserver synchronously.
void ErrorHandler(const ArgumentMap &args, rapidjson::Document *document)
Called when an error is encountered, e.g. when a handler for a URI cannot be found.
void RegisterUrlCallback(const std::string &path, const std::string &template_filename, const UrlCallback &callback, bool is_on_nav_bar=true)
Only one callback may be registered per URL.
TNetworkAddress MakeNetworkAddress(const string &hostname, int port)
static const char * COMMON_JSON_KEY
bool UrlDecode(const string &in, string *out, bool hive_compat)
static std::string Print(bool value, TUnit::type ignored, bool verbose=false)
std::map< std::string, std::string > ArgumentMap
bool IsSecure() const
True if serving all traffic over SSL, false otherwise.
TNetworkAddress http_address_
The address of the interface on which to run this webserver.
static int LogMessageCallbackStatic(const struct sq_connection *connection, const char *message)
Squeasel callback for log events. Returns squeasel success code.
static std::string DebugString()
DEFINE_int32(webserver_port, 25000,"Port to start debug webserver on")
static const char * DOC_FOLDER
std::string DebugString(const T &val)
const UrlCallback & callback() const
void RootHandler(const ArgumentMap &args, rapidjson::Document *document)
Registered to handle "/", populates document with various system-wide information.
static std::string DebugString()
static std::string DebugString()
uint64_t ElapsedTime() const
Returns time in nanosecond.
void GetCommonJson(rapidjson::Document *document)
UrlHandlerMap url_handlers_
DEFINE_bool(enable_webserver_doc_root, true,"If true, webserver may serve static files from the webserver_doc_root")
static std::string DebugString()
string BuildHeaderString(ResponseCode response, ContentType content_type)
static const char * ERROR_KEY
void BuildArgumentMap(const std::string &args, ArgumentMap *output)
const std::string & template_filename() const
boost::shared_mutex url_handlers_lock_
Lock guarding the path_handlers_ map.
static int BeginRequestCallbackStatic(struct sq_connection *connection)
static const uint32_t NOT_PROCESSED
int BeginRequestCallback(struct sq_connection *connection, struct sq_request_info *request_info)
Dispatch point for all incoming requests. Returns squeasel success code.