Revert broken asynchttpserver FutureStream additions.

As discussed in #13394, these changes cannot work. Reverted via

```
git revert --no-commit 5bf571f061
git revert --no-commit abd660c407
git revert --no-commit 955465e5f4
git commit
```
This commit is contained in:
Dominik Picheta 2020-02-22 11:46:24 +00:00 • committed by Dominik Picheta
commit ec8a17cc86
2 changed files with 29 additions and 145 deletions

View file

@ -98,8 +98,6 @@ echo f
## Library changes ## 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` - `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

View file

@ -29,129 +29,43 @@
## await req.respond(Http200, "Hello World") ## await req.respond(Http200, "Hello World")
## ##
## waitFor server.serve(Port(8080), cb) ## 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 tables, asyncnet, asyncdispatch, parseutils, uri, strutils
import httpcore 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 AsyncHttpServer* = ref object
maxLine = 8*1024 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,
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,
maxBody = 8388608): AsyncHttpServer = maxBody = 8388608): AsyncHttpServer =
## Creates a new ``AsyncHttpServer`` instance. ## Creates a new ``AsyncHttpServer`` instance.
new result new result
result.reuseAddr = reuseAddr result.reuseAddr = reuseAddr
result.reusePort = reusePort result.reusePort = reusePort
result.maxBody = maxBody result.maxBody = maxBody
proc addHeaders(msg: var string, headers: HttpHeaders) = proc addHeaders(msg: var string, headers: HttpHeaders) =
for k, v in headers: for k, v in headers:
@ -214,30 +128,13 @@ proc parseProtocol(protocol: string): tuple[orig: string, major, minor: int] =
proc sendStatus(client: AsyncSocket, status: string): Future[void] = proc sendStatus(client: AsyncSocket, status: string): Future[void] =
client.send("HTTP/1.1 " & status & "\c\L\c\L") 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( proc processRequest(
server: AsyncHttpServer, server: AsyncHttpServer,
req: FutureVar[Request], req: FutureVar[Request],
client: AsyncSocket, client: AsyncSocket,
address: string, address: string,
lineFut: FutureVar[string], lineFut: FutureVar[string],
callback: proc (request: Request): Future[void] {.closure, gcsafe.} callback: proc (request: Request): Future[void] {.closure, gcsafe.},
): Future[bool] {.async.} = ): Future[bool] {.async.} =
# Alias `request` to `req.mget()` so we don't have to write `mget` everywhere. # Alias `request` to `req.mget()` so we don't have to write `mget` everywhere.
@ -248,12 +145,10 @@ proc processRequest(
# Header: val # Header: val
# \n # \n
request.headers.clear() request.headers.clear()
request.body = ""
request.hostname.shallowCopy(address) request.hostname.shallowCopy(address)
assert client != nil assert client != nil
request.client = client request.client = client
request.body = ""
when (NimMajor, NimMinor) >= (1, 1):
request.contentLength = 0
# 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
@ -348,19 +243,10 @@ 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)
when (NimMajor, NimMinor) >= (1, 1): if request.body.len != contentLength:
request.contentLength = contentLength await request.respond(Http400, "Bad Request. Content-Length does not match actual.")
if not server.stream: 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
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