I needed help to create a template and/or macro that would allow to read small chunks of data from a http body stream in an elegant manner. My idea can be seen in the following code:
### Replace this code block
### Begin
let chunkSize = 8*1024
var remainder = req.contentLength
while true:
let data = await req.client.recv(min(remainder, chunkSize))
echo data
remainder -= len(data)
if remainder == 0:
break
### End
### With this small piece of code
### Begin
for data in req.bodyStream(chunkSize):
echo data
### End
All code:
import asynchttpserver, asyncdispatch
import asyncnet
import strutils, strformat
const stream = true # for test purposes switch from true to false
proc htmlpage(contentLength, bodyLength: int): string =
return &"""
<!Doctype html>
<html lang="en">
<head><meta charset="utf-8"/></head>
<body>
<form action="/" method="post" enctype="multipart/form-data">
File: <input type="file" name="testfile" accept="text/*"><br />
<input style="margin:10px 0;" type="submit">
</form><br />
Expected Body Length: {contentLength} bytes<br />
Actual Body Length: {bodyLength} bytes
</body>
</html>
"""
proc cb(req: Request) {.async.} =
var
contentLength = 0
bodyLength = 0
if req.reqMethod == HttpPost:
contentLength = req.headers["Content-length"].parseInt
if stream:
# Read 8*1024 bytes at a time
# optional chunkSize parameter. The default is 8*1024
let chunkSize = 8*1024
var remainder = req.contentLength
while true:
let data = await req.client.recv(min(remainder, chunkSize))
echo data
bodyLength += len(data)
remainder -= len(data)
if remainder == 0:
break
else:
bodyLength += req.body.len
await req.respond(Http200, htmlpage(contentLength, bodyLength))
let server = newAsyncHttpServer(maxBody = 10485760, stream = stream)
waitFor server.serve(Port(8080), cb)
Async iterators are an open issue on the Nim github, so your concern is normal. Here is how you would do it in a template:
template forStream(req: Request, chunkSize: int, body: untyped): untyped {.dirty.} = # dirty means all the variable names in the template are usable in the block
var remainder = req.contentLength
while true:
let data = await req.client.recv(min(remainder, chunkSize))
body
remainder -= len(data)
if remainder == 0:
break
proc cb(req: Request) {.async.} =
var
contentLength = 0
bodyLength = 0
if req.reqMethod == HttpPost:
contentLength = req.headers["Content-length"].parseInt
if stream:
req.forStream(8*1024):
echo data
bodyLength += len(data)
else:
bodyLength += req.body.len
await req.respond(Http200, htmlpage(contentLength, bodyLength))
If you want the variable name to be customizable and not just data, you can use identifier construction in templates:
template forStream(req: Request, chunkSize: int, name, body: untyped): untyped =
var remainder = req.contentLength
while true:
let `name` = await req.client.recv(min(remainder, chunkSize))
body
remainder -= len(`name`)
if remainder == 0:
break
req.forStream(8*1024, d):
echo d
Thanks @Hlaaftana but, unfortunately it doesn't work!
/home/hdias/dev/tmp/test.nim(32, 25) template/generic instantiation of `async` from here
/home/hdias/dev/test.nim(43, 10) template/generic instantiation of `forStream` from here
/home/hdias/dev/tmp/test.nim(26, 24) template/generic instantiation of `await` from here
/usr/local/programs/x86_64/nim-1.0.6/lib/pure/asyncmacro.nim(448, 10) Error: Await only available within .async
I don't know if 'async' and the 'await' works inside templates!