mirror of
https://github.com/status-im/nim-json-rpc.git
synced 2025-02-24 02:08:28 +00:00
Chronos HTTP(S) Server (#111)
This commit is contained in:
parent
9db5407e81
commit
3599a73b58
@ -7,6 +7,7 @@ skipDirs = @["tests"]
|
|||||||
|
|
||||||
### Dependencies
|
### Dependencies
|
||||||
requires "nim >= 1.2.0",
|
requires "nim >= 1.2.0",
|
||||||
|
"stew",
|
||||||
"nimcrypto",
|
"nimcrypto",
|
||||||
"stint",
|
"stint",
|
||||||
"chronos",
|
"chronos",
|
||||||
|
@ -23,14 +23,21 @@ type
|
|||||||
const
|
const
|
||||||
MaxHttpRequestSize = 128 * 1024 * 1024 # maximum size of HTTP body in octets
|
MaxHttpRequestSize = 128 * 1024 * 1024 # maximum size of HTTP body in octets
|
||||||
|
|
||||||
proc new(T: type RpcHttpClient, maxBodySize = MaxHttpRequestSize): T =
|
proc new(T: type RpcHttpClient, maxBodySize = MaxHttpRequestSize, secure = false): T =
|
||||||
|
if secure:
|
||||||
|
T(
|
||||||
|
maxBodySize: maxBodySize,
|
||||||
|
httpSession: HttpSessionRef.new(flags={HttpClientFlag.NoVerifyHost,
|
||||||
|
HttpClientFlag.NoVerifyServerName}),
|
||||||
|
)
|
||||||
|
else:
|
||||||
T(
|
T(
|
||||||
maxBodySize: maxBodySize,
|
maxBodySize: maxBodySize,
|
||||||
httpSession: HttpSessionRef.new(),
|
httpSession: HttpSessionRef.new(),
|
||||||
)
|
)
|
||||||
|
|
||||||
proc newRpcHttpClient*(maxBodySize = MaxHttpRequestSize): RpcHttpClient =
|
proc newRpcHttpClient*(maxBodySize = MaxHttpRequestSize, secure = false): RpcHttpClient =
|
||||||
RpcHttpClient.new(maxBodySize)
|
RpcHttpClient.new(maxBodySize, secure)
|
||||||
|
|
||||||
method call*(client: RpcHttpClient, name: string,
|
method call*(client: RpcHttpClient, name: string,
|
||||||
params: JsonNode): Future[Response]
|
params: JsonNode): Future[Response]
|
||||||
@ -45,16 +52,27 @@ method call*(client: RpcHttpClient, name: string,
|
|||||||
req = HttpClientRequestRef.post(client.httpSession,
|
req = HttpClientRequestRef.post(client.httpSession,
|
||||||
client.httpAddress.get,
|
client.httpAddress.get,
|
||||||
body = reqBody.toOpenArrayByte(0, reqBody.len - 1))
|
body = reqBody.toOpenArrayByte(0, reqBody.len - 1))
|
||||||
res = await req.send()
|
res =
|
||||||
|
try:
|
||||||
|
await req.send()
|
||||||
|
except CatchableError as exc:
|
||||||
|
raise (ref RpcPostError)(msg: "Failed to send POST Request with JSON-RPC.", parent: exc)
|
||||||
|
|
||||||
|
if res.status < 200 or res.status >= 300: # res.status is not 2xx (success)
|
||||||
|
raise newException(ErrorResponse, "POST Response: " & $res.status)
|
||||||
|
|
||||||
debug "Message sent to RPC server",
|
debug "Message sent to RPC server",
|
||||||
address = client.httpAddress, msg_len = len(reqBody)
|
address = client.httpAddress, msg_len = len(reqBody)
|
||||||
trace "Message", msg = reqBody
|
trace "Message", msg = reqBody
|
||||||
echo "req body ", reqBody
|
|
||||||
|
|
||||||
let resText = string.fromBytes(await res.getBodyBytes(client.maxBodySize))
|
let resBytes =
|
||||||
|
try:
|
||||||
|
await res.getBodyBytes(client.maxBodySize)
|
||||||
|
except CatchableError as exc:
|
||||||
|
raise (ref FailedHttpResponse)(msg: "Failed to read POST Response for JSON-RPC.", parent: exc)
|
||||||
|
|
||||||
|
let resText = string.fromBytes(resBytes)
|
||||||
trace "Response", text = resText
|
trace "Response", text = resText
|
||||||
echo "response ", resText
|
|
||||||
|
|
||||||
# completed by processMessage - the flow is quite weird here to accomodate
|
# completed by processMessage - the flow is quite weird here to accomodate
|
||||||
# socket and ws clients, but could use a more thorough refactoring
|
# socket and ws clients, but could use a more thorough refactoring
|
||||||
@ -83,9 +101,18 @@ proc connect*(client: RpcHttpClient, url: string)
|
|||||||
if client.httpAddress.isErr:
|
if client.httpAddress.isErr:
|
||||||
raise newException(RpcAddressUnresolvableError, client.httpAddress.error)
|
raise newException(RpcAddressUnresolvableError, client.httpAddress.error)
|
||||||
|
|
||||||
proc connect*(client: RpcHttpClient, address: string, port: Port) {.async.} =
|
proc connect*(client: RpcHttpClient, address: string, port: Port, secure=false) {.async.} =
|
||||||
let addresses = resolveTAddress(address, port)
|
var uri = initUri()
|
||||||
if addresses.len == 0:
|
if secure:
|
||||||
raise newException(RpcAddressUnresolvableError, "Failed to resolve address: " & address)
|
uri.scheme = "https"
|
||||||
ok client.httpAddress, getAddress(addresses[0])
|
else:
|
||||||
|
uri.scheme = "http"
|
||||||
|
uri.hostname = address
|
||||||
|
uri.port = $port
|
||||||
|
|
||||||
|
let res = getAddress(client.httpSession, uri)
|
||||||
|
if res.isOk:
|
||||||
|
client.httpAddress = res
|
||||||
|
else:
|
||||||
|
raise newException(RpcAddressUnresolvableError, res.error)
|
||||||
|
|
@ -8,6 +8,12 @@ type
|
|||||||
InvalidResponse* = object of JsonRpcError
|
InvalidResponse* = object of JsonRpcError
|
||||||
## raised when the server response violates the JSON-RPC protocol
|
## raised when the server response violates the JSON-RPC protocol
|
||||||
|
|
||||||
|
FailedHttpResponse* = object of JsonRpcError
|
||||||
|
## raised when fail to read the underlying HTTP server response
|
||||||
|
|
||||||
|
RpcPostError* = object of JsonRpcError
|
||||||
|
## raised when the client fails to send the POST request with JSON-RPC
|
||||||
|
|
||||||
RpcBindError* = object of JsonRpcError
|
RpcBindError* = object of JsonRpcError
|
||||||
RpcAddressUnresolvableError* = object of JsonRpcError
|
RpcAddressUnresolvableError* = object of JsonRpcError
|
||||||
|
|
||||||
|
3
json_rpc/rpcsecureserver.nim
Normal file
3
json_rpc/rpcsecureserver.nim
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import server
|
||||||
|
import servers/[socketserver, shttpserver]
|
||||||
|
export server, socketserver, shttpserver
|
@ -1,6 +1,8 @@
|
|||||||
import
|
import
|
||||||
|
stew/byteutils,
|
||||||
std/[strutils],
|
std/[strutils],
|
||||||
chronicles, httputils, chronos,
|
chronicles, httputils, chronos,
|
||||||
|
chronos/apps/http/httpserver,
|
||||||
".."/[errors, server]
|
".."/[errors, server]
|
||||||
|
|
||||||
export server
|
export server
|
||||||
@ -8,226 +10,57 @@ export server
|
|||||||
logScope:
|
logScope:
|
||||||
topics = "JSONRPC-HTTP-SERVER"
|
topics = "JSONRPC-HTTP-SERVER"
|
||||||
|
|
||||||
const
|
|
||||||
MaxHttpHeadersSize = 8192 # maximum size of HTTP headers in octets
|
|
||||||
MaxHttpRequestSize = 128 * 1024 # maximum size of HTTP body in octets
|
|
||||||
HttpHeadersTimeout = 120.seconds # timeout for receiving headers (120 sec)
|
|
||||||
HttpBodyTimeout = 12.seconds # timeout for receiving body (12 sec)
|
|
||||||
HeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)]
|
|
||||||
|
|
||||||
type
|
type
|
||||||
ReqStatus = enum
|
ReqStatus = enum
|
||||||
Success, Error, ErrorFailure
|
Success, Error, ErrorFailure
|
||||||
|
|
||||||
RpcHttpServer* = ref object of RpcServer
|
RpcHttpServer* = ref object of RpcServer
|
||||||
servers: seq[StreamServer]
|
httpServers: seq[HttpServerRef]
|
||||||
|
|
||||||
proc sendAnswer(transp: StreamTransport, version: HttpVersion, code: HttpCode,
|
proc addHttpServer*(rpcServer: RpcHttpServer, address: TransportAddress) =
|
||||||
data: string = ""): Future[bool] {.async.} =
|
proc processClientRpc(rpcServer: RpcHttpServer): HttpProcessCallback {.closure.} =
|
||||||
var answer = $version
|
return proc (req: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||||
answer.add(" ")
|
if req.isOk():
|
||||||
answer.add($code)
|
let request = req.get()
|
||||||
answer.add("\r\n")
|
let body = await request.getBody()
|
||||||
answer.add("Date: " & httpDate() & "\r\n")
|
|
||||||
if len(data) > 0:
|
|
||||||
answer.add("Content-Type: application/json\r\n")
|
|
||||||
answer.add("Content-Length: " & $len(data) & "\r\n")
|
|
||||||
answer.add("\r\n")
|
|
||||||
if len(data) > 0:
|
|
||||||
answer.add(data)
|
|
||||||
try:
|
|
||||||
let res = await transp.write(answer)
|
|
||||||
return res == len(answer)
|
|
||||||
except CancelledError as exc: raise exc
|
|
||||||
except CatchableError:
|
|
||||||
return false
|
|
||||||
|
|
||||||
proc validateRequest(transp: StreamTransport,
|
let future = rpcServer.route(string.fromBytes(body))
|
||||||
header: HttpRequestHeader): Future[ReqStatus] {.async.} =
|
|
||||||
if header.meth in {MethodPut, MethodDelete}:
|
|
||||||
# Request method is either PUT or DELETE.
|
|
||||||
debug "PUT/DELETE methods are not allowed", address = transp.remoteAddress()
|
|
||||||
return if await transp.sendAnswer(header.version, Http405):
|
|
||||||
Error
|
|
||||||
else:
|
|
||||||
ErrorFailure
|
|
||||||
|
|
||||||
let length = header.contentLength()
|
|
||||||
if length <= 0:
|
|
||||||
# request length could not be calculated.
|
|
||||||
debug "Content-Length is missing or 0", address = transp.remoteAddress()
|
|
||||||
return if await transp.sendAnswer(header.version, Http411):
|
|
||||||
Error
|
|
||||||
else:
|
|
||||||
ErrorFailure
|
|
||||||
|
|
||||||
if length > MaxHttpRequestSize:
|
|
||||||
# request length is more then `MaxHttpRequestSize`.
|
|
||||||
debug "Maximum size of request body reached",
|
|
||||||
address = transp.remoteAddress()
|
|
||||||
return if await transp.sendAnswer(header.version, Http413):
|
|
||||||
Error
|
|
||||||
else:
|
|
||||||
ErrorFailure
|
|
||||||
|
|
||||||
var ctype = header["Content-Type"]
|
|
||||||
# might be "application/json; charset=utf-8"
|
|
||||||
if "application/json" notin ctype.toLowerAscii():
|
|
||||||
# Content-Type header is not "application/json"
|
|
||||||
debug "Content type must be application/json",
|
|
||||||
address = transp.remoteAddress()
|
|
||||||
return if await transp.sendAnswer(header.version, Http415):
|
|
||||||
Error
|
|
||||||
else:
|
|
||||||
ErrorFailure
|
|
||||||
|
|
||||||
return Success
|
|
||||||
|
|
||||||
proc processClient(server: StreamServer,
|
|
||||||
transp: StreamTransport) {.async, gcsafe.} =
|
|
||||||
## Process transport data to the RPC server
|
|
||||||
var rpc = getUserData[RpcHttpServer](server)
|
|
||||||
var buffer = newSeq[byte](MaxHttpHeadersSize)
|
|
||||||
var header: HttpRequestHeader
|
|
||||||
var connection: string
|
|
||||||
|
|
||||||
debug "Received connection", address = $transp.remoteAddress()
|
|
||||||
while true:
|
|
||||||
try:
|
|
||||||
let hlenfut = transp.readUntil(addr buffer[0], MaxHttpHeadersSize,
|
|
||||||
HeadersMark)
|
|
||||||
let ores = await withTimeout(hlenfut, HttpHeadersTimeout)
|
|
||||||
if not ores:
|
|
||||||
# Timeout
|
|
||||||
debug "Timeout expired while receiving headers",
|
|
||||||
address = transp.remoteAddress()
|
|
||||||
discard await transp.sendAnswer(HttpVersion11, Http408)
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
let hlen = hlenfut.read()
|
|
||||||
buffer.setLen(hlen)
|
|
||||||
header = buffer.parseRequest()
|
|
||||||
if header.failed():
|
|
||||||
# Header could not be parsed
|
|
||||||
debug "Malformed header received",
|
|
||||||
address = transp.remoteAddress()
|
|
||||||
discard await transp.sendAnswer(HttpVersion11, Http400)
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
except TransportLimitError:
|
|
||||||
# size of headers exceeds `MaxHttpHeadersSize`
|
|
||||||
debug "Maximum size of headers limit reached",
|
|
||||||
address = transp.remoteAddress()
|
|
||||||
discard await transp.sendAnswer(HttpVersion11, Http413)
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
except TransportIncompleteError:
|
|
||||||
# remote peer disconnected
|
|
||||||
debug "Remote peer disconnected", address = transp.remoteAddress()
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
except TransportOsError as exc:
|
|
||||||
debug "Problems with networking", address = transp.remoteAddress(),
|
|
||||||
error = exc.msg
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
except CatchableError as exc:
|
|
||||||
debug "Unknown exception", address = transp.remoteAddress(),
|
|
||||||
error = exc.msg
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
|
|
||||||
let vres = await validateRequest(transp, header)
|
|
||||||
|
|
||||||
if vres == Success:
|
|
||||||
trace "Received valid RPC request", address = $transp.remoteAddress()
|
|
||||||
|
|
||||||
# we need to get `Connection` header value before, because
|
|
||||||
# we are reusing `buffer`, and its value will be lost.
|
|
||||||
connection = header["Connection"]
|
|
||||||
|
|
||||||
let length = header.contentLength()
|
|
||||||
buffer.setLen(length)
|
|
||||||
try:
|
|
||||||
let blenfut = transp.readExactly(addr buffer[0], length)
|
|
||||||
let ores = await withTimeout(blenfut, HttpBodyTimeout)
|
|
||||||
if not ores:
|
|
||||||
# Timeout
|
|
||||||
debug "Timeout expired while receiving request body",
|
|
||||||
address = transp.remoteAddress()
|
|
||||||
discard await transp.sendAnswer(header.version, Http413)
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
blenfut.read()
|
|
||||||
except TransportIncompleteError:
|
|
||||||
# remote peer disconnected
|
|
||||||
debug "Remote peer disconnected", address = transp.remoteAddress()
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
except TransportOsError as exc:
|
|
||||||
debug "Problems with networking", address = transp.remoteAddress(),
|
|
||||||
error = exc.msg
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
|
|
||||||
let future = rpc.route(cast[string](buffer))
|
|
||||||
yield future
|
yield future
|
||||||
if future.failed:
|
if future.failed:
|
||||||
# rpc.route exception
|
debug "Internal error while processing JSON-RPC call"
|
||||||
debug "Internal error, while processing RPC call",
|
return await request.respond(Http503, "Internal error while processing JSON-RPC call")
|
||||||
address = transp.remoteAddress()
|
|
||||||
let res = await transp.sendAnswer(header.version, Http503)
|
|
||||||
if not res:
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
var data = future.read()
|
var data = future.read()
|
||||||
let res = await transp.sendAnswer(header.version, Http200, data)
|
let res = await request.respond(Http200, data)
|
||||||
trace "RPC result has been sent", address = $transp.remoteAddress()
|
trace "JSON-RPC result has been sent"
|
||||||
if not res:
|
return res
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
elif vres == ErrorFailure:
|
|
||||||
debug "Remote peer disconnected", address = transp.remoteAddress()
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
|
|
||||||
if header.version in {HttpVersion09, HttpVersion10}:
|
|
||||||
debug "Disconnecting client", address = transp.remoteAddress()
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
if connection == "close":
|
return dumbResponse()
|
||||||
debug "Disconnecting client", address = transp.remoteAddress()
|
|
||||||
await transp.closeWait()
|
|
||||||
break
|
|
||||||
|
|
||||||
debug "Finished connection", address = $transp.remoteAddress()
|
let initialServerCount = len(rpcServer.httpServers)
|
||||||
|
|
||||||
# Utility functions for setting up servers using stream transport addresses
|
|
||||||
|
|
||||||
proc addStreamServer*(server: RpcHttpServer, address: TransportAddress) =
|
|
||||||
try:
|
try:
|
||||||
info "Starting JSON-RPC HTTP server", url = "http://" & $address
|
info "Starting JSON-RPC HTTP server", url = "http://" & $address
|
||||||
var transServer = createStreamServer(address, processClient,
|
var res = HttpServerRef.new(address, processClientRpc(rpcServer))
|
||||||
{ReuseAddr}, udata = server)
|
if res.isOk():
|
||||||
server.servers.add(transServer)
|
let httpServer = res.get()
|
||||||
|
rpcServer.httpServers.add(httpServer)
|
||||||
|
else:
|
||||||
|
raise newException(RpcBindError, "Unable to create server!")
|
||||||
|
|
||||||
except CatchableError as exc:
|
except CatchableError as exc:
|
||||||
error "Failed to create server", address = $address,
|
error "Failed to create server", address = $address,
|
||||||
message = exc.msg
|
message = exc.msg
|
||||||
|
|
||||||
if len(server.servers) == 0:
|
if len(rpcServer.httpServers) != initialServerCount + 1:
|
||||||
# Server was not bound, critical error.
|
# Server was not bound, critical error.
|
||||||
raise newException(RpcBindError, "Unable to create server!")
|
raise newException(RpcBindError, "Unable to create server!")
|
||||||
|
|
||||||
proc addStreamServers*(server: RpcHttpServer,
|
proc addHttpServers*(server: RpcHttpServer,
|
||||||
addresses: openArray[TransportAddress]) =
|
addresses: openArray[TransportAddress]) =
|
||||||
for item in addresses:
|
for item in addresses:
|
||||||
server.addStreamServer(item)
|
server.addHttpServer(item)
|
||||||
|
|
||||||
proc addStreamServer*(server: RpcHttpServer, address: string) =
|
proc addHttpServer*(server: RpcHttpServer, address: string) =
|
||||||
## Create new server and assign it to addresses ``addresses``.
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
var
|
var
|
||||||
tas4: seq[TransportAddress]
|
tas4: seq[TransportAddress]
|
||||||
@ -247,21 +80,22 @@ proc addStreamServer*(server: RpcHttpServer, address: string) =
|
|||||||
discard
|
discard
|
||||||
|
|
||||||
for r in tas4:
|
for r in tas4:
|
||||||
server.addStreamServer(r)
|
server.addHttpServer(r)
|
||||||
added.inc
|
added.inc
|
||||||
|
if added == 0: # avoid ipv4 + ipv6 running together
|
||||||
for r in tas6:
|
for r in tas6:
|
||||||
server.addStreamServer(r)
|
server.addHttpServer(r)
|
||||||
added.inc
|
added.inc
|
||||||
|
|
||||||
if added == 0:
|
if added == 0:
|
||||||
# Addresses could not be resolved, critical error.
|
# Addresses could not be resolved, critical error.
|
||||||
raise newException(RpcAddressUnresolvableError, "Unable to get address!")
|
raise newException(RpcAddressUnresolvableError, "Unable to get address!")
|
||||||
|
|
||||||
proc addStreamServers*(server: RpcHttpServer, addresses: openArray[string]) =
|
proc addHttpServers*(server: RpcHttpServer, addresses: openArray[string]) =
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
server.addStreamServer(address)
|
server.addHttpServer(address)
|
||||||
|
|
||||||
proc addStreamServer*(server: RpcHttpServer, address: string, port: Port) =
|
proc addHttpServer*(server: RpcHttpServer, address: string, port: Port) =
|
||||||
var
|
var
|
||||||
tas4: seq[TransportAddress]
|
tas4: seq[TransportAddress]
|
||||||
tas6: seq[TransportAddress]
|
tas6: seq[TransportAddress]
|
||||||
@ -285,19 +119,19 @@ proc addStreamServer*(server: RpcHttpServer, address: string, port: Port) =
|
|||||||
"Address " & address & " could not be resolved!")
|
"Address " & address & " could not be resolved!")
|
||||||
|
|
||||||
for r in tas4:
|
for r in tas4:
|
||||||
server.addStreamServer(r)
|
server.addHttpServer(r)
|
||||||
added.inc
|
added.inc
|
||||||
for r in tas6:
|
for r in tas6:
|
||||||
server.addStreamServer(r)
|
server.addHttpServer(r)
|
||||||
added.inc
|
added.inc
|
||||||
|
|
||||||
if len(server.servers) == 0:
|
if len(server.httpServers) == 0:
|
||||||
# Server was not bound, critical error.
|
# Server was not bound, critical error.
|
||||||
raise newException(RpcBindError,
|
raise newException(RpcBindError,
|
||||||
"Could not setup server on " & address & ":" & $int(port))
|
"Could not setup server on " & address & ":" & $int(port))
|
||||||
|
|
||||||
proc new*(T: type RpcHttpServer): T =
|
proc new*(T: type RpcHttpServer): T =
|
||||||
T(router: RpcRouter.init(), servers: @[])
|
T(router: RpcRouter.init(), httpServers: @[])
|
||||||
|
|
||||||
proc new*(T: type RpcHttpServer, router: RpcRouter): T =
|
proc new*(T: type RpcHttpServer, router: RpcRouter): T =
|
||||||
T(router: router, servers: @[])
|
T(router: router, servers: @[])
|
||||||
@ -311,12 +145,12 @@ proc newRpcHttpServer*(router: RpcRouter): RpcHttpServer =
|
|||||||
proc newRpcHttpServer*(addresses: openArray[TransportAddress]): RpcHttpServer =
|
proc newRpcHttpServer*(addresses: openArray[TransportAddress]): RpcHttpServer =
|
||||||
## Create new server and assign it to addresses ``addresses``.
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
result = newRpcHttpServer()
|
result = newRpcHttpServer()
|
||||||
result.addStreamServers(addresses)
|
result.addHttpServers(addresses)
|
||||||
|
|
||||||
proc newRpcHttpServer*(addresses: openArray[string]): RpcHttpServer =
|
proc newRpcHttpServer*(addresses: openArray[string]): RpcHttpServer =
|
||||||
## Create new server and assign it to addresses ``addresses``.
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
result = newRpcHttpServer()
|
result = newRpcHttpServer()
|
||||||
result.addStreamServers(addresses)
|
result.addHttpServers(addresses)
|
||||||
|
|
||||||
proc newRpcHttpServer*(addresses: openArray[string], router: RpcRouter): RpcHttpServer =
|
proc newRpcHttpServer*(addresses: openArray[string], router: RpcRouter): RpcHttpServer =
|
||||||
## Create new server and assign it to addresses ``addresses``.
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
@ -330,22 +164,17 @@ proc newRpcHttpServer*(addresses: openArray[TransportAddress], router: RpcRouter
|
|||||||
|
|
||||||
proc start*(server: RpcHttpServer) =
|
proc start*(server: RpcHttpServer) =
|
||||||
## Start the RPC server.
|
## Start the RPC server.
|
||||||
for item in server.servers:
|
for item in server.httpServers:
|
||||||
debug "HTTP RPC server started", address = item.local
|
debug "HTTP RPC server started" # (todo: fix this), address = item
|
||||||
item.start()
|
item.start()
|
||||||
|
|
||||||
proc stop*(server: RpcHttpServer) =
|
proc stop*(server: RpcHttpServer) {.async.} =
|
||||||
## Stop the RPC server.
|
## Stop the RPC server.
|
||||||
for item in server.servers:
|
for item in server.httpServers:
|
||||||
debug "HTTP RPC server stopped", address = item.local
|
debug "HTTP RPC server stopped" # (todo: fix this), address = item.local
|
||||||
item.stop()
|
await item.stop()
|
||||||
|
|
||||||
proc close*(server: RpcHttpServer) =
|
|
||||||
## Cleanup resources of RPC server.
|
|
||||||
for item in server.servers:
|
|
||||||
item.close()
|
|
||||||
|
|
||||||
proc closeWait*(server: RpcHttpServer) {.async.} =
|
proc closeWait*(server: RpcHttpServer) {.async.} =
|
||||||
## Cleanup resources of RPC server.
|
## Cleanup resources of RPC server.
|
||||||
for item in server.servers:
|
for item in server.httpServers:
|
||||||
await item.closeWait()
|
await item.closeWait()
|
||||||
|
198
json_rpc/servers/shttpserver.nim
Normal file
198
json_rpc/servers/shttpserver.nim
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
import
|
||||||
|
stew/byteutils,
|
||||||
|
std/[strutils],
|
||||||
|
chronicles, httputils, chronos,
|
||||||
|
chronos/apps/http/shttpserver,
|
||||||
|
".."/[errors, server]
|
||||||
|
|
||||||
|
export server
|
||||||
|
|
||||||
|
logScope:
|
||||||
|
topics = "JSONRPC-HTTPS-SERVER"
|
||||||
|
|
||||||
|
type
|
||||||
|
ReqStatus = enum
|
||||||
|
Success, Error, ErrorFailure
|
||||||
|
|
||||||
|
RpcSecureHttpServer* = ref object of RpcServer
|
||||||
|
secureHttpServers: seq[SecureHttpServerRef]
|
||||||
|
|
||||||
|
proc addSecureHttpServer*(rpcServer: RpcSecureHttpServer,
|
||||||
|
address: TransportAddress,
|
||||||
|
tlsPrivateKey: TLSPrivateKey,
|
||||||
|
tlsCertificate: TLSCertificate
|
||||||
|
) =
|
||||||
|
proc processClientRpc(rpcServer: RpcSecureHttpServer): HttpProcessCallback {.closure.} =
|
||||||
|
return proc (req: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||||
|
if req.isOk():
|
||||||
|
let request = req.get()
|
||||||
|
let body = await request.getBody()
|
||||||
|
|
||||||
|
let future = rpcServer.route(string.fromBytes(body))
|
||||||
|
yield future
|
||||||
|
if future.failed:
|
||||||
|
debug "Internal error while processing JSON-RPC call"
|
||||||
|
return await request.respond(Http503, "Internal error while processing JSON-RPC call")
|
||||||
|
else:
|
||||||
|
var data = future.read()
|
||||||
|
let res = await request.respond(Http200, data)
|
||||||
|
trace "JSON-RPC result has been sent"
|
||||||
|
return res
|
||||||
|
else:
|
||||||
|
if req.error.code == Http408:
|
||||||
|
debug "Timeout error while processing JSON-RPC call"
|
||||||
|
return dumbResponse()
|
||||||
|
|
||||||
|
let initialServerCount = len(rpcServer.secureHttpServers)
|
||||||
|
try:
|
||||||
|
info "Starting JSON-RPC HTTPS server", url = "https://" & $address
|
||||||
|
var res = SecureHttpServerRef.new(address,
|
||||||
|
processClientRpc(rpcServer),
|
||||||
|
tlsPrivateKey,
|
||||||
|
tlsCertificate,
|
||||||
|
serverFlags = {Secure},
|
||||||
|
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||||
|
)
|
||||||
|
if res.isOk():
|
||||||
|
let secureHttpServer = res.get()
|
||||||
|
rpcServer.secureHttpServers.add(secureHttpServer)
|
||||||
|
else:
|
||||||
|
raise newException(RpcBindError, "Unable to create server!")
|
||||||
|
|
||||||
|
except CatchableError as exc:
|
||||||
|
error "Failed to create server", address = $address,
|
||||||
|
message = exc.msg
|
||||||
|
|
||||||
|
if len(rpcServer.secureHttpServers) != initialServerCount + 1:
|
||||||
|
# Server was not bound, critical error.
|
||||||
|
raise newException(RpcBindError, "Unable to create server!")
|
||||||
|
|
||||||
|
proc addSecureHttpServers*(server: RpcSecureHttpServer,
|
||||||
|
addresses: openArray[TransportAddress],
|
||||||
|
tlsPrivateKey: TLSPrivateKey,
|
||||||
|
tlsCertificate: TLSCertificate
|
||||||
|
) =
|
||||||
|
for item in addresses:
|
||||||
|
server.addSecureHttpServer(item, tlsPrivateKey, tlsCertificate)
|
||||||
|
|
||||||
|
proc addSecureHttpServer*(server: RpcSecureHttpServer,
|
||||||
|
address: string,
|
||||||
|
tlsPrivateKey: TLSPrivateKey,
|
||||||
|
tlsCertificate: TLSCertificate
|
||||||
|
) =
|
||||||
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
|
var
|
||||||
|
tas4: seq[TransportAddress]
|
||||||
|
tas6: seq[TransportAddress]
|
||||||
|
added = 0
|
||||||
|
|
||||||
|
# Attempt to resolve `address` for IPv4 address space.
|
||||||
|
try:
|
||||||
|
tas4 = resolveTAddress(address, AddressFamily.IPv4)
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
# Attempt to resolve `address` for IPv6 address space.
|
||||||
|
try:
|
||||||
|
tas6 = resolveTAddress(address, AddressFamily.IPv6)
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
for r in tas4:
|
||||||
|
server.addSecureHttpServer(r, tlsPrivateKey, tlsCertificate)
|
||||||
|
added.inc
|
||||||
|
if added == 0: # avoid ipv4 + ipv6 running together
|
||||||
|
for r in tas6:
|
||||||
|
server.addSecureHttpServer(r, tlsPrivateKey, tlsCertificate)
|
||||||
|
added.inc
|
||||||
|
|
||||||
|
if added == 0:
|
||||||
|
# Addresses could not be resolved, critical error.
|
||||||
|
raise newException(RpcAddressUnresolvableError, "Unable to get address!")
|
||||||
|
|
||||||
|
proc addSecureHttpServers*(server: RpcSecureHttpServer,
|
||||||
|
addresses: openArray[string],
|
||||||
|
tlsPrivateKey: TLSPrivateKey,
|
||||||
|
tlsCertificate: TLSCertificate
|
||||||
|
) =
|
||||||
|
for address in addresses:
|
||||||
|
server.addSecureHttpServer(address, tlsPrivateKey, tlsCertificate)
|
||||||
|
|
||||||
|
proc addSecureHttpServer*(server: RpcSecureHttpServer,
|
||||||
|
address: string,
|
||||||
|
port: Port,
|
||||||
|
tlsPrivateKey: TLSPrivateKey,
|
||||||
|
tlsCertificate: TLSCertificate
|
||||||
|
) =
|
||||||
|
var
|
||||||
|
tas4: seq[TransportAddress]
|
||||||
|
tas6: seq[TransportAddress]
|
||||||
|
added = 0
|
||||||
|
|
||||||
|
# Attempt to resolve `address` for IPv4 address space.
|
||||||
|
try:
|
||||||
|
tas4 = resolveTAddress(address, port, AddressFamily.IPv4)
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
# Attempt to resolve `address` for IPv6 address space.
|
||||||
|
try:
|
||||||
|
tas6 = resolveTAddress(address, port, AddressFamily.IPv6)
|
||||||
|
except CatchableError:
|
||||||
|
discard
|
||||||
|
|
||||||
|
if len(tas4) == 0 and len(tas6) == 0:
|
||||||
|
# Address was not resolved, critical error.
|
||||||
|
raise newException(RpcAddressUnresolvableError,
|
||||||
|
"Address " & address & " could not be resolved!")
|
||||||
|
|
||||||
|
for r in tas4:
|
||||||
|
server.addSecureHttpServer(r, tlsPrivateKey, tlsCertificate)
|
||||||
|
added.inc
|
||||||
|
for r in tas6:
|
||||||
|
server.addSecureHttpServer(r, tlsPrivateKey, tlsCertificate)
|
||||||
|
added.inc
|
||||||
|
|
||||||
|
if len(server.secureHttpServers) == 0:
|
||||||
|
# Server was not bound, critical error.
|
||||||
|
raise newException(RpcBindError,
|
||||||
|
"Could not setup server on " & address & ":" & $int(port))
|
||||||
|
|
||||||
|
proc new*(T: type RpcSecureHttpServer): T =
|
||||||
|
T(router: RpcRouter.init(), secureHttpServers: @[])
|
||||||
|
|
||||||
|
proc newRpcSecureHttpServer*(): RpcSecureHttpServer =
|
||||||
|
RpcSecureHttpServer.new()
|
||||||
|
|
||||||
|
proc newRpcSecureHttpServer*(addresses: openArray[TransportAddress],
|
||||||
|
tlsPrivateKey: TLSPrivateKey,
|
||||||
|
tlsCertificate: TLSCertificate
|
||||||
|
): RpcSecureHttpServer =
|
||||||
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
|
result = newRpcSecureHttpServer()
|
||||||
|
result.addSecureHttpServers(addresses, tlsPrivateKey, tlsCertificate)
|
||||||
|
|
||||||
|
proc newRpcSecureHttpServer*(addresses: openArray[string],
|
||||||
|
tlsPrivateKey: TLSPrivateKey,
|
||||||
|
tlsCertificate: TLSCertificate
|
||||||
|
): RpcSecureHttpServer =
|
||||||
|
## Create new server and assign it to addresses ``addresses``.
|
||||||
|
result = newRpcSecureHttpServer()
|
||||||
|
result.addSecureHttpServers(addresses, tlsPrivateKey, tlsCertificate)
|
||||||
|
|
||||||
|
proc start*(server: RpcSecureHttpServer) =
|
||||||
|
## Start the RPC server.
|
||||||
|
for item in server.secureHttpServers:
|
||||||
|
debug "HTTPS RPC server started" # (todo: fix this), address = item
|
||||||
|
item.start()
|
||||||
|
|
||||||
|
proc stop*(server: RpcSecureHttpServer) {.async.} =
|
||||||
|
## Stop the RPC server.
|
||||||
|
for item in server.secureHttpServers:
|
||||||
|
debug "HTTPS RPC server stopped" # (todo: fix this), address = item.local
|
||||||
|
await item.stop()
|
||||||
|
|
||||||
|
proc closeWait*(server: RpcSecureHttpServer) {.async.} =
|
||||||
|
## Cleanup resources of RPC server.
|
||||||
|
for item in server.secureHttpServers:
|
||||||
|
await item.closeWait()
|
@ -1,76 +1,15 @@
|
|||||||
import unittest, json, strutils
|
import unittest, json, strutils
|
||||||
import httputils, chronicles
|
import httputils
|
||||||
import ../json_rpc/[rpcserver, rpcclient]
|
import ../json_rpc/[rpcserver, rpcclient]
|
||||||
|
|
||||||
const
|
const TestsCount = 100
|
||||||
TestsCount = 100
|
|
||||||
BufferSize = 8192
|
|
||||||
BigHeaderSize = 8 * 1024 + 1
|
|
||||||
BigBodySize = 128 * 1024 + 1
|
|
||||||
HeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)]
|
|
||||||
|
|
||||||
Requests = [
|
proc simpleTest(address: string, port: Port): Future[bool] {.async.} =
|
||||||
"GET / HTTP/1.1\r\n" &
|
var client = newRpcHttpClient()
|
||||||
"Host: status.im\r\n" &
|
await client.connect(address, port)
|
||||||
"Content-Length: 71\r\n" &
|
var r = await client.call("noParamsProc", %[])
|
||||||
"Content-Type: text/html\r\n" &
|
if r.getStr == "Hello world":
|
||||||
"Connection: close\r\n" &
|
result = true
|
||||||
"\r\n" &
|
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"myProc\",\"params\":[\"abc\", [1, 2, 3]],\"id\":67}",
|
|
||||||
"BADHEADER\r\n\r\n",
|
|
||||||
"GET / HTTP/1.1\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Type: application/json\r\n" &
|
|
||||||
"Connection: close\r\n" &
|
|
||||||
"\r\n",
|
|
||||||
"PUT / HTTP/1.1\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Length: 71\r\n" &
|
|
||||||
"Content-Type: text/html\r\n" &
|
|
||||||
"Connection: close\r\n" &
|
|
||||||
"\r\n" &
|
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"myProc\",\"params\":[\"abc\", [1, 2, 3]],\"id\":67}",
|
|
||||||
"DELETE / HTTP/1.1\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Length: 71\r\n" &
|
|
||||||
"Content-Type: text/html\r\n" &
|
|
||||||
"Connection: close\r\n" &
|
|
||||||
"\r\n" &
|
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"myProc\",\"params\":[\"abc\", [1, 2, 3]],\"id\":67}",
|
|
||||||
"GET / HTTP/0.9\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Length: 71\r\n" &
|
|
||||||
"Content-Type: application/json\r\n" &
|
|
||||||
"\r\n" &
|
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"myProc\",\"params\":[\"abc\", [1, 2, 3]],\"id\":67}",
|
|
||||||
"GET / HTTP/1.0\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Length: 71\r\n" &
|
|
||||||
"Content-Type: application/json\r\n" &
|
|
||||||
"\r\n" &
|
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"myProc\",\"params\":[\"abc\", [1, 2, 3]],\"id\":67}",
|
|
||||||
"GET / HTTP/1.1\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Length: 71\r\n" &
|
|
||||||
"Content-Type: application/json\r\n" &
|
|
||||||
"Connection: close\r\n" &
|
|
||||||
"\r\n" &
|
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"myProc\",\"params\":[\"abc\", [1, 2, 3]],\"id\":67}",
|
|
||||||
"GET / HTTP/1.1\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Length: 49\r\n" &
|
|
||||||
"Content-Type: application/json\r\n" &
|
|
||||||
"Connection: close\r\n" &
|
|
||||||
"\r\n" &
|
|
||||||
"{\"jsonrpc\":\"2.0\",\"method\":\"noParamsProc\",\"id\":67}",
|
|
||||||
"GET / HTTP/1.1\r\n" &
|
|
||||||
"Host: status.im\r\n" &
|
|
||||||
"Content-Length: 137438953472\r\n" &
|
|
||||||
"Content-Type: application/json\r\n" &
|
|
||||||
"Connection: close\r\n" &
|
|
||||||
"\r\n" &
|
|
||||||
"{128 gb Content-Length}",
|
|
||||||
]
|
|
||||||
|
|
||||||
proc continuousTest(address: string, port: Port): Future[int] {.async.} =
|
proc continuousTest(address: string, port: Port): Future[int] {.async.} =
|
||||||
var client = newRpcHttpClient()
|
var client = newRpcHttpClient()
|
||||||
@ -82,76 +21,22 @@ proc continuousTest(address: string, port: Port): Future[int] {.async.} =
|
|||||||
result += 1
|
result += 1
|
||||||
await client.close()
|
await client.close()
|
||||||
|
|
||||||
proc customMessage(address: TransportAddress,
|
proc invalidTest(address: string, port: Port): Future[bool] {.async.} =
|
||||||
data: string,
|
var client = newRpcHttpClient()
|
||||||
expect: int): Future[bool] {.async.} =
|
await client.connect(address, port)
|
||||||
var buffer = newSeq[byte](BufferSize)
|
var invalidA, invalidB: bool
|
||||||
var header: HttpResponseHeader
|
try:
|
||||||
var transp = await connect(address)
|
var r = await client.call("invalidProcA", %[])
|
||||||
defer: transp.close()
|
discard r
|
||||||
|
except ValueError:
|
||||||
let wres = await transp.write(data)
|
invalidA = true
|
||||||
doAssert(wres == len(data))
|
try:
|
||||||
let rres = await transp.readUntil(addr buffer[0], BufferSize, HeadersMark)
|
var r = await client.call("invalidProcB", %[1, 2, 3])
|
||||||
doAssert(rres > 0)
|
discard r
|
||||||
buffer.setLen(rres)
|
except ValueError:
|
||||||
header = parseResponse(buffer)
|
invalidB = true
|
||||||
doAssert(header.success())
|
if invalidA and invalidB:
|
||||||
return header.code == expect
|
result = true
|
||||||
|
|
||||||
proc headerTest(address: string, port: Port): Future[bool] {.async.} =
|
|
||||||
var a = resolveTAddress(address, port)
|
|
||||||
var header = "GET / HTTP/1.1\r\n"
|
|
||||||
var i = 0
|
|
||||||
while len(header) <= BigHeaderSize:
|
|
||||||
header.add("Field" & $i & ": " & $i & "\r\n")
|
|
||||||
inc(i)
|
|
||||||
header.add("Content-Length: 71\r\n")
|
|
||||||
header.add("Content-Type: application/json\r\n")
|
|
||||||
header.add("Connection: close\r\n\r\n")
|
|
||||||
header.add("{\"jsonrpc\":\"2.0\",\"method\":\"myProc\",\"params\":[\"abc\", [1, 2, 3]],\"id\":67}")
|
|
||||||
return await customMessage(a[0], header, 413)
|
|
||||||
|
|
||||||
proc bodyTest(address: string, port: Port): Future[bool] {.async.} =
|
|
||||||
var body = repeat('B', BigBodySize)
|
|
||||||
var a = resolveTAddress(address, port)
|
|
||||||
var header = "GET / HTTP/1.1\r\n"
|
|
||||||
header.add("Content-Length: " & $len(body) & "\r\n")
|
|
||||||
header.add("Content-Type: application/json\r\n")
|
|
||||||
header.add("Connection: close\r\n\r\n")
|
|
||||||
header.add(body)
|
|
||||||
return await customMessage(a[0], header, 413)
|
|
||||||
|
|
||||||
proc disconTest(address: string, port: Port,
|
|
||||||
number: int, expect: int): Future[bool] {.async.} =
|
|
||||||
var a = resolveTAddress(address, port)
|
|
||||||
var buffer = newSeq[byte](BufferSize)
|
|
||||||
var header: HttpResponseHeader
|
|
||||||
var transp = await connect(a[0])
|
|
||||||
defer: transp.close()
|
|
||||||
|
|
||||||
let data = Requests[number]
|
|
||||||
let wres = await transp.write(data)
|
|
||||||
doAssert(wres == len(data))
|
|
||||||
let rres = await transp.readUntil(addr buffer[0], BufferSize, HeadersMark)
|
|
||||||
doAssert(rres > 0)
|
|
||||||
buffer.setLen(rres)
|
|
||||||
header = parseResponse(buffer)
|
|
||||||
doAssert(header.success())
|
|
||||||
if header.code != expect:
|
|
||||||
return false
|
|
||||||
|
|
||||||
let length = header.contentLength()
|
|
||||||
doAssert(length > 0)
|
|
||||||
buffer.setLen(length)
|
|
||||||
await transp.readExactly(addr buffer[0], len(buffer))
|
|
||||||
let left = await transp.read()
|
|
||||||
return len(left) == 0 and transp.atEof()
|
|
||||||
|
|
||||||
proc simpleTest(address: string, port: Port,
|
|
||||||
number: int, expect: int): Future[bool] {.async.} =
|
|
||||||
var a = resolveTAddress(address, port)
|
|
||||||
result = await customMessage(a[0], Requests[number], expect)
|
|
||||||
|
|
||||||
var httpsrv = newRpcHttpServer(["localhost:8545"])
|
var httpsrv = newRpcHttpServer(["localhost:8545"])
|
||||||
|
|
||||||
@ -163,33 +48,13 @@ httpsrv.rpc("noParamsProc") do():
|
|||||||
|
|
||||||
httpsrv.start()
|
httpsrv.start()
|
||||||
|
|
||||||
suite "HTTP Server/HTTP Client RPC test suite":
|
suite "JSON-RPC test suite":
|
||||||
|
test "Simple RPC call":
|
||||||
|
check waitFor(simpleTest("localhost", Port(8545))) == true
|
||||||
test "Continuous RPC calls (" & $TestsCount & " messages)":
|
test "Continuous RPC calls (" & $TestsCount & " messages)":
|
||||||
check waitFor(continuousTest("localhost", Port(8545))) == TestsCount
|
check waitFor(continuousTest("localhost", Port(8545))) == TestsCount
|
||||||
test "Wrong [Content-Type] test":
|
test "Invalid RPC calls":
|
||||||
check waitFor(simpleTest("localhost", Port(8545), 0, 415)) == true
|
check waitFor(invalidTest("localhost", Port(8545))) == true
|
||||||
test "Bad request header test":
|
|
||||||
check waitFor(simpleTest("localhost", Port(8545), 1, 400)) == true
|
|
||||||
test "Zero [Content-Length] test":
|
|
||||||
check waitFor(simpleTest("localhost", Port(8545), 2, 411)) == true
|
|
||||||
test "PUT/DELETE methods test":
|
|
||||||
check:
|
|
||||||
waitFor(simpleTest("localhost", Port(8545), 3, 405)) == true
|
|
||||||
waitFor(simpleTest("localhost", Port(8545), 4, 405)) == true
|
|
||||||
test "Oversized headers test":
|
|
||||||
check waitFor(headerTest("localhost", Port(8545))) == true
|
|
||||||
test "Oversized request test":
|
|
||||||
check waitFor(bodyTest("localhost", Port(8545))) == true
|
|
||||||
test "HTTP/0.9 and HTTP/1.0 client test":
|
|
||||||
check:
|
|
||||||
waitFor(disconTest("localhost", Port(8545), 5, 200)) == true
|
|
||||||
waitFor(disconTest("localhost", Port(8545), 6, 200)) == true
|
|
||||||
test "[Connection]: close test":
|
|
||||||
check waitFor(disconTest("localhost", Port(8545), 7, 200)) == true
|
|
||||||
test "Omitted params test":
|
|
||||||
check waitFor(simpleTest("localhost", Port(8545), 8, 200)) == true
|
|
||||||
test "Big Content-Length":
|
|
||||||
check waitFor(simpleTest("localhost", Port(8545), 9, 413)) == true
|
|
||||||
|
|
||||||
httpsrv.stop()
|
waitFor httpsrv.stop()
|
||||||
waitFor httpsrv.closeWait()
|
waitFor httpsrv.closeWait()
|
||||||
|
123
tests/testhttps.nim
Normal file
123
tests/testhttps.nim
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import unittest, json, strutils
|
||||||
|
import httputils
|
||||||
|
import ../json_rpc/[rpcsecureserver, rpcclient]
|
||||||
|
import chronos/[streams/tlsstream, apps/http/httpcommon]
|
||||||
|
|
||||||
|
const TestsCount = 100
|
||||||
|
|
||||||
|
# To create self-signed certificate and key you can use openssl
|
||||||
|
# openssl req -new -x509 -sha256 -newkey rsa:2048 -nodes \
|
||||||
|
# -keyout example-com.key.pem -days 3650 -out example-com.cert.pem
|
||||||
|
const HttpsSelfSignedRsaKey = """
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCn7tXGLKMIMzOG
|
||||||
|
tVzUixax1/ftlSLcpEAkZMORuiCCnYjtIJhGZdzRFZC8fBlfAJZpLIAOfX2L2f1J
|
||||||
|
ZuwpwDkOIvNqKMBrl5Mvkl5azPT0rtnjuwrcqN5NFtbmZPKFYvbjex2aXGqjl5MW
|
||||||
|
nQIs/ZA++DVEXmaN9oDxcZsvRMDKfrGQf9iLeoVL47Gx9KpqNqD/JLIn4LpieumV
|
||||||
|
yYidm6ukTOqHRvrWm36y6VvKW4TE97THacULmkeahtTf8zDJbbh4EO+gifgwgJ2W
|
||||||
|
BUS0+5hMcWu8111mXmanlOVlcoW8fH8RmPjL1eK1Z3j3SVHEf7oWZtIVW5gGA0jQ
|
||||||
|
nfA4K51RAgMBAAECggEANZ7/R13tWKrwouy6DWuz/WlWUtgx333atUQvZhKmWs5u
|
||||||
|
cDjeJmxUC7b1FhoSB9GqNT7uTLIpKkSaqZthgRtNnIPwcU890Zz+dEwqMJgNByvl
|
||||||
|
it+oYjjRco/+YmaNQaYN6yjelPE5Y678WlYb4b29Fz4t0/zIhj/VgEKkKH2tiXpS
|
||||||
|
TIicoM7pSOscEUfaW3yp5bS5QwNU6/AaF1wws0feBACd19ZkcdPvr52jopbhxlXw
|
||||||
|
h3XTV/vXIJd5zWGp0h/Jbd4xcD4MVo2GjfkeORKY6SjDaNzt8OGtePcKnnbUVu8b
|
||||||
|
2XlDxukhDQXqJ3g0sHz47mhvo4JeIM+FgymRm+3QmQKBgQDTawrEA3Zy9WvucaC7
|
||||||
|
Zah02oE9nuvpF12lZ7WJh7+tZ/1ss+Fm7YspEKaUiEk7nn1CAVFtem4X4YCXTBiC
|
||||||
|
Oqq/o+ipv1yTur0ae6m4pwLm5wcMWBh3H5zjfQTfrClNN8yjWv8u3/sq8KesHPnT
|
||||||
|
R92/sMAptAChPgTzQphWbxFiYwKBgQDLWFaBqXfZYVnTyUvKX8GorS6jGWc6Eh4l
|
||||||
|
lAFA+2EBWDICrUxsDPoZjEXrWCixdqLhyehaI3KEFIx2bcPv6X2c7yx3IG5lA/Gx
|
||||||
|
TZiKlY74c6jOTstkdLW9RJbg1VUHUVZMf/Owt802YmEfUI5S5v7jFmKW6VG+io+K
|
||||||
|
+5KYeHD1uwKBgQDMf53KPA82422jFwYCPjLT1QduM2q97HwIomhWv5gIg63+l4BP
|
||||||
|
rzYMYq6+vZUYthUy41OAMgyLzPQ1ZMXQMi83b7R9fTxvKRIBq9xfYCzObGnE5vHD
|
||||||
|
SDDZWvR75muM5Yxr9nkfPkgVIPMO6Hg+hiVYZf96V0LEtNjU9HWmJYkLQQKBgQCQ
|
||||||
|
ULGUdGHKtXy7AjH3/t3CiKaAupa4cANVSCVbqQy/l4hmvfdu+AbH+vXkgTzgNgKD
|
||||||
|
nHh7AI1Vj//gTSayLlQn/Nbh9PJkXtg5rYiFUn+VdQBo6yMOuIYDPZqXFtCx0Nge
|
||||||
|
kvCwisHpxwiG4PUhgS+Em259DDonsM8PJFx2OYRx4QKBgEQpGhg71Oi9MhPJshN7
|
||||||
|
dYTowaMS5eLTk2264ARaY+hAIV7fgvUa+5bgTVaWL+Cfs33hi4sMRqlEwsmfds2T
|
||||||
|
cnQiJ4cU20Euldfwa5FLnk6LaWdOyzYt/ICBJnKFRwfCUbS4Bu5rtMEM+3t0wxnJ
|
||||||
|
IgaD04WhoL9EX0Qo3DC1+0kG
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This SSL certificate will expire 13 October 2030.
|
||||||
|
const HttpsSelfSignedRsaCert = """
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDnzCCAoegAwIBAgIUUdcusjDd3XQi3FPM8urdFG3qI+8wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwXzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
|
||||||
|
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEYMBYGA1UEAwwPMTI3LjAuMC4xOjQz
|
||||||
|
ODA4MB4XDTIwMTAxMjIxNDUwMVoXDTMwMTAxMDIxNDUwMVowXzELMAkGA1UEBhMC
|
||||||
|
QVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdp
|
||||||
|
dHMgUHR5IEx0ZDEYMBYGA1UEAwwPMTI3LjAuMC4xOjQzODA4MIIBIjANBgkqhkiG
|
||||||
|
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp+7VxiyjCDMzhrVc1IsWsdf37ZUi3KRAJGTD
|
||||||
|
kboggp2I7SCYRmXc0RWQvHwZXwCWaSyADn19i9n9SWbsKcA5DiLzaijAa5eTL5Je
|
||||||
|
Wsz09K7Z47sK3KjeTRbW5mTyhWL243sdmlxqo5eTFp0CLP2QPvg1RF5mjfaA8XGb
|
||||||
|
L0TAyn6xkH/Yi3qFS+OxsfSqajag/ySyJ+C6YnrplcmInZurpEzqh0b61pt+sulb
|
||||||
|
yluExPe0x2nFC5pHmobU3/MwyW24eBDvoIn4MICdlgVEtPuYTHFrvNddZl5mp5Tl
|
||||||
|
ZXKFvHx/EZj4y9XitWd490lRxH+6FmbSFVuYBgNI0J3wOCudUQIDAQABo1MwUTAd
|
||||||
|
BgNVHQ4EFgQUBKha84woY5WkFxKw7qx1cONg1H8wHwYDVR0jBBgwFoAUBKha84wo
|
||||||
|
Y5WkFxKw7qx1cONg1H8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC
|
||||||
|
AQEAHZMYt9Ry+Xj3vTbzpGFQzYQVTJlfJWSN6eWNOivRFQE5io9kOBEe5noa8aLo
|
||||||
|
dLkw6ztxRP2QRJmlhGCO9/HwS17ckrkgZp3EC2LFnzxcBmoZu+owfxOT1KqpO52O
|
||||||
|
IKOl8eVohi1pEicE4dtTJVcpI7VCMovnXUhzx1Ci4Vibns4a6H+BQa19a1JSpifN
|
||||||
|
tO8U5jkjJ8Jprs/VPFhJj2O3di53oDHaYSE5eOrm2ZO14KFHSk9cGcOGmcYkUv8B
|
||||||
|
nV5vnGadH5Lvfxb/BCpuONabeRdOxMt9u9yQ89vNpxFtRdZDCpGKZBCfmUP+5m3m
|
||||||
|
N8r5CwGcIX/XPC3lKazzbZ8baA==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
"""
|
||||||
|
|
||||||
|
proc simpleTest(address: string, port: Port): Future[bool] {.async.} =
|
||||||
|
var client = newRpcHttpClient(secure=true)
|
||||||
|
await client.connect(address, port, secure=true)
|
||||||
|
var r = await client.call("noParamsProc", %[])
|
||||||
|
if r.getStr == "Hello world":
|
||||||
|
result = true
|
||||||
|
|
||||||
|
proc continuousTest(address: string, port: Port): Future[int] {.async.} =
|
||||||
|
var client = newRpcHttpClient(secure=true)
|
||||||
|
result = 0
|
||||||
|
for i in 0..<TestsCount:
|
||||||
|
await client.connect(address, port, secure=true)
|
||||||
|
var r = await client.call("myProc", %[%"abc", %[1, 2, 3, i]])
|
||||||
|
if r.getStr == "Hello abc data: [1, 2, 3, " & $i & "]":
|
||||||
|
result += 1
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
proc invalidTest(address: string, port: Port): Future[bool] {.async.} =
|
||||||
|
var client = newRpcHttpClient(secure=true)
|
||||||
|
await client.connect(address, port, secure=true)
|
||||||
|
var invalidA, invalidB: bool
|
||||||
|
try:
|
||||||
|
var r = await client.call("invalidProcA", %[])
|
||||||
|
discard r
|
||||||
|
except ValueError:
|
||||||
|
invalidA = true
|
||||||
|
try:
|
||||||
|
var r = await client.call("invalidProcB", %[1, 2, 3])
|
||||||
|
discard r
|
||||||
|
except ValueError:
|
||||||
|
invalidB = true
|
||||||
|
if invalidA and invalidB:
|
||||||
|
result = true
|
||||||
|
|
||||||
|
let secureKey = TLSPrivateKey.init(HttpsSelfSignedRsaKey)
|
||||||
|
let secureCert = TLSCertificate.init(HttpsSelfSignedRsaCert)
|
||||||
|
var secureHttpSrv = newRpcSecureHttpServer(["localhost:8545"], secureKey, secureCert)
|
||||||
|
|
||||||
|
# Create RPC on server
|
||||||
|
secureHttpSrv.rpc("myProc") do(input: string, data: array[0..3, int]):
|
||||||
|
result = %("Hello " & input & " data: " & $data)
|
||||||
|
secureHttpSrv.rpc("noParamsProc") do():
|
||||||
|
result = %("Hello world")
|
||||||
|
|
||||||
|
secureHttpSrv.start()
|
||||||
|
|
||||||
|
suite "JSON-RPC test suite":
|
||||||
|
test "Simple RPC call":
|
||||||
|
check waitFor(simpleTest("localhost", Port(8545))) == true
|
||||||
|
test "Continuous RPC calls (" & $TestsCount & " messages)":
|
||||||
|
check waitFor(continuousTest("localhost", Port(8545))) == TestsCount
|
||||||
|
test "Invalid RPC calls":
|
||||||
|
check waitFor(invalidTest("localhost", Port(8545))) == true
|
||||||
|
|
||||||
|
waitFor secureHttpSrv.stop()
|
||||||
|
waitFor secureHttpSrv.closeWait()
|
Loading…
x
Reference in New Issue
Block a user