Skip to content

Commit

Permalink
Revert broken asynchttpserver FutureStream additions.
Browse files Browse the repository at this point in the history
As discussed in #13394, these changes cannot work. Reverted via

```
git revert --no-commit 5bf571f
git revert --no-commit abd660c
git revert --no-commit 955465e
git commit
```
  • Loading branch information
dom96 committed Feb 22, 2020
1 parent c11b91d commit 707d057
Show file tree
Hide file tree
Showing 2 changed files with 31 additions and 147 deletions.
2 changes: 0 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,6 @@

## Library changes

- `asynchttpserver` added an iterator that allows the request body to be read in
chunks of data when new server "stream" option is set to true.
- `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
176 changes: 31 additions & 145 deletions lib/pure/asynchttpserver.nim
Original file line number Diff line number Diff line change
Expand Up @@ -29,129 +29,43 @@
## await req.respond(Http200, "Hello World")
##
## waitFor server.serve(Port(8080), cb)
##
## Basic Post request handle
## =========================
##
## This example will create an HTTP server on port 8080. The server will
## respond with a page with the actual and expected body length after
## submitting a file.
##
## .. code-block::nim
## 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>
## <body>
## <form action="/" method="post" enctype="multipart/form-data">
## File: <input type="file" name="testfile" accept="text/*"><br />
## <input style="margin:10px 0;" type="submit">
## </form><br />
## Expected Body Length: {contentLength} bytes<br />
## Actual Body Length: {bodyLength} bytes
## </body>
## </html>
## """
##
## proc cb(req: Request) {.async.} =
## var
## contentLength = 0
## bodyLength = 0
## if req.reqMethod == HttpPost:
## contentLength = req.headers["Content-length"].parseInt
## 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:
## bodyLength += req.body.len
## await req.respond(Http200, htmlpage(contentLength, bodyLength))
##
## let server = newAsyncHttpServer(maxBody = 10485760, stream = stream) # 10 MB
## waitFor server.serve(Port(8080), cb)

import tables, asyncnet, asyncdispatch, parseutils, uri, strutils
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.

const
maxLine = 8*1024

when (NimMajor, NimMinor) >= (1, 1):
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
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
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.

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

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,
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:
Expand Down Expand Up @@ -214,30 +128,13 @@ 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.
Expand All @@ -248,12 +145,10 @@ proc processRequest(
# Header: val
# \n
request.headers.clear()
request.body = ""
request.hostname.shallowCopy(address)
assert client != nil
request.client = client
request.body = ""
when (NimMajor, NimMinor) >= (1, 1):
request.contentLength = 0

# We should skip at least one empty line before the request
# https://tools.ietf.org/html/rfc7230#section-3.5
Expand Down Expand Up @@ -348,19 +243,10 @@ proc processRequest(
if contentLength > server.maxBody:
await request.respondError(Http413)
return false

when (NimMajor, NimMinor) >= (1, 1):
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
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
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

0 comments on commit 707d057

Please sign in to comment.