aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruzhas <uzhas@ydb.tech>2022-08-23 15:26:42 +0300
committeruzhas <uzhas@ydb.tech>2022-08-23 15:26:42 +0300
commit0f22555742cda02eee3aa1517b5033e98712c83d (patch)
tree8f258a81c533b8fd4da9dd0c39c22d082526372d
parent2475a1aeb474274eeb4c291af3802a9c6fafbd56 (diff)
downloadydb-0f22555742cda02eee3aa1517b5033e98712c83d.tar.gz
YQ HTTP initial version
-rw-r--r--ydb/core/mon/async_http_mon.cpp7
-rw-r--r--ydb/core/mon/mon_impl.h4
-rw-r--r--ydb/core/viewer/CMakeLists.txt11
-rw-r--r--ydb/core/viewer/grpc_request_context_wrapper.cpp88
-rw-r--r--ydb/core/viewer/grpc_request_context_wrapper.h51
-rw-r--r--ydb/core/viewer/http_handler.h35
-rw-r--r--ydb/core/viewer/http_router.cpp58
-rw-r--r--ydb/core/viewer/http_router.h46
-rw-r--r--ydb/core/viewer/http_router_ut.cpp57
-rw-r--r--ydb/core/viewer/json/json.cpp102
-rw-r--r--ydb/core/viewer/json/json.h4
-rw-r--r--ydb/core/viewer/json_acl.h4
-rw-r--r--ydb/core/viewer/json_browse.h4
-rw-r--r--ydb/core/viewer/json_bscontrollerinfo.h4
-rw-r--r--ydb/core/viewer/json_cluster.h6
-rw-r--r--ydb/core/viewer/json_compute.h4
-rw-r--r--ydb/core/viewer/json_config.h4
-rw-r--r--ydb/core/viewer/json_content.h6
-rw-r--r--ydb/core/viewer/json_counters.h4
-rw-r--r--ydb/core/viewer/json_describe.h4
-rw-r--r--ydb/core/viewer/json_handlers.h137
-rw-r--r--ydb/core/viewer/json_handlers_fq.cpp601
-rw-r--r--ydb/core/viewer/json_handlers_vdisk.cpp4
-rw-r--r--ydb/core/viewer/json_handlers_viewer.cpp68
-rw-r--r--ydb/core/viewer/json_healthcheck.h4
-rw-r--r--ydb/core/viewer/json_hiveinfo.h4
-rw-r--r--ydb/core/viewer/json_hivestats.h4
-rw-r--r--ydb/core/viewer/json_hotkeys.h4
-rw-r--r--ydb/core/viewer/json_labeledcounters.h4
-rw-r--r--ydb/core/viewer/json_metainfo.h4
-rw-r--r--ydb/core/viewer/json_netinfo.h4
-rw-r--r--ydb/core/viewer/json_nodelist.h4
-rw-r--r--ydb/core/viewer/json_nodes.h6
-rw-r--r--ydb/core/viewer/json_pqconsumerinfo.h7
-rw-r--r--ydb/core/viewer/json_query.h6
-rw-r--r--ydb/core/viewer/json_storage.h6
-rw-r--r--ydb/core/viewer/json_tabletcounters.h4
-rw-r--r--ydb/core/viewer/json_tabletinfo.h4
-rw-r--r--ydb/core/viewer/json_tenantinfo.h4
-rw-r--r--ydb/core/viewer/json_tenants.h4
-rw-r--r--ydb/core/viewer/json_topicinfo.h4
-rw-r--r--ydb/core/viewer/json_vdisk_req.h6
-rw-r--r--ydb/core/viewer/json_wb_req.h6
-rw-r--r--ydb/core/viewer/json_whoami.h6
-rw-r--r--ydb/core/viewer/protos/CMakeLists.txt1
-rw-r--r--ydb/core/viewer/protos/fq.proto105
-rw-r--r--ydb/core/viewer/ut/CMakeLists.darwin.txt1
-rw-r--r--ydb/core/viewer/ut/CMakeLists.linux.txt1
-rw-r--r--ydb/core/viewer/viewer.cpp34
-rw-r--r--ydb/core/viewer/viewer.h38
-rw-r--r--ydb/core/viewer/viewer_ut.cpp25
51 files changed, 1405 insertions, 208 deletions
diff --git a/ydb/core/mon/async_http_mon.cpp b/ydb/core/mon/async_http_mon.cpp
index 348fea33205..953bbec3a02 100644
--- a/ydb/core/mon/async_http_mon.cpp
+++ b/ydb/core/mon/async_http_mon.cpp
@@ -90,6 +90,9 @@ public:
if (Request->Method == "PUT") {
return HTTP_METHOD_PUT;
}
+ if (Request->Method == "DELETE") {
+ return HTTP_METHOD_DELETE;
+ }
return HTTP_METHOD_UNDEFINED;
}
@@ -224,7 +227,7 @@ public:
"Access-Control-Allow-Origin: " << origin << "\r\n"
"Access-Control-Allow-Credentials: true\r\n"
"Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n"
- "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n"
+ "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n"
"Content-Type: " + type + "\r\n"
"Connection: keep-alive\r\n\r\n";
ReplyWith(request->CreateResponseString(response));
@@ -249,7 +252,7 @@ public:
response << "Access-Control-Allow-Origin: " << origin << "\r\n";
response << "Access-Control-Allow-Credentials: true\r\n";
response << "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n";
- response << "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n";
+ response << "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n";
response << "Content-Type: text/html\r\n";
response << "Content-Length: " << body.Size() << "\r\n";
response << "\r\n";
diff --git a/ydb/core/mon/mon_impl.h b/ydb/core/mon/mon_impl.h
index 1ef978750ee..ab56828c764 100644
--- a/ydb/core/mon/mon_impl.h
+++ b/ydb/core/mon/mon_impl.h
@@ -147,7 +147,7 @@ public:
"Access-Control-Allow-Origin: " << origin << "\r\n"
"Access-Control-Allow-Credentials: true\r\n"
"Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n"
- "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n"
+ "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n"
"Content-Type: " + type + "\r\n"
"Connection: Keep-Alive\r\n\r\n";
Result.SetValue(MakeHolder<NMon::TEvHttpInfoRes>(response, 0, NMon::IEvHttpInfoRes::EContentType::Custom));
@@ -170,7 +170,7 @@ public:
response << "Access-Control-Allow-Origin: " << origin << "\r\n";
response << "Access-Control-Allow-Credentials: true\r\n";
response << "Access-Control-Allow-Headers: Content-Type,Authorization,Origin,Accept\r\n";
- response << "Access-Control-Allow-Methods: OPTIONS, GET, POST\r\n";
+ response << "Access-Control-Allow-Methods: OPTIONS, GET, POST, PUT, DELETE\r\n";
response << "Content-Type: text/html\r\n";
response << "Content-Length: " << body.Size() << "\r\n";
response << "\r\n";
diff --git a/ydb/core/viewer/CMakeLists.txt b/ydb/core/viewer/CMakeLists.txt
index aac3f90b701..354d7d5e881 100644
--- a/ydb/core/viewer/CMakeLists.txt
+++ b/ydb/core/viewer/CMakeLists.txt
@@ -18,11 +18,13 @@ target_link_libraries(ydb-core-viewer PUBLIC
cpp-actors-core
library-cpp-archive
cpp-mime-types
+ cpp-protobuf-json
ydb-core-base
core-blobstorage-base
blobstorage-vdisk-common
core-client-server
ydb-core-health_check
+ ydb-core-grpc_services
ydb-core-node_whiteboard
ydb-core-protos
ydb-core-scheme
@@ -30,11 +32,16 @@ target_link_libraries(ydb-core-viewer PUBLIC
ydb-core-util
core-viewer-json
core-viewer-protos
+ yq-libs-result_formatter
library-persqueue-topic_parser
api-protos
lib-deprecated-kicli
+ public-lib-json_value
)
target_sources(ydb-core-viewer PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/core/viewer/http_router.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/core/viewer/grpc_request_context_wrapper.cpp
+ ${CMAKE_SOURCE_DIR}/ydb/core/viewer/json_handlers_fq.cpp
${CMAKE_SOURCE_DIR}/ydb/core/viewer/json_handlers_vdisk.cpp
${CMAKE_SOURCE_DIR}/ydb/core/viewer/json_handlers_viewer.cpp
${CMAKE_SOURCE_DIR}/ydb/core/viewer/viewer.cpp
@@ -54,11 +61,13 @@ target_link_libraries(ydb-core-viewer.global PUBLIC
cpp-actors-core
library-cpp-archive
cpp-mime-types
+ cpp-protobuf-json
ydb-core-base
core-blobstorage-base
blobstorage-vdisk-common
core-client-server
ydb-core-health_check
+ ydb-core-grpc_services
ydb-core-node_whiteboard
ydb-core-protos
ydb-core-scheme
@@ -66,9 +75,11 @@ target_link_libraries(ydb-core-viewer.global PUBLIC
ydb-core-util
core-viewer-json
core-viewer-protos
+ yq-libs-result_formatter
library-persqueue-topic_parser
api-protos
lib-deprecated-kicli
+ public-lib-json_value
)
target_sources(ydb-core-viewer.global PRIVATE
${CMAKE_BINARY_DIR}/ydb/core/viewer/1cdc663173c623f6a008fb99b02498f1.cpp
diff --git a/ydb/core/viewer/grpc_request_context_wrapper.cpp b/ydb/core/viewer/grpc_request_context_wrapper.cpp
new file mode 100644
index 00000000000..4d2886b365b
--- /dev/null
+++ b/ydb/core/viewer/grpc_request_context_wrapper.cpp
@@ -0,0 +1,88 @@
+#include "grpc_request_context_wrapper.h"
+#include "viewer.h"
+
+namespace NKikimr {
+namespace NViewer {
+
+ TGrpcRequestContextWrapper::TGrpcRequestContextWrapper(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, std::unique_ptr<NProtoBuf::Message> request, TReplySender replySender)
+ : ActorSystem(actorSystem)
+ , Viewer(viewer)
+ , Event(event)
+ , Request(std::move(request))
+ , ReplySender(std::move(replySender))
+ , AuthState(true)
+ , DeadlineAt(TInstant::Max())
+ {
+ const auto& params(Event->Get()->Request.GetParams());
+ JsonSettings.EnumAsNumbers = false;
+ JsonSettings.UI64AsString = !FromStringWithDefault<bool>(params.Get("ui64"), false);
+ JsonSettings.EmptyRepeated = true;
+ const TString& timeout = params.Get("timeout");
+ if (timeout) {
+ DeadlineAt = TInstant::Now() + TDuration::MilliSeconds(FromStringWithDefault<ui32>(timeout, 10000));
+ }
+ Y_UNUSED(ActorSystem);
+ Y_UNUSED(Viewer);
+ Y_VERIFY(ActorSystem);
+ Y_VERIFY(Event);
+ }
+
+ const NProtoBuf::Message* TGrpcRequestContextWrapper::GetRequest() const {
+ return Request.get();
+ }
+
+ NGrpc::TAuthState& TGrpcRequestContextWrapper::GetAuthState() {
+ return AuthState;
+ }
+
+ void TGrpcRequestContextWrapper::Reply(NProtoBuf::Message* resp, ui32 status) {
+ Y_UNUSED(resp);
+ Y_UNUSED(status);
+ Y_VERIFY(resp);
+ ReplySender(ActorSystem, Viewer, Event, JsonSettings, resp, status);
+ }
+
+ void TGrpcRequestContextWrapper::Reply(grpc::ByteBuffer* resp, ui32 status) {
+ Y_UNUSED(resp);
+ Y_UNUSED(status);
+ Y_VERIFY(false, "TGrpcRequestContextWrapper::Reply");
+ }
+
+ void TGrpcRequestContextWrapper::ReplyUnauthenticated(const TString& in) {
+ Y_UNUSED(in);
+ ActorSystem->Send(Event->Sender, new NMon::TEvHttpInfoRes(HTTPUNAUTHORIZEDTEXT + in, 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ }
+
+ void TGrpcRequestContextWrapper::ReplyError(grpc::StatusCode code, const TString& msg, const TString& details) {
+ Y_UNUSED(code);
+ Y_UNUSED(msg);
+ ActorSystem->Send(Event->Sender, new NMon::TEvHttpInfoRes(TStringBuilder() << HTTPBADREQUEST_HEADERS << "code: " << (int)code << ", msg: " << msg << ", details: " << details, 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ }
+
+ TInstant TGrpcRequestContextWrapper::Deadline() const {
+ return DeadlineAt;
+ }
+
+ TSet<TStringBuf> TGrpcRequestContextWrapper::GetPeerMetaKeys() const {
+ return {};
+ }
+
+ TVector<TStringBuf> TGrpcRequestContextWrapper::GetPeerMetaValues(TStringBuf key) const {
+ // todo: remap http public headers into internal grpc headers, e.g
+ // Authorization -> x-ydb-auth-ticket
+ // scope/project
+ if (key == "x-ydb-auth-ticket"sv) {
+ key = "authorization"sv;
+ }
+ const THttpHeaders& headers = Event->Get()->Request.GetHeaders();
+ if (auto h = headers.FindHeader(key)) {
+ return { h->Value() };
+ }
+ return {};
+ }
+
+ google::protobuf::Arena* TGrpcRequestContextWrapper::GetArena() {
+ return &Arena;
+ }
+}
+}
diff --git a/ydb/core/viewer/grpc_request_context_wrapper.h b/ydb/core/viewer/grpc_request_context_wrapper.h
new file mode 100644
index 00000000000..27e5cc346f7
--- /dev/null
+++ b/ydb/core/viewer/grpc_request_context_wrapper.h
@@ -0,0 +1,51 @@
+#include <ydb/core/grpc_services/base/base.h>
+#include <ydb/core/viewer/json/json.h>
+#include <ydb/public/api/protos/yq.pb.h>
+
+namespace NKikimr {
+namespace NViewer {
+
+class IViewer;
+
+typedef std::function<void(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, const TJsonSettings& jsonSettings, NProtoBuf::Message* resp, ui32 status)> TReplySender;
+
+class TGrpcRequestContextWrapper : public NGrpc::IRequestContextBase {
+private:
+ TActorSystem* ActorSystem;
+ IViewer* Viewer;
+ NMon::TEvHttpInfo::TPtr Event;
+ std::unique_ptr<NProtoBuf::Message> Request;
+ TReplySender ReplySender;
+ NGrpc::TAuthState AuthState;
+ google::protobuf::Arena Arena;
+ TJsonSettings JsonSettings;
+ TInstant DeadlineAt;
+
+public:
+ TGrpcRequestContextWrapper(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, std::unique_ptr<NProtoBuf::Message> request, TReplySender replySender);
+ virtual const NProtoBuf::Message* GetRequest() const;
+ virtual NGrpc::TAuthState& GetAuthState();
+ virtual void Reply(NProtoBuf::Message* resp, ui32 status = 0);
+ virtual void Reply(grpc::ByteBuffer* resp, ui32 status = 0);
+ virtual void ReplyUnauthenticated(const TString& in);
+ virtual void ReplyError(grpc::StatusCode code, const TString& msg, const TString& details);
+ virtual TInstant Deadline() const;
+ virtual TSet<TStringBuf> GetPeerMetaKeys() const;
+ virtual TVector<TStringBuf> GetPeerMetaValues(TStringBuf key) const;
+ virtual grpc_compression_level GetCompressionLevel() const { return GRPC_COMPRESS_LEVEL_NONE; }
+
+ virtual google::protobuf::Arena* GetArena();
+
+ virtual void AddTrailingMetadata(const TString&, const TString&) {}
+
+ virtual void UseDatabase(const TString& ) {}
+
+ virtual void SetNextReplyCallback(TOnNextReply&&) {}
+ virtual void FinishStreamingOk() {}
+ virtual TAsyncFinishResult GetFinishFuture() { return {}; }
+ virtual TString GetPeer() const { return {}; }
+ virtual bool SslServer() const { return false; }
+};
+
+}
+}
diff --git a/ydb/core/viewer/http_handler.h b/ydb/core/viewer/http_handler.h
new file mode 100644
index 00000000000..9b879d50289
--- /dev/null
+++ b/ydb/core/viewer/http_handler.h
@@ -0,0 +1,35 @@
+#pragma once
+
+#include <library/cpp/actors/core/mon.h>
+
+namespace NActors {
+class IActor;
+}
+
+namespace NKikimr {
+namespace NViewer {
+
+using namespace NActors;
+class IViewer;
+
+struct TRequest {
+ NMon::TEvHttpInfo::TPtr Event;
+ std::map<TString, TString> PathParams;
+};
+
+class TJsonHandlerBase {
+public:
+ typedef std::shared_ptr<TJsonHandlerBase> TPtr;
+
+public:
+ virtual ~TJsonHandlerBase() = default;
+ virtual IActor* CreateRequestActor(IViewer* viewer, const TRequest& request) = 0;
+ virtual TString GetResponseJsonSchema() = 0;
+ virtual TString GetTags() { return TString(); }
+ virtual TString GetRequestSummary() { return TString(); }
+ virtual TString GetRequestDescription() { return TString(); }
+ virtual TString GetRequestParameters() { return TString(); }
+};
+
+}
+}
diff --git a/ydb/core/viewer/http_router.cpp b/ydb/core/viewer/http_router.cpp
new file mode 100644
index 00000000000..c40c48724d5
--- /dev/null
+++ b/ydb/core/viewer/http_router.cpp
@@ -0,0 +1,58 @@
+#include "http_router.h"
+
+namespace NKikimr {
+namespace NViewer {
+
+namespace {
+ bool MatchPath(TStringBuf pathPattern, TStringBuf path, std::map<TString, TString>& pathParams) {
+ const char delim = '/';
+ pathPattern.SkipPrefix("/"sv);
+ path.SkipPrefix("/"sv);
+
+ TStringBuf pathPatternComponent = pathPattern.NextTok(delim);
+ TStringBuf pathComponent = path.NextTok(delim);
+ while (pathPatternComponent && pathComponent) {
+ if (pathPatternComponent.StartsWith('{') && pathPatternComponent.EndsWith('}')) {
+ TStringBuf paramName = pathPatternComponent.SubString(1, pathPatternComponent.Size() - 2);
+ pathParams.emplace(paramName, pathComponent);
+ } else {
+ if (pathPatternComponent != pathComponent) {
+ return false;
+ }
+ }
+ pathPatternComponent = pathPattern.NextTok(delim);
+ pathComponent = path.NextTok(delim);
+ }
+
+ return !pathPattern && !path && !pathPatternComponent && !pathComponent;
+ }
+}
+
+void THttpRequestRouter::RegisterHandler(HTTP_METHOD method, const TString& pathPattern, TJsonHandlerBase::TPtr handler) {
+ Data.emplace(std::make_pair(method, pathPattern), std::move(handler));
+}
+
+std::optional<THandlerWithParams> THttpRequestRouter::ResolveHandler(HTTP_METHOD method, const TStringBuf& path) const {
+ auto it = Data.find(std::pair<HTTP_METHOD, TString>(method, path));
+ if (it != Data.end()) {
+ return THandlerWithParams(it->second, {});
+ }
+
+ for (const auto& [k ,v] : Data) {
+ if (k.first != method) {
+ continue;
+ }
+ std::map<TString, TString> pathParams;
+ if (MatchPath(k.second, path, pathParams)) {
+ return THandlerWithParams(v, pathParams);
+ }
+ }
+ return {};
+}
+
+size_t THttpRequestRouter::GetSize() const {
+ return Data.size();
+}
+
+}
+}
diff --git a/ydb/core/viewer/http_router.h b/ydb/core/viewer/http_router.h
new file mode 100644
index 00000000000..6549dd24760
--- /dev/null
+++ b/ydb/core/viewer/http_router.h
@@ -0,0 +1,46 @@
+#pragma once
+
+#include "http_handler.h"
+
+namespace NKikimr {
+namespace NViewer {
+
+struct THandlerWithParams {
+ THandlerWithParams(TJsonHandlerBase::TPtr handler, std::map<TString, TString> pathParams)
+ : Handler(std::move(handler))
+ , PathParams(std::move(pathParams))
+ {
+ }
+
+ THandlerWithParams() = default;
+ THandlerWithParams(THandlerWithParams&&) = default;
+ THandlerWithParams& operator=(const THandlerWithParams&) = default;
+ THandlerWithParams& operator=(const THandlerWithParams&&) = default;
+
+ TJsonHandlerBase::TPtr Handler;
+ std::map<TString, TString> PathParams;
+};
+
+class THttpRequestRouter {
+public:
+ void RegisterHandler(HTTP_METHOD method, const TString& pathPattern, TJsonHandlerBase::TPtr handler);
+ void RegisterGetHandler(const TString& pathPattern, TJsonHandlerBase::TPtr handler) {
+ RegisterHandler(HTTP_METHOD_GET, pathPattern, handler);
+ }
+
+ std::optional<THandlerWithParams> ResolveHandler(HTTP_METHOD method, const TStringBuf& path) const;
+ size_t GetSize() const;
+
+ template <typename F>
+ void ForEach(F f) const {
+ for (const auto& [p, handler] : Data) {
+ f(p.first, p.second, handler);
+ }
+ }
+
+private:
+ std::map<std::pair<HTTP_METHOD, TString>, TJsonHandlerBase::TPtr> Data;
+};
+
+}
+}
diff --git a/ydb/core/viewer/http_router_ut.cpp b/ydb/core/viewer/http_router_ut.cpp
new file mode 100644
index 00000000000..04e0a0c86e7
--- /dev/null
+++ b/ydb/core/viewer/http_router_ut.cpp
@@ -0,0 +1,57 @@
+#include <library/cpp/testing/unittest/registar.h>
+#include "http_router.h"
+
+using namespace NKikimr;
+using namespace NViewer;
+
+namespace {
+class TTestHandler : public TJsonHandlerBase {
+ int Id;
+public:
+ explicit TTestHandler(int id)
+ : Id(id)
+ {
+ }
+
+ int GetId() const { return Id; }
+ virtual IActor* CreateRequestActor(IViewer* , const TRequest&) { return nullptr; }
+ virtual TString GetResponseJsonSchema() { return TString(); }
+};
+
+std::shared_ptr<TTestHandler> AsTestHandler(TJsonHandlerBase::TPtr h) {
+ return std::static_pointer_cast<TTestHandler>(h);
+}
+
+}
+Y_UNIT_TEST_SUITE(HttpRouter) {
+ Y_UNIT_TEST(Basic) {
+ THttpRequestRouter router;
+ router.RegisterHandler(HTTP_METHOD_GET, "/apix/v1/fq/query", std::make_shared<TTestHandler>(1));
+ router.RegisterHandler(HTTP_METHOD_GET, "/apix/v1/fq/query/{query_id}", std::make_shared<TTestHandler>(2));
+ UNIT_ASSERT_VALUES_EQUAL(router.GetSize(), 2);
+
+ std::optional<THandlerWithParams> resolve1 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq"sv);
+ UNIT_ASSERT(!resolve1);
+
+ std::optional<THandlerWithParams> resolve2 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq/query/1234/status"sv);
+ UNIT_ASSERT(!resolve2);
+
+ std::optional<THandlerWithParams> resolve3 = router.ResolveHandler(HTTP_METHOD_POST, "/apix/v1/fq/query"sv);
+ UNIT_ASSERT(!resolve3);
+
+ std::optional<THandlerWithParams> resolve4 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq/query"sv);
+ UNIT_ASSERT(resolve4);
+ UNIT_ASSERT(resolve4->Handler);
+ UNIT_ASSERT_VALUES_EQUAL(AsTestHandler(resolve4->Handler)->GetId(), 1);
+ UNIT_ASSERT_VALUES_EQUAL(resolve4->PathParams.size(), 0u);
+
+ std::optional<THandlerWithParams> resolve5 = router.ResolveHandler(HTTP_METHOD_GET, "/apix/v1/fq/query/1234"sv);
+ UNIT_ASSERT(resolve5);
+ UNIT_ASSERT(resolve5->Handler);
+ UNIT_ASSERT_VALUES_EQUAL(AsTestHandler(resolve5->Handler)->GetId(), 2);
+ UNIT_ASSERT_VALUES_EQUAL(resolve5->PathParams.size(), 1u);
+
+ UNIT_ASSERT_VALUES_EQUAL(resolve5->PathParams.begin()->first, "query_id");
+ UNIT_ASSERT_VALUES_EQUAL(resolve5->PathParams.begin()->second, "1234");
+ }
+}
diff --git a/ydb/core/viewer/json/json.cpp b/ydb/core/viewer/json/json.cpp
index 51d7c9a32d0..00d969a043f 100644
--- a/ydb/core/viewer/json/json.cpp
+++ b/ydb/core/viewer/json/json.cpp
@@ -4,8 +4,15 @@
#include <util/string/printf.h>
#include <util/charset/utf8.h>
#include <util/stream/str.h>
+#include <google/protobuf/duration.pb.h>
+#include <google/protobuf/timestamp.pb.h>
+#include <google/protobuf/util/time_util.h>
#include "json.h"
+#ifdef GetMessage
+#undef GetMessage
+#endif
+
void TProtoToJson::EscapeJsonString(IOutputStream& os, const TString& s) {
const char* b = s.begin();
const char* e = s.end();
@@ -59,6 +66,22 @@ void TProtoToJson::ProtoToJson(IOutputStream& to, const ::google::protobuf::Enum
}
void TProtoToJson::ProtoToJson(IOutputStream& to, const ::google::protobuf::Message& protoFrom, const TJsonSettings& jsonSettings) {
+ if (protoFrom.GetTypeName() == google::protobuf::Timestamp::descriptor()->full_name()) {
+ auto& ts = static_cast<const google::protobuf::Timestamp&>(protoFrom);
+ to << '"';
+ if (ts.seconds() || ts.nanos()) {
+ to << google::protobuf::util::TimeUtil::ToString(ts);
+ }
+ to << '"';
+ return;
+ }
+
+ if (protoFrom.GetTypeName() == google::protobuf::Duration::descriptor()->full_name()) {
+ auto& d = static_cast<const google::protobuf::Duration&>(protoFrom);
+ to << '"' << google::protobuf::util::TimeUtil::ToString(d) << '"';
+ return;
+ }
+
to << '{';
ProtoToJsonInline(to, protoFrom, jsonSettings);
to << '}';
@@ -423,54 +446,103 @@ void TProtoToJson::ProtoToJsonSchema(IOutputStream& to, const TJsonSettings& jso
to << "\":";
to << "{\"type\":\"oneOf\"}";
}
+ char fieldSeparator = oneofFields ? ',' : ' ';
for (int idx = 0; idx < fields; ++idx) {
const FieldDescriptor* fieldDescriptor = descriptor->field(idx);
- if (idx != 0 || oneofFields != 0) {
- to << ',';
- }
- to << '"';
TString name;
if (jsonSettings.NameGenerator) {
name = jsonSettings.NameGenerator(*fieldDescriptor);
} else {
name = fieldDescriptor->name();
}
+ to << fieldSeparator;
+ fieldSeparator = ',';
+
+ to << '"';
EscapeJsonString(to, name);
to << "\":";
if (fieldDescriptor->is_repeated()) {
to << "{\"type\":\"array\",\"items\":";
}
if (fieldDescriptor->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
- if (descriptors.insert(descriptor).second) {
+ if (fieldDescriptor->message_type()->full_name() == google::protobuf::Timestamp::descriptor()->full_name()) {
+ to << "{\"type\":\"string\",\"format\":\"date-time\"}";
+ } else if (fieldDescriptor->message_type()->full_name() == google::protobuf::Duration::descriptor()->full_name()) {
+ to << "{\"type\":\"string\", \"example\":\"3600s\"}";
+ } else if (descriptors.insert(descriptor).second) {
ProtoToJsonSchema(to, jsonSettings, fieldDescriptor->message_type(), descriptors);
+ descriptors.erase(descriptor);
} else {
to << "{}";
}
} else {
- to << "{\"type\":\"";
+ to << "{";
+ TString type;
+ TString format;
switch (fieldDescriptor->cpp_type()) {
case FieldDescriptor::CPPTYPE_INT32:
+ type = "integer";
+ format = "int32";
+ break;
case FieldDescriptor::CPPTYPE_UINT32:
- case FieldDescriptor::CPPTYPE_ENUM:
- to << "integer";
+ type = "integer";
+ format = "uint32";
break;
- case FieldDescriptor::CPPTYPE_STRING:
case FieldDescriptor::CPPTYPE_INT64:
+ type = "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53))
+ format = "int64";
+ break;
case FieldDescriptor::CPPTYPE_UINT64:
- to << "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53))
+ type = "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53))
+ format = "uint64";
+ break;
+ case FieldDescriptor::CPPTYPE_STRING:
+ case FieldDescriptor::CPPTYPE_ENUM:
+ type = "string"; // because of JS compatibility (JavaScript could not handle large numbers (bigger than 2^53))
break;
case FieldDescriptor::CPPTYPE_FLOAT:
+ type = "number";
+ format = "float";
+ break;
case FieldDescriptor::CPPTYPE_DOUBLE:
- to << "number";
+ type = "number";
+ format = "double";
break;
case FieldDescriptor::CPPTYPE_BOOL:
- to << "boolean";
+ type = "boolean";
break;
case FieldDescriptor::CPPTYPE_MESSAGE:
- to << "object";
+ type = "object";
break;
- };
- to << '"';
+ }
+
+ to << "\"type\":\"" << type << "\"";
+ if (format) {
+ to << ", \"format\":\"" << format << "\"";
+ }
+ if (fieldDescriptor->cpp_type() == FieldDescriptor::CPPTYPE_ENUM) {
+ to << ", \"enum\": [";
+ auto enumDescriptor = fieldDescriptor->enum_type();
+ auto valueCount = enumDescriptor->value_count();
+ auto sep = ' ';
+ TString defaultValue;
+ for (int i = 0; i < valueCount; ++i) {
+ auto enumValueDescriptor = enumDescriptor->value(i);
+ if (jsonSettings.EnumValueFilter && !jsonSettings.EnumValueFilter(enumValueDescriptor->name())) {
+ continue;
+ }
+ to << sep;
+ sep = ',';
+ to << "\"" << enumValueDescriptor->name() << "\"";
+ if (!defaultValue) {
+ defaultValue = enumValueDescriptor->name();
+ }
+ }
+ to << "]";
+ if (defaultValue) {
+ to << ", \"default\": \"" << defaultValue << "\"";
+ }
+ }
to << '}';
}
if (fieldDescriptor->is_repeated()) {
diff --git a/ydb/core/viewer/json/json.h b/ydb/core/viewer/json/json.h
index 01e3b85e2d9..87231fd1cfa 100644
--- a/ydb/core/viewer/json/json.h
+++ b/ydb/core/viewer/json/json.h
@@ -8,12 +8,14 @@ struct TJsonSettings {
using TMapperKey = const ::google::protobuf::FieldDescriptor*;
using TMapperValue = std::function<void(IOutputStream&, const ::google::protobuf::Message&, const TJsonSettings&)>;
using TNameGenerator = std::function<TString(const google::protobuf::FieldDescriptor&)>;
+ using TEnumValueFilter = std::function<bool(const TString& value)>;
bool UI64AsString = true; // JavaScript could not handle large numbers (bigger than 2^53)
bool EnumAsNumbers = true;
bool EmptyRepeated = false;
TString NaN = "null";
std::unordered_map<TMapperKey, TMapperValue> FieldRemapper;
- TNameGenerator NameGenerator = {};
+ TNameGenerator NameGenerator;
+ TEnumValueFilter EnumValueFilter;
};
class TProtoToJson {
diff --git a/ydb/core/viewer/json_acl.h b/ydb/core/viewer/json_acl.h
index 43034830fc4..360e6c81866 100644
--- a/ydb/core/viewer/json_acl.h
+++ b/ydb/core/viewer/json_acl.h
@@ -27,9 +27,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonACL(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonACL(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) {
diff --git a/ydb/core/viewer/json_browse.h b/ydb/core/viewer/json_browse.h
index 53f95f10346..6a5532ed50e 100644
--- a/ydb/core/viewer/json_browse.h
+++ b/ydb/core/viewer/json_browse.h
@@ -70,9 +70,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonBrowse(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonBrowse(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void ParsePath(const TString& path, const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_bscontrollerinfo.h b/ydb/core/viewer/json_bscontrollerinfo.h
index 1daafd2368e..e0360953db4 100644
--- a/ydb/core/viewer/json_bscontrollerinfo.h
+++ b/ydb/core/viewer/json_bscontrollerinfo.h
@@ -27,9 +27,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonBSControllerInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonBSControllerInfo(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_cluster.h b/ydb/core/viewer/json_cluster.h
index c7d15c25dec..4aeacb4f577 100644
--- a/ydb/core/viewer/json_cluster.h
+++ b/ydb/core/viewer/json_cluster.h
@@ -40,12 +40,12 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonCluster(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonCluster(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Initiator(ev->Sender)
+ , Initiator(request.Event->Sender)
, Requested(0)
, Received(0)
- , Event(ev)
+ , Event(request.Event)
{
const auto& params(Event->Get()->Request.GetParams());
JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool>(params.Get("enums"), true);
diff --git a/ydb/core/viewer/json_compute.h b/ydb/core/viewer/json_compute.h
index c8e69815ca4..1cc911265db 100644
--- a/ydb/core/viewer/json_compute.h
+++ b/ydb/core/viewer/json_compute.h
@@ -51,9 +51,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonCompute(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonCompute(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
TString GetDomainId(TPathId pathId) {
diff --git a/ydb/core/viewer/json_config.h b/ydb/core/viewer/json_config.h
index d9c3348a590..3efc43b53d4 100644
--- a/ydb/core/viewer/json_config.h
+++ b/ydb/core/viewer/json_config.h
@@ -22,9 +22,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonConfig(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonConfig(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_content.h b/ydb/core/viewer/json_content.h
index 79b230f1288..9ed057c7886 100644
--- a/ydb/core/viewer/json_content.h
+++ b/ydb/core/viewer/json_content.h
@@ -29,10 +29,10 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonContent(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonContent(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Initiator(ev->Sender)
- , Event(ev)
+ , Initiator(request.Event->Sender)
+ , Event(request.Event)
{}
STFUNC(StateWaitingBrowse) {
diff --git a/ydb/core/viewer/json_counters.h b/ydb/core/viewer/json_counters.h
index c56719c77d5..7e69b505d9c 100644
--- a/ydb/core/viewer/json_counters.h
+++ b/ydb/core/viewer/json_counters.h
@@ -31,9 +31,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonCounters(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonCounters(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
, Requested(0)
, Received(0)
{}
diff --git a/ydb/core/viewer/json_describe.h b/ydb/core/viewer/json_describe.h
index 3f6ec6b59c2..00abe2190a8 100644
--- a/ydb/core/viewer/json_describe.h
+++ b/ydb/core/viewer/json_describe.h
@@ -28,9 +28,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonDescribe(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonDescribe(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) {
diff --git a/ydb/core/viewer/json_handlers.h b/ydb/core/viewer/json_handlers.h
index 526c671d013..84d049be195 100644
--- a/ydb/core/viewer/json_handlers.h
+++ b/ydb/core/viewer/json_handlers.h
@@ -1,25 +1,15 @@
#pragma once
+#include "http_router.h"
#include "viewer.h"
-#include <ydb/core/viewer/json/json.h>
namespace NKikimr::NViewer {
-class TJsonHandlerBase {
-public:
- virtual ~TJsonHandlerBase() = default;
- virtual IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) = 0;
- virtual TString GetResponseJsonSchema() = 0;
- virtual TString GetRequestSummary() { return TString(); }
- virtual TString GetRequestDescription() { return TString(); }
- virtual TString GetRequestParameters() { return TString(); }
-};
-
template <typename ActorRequestType>
class TJsonHandler : public TJsonHandlerBase {
public:
- IActor* CreateRequestActor(IViewer* viewer, NMon::TEvHttpInfo::TPtr& event) override {
- return new ActorRequestType(viewer, event);
+ IActor* CreateRequestActor(IViewer* viewer, const TRequest& request) override {
+ return new ActorRequestType(viewer, request);
}
TString GetResponseJsonSchema() override {
@@ -45,63 +35,95 @@ public:
template <typename TTagInfo>
struct TJsonHandlers {
- THashMap<TString, TAutoPtr<TJsonHandlerBase>> JsonHandlers;
+ THttpRequestRouter Router;
void Init();
void Handle(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev, const TActorContext &ctx) {
NMon::TEvHttpInfo* msg = ev->Get();
- auto itJson = JsonHandlers.find(msg->Request.GetPage()->Path + msg->Request.GetPathInfo());
- if (itJson == JsonHandlers.end()) {
- itJson = JsonHandlers.find(msg->Request.GetPathInfo());
- }
- if (itJson != JsonHandlers.end()) {
- try {
- ctx.ExecutorThread.RegisterActor(itJson->second->CreateRequestActor(viewer, ev));
- }
- catch (const std::exception& e) {
- ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(TString("HTTP/1.1 400 Bad Request\r\n\r\n") + e.what(), 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ auto handlerWithParamsO = Router.ResolveHandler(msg->Request.GetMethod(), msg->Request.GetPage()->Path + msg->Request.GetPathInfo());
+ if (!handlerWithParamsO) {
+ // for legendary /viewer handlers
+ handlerWithParamsO = Router.ResolveHandler(msg->Request.GetMethod(), msg->Request.GetPathInfo());
+ if (!handlerWithParamsO) {
+ ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(NMonitoring::HTTPNOTFOUND));
return;
}
- } else {
- ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(NMonitoring::HTTPNOTFOUND));
+ }
+ try {
+ TRequest request;
+ request.Event = ev;
+ request.PathParams = handlerWithParamsO->PathParams;
+ ctx.ExecutorThread.RegisterActor(handlerWithParamsO->Handler->CreateRequestActor(viewer, request));
+ } catch (const std::exception& e) {
+ ctx.Send(ev->Sender, new NMon::TEvHttpInfoRes(TString("HTTP/1.1 400 Bad Request\r\n\r\n") + e.what(), 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ return;
}
}
- void PrintForSwagger(TStringStream &json) {
- for (auto itJson = JsonHandlers.begin(); itJson != JsonHandlers.end(); ++itJson) {
- if (itJson != JsonHandlers.begin()) {
- json << ',';
- }
- TString name = itJson->first;
- json << "\"/" << name << '"' << ":{";
- json << "\"get\":{";
- json << "\"tags\":[\"" << TTagInfo::TagName << "\"],";
- json << "\"produces\":[\"application/json\"],";
- TString summary = itJson->second->GetRequestSummary();
- if (!summary.empty()) {
- json << "\"summary\":" << summary << ',';
- }
- TString description = itJson->second->GetRequestDescription();
- if (!description.empty()) {
- json << "\"description\":" << description << ',';
- }
- TString parameters = itJson->second->GetRequestParameters();
- if (!parameters.empty()) {
- json << "\"parameters\":" << parameters << ',';
- }
- json << "\"responses\":{";
- json << "\"200\":{";
- TString schema = itJson->second->GetResponseJsonSchema();
- if (!schema.empty()) {
- json << "\"schema\":" << schema;
- }
+ void PrintForSwagger(TStringStream &json) const {
+ std::map<TString, std::map<HTTP_METHOD, TJsonHandlerBase*>> allHandlers;
+
+ Router.ForEach([&](HTTP_METHOD method, const TString& pathPattern, TJsonHandlerBase::TPtr handler) {
+ allHandlers[pathPattern][method] = handler.get();
+ });
+
+ char sep = ' ';
+ for (const auto& [path, method2handler] : allHandlers) {
+ json << sep;
+ sep = ',';
+
+ json << '"' << path << '"' << ":{";
+ char methodSep = ' ';
+ for (const auto& [method, handler] : method2handler) {
+ json << methodSep;
+ methodSep = ',';
+
+ auto methodName = ResolveMethodName(method);
+ json << "\"" << methodName << "\":{";
+ json << "\"tags\":[\"" << TTagInfo::TagName << "\"],";
+ json << "\"produces\":[\"application/json\"],";
+ TString summary = handler->GetRequestSummary();
+ if (!summary.empty()) {
+ json << "\"summary\":" << summary << ',';
+ }
+ TString description = handler->GetRequestDescription();
+ if (!description.empty()) {
+ json << "\"description\":" << description << ',';
+ }
+ TString parameters = handler->GetRequestParameters();
+ if (!parameters.empty()) {
+ json << "\"parameters\":" << parameters << ',';
+ }
+ json << "\"responses\":{";
+ json << "\"200\":{";
+ TString schema = handler->GetResponseJsonSchema();
+ if (!schema.empty()) {
+ json << "\"schema\":" << schema;
+ }
+ json << "}";
json << "}";
json << "}";
- json << "}";
+ }
json << "}";
}
}
+
+ static std::string_view ResolveMethodName(HTTP_METHOD method) {
+ switch (method) {
+ case HTTP_METHOD_GET:
+ return "get"sv;
+ case HTTP_METHOD_POST:
+ return "post"sv;
+ case HTTP_METHOD_PUT:
+ return "put"sv;
+ case HTTP_METHOD_DELETE:
+ return "delete"sv;
+ default:
+ return "unknown http method"sv;
+ }
+ }
+
};
struct TViewerTagInfo {
@@ -110,9 +132,12 @@ struct TViewerTagInfo {
struct TVDiskTagInfo {
static constexpr auto TagName = "vdisk";
};
+struct FQTagInfo {
+ static constexpr auto TagName = "fq";
+};
using TViewerJsonHandlers = TJsonHandlers<TViewerTagInfo>;
using TVDiskJsonHandlers = TJsonHandlers<TVDiskTagInfo>;
-
+using TFQJsonHandlers = TJsonHandlers<FQTagInfo>;
}
diff --git a/ydb/core/viewer/json_handlers_fq.cpp b/ydb/core/viewer/json_handlers_fq.cpp
new file mode 100644
index 00000000000..01c20fb8a33
--- /dev/null
+++ b/ydb/core/viewer/json_handlers_fq.cpp
@@ -0,0 +1,601 @@
+#include "grpc_request_context_wrapper.h"
+#include "http_router.h"
+#include "json_handlers.h"
+#include "viewer.h"
+
+#include <library/cpp/actors/core/actor_bootstrapped.h>
+#include <library/cpp/actors/core/mon.h>
+#include <library/cpp/protobuf/json/json2proto.h>
+#include <ydb/core/protos/services.pb.h>
+#include <ydb/core/grpc_services/grpc_request_proxy.h>
+#include <ydb/core/grpc_services/service_yq.h>
+#include <ydb/core/viewer/protos/fq.pb.h>
+#include <ydb/core/yq/libs/result_formatter/result_formatter.h>
+
+namespace NKikimr {
+namespace NViewer {
+
+using namespace NActors;
+
+using ::google::protobuf::Descriptor;
+using ::google::protobuf::FieldDescriptor;
+using ::google::protobuf::Reflection;
+using ::google::protobuf::RepeatedField;
+using ::google::protobuf::RepeatedPtrField;
+
+#define SIMPLE_COPY_FIELD(field) dst.set_##field(src.field())
+#define SIMPLE_COPY_RENAME_FIELD(srcField, dstField) dst.set_##dstField(src.srcField())
+
+#define SIMPLE_COPY_MUTABLE_FIELD(field) *dst.mutable_##field() = src.field()
+#define SIMPLE_COPY_MUTABLE_RENAME_FIELD(srcField, dstField) *dst.mutable_##dstField() = src.srcField()
+
+#define SIMPLE_COPY_REPEATABLE_FIELD(field) FqConvert(src.field(), *dst.mutable_##field())
+#define SIMPLE_COPY_REPEATABLE_RENAME_FIELD(srcField, dstField) FqConvert(src.srcField(), *dst.mutable_##dstField())
+
+#define FQ_CONVERT_FIELD(field) FqConvert(src.field(), *dst.mutable_##field())
+#define FQ_CONVERT_RENAME_FIELD(srcField, dstField) FqConvert(src.srcField(), *dst.mutable_##dstField())
+
+template <typename T>
+void FqConvert(const T& src, T& dst) {
+ dst.CopyFrom(src);
+}
+
+template <typename T>
+void FqConvert(const T& src, ::google::protobuf::Empty& dst) {
+ Y_UNUSED(src);
+ Y_UNUSED(dst);
+}
+
+template <typename T, typename U>
+void FqConvert(const RepeatedPtrField<T>& src, RepeatedPtrField<U>& dst) {
+ dst.Reserve(src.size());
+ for (auto& v : src) {
+ FqConvert(v, *dst.Add());
+ }
+}
+
+void FqConvert(const Ydb::Operations::Operation& src, FederatedQueryHttp::Error& dst) {
+ SIMPLE_COPY_FIELD(status);
+ SIMPLE_COPY_MUTABLE_FIELD(issues);
+}
+
+#define FQ_CONVERT_QUERY_CONTENT(srcType, dstType) \
+void FqConvert(const srcType& src, dstType& dst) { \
+ SIMPLE_COPY_FIELD(type); \
+ SIMPLE_COPY_FIELD(name); \
+ SIMPLE_COPY_FIELD(text); \
+ SIMPLE_COPY_FIELD(description); \
+}
+
+FQ_CONVERT_QUERY_CONTENT(FederatedQueryHttp::CreateQueryRequest, YandexQuery::QueryContent);
+FQ_CONVERT_QUERY_CONTENT(YandexQuery::QueryContent, FederatedQueryHttp::GetQueryResult);
+
+void FqConvert(const FederatedQueryHttp::CreateQueryRequest& src, YandexQuery::CreateQueryRequest& dst) {
+ FqConvert(src, *dst.mutable_content());
+
+ dst.set_execute_mode(YandexQuery::RUN);
+
+ auto& content = *dst.mutable_content();
+ if (content.type() == YandexQuery::QueryContent::QUERY_TYPE_UNSPECIFIED) {
+ content.set_type(YandexQuery::QueryContent::ANALYTICS);
+ }
+
+ if (content.acl().visibility() == YandexQuery::Acl::VISIBILITY_UNSPECIFIED) {
+ content.mutable_acl()->set_visibility(YandexQuery::Acl::PRIVATE);
+ }
+
+ content.set_automatic(true);
+}
+
+void FqConvert(const YandexQuery::CreateQueryResult& src, FederatedQueryHttp::CreateQueryResult& dst) {
+ SIMPLE_COPY_RENAME_FIELD(query_id, id);
+}
+
+void FqConvert(const YandexQuery::CommonMeta& src, FederatedQueryHttp::QueryMeta& dst) {
+ SIMPLE_COPY_MUTABLE_FIELD(created_at);
+}
+
+void FqConvert(const YandexQuery::QueryMeta& src, FederatedQueryHttp::QueryMeta& dst) {
+ SIMPLE_COPY_MUTABLE_FIELD(submitted_at);
+ SIMPLE_COPY_MUTABLE_FIELD(finished_at);
+ FqConvert(src.common(), dst);
+}
+
+FederatedQueryHttp::GetQueryResult::ComputeStatus RemapQueryStatus(YandexQuery::QueryMeta::ComputeStatus status) {
+ switch (status) {
+ case YandexQuery::QueryMeta::COMPLETED:
+ return FederatedQueryHttp::GetQueryResult::COMPLETED;
+
+ case YandexQuery::QueryMeta::ABORTED_BY_USER:
+ [[fallthrough]];
+ case YandexQuery::QueryMeta::ABORTING_BY_SYSTEM:
+ [[fallthrough]];
+ case YandexQuery::QueryMeta::FAILED:
+ return FederatedQueryHttp::GetQueryResult::FAILED;
+
+ default:
+ return FederatedQueryHttp::GetQueryResult::RUNNING;
+ }
+}
+
+void FqConvert(const YandexQuery::ResultSetMeta& src, FederatedQueryHttp::ResultSetMeta& dst) {
+ SIMPLE_COPY_FIELD(rows_count);
+ SIMPLE_COPY_FIELD(truncated);
+}
+
+void FqConvert(const YandexQuery::Query& src, FederatedQueryHttp::GetQueryResult& dst) {
+ FQ_CONVERT_FIELD(meta);
+
+ FqConvert(src.content(), dst);
+ dst.set_id(src.meta().common().id());
+ dst.set_status(RemapQueryStatus(src.meta().status()));
+
+ for (const auto& result_meta : src.result_set_meta()) {
+ FqConvert(result_meta, *dst.mutable_result_sets()->Add());
+ }
+
+ SIMPLE_COPY_MUTABLE_RENAME_FIELD(issue, issues);
+ dst.mutable_issues()->MergeFrom(src.transient_issue());
+}
+
+void FqConvert(const FederatedQueryHttp::GetQueryRequest& src, YandexQuery::DescribeQueryRequest& dst) {
+ SIMPLE_COPY_FIELD(query_id);
+}
+
+void FqConvert(const YandexQuery::DescribeQueryResult& src, FederatedQueryHttp::GetQueryResult& dst) {
+ FqConvert(src.query(), dst);
+}
+
+void FqConvert(const FederatedQueryHttp::DeleteQueryRequest& src, YandexQuery::DeleteQueryRequest& dst) {
+ SIMPLE_COPY_FIELD(query_id);
+}
+
+void FqConvert(const FederatedQueryHttp::GetQueryStatusRequest& src, YandexQuery::GetQueryStatusRequest& dst) {
+ SIMPLE_COPY_FIELD(query_id);
+}
+
+void FqConvert(const YandexQuery::GetQueryStatusResult& src, FederatedQueryHttp::GetQueryStatusResult& dst) {
+ dst.set_status(RemapQueryStatus(src.status()));
+}
+
+void FqConvert(const FederatedQueryHttp::StopQueryRequest& src, YandexQuery::ControlQueryRequest& dst) {
+ SIMPLE_COPY_FIELD(query_id);
+ dst.set_action(YandexQuery::ABORT);
+}
+
+void FqConvert(const FederatedQueryHttp::GetResultDataRequest& src, YandexQuery::GetResultDataRequest& dst) {
+ SIMPLE_COPY_FIELD(query_id);
+ SIMPLE_COPY_FIELD(result_set_index);
+ SIMPLE_COPY_FIELD(offset);
+ SIMPLE_COPY_FIELD(limit);
+
+ if (!dst.limit()) {
+ dst.set_limit(100);
+ }
+}
+
+void FqConvert(const YandexQuery::GetResultDataResult& src, FederatedQueryHttp::GetResultDataResult& dst) {
+ SIMPLE_COPY_MUTABLE_FIELD(result_set);
+}
+
+template <typename T>
+void FqPackToJson(TStringStream& json, const T& httpResult, const TJsonSettings& jsonSettings) {
+ TProtoToJson::ProtoToJson(json, httpResult, jsonSettings);
+}
+
+void FqPackToJson(TStringStream& json, const FederatedQueryHttp::GetResultDataResult& httpResult, const TJsonSettings&) {
+ auto resultSet = NYdb::TResultSet(httpResult.result_set());
+ NJson::TJsonValue v;
+ NYq::FormatResultSet(v, resultSet, true);
+ NJson::TJsonWriterConfig jsonWriterConfig;
+ jsonWriterConfig.WriteNanAsString = true;
+ NJson::WriteJson(&json, &v, jsonWriterConfig);
+}
+
+template <typename GrpcProtoRequestType, typename HttpProtoRequestType, typename GrpcProtoResultType, typename HttpProtoResultType, typename GrpcProtoResponseType>
+class TGrpcCallWrapper : public TActorBootstrapped<TGrpcCallWrapper<GrpcProtoRequestType, HttpProtoRequestType, GrpcProtoResultType, HttpProtoResultType, GrpcProtoResponseType>> {
+ IViewer* const Viewer;
+ const TRequest Request;
+
+ typedef std::function<std::unique_ptr<NGRpcService::TEvProxyRuntimeEvent>(TIntrusivePtr<NGrpc::IRequestContextBase> ctx)> TGrpcProxyEventFactory;
+ TGrpcProxyEventFactory EventFactory;
+
+ NProtobufJson::TJson2ProtoConfig Json2ProtoConfig;
+
+public:
+ typedef GrpcProtoRequestType TGrpcProtoRequestType;
+ typedef HttpProtoRequestType THttpProtoRequestType;
+ typedef GrpcProtoResultType TGrpcProtoResultType;
+ typedef HttpProtoResultType THttpProtoResultType;
+ typedef GrpcProtoResponseType TGrpcProtoResponseType;
+
+public:
+ static constexpr NKikimrServices::TActivity::EType ActorActivityType() {
+ return NKikimrServices::TActivity::VIEWER_HANDLER;
+ }
+
+ TGrpcCallWrapper(IViewer* viewer, const TRequest& request, TGrpcProxyEventFactory eventFactory)
+ : Viewer(viewer)
+ , Request(request)
+ , EventFactory(eventFactory)
+ {
+ Json2ProtoConfig = NProtobufJson::TJson2ProtoConfig()
+ .SetFieldNameMode(NProtobufJson::TJson2ProtoConfig::FieldNameCamelCase)
+ .SetMapAsObject(true);
+ }
+
+ const NMon::TEvHttpInfo::TPtr& GetEvent() const {
+ return Request.Event;
+ }
+
+ void Bootstrap(const TActorContext& ctx) {
+ auto grpcRequest = std::make_unique<TGrpcProtoRequestType>();
+ if (Parse(ctx, *grpcRequest)) {
+ TIntrusivePtr<TGrpcRequestContextWrapper> requestContext = new TGrpcRequestContextWrapper(ctx.ActorSystem(), Viewer, GetEvent(), std::move(grpcRequest), &SendReply);
+ ctx.Send(NGRpcService::CreateGRpcRequestProxyId(), EventFactory(requestContext).release());
+ }
+
+ this->Die(ctx);
+ }
+
+ bool Parse(const TActorContext& ctx, TGrpcProtoRequestType& grpcRequest) {
+ try {
+ THttpProtoRequestType request;
+ const auto& httpRequest = GetEvent()->Get()->Request;
+ // todo: check Headers to copy idempotency-key into protobuf
+ if (httpRequest.GetMethod() == HTTP_METHOD_POST && httpRequest.GetHeader("content-type") == "application/json"sv) {
+ // todo: fix Duration + Timestamp parsing
+ // todo: PostContent is empty for PUT in monlib
+ NProtobufJson::Json2Proto(httpRequest.GetPostContent(), request, Json2ProtoConfig);
+ }
+
+ const auto& params = httpRequest.GetParams();
+ for (const auto& [name, value] : params) {
+ SetProtoMessageField(request, name, value);
+ }
+
+ // path params should overwrite query params in case of conflict
+ for (const auto& [name, value] : Request.PathParams) {
+ SetProtoMessageField(request, name, value);
+ }
+ FqConvert(request, grpcRequest);
+ return true;
+ } catch (const std::exception& e) {
+ ReplyError(ctx, TStringBuilder() << "Error in parsing: " << e.what() << ", original text: " << GetEvent()->Get()->Request.GetPostContent());
+ return false;
+ }
+ }
+
+ static void SetProtoMessageField(THttpProtoRequestType& request, const TString& name, const TString& value) {
+ const Reflection* reflection = request.GetReflection();
+ const Descriptor* descriptor = request.GetDescriptor();
+ auto field = descriptor->FindFieldByLowercaseName(name);
+ if (!field) {
+ return;
+ }
+
+ switch (field->cpp_type()) {
+ case FieldDescriptor::CPPTYPE_INT32:
+ return reflection->SetInt32(&request, field, FromString<i32>(value));
+ case FieldDescriptor::CPPTYPE_INT64:
+ return reflection->SetInt64(&request, field, FromString<i64>(value));
+ case FieldDescriptor::CPPTYPE_UINT32:
+ return reflection->SetUInt32(&request, field, FromString<ui32>(value));
+ case FieldDescriptor::CPPTYPE_UINT64:
+ return reflection->SetUInt64(&request, field, FromString<ui64>(value));
+ case FieldDescriptor::CPPTYPE_STRING:
+ return reflection->SetString(&request, field, value);
+ default:
+ break;
+ }
+ }
+
+ void ReplyError(const TActorContext& ctx, const TString& error) {
+ ctx.Send(GetEvent()->Sender, new NMon::TEvHttpInfoRes(TStringBuilder() << HTTPBADREQUESTJSON << error, 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ }
+
+ static void SendReply(TActorSystem* actorSystem, IViewer* viewer, const NMon::TEvHttpInfo::TPtr& event, const TJsonSettings& jsonSettings, NProtoBuf::Message* resp, ui32 status) {
+ Y_VERIFY(resp);
+ Y_VERIFY(resp->GetArena());
+ Y_UNUSED(status);
+ auto* typedResponse = static_cast<TGrpcProtoResponseType*>(resp);
+ if (!typedResponse->operation().result().template Is<TGrpcProtoResultType>()) {
+ TStringStream json;
+ auto* httpResult = google::protobuf::Arena::CreateMessage<FederatedQueryHttp::Error>(resp->GetArena());
+ FqConvert(typedResponse->operation(), *httpResult);
+ FqPackToJson(json, *httpResult, jsonSettings);
+
+ // todo: remap ydb status to http code
+ actorSystem->Send(event->Sender, new NMon::TEvHttpInfoRes(TStringBuilder() << HTTPBADREQUESTJSON << json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ return;
+ }
+
+ auto* grpcResult = google::protobuf::Arena::CreateMessage<TGrpcProtoResultType>(resp->GetArena());
+ typedResponse->operation().result().UnpackTo(grpcResult);
+
+ if (THttpProtoResultType::descriptor()->full_name() == google::protobuf::Empty::descriptor()->full_name()) {
+ actorSystem->Send(event->Sender, new NMon::TEvHttpInfoRes(
+ "HTTP/1.1 204 No Content\r\n"
+ "Connection: Keep-Alive\r\n\r\n", 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ return;
+ }
+
+ TStringStream json;
+ auto* httpResult = google::protobuf::Arena::CreateMessage<THttpProtoResultType>(resp->GetArena());
+ FqConvert(*grpcResult, *httpResult);
+ FqPackToJson(json, *httpResult, jsonSettings);
+
+ actorSystem->Send(event->Sender, new NMon::TEvHttpInfoRes(viewer->GetHTTPOKJSON(event->Get()) + json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ }
+};
+
+template <typename ProtoType>
+void ProtoToPublicJsonSchema(IOutputStream& to) {
+ TJsonSettings settings;
+ settings.EnumAsNumbers = false;
+ settings.EmptyRepeated = false;
+ settings.EnumValueFilter = [](const TString& value) {
+ return !value.EndsWith("UNSPECIFIED");
+ };
+
+ TProtoToJson::ProtoToJsonSchema<ProtoType>(to, settings);
+}
+
+
+#define DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, t1, t2, t3, t4, t5) \
+class TJson##action : public TGrpcCallWrapper<t1, t2, t3, t4, t5> { \
+ typedef TGrpcCallWrapper<t1, t2, t3, t4, t5> TBase; \
+public: \
+ TJson##action(IViewer* viewer, const TRequest& request) \
+ : TBase(viewer, request, &NGRpcService::Create##internalAction##RequestOperationCall) {} \
+}
+
+#define DECLARE_YQ_GRPC_ACTOR(action, internalAction) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, FederatedQueryHttp::action##Result, YandexQuery::internalAction##Response)
+#define DECLARE_YQ_GRPC_ACTOR_WIHT_EMPTY_RESULT(action, internalAction) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, ::google::protobuf::Empty, YandexQuery::internalAction##Response)
+
+//#define DECLARE_YQ_GRPC_ACTOR(action) DECLARE_YQ_GRPC_ACTOR_IMPL(action, action, YandexQuery::action##Request, YandexQuery::action##Request, YandexQuery::action##Result, YandexQuery::action##Result, YandexQuery::action##Response)
+//#define DECLARE_YQ_GRPC_ACTOR_WITH_REMAPPING(action, internalAction) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, FederatedQueryHttp::action##Result, YandexQuery::internalAction##Response)
+//#define DECLARE_YQ_GRPC_ACTOR_WITH_REMAPPING_RESULT(action, internalAction, resultType) DECLARE_YQ_GRPC_ACTOR_IMPL(action, internalAction, YandexQuery::internalAction##Request, FederatedQueryHttp::action##Request, YandexQuery::internalAction##Result, resultType, YandexQuery::internalAction##Response)
+
+// create queries
+DECLARE_YQ_GRPC_ACTOR(CreateQuery, CreateQuery);
+
+template <>
+struct TJsonRequestSchema<TJsonCreateQuery> {
+ static TString GetSchema() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::CreateQueryResult>(stream);
+ return stream.Str();
+ }
+};
+
+template <>
+struct TJsonRequestParameters<TJsonCreateQuery> {
+ static TString GetParameters() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::CreateQueryRequest>(stream);
+ auto bodyScheme = stream.Str();
+
+ return TStringBuilder() << R"___([
+ {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"},
+ {"name":"body","in":"body","required":true, "schema": )___" << bodyScheme << "}]";
+ }
+};
+
+template <>
+struct TJsonRequestSummary<TJsonCreateQuery> {
+ static TString GetSummary() {
+ return "\"Create new query\"";
+ }
+};
+
+template <>
+struct TJsonRequestDescription<TJsonCreateQuery> {
+ static TString GetDescription() {
+ return "\"Create new query and optionally run it\"";
+ }
+};
+
+// describe query
+DECLARE_YQ_GRPC_ACTOR(GetQuery, DescribeQuery);
+
+template <>
+struct TJsonRequestSchema<TJsonGetQuery> {
+ static TString GetSchema() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::GetQueryResult>(stream);
+ return stream.Str();
+ }
+};
+
+template <>
+struct TJsonRequestParameters<TJsonGetQuery> {
+ static TString GetParameters() {
+ return R"___([
+ {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"},
+ {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"}
+ ])___";
+ }
+};
+
+template <>
+struct TJsonRequestSummary<TJsonGetQuery> {
+ static TString GetSummary() {
+ return "\"Get information about query\"";
+ }
+};
+
+template <>
+struct TJsonRequestDescription<TJsonGetQuery> {
+ static TString GetDescription() {
+ return "\"Get detailed information about specified query\"";
+ }
+};
+
+// delete query
+DECLARE_YQ_GRPC_ACTOR_WIHT_EMPTY_RESULT(DeleteQuery, DeleteQuery);
+
+template <>
+struct TJsonRequestSchema<TJsonDeleteQuery> {
+ static TString GetSchema() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::DeleteQueryResult>(stream);
+ return stream.Str();
+ }
+};
+
+template <>
+struct TJsonRequestParameters<TJsonDeleteQuery> {
+ static TString GetParameters() {
+ return R"___([
+ {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"},
+ {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"},
+ {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"}
+ ])___";
+ }
+};
+
+template <>
+struct TJsonRequestSummary<TJsonDeleteQuery> {
+ static TString GetSummary() {
+ return "\"Delete query\"";
+ }
+};
+
+template <>
+struct TJsonRequestDescription<TJsonDeleteQuery> {
+ static TString GetDescription() {
+ return "\"Delete existing query by id\"";
+ }
+};
+
+// get query status
+DECLARE_YQ_GRPC_ACTOR(GetQueryStatus, GetQueryStatus);
+
+template <>
+struct TJsonRequestSchema<TJsonGetQueryStatus> {
+ static TString GetSchema() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::GetQueryStatusResult>(stream);
+ return stream.Str();
+ }
+};
+
+template <>
+struct TJsonRequestParameters<TJsonGetQueryStatus> {
+ static TString GetParameters() {
+ return R"___([
+ {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"},
+ {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"},
+ {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"}
+ ])___";
+ }
+};
+
+template <>
+struct TJsonRequestSummary<TJsonGetQueryStatus> {
+ static TString GetSummary() {
+ return "\"Get query status\"";
+ }
+};
+
+template <>
+struct TJsonRequestDescription<TJsonGetQueryStatus> {
+ static TString GetDescription() {
+ return "\"Get status of the query\"";
+ }
+};
+
+// stop query
+DECLARE_YQ_GRPC_ACTOR_WIHT_EMPTY_RESULT(StopQuery, ControlQuery);
+
+template <>
+struct TJsonRequestSchema<TJsonStopQuery> {
+ static TString GetSchema() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::StopQueryResult>(stream);
+ return stream.Str();
+ }
+};
+
+template <>
+struct TJsonRequestParameters<TJsonStopQuery> {
+ static TString GetParameters() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::StopQueryRequest>(stream);
+ auto bodyScheme = stream.Str();
+
+ return TStringBuilder() << R"___([
+ {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"},
+ {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"},
+ {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"},
+ {"name":"body","in":"body","required":false, "schema": )___" << bodyScheme << "}]";
+ }
+};
+
+template <>
+struct TJsonRequestSummary<TJsonStopQuery> {
+ static TString GetSummary() {
+ return "\"Stop query\"";
+ }
+};
+
+template <>
+struct TJsonRequestDescription<TJsonStopQuery> {
+ static TString GetDescription() {
+ return "\"Stop running query\"";
+ }
+};
+
+// get result data
+DECLARE_YQ_GRPC_ACTOR(GetResultData, GetResultData);
+
+template <>
+struct TJsonRequestSchema<TJsonGetResultData> {
+ static TString GetSchema() {
+ TStringStream stream;
+ ProtoToPublicJsonSchema<FederatedQueryHttp::GetResultDataResult>(stream);
+ return stream.Str();
+ }
+};
+
+template <>
+struct TJsonRequestParameters<TJsonGetResultData> {
+ static TString GetParameters() {
+ return R"___([
+ {"name":"folder_id","in":"path","description":"folder id","required":true,"type":"string"},
+ {"name":"idempotency-key","in":"header","description":"idempotency key", "required": false, "type":"string"},
+ {"name":"query_id","in":"path","description":"query id", "required": true, "type":"string"},
+ {"name":"result_set_index","in":"query","description":"result set index","required": true, "type":"integer"},
+ {"name":"offset","in":"query","description":"row offset","default":0, "type":"integer"},
+ {"name":"limit","in":"query","description":"row limit","default":100, "type":"integer"}
+ ])___";
+ }
+};
+
+template <>
+struct TJsonRequestSummary<TJsonGetResultData> {
+ static TString GetSummary() {
+ return "\"Get query results\"";
+ }
+};
+
+template <>
+struct TJsonRequestDescription<TJsonGetResultData> {
+ static TString GetDescription() {
+ return "\"Get query results\"";
+ }
+};
+
+template <>
+void TFQJsonHandlers::Init() {
+ Router.RegisterHandler(HTTP_METHOD_POST, "/fq/json/queries", std::make_shared<TJsonHandler<TJsonCreateQuery>>());
+ Router.RegisterHandler(HTTP_METHOD_GET, "/fq/json/queries/{query_id}", std::make_shared<TJsonHandler<TJsonGetQuery>>());
+ Router.RegisterHandler(HTTP_METHOD_DELETE, "/fq/json/queries/{query_id}", std::make_shared<TJsonHandler<TJsonDeleteQuery>>());
+ Router.RegisterHandler(HTTP_METHOD_GET, "/fq/json/queries/{query_id}/status", std::make_shared<TJsonHandler<TJsonGetQueryStatus>>());
+ Router.RegisterHandler(HTTP_METHOD_GET, "/fq/json/queries/{query_id}/results", std::make_shared<TJsonHandler<TJsonGetResultData>>());
+ Router.RegisterHandler(HTTP_METHOD_POST, "/fq/json/queries/{query_id}/stop", std::make_shared<TJsonHandler<TJsonStopQuery>>());
+}
+
+}
+}
diff --git a/ydb/core/viewer/json_handlers_vdisk.cpp b/ydb/core/viewer/json_handlers_vdisk.cpp
index 4684d8a2ffa..a80b01f7755 100644
--- a/ydb/core/viewer/json_handlers_vdisk.cpp
+++ b/ydb/core/viewer/json_handlers_vdisk.cpp
@@ -8,8 +8,8 @@ namespace NKikimr::NViewer {
template <>
void TVDiskJsonHandlers::Init() {
- JsonHandlers["vdisk/json/vdiskstat"] = new TJsonHandler<TJsonVDiskStat>;
- JsonHandlers["vdisk/json/getblob"] = new TJsonHandler<TJsonGetBlob>;
+ Router.RegisterGetHandler("/vdisk/json/vdiskstat", std::make_shared<TJsonHandler<TJsonVDiskStat>>());
+ Router.RegisterGetHandler("/vdisk/json/getblob", std::make_shared<TJsonHandler<TJsonGetBlob>>());
}
}
diff --git a/ydb/core/viewer/json_handlers_viewer.cpp b/ydb/core/viewer/json_handlers_viewer.cpp
index a564147bb98..57dde5c5bcf 100644
--- a/ydb/core/viewer/json_handlers_viewer.cpp
+++ b/ydb/core/viewer/json_handlers_viewer.cpp
@@ -39,36 +39,38 @@ namespace NKikimr::NViewer {
template <>
void TViewerJsonHandlers::Init() {
- JsonHandlers["/json/nodelist"] = new TJsonHandler<TJsonNodeList>;
- JsonHandlers["/json/nodeinfo"] = new TJsonHandler<TJsonNodeInfo>;
- JsonHandlers["/json/vdiskinfo"] = new TJsonHandler<TJsonVDiskInfo>;
- JsonHandlers["/json/pdiskinfo"] = new TJsonHandler<TJsonPDiskInfo>;
- JsonHandlers["/json/describe"] = new TJsonHandler<TJsonDescribe>;
- JsonHandlers["/json/hotkeys"] = new TJsonHandler<TJsonHotkeys>;
- JsonHandlers["/json/sysinfo"] = new TJsonHandler<TJsonSysInfo>;
- JsonHandlers["/json/tabletinfo"] = new TJsonHandler<TJsonTabletInfo>;
- JsonHandlers["/json/hiveinfo"] = new TJsonHandler<TJsonHiveInfo>;
- JsonHandlers["/json/bsgroupinfo"] = new TJsonHandler<TJsonBSGroupInfo>;
- JsonHandlers["/json/bscontrollerinfo"] = new TJsonHandler<TJsonBSControllerInfo>;
- JsonHandlers["/json/config"] = new TJsonHandler<TJsonConfig>;
- JsonHandlers["/json/counters"] = new TJsonHandler<TJsonCounters>;
- JsonHandlers["/json/topicinfo"] = new TJsonHandler<TJsonTopicInfo>;
- JsonHandlers["/json/pqconsumerinfo"] = new TJsonHandler<TJsonPQConsumerInfo>();
- JsonHandlers["/json/tabletcounters"] = new TJsonHandler<TJsonTabletCounters>;
- JsonHandlers["/json/storage"] = new TJsonHandler<TJsonStorage>;
- JsonHandlers["/json/metainfo"] = new TJsonHandler<TJsonMetaInfo>;
- JsonHandlers["/json/browse"] = new TJsonHandler<TJsonBrowse>;
- JsonHandlers["/json/cluster"] = new TJsonHandler<TJsonCluster>;
- JsonHandlers["/json/content"] = new TJsonHandler<TJsonContent>;
- JsonHandlers["/json/labeledcounters"] = new TJsonHandler<TJsonLabeledCounters>;
- JsonHandlers["/json/tenants"] = new TJsonHandler<TJsonTenants>;
- JsonHandlers["/json/hivestats"] = new TJsonHandler<TJsonHiveStats>;
- JsonHandlers["/json/tenantinfo"] = new TJsonHandler<TJsonTenantInfo>;
- JsonHandlers["/json/whoami"] = new TJsonHandler<TJsonWhoAmI>;
- JsonHandlers["/json/query"] = new TJsonHandler<TJsonQuery>;
- JsonHandlers["/json/netinfo"] = new TJsonHandler<TJsonNetInfo>;
- JsonHandlers["/json/compute"] = new TJsonHandler<TJsonCompute>;
- JsonHandlers["/json/healthcheck"] = new TJsonHandler<TJsonHealthCheck>;
- JsonHandlers["/json/nodes"] = new TJsonHandler<TJsonNodes>;
- JsonHandlers["/json/acl"] = new TJsonHandler<TJsonACL>;
-}}
+ Router.RegisterGetHandler("/json/nodelist", std::make_shared<TJsonHandler<TJsonNodeList>>());
+ Router.RegisterGetHandler("/json/nodeinfo", std::make_shared<TJsonHandler<TJsonNodeInfo>>());
+ Router.RegisterGetHandler("/json/vdiskinfo", std::make_shared<TJsonHandler<TJsonVDiskInfo>>());
+ Router.RegisterGetHandler("/json/pdiskinfo", std::make_shared<TJsonHandler<TJsonPDiskInfo>>());
+ Router.RegisterGetHandler("/json/describe", std::make_shared<TJsonHandler<TJsonDescribe>>());
+ Router.RegisterGetHandler("/json/hotkeys", std::make_shared<TJsonHandler<TJsonHotkeys>>());
+ Router.RegisterGetHandler("/json/sysinfo", std::make_shared<TJsonHandler<TJsonSysInfo>>());
+ Router.RegisterGetHandler("/json/tabletinfo", std::make_shared<TJsonHandler<TJsonTabletInfo>>());
+ Router.RegisterGetHandler("/json/hiveinfo", std::make_shared<TJsonHandler<TJsonHiveInfo>>());
+ Router.RegisterGetHandler("/json/bsgroupinfo", std::make_shared<TJsonHandler<TJsonBSGroupInfo>>());
+ Router.RegisterGetHandler("/json/bscontrollerinfo", std::make_shared<TJsonHandler<TJsonBSControllerInfo>>());
+ Router.RegisterGetHandler("/json/config", std::make_shared<TJsonHandler<TJsonConfig>>());
+ Router.RegisterGetHandler("/json/counters", std::make_shared<TJsonHandler<TJsonCounters>>());
+ Router.RegisterGetHandler("/json/topicinfo", std::make_shared<TJsonHandler<TJsonTopicInfo>>());
+ Router.RegisterGetHandler("/json/pqconsumerinfo", std::make_shared<TJsonHandler<TJsonPQConsumerInfo>>());
+ Router.RegisterGetHandler("/json/tabletcounters", std::make_shared<TJsonHandler<TJsonTabletCounters>>());
+ Router.RegisterGetHandler("/json/storage", std::make_shared<TJsonHandler<TJsonStorage>>());
+ Router.RegisterGetHandler("/json/metainfo", std::make_shared<TJsonHandler<TJsonMetaInfo>>());
+ Router.RegisterGetHandler("/json/browse", std::make_shared<TJsonHandler<TJsonBrowse>>());
+ Router.RegisterGetHandler("/json/cluster", std::make_shared<TJsonHandler<TJsonCluster>>());
+ Router.RegisterGetHandler("/json/content", std::make_shared<TJsonHandler<TJsonContent>>());
+ Router.RegisterGetHandler("/json/labeledcounters", std::make_shared<TJsonHandler<TJsonLabeledCounters>>());
+ Router.RegisterGetHandler("/json/tenants", std::make_shared<TJsonHandler<TJsonTenants>>());
+ Router.RegisterGetHandler("/json/hivestats", std::make_shared<TJsonHandler<TJsonHiveStats>>());
+ Router.RegisterGetHandler("/json/tenantinfo", std::make_shared<TJsonHandler<TJsonTenantInfo>>());
+ Router.RegisterGetHandler("/json/whoami", std::make_shared<TJsonHandler<TJsonWhoAmI>>());
+ Router.RegisterGetHandler("/json/query", std::make_shared<TJsonHandler<TJsonQuery>>());
+ Router.RegisterGetHandler("/json/netinfo", std::make_shared<TJsonHandler<TJsonNetInfo>>());
+ Router.RegisterGetHandler("/json/compute", std::make_shared<TJsonHandler<TJsonCompute>>());
+ Router.RegisterGetHandler("/json/healthcheck", std::make_shared<TJsonHandler<TJsonHealthCheck>>());
+ Router.RegisterGetHandler("/json/nodes", std::make_shared<TJsonHandler<TJsonNodes>>());
+ Router.RegisterGetHandler("/json/acl", std::make_shared<TJsonHandler<TJsonACL>>());
+}
+
+}
diff --git a/ydb/core/viewer/json_healthcheck.h b/ydb/core/viewer/json_healthcheck.h
index 2f0e5efa736..8739f6008ba 100644
--- a/ydb/core/viewer/json_healthcheck.h
+++ b/ydb/core/viewer/json_healthcheck.h
@@ -28,9 +28,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonHealthCheck(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonHealthCheck(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap() {
diff --git a/ydb/core/viewer/json_hiveinfo.h b/ydb/core/viewer/json_hiveinfo.h
index 0fa58d281d2..a229cb7096e 100644
--- a/ydb/core/viewer/json_hiveinfo.h
+++ b/ydb/core/viewer/json_hiveinfo.h
@@ -28,9 +28,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonHiveInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonHiveInfo(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap() {
diff --git a/ydb/core/viewer/json_hivestats.h b/ydb/core/viewer/json_hivestats.h
index 4c2e6230069..0460526b813 100644
--- a/ydb/core/viewer/json_hivestats.h
+++ b/ydb/core/viewer/json_hivestats.h
@@ -27,9 +27,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonHiveStats(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonHiveStats(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap() {
diff --git a/ydb/core/viewer/json_hotkeys.h b/ydb/core/viewer/json_hotkeys.h
index 714a9aa17c4..6ec4cc372f7 100644
--- a/ydb/core/viewer/json_hotkeys.h
+++ b/ydb/core/viewer/json_hotkeys.h
@@ -41,9 +41,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonHotkeys(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonHotkeys(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void FillParams(NKikimrSchemeOp::TDescribePath* record, const TCgiParameters& params) {
diff --git a/ydb/core/viewer/json_labeledcounters.h b/ydb/core/viewer/json_labeledcounters.h
index 7db53ed4e28..11eaacbad04 100644
--- a/ydb/core/viewer/json_labeledcounters.h
+++ b/ydb/core/viewer/json_labeledcounters.h
@@ -33,9 +33,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonLabeledCounters(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonLabeledCounters(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_metainfo.h b/ydb/core/viewer/json_metainfo.h
index f507909e245..97c3bb69e0e 100644
--- a/ydb/core/viewer/json_metainfo.h
+++ b/ydb/core/viewer/json_metainfo.h
@@ -41,9 +41,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonMetaInfo(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonMetaInfo(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_netinfo.h b/ydb/core/viewer/json_netinfo.h
index 8d4ab1ddee1..3c0446ef771 100644
--- a/ydb/core/viewer/json_netinfo.h
+++ b/ydb/core/viewer/json_netinfo.h
@@ -44,9 +44,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonNetInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonNetInfo(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap() {
diff --git a/ydb/core/viewer/json_nodelist.h b/ydb/core/viewer/json_nodelist.h
index 75122941515..c9653d465c4 100644
--- a/ydb/core/viewer/json_nodelist.h
+++ b/ydb/core/viewer/json_nodelist.h
@@ -22,9 +22,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonNodeList(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonNodeList(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_nodes.h b/ydb/core/viewer/json_nodes.h
index c6359409e01..cc58aaec7a3 100644
--- a/ydb/core/viewer/json_nodes.h
+++ b/ydb/core/viewer/json_nodes.h
@@ -54,10 +54,10 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonNodes(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonNodes(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Initiator(ev->Sender)
- , Event(std::move(ev))
+ , Initiator(request.Event->Sender)
+ , Event(request.Event)
{
const auto& params(Event->Get()->Request.GetParams());
JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool>(params.Get("enums"), true);
diff --git a/ydb/core/viewer/json_pqconsumerinfo.h b/ydb/core/viewer/json_pqconsumerinfo.h
index b7526edf7df..e312b585fac 100644
--- a/ydb/core/viewer/json_pqconsumerinfo.h
+++ b/ydb/core/viewer/json_pqconsumerinfo.h
@@ -34,12 +34,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonPQConsumerInfo(
- IViewer* viewer,
- NMon::TEvHttpInfo::TPtr& ev
- )
+ TJsonPQConsumerInfo(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_query.h b/ydb/core/viewer/json_query.h
index ac32ad0fc5c..1a88425b7e7 100644
--- a/ydb/core/viewer/json_query.h
+++ b/ydb/core/viewer/json_query.h
@@ -40,10 +40,10 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonQuery(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonQuery(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Initiator(ev->Sender)
- , Event(ev)
+ , Initiator(request.Event->Sender)
+ , Event(request.Event)
{}
STATEFN(StateWork) {
diff --git a/ydb/core/viewer/json_storage.h b/ydb/core/viewer/json_storage.h
index 4644f6f710c..b48fe875d73 100644
--- a/ydb/core/viewer/json_storage.h
+++ b/ydb/core/viewer/json_storage.h
@@ -65,10 +65,10 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonStorage(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonStorage(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Initiator(ev->Sender)
- , Event(std::move(ev))
+ , Initiator(request.Event->Sender)
+ , Event(request.Event)
{
const auto& params(Event->Get()->Request.GetParams());
JsonSettings.EnumAsNumbers = !FromStringWithDefault<bool>(params.Get("enums"), true);
diff --git a/ydb/core/viewer/json_tabletcounters.h b/ydb/core/viewer/json_tabletcounters.h
index 19202d95d16..6ac06fac553 100644
--- a/ydb/core/viewer/json_tabletcounters.h
+++ b/ydb/core/viewer/json_tabletcounters.h
@@ -33,9 +33,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonTabletCounters(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonTabletCounters(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
static NTabletPipe::TClientConfig InitPipeClientConfig() {
diff --git a/ydb/core/viewer/json_tabletinfo.h b/ydb/core/viewer/json_tabletinfo.h
index 3f273e8e95b..2061dfe2fe2 100644
--- a/ydb/core/viewer/json_tabletinfo.h
+++ b/ydb/core/viewer/json_tabletinfo.h
@@ -55,8 +55,8 @@ class TJsonTabletInfo : public TJsonWhiteboardRequest<TEvWhiteboard::TEvTabletSt
using TThis = TJsonTabletInfo;
TVector<ui64> Tablets;
public:
- TJsonTabletInfo(IViewer *viewer, NMon::TEvHttpInfo::TPtr &ev)
- : TJsonWhiteboardRequest(viewer, ev)
+ TJsonTabletInfo(IViewer* viewer, const TRequest& request)
+ : TJsonWhiteboardRequest(viewer, request)
{}
static NTabletPipe::TClientConfig InitPipeClientConfig() {
diff --git a/ydb/core/viewer/json_tenantinfo.h b/ydb/core/viewer/json_tenantinfo.h
index 9a27f8e9682..515350e8e72 100644
--- a/ydb/core/viewer/json_tenantinfo.h
+++ b/ydb/core/viewer/json_tenantinfo.h
@@ -49,9 +49,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonTenantInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonTenantInfo(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
TString GetDomainId(TPathId pathId) {
diff --git a/ydb/core/viewer/json_tenants.h b/ydb/core/viewer/json_tenants.h
index 098d15813c4..f44f4f516be 100644
--- a/ydb/core/viewer/json_tenants.h
+++ b/ydb/core/viewer/json_tenants.h
@@ -30,9 +30,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonTenants(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonTenants(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap() {
diff --git a/ydb/core/viewer/json_topicinfo.h b/ydb/core/viewer/json_topicinfo.h
index 54f4ccde24b..27109113be4 100644
--- a/ydb/core/viewer/json_topicinfo.h
+++ b/ydb/core/viewer/json_topicinfo.h
@@ -29,9 +29,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonTopicInfo(IViewer* viewer, NMon::TEvHttpInfo::TPtr &ev)
+ TJsonTopicInfo(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
diff --git a/ydb/core/viewer/json_vdisk_req.h b/ydb/core/viewer/json_vdisk_req.h
index 3a6c3d08844..20d5298bac4 100644
--- a/ydb/core/viewer/json_vdisk_req.h
+++ b/ydb/core/viewer/json_vdisk_req.h
@@ -66,10 +66,10 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonVDiskRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonVDiskRequest(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Initiator(ev->Sender)
- , Event(ev)
+ , Initiator(request.Event->Sender)
+ , Event(request.Event)
{}
virtual void Bootstrap() {
diff --git a/ydb/core/viewer/json_wb_req.h b/ydb/core/viewer/json_wb_req.h
index a0f0d9cdda8..8fd19767f30 100644
--- a/ydb/core/viewer/json_wb_req.h
+++ b/ydb/core/viewer/json_wb_req.h
@@ -66,10 +66,10 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonWhiteboardRequest(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonWhiteboardRequest(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Initiator(ev->Sender)
- , Event(ev)
+ , Initiator(request.Event->Sender)
+ , Event(request.Event)
{}
THolder<RequestType> BuildRequest(TNodeId nodeId) {
diff --git a/ydb/core/viewer/json_whoami.h b/ydb/core/viewer/json_whoami.h
index fea374b41df..5bd48a75d40 100644
--- a/ydb/core/viewer/json_whoami.h
+++ b/ydb/core/viewer/json_whoami.h
@@ -22,9 +22,9 @@ public:
return NKikimrServices::TActivity::VIEWER_HANDLER;
}
- TJsonWhoAmI(IViewer* viewer, NMon::TEvHttpInfo::TPtr& ev)
+ TJsonWhoAmI(IViewer* viewer, const TRequest& request)
: Viewer(viewer)
- , Event(ev)
+ , Event(request.Event)
{}
void Bootstrap(const TActorContext& ctx) {
@@ -39,7 +39,7 @@ public:
Y_PROTOBUF_SUPPRESS_NODISCARD userToken.ParseFromString(Event->Get()->UserToken);
TStringStream json;
TProtoToJson::ProtoToJson(json, userToken, JsonSettings);
- ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get()) + json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom));
+ ctx.Send(Event->Sender, new NMon::TEvHttpInfoRes(Viewer->GetHTTPOKJSON(Event->Get()) +json.Str(), 0, NMon::IEvHttpInfoRes::EContentType::Custom));
Die(ctx);
}
diff --git a/ydb/core/viewer/protos/CMakeLists.txt b/ydb/core/viewer/protos/CMakeLists.txt
index 9331c1366e2..a2cdf6fc63f 100644
--- a/ydb/core/viewer/protos/CMakeLists.txt
+++ b/ydb/core/viewer/protos/CMakeLists.txt
@@ -15,6 +15,7 @@ target_link_libraries(core-viewer-protos PUBLIC
contrib-libs-protobuf
)
target_proto_messages(core-viewer-protos PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/core/viewer/protos/fq.proto
${CMAKE_SOURCE_DIR}/ydb/core/viewer/protos/viewer.proto
)
target_proto_addincls(core-viewer-protos
diff --git a/ydb/core/viewer/protos/fq.proto b/ydb/core/viewer/protos/fq.proto
new file mode 100644
index 00000000000..2c6a19f833c
--- /dev/null
+++ b/ydb/core/viewer/protos/fq.proto
@@ -0,0 +1,105 @@
+syntax = "proto3";
+option cc_enable_arenas = true;
+
+package FederatedQueryHttp;
+
+import "ydb/public/api/protos/annotations/validation.proto";
+import "ydb/public/api/protos/ydb_issue_message.proto";
+import "ydb/public/api/protos/ydb_status_codes.proto";
+import "ydb/public/api/protos/ydb_value.proto";
+import "google/protobuf/timestamp.proto";
+import "ydb/public/api/protos/yq.proto";
+
+////////////////////////////////////////////////////////////
+
+message Error {
+ Ydb.StatusIds.StatusCode status = 1;
+ repeated Ydb.Issue.IssueMessage issues = 2;
+}
+
+message QueryMeta {
+ google.protobuf.Timestamp created_at = 1;
+ google.protobuf.Timestamp submitted_at = 2;
+ google.protobuf.Timestamp finished_at = 3;
+}
+
+message Column {
+ string name = 1;
+ string type = 2;
+}
+
+message ResultSetMeta {
+ int64 rows_count = 1 [(Ydb.value) = ">= 0"];
+ bool truncated = 2;
+}
+
+message CreateQueryRequest {
+ YandexQuery.QueryContent.QueryType type = 1;
+ string name = 2 [(Ydb.length).le = 1024];
+ string text = 3 [(Ydb.length).le = 102400]; // The text of the query itself
+ string description = 4 [(Ydb.length).le = 10240]; // Description of the query, there can be any text
+}
+
+message CreateQueryResult {
+ string id = 1 [(Ydb.length).le = 1024];
+}
+
+message GetQueryRequest {
+ string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}];
+}
+
+message GetQueryResult {
+ enum ComputeStatus {
+ COMPUTE_STATUS_UNSPECIFIED = 0;
+ RUNNING = 1;
+ COMPLETED = 2;
+ FAILED = 3;
+ }
+
+ string id = 1;
+ YandexQuery.QueryContent.QueryType type = 2;
+ string name = 3 [(Ydb.length).le = 1024];
+ string description = 4 [(Ydb.length).le = 10240]; // Description of the query, there can be any text
+ ComputeStatus status = 5;
+ string text = 6 [(Ydb.length).le = 102400]; // The text of the query itself
+ QueryMeta meta = 7;
+ repeated Ydb.Issue.IssueMessage issues = 8;
+ repeated ResultSetMeta result_sets = 9;
+}
+
+message GetQueryStatusRequest {
+ string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}];
+}
+
+message GetQueryStatusResult {
+ GetQueryResult.ComputeStatus status = 1;
+}
+
+message DeleteQueryRequest {
+ string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}];
+}
+
+message DeleteQueryResult {
+}
+
+message StopQueryRequest {
+ string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}];
+}
+
+message StopQueryResult {
+}
+
+message GetResultDataRequest {
+ string query_id = 1 [(Ydb.length).range = {min: 1, max: 1024}];
+ int32 result_set_index = 2 [(Ydb.value) = ">= 0"];
+ int64 offset = 3 [(Ydb.value) = ">= 0"];
+ int64 limit = 4 [(Ydb.value) = "[1; 1000]"];
+}
+
+message RowData {
+ repeated string data = 1;
+}
+
+message GetResultDataResult {
+ Ydb.ResultSet result_set = 1;
+}
diff --git a/ydb/core/viewer/ut/CMakeLists.darwin.txt b/ydb/core/viewer/ut/CMakeLists.darwin.txt
index ea1a9b5c678..b7c4189ce10 100644
--- a/ydb/core/viewer/ut/CMakeLists.darwin.txt
+++ b/ydb/core/viewer/ut/CMakeLists.darwin.txt
@@ -31,6 +31,7 @@ target_link_options(ydb-core-viewer-ut PRIVATE
CoreFoundation
)
target_sources(ydb-core-viewer-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/core/viewer/http_router_ut.cpp
${CMAKE_SOURCE_DIR}/ydb/core/viewer/viewer_ut.cpp
)
add_test(
diff --git a/ydb/core/viewer/ut/CMakeLists.linux.txt b/ydb/core/viewer/ut/CMakeLists.linux.txt
index bd8010e7ddf..6ef556bbe63 100644
--- a/ydb/core/viewer/ut/CMakeLists.linux.txt
+++ b/ydb/core/viewer/ut/CMakeLists.linux.txt
@@ -35,6 +35,7 @@ target_link_options(ydb-core-viewer-ut PRIVATE
-ldl
)
target_sources(ydb-core-viewer-ut PRIVATE
+ ${CMAKE_SOURCE_DIR}/ydb/core/viewer/http_router_ut.cpp
${CMAKE_SOURCE_DIR}/ydb/core/viewer/viewer_ut.cpp
)
add_test(
diff --git a/ydb/core/viewer/viewer.cpp b/ydb/core/viewer/viewer.cpp
index d171e6a684e..46a270124a7 100644
--- a/ydb/core/viewer/viewer.cpp
+++ b/ydb/core/viewer/viewer.cpp
@@ -25,6 +25,7 @@
#include "json_bsgroupinfo.h"
#include "json_nodeinfo.h"
#include "json_vdiskinfo.h"
+#include "http_router.h"
namespace NKikimr {
@@ -32,7 +33,6 @@ namespace NViewer {
using namespace NNodeWhiteboard;
-
void SetupPQVirtualHandlers(IViewer* viewer) {
viewer->RegisterVirtualHandler(
NKikimrViewer::EObjectType::Root,
@@ -118,6 +118,13 @@ public:
.UseAuth = true,
.AllowedSIDs = allowedSIDs,
});
+ mon->RegisterActorPage({
+ .Title = "FederatedQuery",
+ .RelPath = "fq",
+ .ActorSystem = ctx.ExecutorThread.ActorSystem,
+ .ActorId = ctx.SelfID,
+ .UseAuth = false,
+ });
auto whiteboardServiceId = NNodeWhiteboard::MakeNodeWhiteboardServiceId(ctx.SelfID.NodeId());
ctx.Send(whiteboardServiceId, new NNodeWhiteboard::TEvWhiteboard::TEvSystemStateAddEndpoint(
"http-mon", Sprintf(":%d", KikimrRunConfig.AppConfig.GetMonitoringConfig().GetMonitoringPort())));
@@ -126,6 +133,7 @@ public:
ViewerJsonHandlers.Init();
VDiskJsonHandlers.Init();
+ FQJsonHandlers.Init();
TWhiteboardInfo<TEvWhiteboard::TEvNodeStateResponse>::InitMerger();
TWhiteboardInfo<TEvWhiteboard::TEvBSGroupStateResponse>::InitMerger();
@@ -170,7 +178,8 @@ public:
private:
TViewerJsonHandlers ViewerJsonHandlers;
TVDiskJsonHandlers VDiskJsonHandlers;
- THashMap<TString, TAutoPtr<TJsonHandlerBase>> JsonHandlers;
+ TFQJsonHandlers FQJsonHandlers;
+
const TKikimrRunConfig KikimrRunConfig;
std::unordered_multimap<NKikimrViewer::EObjectType, TVirtualHandler> VirtualHandlersByParentType;
std::unordered_map<NKikimrViewer::EObjectType, TContentHandler> ContentHandlers;
@@ -206,13 +215,22 @@ private:
json << "},";
json << "\"basePath\": \"/\",";
json << "\"schemes\": [\"" << protocol << "\"],";
- json << R"___("consumes": ["application/json"],
+ json << R"___("securityDefinitions": {
+ "token": {
+ "type": "apiKey",
+ "in": "header",
+ "name": "authorization",
+ "description": "Authentication token"
+ }
+ },
+ "consumes": ["application/json"],
"produces": ["application/json"],
"paths": {)___";
-
ViewerJsonHandlers.PrintForSwagger(json);
json << ',';
VDiskJsonHandlers.PrintForSwagger(json);
+ json << ',';
+ FQJsonHandlers.PrintForSwagger(json);
json << R"___(},"definitions":{)___";
json << R"___(}})___";
@@ -318,12 +336,18 @@ private:
if (msg->Request.GetPathInfo().StartsWith("/json/")) {
if (filename.StartsWith("viewer")) {
ViewerJsonHandlers.Handle(this, ev, ctx);
+ return;
}
if (filename.StartsWith("vdisk")) {
VDiskJsonHandlers.Handle(this, ev, ctx);
+ return;
+ }
+ if (filename.StartsWith("fq")) {
+ FQJsonHandlers.Handle(this, ev, ctx);
+ return;
}
- return;
}
+
if (filename.StartsWith("counters/hosts")) {
ctx.ExecutorThread.RegisterActor(new TCountersHostsList(this, ev));
return;
diff --git a/ydb/core/viewer/viewer.h b/ydb/core/viewer/viewer.h
index 515908ad09d..8c107c2e37d 100644
--- a/ydb/core/viewer/viewer.h
+++ b/ydb/core/viewer/viewer.h
@@ -145,40 +145,32 @@ static const char HTTPOKJSON[] = "HTTP/1.1 200 Ok\r\nAccess-Control-Allow-Origin
static const char HTTPOKTEXT[] = "HTTP/1.1 200 Ok\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\n";
static const char HTTPFORBIDDENJSON[] = "HTTP/1.1 403 Forbidden\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: application/json; charset=utf-8\r\nConnection: Close\r\n\r\n";
static const char HTTPGATEWAYTIMEOUT[] = "HTTP/1.1 504 Gateway Time-out\r\nConnection: Close\r\n\r\nGateway Time-out\r\n";
-static const char HTTPBADREQUEST[] = "HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n\r\nBad Request\r\n";
-static const char HTTPBADREQUEST_HEADERS[] = "HTTP/1.1 400 Bad Request\r\nConnection: Close\r\n\r\n";
+static const char HTTPBADREQUEST[] = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\nBad Request\r\n";
+static const char HTTPBADREQUEST_HEADERS[] = "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\n";
+static const char HTTPBADREQUESTJSON[] = "HTTP/1.1 400 Bad Request\r\nAccess-Control-Allow-Origin: *\r\nContent-Type: application/json; charset=utf-8\r\nConnection: Close\r\n\r\n";
+static const char HTTPUNAUTHORIZEDTEXT[] = "HTTP/1.1 401 Unauthorized \r\nAccess-Control-Allow-Origin: *\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: Close\r\n\r\n";
-template <typename ValueType>
-void SplitIds(TStringBuf source, char delim, TVector<ValueType>& values) {
+template <typename ValueType, typename OutputIteratorType>
+void GenericSplitIds(TStringBuf source, char delim, OutputIteratorType it) {
for (TStringBuf value = source.NextTok(delim); !value.empty(); value = source.NextTok(delim)) {
- if (value == ".") {
- values.emplace_back(ValueType());
- } else {
- values.emplace_back(FromStringWithDefault<ValueType>(value, ValueType()));
- }
+ const auto newValue = (value == ".") ? ValueType() : FromStringWithDefault<ValueType>(value, ValueType());
+ *(it++) = newValue;
}
}
template <typename ValueType>
+void SplitIds(TStringBuf source, char delim, TVector<ValueType>& values) {
+ GenericSplitIds<ValueType>(source, delim, std::back_inserter(values));
+}
+
+template <typename ValueType>
void SplitIds(TStringBuf source, char delim, std::vector<ValueType>& values) {
- for (TStringBuf value = source.NextTok(delim); !value.empty(); value = source.NextTok(delim)) {
- if (value == ".") {
- values.emplace_back(ValueType());
- } else {
- values.emplace_back(FromStringWithDefault<ValueType>(value, ValueType()));
- }
- }
+ GenericSplitIds<ValueType>(source, delim, std::back_inserter(values));
}
template <typename ValueType>
void SplitIds(TStringBuf source, char delim, std::unordered_set<ValueType>& values) {
- for (TStringBuf value = source.NextTok(delim); !value.empty(); value = source.NextTok(delim)) {
- if (value == ".") {
- values.emplace(ValueType());
- } else {
- values.emplace(FromStringWithDefault<ValueType>(value, ValueType()));
- }
- }
+ GenericSplitIds<ValueType>(source, delim, std::inserter(values, values.end()));
}
TString GetHTTPOKJSON();
diff --git a/ydb/core/viewer/viewer_ut.cpp b/ydb/core/viewer/viewer_ut.cpp
index 020239e8a5f..e9c89d42fe7 100644
--- a/ydb/core/viewer/viewer_ut.cpp
+++ b/ydb/core/viewer/viewer_ut.cpp
@@ -1,8 +1,10 @@
#include <library/cpp/testing/unittest/registar.h>
#include <library/cpp/testing/unittest/tests_data.h>
#include <library/cpp/actors/interconnect/interconnect.h>
+#include <library/cpp/json/json_reader.h>
#include <util/stream/null.h>
#include <ydb/core/viewer/protos/viewer.pb.h>
+#include "json_handlers.h"
#include "json_tabletinfo.h"
#include "json_vdiskinfo.h"
#include "json_pdiskinfo.h"
@@ -85,4 +87,27 @@ Y_UNIT_TEST_SUITE(Viewer) {
UNIT_ASSERT_VALUES_EQUAL(result->Record.PDiskStateInfoSize(), 100000);
Ctest << "Data has merged" << Endl;
}
+
+ template <typename T>
+ void TestSwagger() {
+ T h;
+ h.Init();
+
+ TStringStream json;
+ json << "{";
+ h.PrintForSwagger(json);
+ json << "}";
+
+ NJson::TJsonReaderConfig jsonCfg;
+ jsonCfg.DontValidateUtf8 = true;
+ jsonCfg.AllowComments = false;
+
+ ValidateJsonThrow(json.Str(), jsonCfg);
+ }
+
+ Y_UNIT_TEST(Swagger) {
+ TestSwagger<TViewerJsonHandlers>();
+ TestSwagger<TVDiskJsonHandlers>();
+ TestSwagger<TFQJsonHandlers>();
+ }
}