nwaku/waku/waku_api/rest/server.nim
NagyZoltanPeter d832f92a43
chore: Implemented CORS handling for nwaku REST server (#2470)
* Add allowOrigin configuration for wakunode and WakuRestServer
Update nim-presto to the latest master that contains middleware support
Rework Rest Server in waku to utilize chronos' and presto's new middleware design and added proper CORS handling.
Added cors tests and fixes

Co-authored-by: Ivan FB <128452529+Ivansete-status@users.noreply.github.com>
2024-02-29 09:48:14 +01:00

202 lines
6.9 KiB
Nim

when (NimMajor, NimMinor) < (1, 4):
{.push raises: [Defect].}
else:
{.push raises: [].}
import
stew/results,
stew/shims/net,
chronicles,
chronos,
chronos/apps/http/httpserver,
presto,
presto/middleware,
presto/servercommon
import
./origin_handler
type
RestServerResult*[T] = Result[T, string]
WakuRestServer* = object of RootObj
router*: RestRouter
httpServer*: HttpServerRef
WakuRestServerRef* = ref WakuRestServer
### Configuration
type RestServerConf* = object
cacheSize*: Natural ## \
## The maximum number of recently accessed states that are kept in \
## memory. Speeds up requests obtaining information for consecutive
## slots or epochs.
cacheTtl*: Natural ## \
## The number of seconds to keep recently accessed states in memory
requestTimeout*: Natural ## \
## The number of seconds to wait until complete REST request will be received
maxRequestBodySize*: Natural ## \
## Maximum size of REST request body (kilobytes)
maxRequestHeadersSize*: Natural ## \
## Maximum size of REST request headers (kilobytes)
proc default*(T: type RestServerConf): T =
RestServerConf(
cacheSize: 3,
cacheTtl: 60,
requestTimeout: 0,
maxRequestBodySize: 16_384,
maxRequestHeadersSize: 64
)
### Initialization
proc new*(t: typedesc[WakuRestServerRef],
router: RestRouter,
address: TransportAddress,
serverIdent: string = PrestoIdent,
serverFlags = {HttpServerFlags.NotifyDisconnect},
socketFlags: set[ServerFlags] = {ReuseAddr},
serverUri = Uri(),
maxConnections: int = -1,
backlogSize: int = DefaultBacklogSize,
bufferSize: int = 4096,
httpHeadersTimeout = 10.seconds,
maxHeadersSize: int = 8192,
maxRequestBodySize: int = 1_048_576,
requestErrorHandler: RestRequestErrorHandler = nil,
dualstack = DualStackType.Auto,
allowedOrigin: Option[string] = none(string)
): RestServerResult[WakuRestServerRef] =
var server = WakuRestServerRef(router: router)
let restMiddleware = RestServerMiddlewareRef.new(router = server.router, errorHandler = requestErrorHandler)
let originHandlerMiddleware = OriginHandlerMiddlewareRef.new(allowedOrigin)
let middlewares = [originHandlerMiddleware,
restMiddleware]
## This must be empty and needed only to confirm original initialization requirements of
## the RestHttpServer now combining old and new middleware approach.
proc defaultProcessCallback(rf: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError]).} =
discard
let sres = HttpServerRef.new(address
, defaultProcessCallback
, serverFlags
, socketFlags
, serverUri
, serverIdent
, maxConnections
, bufferSize
, backlogSize
, httpHeadersTimeout
, maxHeadersSize
, maxRequestBodySize
, dualstack = dualstack
, middlewares = middlewares)
if sres.isOk():
server.httpServer = sres.get()
ok(server)
else:
err(sres.error)
proc getRouter(): 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
# disable allowed origin handling by presto, we add our own handling as middleware
RestRouter.init(validate, allowedOrigin = none(string))
proc init*(T: type WakuRestServerRef,
ip: IpAddress, port: Port,
allowedOrigin=none(string),
conf=RestServerConf.default(),
requestErrorHandler: RestRequestErrorHandler = nil): RestServerResult[T] =
let address = initTAddress(ip, port)
let serverFlags = {
HttpServerFlags.QueryCommaSeparatedArray,
HttpServerFlags.NotifyDisconnect
}
let
headersTimeout = if conf.requestTimeout == 0: chronos.InfiniteDuration
else: seconds(int64(conf.requestTimeout))
maxHeadersSize = conf.maxRequestHeadersSize * 1024
maxRequestBodySize = conf.maxRequestBodySize * 1024
let router = getRouter()
try:
return WakuRestServerRef.new(
router,
address,
serverFlags = serverFlags,
httpHeadersTimeout = headersTimeout,
maxHeadersSize = maxHeadersSize,
maxRequestBodySize = maxRequestBodySize,
requestErrorHandler = requestErrorHandler,
allowedOrigin = allowedOrigin
)
except CatchableError:
return err(getCurrentExceptionMsg())
proc newRestHttpServer*(ip: IpAddress, port: Port,
allowedOrigin=none(string),
conf=RestServerConf.default(),
requestErrorHandler: RestRequestErrorHandler = nil):
RestServerResult[WakuRestServerRef] =
WakuRestServerRef.init(ip, port, allowedOrigin, conf, requestErrorHandler)
proc localAddress*(rs: WakuRestServerRef): TransportAddress =
## Returns `rs` bound local socket address.
rs.httpServer.instance.localAddress()
proc state*(rs: WakuRestServerRef): RestServerState =
## Returns current REST server's state.
case rs.httpServer.state
of HttpServerState.ServerClosed:
RestServerState.Closed
of HttpServerState.ServerStopped:
RestServerState.Stopped
of HttpServerState.ServerRunning:
RestServerState.Running
proc start*(rs: WakuRestServerRef) =
## Starts REST server.
rs.httpServer.start()
notice "REST service started", address = $rs.localAddress()
proc stop*(rs: WakuRestServerRef) {.async: (raises: []).} =
## Stop REST server from accepting new connections.
await rs.httpServer.stop()
notice "REST service stopped", address = $rs.localAddress()
proc drop*(rs: WakuRestServerRef): Future[void] {.
async: (raw: true, raises: []).} =
## Drop all pending connections.
rs.httpServer.drop()
proc closeWait*(rs: WakuRestServerRef) {.async: (raises: []).} =
## Stop REST server and drop all the pending connections.
await rs.httpServer.closeWait()
notice "REST service closed", address = $rs.localAddress()
proc join*(rs: WakuRestServerRef): Future[void] {.
async: (raw: true, raises: [CancelledError]).} =
## Wait until REST server will not be closed.
rs.httpServer.join()