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
39 changes: 37 additions & 2 deletions lib/pure/asynchttpserver.nim
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
##
## waitFor server.serve(Port(8080), cb)

include "system/inclrtl"

import tables, asyncnet, asyncdispatch, parseutils, uri, strutils
import httpcore

export httpcore except parseHeader

const
maxLine = 8*1024
chunkSize = 1048

# TODO: If it turns out that the decisions that asynchttpserver makes
# explicitly, about whether to close the client sockets or upgrade them are
Expand All @@ -52,6 +55,7 @@ type
url*: Uri
hostname*: string ## The hostname of the client that made the request.
body*: string
bodyStream*: FutureStream[string]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when (NimMajor, NimMinor) >= (1, 1):
  bodyStream*: FutureStream[string]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when (NimMajor, NimMinor) >= (1, 1):
  bodyStream*: FutureStream[string]

When I put the annotation I get this error:
Error: constant expression expected

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
    when (NimMajor, NimMinor) >= (1, 1):
      bodyStream*: FutureStream[string]

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bah, what's up with that...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works if outside of type declaration

when (NimMajor, NimMinor) >= (1, 1):
  const
    maxLine = 8*1024
    chunkSize = 1048

  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:
  const
    maxLine = 8*1024

  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
    reusePort: bool
    maxBody: int ## The maximum content-length that will be read for the body.


AsyncHttpServer* = ref object
socket: AsyncSocket
Expand Down Expand Up @@ -128,6 +132,31 @@ 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")

proc readBody(
req: FutureVar[Request],
contentLength: int
): Future[bool] {.since: (1, 1), async.} =

var remainder = contentLength
while remainder > 0:
let readSize = min(remainder, chunkSize)
let data = await req.mget().client.recv(read_size)
if data.len != read_size:
return true
await req.mget().bodyStream.write(data)
remainder -= data.len

req.mget().bodyStream.complete()
return false

proc readBody(
req: FutureVar[Request],
contentLength: int
): Future[bool] {.async.} =

req.mget().body = await req.mget().client.recv(contentLength)
return if req.mget().body.len == contentLength: false else: true

proc processRequest(
server: AsyncHttpServer,
req: FutureVar[Request],
Expand All @@ -149,6 +178,7 @@ proc processRequest(
request.hostname.shallowCopy(address)
assert client != nil
request.client = client
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 +273,15 @@ proc processRequest(
if contentLength > server.maxBody:
await request.respondError(Http413)
return false
request.body = await client.recv(contentLength)
if request.body.len != contentLength:
if await req.readBody(contentLength):
await request.respond(Http400, "Bad Request. Content-Length does not match actual.")
return true

## 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