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
|
||||
|
||||
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.
|
||||
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
|
||||
```
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -268,6 +268,38 @@ type
|
|||
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
|
||||
|
||||
metricsServer* {.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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,12 +1079,15 @@ 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()
|
||||
|
||||
|
|
|
@ -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