This is an automated email from the ASF dual-hosted git repository.
alexey pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/kudu.git
The following commit(s) were added to refs/heads/master by this push:
new 7ecad3550 KUDU-3543 Fix Content-Type headers
7ecad3550 is described below
commit 7ecad355065e4d0eb891c87813f347bb7d6819b2
Author: Marton Greber <[email protected]>
AuthorDate: Sat Jan 6 00:50:16 2024 +0100
KUDU-3543 Fix Content-Type headers
This patch addresses an issue with the webserver's Content-Type headers.
The problem lies in the webserver's handler functions, where we use a
boolean variable called 'is_styled' to decide on the style mode.
In the root Webserver::RunPathHandler function, only StyleMode::STYLED
or StyleMode::UNSTYLED are returned. Although SendResponse is invoked
with a use_style variable of type StyleMode, we only end up with
StyleMode::STYLED or StyleMode::UNSTYLED in the subsequent code paths.
Consequently, we never utilize StyleMode::BINARY. This means that the
master's /ipki-ca-cert-der endpoint does not have the expected
application/octet-stream content-type header; instead, it has text/plain.
(See KUDU-3543 for evidence).
This patch replaces the 'is_styled' boolean with the StyleMode enum
entirely. I have added test cases to verify the Content-Type headers of
the Master and TServer endpoints.
[1]
https://github.com/apache/kudu/blob/master/src/kudu/server/webserver.cc#L686-L687
Change-Id: I746dcfbaadb2fb95292c2d4047cb7adb9971b42f
Reviewed-on: http://gerrit.cloudera.org:8080/20868
Tested-by: Marton Greber <[email protected]>
Reviewed-by: Zoltan Chovan <[email protected]>
Reviewed-by: Alexey Serbin <[email protected]>
---
src/kudu/master/master-test.cc | 36 ++++++++++++++++
src/kudu/master/master_path_handlers.cc | 15 ++++---
src/kudu/server/default_path_handlers.cc | 26 ++++++------
src/kudu/server/pprof_path_handlers.cc | 15 ++++---
src/kudu/server/rpcz-path-handler.cc | 5 ++-
src/kudu/server/startup_path_handler.cc | 3 +-
src/kudu/server/tracing_path_handlers.cc | 3 +-
src/kudu/server/webserver.cc | 22 +++++-----
src/kudu/server/webserver.h | 29 +++++--------
src/kudu/tserver/tablet_server-test.cc | 23 +++++++++++
src/kudu/tserver/tserver_path_handlers.cc | 18 ++++----
src/kudu/util/jwt-util-test.cc | 4 +-
src/kudu/util/mini_oidc.cc | 6 +--
src/kudu/util/test_util.cc | 68 +++++++++++++++++++++++++++++++
src/kudu/util/test_util.h | 15 +++++++
src/kudu/util/thread.cc | 2 +-
src/kudu/util/web_callback_registry.h | 20 +++++++--
17 files changed, 227 insertions(+), 83 deletions(-)
diff --git a/src/kudu/master/master-test.cc b/src/kudu/master/master-test.cc
index 2554f3903..fd58a0b75 100644
--- a/src/kudu/master/master-test.cc
+++ b/src/kudu/master/master-test.cc
@@ -3798,5 +3798,41 @@ TEST_F(MasterTest, TestMastersListedInDumpEndpoints) {
ASSERT_NE("", master["start_time"].GetString());
}
+TEST_F(MasterTest, TestMasterContentTypeHeaders) {
+ // /pprof endpoints take longer to finish.
+ SKIP_IF_SLOW_NOT_ALLOWED();
+
+ // Create a table with one partition for the '/table?id=' endpoint.
+ constexpr const char* const kTableName = "testtb";
+ const Schema kTableSchema({ColumnSchema("key", INT32),
+ ColumnSchema("val", INT32)}, 2);
+ FLAGS_default_num_replicas = 1;
+ KuduPartialRow a_lower(&kTableSchema);
+ KuduPartialRow a_upper(&kTableSchema);
+ ASSERT_OK(a_lower.SetInt32("key", 0));
+ ASSERT_OK(a_upper.SetInt32("key", 100));
+ CreateTableResponsePB create_table_resp;
+ ASSERT_OK(CreateTable(
+ kTableName, kTableSchema, nullopt, nullopt, nullopt, {}, {{a_lower,
a_upper}},
+ {}, {{{"key"}, 2, 0}}, &create_table_resp));
+
+ // Get all the endpoints to be tested.
+ unordered_map<string, string> endpoint_type_map =
GetCommonWebserverEndpoints();
+ unordered_map<string, string> master_endpoints =
+ GetMasterWebserverEndpoints(create_table_resp.table_id());
+ endpoint_type_map.merge(master_endpoints);
+
+ EasyCurl c;
+ c.set_return_headers(true);
+ faststring buf;
+ const auto addr = Substitute("http://$0",
mini_master_->bound_http_addr().ToString());
+
+ // Check that all the endpoints have the defined Content-Type headers.
+ for (const auto& [endpoint, type] : endpoint_type_map) {
+ ASSERT_OK(c.FetchURL(Substitute("$0/$1", addr, endpoint), &buf));
+ ASSERT_STR_CONTAINS(buf.ToString(), Substitute("Content-Type: $0", type));
+ }
+}
+
} // namespace master
} // namespace kudu
diff --git a/src/kudu/master/master_path_handlers.cc
b/src/kudu/master/master_path_handlers.cc
index 92cdc223d..f1c36fdfe 100644
--- a/src/kudu/master/master_path_handlers.cc
+++ b/src/kudu/master/master_path_handlers.cc
@@ -873,44 +873,43 @@ void MasterPathHandlers::HandleDumpEntities(const
Webserver::WebRequest& /*req*/
}
Status MasterPathHandlers::Register(Webserver* server) {
- constexpr const bool is_styled = true;
constexpr const bool is_on_nav_bar = true;
server->RegisterPathHandler(
"/tablet-servers", "Tablet Servers",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleTabletServers(req, resp);
},
- is_styled, is_on_nav_bar);
+ StyleMode::STYLED, is_on_nav_bar);
server->RegisterPathHandler(
"/tables", "Tables",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleCatalogManager(req, resp);
},
- is_styled, is_on_nav_bar);
+ StyleMode::STYLED, is_on_nav_bar);
server->RegisterPathHandler(
"/table", "",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleTablePage(req, resp);
},
- is_styled, false);
+ StyleMode::STYLED, false);
server->RegisterPathHandler(
"/masters", "Masters",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleMasters(req, resp);
},
- is_styled, is_on_nav_bar);
+ StyleMode::STYLED, is_on_nav_bar);
server->RegisterPrerenderedPathHandler(
"/ipki-ca-cert", "IPKI CA certificate",
[this](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
this->HandleIpkiCaCert(DataFormat::PEM, req, resp);
},
- false /*is_styled*/, true /*is_on_nav_bar*/);
+ StyleMode::UNSTYLED , true /*is_on_nav_bar*/);
server->RegisterPrerenderedPathHandler(
"/ipki-ca-cert-pem", "IPKI CA certificate (PEM format)",
[this](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
this->HandleIpkiCaCert(DataFormat::PEM, req, resp);
},
- false /*is_styled*/, false /*is_on_nav_bar*/);
+ StyleMode::UNSTYLED, false /*is_on_nav_bar*/);
server->RegisterBinaryDataPathHandler(
"/ipki-ca-cert-der", "IPKI CA certificate (DER format)",
[this](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
@@ -921,7 +920,7 @@ Status MasterPathHandlers::Register(Webserver* server) {
[this](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
this->HandleDumpEntities(req, resp);
},
- false, false);
+ StyleMode::UNSTYLED, false);
return Status::OK();
}
diff --git a/src/kudu/server/default_path_handlers.cc
b/src/kudu/server/default_path_handlers.cc
index 8856743b4..7457710ac 100644
--- a/src/kudu/server/default_path_handlers.cc
+++ b/src/kudu/server/default_path_handlers.cc
@@ -441,27 +441,27 @@ static void ConfigurationHandler(const
Webserver::WebRequest& /* req */,
FillTimeSourceConfigs(output);
}
void AddPreInitializedDefaultPathHandlers(Webserver* webserver) {
- bool styled = true;
bool on_nav_bar = true;
- webserver->RegisterPathHandler("/logs", "Logs", LogsHandler, styled,
on_nav_bar);
- webserver->RegisterPrerenderedPathHandler("/varz", "Flags", FlagsHandler,
styled, on_nav_bar);
+ webserver->RegisterPathHandler("/logs", "Logs", LogsHandler,
StyleMode::STYLED, on_nav_bar);
+ webserver->RegisterPrerenderedPathHandler("/varz", "Flags", FlagsHandler,
StyleMode::STYLED,
+ on_nav_bar);
webserver->RegisterPathHandler("/config", "Configuration",
ConfigurationHandler,
- styled, on_nav_bar);
+ StyleMode::STYLED, on_nav_bar);
webserver->RegisterPrerenderedPathHandler("/memz", "Memory (total)",
MemUsageHandler,
- styled, on_nav_bar);
+ StyleMode::STYLED, on_nav_bar);
webserver->RegisterPrerenderedPathHandler("/mem-trackers", "Memory
(detail)", MemTrackersHandler,
- styled, on_nav_bar);
+ StyleMode::STYLED, on_nav_bar);
}
void AddPostInitializedDefaultPathHandlers(Webserver* webserver) {
webserver->RegisterPrerenderedPathHandler("/stacks", "Stacks", StacksHandler,
- /*is_styled=*/false,
+ StyleMode::UNSTYLED,
/*is_on_nav_bar=*/true);
webserver->RegisterPrerenderedPathHandler("/version", "VersionInfo",
VersionInfoHandler,
- /*is_styled=*/false,
+ StyleMode::UNSTYLED,
/*is_on_nav_bar*/false);
webserver->RegisterPrerenderedPathHandler("/healthz", "Health",
HealthHandler,
- /*is_styled=*/false,
+ StyleMode::UNSTYLED,
/*is_on_nav_bar=*/true);
AddPprofPathHandlers(webserver);
}
@@ -531,16 +531,15 @@ void RegisterMetricsJsonHandler(Webserver* webserver,
const MetricRegistry* cons
Webserver::PrerenderedWebResponse* resp) {
WriteMetricsAsJson(metrics, req, resp);
};
- bool not_styled = false;
bool not_on_nav_bar = false;
bool is_on_nav_bar = true;
webserver->RegisterPrerenderedPathHandler("/metrics", "JSON Metrics",
callback,
- not_styled, is_on_nav_bar);
+ StyleMode::UNSTYLED,
is_on_nav_bar);
// The old name -- this is preserved for compatibility with older releases of
// monitoring software which expects the old name.
webserver->RegisterPrerenderedPathHandler("/jsonmetricz", "Metrics",
callback,
- not_styled, not_on_nav_bar);
+ StyleMode::UNSTYLED,
not_on_nav_bar);
}
void RegisterMetricsPrometheusHandler(Webserver* webserver, const
MetricRegistry* const metrics) {
@@ -548,10 +547,9 @@ void RegisterMetricsPrometheusHandler(Webserver*
webserver, const MetricRegistry
Webserver::PrerenderedWebResponse* resp) {
WriteMetricsAsPrometheus(metrics, req, resp);
};
- constexpr bool not_styled = false;
constexpr bool is_on_nav_bar = true;
webserver->RegisterPrerenderedPathHandler("/metrics_prometheus", "Prometheus
Metrics", callback,
- not_styled, is_on_nav_bar);
+ StyleMode::UNSTYLED,
is_on_nav_bar);
}
} // namespace kudu
diff --git a/src/kudu/server/pprof_path_handlers.cc
b/src/kudu/server/pprof_path_handlers.cc
index 3b809218d..6ee5508b7 100644
--- a/src/kudu/server/pprof_path_handlers.cc
+++ b/src/kudu/server/pprof_path_handlers.cc
@@ -258,14 +258,17 @@ void AddPprofPathHandlers(Webserver* webserver) {
// Path handlers for remote pprof profiling. For information see:
// https://gperftools.googlecode.com/svn/trunk/doc/pprof_remote_servers.html
webserver->RegisterPrerenderedPathHandler("/pprof/cmdline", "",
PprofCmdLineHandler,
- false, false);
- webserver->RegisterPrerenderedPathHandler("/pprof/heap", "",
PprofHeapHandler, false, false);
- webserver->RegisterPrerenderedPathHandler("/pprof/growth", "",
PprofGrowthHandler, false, false);
+ StyleMode::UNSTYLED, false);
+ webserver->RegisterPrerenderedPathHandler("/pprof/heap", "",
PprofHeapHandler,
+ StyleMode::UNSTYLED, false);
+ webserver->RegisterPrerenderedPathHandler("/pprof/growth", "",
PprofGrowthHandler,
+ StyleMode::UNSTYLED, false);
webserver->RegisterPrerenderedPathHandler("/pprof/profile", "",
PprofCpuProfileHandler,
- false, false);
- webserver->RegisterPrerenderedPathHandler("/pprof/symbol", "",
PprofSymbolHandler, false, false);
+ StyleMode::UNSTYLED, false);
+ webserver->RegisterPrerenderedPathHandler("/pprof/symbol", "",
PprofSymbolHandler,
+ StyleMode::UNSTYLED, false);
webserver->RegisterPrerenderedPathHandler("/pprof/contention", "",
PprofContentionHandler,
- false, false);
+ StyleMode::UNSTYLED, false);
}
} // namespace kudu
diff --git a/src/kudu/server/rpcz-path-handler.cc
b/src/kudu/server/rpcz-path-handler.cc
index 528fa4e60..159639f9d 100644
--- a/src/kudu/server/rpcz-path-handler.cc
+++ b/src/kudu/server/rpcz-path-handler.cc
@@ -21,11 +21,12 @@
#include <memory>
#include <sstream>
#include <string>
+#include <type_traits>
#include <unordered_map>
#include "kudu/gutil/map-util.h"
#include "kudu/gutil/strings/numbers.h"
-#include "kudu/rpc/messenger.h"
+#include "kudu/rpc/messenger.h" // IWYU pragma: keep
#include "kudu/rpc/rpc_introspection.pb.h"
#include "kudu/rpc/rpcz_store.h"
#include "kudu/server/webserver.h"
@@ -81,7 +82,7 @@ void AddRpczPathHandlers(const shared_ptr<Messenger>&
messenger, Webserver* webs
[messenger](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
RpczPathHandler(messenger, req, resp);
},
- false, true);
+ StyleMode::UNSTYLED, true /*is_on_nav_bar*/);
}
} // namespace kudu
diff --git a/src/kudu/server/startup_path_handler.cc
b/src/kudu/server/startup_path_handler.cc
index e059c1f98..13a3abf5f 100644
--- a/src/kudu/server/startup_path_handler.cc
+++ b/src/kudu/server/startup_path_handler.cc
@@ -119,14 +119,13 @@ void StartupPathHandler::Startup(const
Webserver::WebRequest& /*req*/,
}
void StartupPathHandler::RegisterStartupPathHandler(Webserver *webserver) {
- bool styled = true;
bool on_nav_bar = true;
webserver->RegisterPathHandler("/startup", "Startup",
[this](const Webserver::WebRequest& req,
Webserver::WebResponse* resp) {
this->Startup(req, resp);
},
- styled, on_nav_bar);
+ StyleMode::STYLED, on_nav_bar);
}
void StartupPathHandler::set_is_tablet_server(bool is_tablet_server) {
diff --git a/src/kudu/server/tracing_path_handlers.cc
b/src/kudu/server/tracing_path_handlers.cc
index 842504b45..086424936 100644
--- a/src/kudu/server/tracing_path_handlers.cc
+++ b/src/kudu/server/tracing_path_handlers.cc
@@ -277,8 +277,7 @@ void TracingPathHandlers::RegisterHandlers(Webserver*
server) {
e.first, "", [e](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
HandleRequest(e.second, req, resp);
- },
- false /* styled */, false /* is_on_nav_bar */);
+ }, StyleMode::UNSTYLED, false /* is_on_nav_bar */);
}
}
diff --git a/src/kudu/server/webserver.cc b/src/kudu/server/webserver.cc
index 07727949c..0a52227d3 100644
--- a/src/kudu/server/webserver.cc
+++ b/src/kudu/server/webserver.cc
@@ -442,7 +442,7 @@ Status Webserver::Start() {
[this](const WebRequest& req, WebResponse* resp) {
this->RootHandler(req, resp);
},
- /*is_styled=*/true, /*is_on_nav_bar=*/true);
+ StyleMode::STYLED, /*is_on_nav_bar=*/true);
vector<Sockaddr> addrs;
RETURN_NOT_OK(GetBoundAddresses(&addrs));
@@ -682,8 +682,8 @@ sq_callback_result_t Webserver::RunPathHandler(
}
// Should we render with css styles?
- StyleMode use_style = handler.is_styled() && !ContainsKey(req.parsed_args,
"raw") ?
- StyleMode::STYLED : StyleMode::UNSTYLED;
+ StyleMode use_style = ContainsKey(req.parsed_args, "raw") ?
+ StyleMode::UNSTYLED : handler.style_mode();
SendResponse(connection, resp, &req, use_style);
return SQ_HANDLED_OK;
}
@@ -834,7 +834,7 @@ string Webserver::MustachePartialTag(const string& path) {
}
void Webserver::RegisterPathHandler(const string& path, const string& alias,
- const PathHandlerCallback& callback, bool is_styled, bool is_on_nav_bar) {
+ const PathHandlerCallback& callback, StyleMode style_mode, bool
is_on_nav_bar) {
string render_path = (path == "/") ? "/home" : path;
auto wrapped_cb = [=](const WebRequest& req, PrerenderedWebResponse*
rendered_resp) {
WebResponse resp;
@@ -846,17 +846,17 @@ void Webserver::RegisterPathHandler(const string& path,
const string& alias,
// do not render the page
if (render_path != "/home" || is_started_) {
stringstream out;
- Render(render_path, resp.output, is_styled, &out);
+ Render(render_path, resp.output, style_mode, &out);
rendered_resp->output << out.rdbuf();
}
};
- RegisterPrerenderedPathHandler(path, alias, wrapped_cb, is_styled,
is_on_nav_bar);
+ RegisterPrerenderedPathHandler(path, alias, wrapped_cb, style_mode,
is_on_nav_bar);
}
void Webserver::RegisterPrerenderedPathHandler(const string& path, const
string& alias,
- const PrerenderedPathHandlerCallback& callback, bool is_styled, bool
is_on_nav_bar) {
+ const PrerenderedPathHandlerCallback& callback, StyleMode style_mode, bool
is_on_nav_bar) {
std::lock_guard l(lock_);
- InsertOrDie(&path_handlers_, path, new PathHandler(is_styled, is_on_nav_bar,
alias, callback));
+ InsertOrDie(&path_handlers_, path, new PathHandler(style_mode,
is_on_nav_bar, alias, callback));
}
void Webserver::RegisterBinaryDataPathHandler(
@@ -864,7 +864,7 @@ void Webserver::RegisterBinaryDataPathHandler(
const string& alias,
const PrerenderedPathHandlerCallback& callback) {
std::lock_guard l(lock_);
- InsertOrDie(&path_handlers_, path, new PathHandler(false /*is_styled*/,
+ InsertOrDie(&path_handlers_, path, new PathHandler(StyleMode::BINARY,
false /*is_on_nav_bar*/,
alias,
callback));
@@ -954,11 +954,11 @@ void Webserver::RenderMainTemplate(
RenderTemplate(kMainTemplate, opts_.doc_root, ej.value(), output);
}
-void Webserver::Render(const string& path, const EasyJson& ej, bool use_style,
+void Webserver::Render(const string& path, const EasyJson& ej, StyleMode
style_mode,
stringstream* output) {
if (MustacheTemplateAvailable(path)) {
RenderTemplate(MustachePartialTag(path), opts_.doc_root, ej.value(),
output);
- } else if (use_style) {
+ } else if (style_mode == StyleMode::STYLED) {
(*output) << "<pre>" << ej.ToString() << "</pre>";
} else {
(*output) << ej.ToString();
diff --git a/src/kudu/server/webserver.h b/src/kudu/server/webserver.h
index 073ee8d84..a1be885f0 100644
--- a/src/kudu/server/webserver.h
+++ b/src/kudu/server/webserver.h
@@ -67,17 +67,17 @@ class Webserver : public WebCallbackRegistry {
// Register a route 'path' to be rendered via template.
// The appropriate template to use is determined by 'path'.
- // If 'is_styled' is true, the page will be styled and include a header and
footer.
+ // If 'style_mode' is StyleMode::STYLED, the page will be styled and include
a header and footer.
// If 'is_on_nav_bar' is true, a link to the page will be placed on the
navbar
// in the header of styled pages. The link text is given by 'alias'.
void RegisterPathHandler(const std::string& path, const std::string& alias,
const PathHandlerCallback& callback,
- bool is_styled, bool is_on_nav_bar) override;
+ StyleMode style_mode, bool is_on_nav_bar) override;
// Register a route 'path'. See the RegisterPathHandler for details.
void RegisterPrerenderedPathHandler(const std::string& path, const
std::string& alias,
const PrerenderedPathHandlerCallback&
callback,
- bool is_styled,
+ StyleMode style_mode,
bool is_on_nav_bar) override;
// Register route 'path' for application/octet-stream (binary data)
responses.
@@ -105,21 +105,21 @@ class Webserver : public WebCallbackRegistry {
// Container class for a list of path handler callbacks for a single URL.
class PathHandler {
public:
- PathHandler(bool is_styled, bool is_on_nav_bar, std::string alias,
+ PathHandler(StyleMode style_mode, bool is_on_nav_bar, std::string alias,
PrerenderedPathHandlerCallback callback)
- : is_styled_(is_styled),
+ : style_mode_(style_mode),
is_on_nav_bar_(is_on_nav_bar),
alias_(std::move(alias)),
callback_(std::move(callback)) {}
- bool is_styled() const { return is_styled_; }
+ StyleMode style_mode() const { return style_mode_; }
bool is_on_nav_bar() const { return is_on_nav_bar_; }
const std::string& alias() const { return alias_; }
const PrerenderedPathHandlerCallback& callback() const { return callback_;
}
private:
- // If true, the page appears is rendered styled.
- const bool is_styled_;
+ // The style mode controls how the page is rendered, and what content-type
header is used.
+ const StyleMode style_mode_;
// If true, the page appears in the navigation bar.
const bool is_on_nav_bar_;
@@ -156,7 +156,7 @@ class Webserver : public WebCallbackRegistry {
// Renders the template corresponding to 'path' (if available), using
// fields in 'ej'.
- void Render(const std::string& path, const EasyJson& ej, bool use_style,
+ void Render(const std::string& path, const EasyJson& ej, StyleMode
style_mode,
std::stringstream* output);
// Dispatch point for all incoming requests.
@@ -188,19 +188,10 @@ class Webserver : public WebCallbackRegistry {
//
// 'req' may be null if we're early enough in processing that we haven't
// parsed the request yet (e.g. an early error out).
- //
- // If 'mode' is STYLED, includes page styling elements like CSS, navigation
bar, etc.
- //
- // BINARY is a rare case when a binary data is sent as a response.
- enum class StyleMode {
- STYLED,
- UNSTYLED,
- BINARY,
- };
void SendResponse(struct sq_connection* connection,
PrerenderedWebResponse* resp,
const WebRequest* req = nullptr,
- StyleMode mode = StyleMode::UNSTYLED);
+ StyleMode style_mode = StyleMode::UNSTYLED);
const WebserverOptions opts_;
diff --git a/src/kudu/tserver/tablet_server-test.cc
b/src/kudu/tserver/tablet_server-test.cc
index 16b8b9fac..e20ec51d7 100644
--- a/src/kudu/tserver/tablet_server-test.cc
+++ b/src/kudu/tserver/tablet_server-test.cc
@@ -34,6 +34,7 @@
#include <string>
#include <thread>
#include <type_traits>
+#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -157,6 +158,7 @@ using std::string;
using std::thread;
using std::unique_ptr;
using std::unordered_set;
+using std::unordered_map;
using std::vector;
using strings::Substitute;
@@ -618,6 +620,27 @@ TEST_F(TabletServerTest, TestWebPages) {
#endif
}
+TEST_F(TabletServerTest, TestTabletServerContentTypeHeaders) {
+ // /pprof endpoints take longer to finish.
+ SKIP_IF_SLOW_NOT_ALLOWED();
+
+ // Get all the endpoints to be tested.
+ unordered_map<string, string> endpoint_type_map =
GetCommonWebserverEndpoints();
+ unordered_map<string, string> tserver_endpoints =
GetTServerWebserverEndpoints(kTabletId);
+ endpoint_type_map.merge(tserver_endpoints);
+
+ EasyCurl c;
+ c.set_return_headers(true);
+ faststring buf;
+ const auto addr = Substitute("http://$0",
mini_server_->bound_http_addr().ToString());
+
+ // Check that all the endpoints have the defined Content-Type headers.
+ for (const auto& [endpoint, type] : endpoint_type_map) {
+ ASSERT_OK(c.FetchURL(Substitute("$0/$1", addr, endpoint), &buf));
+ ASSERT_STR_CONTAINS(buf.ToString(), Substitute("Content-Type: $0", type));
+ }
+}
+
// Ensure that when a replica is in a failed / shutdown state, it returns an
// error for ConsensusState() requests.
TEST_F(TabletServerTest, TestFailedTabletsRejectConsensusState) {
diff --git a/src/kudu/tserver/tserver_path_handlers.cc
b/src/kudu/tserver/tserver_path_handlers.cc
index 37e925650..a1ba5bcb2 100644
--- a/src/kudu/tserver/tserver_path_handlers.cc
+++ b/src/kudu/tserver/tserver_path_handlers.cc
@@ -202,55 +202,55 @@ Status TabletServerPathHandlers::Register(Webserver*
server) {
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleScansPage(req, resp);
},
- true /* styled */, false /* is_on_nav_bar */);
+ StyleMode::STYLED, false /* is_on_nav_bar */);
server->RegisterPathHandler(
"/tablets", "Tablets",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleTabletsPage(req, resp);
},
- true /* styled */, true /* is_on_nav_bar */);
+ StyleMode::STYLED, true /* is_on_nav_bar */);
server->RegisterPathHandler(
"/tablet", "",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleTabletPage(req, resp);
},
- true /* styled */, false /* is_on_nav_bar */);
+ StyleMode::STYLED, false /* is_on_nav_bar */);
server->RegisterPrerenderedPathHandler(
"/transactions", "",
[this](const Webserver::WebRequest& req,
Webserver::PrerenderedWebResponse* resp) {
this->HandleTransactionsPage(req, resp);
},
- true /* styled */, false /* is_on_nav_bar */);
+ StyleMode::STYLED, false /* is_on_nav_bar */);
server->RegisterPathHandler(
"/tablet-rowsetlayout-svg", "",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleTabletSVGPage(req, resp);
},
- true /* styled */, false /* is_on_nav_bar */);
+ StyleMode::STYLED, false /* is_on_nav_bar */);
server->RegisterPathHandler(
"/tablet-consensus-status", "",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleConsensusStatusPage(req, resp);
},
- true /* styled */, false /* is_on_nav_bar */);
+ StyleMode::STYLED, false /* is_on_nav_bar */);
server->RegisterPathHandler(
"/log-anchors", "",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleLogAnchorsPage(req, resp);
},
- true /* styled */, false /* is_on_nav_bar */);
+ StyleMode::STYLED, false /* is_on_nav_bar */);
server->RegisterPathHandler(
"/dashboards", "Dashboards",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleDashboardsPage(req, resp);
},
- true /* styled */, true /* is_on_nav_bar */);
+ StyleMode::STYLED, true /* is_on_nav_bar */);
server->RegisterPathHandler(
"/maintenance-manager", "",
[this](const Webserver::WebRequest& req, Webserver::WebResponse* resp) {
this->HandleMaintenanceManagerPage(req, resp);
},
- true /* styled */, false /* is_on_nav_bar */);
+ StyleMode::STYLED, false /* is_on_nav_bar */);
return Status::OK();
}
diff --git a/src/kudu/util/jwt-util-test.cc b/src/kudu/util/jwt-util-test.cc
index c3655964c..0b2d380ec 100644
--- a/src/kudu/util/jwt-util-test.cc
+++ b/src/kudu/util/jwt-util-test.cc
@@ -851,7 +851,7 @@ class JWKSMockServer {
opts.bind_interface = "127.0.0.1";
webserver_.reset(new Webserver(opts));
webserver_->RegisterPrerenderedPathHandler("/jwks", "JWKS",
SimpleJWKSHandler,
- /*is_styled*/false,
/*is_on_nav_bar*/false);
+ StyleMode::UNSTYLED,
/*is_on_nav_bar*/false);
RETURN_NOT_OK(webserver_->Start());
vector<Sockaddr> addrs;
RETURN_NOT_OK(webserver_->GetBoundAddresses(&addrs));
@@ -876,7 +876,7 @@ class JWKSMockServer {
resp->output << jwks;
resp->status_code = HttpStatusCode::Ok;
},
- /*is_styled*/false, /*is_on_nav_bar*/false);
+ StyleMode::UNSTYLED, /*is_on_nav_bar*/false);
}
RETURN_NOT_OK(webserver_->Start());
vector<Sockaddr> addrs;
diff --git a/src/kudu/util/mini_oidc.cc b/src/kudu/util/mini_oidc.cc
index 36103f0e5..fda284588 100644
--- a/src/kudu/util/mini_oidc.cc
+++ b/src/kudu/util/mini_oidc.cc
@@ -126,7 +126,7 @@ Status MiniOidc::Start() {
kRsaInvalidPubKeyJwkN, kRsaPubKeyJwkE),
resp->status_code = HttpStatusCode::Ok;
},
- /*is_styled*/ false,
+ StyleMode::UNSTYLED,
/*is_on_nav_bar*/ false);
}
@@ -146,7 +146,7 @@ Status MiniOidc::Start() {
kRsaPubKeyJwkE),
resp->status_code = HttpStatusCode::Ok;
},
- /*is_styled*/ false,
+ StyleMode::UNSTYLED,
/*is_on_nav_bar*/ false);
LOG(INFO) << "Starting JWKS server";
@@ -183,7 +183,7 @@ Status MiniOidc::Start() {
Webserver::PrerenderedWebResponse* resp) {
OidcDiscoveryHandler(req, resp, discovery_jwks_url);
},
- /*is_styled*/ false,
+ StyleMode::UNSTYLED,
/*is_on_nav_bar*/ false);
LOG(INFO) << "Starting OIDC Discovery server";
diff --git a/src/kudu/util/test_util.cc b/src/kudu/util/test_util.cc
index b491018fc..9129a5ce8 100644
--- a/src/kudu/util/test_util.cc
+++ b/src/kudu/util/test_util.cc
@@ -72,6 +72,7 @@ DECLARE_bool(enable_multi_tenancy);
DECLARE_bool(encrypt_data_at_rest);
using std::string;
+using std::unordered_map;
using std::vector;
using strings::Substitute;
@@ -96,6 +97,10 @@ static const char* const kEncryptionTenantID =
"00000000000000000000000000000000
static const uint64_t kTestBeganAtMicros = Env::Default()->NowMicros();
+static const char* const kContentTypeTextPlain = "text/plain";
+static const char* const kContentTypeTextHtml = "text/html";
+static const char* const kContentTypeApplicationOctet =
"application/octet-stream";
+
// Global which production code can check to see if it is running
// in a GTest environment (assuming the test binary links in this module,
// which is typically a good assumption).
@@ -652,4 +657,67 @@ Status FindHomeDir(const string& name, const string&
bin_dir, string* home_dir)
return Status::OK();
}
+const unordered_map<string, string>& GetCommonWebserverEndpoints() {
+ static const unordered_map<string, string> common_endpoints = {
+ {"logs", kContentTypeTextHtml},
+ {"varz", kContentTypeTextHtml},
+ {"config", kContentTypeTextHtml},
+ {"memz", kContentTypeTextHtml},
+ {"mem-trackers", kContentTypeTextHtml},
+ {"stacks", kContentTypeTextPlain},
+ {"version", kContentTypeTextPlain},
+ {"healthz", kContentTypeTextPlain},
+ {"metrics", kContentTypeTextPlain},
+ {"jsonmetricz", kContentTypeTextPlain},
+ {"metrics_prometheus", kContentTypeTextPlain},
+ {"rpcz", kContentTypeTextPlain},
+ {"startup", kContentTypeTextHtml},
+ {"pprof/cmdline", kContentTypeTextPlain},
+ {"pprof/heap", kContentTypeTextPlain},
+ {"pprof/growth", kContentTypeTextPlain},
+ {"pprof/profile", kContentTypeTextPlain},
+ {"pprof/symbol", kContentTypeTextPlain},
+ {"pprof/contention", kContentTypeTextPlain},
+ {"tracing/json/begin_monitoring", kContentTypeTextPlain},
+ {"tracing/json/end_monitoring", kContentTypeTextPlain},
+ {"tracing/json/capture_monitoring", kContentTypeTextPlain},
+ {"tracing/json/get_monitoring_status", kContentTypeTextPlain},
+ {"tracing/json/categories", kContentTypeTextPlain},
+ {"tracing/json/begin_recording", kContentTypeTextPlain},
+ {"tracing/json/get_buffer_percent_full", kContentTypeTextPlain},
+ {"tracing/json/end_recording", kContentTypeTextPlain},
+ {"tracing/json/end_recording_compressed", kContentTypeTextPlain},
+ {"tracing/json/simple_dump", kContentTypeTextPlain}};
+ return common_endpoints;
+}
+
+// Add necessary query params to get 200 response in tests.
+const unordered_map<string, string>& GetTServerWebserverEndpoints(const
string& tablet_id) {
+ static const unordered_map<string, string> tserver_endpoints = {
+ {"scans", kContentTypeTextHtml},
+ {"tablets", kContentTypeTextHtml},
+ {Substitute("tablet?id=$0", tablet_id), kContentTypeTextHtml},
+ {"transactions", kContentTypeTextHtml},
+ {Substitute("tablet-rowsetlayout-svg?id=$0", tablet_id),
kContentTypeTextHtml},
+ {Substitute("tablet-consensus-status?id=$0", tablet_id),
kContentTypeTextHtml},
+ {Substitute("log-anchors?id=$0", tablet_id), kContentTypeTextHtml},
+ {"dashboards", kContentTypeTextHtml},
+ {"maintenance-manager", kContentTypeTextHtml}};
+ return tserver_endpoints;
+}
+
+// Add necessary query params to get 200 response in tests.
+const unordered_map<string, string>& GetMasterWebserverEndpoints(const string&
table_id) {
+ static unordered_map<string, string> master_endpoints = {
+ {"tablet-servers", kContentTypeTextHtml},
+ {"tables", kContentTypeTextHtml},
+ {Substitute("table?id=$0", table_id), kContentTypeTextHtml},
+ {"masters", kContentTypeTextHtml},
+ {"ipki-ca-cert", kContentTypeTextPlain},
+ {"ipki-ca-cert-pem", kContentTypeTextPlain},
+ {"ipki-ca-cert-der", kContentTypeApplicationOctet},
+ {"dump-entities", kContentTypeTextPlain}};
+ return master_endpoints;
+}
+
} // namespace kudu
diff --git a/src/kudu/util/test_util.h b/src/kudu/util/test_util.h
index afff0d507..33071112b 100644
--- a/src/kudu/util/test_util.h
+++ b/src/kudu/util/test_util.h
@@ -26,6 +26,7 @@
#include <memory>
#include <string>
#include <vector>
+#include <unordered_map>
#include <gtest/gtest.h>
@@ -201,5 +202,19 @@ Status FindHomeDir(const std::string& name,
const std::string& bin_dir,
std::string* home_dir) WARN_UNUSED_RESULT;
+// Contains the endpoints which are to be found on Master and TServer as well.
+// Key: endpoint name, value: content-type header for that particular endpoint.
+const std::unordered_map<std::string, std::string>&
GetCommonWebserverEndpoints();
+
+// Contains the endpoints which are to be found on TServer only.
+// Key: endpoint name, value: content-type header for that particular endpoint.
+const std::unordered_map<std::string, std::string>&
GetTServerWebserverEndpoints(
+ const std::string& tablet_id);
+
+// Contains the endpoints which are to be found on Master only.
+// Key: endpoint name, value: content-type header for that particular endpoint.
+const std::unordered_map<std::string, std::string>&
GetMasterWebserverEndpoints(
+ const std::string& table_id);
+
} // namespace kudu
#endif
diff --git a/src/kudu/util/thread.cc b/src/kudu/util/thread.cc
index beab6f06e..0293ef1a4 100644
--- a/src/kudu/util/thread.cc
+++ b/src/kudu/util/thread.cc
@@ -337,7 +337,7 @@ Status ThreadMgr::StartInstrumentation(const
scoped_refptr<MetricEntity>& metric
WebCallbackRegistry::WebResponse* resp) {
this->ThreadPathHandler(req, resp);
},
- /* is_styled= */ true,
+ StyleMode::STYLED,
/* is_on_nav_bar= */ true);
}
return Status::OK();
diff --git a/src/kudu/util/web_callback_registry.h
b/src/kudu/util/web_callback_registry.h
index 7bd4d309b..d0e39db44 100644
--- a/src/kudu/util/web_callback_registry.h
+++ b/src/kudu/util/web_callback_registry.h
@@ -37,6 +37,18 @@ enum class HttpStatusCode {
ServiceUnavailable, // 503
};
+// StyleMode is an enumeration used to define the format of the server's
response to the client.
+// This format determines how the response data is presented and interpreted
by the client.
+enum class StyleMode {
+ // This mode includes additional styling elements in the response,
+ // such as CSS, navigation bar, etc.
+ STYLED,
+ // In this mode, the response is sent without any styling elements.
+ UNSTYLED,
+ // In rare cases when a binary data is sent as a response.
+ BINARY
+};
+
// Interface for registering webserver callbacks.
//
// To register a webserver callback for /example/path:
@@ -104,25 +116,25 @@ class WebCallbackRegistry {
virtual ~WebCallbackRegistry() = default;
// Register a callback for a URL path. Path should not include the
- // http://hostname/ prefix. If is_styled is true, the page is meant to be for
+ // http://hostname/ prefix. If style_mode is StyleMode::STYLED, the page is
meant to be for
// people to look at and is styled. If false, it is meant to be for
machines to
// scrape. If is_on_nav_bar is true, a link to this page is
// printed in the navigation bar at the top of each debug page. Otherwise the
// link does not appear, and the page is rendered without HTML headers and
// footers.
- // The first registration's choice of is_styled overrides all
+ // The first registration's choice of style_mode overrides all
// subsequent registrations for that URL.
// For each call to RegisterPathHandler(), the file
$KUDU_HOME/www<path>.mustache
// should exist.
virtual void RegisterPathHandler(const std::string& path, const std::string&
alias,
const PathHandlerCallback& callback,
- bool is_styled, bool is_on_nav_bar) = 0;
+ StyleMode style_mode, bool is_on_nav_bar) =
0;
// Same as RegisterPathHandler(), except that callback produces prerendered
HTML.
// Use RegisterPathHandler() with a mustache template instead.
virtual void RegisterPrerenderedPathHandler(const std::string& path, const
std::string& alias,
const
PrerenderedPathHandlerCallback& callback,
- bool is_styled,
+ StyleMode style_mode,
bool is_on_nav_bar) = 0;
// Register a callback for a URL path that returns binary data, a.k.a. octet