diff options
author | Henrique Dias <mrhdias@gmail.com> | 2020-02-14 20:59:37 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-14 21:59:37 +0100 |
commit | 5bf571f061d53d35aab727f420afd9f415987723 (patch) | |
tree | 1d8592608993b094ba1a087f776d27d80e624dad /lib/pure | |
parent | 3300e317a419d0f8f5e6dfcf822702a32738eb27 (diff) | |
download | Nim-5bf571f061d53d35aab727f420afd9f415987723.tar.gz |
Fix to asynchttpserver form data/body broken with #13147 (#13394)
* Fix to asynchttpserver form data/body broken with #13147 * New implementation that use a interator instance of future streams * asynchttpserver now can handle chunks of data.
Diffstat (limited to 'lib/pure')
-rw-r--r-- | lib/pure/asynchttpserver.nim | 116 |
1 files changed, 73 insertions, 43 deletions
diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 5292ac75a..9aed0255b 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -41,13 +41,13 @@ ## import asynchttpserver, asyncdispatch ## 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> +## <head><meta charset="utf-8"/></head> ## <body> ## <form action="/" method="post" enctype="multipart/form-data"> ## File: <input type="file" name="testfile" accept="text/*"><br /> @@ -65,17 +65,23 @@ ## bodyLength = 0 ## if req.reqMethod == HttpPost: ## contentLength = req.headers["Content-length"].parseInt -## if contentLength < 8*1024: # the default chunkSize -## # read the request body at once -## let body = await req.bodyStream.readAll(); -## bodyLength = body.len +## if stream: +## # Read 8*1024 bytes at a time +## # optional chunkSize parameter. The default is 8*1024 +## for length, data in req.bodyStream(8*1024): +## let content = await data +## if length == content.len: +## bodyLength += content.len +## else: +## # Handle exception +## await req.respond(Http400, +## "Bad Request. Data read has a different length than the expected.") +## return ## else: -## # read 8*1024 bytes at a time -## while (let data = await req.bodyStream.read(); data[0]): -## bodyLength += data[1].len +## bodyLength += req.body.len ## await req.respond(Http200, htmlpage(contentLength, bodyLength)) ## -## let server = newAsyncHttpServer(maxBody = 10485760) # 10 MB +## let server = newAsyncHttpServer(maxBody = 10485760, stream = stream) # 10 MB ## waitFor server.serve(Port(8080), cb) import tables, asyncnet, asyncdispatch, parseutils, uri, strutils @@ -93,9 +99,6 @@ const maxLine = 8*1024 when (NimMajor, NimMinor) >= (1, 1): - const - chunkSize = 8*1024 ## This seems perfectly reasonable for default chunkSize. - type Request* = object client*: AsyncSocket # TODO: Separate this into a Response object? @@ -104,8 +107,16 @@ when (NimMajor, NimMinor) >= (1, 1): protocol*: tuple[orig: string, major, minor: int] url*: Uri hostname*: string ## The hostname of the client that made the request. - body*: string # For future removal - bodyStream*: FutureStream[string] + body*: string + contentLength*: int + + type + AsyncHttpServer* = ref object + socket: AsyncSocket + reuseAddr: bool + reusePort: bool + maxBody: int ## The maximum content-length that will be read for the body. + stream: bool ## By default (stream = false), the body of the request is read immediately else: type Request* = object @@ -117,20 +128,30 @@ else: hostname*: string ## The hostname of the client that made the request. body*: string -type - AsyncHttpServer* = ref object - socket: AsyncSocket - reuseAddr: bool - reusePort: bool - maxBody: int ## The maximum content-length that will be read for the body. + type + AsyncHttpServer* = ref object + socket: AsyncSocket + reuseAddr: bool + reusePort: bool + maxBody: int ## The maximum content-length that will be read for the body. -proc newAsyncHttpServer*(reuseAddr = true, reusePort = false, +when (NimMajor, NimMinor) >= (1, 1): + proc newAsyncHttpServer*(reuseAddr = true, reusePort = false, + maxBody = 8388608, stream = false): AsyncHttpServer = + ## Creates a new ``AsyncHttpServer`` instance. + new result + result.reuseAddr = reuseAddr + result.reusePort = reusePort + result.maxBody = maxBody + result.stream = stream +else: + proc newAsyncHttpServer*(reuseAddr = true, reusePort = false, maxBody = 8388608): AsyncHttpServer = - ## Creates a new ``AsyncHttpServer`` instance. - new result - result.reuseAddr = reuseAddr - result.reusePort = reusePort - result.maxBody = maxBody + ## Creates a new ``AsyncHttpServer`` instance. + new result + result.reuseAddr = reuseAddr + result.reusePort = reusePort + result.maxBody = maxBody proc addHeaders(msg: var string, headers: HttpHeaders) = for k, v in headers: @@ -193,13 +214,30 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] = proc sendStatus(client: AsyncSocket, status: string): Future[void] = client.send("HTTP/1.1 " & status & "\c\L\c\L") +when (NimMajor, NimMinor) >= (1, 1): + iterator bodyStream*( + request: Request, + chunkSize: int = 8*1024): (int, Future[string]) = + ## The chunkSize parameter is optional and default value is 8*1024 bytes. + ## + ## This iterator return a tuple with the length of the data that was read + ## and a future. + var remainder = request.contentLength + while remainder > 0: + let readSize = min(remainder, chunkSize) + let data = request.client.recv(readSize) + if data.failed: + raise newException(ValueError, "Error reading POST data from client.") + yield (readSize, data) + remainder -= readSize + proc processRequest( server: AsyncHttpServer, req: FutureVar[Request], client: AsyncSocket, address: string, lineFut: FutureVar[string], - callback: proc (request: Request): Future[void] {.closure, gcsafe.}, + callback: proc (request: Request): Future[void] {.closure, gcsafe.} ): Future[bool] {.async.} = # Alias `request` to `req.mget()` so we don't have to write `mget` everywhere. @@ -213,13 +251,9 @@ proc processRequest( request.hostname.shallowCopy(address) assert client != nil request.client = client + request.body = "" when (NimMajor, NimMinor) >= (1, 1): - request.bodyStream = newFutureStream[string]() - # To uncomment in the future after compatibility issues - # with third parties are solved - # else: - # request.body = "" - request.body = "" # Temporary fix for future removal + request.contentLength = 0 # We should skip at least one empty line before the request # https://tools.ietf.org/html/rfc7230#section-3.5 @@ -316,16 +350,12 @@ proc processRequest( return false when (NimMajor, NimMinor) >= (1, 1): - var remainder = contentLength - while remainder > 0: - let readSize = min(remainder, chunkSize) - let data = await client.recv(read_size) - if data.len != read_size: + request.contentLength = contentLength + if not server.stream: + request.body = await client.recv(contentLength) + if request.body.len != contentLength: await request.respond(Http400, "Bad Request. Content-Length does not match actual.") return true - await request.bodyStream.write(data) - remainder -= data.len - request.bodyStream.complete() else: request.body = await client.recv(contentLength) if request.body.len != contentLength: |