Add hints for HTTP handlers

Add hints to HTTP handlers to help users avoid
misspellings. For example, if a user mistakenly writes
`/dashboad` instead of `/dashboard`, they will now get a
hint that /dashboard is the correct handler.

This change will improve the user experience by making it
easier for users to find the correct handlers.

 #47662
This commit is contained in:
Ruslan Mardugalliamov 2023-08-06 15:33:36 -04:00
parent 57025eed65
commit 5cdeacf4cf
10 changed files with 69 additions and 2 deletions

View File

@ -57,6 +57,7 @@ if (BUILD_STANDALONE_KEEPER)
${CMAKE_CURRENT_SOURCE_DIR}/../../src/IO/ReadBuffer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Server/HTTPPathHints.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Server/KeeperTCPHandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Server/TCPServer.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../../src/Server/NotFoundHandler.cpp

View File

@ -132,21 +132,25 @@ void addCommonDefaultHandlersFactory(HTTPRequestHandlerFactoryMain & factory, IS
auto ping_handler = std::make_shared<HandlingRuleHTTPHandlerFactory<StaticRequestHandler>>(server, ping_response_expression);
ping_handler->attachStrictPath("/ping");
ping_handler->allowGetAndHeadRequest();
factory.addPathToHints("/ping");
factory.addHandler(ping_handler);
auto replicas_status_handler = std::make_shared<HandlingRuleHTTPHandlerFactory<ReplicasStatusHandler>>(server);
replicas_status_handler->attachNonStrictPath("/replicas_status");
replicas_status_handler->allowGetAndHeadRequest();
factory.addPathToHints("/replicas_status");
factory.addHandler(replicas_status_handler);
auto play_handler = std::make_shared<HandlingRuleHTTPHandlerFactory<WebUIRequestHandler>>(server);
play_handler->attachNonStrictPath("/play");
play_handler->allowGetAndHeadRequest();
factory.addPathToHints("/play");
factory.addHandler(play_handler);
auto dashboard_handler = std::make_shared<HandlingRuleHTTPHandlerFactory<WebUIRequestHandler>>(server);
dashboard_handler->attachNonStrictPath("/dashboard");
dashboard_handler->allowGetAndHeadRequest();
factory.addPathToHints("/dashboard");
factory.addHandler(dashboard_handler);
auto js_handler = std::make_shared<HandlingRuleHTTPHandlerFactory<WebUIRequestHandler>>(server);

View File

@ -0,0 +1,16 @@
#include <Server/HTTPPathHints.h>
namespace DB
{
void HTTPPathHints::add(const String & http_path)
{
http_paths.push_back(http_path);
}
std::vector<String> HTTPPathHints::getAllRegisteredNames() const
{
return http_paths;
}
}

View File

@ -0,0 +1,22 @@
#pragma once
#include <base/types.h>
#include <Common/NamePrompter.h>
namespace DB
{
class HTTPPathHints : public IHints<1, HTTPPathHints>
{
public:
std::vector<String> getAllRegisteredNames() const override;
void add(const String & http_path);
private:
std::vector<String> http_paths;
};
using HTTPPathHintsPtr = std::shared_ptr<HTTPPathHints>;
}

View File

@ -29,7 +29,7 @@ std::unique_ptr<HTTPRequestHandler> HTTPRequestHandlerFactoryMain::createRequest
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_HEAD
|| request.getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
{
return std::unique_ptr<HTTPRequestHandler>(new NotFoundHandler);
return std::unique_ptr<HTTPRequestHandler>(new NotFoundHandler(hints.getHints(request.getURI())));
}
return nullptr;

View File

@ -1,6 +1,7 @@
#pragma once
#include <Server/HTTP/HTTPRequestHandlerFactory.h>
#include <Server/HTTPPathHints.h>
#include <vector>
@ -15,11 +16,14 @@ public:
void addHandler(HTTPRequestHandlerFactoryPtr child_factory) { child_factories.emplace_back(child_factory); }
void addPathToHints(const std::string & http_path) { hints.add(http_path); }
std::unique_ptr<HTTPRequestHandler> createRequestHandler(const HTTPServerRequest & request) override;
private:
Poco::Logger * log;
std::string name;
HTTPPathHints hints;
std::vector<HTTPRequestHandlerFactoryPtr> child_factories;
};

View File

@ -10,7 +10,8 @@ void NotFoundHandler::handleRequest(HTTPServerRequest & request, HTTPServerRespo
try
{
response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
*response.send() << "There is no handle " << request.getURI() << "\n\n"
*response.send() << "There is no handle " << request.getURI()
<< (!hints.empty() ? fmt::format(". Maybe you meant {}.", hints.front()) : "") << "\n\n"
<< "Use / or /ping for health checks.\n"
<< "Or /replicas_status for more sophisticated health checks.\n\n"
<< "Send queries from your program with POST method or GET /?query=...\n\n"

View File

@ -9,7 +9,10 @@ namespace DB
class NotFoundHandler : public HTTPRequestHandler
{
public:
NotFoundHandler(std::vector<std::string> hints_) : hints(std::move(hints_)) {}
void handleRequest(HTTPServerRequest & request, HTTPServerResponse & response) override;
private:
std::vector<std::string> hints;
};
}

View File

@ -0,0 +1,4 @@
There is no handle /sashboards. Maybe you meant /dashboard
There is no handle /sashboard. Maybe you meant /dashboard
There is no handle /sashboarb. Maybe you meant /dashboard
There is no handle /sashboaxb. Maybe you meant /dashboard

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
CURDIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
# shellcheck source=../shell_config.sh
. "$CURDIR"/../shell_config.sh
export CLICKHOUSE_URL="${CLICKHOUSE_PORT_HTTP_PROTO}://${CLICKHOUSE_HOST}:${CLICKHOUSE_PORT_HTTP}/"
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}sashboards" | grep -o ".* Maybe you meant /dashboard"
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}sashboard" | grep -o ".* Maybe you meant /dashboard"
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}sashboarb" | grep -o ".* Maybe you meant /dashboard"
${CLICKHOUSE_CURL} -sS "${CLICKHOUSE_URL}sashboaxb" | grep -o ".* Maybe you meant /dashboard"