2023-12-14 08:34:13 +07:00
|
|
|
# json-rpc
|
|
|
|
|
# Copyright (c) 2019-2023 Status Research & Development GmbH
|
|
|
|
|
# Licensed under either of
|
|
|
|
|
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
|
|
|
|
|
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
|
|
|
|
|
# at your option.
|
|
|
|
|
# This file may not be copied, modified, or distributed except according to
|
|
|
|
|
# those terms.
|
|
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
{.push raises: [], gcsafe.}
|
|
|
|
|
|
2021-02-15 13:45:51 +01:00
|
|
|
import
|
2024-10-22 21:58:46 +02:00
|
|
|
std/sequtils,
|
2021-02-15 13:45:51 +01:00
|
|
|
chronicles, httputils, chronos,
|
2021-11-22 20:24:11 +02:00
|
|
|
chronos/apps/http/[httpserver, shttpserver],
|
2024-10-22 21:58:46 +02:00
|
|
|
../private/utils,
|
2024-01-08 10:37:29 +07:00
|
|
|
../errors,
|
2024-01-03 20:06:53 +07:00
|
|
|
../server
|
2021-03-26 13:17:00 +01:00
|
|
|
|
2024-01-03 20:06:53 +07:00
|
|
|
export
|
|
|
|
|
server, shttpserver
|
2018-07-14 10:51:54 +03:00
|
|
|
|
|
|
|
|
logScope:
|
2020-06-09 17:00:21 +03:00
|
|
|
topics = "JSONRPC-HTTP-SERVER"
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2022-10-25 18:48:44 +02:00
|
|
|
const
|
|
|
|
|
JsonRpcIdent = "nim-json-rpc"
|
|
|
|
|
|
2018-07-14 10:51:54 +03:00
|
|
|
type
|
2022-07-17 12:41:18 +07:00
|
|
|
# HttpAuthHook: handle CORS, JWT auth, etc. in HTTP header
|
|
|
|
|
# before actual request processed
|
|
|
|
|
# return value:
|
|
|
|
|
# - nil: auth success, continue execution
|
|
|
|
|
# - HttpResponse: could not authenticate, stop execution
|
|
|
|
|
# and return the response
|
2024-10-22 21:58:46 +02:00
|
|
|
HttpAuthHook* =
|
|
|
|
|
proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CatchableError]).}
|
2022-07-17 12:41:18 +07:00
|
|
|
|
2024-01-25 19:41:56 +07:00
|
|
|
# This inheritance arrangement is useful for
|
|
|
|
|
# e.g. combo HTTP server
|
|
|
|
|
RpcHttpHandler* = ref object of RpcServer
|
|
|
|
|
maxChunkSize*: int
|
|
|
|
|
|
|
|
|
|
RpcHttpServer* = ref object of RpcHttpHandler
|
2021-11-22 10:09:13 -03:00
|
|
|
httpServers: seq[HttpServerRef]
|
2022-07-17 12:41:18 +07:00
|
|
|
authHooks: seq[HttpAuthHook]
|
2024-01-25 19:41:56 +07:00
|
|
|
|
|
|
|
|
proc serveHTTP*(rpcServer: RpcHttpHandler, request: HttpRequestRef):
|
|
|
|
|
Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
|
|
|
|
|
try:
|
2024-10-22 21:58:46 +02:00
|
|
|
let req = await request.getBody()
|
|
|
|
|
debug "Received JSON-RPC request",
|
|
|
|
|
address = request.remote().valueOr(default(TransportAddress)),
|
|
|
|
|
len = req.len
|
2024-01-25 19:41:56 +07:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
let
|
|
|
|
|
data = await rpcServer.route(req)
|
|
|
|
|
chunkSize = rpcServer.maxChunkSize
|
|
|
|
|
streamType =
|
|
|
|
|
if data.len <= chunkSize:
|
|
|
|
|
HttpResponseStreamType.Plain
|
|
|
|
|
else:
|
|
|
|
|
HttpResponseStreamType.Chunked
|
|
|
|
|
response = request.getResponse()
|
|
|
|
|
|
|
|
|
|
response.addHeader("Content-Type", "application/json")
|
|
|
|
|
|
|
|
|
|
await response.prepare(streamType)
|
2024-01-25 19:41:56 +07:00
|
|
|
let maxLen = data.len
|
|
|
|
|
|
|
|
|
|
var len = data.len
|
|
|
|
|
while len > chunkSize:
|
2024-10-22 21:58:46 +02:00
|
|
|
await response.send(data[maxLen - len].unsafeAddr, chunkSize)
|
2024-01-25 19:41:56 +07:00
|
|
|
len -= chunkSize
|
|
|
|
|
|
|
|
|
|
if len > 0:
|
2024-10-22 21:58:46 +02:00
|
|
|
await response.send(data[maxLen - len].unsafeAddr, len)
|
2024-01-25 19:41:56 +07:00
|
|
|
|
|
|
|
|
await response.finish()
|
2024-10-22 21:58:46 +02:00
|
|
|
response
|
2024-01-25 19:41:56 +07:00
|
|
|
except CancelledError as exc:
|
|
|
|
|
raise exc
|
|
|
|
|
except CatchableError as exc:
|
|
|
|
|
debug "Internal error while processing JSON-RPC call"
|
2024-10-22 21:58:46 +02:00
|
|
|
defaultResponse(exc)
|
2021-11-22 10:09:13 -03:00
|
|
|
|
2024-01-03 20:06:53 +07:00
|
|
|
proc processClientRpc(rpcServer: RpcHttpServer): HttpProcessCallback2 =
|
2024-01-25 19:41:56 +07:00
|
|
|
return proc (req: RequestFence): Future[HttpResponseRef]
|
|
|
|
|
{.async: (raises: [CancelledError]).} =
|
2024-01-03 20:06:53 +07:00
|
|
|
if not req.isOk():
|
2024-10-22 21:58:46 +02:00
|
|
|
debug "Got invalid request", err = req.error()
|
2024-01-03 20:06:53 +07:00
|
|
|
return defaultResponse()
|
2022-07-17 12:41:18 +07:00
|
|
|
|
2024-01-03 20:06:53 +07:00
|
|
|
let request = req.get()
|
|
|
|
|
# if hook result is not nil,
|
|
|
|
|
# it means we should return immediately
|
|
|
|
|
try:
|
2022-07-17 12:41:18 +07:00
|
|
|
for hook in rpcServer.authHooks:
|
|
|
|
|
let res = await hook(request)
|
|
|
|
|
if not res.isNil:
|
|
|
|
|
return res
|
2024-10-22 21:58:46 +02:00
|
|
|
except CancelledError as exc:
|
|
|
|
|
raise exc
|
2024-01-03 20:06:53 +07:00
|
|
|
except CatchableError as exc:
|
|
|
|
|
error "Internal error while processing JSON-RPC hook", msg=exc.msg
|
2024-01-27 14:47:24 +07:00
|
|
|
return defaultResponse(exc)
|
2022-07-17 12:41:18 +07:00
|
|
|
|
2024-01-25 19:41:56 +07:00
|
|
|
return await rpcServer.serveHTTP(request)
|
2019-05-14 18:42:51 +03:00
|
|
|
|
2022-10-25 18:48:44 +02:00
|
|
|
proc addHttpServer*(
|
|
|
|
|
rpcServer: RpcHttpServer,
|
|
|
|
|
address: TransportAddress,
|
|
|
|
|
socketFlags: set[ServerFlags] = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr},
|
|
|
|
|
serverUri = Uri(),
|
|
|
|
|
serverIdent = "",
|
|
|
|
|
maxConnections: int = -1,
|
|
|
|
|
bufferSize: int = 4096,
|
|
|
|
|
backlogSize: int = 100,
|
|
|
|
|
httpHeadersTimeout = 10.seconds,
|
|
|
|
|
maxHeadersSize: int = 8192,
|
2024-10-22 21:58:46 +02:00
|
|
|
maxRequestBodySize: int = 1_048_576) {.raises: [JsonRpcError].} =
|
2022-10-25 18:48:44 +02:00
|
|
|
let server = HttpServerRef.new(
|
|
|
|
|
address,
|
|
|
|
|
processClientRpc(rpcServer),
|
|
|
|
|
{},
|
|
|
|
|
socketFlags,
|
|
|
|
|
serverUri, JsonRpcIdent, maxConnections, backlogSize,
|
|
|
|
|
bufferSize, httpHeadersTimeout, maxHeadersSize, maxRequestBodySize
|
|
|
|
|
).valueOr:
|
2021-11-22 20:24:11 +02:00
|
|
|
error "Failed to create server", address = $address,
|
2022-10-25 18:48:44 +02:00
|
|
|
message = error
|
|
|
|
|
raise newException(RpcBindError, "Unable to create server: " & $error)
|
|
|
|
|
|
|
|
|
|
rpcServer.httpServers.add server
|
|
|
|
|
|
|
|
|
|
proc addSecureHttpServer*(
|
|
|
|
|
rpcServer: RpcHttpServer,
|
|
|
|
|
address: TransportAddress,
|
|
|
|
|
tlsPrivateKey: TLSPrivateKey,
|
|
|
|
|
tlsCertificate: TLSCertificate,
|
|
|
|
|
socketFlags: set[ServerFlags] = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr},
|
|
|
|
|
serverUri = Uri(),
|
|
|
|
|
serverIdent: string = JsonRpcIdent,
|
|
|
|
|
secureFlags: set[TLSFlags] = {},
|
|
|
|
|
maxConnections: int = -1,
|
|
|
|
|
backlogSize: int = 100,
|
|
|
|
|
bufferSize: int = 4096,
|
|
|
|
|
httpHeadersTimeout = 10.seconds,
|
|
|
|
|
maxHeadersSize: int = 8192,
|
2024-10-22 21:58:46 +02:00
|
|
|
maxRequestBodySize: int = 1_048_576) {.raises: [JsonRpcError].} =
|
2022-10-25 18:48:44 +02:00
|
|
|
let server = SecureHttpServerRef.new(
|
|
|
|
|
address,
|
|
|
|
|
processClientRpc(rpcServer),
|
|
|
|
|
tlsPrivateKey,
|
|
|
|
|
tlsCertificate,
|
|
|
|
|
{HttpServerFlags.Secure},
|
|
|
|
|
socketFlags,
|
|
|
|
|
serverUri, JsonRpcIdent, secureFlags, maxConnections, backlogSize,
|
|
|
|
|
bufferSize, httpHeadersTimeout, maxHeadersSize, maxRequestBodySize
|
|
|
|
|
).valueOr:
|
2018-07-14 10:51:54 +03:00
|
|
|
error "Failed to create server", address = $address,
|
2022-10-25 18:48:44 +02:00
|
|
|
message = error
|
|
|
|
|
raise newException(RpcBindError, "Unable to create server: " & $error)
|
|
|
|
|
|
|
|
|
|
rpcServer.httpServers.add server
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2021-11-22 10:09:13 -03:00
|
|
|
proc addHttpServers*(server: RpcHttpServer,
|
2024-10-22 21:58:46 +02:00
|
|
|
addresses: openArray[TransportAddress]) {.raises: [JsonRpcError].} =
|
|
|
|
|
## Start a server on at least one of the given addresses, or raise
|
|
|
|
|
if addresses.len == 0:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
var lastExc: ref JsonRpcError
|
2018-07-14 10:51:54 +03:00
|
|
|
for item in addresses:
|
2024-10-22 21:58:46 +02:00
|
|
|
try:
|
|
|
|
|
server.addHttpServer(item)
|
|
|
|
|
except JsonRpcError as exc:
|
|
|
|
|
lastExc = exc
|
|
|
|
|
if server.httpServers.len == 0:
|
|
|
|
|
raise lastExc
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2021-11-22 20:24:11 +02:00
|
|
|
proc addSecureHttpServers*(server: RpcHttpServer,
|
|
|
|
|
addresses: openArray[TransportAddress],
|
|
|
|
|
tlsPrivateKey: TLSPrivateKey,
|
2024-10-22 21:58:46 +02:00
|
|
|
tlsCertificate: TLSCertificate) {.raises: [JsonRpcError].} =
|
|
|
|
|
## Start a server on at least one of the given addresses, or raise
|
|
|
|
|
if addresses.len == 0:
|
|
|
|
|
return
|
2021-11-22 20:24:11 +02:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
var lastExc: ref JsonRpcError
|
|
|
|
|
for item in addresses:
|
|
|
|
|
try:
|
|
|
|
|
server.addSecureHttpServer(item, tlsPrivateKey, tlsCertificate)
|
|
|
|
|
except JsonRpcError as exc:
|
|
|
|
|
lastExc = exc
|
|
|
|
|
if server.httpServers.len == 0:
|
|
|
|
|
raise lastExc
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
proc addHttpServer*(server: RpcHttpServer, address: string) {.raises: [JsonRpcError].} =
|
2021-11-22 20:24:11 +02:00
|
|
|
## Create new server and assign it to addresses ``addresses``.
|
2024-10-22 21:58:46 +02:00
|
|
|
addHttpServers(server, toSeq(resolveIP([address])))
|
2021-11-22 20:24:11 +02:00
|
|
|
|
|
|
|
|
proc addSecureHttpServer*(server: RpcHttpServer,
|
|
|
|
|
address: string,
|
|
|
|
|
tlsPrivateKey: TLSPrivateKey,
|
2024-10-22 21:58:46 +02:00
|
|
|
tlsCertificate: TLSCertificate) {.raises: [JsonRpcError].} =
|
|
|
|
|
addSecureHttpServers(server, toSeq(resolveIP([address])), tlsPrivateKey, tlsCertificate)
|
2021-11-22 20:24:11 +02:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
proc addHttpServers*(server: RpcHttpServer, addresses: openArray[string]) {.raises: [JsonRpcError].} =
|
|
|
|
|
addHttpServers(server, toSeq(resolveIP(addresses)))
|
2021-11-22 20:24:11 +02:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
proc addHttpServer*(server: RpcHttpServer, address: string, port: Port) {.raises: [JsonRpcError].} =
|
|
|
|
|
addHttpServers(server, toSeq(resolveIP(address, port)))
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2021-11-22 20:24:11 +02:00
|
|
|
proc addSecureHttpServer*(server: RpcHttpServer,
|
|
|
|
|
address: string,
|
|
|
|
|
port: Port,
|
|
|
|
|
tlsPrivateKey: TLSPrivateKey,
|
2024-10-22 21:58:46 +02:00
|
|
|
tlsCertificate: TLSCertificate) {.raises: [JsonRpcError].} =
|
|
|
|
|
addSecureHttpServers(server, toSeq(resolveIP(address, port)), tlsPrivateKey, tlsCertificate)
|
2021-11-22 20:24:11 +02:00
|
|
|
|
2022-07-17 12:41:18 +07:00
|
|
|
proc new*(T: type RpcHttpServer, authHooks: seq[HttpAuthHook] = @[]): T =
|
2024-01-12 17:05:55 +07:00
|
|
|
T(router: RpcRouter.init(), httpServers: @[], authHooks: authHooks, maxChunkSize: 8192)
|
2021-02-15 13:45:51 +01:00
|
|
|
|
2022-07-17 12:41:18 +07:00
|
|
|
proc new*(T: type RpcHttpServer, router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): T =
|
2024-01-12 17:05:55 +07:00
|
|
|
T(router: router, httpServers: @[], authHooks: authHooks, maxChunkSize: 8192)
|
2021-06-29 17:13:31 +02:00
|
|
|
|
2022-07-17 12:41:18 +07:00
|
|
|
proc newRpcHttpServer*(authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer =
|
|
|
|
|
RpcHttpServer.new(authHooks)
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2022-07-17 12:41:18 +07:00
|
|
|
proc newRpcHttpServer*(router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer =
|
|
|
|
|
RpcHttpServer.new(router, authHooks)
|
2021-06-29 17:13:31 +02:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
proc newRpcHttpServer*(addresses: openArray[TransportAddress], authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer {.raises: [JsonRpcError].} =
|
2018-07-14 10:51:54 +03:00
|
|
|
## Create new server and assign it to addresses ``addresses``.
|
2022-07-17 12:41:18 +07:00
|
|
|
result = newRpcHttpServer(authHooks)
|
2021-11-22 10:09:13 -03:00
|
|
|
result.addHttpServers(addresses)
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
proc newRpcHttpServer*(addresses: openArray[string], authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer {.raises: [JsonRpcError].} =
|
2018-07-14 10:51:54 +03:00
|
|
|
## Create new server and assign it to addresses ``addresses``.
|
2022-07-17 12:41:18 +07:00
|
|
|
result = newRpcHttpServer(authHooks)
|
2021-11-22 10:09:13 -03:00
|
|
|
result.addHttpServers(addresses)
|
2018-07-14 10:51:54 +03:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
proc newRpcHttpServer*(addresses: openArray[string], router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer {.raises: [JsonRpcError].} =
|
2021-06-29 17:13:31 +02:00
|
|
|
## Create new server and assign it to addresses ``addresses``.
|
2022-07-17 12:41:18 +07:00
|
|
|
result = newRpcHttpServer(router, authHooks)
|
2021-11-22 16:25:29 +02:00
|
|
|
result.addHttpServers(addresses)
|
2021-06-29 17:13:31 +02:00
|
|
|
|
2024-10-22 21:58:46 +02:00
|
|
|
proc newRpcHttpServer*(addresses: openArray[TransportAddress], router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer {.raises: [JsonRpcError].} =
|
2021-07-12 07:22:01 +02:00
|
|
|
## Create new server and assign it to addresses ``addresses``.
|
2022-07-17 12:41:18 +07:00
|
|
|
result = newRpcHttpServer(router, authHooks)
|
2021-11-22 16:25:29 +02:00
|
|
|
result.addHttpServers(addresses)
|
2021-07-12 07:22:01 +02:00
|
|
|
|
2018-07-14 10:51:54 +03:00
|
|
|
proc start*(server: RpcHttpServer) =
|
|
|
|
|
## Start the RPC server.
|
2021-11-22 10:09:13 -03:00
|
|
|
for item in server.httpServers:
|
2024-10-22 21:58:46 +02:00
|
|
|
info "Starting JSON-RPC HTTP server", url = item.baseUri
|
2018-07-14 10:51:54 +03:00
|
|
|
item.start()
|
|
|
|
|
|
2021-11-22 10:09:13 -03:00
|
|
|
proc stop*(server: RpcHttpServer) {.async.} =
|
2018-07-14 10:51:54 +03:00
|
|
|
## Stop the RPC server.
|
2021-11-22 10:09:13 -03:00
|
|
|
for item in server.httpServers:
|
|
|
|
|
await item.stop()
|
2024-10-22 21:58:46 +02:00
|
|
|
info "Stopped JSON-RPC HTTP server", url = item.baseUri
|
2018-09-13 18:06:33 +01:00
|
|
|
|
|
|
|
|
proc closeWait*(server: RpcHttpServer) {.async.} =
|
|
|
|
|
## Cleanup resources of RPC server.
|
2021-11-22 10:09:13 -03:00
|
|
|
for item in server.httpServers:
|
2018-09-13 18:06:33 +01:00
|
|
|
await item.closeWait()
|
2024-01-07 14:40:59 +07:00
|
|
|
|
|
|
|
|
proc localAddress*(server: RpcHttpServer): seq[TransportAddress] =
|
|
|
|
|
for item in server.httpServers:
|
|
|
|
|
result.add item.instance.localAddress()
|
2024-01-12 17:05:55 +07:00
|
|
|
|
|
|
|
|
proc setMaxChunkSize*(server: RpcHttpServer, maxChunkSize: int) =
|
|
|
|
|
server.maxChunkSize = maxChunkSize
|