diff --git a/changelog.md b/changelog.md index 17eedb3a104d..593a61354672 100644 --- a/changelog.md +++ b/changelog.md @@ -53,6 +53,7 @@ ## Library changes +- `asynchttpserver` now the request body is a FutureStream. - `asyncdispatch.drain` now properly takes into account `selector.hasPendingOperations` and only returns once all pending async operations are guaranteed to have completed. - `asyncdispatch.drain` now consistently uses the passed timeout value for all diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index 186f0da41465..2754b128ba29 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -35,24 +35,41 @@ import httpcore export httpcore except parseHeader -const - maxLine = 8*1024 - # TODO: If it turns out that the decisions that asynchttpserver makes # explicitly, about whether to close the client sockets or upgrade them are # wrong, then add a return value which determines what to do for the callback. # Also, maybe move `client` out of `Request` object and into the args for # the proc. -type - Request* = object - client*: AsyncSocket # TODO: Separate this into a Response object? - reqMethod*: HttpMethod - headers*: HttpHeaders - protocol*: tuple[orig: string, major, minor: int] - url*: Uri - hostname*: string ## The hostname of the client that made the request. - body*: string +const + maxLine = 8*1024 + +when (NimMajor, NimMinor) >= (1, 1): + const + chunkSize = 8*1048 ## This seems perfectly reasonable for default chunkSize. + + type + Request* = object + client*: AsyncSocket # TODO: Separate this into a Response object? + reqMethod*: HttpMethod + headers*: HttpHeaders + protocol*: tuple[orig: string, major, minor: int] + url*: Uri + hostname*: string ## The hostname of the client that made the request. + body*: string + bodyStream*: FutureStream[string] +else: + type + Request* = object + client*: AsyncSocket # TODO: Separate this into a Response object? + reqMethod*: HttpMethod + headers*: HttpHeaders + protocol*: tuple[orig: string, major, minor: int] + url*: Uri + hostname*: string ## The hostname of the client that made the request. + body*: string + +type AsyncHttpServer* = ref object socket: AsyncSocket reuseAddr: bool @@ -149,6 +166,8 @@ proc processRequest( request.hostname.shallowCopy(address) assert client != nil request.client = client + when (NimMajor, NimMinor) >= (1, 1): + request.bodyStream = newFutureStream[string]() # We should skip at least one empty line before the request # https://tools.ietf.org/html/rfc7230#section-3.5 @@ -243,10 +262,23 @@ proc processRequest( if contentLength > server.maxBody: await request.respondError(Http413) return false - 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 + + 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: + 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: + await request.respond(Http400, "Bad Request. Content-Length does not match actual.") + return true elif request.reqMethod == HttpPost: await request.respond(Http411, "Content-Length required.") return true