feat(rest): Add HTTP REST API (#727). Integrate REST API with the Waku V2 node

This commit is contained in:
Lorenzo Delgado 2022-06-28 12:22:59 +02:00 committed by Lorenzo Delgado
parent daaca4a4a3
commit 732f853f55
10 changed files with 197 additions and 67 deletions

View File

@ -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.

55
docs/api/v2/rest-api.md Normal file
View File

@ -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.

View File

@ -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
```

View File

@ -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()

View File

@ -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()

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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 & "/"