mirror of https://github.com/waku-org/nwaku.git
feat(rest): Add HTTP REST API (#727). Integrate REST API with the Waku V2 node
This commit is contained in:
parent
daaca4a4a3
commit
732f853f55
|
@ -91,3 +91,8 @@ proc resume*(node: WakuNode, peerList: Option[seq[PeerInfo]]) =
|
||||||
## JSON RPC
|
## JSON RPC
|
||||||
|
|
||||||
TODO To specify
|
TODO To specify
|
||||||
|
|
||||||
|
|
||||||
|
## REST API
|
||||||
|
|
||||||
|
[Here](./rest-api.md) you can find more details on the Node HTTP REST API.
|
||||||
|
|
|
@ -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.
|
|
@ -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.
|
> **Note:** Private and Admin API functionality are disabled by default.
|
||||||
To configure a nwaku node with these enabled,
|
To configure a nwaku node with these enabled,
|
||||||
use the `--rpc-admin:true` and `--rpc-private:true` CLI options.
|
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
|
||||||
|
```
|
||||||
|
|
|
@ -32,12 +32,7 @@ suite "REST API - Debug":
|
||||||
|
|
||||||
let restPort = Port(8546)
|
let restPort = Port(8546)
|
||||||
let restAddress = ValidIpAddress.init("0.0.0.0")
|
let restAddress = ValidIpAddress.init("0.0.0.0")
|
||||||
let restServer = RestServerRef.init(
|
let restServer = RestServerRef.init(restAddress, restPort).tryGet()
|
||||||
restAddress,
|
|
||||||
restPort,
|
|
||||||
none(string),
|
|
||||||
none(RestServerConf)
|
|
||||||
)
|
|
||||||
|
|
||||||
installDebugApiHandlers(restServer.router, node)
|
installDebugApiHandlers(restServer.router, node)
|
||||||
restServer.start()
|
restServer.start()
|
||||||
|
|
|
@ -43,12 +43,7 @@ suite "REST API - Relay":
|
||||||
|
|
||||||
let restPort = Port(8546)
|
let restPort = Port(8546)
|
||||||
let restAddress = ValidIpAddress.init("0.0.0.0")
|
let restAddress = ValidIpAddress.init("0.0.0.0")
|
||||||
let restServer = RestServerRef.init(
|
let restServer = RestServerRef.init(restAddress, restPort).tryGet()
|
||||||
restAddress,
|
|
||||||
restPort,
|
|
||||||
none(string),
|
|
||||||
none(RestServerConf)
|
|
||||||
)
|
|
||||||
|
|
||||||
let topicCache = TopicCache.init()
|
let topicCache = TopicCache.init()
|
||||||
|
|
||||||
|
@ -93,12 +88,7 @@ suite "REST API - Relay":
|
||||||
|
|
||||||
let restPort = Port(8546)
|
let restPort = Port(8546)
|
||||||
let restAddress = ValidIpAddress.init("0.0.0.0")
|
let restAddress = ValidIpAddress.init("0.0.0.0")
|
||||||
let restServer = RestServerRef.init(
|
let restServer = RestServerRef.init(restAddress, restPort).tryGet()
|
||||||
restAddress,
|
|
||||||
restPort,
|
|
||||||
none(string),
|
|
||||||
none(RestServerConf)
|
|
||||||
)
|
|
||||||
|
|
||||||
let topicCache = TopicCache.init()
|
let topicCache = TopicCache.init()
|
||||||
topicCache.subscribe("pubsub-topic-1")
|
topicCache.subscribe("pubsub-topic-1")
|
||||||
|
@ -146,12 +136,7 @@ suite "REST API - Relay":
|
||||||
|
|
||||||
let restPort = Port(8546)
|
let restPort = Port(8546)
|
||||||
let restAddress = ValidIpAddress.init("0.0.0.0")
|
let restAddress = ValidIpAddress.init("0.0.0.0")
|
||||||
let restServer = RestServerRef.init(
|
let restServer = RestServerRef.init(restAddress, restPort).tryGet()
|
||||||
restAddress,
|
|
||||||
restPort,
|
|
||||||
none(string),
|
|
||||||
none(RestServerConf)
|
|
||||||
)
|
|
||||||
|
|
||||||
let pubSubTopic = "/waku/2/default-waku/proto"
|
let pubSubTopic = "/waku/2/default-waku/proto"
|
||||||
let messages = @[
|
let messages = @[
|
||||||
|
@ -203,12 +188,7 @@ suite "REST API - Relay":
|
||||||
# RPC server setup
|
# RPC server setup
|
||||||
let restPort = Port(8546)
|
let restPort = Port(8546)
|
||||||
let restAddress = ValidIpAddress.init("0.0.0.0")
|
let restAddress = ValidIpAddress.init("0.0.0.0")
|
||||||
let restServer = RestServerRef.init(
|
let restServer = RestServerRef.init(restAddress, restPort).tryGet()
|
||||||
restAddress,
|
|
||||||
restPort,
|
|
||||||
none(string),
|
|
||||||
none(RestServerConf)
|
|
||||||
)
|
|
||||||
|
|
||||||
let topicCache = TopicCache.init()
|
let topicCache = TopicCache.init()
|
||||||
|
|
||||||
|
|
|
@ -2,4 +2,4 @@
|
||||||
|
|
||||||
This folder contains code related to running a `wakunode2` process. The main entrypoint is the `wakunode2` file.
|
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.
|
||||||
|
|
|
@ -267,6 +267,38 @@ type
|
||||||
desc: "Enable access to JSON-RPC Private API: true|false",
|
desc: "Enable access to JSON-RPC Private API: true|false",
|
||||||
defaultValue: false
|
defaultValue: false
|
||||||
name: "rpc-private" }: bool
|
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
|
## Metrics config
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,17 @@
|
||||||
{.push raises: [Defect].}
|
{.push raises: [Defect].}
|
||||||
|
|
||||||
import
|
import
|
||||||
std/[os, times],
|
stew/results,
|
||||||
std/typetraits,
|
|
||||||
stew/[byteutils, io2],
|
|
||||||
stew/shims/net,
|
stew/shims/net,
|
||||||
chronicles, chronos,
|
chronicles,
|
||||||
metrics, metrics/chronos_httpserver,
|
chronos,
|
||||||
bearssl,
|
|
||||||
presto
|
presto
|
||||||
|
|
||||||
|
|
||||||
proc getRouter(allowedOrigin: Option[string]): RestRouter =
|
type RestServerResult*[T] = Result[T, cstring]
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
type RestServerConf* = object
|
type RestServerConf* = object
|
||||||
cacheSize*: Natural ## \
|
cacheSize*: Natural ## \
|
||||||
|
@ -40,7 +31,7 @@ type RestServerConf* = object
|
||||||
maxRequestHeadersSize*: Natural ## \
|
maxRequestHeadersSize*: Natural ## \
|
||||||
## Maximum size of REST request headers (kilobytes)
|
## Maximum size of REST request headers (kilobytes)
|
||||||
|
|
||||||
proc default(T: type RestServerConf): RestServerConf =
|
proc default*(T: type RestServerConf): T =
|
||||||
RestServerConf(
|
RestServerConf(
|
||||||
cacheSize: 3,
|
cacheSize: 3,
|
||||||
cacheTtl: 60,
|
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,
|
ip: ValidIpAddress, port: Port,
|
||||||
allowedOrigin: Option[string],
|
allowedOrigin=none(string),
|
||||||
config: Option[RestServerConf]): T =
|
conf=RestServerConf.default()): RestServerResult[T] =
|
||||||
let address = initTAddress(ip, port)
|
let address = initTAddress(ip, port)
|
||||||
let serverFlags = {
|
let serverFlags = {
|
||||||
HttpServerFlags.QueryCommaSeparatedArray,
|
HttpServerFlags.QueryCommaSeparatedArray,
|
||||||
HttpServerFlags.NotifyDisconnect
|
HttpServerFlags.NotifyDisconnect
|
||||||
}
|
}
|
||||||
|
|
||||||
let conf = if config.isSome: config.get()
|
|
||||||
else: RestServerConf.default()
|
|
||||||
|
|
||||||
let
|
let
|
||||||
headersTimeout = if conf.requestTimeout == 0: chronos.InfiniteDuration
|
headersTimeout = if conf.requestTimeout == 0: chronos.InfiniteDuration
|
||||||
else: seconds(int64(conf.requestTimeout))
|
else: seconds(int64(conf.requestTimeout))
|
||||||
|
@ -70,18 +70,24 @@ template init*(T: type RestServerRef,
|
||||||
maxRequestBodySize = conf.maxRequestBodySize * 1024
|
maxRequestBodySize = conf.maxRequestBodySize * 1024
|
||||||
|
|
||||||
let router = getRouter(allowedOrigin)
|
let router = getRouter(allowedOrigin)
|
||||||
let res = RestServerRef.new(
|
|
||||||
router,
|
|
||||||
address,
|
|
||||||
serverFlags = serverFlags,
|
|
||||||
httpHeadersTimeout = headersTimeout,
|
|
||||||
maxHeadersSize = maxHeadersSize,
|
|
||||||
maxRequestBodySize = maxRequestBodySize
|
|
||||||
)
|
|
||||||
|
|
||||||
if res.isErr():
|
var res: RestResult[RestServerRef]
|
||||||
notice "Rest server could not be started", address = $address, reason = res.error()
|
try:
|
||||||
nil
|
res = RestServerRef.new(
|
||||||
else:
|
router,
|
||||||
notice "Starting REST HTTP server", url = "http://" & $ip & ":" & $port & "/"
|
address,
|
||||||
res.get()
|
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)
|
||||||
|
|
||||||
|
|
|
@ -791,6 +791,7 @@ when isMainModule:
|
||||||
../../common/utils/nat,
|
../../common/utils/nat,
|
||||||
./config,
|
./config,
|
||||||
./waku_setup,
|
./waku_setup,
|
||||||
|
./wakunode2_setup_rest,
|
||||||
./storage/message/waku_message_store,
|
./storage/message/waku_message_store,
|
||||||
./storage/peer/waku_peer_storage
|
./storage/peer/waku_peer_storage
|
||||||
|
|
||||||
|
@ -1078,11 +1079,14 @@ when isMainModule:
|
||||||
# 6/7 Start monitoring tools and external interfaces
|
# 6/7 Start monitoring tools and external interfaces
|
||||||
proc startExternal(node: WakuNode, conf: WakuNodeConf): SetupResult[bool] =
|
proc startExternal(node: WakuNode, conf: WakuNodeConf): SetupResult[bool] =
|
||||||
## Start configured external interfaces and monitoring tools
|
## 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.
|
## monitoring ports.
|
||||||
|
|
||||||
if conf.rpc:
|
if conf.rpc:
|
||||||
startRpc(node, conf.rpcAddress, Port(conf.rpcPort + conf.portsShift), conf)
|
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:
|
if conf.metricsLogging:
|
||||||
startMetricsLog()
|
startMetricsLog()
|
||||||
|
|
|
@ -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 & "/"
|
Loading…
Reference in New Issue