diff --git a/docs/api/v2/node.md b/docs/api/v2/node.md index 8c9006e1b..740c2dac8 100644 --- a/docs/api/v2/node.md +++ b/docs/api/v2/node.md @@ -91,3 +91,8 @@ proc resume*(node: WakuNode, peerList: Option[seq[PeerInfo]]) = ## JSON RPC TODO To specify + + +## REST API + +[Here](./rest-api.md) you can find more details on the Node HTTP REST API. diff --git a/docs/api/v2/rest-api.md b/docs/api/v2/rest-api.md new file mode 100644 index 000000000..f874dcc62 --- /dev/null +++ b/docs/api/v2/rest-api.md @@ -0,0 +1,55 @@ +## HTTP REST API + +Similar to the JSON-RPC API, the HTTP REST API consists of a set of methods operating on the Waku Node remotely over HTTP. + +This API is divided in different _namespaces_ which group a set of resources: + +| Namespace | Description | +------------|-------------- +| `/debug` | Information about a Waku v2 node. | +| `/relay` | Control of the relaying of messages. See [11/WAKU2-RELAY](https://rfc.vac.dev/spec/11/) RFC | +| `/store` | Retrieve the message history. See [13/WAKU2-STORE](https://rfc.vac.dev/spec/13/) RFC | +| `/filter` | Control of the content filtering. See [12/WAKU2-FILTER](https://rfc.vac.dev/spec/12/) RFC | +| `/admin` | Privileged access to the internal operations of the node. | +| `/private` | Provides functionality to encrypt/decrypt `WakuMessage` payloads using either symmetric or asymmetric cryptography. This allows backwards compatibility with Waku v1 nodes. | + +The full HTTP REST API documentation can be found here: [TBD]() + +### API Specification + +The HTTP REST API has been designed following the OpenAPI 3.0.3 standard specification format. The OpenAPI specification file can be found here: [TBD]() + +Check the [OpenAPI Tools](https://openapi.tools/) site for the right tool for you (e.g. REST API client generator) + + +### Usage example + +#### [`get_waku_v2_debug_v1_info`](https://rfc.vac.dev/spec/16/#get_waku_v2_debug_v1_info) + +JSON-RPC call: + +```bash +curl -d '{"jsonrpc":"2.0","method":"get_waku_v2_debug_v1_info","params":[],"id":1}' -H 'Content-Type: application/json' localhost:8645 -s | jq +``` + +Equivalent call for the REST API: + +```bash +curl http://localhost:8645/debug/v1/info -s | jq +``` + + +### Node configuration + +A subset of the node configuration can be used to modify the behaviour of the HTTP REST API. These are the relevant command line options: + +| CLI option | Description | Default value | +|------------|-------------|---------------| +|`--rest` | Enable Waku REST HTTP server. | `false` | +|`--rest-address` | Listening address of the REST HTTP server. | `127.0.0.1` | +|`--rest-port` | Listening port of the REST HTTP server. | `8645` | +|`--rest-relay-cache-capacity` | Capacity of the Relay REST API message cache. | `30` | +|`--rest-admin` | Enable access to REST HTTP Admin API. | `false` | +|`--rest-private` | Enable access to REST HTTP Private API. | `false` | + +Note that these command line options have their counterpart option in the node configuration file. diff --git a/docs/operators/quickstart.md b/docs/operators/quickstart.md index 501abd282..6057ad2a3 100644 --- a/docs/operators/quickstart.md +++ b/docs/operators/quickstart.md @@ -26,3 +26,17 @@ A running nwaku node can be interacted with using the [Waku v2 JSON RPC API](htt > **Note:** Private and Admin API functionality are disabled by default. To configure a nwaku node with these enabled, use the `--rpc-admin:true` and `--rpc-private:true` CLI options. + +```bash +curl -d '{"jsonrpc":"2.0","method":"get_waku_v2_debug_v1_info","params":[],"id":1}' -H 'Content-Type: application/json' localhost:8546 -s | jq +``` + + +Or using the [Waku v2 HTTP REST API](../api/v2/rest-api.md): + +> **Note:** REST API functionality is in ALPHA and therefore it is disabled by default. To configure a nwaku node with this enabled, use the `--rest:true` CLI option. + + +```bash +curl http://localhost:8546/debug/v1/info -s | jq +``` diff --git a/tests/v2/test_rest_debug_api.nim b/tests/v2/test_rest_debug_api.nim index 3e3586ddf..3fff36531 100644 --- a/tests/v2/test_rest_debug_api.nim +++ b/tests/v2/test_rest_debug_api.nim @@ -32,12 +32,7 @@ suite "REST API - Debug": let restPort = Port(8546) let restAddress = ValidIpAddress.init("0.0.0.0") - let restServer = RestServerRef.init( - restAddress, - restPort, - none(string), - none(RestServerConf) - ) + let restServer = RestServerRef.init(restAddress, restPort).tryGet() installDebugApiHandlers(restServer.router, node) restServer.start() diff --git a/tests/v2/test_rest_relay_api.nim b/tests/v2/test_rest_relay_api.nim index df24b08b8..5923d2726 100644 --- a/tests/v2/test_rest_relay_api.nim +++ b/tests/v2/test_rest_relay_api.nim @@ -43,12 +43,7 @@ suite "REST API - Relay": let restPort = Port(8546) let restAddress = ValidIpAddress.init("0.0.0.0") - let restServer = RestServerRef.init( - restAddress, - restPort, - none(string), - none(RestServerConf) - ) + let restServer = RestServerRef.init(restAddress, restPort).tryGet() let topicCache = TopicCache.init() @@ -93,12 +88,7 @@ suite "REST API - Relay": let restPort = Port(8546) let restAddress = ValidIpAddress.init("0.0.0.0") - let restServer = RestServerRef.init( - restAddress, - restPort, - none(string), - none(RestServerConf) - ) + let restServer = RestServerRef.init(restAddress, restPort).tryGet() let topicCache = TopicCache.init() topicCache.subscribe("pubsub-topic-1") @@ -146,12 +136,7 @@ suite "REST API - Relay": let restPort = Port(8546) let restAddress = ValidIpAddress.init("0.0.0.0") - let restServer = RestServerRef.init( - restAddress, - restPort, - none(string), - none(RestServerConf) - ) + let restServer = RestServerRef.init(restAddress, restPort).tryGet() let pubSubTopic = "/waku/2/default-waku/proto" let messages = @[ @@ -203,12 +188,7 @@ suite "REST API - Relay": # RPC server setup let restPort = Port(8546) let restAddress = ValidIpAddress.init("0.0.0.0") - let restServer = RestServerRef.init( - restAddress, - restPort, - none(string), - none(RestServerConf) - ) + let restServer = RestServerRef.init(restAddress, restPort).tryGet() let topicCache = TopicCache.init() diff --git a/waku/v2/node/README.md b/waku/v2/node/README.md index 3d7b1cccc..a42f04809 100644 --- a/waku/v2/node/README.md +++ b/waku/v2/node/README.md @@ -2,4 +2,4 @@ This folder contains code related to running a `wakunode2` process. The main entrypoint is the `wakunode2` file. -See `../../docs/api/v2/node.md` for more details on the the Nim Node API. +See `../../docs/api/v2/node.md` for more details on the Nim Node API. diff --git a/waku/v2/node/config.nim b/waku/v2/node/config.nim index a18e5b71a..12a57e3fc 100644 --- a/waku/v2/node/config.nim +++ b/waku/v2/node/config.nim @@ -267,6 +267,38 @@ type desc: "Enable access to JSON-RPC Private API: true|false", defaultValue: false name: "rpc-private" }: bool + + ## REST HTTP config + + rest* {. + desc: "Enable Waku REST HTTP server: true|false", + defaultValue: false + name: "rest" }: bool + + restAddress* {. + desc: "Listening address of the REST HTTP server.", + defaultValue: ValidIpAddress.init("127.0.0.1") + name: "rest-address" }: ValidIpAddress + + restPort* {. + desc: "Listening port of the REST HTTP server.", + defaultValue: 8645 + name: "rest-port" }: uint16 + + restRelayCacheCapaciy* {. + desc: "Capacity of the Relay REST API message cache.", + defaultValue: 30 + name: "rest-relay-cache-capacity" }: uint32 + + restAdmin* {. + desc: "Enable access to REST HTTP Admin API: true|false", + defaultValue: false + name: "rest-admin" }: bool + + restPrivate* {. + desc: "Enable access to REST HTTP Private API: true|false", + defaultValue: false + name: "rest-private" }: bool ## Metrics config diff --git a/waku/v2/node/rest/server.nim b/waku/v2/node/rest/server.nim index 19e642f09..a0d617579 100644 --- a/waku/v2/node/rest/server.nim +++ b/waku/v2/node/rest/server.nim @@ -1,26 +1,17 @@ {.push raises: [Defect].} import - std/[os, times], - std/typetraits, - stew/[byteutils, io2], + stew/results, stew/shims/net, - chronicles, chronos, - metrics, metrics/chronos_httpserver, - bearssl, + chronicles, + chronos, presto -proc getRouter(allowedOrigin: Option[string]): RestRouter = - # TODO: Review this `validate` method. Check in nim-presto what is this used for. - proc validate(pattern: string, value: string): int = - ## This is rough validation procedure which should be simple and fast, - ## because it will be used for query routing. - if pattern.startsWith("{") and pattern.endsWith("}"): 0 - else: 1 +type RestServerResult*[T] = Result[T, cstring] - RestRouter.init(validate, allowedOrigin = allowedOrigin) +### Configuration type RestServerConf* = object cacheSize*: Natural ## \ @@ -40,7 +31,7 @@ type RestServerConf* = object maxRequestHeadersSize*: Natural ## \ ## Maximum size of REST request headers (kilobytes) -proc default(T: type RestServerConf): RestServerConf = +proc default*(T: type RestServerConf): T = RestServerConf( cacheSize: 3, cacheTtl: 60, @@ -50,19 +41,28 @@ proc default(T: type RestServerConf): RestServerConf = ) -template init*(T: type RestServerRef, +### Initialization + +proc getRouter(allowedOrigin: Option[string]): RestRouter = + # TODO: Review this `validate` method. Check in nim-presto what is this used for. + proc validate(pattern: string, value: string): int = + ## This is rough validation procedure which should be simple and fast, + ## because it will be used for query routing. + if pattern.startsWith("{") and pattern.endsWith("}"): 0 + else: 1 + + RestRouter.init(validate, allowedOrigin = allowedOrigin) + +proc init*(T: type RestServerRef, ip: ValidIpAddress, port: Port, - allowedOrigin: Option[string], - config: Option[RestServerConf]): T = + allowedOrigin=none(string), + conf=RestServerConf.default()): RestServerResult[T] = let address = initTAddress(ip, port) let serverFlags = { HttpServerFlags.QueryCommaSeparatedArray, HttpServerFlags.NotifyDisconnect } - let conf = if config.isSome: config.get() - else: RestServerConf.default() - let headersTimeout = if conf.requestTimeout == 0: chronos.InfiniteDuration else: seconds(int64(conf.requestTimeout)) @@ -70,18 +70,24 @@ template init*(T: type RestServerRef, maxRequestBodySize = conf.maxRequestBodySize * 1024 let router = getRouter(allowedOrigin) - let res = RestServerRef.new( - router, - address, - serverFlags = serverFlags, - httpHeadersTimeout = headersTimeout, - maxHeadersSize = maxHeadersSize, - maxRequestBodySize = maxRequestBodySize - ) - if res.isErr(): - notice "Rest server could not be started", address = $address, reason = res.error() - nil - else: - notice "Starting REST HTTP server", url = "http://" & $ip & ":" & $port & "/" - res.get() + var res: RestResult[RestServerRef] + try: + res = RestServerRef.new( + router, + address, + serverFlags = serverFlags, + httpHeadersTimeout = headersTimeout, + maxHeadersSize = maxHeadersSize, + maxRequestBodySize = maxRequestBodySize + ) + except CatchableError as ex: + return err(cstring(ex.msg)) + + res + +proc newRestHttpServer*(ip: ValidIpAddress, port: Port, + allowedOrigin=none(string), + conf=RestServerConf.default()): RestServerResult[RestServerRef] = + RestServerRef.init(ip, port, allowedOrigin, conf) + diff --git a/waku/v2/node/wakunode2.nim b/waku/v2/node/wakunode2.nim index b04cfd921..7be90cc86 100644 --- a/waku/v2/node/wakunode2.nim +++ b/waku/v2/node/wakunode2.nim @@ -791,6 +791,7 @@ when isMainModule: ../../common/utils/nat, ./config, ./waku_setup, + ./wakunode2_setup_rest, ./storage/message/waku_message_store, ./storage/peer/waku_peer_storage @@ -1078,11 +1079,14 @@ when isMainModule: # 6/7 Start monitoring tools and external interfaces proc startExternal(node: WakuNode, conf: WakuNodeConf): SetupResult[bool] = ## Start configured external interfaces and monitoring tools - ## on a Waku v2 node, including the RPC API and metrics + ## on a Waku v2 node, including the RPC API, REST API and metrics ## monitoring ports. if conf.rpc: startRpc(node, conf.rpcAddress, Port(conf.rpcPort + conf.portsShift), conf) + + if conf.rest: + startRestServer(node, conf.restAddress, Port(conf.restPort + conf.portsShift), conf) if conf.metricsLogging: startMetricsLog() diff --git a/waku/v2/node/wakunode2_setup_rest.nim b/waku/v2/node/wakunode2_setup_rest.nim new file mode 100644 index 000000000..fc63440ed --- /dev/null +++ b/waku/v2/node/wakunode2_setup_rest.nim @@ -0,0 +1,39 @@ +{.push raises: [Defect].} + +import + stew/shims/net, + chronicles, + presto +import + ./config, + ./wakunode2, + ./rest/server, + ./rest/debug/debug_api, + ./rest/relay/[relay_api, + topic_cache] + + +logScope: + topics = "wakunode.setup.rest" + + +proc startRestServer*(node: WakuNode, address: ValidIpAddress, port: Port, conf: WakuNodeConf) = + let serverResult = newRestHttpServer(address, port) + if serverResult.isErr(): + notice "REST HTTP server could not be started", address = $address&":" & $port, reason = serverResult.error() + return + + let server = serverResult.get() + + ## Debug REST API + installDebugApiHandlers(server.router, node) + + ## Relay REST API + if conf.relay: + # TODO: Simplify topic cache object initialization + let relayCacheConfig = TopicCacheConfig(capacity: int(conf.restRelayCacheCapaciy)) + let relayCache = TopicCache.init(conf=relayCacheConfig) + installRelayApiHandlers(server.router, node, relayCache) + + server.start() + info "Starting REST HTTP server", url = "http://" & $address & ":" & $port & "/"