Option to allow the request body to be processed outside the asynchttpserver library. (#13147)
Allow the request body to be processed outside the asynchttpserver library to break big files into chunks of data. This change does not break anything.
This commit is contained in:
parent
bfe96e069b
commit
955465e5f4
2 changed files with 49 additions and 16 deletions
|
|
@ -57,6 +57,7 @@
|
||||||
|
|
||||||
## Library changes
|
## Library changes
|
||||||
|
|
||||||
|
- `asynchttpserver` now the request body is a FutureStream.
|
||||||
- `asyncdispatch.drain` now properly takes into account `selector.hasPendingOperations`
|
- `asyncdispatch.drain` now properly takes into account `selector.hasPendingOperations`
|
||||||
and only returns once all pending async operations are guaranteed to have completed.
|
and only returns once all pending async operations are guaranteed to have completed.
|
||||||
- `asyncdispatch.drain` now consistently uses the passed timeout value for all
|
- `asyncdispatch.drain` now consistently uses the passed timeout value for all
|
||||||
|
|
|
||||||
|
|
@ -35,24 +35,41 @@ import httpcore
|
||||||
|
|
||||||
export httpcore except parseHeader
|
export httpcore except parseHeader
|
||||||
|
|
||||||
const
|
|
||||||
maxLine = 8*1024
|
|
||||||
|
|
||||||
# TODO: If it turns out that the decisions that asynchttpserver makes
|
# TODO: If it turns out that the decisions that asynchttpserver makes
|
||||||
# explicitly, about whether to close the client sockets or upgrade them are
|
# 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.
|
# 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
|
# Also, maybe move `client` out of `Request` object and into the args for
|
||||||
# the proc.
|
# 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
|
AsyncHttpServer* = ref object
|
||||||
socket: AsyncSocket
|
socket: AsyncSocket
|
||||||
reuseAddr: bool
|
reuseAddr: bool
|
||||||
|
|
@ -149,6 +166,8 @@ proc processRequest(
|
||||||
request.hostname.shallowCopy(address)
|
request.hostname.shallowCopy(address)
|
||||||
assert client != nil
|
assert client != nil
|
||||||
request.client = client
|
request.client = client
|
||||||
|
when (NimMajor, NimMinor) >= (1, 1):
|
||||||
|
request.bodyStream = newFutureStream[string]()
|
||||||
|
|
||||||
# We should skip at least one empty line before the request
|
# We should skip at least one empty line before the request
|
||||||
# https://tools.ietf.org/html/rfc7230#section-3.5
|
# https://tools.ietf.org/html/rfc7230#section-3.5
|
||||||
|
|
@ -243,10 +262,23 @@ proc processRequest(
|
||||||
if contentLength > server.maxBody:
|
if contentLength > server.maxBody:
|
||||||
await request.respondError(Http413)
|
await request.respondError(Http413)
|
||||||
return false
|
return false
|
||||||
request.body = await client.recv(contentLength)
|
|
||||||
if request.body.len != contentLength:
|
when (NimMajor, NimMinor) >= (1, 1):
|
||||||
await request.respond(Http400, "Bad Request. Content-Length does not match actual.")
|
var remainder = contentLength
|
||||||
return true
|
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:
|
elif request.reqMethod == HttpPost:
|
||||||
await request.respond(Http411, "Content-Length required.")
|
await request.respond(Http411, "Content-Length required.")
|
||||||
return true
|
return true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue