YugabyteDB (2.13.1.0-b60, 21121d69985fbf76aa6958d8f04a9bfa936293b5)

Coverage Report

Created: 2022-03-22 16:43

/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.5k
i < 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