Change C++ application entry point to remove dependency on frontends (#3562)

This commit is contained in:
Eddy Ashton 2022-02-21 10:13:33 +00:00 коммит произвёл GitHub
Родитель d7e1277ed2
Коммит 5b40ba7b42
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 126 добавлений и 167 удалений

Просмотреть файл

@ -1 +1 @@
No no he's not dead, he's, he's restin'!
No no he's not dead, he's, he's resting!

Просмотреть файл

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- The entry point for creation of C++ apps is now `make_user_endpoints()`. The old entry point `get_rpc_handler()` has been removed.
## [2.0.0-rc1]
### Added

Просмотреть файл

@ -3,14 +3,14 @@ Developer API
A CCF application is composed of the following:
- The :ref:`Application Entry Point <build_apps/api:Application Entry Point>` which registers the application in CCF.
- The :ref:`Application Entry Point <build_apps/api:Application Entry Point>` which creates the application in CCF.
- A collection of :cpp:class:`endpoints <ccf::endpoints::Endpoint>` handling HTTP requests and grouped in a single :cpp:class:`registry <ccf::endpoints::EndpointRegistry>`. An :cpp:class:`endpoint <ccf::endpoints::Endpoint>` reads and writes to the key-value store via the :ref:`Key-Value Store API <build_apps/kv/api:Key-Value Store API>`.
- An optional set of :ref:`JavaScript FFI Plugins <build_apps/api:JavaScript FFI Plugins>` that can be registered to extend the built-in JavaScript API surface.
Application Entry Point
-----------------------
.. doxygenfunction:: ccfapp::get_rpc_handler
.. doxygenfunction:: ccfapp::make_user_endpoints
:project: CCF

Просмотреть файл

@ -19,8 +19,7 @@ The Logging application simply has:
.. note::
:cpp:class:`kv::Store` tables are essentially the only interface between CCF
and the application, and the sole mechanism for it to have state.
:cpp:class:`kv::Store` tables are the only interface between CCF and the replicated application, and the sole mechanism for it to have distributed state.
The Logging application keeps its state in a pair of tables, one containing private encrypted logs and the other containing public unencrypted logs. Their type is defined as:
@ -44,10 +43,10 @@ The Logging application simply has:
:lines: 1
:dedent:
RPC Handler
-----------
Application Endpoints
---------------------
The type returned by :cpp:func:`ccfapp::get_rpc_handler()` should subclass :cpp:class:`ccf::RpcFrontend`, passing the base constructor a reference to an implementation of :cpp:class:`ccf::EndpointRegistry`:
The implementation of :cpp:func:`ccfapp::make_user_endpoints()` should return a subclass of :cpp:class:`ccf::endpoints::EndpointRegistry`, containing the endpoints that constitute the app.
.. literalinclude:: ../../samples/apps/logging/logging.cpp
:language: cpp
@ -75,7 +74,9 @@ Each function is installed as the handler for a specific HTTP resource, defined
This example installs at ``"log/private", HTTP_POST``, so will be invoked for HTTP requests beginning :http:POST:`/app/log/private`.
The return value from ``make_endpoint`` is an ``Endpoint&`` object which can be used to alter how the handler is executed. For example, the handler for :http:POST:`/app/log/private` shown above sets a `schema` declaring the types of its request and response bodies. These will be used in calls to the :http:GET:`/app/api` endpoint to populate the relevant parts of the OpenAPI document. There are other endpoints installed for the URI path ``/app/log/private`` with different verbs, to handle :http:GET:`GET </app/log/private>` and :http:DELETE:`DELETE </app/log/private>` requests. Any other verbs, without an installed endpoint, will not be accepted - the framework will return a ``405 Method Not Allowed`` response.
The return value from ``make_endpoint`` is an ``Endpoint&`` object which can be used to alter how the handler is executed. For example, the handler for :http:POST:`/app/log/private` shown above sets a `schema` declaring the types of its request and response bodies. These will be used in calls to the :http:GET:`/app/api` endpoint to populate the relevant parts of the OpenAPI document. That OpenAPI document in turn is used to generate the entries in this documentation describing :http:POST:`/app/log/private`.
There are other endpoints installed for the URI path ``/app/log/private`` with different verbs, to handle :http:GET:`GET </app/log/private>` and :http:DELETE:`DELETE </app/log/private>` requests. Requests with those verbs will be executed by the appropriate handler. Any other verbs, without an installed endpoint, will not be accepted - the framework will return a ``405 Method Not Allowed`` response.
To process the raw body directly, a handler should use the general lambda signature which takes a single ``EndpointContext&`` parameter. Examples of this are also included in the logging sample app. For instance the ``log_record_text`` handler takes a raw string as the request body:
@ -162,7 +163,7 @@ The final piece is the definition of the endpoint itself, which uses an instance
Default Endpoints
~~~~~~~~~~~~~~~~~
The logging app sample exposes several built-in endpoints which are provided by the framework for convenience, such as ``/app/tx``, ``/app/commit``, and ``/app/user_id``. It is also possible to write an app which does not expose these endpoints, either to build a minimal user-facing API or to re-wrap this common functionality in your own format or authentication. A sample of this is provided in ``samples/apps/nobuiltins``. Whereas the logging app declares a registry inheriting from :cpp:class:`ccf::CommonEndpointRegistry`, this app inherits from :cpp:class:`ccf::BaseEndpointRegistry` which does not install any default endpoints:
The logging app sample exposes several built-in endpoints which are provided by the framework for convenience, such as :http:GET:`/app/tx`, :http:GET:`/app/commit`, and :http:GET:`/app/receipt`. It is also possible to write an app which does not expose these endpoints, either to build a minimal user-facing API or to re-wrap this common functionality in your own format or authentication. A sample of this is provided in ``samples/apps/nobuiltins``. Whereas the logging app declares a registry inheriting from :cpp:class:`ccf::CommonEndpointRegistry`, this app inherits from :cpp:class:`ccf::BaseEndpointRegistry` which does not install any default endpoints:
.. literalinclude:: ../../samples/apps/nobuiltins/nobuiltins.cpp
:language: cpp

Просмотреть файл

@ -2,36 +2,57 @@
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/ccf_deprecated.h"
#include "ccf/common_endpoint_registry.h"
#include "ccf/js_plugin.h"
#include "ccf/node_context.h"
#include <memory>
#include <vector>
// Forward declarations, can be removed with deprecation
namespace ccf
{
// Forward declarations
class RpcFrontend;
}
struct NetworkTables;
namespace kv
{
class Store;
}
namespace ccfapp
{
// Forward declaration
struct AbstractNodeContext;
CCF_DEPRECATED("Replace with make_user_endpoints")
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
kv::Store& store, AbstractNodeContext& context);
}
namespace ccf
{
class UserEndpointRegistry : public CommonEndpointRegistry
{
public:
UserEndpointRegistry(ccfapp::AbstractNodeContext& context) :
CommonEndpointRegistry(get_actor_prefix(ActorsType::users), context)
{}
};
}
namespace ccfapp
{
// SNIPPET_START: app_interface
/** To be implemented by the application to be registered by CCF.
/** To be implemented by the application. Creates a collection of endpoints
* which will be exposed to callers under /app.
*
* @param network Access to the network's replicated tables
* @param context Access to node and host services
*
* @return Shared pointer to the application handler instance
* @return Unique pointer to the endpoint registry instance
*/
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
ccf::NetworkTables& network, AbstractNodeContext& context);
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints(
ccfapp::AbstractNodeContext& context);
/** To be implemented by the application to be registered by CCF.
/** To be implemented by the application.
*
* @return Vector of JavaScript FFI plugins
*/

Просмотреть файл

@ -1,32 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/common_endpoint_registry.h"
#include "ccf/node_context.h"
#include "node/rpc/frontend.h"
#include "service/network_tables.h"
namespace ccf
{
class UserEndpointRegistry : public CommonEndpointRegistry
{
public:
UserEndpointRegistry(ccfapp::AbstractNodeContext& context) :
CommonEndpointRegistry(get_actor_prefix(ActorsType::users), context)
{}
};
class SimpleUserRpcFrontend : public RpcFrontend
{
protected:
UserEndpointRegistry common_handlers;
public:
SimpleUserRpcFrontend(
kv::Store& tables, ccfapp::AbstractNodeContext& context) :
RpcFrontend(tables, common_handlers),
common_handlers(context)
{}
};
}

Просмотреть файл

@ -11,7 +11,6 @@
#include "ccf/historical_queries_adapter.h"
#include "ccf/http_query.h"
#include "ccf/indexing/strategies/seqnos_by_key_bucketed.h"
#include "ccf/user_frontend.h"
#include "ccf/version.h"
#include "node/tx_receipt.h"
@ -166,6 +165,13 @@ namespace loggingapp
get_public_params_schema(nlohmann::json::parse(j_get_public_in)),
get_public_result_schema(nlohmann::json::parse(j_get_public_out))
{
openapi_info.title = "CCF Sample Logging App";
openapi_info.description =
"This CCF sample app implements a simple logging application, securely "
"recording messages at client-specified IDs. It demonstrates most of "
"the features available to CCF apps.";
openapi_info.document_version = "1.7.0";
index_per_public_key = std::make_shared<RecordsIndexingStrategy>(
PUBLIC_RECORDS, context.get_lfs_access(), 10000, 20);
context.get_indexing_strategies().install_strategy(index_per_public_key);
@ -1482,38 +1488,15 @@ namespace loggingapp
ccf::UserEndpointRegistry::tick(elapsed, tx_count);
}
};
class Logger : public ccf::RpcFrontend
{
private:
LoggerHandlers logger_handlers;
public:
Logger(ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context) :
ccf::RpcFrontend(*network.tables, logger_handlers),
logger_handlers(context)
{}
void open(std::optional<crypto::Pem*> identity = std::nullopt) override
{
ccf::RpcFrontend::open(identity);
logger_handlers.openapi_info.title = "CCF Sample Logging App";
logger_handlers.openapi_info.description =
"This CCF sample app implements a simple logging application, securely "
"recording messages at client-specified IDs. It demonstrates most of "
"the features available to CCF apps.";
logger_handlers.openapi_info.document_version = "1.7.0";
}
};
}
namespace ccfapp
{
// SNIPPET_START: app_interface
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
ccf::NetworkTables& nwt, ccfapp::AbstractNodeContext& context)
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints(
ccfapp::AbstractNodeContext& context)
{
return make_shared<loggingapp::Logger>(nwt, context);
return std::make_unique<loggingapp::LoggerHandlers>(context);
}
// SNIPPET_END: app_interface
}

Просмотреть файл

@ -329,26 +329,13 @@ namespace nobuiltins
.install();
}
};
class NoBuiltinsFrontend : public ccf::RpcFrontend
{
private:
NoBuiltinsRegistry nbr;
public:
NoBuiltinsFrontend(
ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context) :
ccf::RpcFrontend(*network.tables, nbr),
nbr(context)
{}
};
}
namespace ccfapp
{
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
ccf::NetworkTables& nwt, ccfapp::AbstractNodeContext& context)
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints(
ccfapp::AbstractNodeContext& context)
{
return std::make_shared<nobuiltins::NoBuiltinsFrontend>(nwt, context);
return std::make_unique<nobuiltins::NoBuiltinsRegistry>(context);
}
}

Просмотреть файл

@ -6,10 +6,10 @@
namespace ccfapp
{
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context)
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints(
ccfapp::AbstractNodeContext& context)
{
return get_rpc_handler_impl(network, context);
return make_user_endpoints_impl(context);
}
std::vector<ccf::js::FFIPlugin> get_js_plugins()

Просмотреть файл

@ -5,7 +5,6 @@
#include "ccf/crypto/key_wrap.h"
#include "ccf/crypto/rsa_key_pair.h"
#include "ccf/historical_queries_adapter.h"
#include "ccf/user_frontend.h"
#include "ccf/version.h"
#include "js/wrap.h"
#include "kv/untyped_map.h"
@ -32,7 +31,6 @@ namespace ccfapp
struct JSDynamicEndpoint : public ccf::endpoints::EndpointDefinition
{};
NetworkTables& network;
ccfapp::AbstractNodeContext& context;
metrics::Tracker metrics_tracker;
@ -471,9 +469,8 @@ namespace ccfapp
}
public:
JSHandlers(NetworkTables& network, AbstractNodeContext& context) :
JSHandlers(AbstractNodeContext& context) :
UserEndpointRegistry(context),
network(network),
context(context)
{
metrics_tracker.install_endpoint(*this);
@ -681,21 +678,10 @@ namespace ccfapp
#pragma clang diagnostic pop
class JS : public ccf::RpcFrontend
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints_impl(
ccfapp::AbstractNodeContext& context)
{
private:
JSHandlers js_handlers;
public:
JS(NetworkTables& network, ccfapp::AbstractNodeContext& context) :
ccf::RpcFrontend(*network.tables, js_handlers),
js_handlers(network, context)
{}
};
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler_impl(
NetworkTables& network, ccfapp::AbstractNodeContext& context)
{
return make_shared<JS>(network, context);
return std::make_unique<JSHandlers>(context);
}
} // namespace ccfapp

Просмотреть файл

@ -6,6 +6,6 @@
namespace ccfapp
{
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler_impl(
ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context);
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints_impl(
ccfapp::AbstractNodeContext& context);
}

Просмотреть файл

@ -6,10 +6,10 @@
namespace ccfapp
{
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context)
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints(
ccfapp::AbstractNodeContext& context)
{
return get_rpc_handler_impl(network, context);
return make_user_endpoints_impl(context);
}
std::vector<ccf::js::FFIPlugin> get_js_plugins()

Просмотреть файл

@ -5,11 +5,11 @@
#include "ccf/crypto/key_wrap.h"
#include "ccf/crypto/rsa_key_pair.h"
#include "ccf/historical_queries_adapter.h"
#include "ccf/user_frontend.h"
#include "ccf/version.h"
#include "kv/untyped_map.h"
#include "kv_module_loader.h"
#include "named_auth_policies.h"
#include "service/table_names.h"
#include "tmpl/ccf_global.h"
#include "tmpl/console_global.h"
#include "tmpl/request.h"
@ -35,7 +35,6 @@ namespace ccfapp
struct JSDynamicEndpoint : public ccf::endpoints::EndpointDefinition
{};
NetworkTables& network;
ccfapp::AbstractNodeContext& node_context;
::metrics::Tracker metrics_tracker;
@ -301,9 +300,8 @@ namespace ccfapp
}
public:
V8Handlers(NetworkTables& network, AbstractNodeContext& context) :
V8Handlers(AbstractNodeContext& context) :
UserEndpointRegistry(context),
network(network),
node_context(context)
{
metrics_tracker.install_endpoint(*this);
@ -509,30 +507,14 @@ namespace ccfapp
}
};
/**
* V8 Frontend for RPC calls
*/
class V8Frontend : public ccf::RpcFrontend
{
private:
V8Handlers handlers;
public:
V8Frontend(NetworkTables& network, ccfapp::AbstractNodeContext& context) :
ccf::RpcFrontend(*network.tables, handlers),
handlers(network, context)
{}
};
/// Returns a new V8 Rpc Frontend
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler_impl(
NetworkTables& network, ccfapp::AbstractNodeContext& context)
/// Returns new V8 Endpoints
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints_impl(
ccfapp::AbstractNodeContext& context)
{
// V8 initialization needs to move to a more central place
// once/if V8 is integrated into core CCF.
v8_initialize();
return make_shared<V8Frontend>(network, context);
return std::make_unique<V8Handlers>(context);
}
} // namespace ccfapp

Просмотреть файл

@ -6,6 +6,6 @@
namespace ccfapp
{
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler_impl(
ccf::NetworkTables& network, ccfapp::AbstractNodeContext& context);
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints_impl(
ccfapp::AbstractNodeContext& context);
}

Просмотреть файл

@ -3,7 +3,6 @@
#include "../tpcc_serializer.h"
#include "apps/utils/metrics_tracker.h"
#include "ccf/app_interface.h"
#include "ccf/user_frontend.h"
#include "tpcc_setup.h"
#include "tpcc_tables.h"
#include "tpcc_transactions.h"
@ -145,22 +144,10 @@ namespace ccfapp
}
};
class Tpcc : public ccf::RpcFrontend
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints(
ccfapp::AbstractNodeContext& context)
{
private:
TpccHandlers tpcc_handlers;
public:
Tpcc(kv::Store& store, AbstractNodeContext& context) :
RpcFrontend(store, tpcc_handlers),
tpcc_handlers(context)
{}
};
std::shared_ptr<ccf::RpcFrontend> get_rpc_handler(
NetworkTables& nwt, AbstractNodeContext& context)
{
return make_shared<Tpcc>(*nwt.tables, context);
return std::make_unique<TpccHandlers>(context);
}
}

Просмотреть файл

@ -17,6 +17,7 @@
#include "node/rpc/forwarder.h"
#include "node/rpc/member_frontend.h"
#include "node/rpc/node_frontend.h"
#include "node/rpc/user_frontend.h"
#include "oe_init.h"
#include "ringbuffer_logger.h"
#include "rpc_map.h"
@ -164,7 +165,8 @@ namespace enclave
network, *context, share_manager));
rpc_map->register_frontend<ccf::ActorsType::users>(
ccfapp::get_rpc_handler(network, *context));
std::make_unique<ccf::UserRpcFrontend>(
network, ccfapp::make_user_endpoints(*context)));
rpc_map->register_frontend<ccf::ActorsType::nodes>(
std::make_unique<ccf::NodeRpcFrontend>(network, *context));

Просмотреть файл

@ -8,7 +8,6 @@
#include "ccf/ds/logger.h"
#include "ccf/json_handler.h"
#include "ccf/serdes.h"
#include "ccf/user_frontend.h"
#include "consensus/aft/request.h"
#include "ds/files.h"
#include "frontend_test_infra.h"
@ -34,6 +33,19 @@ std::atomic<uint16_t> threading::ThreadMessaging::thread_count = 0;
using namespace ccf;
using namespace std;
class SimpleUserRpcFrontend : public RpcFrontend
{
protected:
UserEndpointRegistry common_handlers;
public:
SimpleUserRpcFrontend(
kv::Store& tables, ccfapp::AbstractNodeContext& context) :
RpcFrontend(tables, common_handlers),
common_handlers(context)
{}
};
class BaseTestFrontend : public SimpleUserRpcFrontend
{
public:

Просмотреть файл

@ -7,12 +7,12 @@
#include "ccf/ds/logger.h"
#include "ccf/serdes.h"
#include "ccf/service/signed_req.h"
#include "ccf/user_frontend.h"
#include "ds/files.h"
#include "kv/test/null_encryptor.h"
#include "kv/test/stub_consensus.h"
#include "node/history.h"
#include "node/rpc/member_frontend.h"
#include "node/rpc/user_frontend.h"
#include "node_stub.h"
#include "service/genesis_gen.h"

Просмотреть файл

@ -0,0 +1,24 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the Apache 2.0 License.
#pragma once
#include "ccf/app_interface.h"
#include "node/network_state.h"
#include "node/rpc/frontend.h"
namespace ccf
{
class UserRpcFrontend : public RpcFrontend
{
protected:
std::unique_ptr<ccf::endpoints::EndpointRegistry> endpoints;
public:
UserRpcFrontend(
NetworkState& network,
std::unique_ptr<ccf::endpoints::EndpointRegistry>&& endpoints_) :
RpcFrontend(*network.tables, *endpoints_),
endpoints(std::move(endpoints_))
{}
};
}