Skip to content

Commit d68a3d3

Browse files
committed
chunked encoding test
1 parent e22fb37 commit d68a3d3

3 files changed

Lines changed: 84 additions & 10 deletions

File tree

json_rpc/clients/httpclient.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ method send(
6161

6262
let
6363
req = HttpClientRequestRef.post(
64-
client.httpSession, client.httpAddress, headers = headers, body = default(array[0, byte])
64+
client.httpSession, client.httpAddress, headers = headers
6565
)
6666

6767
res =
@@ -106,7 +106,7 @@ method request(
106106

107107
let
108108
req = HttpClientRequestRef.post(
109-
client.httpSession, client.httpAddress, headers = headers, body = default(array[0, byte])
109+
client.httpSession, client.httpAddress, headers = headers
110110
)
111111

112112
res =

json_rpc/router.nim

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ export chronos, jsonmarshal, json, jrpc_sys
2222
logScope:
2323
topics = "jsonrpc router"
2424

25+
template errorReturnWorkaround(body) =
26+
when NimMajor < 2:
27+
body
28+
raiseAssert "never hit"
29+
else:
30+
body
31+
2532
type
2633
RpcProc* = proc(params: RequestParamsRx): Future[JsonString] {.async.}
2734
## Procedure signature accepted as an RPC call by server - if the function
@@ -210,8 +217,8 @@ macro rpc*(server: RpcRouter, formatType, procList: untyped): untyped =
210217
of nnkIdent, nnkAccQuoted:
211218
$prc[0]
212219
else:
213-
error "Unsupported rpc proc definition", prc
214-
"" # Nim 1.6
220+
errorReturnWorkaround:
221+
error "Unsupported rpc proc definition", prc
215222
result.add rpcImpl(server, path, formatType, prc)
216223
else:
217224
error "Only proc definitions are allowed within an rpc context", prc

tests/testhttp.nim

Lines changed: 73 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88
# those terms.
99

1010
import
11-
unittest2, chronos/unittest2/asynctests,
11+
unittest2,
12+
chronos/unittest2/asynctests,
1213
../json_rpc/[rpcserver, rpcclient, jsonmarshal],
13-
./private/helpers
14+
./private/helpers,
15+
stew/byteutils
1416

1517
const TestsCount = 100
1618
const bigChunkSize = 4 * 8192
@@ -19,14 +21,14 @@ suite "JSON-RPC/http":
1921
setup:
2022
var httpsrv = newRpcHttpServer(["127.0.0.1:0"])
2123
# Create RPC on server
22-
httpsrv.rpc("myProc") do(input: string, data: array[0..3, int]):
24+
httpsrv.rpc("myProc") do(input: string, data: array[0 .. 3, int]):
2325
result = %("Hello " & input & " data: " & $data)
2426
httpsrv.rpc("noParamsProc") do():
2527
result = %("Hello world")
2628

2729
httpsrv.rpc("bigchunkMethod") do() -> seq[byte]:
2830
result = newSeq[byte](bigChunkSize)
29-
for i in 0..<result.len:
31+
for i in 0 ..< result.len:
3032
result[i] = byte(i mod 255)
3133

3234
httpsrv.setMaxChunkSize(8192)
@@ -47,10 +49,11 @@ suite "JSON-RPC/http":
4749

4850
asyncTest "Continuous RPC calls (" & $TestsCount & " messages)":
4951
var client = newRpcHttpClient()
50-
for i in 0..<TestsCount:
52+
for i in 0 ..< TestsCount:
5153
await client.connect("http://" & serverAddress)
5254
var r = await client.call("myProc", %[%"abc", %[1, 2, 3, i]])
53-
check: r.string == "\"Hello abc data: [1, 2, 3, " & $i & "]\""
55+
check:
56+
r.string == "\"Hello abc data: [1, 2, 3, " & $i & "]\""
5457
await client.close()
5558

5659
asyncTest "Invalid RPC calls":
@@ -88,3 +91,67 @@ suite "JSON-RPC/http":
8891

8992
check:
9093
notif
94+
95+
asyncTest "Chunked encoding":
96+
# The server doesn't use chunked encoding but the client might encounter
97+
# such servers - test that it works as expected
98+
let host = "127.0.0.1"
99+
var server = createStreamServer(initTAddress(host & ":0"), {ReuseAddr})
100+
defer:
101+
await server.closeWait()
102+
103+
let port = server.localAddress().port
104+
105+
proc simpleServerResponder() {.async.} =
106+
try:
107+
let conn = await server.accept()
108+
109+
# 1. Consume the incoming headers but ignore the body hoping it's small
110+
# enough
111+
var headerBuf: seq[byte] = newSeq[byte](8192)
112+
discard
113+
await conn.readUntil(addr headerBuf[0], headerBuf.len, toBytes("\r\n\r\n"))
114+
115+
# 2. Send HTTP response with Chunked Transfer Encoding
116+
var respHeader = (
117+
"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n" &
118+
"Transfer-Encoding: chunked\r\n\r\n"
119+
).toBytes()
120+
discard await conn.write(respHeader)
121+
122+
# JSON Response broken into two chunks
123+
let fullMsg = """{"jsonrpc":"2.0","result":{"s":"part1"},"id":1}"""
124+
let mid = fullMsg.len div 2
125+
let part1 = fullMsg[0 ..< mid]
126+
let part2 = fullMsg[mid ..^ 1]
127+
128+
# Chunk 1: size in hex + \r\n
129+
discard await conn.write(
130+
(toHex(part1.len).strip(trailing = false, chars = {'0'}) & "\r\n").toBytes()
131+
)
132+
discard await conn.write(part1.toBytes())
133+
134+
# Chunk 2: size in hex + \r\n
135+
discard await conn.write(
136+
("\r\n" & toHex(part2.len).strip(trailing = false, chars = {'0'}) & "\r\n").toBytes()
137+
)
138+
discard await conn.write(part2.toBytes())
139+
140+
# Final empty chunk to end the stream (0\r\n\r\n)
141+
discard await conn.write("\r\n0\r\n\r\n".toBytes())
142+
await conn.closeWait()
143+
except CatchableError as exc:
144+
raiseAssert exc.msg
145+
146+
var st = simpleServerResponder()
147+
148+
var client = newRpcHttpClient()
149+
150+
try:
151+
await client.connect(host, port, false)
152+
153+
let res = await client.call("anyMethod", %*{"param": "value"})
154+
check res == JsonString """{"s":"part1"}"""
155+
finally:
156+
await client.close()
157+
await st.cancelAndWait()

0 commit comments

Comments
 (0)