Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 13 commits into from
Feb 21, 2022
2 changes: 1 addition & 1 deletion .daily_canary
Original file line number Diff line number Diff line change
@@ -1 +1 @@
No no he's not dead, he's, he's restin'!
No no he's not dead, he's, he's resting!
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions doc/build_apps/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
15 changes: 8 additions & 7 deletions doc/build_apps/logging_cpp.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand Down Expand Up @@ -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:

Expand Down Expand Up @@ -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
Expand Down
41 changes: 31 additions & 10 deletions include/ccf/app_interface.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down
32 changes: 0 additions & 32 deletions include/ccf/user_frontend.h

This file was deleted.

37 changes: 10 additions & 27 deletions samples/apps/logging/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
}
19 changes: 3 additions & 16 deletions samples/apps/nobuiltins/nobuiltins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 3 deletions src/apps/js_generic/js_generic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
24 changes: 5 additions & 19 deletions src/apps/js_generic/js_generic_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -32,7 +31,6 @@ namespace ccfapp
struct JSDynamicEndpoint : public ccf::endpoints::EndpointDefinition
{};

NetworkTables& network;
ccfapp::AbstractNodeContext& context;
metrics::Tracker metrics_tracker;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions src/apps/js_generic/js_generic_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 3 additions & 3 deletions src/apps/js_v8/js_v8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Loading