Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option to allow the request body to be processed outside the asynchttpserver library. #13147

Merged
merged 12 commits into from
Feb 4, 2020
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 48 additions & 16 deletions lib/pure/asynchttpserver.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down