Chronos HTTP(S) Server (#111)
This commit is contained in:
parent
9db5407e81
commit
3599a73b58
|
@ -7,6 +7,7 @@ skipDirs = @["tests"]
|
|||
|
||||
### Dependencies
|
||||
requires "nim >= 1.2.0",
|
||||
"stew",
|
||||
"nimcrypto",
|
||||
"stint",
|
||||
"chronos",
|
||||
|
|
|
@ -23,14 +23,21 @@ type
|
|||
const
|
||||
MaxHttpRequestSize = 128 * 1024 * 1024 # maximum size of HTTP body in octets
|
||||
|
||||
proc new(T: type RpcHttpClient, maxBodySize = MaxHttpRequestSize): T =
|
||||
T(
|
||||
maxBodySize: maxBodySize,
|
||||
httpSession: HttpSessionRef.new(),
|
||||
)
|
||||
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(
|
||||
maxBodySize: maxBodySize,
|
||||
httpSession: HttpSessionRef.new(),
|
||||
)
|
||||
|
||||
proc newRpcHttpClient*(maxBodySize = MaxHttpRequestSize): RpcHttpClient =
|
||||
RpcHttpClient.new(maxBodySize)
|
||||
proc newRpcHttpClient*(maxBodySize = MaxHttpRequestSize, secure = false): RpcHttpClient =
|
||||
RpcHttpClient.new(maxBodySize, secure)
|
||||
|
||||
method call*(client: RpcHttpClient, name: string,
|
||||
params: JsonNode): Future[Response]
|
||||
|
@ -45,16 +52,27 @@ method call*(client: RpcHttpClient, name: string,
|
|||
req = HttpClientRequestRef.post(client.httpSession,
|
||||
client.httpAddress.get,
|
||||
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",
|
||||
address = client.httpAddress, msg_len = len(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
|
||||
echo "response ", resText
|
||||
|
||||
# completed by processMessage - the flow is quite weird here to accomodate
|
||||
# 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:
|
||||
raise newException(RpcAddressUnresolvableError, client.httpAddress.error)
|
||||
|
||||
proc connect*(client: RpcHttpClient, address: string, port: Port) {.async.} =
|
||||
let addresses = resolveTAddress(address, port)
|
||||
if addresses.len == 0:
|
||||
raise newException(RpcAddressUnresolvableError, "Failed to resolve address: " & address)
|
||||
ok client.httpAddress, getAddress(addresses[0])
|
||||
proc connect*(client: RpcHttpClient, address: string, port: Port, secure=false) {.async.} =
|
||||
var uri = initUri()
|
||||
if secure:
|
||||
uri.scheme = "https"
|
||||
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
|
||||
## 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
|
||||
RpcAddressUnresolvableError* = object of JsonRpcError
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import server
|
||||
import servers/[socketserver, shttpserver]
|
||||
export server, socketserver, shttpserver
|
|
@ -1,6 +1,8 @@
|
|||
import
|
||||
stew/byteutils,
|
||||
std/[strutils],
|
||||
chronicles, httputils, chronos,
|
||||
chronos/apps/http/httpserver,
|
||||
".."/[errors, server]
|
||||
|
||||
export server
|
||||
|
@ -8,226 +10,57 @@ export server
|
|||
logScope:
|
||||
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
|
||||
ReqStatus = enum
|
||||
Success, Error, ErrorFailure
|
||||
|
||||
RpcHttpServer* = ref object of RpcServer
|
||||
servers: seq[StreamServer]
|
||||
httpServers: seq[HttpServerRef]
|
||||
|
||||
proc sendAnswer(transp: StreamTransport, version: HttpVersion, code: HttpCode,
|
||||
data: string = ""): Future[bool] {.async.} =
|
||||
var answer = $version
|
||||
answer.add(" ")
|
||||
answer.add($code)
|
||||
answer.add("\r\n")
|
||||
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 addHttpServer*(rpcServer: RpcHttpServer, address: TransportAddress) =
|
||||
proc processClientRpc(rpcServer: RpcHttpServer): HttpProcessCallback {.closure.} =
|
||||
return proc (req: RequestFence): Future[HttpResponseRef] {.async.} =
|
||||
if req.isOk():
|
||||
let request = req.get()
|
||||
let body = await request.getBody()
|
||||
|
||||
proc validateRequest(transp: StreamTransport,
|
||||
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
|
||||
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:
|
||||
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
|
||||
if future.failed:
|
||||
# rpc.route exception
|
||||
debug "Internal error, while processing RPC call",
|
||||
address = transp.remoteAddress()
|
||||
let res = await transp.sendAnswer(header.version, Http503)
|
||||
if not res:
|
||||
await transp.closeWait()
|
||||
break
|
||||
var data = future.read()
|
||||
let res = await request.respond(Http200, data)
|
||||
trace "JSON-RPC result has been sent"
|
||||
return res
|
||||
else:
|
||||
var data = future.read()
|
||||
let res = await transp.sendAnswer(header.version, Http200, data)
|
||||
trace "RPC result has been sent", address = $transp.remoteAddress()
|
||||
if not res:
|
||||
await transp.closeWait()
|
||||
break
|
||||
elif vres == ErrorFailure:
|
||||
debug "Remote peer disconnected", address = transp.remoteAddress()
|
||||
await transp.closeWait()
|
||||
break
|
||||
return dumbResponse()
|
||||
|
||||
if header.version in {HttpVersion09, HttpVersion10}:
|
||||
debug "Disconnecting client", address = transp.remoteAddress()
|
||||
await transp.closeWait()
|
||||
break
|
||||
else:
|
||||
if connection == "close":
|
||||
debug "Disconnecting client", address = transp.remoteAddress()
|
||||
await transp.closeWait()
|
||||
break
|
||||
|
||||
debug "Finished connection", address = $transp.remoteAddress()
|
||||
|
||||
# Utility functions for setting up servers using stream transport addresses
|
||||
|
||||
proc addStreamServer*(server: RpcHttpServer, address: TransportAddress) =
|
||||
let initialServerCount = len(rpcServer.httpServers)
|
||||
try:
|
||||
info "Starting JSON-RPC HTTP server", url = "http://" & $address
|
||||
var transServer = createStreamServer(address, processClient,
|
||||
{ReuseAddr}, udata = server)
|
||||
server.servers.add(transServer)
|
||||
var res = HttpServerRef.new(address, processClientRpc(rpcServer))
|
||||
if res.isOk():
|
||||
let httpServer = res.get()
|
||||
rpcServer.httpServers.add(httpServer)
|
||||
else:
|
||||
raise newException(RpcBindError, "Unable to create server!")
|
||||
|
||||
except CatchableError as exc:
|
||||
error "Failed to create server", address = $address,
|
||||
message = exc.msg
|
||||
|
||||
if len(server.servers) == 0:
|
||||
if len(rpcServer.httpServers) != initialServerCount + 1:
|
||||
# Server was not bound, critical error.
|
||||
raise newException(RpcBindError, "Unable to create server!")
|
||||
|
||||
proc addStreamServers*(server: RpcHttpServer,
|
||||
proc addHttpServers*(server: RpcHttpServer,
|
||||
addresses: openArray[TransportAddress]) =
|
||||
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``.
|
||||
var
|
||||
tas4: seq[TransportAddress]
|
||||
|
@ -247,21 +80,22 @@ proc addStreamServer*(server: RpcHttpServer, address: string) =
|
|||
discard
|
||||
|
||||
for r in tas4:
|
||||
server.addStreamServer(r)
|
||||
added.inc
|
||||
for r in tas6:
|
||||
server.addStreamServer(r)
|
||||
server.addHttpServer(r)
|
||||
added.inc
|
||||
if added == 0: # avoid ipv4 + ipv6 running together
|
||||
for r in tas6:
|
||||
server.addHttpServer(r)
|
||||
added.inc
|
||||
|
||||
if added == 0:
|
||||
# Addresses could not be resolved, critical error.
|
||||
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:
|
||||
server.addStreamServer(address)
|
||||
server.addHttpServer(address)
|
||||
|
||||
proc addStreamServer*(server: RpcHttpServer, address: string, port: Port) =
|
||||
proc addHttpServer*(server: RpcHttpServer, address: string, port: Port) =
|
||||
var
|
||||
tas4: seq[TransportAddress]
|
||||
tas6: seq[TransportAddress]
|
||||
|
@ -285,19 +119,19 @@ proc addStreamServer*(server: RpcHttpServer, address: string, port: Port) =
|
|||
"Address " & address & " could not be resolved!")
|
||||
|
||||
for r in tas4:
|
||||
server.addStreamServer(r)
|
||||
server.addHttpServer(r)
|
||||
added.inc
|
||||
for r in tas6:
|
||||
server.addStreamServer(r)
|
||||
server.addHttpServer(r)
|
||||
added.inc
|
||||
|
||||
if len(server.servers) == 0:
|
||||
if len(server.httpServers) == 0:
|
||||
# Server was not bound, critical error.
|
||||
raise newException(RpcBindError,
|
||||
"Could not setup server on " & address & ":" & $int(port))
|
||||
|
||||
proc new*(T: type RpcHttpServer): T =
|
||||
T(router: RpcRouter.init(), servers: @[])
|
||||
T(router: RpcRouter.init(), httpServers: @[])
|
||||
|
||||
proc new*(T: type RpcHttpServer, router: RpcRouter): T =
|
||||
T(router: router, servers: @[])
|
||||
|
@ -311,12 +145,12 @@ proc newRpcHttpServer*(router: RpcRouter): RpcHttpServer =
|
|||
proc newRpcHttpServer*(addresses: openArray[TransportAddress]): RpcHttpServer =
|
||||
## Create new server and assign it to addresses ``addresses``.
|
||||
result = newRpcHttpServer()
|
||||
result.addStreamServers(addresses)
|
||||
result.addHttpServers(addresses)
|
||||
|
||||
proc newRpcHttpServer*(addresses: openArray[string]): RpcHttpServer =
|
||||
## Create new server and assign it to addresses ``addresses``.
|
||||
result = newRpcHttpServer()
|
||||
result.addStreamServers(addresses)
|
||||
result.addHttpServers(addresses)
|
||||
|
||||
proc newRpcHttpServer*(addresses: openArray[string], router: RpcRouter): RpcHttpServer =
|
||||
## Create new server and assign it to addresses ``addresses``.
|
||||
|
@ -330,22 +164,17 @@ proc newRpcHttpServer*(addresses: openArray[TransportAddress], router: RpcRouter
|
|||
|
||||
proc start*(server: RpcHttpServer) =
|
||||
## Start the RPC server.
|
||||
for item in server.servers:
|
||||
debug "HTTP RPC server started", address = item.local
|
||||
for item in server.httpServers:
|
||||
debug "HTTP RPC server started" # (todo: fix this), address = item
|
||||
item.start()
|
||||
|
||||
proc stop*(server: RpcHttpServer) =
|
||||
proc stop*(server: RpcHttpServer) {.async.} =
|
||||
## Stop the RPC server.
|
||||
for item in server.servers:
|
||||
debug "HTTP RPC server stopped", address = item.local
|
||||
item.stop()
|
||||
|
||||
proc close*(server: RpcHttpServer) =
|
||||
## Cleanup resources of RPC server.
|
||||
for item in server.servers:
|
||||
item.close()
|
||||
for item in server.httpServers:
|
||||
debug "HTTP RPC server stopped" # (todo: fix this), address = item.local
|
||||
await item.stop()
|
||||
|
||||
proc closeWait*(server: RpcHttpServer) {.async.} =
|
||||
## Cleanup resources of RPC server.
|
||||
for item in server.servers:
|
||||
for item in server.httpServers:
|
||||
await item.closeWait()
|
||||
|
|
|
@ -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 httputils, chronicles
|
||||
import httputils
|
||||
import ../json_rpc/[rpcserver, rpcclient]
|
||||
|
||||
const
|
||||
TestsCount = 100
|
||||
BufferSize = 8192
|
||||
BigHeaderSize = 8 * 1024 + 1
|
||||
BigBodySize = 128 * 1024 + 1
|
||||
HeadersMark = @[byte(0x0D), byte(0x0A), byte(0x0D), byte(0x0A)]
|
||||
const TestsCount = 100
|
||||
|
||||
Requests = [
|
||||
"GET / 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}",
|
||||
"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 simpleTest(address: string, port: Port): Future[bool] {.async.} =
|
||||
var client = newRpcHttpClient()
|
||||
await client.connect(address, port)
|
||||
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()
|
||||
|
@ -82,76 +21,22 @@ proc continuousTest(address: string, port: Port): Future[int] {.async.} =
|
|||
result += 1
|
||||
await client.close()
|
||||
|
||||
proc customMessage(address: TransportAddress,
|
||||
data: string,
|
||||
expect: int): Future[bool] {.async.} =
|
||||
var buffer = newSeq[byte](BufferSize)
|
||||
var header: HttpResponseHeader
|
||||
var transp = await connect(address)
|
||||
defer: transp.close()
|
||||
|
||||
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())
|
||||
return header.code == expect
|
||||
|
||||
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)
|
||||
proc invalidTest(address: string, port: Port): Future[bool] {.async.} =
|
||||
var client = newRpcHttpClient()
|
||||
await client.connect(address, port)
|
||||
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
|
||||
|
||||
var httpsrv = newRpcHttpServer(["localhost:8545"])
|
||||
|
||||
|
@ -163,33 +48,13 @@ httpsrv.rpc("noParamsProc") do():
|
|||
|
||||
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)":
|
||||
check waitFor(continuousTest("localhost", Port(8545))) == TestsCount
|
||||
test "Wrong [Content-Type] test":
|
||||
check waitFor(simpleTest("localhost", Port(8545), 0, 415)) == 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
|
||||
test "Invalid RPC calls":
|
||||
check waitFor(invalidTest("localhost", Port(8545))) == true
|
||||
|
||||
httpsrv.stop()
|
||||
waitFor httpsrv.stop()
|
||||
waitFor httpsrv.closeWait()
|
||||
|
|
|
@ -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…
Reference in New Issue