/Users/deen/code/yugabyte-db/src/yb/server/webserver.cc
| Line | Count | Source (jump to first uncovered line) | 
| 1 |  | // Licensed to the Apache Software Foundation (ASF) under one | 
| 2 |  | // or more contributor license agreements.  See the NOTICE file | 
| 3 |  | // distributed with this work for additional information | 
| 4 |  | // regarding copyright ownership.  The ASF licenses this file | 
| 5 |  | // to you under the Apache License, Version 2.0 (the | 
| 6 |  | // "License"); you may not use this file except in compliance | 
| 7 |  | // with the License.  You may obtain a copy of the License at | 
| 8 |  | // | 
| 9 |  | //   http://www.apache.org/licenses/LICENSE-2.0 | 
| 10 |  | // | 
| 11 |  | // Unless required by applicable law or agreed to in writing, | 
| 12 |  | // software distributed under the License is distributed on an | 
| 13 |  | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | 
| 14 |  | // KIND, either express or implied.  See the License for the | 
| 15 |  | // specific language governing permissions and limitations | 
| 16 |  | // under the License. | 
| 17 |  | // | 
| 18 |  | // The following only applies to changes made to this file as part of YugaByte development. | 
| 19 |  | // | 
| 20 |  | // Portions Copyright (c) YugaByte, Inc. | 
| 21 |  | // | 
| 22 |  | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except | 
| 23 |  | // in compliance with the License.  You may obtain a copy of the License at | 
| 24 |  | // | 
| 25 |  | // http://www.apache.org/licenses/LICENSE-2.0 | 
| 26 |  | // | 
| 27 |  | // Unless required by applicable law or agreed to in writing, software distributed under the License | 
| 28 |  | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express | 
| 29 |  | // or implied.  See the License for the specific language governing permissions and limitations | 
| 30 |  | // under the License. | 
| 31 |  | // | 
| 32 |  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
| 33 |  | // you may not use this file except in compliance with the License. | 
| 34 |  | // You may obtain a copy of the License at | 
| 35 |  | // | 
| 36 |  | // http://www.apache.org/licenses/LICENSE-2.0 | 
| 37 |  | // | 
| 38 |  | // Unless required by applicable law or agreed to in writing, software | 
| 39 |  | // distributed under the License is distributed on an "AS IS" BASIS, | 
| 40 |  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
| 41 |  | // See the License for the specific language governing permissions and | 
| 42 |  | // limitations under the License. | 
| 43 |  |  | 
| 44 |  | #include "yb/server/webserver.h" | 
| 45 |  |  | 
| 46 |  | #include <stdio.h> | 
| 47 |  |  | 
| 48 |  | #include <algorithm> | 
| 49 |  | #include <functional> | 
| 50 |  | #include <map> | 
| 51 |  | #include <string> | 
| 52 |  | #include <type_traits> | 
| 53 |  | #include <vector> | 
| 54 |  |  | 
| 55 |  | #include <boost/algorithm/string.hpp> | 
| 56 |  | #include <cds/init.h> | 
| 57 |  | #include <glog/logging.h> | 
| 58 |  | #include <squeasel.h> | 
| 59 |  |  | 
| 60 |  | #include "yb/gutil/dynamic_annotations.h" | 
| 61 |  | #include "yb/gutil/map-util.h" | 
| 62 |  | #include "yb/gutil/stl_util.h" | 
| 63 |  | #include "yb/gutil/stringprintf.h" | 
| 64 |  | #include "yb/gutil/strings/join.h" | 
| 65 |  | #include "yb/gutil/strings/numbers.h" | 
| 66 |  | #include "yb/gutil/strings/split.h" | 
| 67 |  | #include "yb/gutil/strings/stringpiece.h" | 
| 68 |  | #include "yb/gutil/strings/strip.h" | 
| 69 |  |  | 
| 70 |  | #include "yb/util/env.h" | 
| 71 |  | #include "yb/util/flag_tags.h" | 
| 72 |  | #include "yb/util/net/net_util.h" | 
| 73 |  | #include "yb/util/net/sockaddr.h" | 
| 74 |  | #include "yb/util/scope_exit.h" | 
| 75 |  | #include "yb/util/shared_lock.h" | 
| 76 |  | #include "yb/util/status.h" | 
| 77 |  | #include "yb/util/status_format.h" | 
| 78 |  | #include "yb/util/url-coding.h" | 
| 79 |  | #include "yb/util/zlib.h" | 
| 80 |  |  | 
| 81 |  | #if defined(__APPLE__) | 
| 82 |  | typedef sig_t sighandler_t; | 
| 83 |  | #endif | 
| 84 |  |  | 
| 85 |  | DEFINE_int32(webserver_max_post_length_bytes, 1024 * 1024, | 
| 86 |  |              "The maximum length of a POST request that will be accepted by " | 
| 87 |  |              "the embedded web server."); | 
| 88 |  | TAG_FLAG(webserver_max_post_length_bytes, advanced); | 
| 89 |  | TAG_FLAG(webserver_max_post_length_bytes, runtime); | 
| 90 |  |  | 
| 91 |  | DEFINE_int32(webserver_zlib_compression_level, 1, | 
| 92 |  |              "The zlib compression level." | 
| 93 |  |              "Lower compression levels result in faster execution, but less compression"); | 
| 94 |  | TAG_FLAG(webserver_zlib_compression_level, advanced); | 
| 95 |  | TAG_FLAG(webserver_zlib_compression_level, runtime); | 
| 96 |  |  | 
| 97 |  | DEFINE_uint64(webserver_compression_threshold_kb, 4, | 
| 98 |  |               "The threshold of response size above which compression is performed." | 
| 99 |  |               "Default value is 4KB"); | 
| 100 |  | TAG_FLAG(webserver_compression_threshold_kb, advanced); | 
| 101 |  | TAG_FLAG(webserver_compression_threshold_kb, runtime); | 
| 102 |  |  | 
| 103 |  | namespace yb { | 
| 104 |  |  | 
| 105 |  | using std::string; | 
| 106 |  | using std::stringstream; | 
| 107 |  | using std::vector; | 
| 108 |  | using std::make_pair; | 
| 109 |  |  | 
| 110 |  | using namespace std::placeholders; | 
| 111 |  |  | 
| 112 |  | Webserver::Webserver(const WebserverOptions& opts, const std::string& server_name) | 
| 113 |  |   : opts_(opts), | 
| 114 |  |     context_(nullptr), | 
| 115 | 28.4k |     server_name_(server_name) { | 
| 116 | 28.4k |   string host = opts.bind_interface.empty() ? "0.0.0.0"8: opts.bind_interface28.3k; | 
| 117 | 28.4k |   http_address_ = host + ":" + std::to_string(opts.port); | 
| 118 | 28.4k | } | 
| 119 |  |  | 
| 120 | 257 | Webserver::~Webserver() { | 
| 121 | 257 |   Stop(); | 
| 122 | 257 |   STLDeleteValues(&path_handlers_); | 
| 123 | 257 | } | 
| 124 |  |  | 
| 125 | 6 | void Webserver::RootHandler(const Webserver::WebRequest& args, Webserver::WebResponse* resp) { | 
| 126 | 6 | } | 
| 127 |  |  | 
| 128 | 635 | void Webserver::BuildArgumentMap(const string& args, ArgumentMap* output) { | 
| 129 | 635 |   vector<GStringPiece> arg_pairs = strings::Split(args, "&"); | 
| 130 |  |  | 
| 131 | 636 |   for (const GStringPiece& arg_pair : arg_pairs) { | 
| 132 | 636 |     vector<GStringPiece> key_value = strings::Split(arg_pair, "="); | 
| 133 | 636 |     if (key_value.empty()) continue0; | 
| 134 |  |  | 
| 135 | 636 |     string key; | 
| 136 | 636 |     if (!UrlDecode(key_value[0].ToString(), &key)) continue0; | 
| 137 | 636 |     string value; | 
| 138 | 636 |     if (!UrlDecode((key_value.size() >= 2 ? key_value[1].ToString()634: ""2), &value)) continue0; | 
| 139 | 636 |     boost::to_lower(key); | 
| 140 | 636 |     (*output)[key] = value; | 
| 141 | 636 |   } | 
| 142 | 635 | } | 
| 143 |  |  | 
| 144 | 56.3k | bool Webserver::IsSecure() const { | 
| 145 | 56.3k |   return !opts_.certificate_file.empty(); | 
| 146 | 56.3k | } | 
| 147 |  |  | 
| 148 | 27.8k | Status Webserver::BuildListenSpec(string* spec) const { | 
| 149 | 27.8k |   std::vector<Endpoint> endpoints; | 
| 150 | 27.8k |   RETURN_NOT_OK(ParseAddressList(http_address_, 80, &endpoints)); | 
| 151 | 27.8k |   if (endpoints.empty()) { | 
| 152 | 0 |     return STATUS_FORMAT( | 
| 153 | 0 |       ConfigurationError, | 
| 154 | 0 |       "No IPs available for address $0", http_address_); | 
| 155 | 0 |   } | 
| 156 | 27.8k |   std::vector<string> parts; | 
| 157 | 28.5k |   for (const auto& endpoint : endpoints) { | 
| 158 |  |     // Mongoose makes sockets with 's' suffixes accept SSL traffic only | 
| 159 | 28.5k |     parts.push_back(ToString(endpoint) + (IsSecure() ? "s"0: "")); | 
| 160 | 28.5k |   } | 
| 161 |  |  | 
| 162 | 27.8k |   JoinStrings(parts, ",", spec); | 
| 163 | 27.8k |   LOG(INFO) << "Webserver listen spec is " << *spec; | 
| 164 | 27.8k |   return Status::OK(); | 
| 165 | 27.8k | } | 
| 166 |  |  | 
| 167 | 27.8k | Status Webserver::Start() { | 
| 168 | 27.8k |   LOG(INFO) << "Starting webserver on " << http_address_; | 
| 169 |  |  | 
| 170 | 27.8k |   vector<const char*> options; | 
| 171 |  |  | 
| 172 | 27.8k |   if (static_pages_available()) { | 
| 173 | 27.8k |     LOG(INFO) << "Document root: " << opts_.doc_root; | 
| 174 | 27.8k |     options.push_back("document_root"); | 
| 175 | 27.8k |     options.push_back(opts_.doc_root.c_str()); | 
| 176 | 27.8k |     options.push_back("enable_directory_listing"); | 
| 177 | 27.8k |     options.push_back("no"); | 
| 178 | 27.8k |   } else { | 
| 179 | 0 |     LOG(INFO)<< "Document root disabled"; | 
| 180 | 0 |   } | 
| 181 |  |  | 
| 182 | 27.8k |   if (IsSecure()) { | 
| 183 | 0 |     LOG(INFO) << "Webserver: Enabling HTTPS support"; | 
| 184 | 0 |     options.push_back("ssl_certificate"); | 
| 185 | 0 |     options.push_back(opts_.certificate_file.c_str()); | 
| 186 | 0 |   } | 
| 187 |  |  | 
| 188 | 27.8k |   if (!opts_.authentication_domain.empty()) { | 
| 189 | 0 |     options.push_back("authentication_domain"); | 
| 190 | 0 |     options.push_back(opts_.authentication_domain.c_str()); | 
| 191 | 0 |   } | 
| 192 |  |  | 
| 193 | 27.8k |   if (!opts_.password_file.empty()) { | 
| 194 |  |     // Mongoose doesn't log anything if it can't stat the password file (but will if it | 
| 195 |  |     // can't open it, which it tries to do during a request) | 
| 196 | 0 |     if (!Env::Default()->FileExists(opts_.password_file)) { | 
| 197 | 0 |       stringstream ss; | 
| 198 | 0 |       ss << "Webserver: Password file does not exist: " << opts_.password_file; | 
| 199 | 0 |       return STATUS(InvalidArgument, ss.str()); | 
| 200 | 0 |     } | 
| 201 | 0 |     LOG(INFO) << "Webserver: Password file is " << opts_.password_file; | 
| 202 | 0 |     options.push_back("global_passwords_file"); | 
| 203 | 0 |     options.push_back(opts_.password_file.c_str()); | 
| 204 | 0 |   } | 
| 205 |  |  | 
| 206 | 27.8k |   options.push_back("listening_ports"); | 
| 207 | 27.8k |   string listening_str; | 
| 208 | 27.8k |   RETURN_NOT_OK(BuildListenSpec(&listening_str)); | 
| 209 | 27.8k |   options.push_back(listening_str.c_str()); | 
| 210 |  |  | 
| 211 |  |   // Num threads | 
| 212 | 27.8k |   options.push_back("num_threads"); | 
| 213 | 27.8k |   string num_threads_str = SimpleItoa(opts_.num_worker_threads); | 
| 214 | 27.8k |   options.push_back(num_threads_str.c_str()); | 
| 215 |  |  | 
| 216 |  |   // Options must be a NULL-terminated list | 
| 217 | 27.8k |   options.push_back(nullptr); | 
| 218 |  |  | 
| 219 |  |   // mongoose ignores SIGCHLD and we need it to run kinit. This means that since | 
| 220 |  |   // mongoose does not reap its own children CGI programs must be avoided. | 
| 221 |  |   // Save the signal handler so we can restore it after mongoose sets it to be ignored. | 
| 222 | 27.8k |   sighandler_t sig_chld = signal(SIGCHLD, SIG_DFL); | 
| 223 |  |  | 
| 224 | 27.8k |   sq_callbacks callbacks; | 
| 225 | 27.8k |   memset(&callbacks, 0, sizeof(callbacks)); | 
| 226 | 27.8k |   callbacks.begin_request = &Webserver::BeginRequestCallbackStatic; | 
| 227 | 27.8k |   callbacks.log_message = &Webserver::LogMessageCallbackStatic; | 
| 228 | 27.8k |   callbacks.enter_worker_thread = &Webserver::EnterWorkerThreadCallbackStatic; | 
| 229 | 27.8k |   callbacks.leave_worker_thread = &Webserver::LeaveWorkerThreadCallbackStatic; | 
| 230 |  |  | 
| 231 |  |   // To work around not being able to pass member functions as C callbacks, we store a | 
| 232 |  |   // pointer to this server in the per-server state, and register a static method as the | 
| 233 |  |   // default callback. That method unpacks the pointer to this and calls the real | 
| 234 |  |   // callback. | 
| 235 | 27.8k |   context_ = sq_start(&callbacks, reinterpret_cast<void*>(this), &options[0]); | 
| 236 |  |  | 
| 237 |  |   // Restore the child signal handler so wait() works properly. | 
| 238 | 27.8k |   signal(SIGCHLD, sig_chld); | 
| 239 |  |  | 
| 240 | 27.8k |   if (context_ == nullptr) { | 
| 241 | 94 |     stringstream error_msg; | 
| 242 | 94 |     error_msg << "Webserver: Could not start on address " << http_address_; | 
| 243 | 94 |     TryRunLsof(Endpoint(IpAddress(), opts_.port)); | 
| 244 | 94 |     return STATUS(NetworkError, error_msg.str()); | 
| 245 | 94 |   } | 
| 246 |  |  | 
| 247 | 27.7k |   PathHandlerCallback default_callback = | 
| 248 | 27.7k |       std::bind(boost::mem_fn(&Webserver::RootHandler), this, _1, _2); | 
| 249 |  |  | 
| 250 | 27.7k |   RegisterPathHandler("/", "Home", default_callback); | 
| 251 |  |  | 
| 252 | 27.7k |   std::vector<Endpoint> addrs; | 
| 253 | 27.7k |   RETURN_NOT_OK(GetBoundAddresses(&addrs)); | 
| 254 | 27.7k |   string bound_addresses_str; | 
| 255 | 28.4k |   for (const auto& addr : addrs) { | 
| 256 | 28.4k |     if (!bound_addresses_str.empty()) { | 
| 257 | 673 |       bound_addresses_str += ", "; | 
| 258 | 673 |     } | 
| 259 | 28.4k |     bound_addresses_str += "http://" + ToString(addr) + "/"; | 
| 260 | 28.4k |   } | 
| 261 |  |  | 
| 262 | 27.7k |   LOG(INFO) << "Webserver started. Bound to: " << bound_addresses_str; | 
| 263 | 27.7k |   return Status::OK(); | 
| 264 | 27.7k | } | 
| 265 |  |  | 
| 266 | 775 | void Webserver::Stop() { | 
| 267 | 775 |   std::lock_guard<std::mutex> lock_(stop_mutex_); | 
| 268 | 775 |   if (context_ != nullptr) { | 
| 269 | 185 |     sq_stop(context_); | 
| 270 | 185 |     context_ = nullptr; | 
| 271 | 185 |   } | 
| 272 | 775 | } | 
| 273 |  |  | 
| 274 | 17.4k | Status Webserver::GetInputHostPort(HostPort* hp) const { | 
| 275 | 17.4k |   std::vector<HostPort> parsed_hps; | 
| 276 | 17.4k |   RETURN_NOT_OK(HostPort::ParseStrings( | 
| 277 | 17.4k |     http_address_, | 
| 278 | 17.4k |     0 /* default port */, | 
| 279 | 17.4k |     &parsed_hps)); | 
| 280 |  |  | 
| 281 |  |   // Webserver always gets a single host:port specification from WebserverOptions. | 
| 282 | 17.4k |   DCHECK_EQ(parsed_hps.size(), 1); | 
| 283 | 17.4k |   if (parsed_hps.size() != 1) { | 
| 284 | 0 |     return STATUS(InvalidArgument, "Expected single host port in WebserverOptions host port"); | 
| 285 | 0 |   } | 
| 286 |  |  | 
| 287 | 17.4k |   *hp = parsed_hps[0]; | 
| 288 | 17.4k |   return Status::OK(); | 
| 289 | 17.4k | } | 
| 290 |  |  | 
| 291 | 32.5k | Status Webserver::GetBoundAddresses(std::vector<Endpoint>* addrs_ptr) const { | 
| 292 | 32.5k |   if (!context_) { | 
| 293 | 2 |     return STATUS(IllegalState, "Not started"); | 
| 294 | 2 |   } | 
| 295 |  |  | 
| 296 | 32.5k |   struct sockaddr_storage** sockaddrs = nullptr; | 
| 297 | 32.5k |   int num_addrs; | 
| 298 |  |  | 
| 299 | 32.5k |   if (sq_get_bound_addresses(context_, &sockaddrs, &num_addrs)) { | 
| 300 | 0 |     return STATUS(NetworkError, "Unable to get bound addresses from Mongoose"); | 
| 301 | 0 |   } | 
| 302 | 32.5k |   auto cleanup = ScopeExit([sockaddrs, num_addrs] { | 
| 303 | 32.5k |     if (!sockaddrs) { | 
| 304 | 0 |       return; | 
| 305 | 0 |     } | 
| 306 | 66.4k |     for (int i = 0; 32.5ki < num_addrs; ++i33.9k) { | 
| 307 | 33.9k |       free(sockaddrs[i]); | 
| 308 | 33.9k |     } | 
| 309 | 32.5k |     free(sockaddrs); | 
| 310 | 32.5k |   }); | 
| 311 | 32.5k |   auto& addrs = *addrs_ptr; | 
| 312 | 32.5k |   addrs.resize(num_addrs); | 
| 313 |  |  | 
| 314 | 66.4k |   for (int i = 0; i < num_addrs; i++33.9k) { | 
| 315 | 33.9k |     switch (sockaddrs[i]->ss_family) { | 
| 316 | 32.5k |       case AF_INET: { | 
| 317 | 32.5k |         sockaddr_in* addr = reinterpret_cast<struct sockaddr_in*>(sockaddrs[i]); | 
| 318 | 32.5k |         RSTATUS_DCHECK( | 
| 319 | 32.5k |             addrs[i].capacity() >= sizeof(*addr), IllegalState, "Unexpected size of struct"); | 
| 320 | 32.5k |         memcpy(addrs[i].data(), addr, sizeof(*addr)); | 
| 321 | 32.5k |         break; | 
| 322 | 0 |       } | 
| 323 | 1.34k |       case AF_INET6: { | 
| 324 | 1.34k |         sockaddr_in6* addr6 = reinterpret_cast<struct sockaddr_in6*>(sockaddrs[i]); | 
| 325 | 1.34k |         RSTATUS_DCHECK( | 
| 326 | 1.34k |             addrs[i].capacity() >= sizeof(*addr6), IllegalState, "Unexpected size of struct"); | 
| 327 | 1.34k |         memcpy(addrs[i].data(), addr6, sizeof(*addr6)); | 
| 328 | 1.34k |         break; | 
| 329 | 0 |       } | 
| 330 | 0 |       default: { | 
| 331 | 0 |         LOG(ERROR) << "Unexpected address family: " << sockaddrs[i]->ss_family; | 
| 332 | 0 |         RSTATUS_DCHECK(false, IllegalState, "Unexpected address family"); | 
| 333 | 0 |         break; | 
| 334 | 0 |       } | 
| 335 | 33.9k |     } | 
| 336 | 33.9k |   } | 
| 337 |  |  | 
| 338 | 32.5k |   return Status::OK(); | 
| 339 | 32.5k | } | 
| 340 |  | int Webserver::LogMessageCallbackStatic(const struct sq_connection* connection, | 
| 341 | 144 |                                         const char* message) { | 
| 342 | 144 |   if (message != nullptr) { | 
| 343 | 144 |     LOG(INFO) << "Webserver: " << message; | 
| 344 | 144 |     return 1; | 
| 345 | 144 |   } | 
| 346 | 0 |   return 0; | 
| 347 | 144 | } | 
| 348 |  |  | 
| 349 | 18.2k | int Webserver::BeginRequestCallbackStatic(struct sq_connection* connection) { | 
| 350 | 18.2k |   struct sq_request_info* request_info = sq_get_request_info(connection); | 
| 351 | 18.2k |   Webserver* instance = reinterpret_cast<Webserver*>(request_info->user_data); | 
| 352 | 18.2k |   return instance->BeginRequestCallback(connection, request_info); | 
| 353 | 18.2k | } | 
| 354 |  |  | 
| 355 |  | int Webserver::BeginRequestCallback(struct sq_connection* connection, | 
| 356 | 18.2k |                                     struct sq_request_info* request_info) { | 
| 357 | 18.2k |   PathHandler* handler; | 
| 358 | 18.2k |   { | 
| 359 | 18.2k |     SharedLock<std::shared_timed_mutex> lock(lock_); | 
| 360 | 18.2k |     PathHandlerMap::const_iterator it = path_handlers_.find(request_info->uri); | 
| 361 | 18.2k |     if (it == path_handlers_.end()) { | 
| 362 |  |       // Let Mongoose deal with this request; returning NULL will fall through | 
| 363 |  |       // to the default handler which will serve files. | 
| 364 | 3 |       if (static_pages_available()) { | 
| 365 | 3 |         VLOG(2) << "HTTP File access: " << request_info->uri0; | 
| 366 | 3 |         return 0; | 
| 367 | 3 |       } else { | 
| 368 | 0 |         sq_printf(connection, "HTTP/1.1 404 Not Found\r\n" | 
| 369 | 0 |                   "Content-Type: text/plain\r\n\r\n"); | 
| 370 | 0 |         sq_printf(connection, "No handler for URI %s\r\n\r\n", request_info->uri); | 
| 371 | 0 |         return 1; | 
| 372 | 0 |       } | 
| 373 | 3 |     } | 
| 374 | 18.2k |     handler = it->second; | 
| 375 | 18.2k |   } | 
| 376 |  |  | 
| 377 | 0 |   return RunPathHandler(*handler, connection, request_info); | 
| 378 | 18.2k | } | 
| 379 |  |  | 
| 380 |  | int Webserver::RunPathHandler(const PathHandler& handler, | 
| 381 |  |                               struct sq_connection* connection, | 
| 382 | 18.2k |                               struct sq_request_info* request_info) { | 
| 383 |  |   // Should we render with css styles? | 
| 384 | 18.2k |   bool use_style = true; | 
| 385 |  |  | 
| 386 | 18.2k |   WebRequest req; | 
| 387 | 18.2k |   req.redirect_uri = request_info->uri; | 
| 388 | 18.2k |   if (request_info->query_string != nullptr) { | 
| 389 | 635 |     req.query_string = request_info->query_string; | 
| 390 | 635 |     BuildArgumentMap(request_info->query_string, &req.parsed_args); | 
| 391 | 635 |   } | 
| 392 | 18.2k |   req.request_method = request_info->request_method; | 
| 393 | 18.2k |   if (req.request_method == "POST") { | 
| 394 | 6 |     const char* content_len_str = sq_get_header(connection, "Content-Length"); | 
| 395 | 6 |     int32_t content_len = 0; | 
| 396 | 6 |     if (content_len_str == nullptr || | 
| 397 | 6 |         !safe_strto32(content_len_str, &content_len)) { | 
| 398 | 0 |       sq_printf(connection, "HTTP/1.1 411 Length Required\r\n"); | 
| 399 | 0 |       return 1; | 
| 400 | 0 |     } | 
| 401 | 6 |     if (content_len > FLAGS_webserver_max_post_length_bytes) { | 
| 402 |  |       // TODO: for this and other HTTP requests, we should log the | 
| 403 |  |       // remote IP, etc. | 
| 404 | 1 |       LOG(WARNING) << "Rejected POST with content length " << content_len; | 
| 405 | 1 |       sq_printf(connection, "HTTP/1.1 413 Request Entity Too Large\r\n"); | 
| 406 | 1 |       return 1; | 
| 407 | 1 |     } | 
| 408 |  |  | 
| 409 | 5 |     char buf[8192]; | 
| 410 | 5 |     int rem = content_len; | 
| 411 | 39 |     while (rem > 0) { | 
| 412 | 34 |       int n = sq_read(connection, buf, std::min<int>(sizeof(buf), rem)); | 
| 413 | 34 |       if (n <= 0) { | 
| 414 | 0 |         LOG(WARNING) << "error reading POST data: expected " | 
| 415 | 0 |                      << content_len << " bytes but only read " | 
| 416 | 0 |                      << req.post_data.size(); | 
| 417 | 0 |         sq_printf(connection, "HTTP/1.1 500 Internal Server Error\r\n"); | 
| 418 | 0 |         return 1; | 
| 419 | 0 |       } | 
| 420 |  |  | 
| 421 | 34 |       req.post_data.append(buf, n); | 
| 422 | 34 |       rem -= n; | 
| 423 | 34 |     } | 
| 424 | 5 |   } | 
| 425 |  |  | 
| 426 | 18.2k |   if (!handler.is_styled() || ContainsKey(req.parsed_args, "raw")71) { | 
| 427 | 18.2k |     use_style = false; | 
| 428 | 18.2k |   } | 
| 429 |  |  | 
| 430 | 18.2k |   WebResponse resp; | 
| 431 | 18.2k |   WebResponse* resp_ptr = &resp; | 
| 432 |  |   // Default response code should be OK. | 
| 433 | 18.2k |   resp_ptr->code = 200; | 
| 434 | 18.2k |   stringstream *output = &resp_ptr->output; | 
| 435 | 18.2k |   if (use_style) { | 
| 436 | 66 |     BootstrapPageHeader(output); | 
| 437 | 66 |   } | 
| 438 | 18.2k |   for (const PathHandlerCallback& callback_ : handler.callbacks()) { | 
| 439 | 18.2k |     callback_(req, resp_ptr); | 
| 440 | 18.2k |     if (resp_ptr->code == 503) { | 
| 441 | 2 |       sq_printf(connection, "HTTP/1.1 503 Service Unavailable\r\n"); | 
| 442 | 2 |       return 1; | 
| 443 | 2 |     } | 
| 444 | 18.2k |   } | 
| 445 | 18.2k |   if (use_style) { | 
| 446 | 66 |     BootstrapPageFooter(output); | 
| 447 | 66 |   } | 
| 448 |  |   // Check if gzip compression is accepted by the caller. If so, compress the | 
| 449 |  |   // content and replace the prerendered output. | 
| 450 | 18.2k |   const char* accept_encoding_str = sq_get_header(connection, "Accept-Encoding"); | 
| 451 | 18.2k |   bool is_compressed = false; | 
| 452 | 18.2k |   vector<string> encodings = strings::Split(accept_encoding_str, ","); | 
| 453 | 18.2k |   for (string& encoding : encodings) { | 
| 454 | 9 |     StripWhiteSpace(&encoding); | 
| 455 | 9 |     if (encoding == "gzip") { | 
| 456 |  |       // Don't bother compressing empty content. | 
| 457 | 2 |       const string& uncompressed = resp_ptr->output.str(); | 
| 458 | 2 |       if (uncompressed.size() < FLAGS_webserver_compression_threshold_kb * 1024) { | 
| 459 | 0 |         break; | 
| 460 | 0 |       } | 
| 461 |  |  | 
| 462 | 2 |       std::ostringstream oss; | 
| 463 | 2 |       int level = FLAGS_webserver_zlib_compression_level > 0 && | 
| 464 | 2 |         FLAGS_webserver_zlib_compression_level <= 9 ? | 
| 465 | 2 |         FLAGS_webserver_zlib_compression_level : 10; | 
| 466 | 2 |       Status s = zlib::CompressLevel(uncompressed, level, &oss); | 
| 467 | 2 |       if (s.ok()) { | 
| 468 | 2 |         resp_ptr->output.str(oss.str()); | 
| 469 | 2 |         is_compressed = true; | 
| 470 | 2 |       } else { | 
| 471 | 0 |         LOG(WARNING) << "Could not compress output: " << s.ToString(); | 
| 472 | 0 |       } | 
| 473 | 2 |       break; | 
| 474 | 2 |     } | 
| 475 | 9 |   } | 
| 476 |  |  | 
| 477 | 18.2k |   string str = output->str(); | 
| 478 |  |   // Without styling, render the page as plain text | 
| 479 | 18.2k |   if (!use_style) { | 
| 480 | 18.2k |     sq_printf(connection, "HTTP/1.1 200 OK\r\n" | 
| 481 | 18.2k |               "Content-Type: text/plain\r\n" | 
| 482 | 18.2k |               "Content-Length: %zd\r\n" | 
| 483 | 18.2k |               "%s" | 
| 484 | 18.2k |               "Access-Control-Allow-Origin: *\r\n" | 
| 485 | 18.2k |               "\r\n", str.length(), is_compressed ? "Content-Encoding: gzip\r\n"0: ""); | 
| 486 | 18.2k |   } else { | 
| 487 | 66 |     sq_printf(connection, "HTTP/1.1 200 OK\r\n" | 
| 488 | 66 |               "Content-Type: text/html\r\n" | 
| 489 | 66 |               "Content-Length: %zd\r\n" | 
| 490 | 66 |               "%s" | 
| 491 | 66 |               "Access-Control-Allow-Origin: *\r\n" | 
| 492 | 66 |               "\r\n", str.length(), is_compressed ? "Content-Encoding: gzip\r\n"2: ""64); | 
| 493 | 66 |   } | 
| 494 |  |  | 
| 495 |  |   // Make sure to use sq_write for printing the body; sq_printf truncates at 8kb | 
| 496 | 18.2k |   sq_write(connection, str.c_str(), str.length()); | 
| 497 | 18.2k |   return 1; | 
| 498 | 18.2k | } | 
| 499 |  |  | 
| 500 |  | void Webserver::RegisterPathHandler(const string& path, | 
| 501 |  |                                     const string& alias, | 
| 502 |  |                                     const PathHandlerCallback& callback, | 
| 503 |  |                                     bool is_styled, | 
| 504 |  |                                     bool is_on_nav_bar, | 
| 505 | 1.02M |                                     const std::string icon) { | 
| 506 | 1.02M |   std::lock_guard<std::shared_timed_mutex> lock(lock_); | 
| 507 | 1.02M |   auto it = path_handlers_.find(path); | 
| 508 | 1.02M |   if (it == path_handlers_.end()) { | 
| 509 | 1.00M |     it = path_handlers_.insert( | 
| 510 | 1.00M |         make_pair(path, new PathHandler(is_styled, is_on_nav_bar, alias, icon))).first; | 
| 511 | 1.00M |   } | 
| 512 | 1.02M |   it->second->AddCallback(callback); | 
| 513 | 1.02M | } | 
| 514 |  |  | 
| 515 |  | const char* const PAGE_HEADER = "<!DOCTYPE html>" | 
| 516 |  | "<html>" | 
| 517 |  | "  <head>" | 
| 518 |  | "    <title>YugabyteDB</title>" | 
| 519 |  | "    <link rel='shortcut icon' href='/favicon.ico'>" | 
| 520 |  | "    <link href='/bootstrap/css/bootstrap.min.css' rel='stylesheet' media='screen' />" | 
| 521 |  | "    <link href='/bootstrap/css/bootstrap-theme.min.css' rel='stylesheet' media='screen' />" | 
| 522 |  | "    <link href='/font-awesome/css/font-awesome.min.css' rel='stylesheet' media='screen' />" | 
| 523 |  | "    <link href='/yb.css' rel='stylesheet' media='screen' />" | 
| 524 |  | "  </head>" | 
| 525 |  | "\n" | 
| 526 |  | "<body>" | 
| 527 |  | "\n"; | 
| 528 |  |  | 
| 529 |  | static const char* const NAVIGATION_BAR_PREFIX = | 
| 530 |  | "  <nav class=\"navbar navbar-fixed-top navbar-inverse sidebar-wrapper\" role=\"navigation\">" | 
| 531 |  | "    <ul class=\"nav sidebar-nav\">" | 
| 532 |  | "      <li><a href='/'><img src='/logo.png' alt='YugabyteDB' class='nav-logo' /></a></li>" | 
| 533 |  | "\n"; | 
| 534 |  |  | 
| 535 |  | static const char* const NAVIGATION_BAR_SUFFIX = | 
| 536 |  | "    </ul>" | 
| 537 |  | "  </nav>" | 
| 538 |  | "\n\n" | 
| 539 |  | "    <div class='yb-main container-fluid'>"; | 
| 540 |  |  | 
| 541 | 66 | void Webserver::BootstrapPageHeader(stringstream* output) { | 
| 542 | 66 |   (*output) << PAGE_HEADER; | 
| 543 | 66 |   (*output) << NAVIGATION_BAR_PREFIX; | 
| 544 | 2.42k |   for (const PathHandlerMap::value_type& handler : path_handlers_) { | 
| 545 | 2.42k |     if (handler.second->is_on_nav_bar()) { | 
| 546 | 241 |       (*output) << "<li class='nav-item'>" | 
| 547 | 241 |                 << "<a href='" << handler.first << "'>" | 
| 548 | 241 |                 << "<div><i class='" << handler.second->icon() << "'aria-hidden='true'></i></div>" | 
| 549 | 241 |                 << handler.second->alias() | 
| 550 | 241 |                 << "</a></li>" << "\n"; | 
| 551 | 241 |     } | 
| 552 | 2.42k |   } | 
| 553 | 66 |   (*output) << NAVIGATION_BAR_SUFFIX; | 
| 554 |  |  | 
| 555 | 66 |   if (!static_pages_available()) { | 
| 556 | 0 |     (*output) << "<div style=\"color: red\"><strong>" | 
| 557 | 0 |               << "Static pages not available. Configure YB_HOME or use the --webserver_doc_root " | 
| 558 | 0 |               << "flag to fix page styling.</strong></div>\n"; | 
| 559 | 0 |   } | 
| 560 | 66 | } | 
| 561 |  |  | 
| 562 | 27.8k | bool Webserver::static_pages_available() const { | 
| 563 | 27.8k |   return !opts_.doc_root.empty() && opts_.enable_doc_root; | 
| 564 | 27.8k | } | 
| 565 |  |  | 
| 566 | 25.8k | void Webserver::set_footer_html(const std::string& html) { | 
| 567 | 25.8k |   std::lock_guard<std::shared_timed_mutex> l(lock_); | 
| 568 | 25.8k |   footer_html_ = html; | 
| 569 | 25.8k | } | 
| 570 |  |  | 
| 571 | 66 | void Webserver::BootstrapPageFooter(stringstream* output) { | 
| 572 | 66 |   SharedLock<std::shared_timed_mutex> l(lock_); | 
| 573 | 66 |   *output << "<div class='yb-bottom-spacer'></div></div>\n"; // end bootstrap 'container' div | 
| 574 | 66 |   if (!footer_html_.empty()) { | 
| 575 | 57 |     *output << "<footer class='footer'><div class='yb-footer container text-muted'>"; | 
| 576 | 57 |     *output << footer_html_; | 
| 577 | 57 |     *output << "</div></footer>"; | 
| 578 | 57 |   } | 
| 579 | 66 |   *output << "</body></html>"; | 
| 580 | 66 | } | 
| 581 |  |  | 
| 582 | 961 | void Webserver::EnterWorkerThreadCallbackStatic() { | 
| 583 | 961 |   cds::threading::Manager::attachThread(); | 
| 584 | 961 | } | 
| 585 |  |  | 
| 586 | 538 | void Webserver::LeaveWorkerThreadCallbackStatic() { | 
| 587 | 538 |   cds::threading::Manager::detachThread(); | 
| 588 | 538 | } | 
| 589 |  |  | 
| 590 |  | } // namespace yb |