nim-json-rpc/json_rpc/servers/httpserver.nim

277 lines
9.8 KiB
Nim
Raw Normal View History

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.
{.push raises: [], gcsafe.}
import
std/sequtils,
chronicles, httputils, chronos,
chronos/apps/http/[httpserver, shttpserver],
../private/utils,
../errors,
../server
export
server, shttpserver
2018-07-14 10:51:54 +03:00
logScope:
topics = "JSONRPC-HTTP-SERVER"
2018-07-14 10:51:54 +03:00
const
JsonRpcIdent = "nim-json-rpc"
2018-07-14 10:51:54 +03:00
type
# 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
HttpAuthHook* =
proc(request: HttpRequestRef): Future[HttpResponseRef] {.async: (raises: [CatchableError]).}
# 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]
authHooks: seq[HttpAuthHook]
proc serveHTTP*(rpcServer: RpcHttpHandler, request: HttpRequestRef):
Future[HttpResponseRef] {.async: (raises: [CancelledError]).} =
try:
let req = await request.getBody()
debug "Received JSON-RPC request",
address = request.remote().valueOr(default(TransportAddress)),
len = req.len
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)
let maxLen = data.len
var len = data.len
while len > chunkSize:
await response.send(data[maxLen - len].unsafeAddr, chunkSize)
len -= chunkSize
if len > 0:
await response.send(data[maxLen - len].unsafeAddr, len)
await response.finish()
response
except CancelledError as exc:
raise exc
except CatchableError as exc:
debug "Internal error while processing JSON-RPC call"
defaultResponse(exc)
2021-11-22 10:09:13 -03:00
proc processClientRpc(rpcServer: RpcHttpServer): HttpProcessCallback2 =
return proc (req: RequestFence): Future[HttpResponseRef]
{.async: (raises: [CancelledError]).} =
if not req.isOk():
debug "Got invalid request", err = req.error()
return defaultResponse()
let request = req.get()
# if hook result is not nil,
# it means we should return immediately
try:
for hook in rpcServer.authHooks:
let res = await hook(request)
if not res.isNil:
return res
except CancelledError as exc:
raise exc
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)
return await rpcServer.serveHTTP(request)
2019-05-14 18:42:51 +03: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,
maxRequestBodySize: int = 1_048_576) {.raises: [JsonRpcError].} =
let server = HttpServerRef.new(
address,
processClientRpc(rpcServer),
{},
socketFlags,
serverUri, JsonRpcIdent, maxConnections, backlogSize,
bufferSize, httpHeadersTimeout, maxHeadersSize, maxRequestBodySize
).valueOr:
error "Failed to create server", address = $address,
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,
maxRequestBodySize: int = 1_048_576) {.raises: [JsonRpcError].} =
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,
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,
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:
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
proc addSecureHttpServers*(server: RpcHttpServer,
addresses: openArray[TransportAddress],
tlsPrivateKey: TLSPrivateKey,
tlsCertificate: TLSCertificate) {.raises: [JsonRpcError].} =
## Start a server on at least one of the given addresses, or raise
if addresses.len == 0:
return
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
proc addHttpServer*(server: RpcHttpServer, address: string) {.raises: [JsonRpcError].} =
## Create new server and assign it to addresses ``addresses``.
addHttpServers(server, toSeq(resolveIP([address])))
proc addSecureHttpServer*(server: RpcHttpServer,
address: string,
tlsPrivateKey: TLSPrivateKey,
tlsCertificate: TLSCertificate) {.raises: [JsonRpcError].} =
addSecureHttpServers(server, toSeq(resolveIP([address])), tlsPrivateKey, tlsCertificate)
proc addHttpServers*(server: RpcHttpServer, addresses: openArray[string]) {.raises: [JsonRpcError].} =
addHttpServers(server, toSeq(resolveIP(addresses)))
proc addHttpServer*(server: RpcHttpServer, address: string, port: Port) {.raises: [JsonRpcError].} =
addHttpServers(server, toSeq(resolveIP(address, port)))
2018-07-14 10:51:54 +03:00
proc addSecureHttpServer*(server: RpcHttpServer,
address: string,
port: Port,
tlsPrivateKey: TLSPrivateKey,
tlsCertificate: TLSCertificate) {.raises: [JsonRpcError].} =
addSecureHttpServers(server, toSeq(resolveIP(address, port)), tlsPrivateKey, tlsCertificate)
proc new*(T: type RpcHttpServer, authHooks: seq[HttpAuthHook] = @[]): T =
T(router: RpcRouter.init(), httpServers: @[], authHooks: authHooks, maxChunkSize: 8192)
proc new*(T: type RpcHttpServer, router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): T =
T(router: router, httpServers: @[], authHooks: authHooks, maxChunkSize: 8192)
proc newRpcHttpServer*(authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer =
RpcHttpServer.new(authHooks)
2018-07-14 10:51:54 +03:00
proc newRpcHttpServer*(router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer =
RpcHttpServer.new(router, authHooks)
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``.
result = newRpcHttpServer(authHooks)
2021-11-22 10:09:13 -03:00
result.addHttpServers(addresses)
2018-07-14 10:51:54 +03: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``.
result = newRpcHttpServer(authHooks)
2021-11-22 10:09:13 -03:00
result.addHttpServers(addresses)
2018-07-14 10:51:54 +03:00
proc newRpcHttpServer*(addresses: openArray[string], router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer {.raises: [JsonRpcError].} =
## Create new server and assign it to addresses ``addresses``.
result = newRpcHttpServer(router, authHooks)
2021-11-22 16:25:29 +02:00
result.addHttpServers(addresses)
proc newRpcHttpServer*(addresses: openArray[TransportAddress], router: RpcRouter, authHooks: seq[HttpAuthHook] = @[]): RpcHttpServer {.raises: [JsonRpcError].} =
## Create new server and assign it to addresses ``addresses``.
result = newRpcHttpServer(router, authHooks)
2021-11-22 16:25:29 +02:00
result.addHttpServers(addresses)
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:
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()
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()
proc setMaxChunkSize*(server: RpcHttpServer, maxChunkSize: int) =
server.maxChunkSize = maxChunkSize