Properly fix case when request body size exceeds maximum allowed size.
This commit is contained in:
parent
970e5641d7
commit
3e9ffae407
|
@ -7,6 +7,8 @@
|
|||
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||
# MIT license (LICENSE-MIT)
|
||||
import stew/results, httputils, strutils, uri
|
||||
import ../../asyncloop, ../../asyncsync
|
||||
import ../../streams/[asyncstream, boundstream]
|
||||
export results, httputils, strutils
|
||||
|
||||
const
|
||||
|
@ -30,6 +32,35 @@ type
|
|||
ContentEncodingFlags* {.pure.} = enum
|
||||
Identity, Br, Compress, Deflate, Gzip
|
||||
|
||||
HttpBodyReader* = ref object of AsyncStreamReader
|
||||
streams*: seq[AsyncStreamReader]
|
||||
|
||||
proc newHttpBodyReader*(streams: varargs[AsyncStreamReader]): HttpBodyReader =
|
||||
## HttpBodyReader is AsyncStreamReader which holds references to all the
|
||||
## ``streams``. Also on close it will close all the ``streams``.
|
||||
##
|
||||
## First stream in sequence will be used as a source.
|
||||
doAssert(len(streams) > 0, "At least one stream must be added")
|
||||
var res = HttpBodyReader(streams: @streams)
|
||||
res.init(streams[0])
|
||||
res
|
||||
|
||||
proc closeWait*(bstream: HttpBodyReader) {.async.} =
|
||||
## Close and free resource allocated by body reader.
|
||||
if len(bstream.streams) > 0:
|
||||
var res = newSeq[Future[void]]()
|
||||
for item in bstream.streams.items():
|
||||
res.add(item.closeWait())
|
||||
await allFutures(res)
|
||||
await procCall(AsyncStreamReader(bstream).closeWait())
|
||||
|
||||
proc atBound*(bstream: HttpBodyReader): bool =
|
||||
## Returns ``true`` if lowest stream is at EOF.
|
||||
let lreader = bstream.streams[^1]
|
||||
doAssert(lreader of BoundedStreamReader)
|
||||
let breader = cast[BoundedStreamReader](lreader)
|
||||
breader.atEof() and (breader.bytesLeft() == 0)
|
||||
|
||||
proc newHttpDefect*(msg: string): ref HttpDefect =
|
||||
newException(HttpDefect, msg)
|
||||
|
||||
|
|
|
@ -316,7 +316,7 @@ proc prepareRequest(conn: HttpConnectionRef,
|
|||
|
||||
ok(request)
|
||||
|
||||
proc getBodyStream*(request: HttpRequestRef): HttpResult[AsyncStreamReader] =
|
||||
proc getBodyReader*(request: HttpRequestRef): HttpResult[HttpBodyReader] =
|
||||
## Returns stream's reader instance which can be used to read request's body.
|
||||
##
|
||||
## Please be sure to handle ``Expect`` header properly.
|
||||
|
@ -324,10 +324,15 @@ proc getBodyStream*(request: HttpRequestRef): HttpResult[AsyncStreamReader] =
|
|||
## Streams which was obtained using this procedure must be closed to avoid
|
||||
## leaks.
|
||||
if HttpRequestFlags.BoundBody in request.requestFlags:
|
||||
ok(newBoundedStreamReader(request.connection.reader,
|
||||
request.contentLength))
|
||||
let bstream = newBoundedStreamReader(request.connection.reader,
|
||||
request.contentLength)
|
||||
ok(newHttpBodyReader(bstream))
|
||||
elif HttpRequestFlags.UnboundBody in request.requestFlags:
|
||||
ok(newChunkedStreamReader(request.connection.reader))
|
||||
let maxBodySize = request.connection.server.maxRequestBodySize
|
||||
let bstream = newBoundedStreamReader(request.connection.reader, maxBodySize,
|
||||
comparison = BoundCmp.LessOrEqual)
|
||||
let cstream = newChunkedStreamReader(bstream)
|
||||
ok(newHttpBodyReader(cstream, bstream))
|
||||
else:
|
||||
err("Request do not have body available")
|
||||
|
||||
|
@ -347,21 +352,25 @@ proc handleExpect*(request: HttpRequestRef) {.async.} =
|
|||
|
||||
proc getBody*(request: HttpRequestRef): Future[seq[byte]] {.async.} =
|
||||
## Obtain request's body as sequence of bytes.
|
||||
let res = request.getBodyStream()
|
||||
let res = request.getBodyReader()
|
||||
if res.isErr():
|
||||
return @[]
|
||||
else:
|
||||
let reader = res.get()
|
||||
try:
|
||||
await request.handleExpect()
|
||||
return await read(res.get())
|
||||
return await reader.read()
|
||||
except AsyncStreamError:
|
||||
if reader.atBound():
|
||||
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||
else:
|
||||
raise newHttpCriticalError("Unable to read request's body")
|
||||
finally:
|
||||
await closeWait(res.get())
|
||||
|
||||
proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} =
|
||||
## Consume/discard request's body.
|
||||
let res = request.getBodyStream()
|
||||
let res = request.getBodyReader()
|
||||
if res.isErr():
|
||||
return
|
||||
else:
|
||||
|
@ -370,7 +379,10 @@ proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} =
|
|||
await request.handleExpect()
|
||||
discard await reader.consume()
|
||||
except AsyncStreamError:
|
||||
raise newHttpCriticalError("Unable to consume request's body")
|
||||
if reader.atBound():
|
||||
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||
else:
|
||||
raise newHttpCriticalError("Unable to read request's body")
|
||||
finally:
|
||||
await closeWait(res.get())
|
||||
|
||||
|
@ -413,7 +425,7 @@ proc getRequest(conn: HttpConnectionRef): Future[HttpRequestRef] {.async.} =
|
|||
else:
|
||||
let res = prepareRequest(conn, header)
|
||||
if res.isErr():
|
||||
raise newHttpCriticalError("Invalid request received")
|
||||
raise newHttpCriticalError("Invalid request received", res.error)
|
||||
else:
|
||||
return res.get()
|
||||
except AsyncStreamIncompleteError:
|
||||
|
@ -460,7 +472,7 @@ proc new(ht: typedesc[HttpConnectionRef], server: HttpServerRef,
|
|||
writer: writer
|
||||
)
|
||||
|
||||
proc close(conn: HttpConnectionRef) {.async.} =
|
||||
proc closeWait(conn: HttpConnectionRef) {.async.} =
|
||||
if HttpServerFlags.Secure in conn.server.flags:
|
||||
# First we will close TLS streams.
|
||||
await allFutures(conn.reader.closeWait(), conn.writer.closeWait())
|
||||
|
@ -469,7 +481,7 @@ proc close(conn: HttpConnectionRef) {.async.} =
|
|||
await allFutures(conn.mainReader.closeWait(), conn.mainWriter.closeWait(),
|
||||
conn.transp.closeWait())
|
||||
|
||||
proc close(req: HttpRequestRef) {.async.} =
|
||||
proc closeWait(req: HttpRequestRef) {.async.} =
|
||||
if req.response.isSome():
|
||||
let resp = req.response.get()
|
||||
if (HttpResponseFlags.Chunked in resp.flags) and
|
||||
|
@ -488,10 +500,10 @@ proc createConnection(server: HttpServerRef,
|
|||
await handshake(conn.tlsStream)
|
||||
return conn
|
||||
except CancelledError as exc:
|
||||
await conn.close()
|
||||
await conn.closeWait()
|
||||
raise exc
|
||||
except TLSStreamError:
|
||||
await conn.close()
|
||||
await conn.closeWait()
|
||||
raise newHttpCriticalError("Unable to establish secure connection")
|
||||
|
||||
proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
||||
|
@ -557,14 +569,18 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
|||
break
|
||||
|
||||
breakLoop = false
|
||||
var lastError: ref CatchableError
|
||||
var lastErrorCode: Option[HttpCode]
|
||||
|
||||
try:
|
||||
resp = await conn.server.processCallback(arg)
|
||||
except CancelledError:
|
||||
breakLoop = true
|
||||
except CatchableError as exc:
|
||||
lastError = exc
|
||||
except HttpCriticalError as exc:
|
||||
lastErrorCode = some(exc.code)
|
||||
except HttpRecoverableError as exc:
|
||||
lastErrorCode = some(exc.code)
|
||||
except CatchableError:
|
||||
lastErrorCode = some(Http503)
|
||||
|
||||
if breakLoop:
|
||||
break
|
||||
|
@ -584,7 +600,7 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
|||
else:
|
||||
let request = arg.get()
|
||||
var keepConn = if request.version == HttpVersion11: true else: false
|
||||
if isNil(lastError):
|
||||
if lastErrorCode.isNone():
|
||||
if isNil(resp):
|
||||
# Response was `nil`.
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http404,
|
||||
|
@ -603,17 +619,17 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
|||
# some data was already sent to the client.
|
||||
discard
|
||||
else:
|
||||
discard await conn.sendErrorResponse(HttpVersion11, Http503, true)
|
||||
|
||||
discard await conn.sendErrorResponse(HttpVersion11, lastErrorCode.get(),
|
||||
false)
|
||||
# Closing and releasing all the request resources.
|
||||
await request.close()
|
||||
await request.closeWait()
|
||||
|
||||
if not(keepConn):
|
||||
break
|
||||
|
||||
# Connection could be `nil` only when secure handshake is failed.
|
||||
if not(isNil(conn)):
|
||||
await conn.close()
|
||||
await conn.closeWait()
|
||||
|
||||
server.connections.del(transp.getId())
|
||||
# if server.maxConnections > 0:
|
||||
|
@ -674,7 +690,7 @@ proc drop*(server: HttpServerRef) {.async.} =
|
|||
if server.state in {ServerStopped, ServerRunning}:
|
||||
discard
|
||||
|
||||
proc close*(server: HttpServerRef) {.async.} =
|
||||
proc closeWait*(server: HttpServerRef) {.async.} =
|
||||
## Stop HTTP server and drop all the pending connections.
|
||||
if server.state != ServerClosed:
|
||||
await server.stop()
|
||||
|
@ -713,7 +729,7 @@ proc getMultipartReader*(req: HttpRequestRef): HttpResult[MultiPartReaderRef] =
|
|||
let boundary = ? getMultipartBoundary(
|
||||
req.headers.getList("content-type")
|
||||
)
|
||||
var stream = ? req.getBodyStream()
|
||||
var stream = ? req.getBodyReader()
|
||||
ok(MultiPartReaderRef.new(stream, boundary))
|
||||
else:
|
||||
err("Request's data is not multipart encoded")
|
||||
|
@ -732,7 +748,8 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} =
|
|||
var table = HttpTable.init()
|
||||
# getBody() will handle `Expect`.
|
||||
var body = await req.getBody()
|
||||
## TODO (cheatfate) double copy here.
|
||||
# TODO (cheatfate) double copy here, because of `byte` to `char`
|
||||
# conversion.
|
||||
var strbody = newString(len(body))
|
||||
if len(body) > 0:
|
||||
copyMem(addr strbody[0], addr body[0], len(body))
|
||||
|
@ -751,10 +768,10 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} =
|
|||
try:
|
||||
await req.handleExpect()
|
||||
except CancelledError as exc:
|
||||
await mpreader.close()
|
||||
await mpreader.closeWait()
|
||||
raise exc
|
||||
except HttpCriticalError as exc:
|
||||
await mpreader.close()
|
||||
await mpreader.closeWait()
|
||||
raise exc
|
||||
|
||||
# Reading multipart/form-data parts.
|
||||
|
@ -764,25 +781,26 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} =
|
|||
try:
|
||||
part = await mpreader.readPart()
|
||||
var value = await part.getBody()
|
||||
## TODO (cheatfate) double copy here.
|
||||
# TODO (cheatfate) double copy here, because of `byte` to `char`
|
||||
# conversion.
|
||||
var strvalue = newString(len(value))
|
||||
if len(value) > 0:
|
||||
copyMem(addr strvalue[0], addr value[0], len(value))
|
||||
table.add(part.name, strvalue)
|
||||
await part.close()
|
||||
except MultiPartEoM:
|
||||
await part.closeWait()
|
||||
except MultipartEOMError:
|
||||
runLoop = false
|
||||
except HttpCriticalError as exc:
|
||||
if not(part.isEmpty()):
|
||||
await part.closeWait()
|
||||
await mpreader.closeWait()
|
||||
raise exc
|
||||
except CancelledError as exc:
|
||||
if not(part.isEmpty()):
|
||||
await part.close()
|
||||
await mpreader.close()
|
||||
await part.closeWait()
|
||||
await mpreader.closeWait()
|
||||
raise exc
|
||||
except MultipartError as exc:
|
||||
if not(part.isEmpty()):
|
||||
await part.close()
|
||||
await mpreader.close()
|
||||
raise exc
|
||||
await mpreader.close()
|
||||
await mpreader.closeWait()
|
||||
req.postTable = some(table)
|
||||
return table
|
||||
else:
|
||||
|
|
|
@ -21,7 +21,7 @@ type
|
|||
MultiPartReader* = object
|
||||
case kind: MultiPartSource
|
||||
of MultiPartSource.Stream:
|
||||
stream*: AsyncStreamReader
|
||||
stream*: HttpBodyReader
|
||||
of MultiPartSource.Buffer:
|
||||
discard
|
||||
firstTime: bool
|
||||
|
@ -35,6 +35,7 @@ type
|
|||
MultiPart* = object
|
||||
case kind: MultiPartSource
|
||||
of MultiPartSource.Stream:
|
||||
breader: HttpBodyReader
|
||||
stream: BoundedStreamReader
|
||||
of MultiPartSource.Buffer:
|
||||
discard
|
||||
|
@ -45,16 +46,10 @@ type
|
|||
filename*: string
|
||||
|
||||
MultipartError* = object of HttpCriticalError
|
||||
MultipartEoM* = object of MultipartError
|
||||
MultipartIncorrectError* = object of MultipartError
|
||||
MultipartIncompleteError* = object of MultipartError
|
||||
MultipartReadError* = object of MultipartError
|
||||
MultipartEOMError* = object of MultipartError
|
||||
|
||||
BChar* = byte | char
|
||||
|
||||
proc newMultipartReadError(msg: string): ref MultipartReadError =
|
||||
newException(MultipartReadError, msg)
|
||||
|
||||
proc startsWith*(s, prefix: openarray[byte]): bool =
|
||||
var i = 0
|
||||
while true:
|
||||
|
@ -97,7 +92,7 @@ proc init*[A: BChar, B: BChar](mpt: typedesc[MultiPartReader],
|
|||
buffer: buf, offset: 0, boundary: fboundary)
|
||||
|
||||
proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef],
|
||||
stream: AsyncStreamReader,
|
||||
stream: HttpBodyReader,
|
||||
boundary: openarray[B],
|
||||
partHeadersMaxSize = 4096): MultiPartReaderRef =
|
||||
## Create new MultiPartReader instance with `stream` interface.
|
||||
|
@ -149,14 +144,14 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
|||
mpr.firstTime = false
|
||||
if not(startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 3),
|
||||
mpr.boundary.toOpenArray(2, len(mpr.boundary) - 1))):
|
||||
raise newException(MultiPartIncorrectError,
|
||||
"Unexpected boundary encountered")
|
||||
raise newHttpCriticalError("Unexpected boundary encountered")
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except AsyncStreamIncompleteError:
|
||||
raise newMultipartReadError("Error reading multipart message")
|
||||
except AsyncStreamReadError:
|
||||
raise newMultipartReadError("Error reading multipart message")
|
||||
except AsyncStreamError:
|
||||
if mpr.stream.atBound():
|
||||
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||
else:
|
||||
raise newHttpCriticalError("Unable to read multipart body")
|
||||
|
||||
# Reading part's headers
|
||||
try:
|
||||
|
@ -167,26 +162,26 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
|||
await mpr.stream.readExactly(addr mpr.buffer[0], 2)
|
||||
if mpr.buffer[0] == 0x0D'u8 and mpr.buffer[1] == 0x0A'u8:
|
||||
# If 3rd and 4th bytes are CRLF we are exactly at the end of message.
|
||||
raise newException(MultiPartEoM,
|
||||
raise newException(MultipartEOMError,
|
||||
"End of multipart message")
|
||||
else:
|
||||
raise newException(MultiPartIncorrectError,
|
||||
"Incorrect part headers found")
|
||||
raise newHttpCriticalError("Incorrect multipart header found")
|
||||
if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8:
|
||||
raise newException(MultiPartIncorrectError,
|
||||
"Unexpected boundary suffix")
|
||||
raise newHttpCriticalError("Incorrect multipart boundary found")
|
||||
|
||||
# If two bytes are CRLF we are at the part beginning.
|
||||
# Reading part's headers
|
||||
let res = await mpr.stream.readUntil(addr mpr.buffer[0], len(mpr.buffer),
|
||||
HeadersMark)
|
||||
var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false)
|
||||
if headersList.failed():
|
||||
raise newException(MultiPartIncorrectError,
|
||||
"Incorrect part headers found")
|
||||
raise newHttpCriticalError("Incorrect multipart's headers found")
|
||||
inc(mpr.counter)
|
||||
|
||||
var part = MultiPart(
|
||||
kind: MultiPartSource.Stream,
|
||||
headers: HttpTable.init(),
|
||||
breader: mpr.stream,
|
||||
stream: newBoundedStreamReader(mpr.stream, -1, mpr.boundary),
|
||||
counter: mpr.counter
|
||||
)
|
||||
|
@ -196,17 +191,20 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
|||
|
||||
let sres = part.setPartNames()
|
||||
if sres.isErr():
|
||||
raise newException(MultiPartIncorrectError, sres.error)
|
||||
raise newHttpCriticalError(sres.error)
|
||||
return part
|
||||
|
||||
except CancelledError as exc:
|
||||
raise exc
|
||||
except AsyncStreamIncompleteError:
|
||||
raise newMultipartReadError("Error reading multipart message")
|
||||
except AsyncStreamLimitError:
|
||||
raise newMultipartReadError("Multipart message headers size too big")
|
||||
except AsyncStreamReadError:
|
||||
raise newMultipartReadError("Error reading multipart message")
|
||||
except AsyncStreamError:
|
||||
if mpr.stream.atBound():
|
||||
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||
else:
|
||||
raise newHttpCriticalError("Unable to read multipart body")
|
||||
|
||||
proc atBound*(mp: MultiPart): bool =
|
||||
## Returns ``true`` if MultiPart's stream reached request body maximum size.
|
||||
mp.breader.atBound()
|
||||
|
||||
proc getBody*(mp: MultiPart): Future[seq[byte]] {.async.} =
|
||||
## Get multipart's ``mp`` value as sequence of bytes.
|
||||
|
@ -216,7 +214,10 @@ proc getBody*(mp: MultiPart): Future[seq[byte]] {.async.} =
|
|||
let res = await mp.stream.read()
|
||||
return res
|
||||
except AsyncStreamError:
|
||||
raise newException(MultipartReadError, "Could not read multipart body")
|
||||
if mp.breader.atBound():
|
||||
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||
else:
|
||||
raise newHttpCriticalError("Unable to read multipart body")
|
||||
of MultiPartSource.Buffer:
|
||||
return mp.buffer
|
||||
|
||||
|
@ -225,9 +226,12 @@ proc consumeBody*(mp: MultiPart) {.async.} =
|
|||
case mp.kind
|
||||
of MultiPartSource.Stream:
|
||||
try:
|
||||
await mp.stream.consume()
|
||||
discard await mp.stream.consume()
|
||||
except AsyncStreamError:
|
||||
raise newException(MultipartReadError, "Could not consume multipart body")
|
||||
if mp.breader.atBound():
|
||||
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||
else:
|
||||
raise newHttpCriticalError("Unable to consume multipart body")
|
||||
of MultiPartSource.Buffer:
|
||||
discard
|
||||
|
||||
|
@ -240,7 +244,7 @@ proc getBodyStream*(mp: MultiPart): HttpResult[AsyncStreamReader] =
|
|||
else:
|
||||
err("Could not obtain stream from buffer-like part")
|
||||
|
||||
proc close*(mp: MultiPart) {.async.} =
|
||||
proc closeWait*(mp: MultiPart) {.async.} =
|
||||
## Close and release MultiPart's ``mp`` stream and resources.
|
||||
case mp.kind
|
||||
of MultiPartSource.Stream:
|
||||
|
@ -248,7 +252,7 @@ proc close*(mp: MultiPart) {.async.} =
|
|||
else:
|
||||
discard
|
||||
|
||||
proc close*(mpr: MultiPartReaderRef) {.async.} =
|
||||
proc closeWait*(mpr: MultiPartReaderRef) {.async.} =
|
||||
## Close and release MultiPartReader's ``mpr`` stream and resources.
|
||||
case mpr.kind
|
||||
of MultiPartSource.Stream:
|
||||
|
@ -412,6 +416,8 @@ func getMultipartBoundary*(ch: openarray[string]): HttpResult[string] =
|
|||
if len(ch) == 0:
|
||||
err("Content-Type header is missing")
|
||||
else:
|
||||
if len(ch[0]) == 0:
|
||||
return err("Content-Type header has empty value")
|
||||
let mparts = ch[0].split(";")
|
||||
if strip(mparts[0]).toLowerAscii() != "multipart/form-data":
|
||||
return err("Content-Type is not multipart")
|
||||
|
|
|
@ -219,7 +219,7 @@ proc boundedWriteLoop(stream: AsyncStreamWriter) {.async.} =
|
|||
|
||||
proc bytesLeft*(stream: BoundedStreamRW): uint64 =
|
||||
## Returns number of bytes left in stream.
|
||||
stream.boundSize - stream.bytesCount
|
||||
uint64(stream.boundSize) - stream.bytesCount
|
||||
|
||||
proc init*[T](child: BoundedStreamReader, rsource: AsyncStreamReader,
|
||||
bufferSize = BoundedBufferSize, udata: ref T) =
|
||||
|
|
|
@ -491,6 +491,22 @@ suite "ChunkedStream test suite":
|
|||
"Wikipedia in\r\n\r\nchunks."],
|
||||
["4\r\nWiki\r\n5\r\npedia\r\nE\r\n in\r\n\r\nchunks.\r\n0\r\n\r\n0\r\n\r\n",
|
||||
"Wikipedia in\r\n\r\nchunks."],
|
||||
["3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key1\"" &
|
||||
"\r\n\r\nA\r\n\r\n" &
|
||||
"3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key2\"" &
|
||||
"\r\n\r\nB\r\n\r\n" &
|
||||
"3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key3\"" &
|
||||
"\r\n\r\nC\r\n\r\n" &
|
||||
"b\r\n--f98f0--\r\n\r\n" &
|
||||
"0\r\n\r\n",
|
||||
"--f98f0\r\nContent-Disposition: form-data; name=\"key1\"" &
|
||||
"\r\n\r\nA\r\n" &
|
||||
"--f98f0\r\nContent-Disposition: form-data; name=\"key2\"" &
|
||||
"\r\n\r\nB\r\n" &
|
||||
"--f98f0\r\nContent-Disposition: form-data; name=\"key3\"" &
|
||||
"\r\n\r\nC\r\n" &
|
||||
"--f98f0--\r\n"
|
||||
]
|
||||
]
|
||||
proc checkVector(address: TransportAddress,
|
||||
inputstr: string): Future[string] {.async.} =
|
||||
|
|
|
@ -69,7 +69,6 @@ N8r5CwGcIX/XPC3lKazzbZ8baA==
|
|||
-----END CERTIFICATE-----
|
||||
"""
|
||||
|
||||
|
||||
suite "HTTP server testing suite":
|
||||
proc httpClient(address: TransportAddress,
|
||||
data: string): Future[string] {.async.} =
|
||||
|
@ -120,6 +119,69 @@ suite "HTTP server testing suite":
|
|||
await allFutures(reader.closeWait(), writer.closeWait(),
|
||||
transp.closeWait())
|
||||
|
||||
proc testTooBigBodyChunked(address: TransportAddress,
|
||||
operation: int): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||
async.} =
|
||||
if r.isOk():
|
||||
let request = r.get()
|
||||
try:
|
||||
if operation == 0:
|
||||
let body {.used.} = await request.getBody()
|
||||
elif operation == 1:
|
||||
await request.consumeBody()
|
||||
elif operation == 2:
|
||||
let ptable {.used.} = await request.post()
|
||||
elif operation == 3:
|
||||
let ptable {.used.} = await request.post()
|
||||
except HttpCriticalError as exc:
|
||||
if exc.code == Http413:
|
||||
serverRes = true
|
||||
# Reraising exception, because processor should properly handle it.
|
||||
raise exc
|
||||
else:
|
||||
return dumbResponse()
|
||||
|
||||
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
let res = HttpServerRef.new(address, process,
|
||||
maxRequestBodySize = 10,
|
||||
socketFlags = socketFlags)
|
||||
if res.isErr():
|
||||
return false
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
|
||||
let request =
|
||||
if operation in [0, 1, 2]:
|
||||
"POST / HTTP/1.0\r\n" &
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n" &
|
||||
"Transfer-Encoding: chunked\r\n" &
|
||||
"Cookie: 2\r\n\r\n" &
|
||||
"5\r\na=a&b\r\n5\r\n=b&c=\r\n4\r\nc&d=\r\n4\r\n%D0%\r\n" &
|
||||
"2\r\n9F\r\n0\r\n\r\n"
|
||||
elif operation in [3]:
|
||||
"POST / HTTP/1.0\r\n" &
|
||||
"Host: 127.0.0.1:30080\r\n" &
|
||||
"Transfer-Encoding: chunked\r\n" &
|
||||
"Content-Type: multipart/form-data; boundary=f98f0\r\n\r\n" &
|
||||
"3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key1\"" &
|
||||
"\r\n\r\nA\r\n\r\n" &
|
||||
"3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key2\"" &
|
||||
"\r\n\r\nB\r\n\r\n" &
|
||||
"3b\r\n--f98f0\r\nContent-Disposition: form-data; name=\"key3\"" &
|
||||
"\r\n\r\nC\r\n\r\n" &
|
||||
"b\r\n--f98f0--\r\n\r\n" &
|
||||
"0\r\n\r\n"
|
||||
else:
|
||||
""
|
||||
|
||||
let data = await httpClient(address, request)
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.startsWith("HTTP/1.1 413"))
|
||||
|
||||
test "Request headers timeout test":
|
||||
proc testTimeout(address: TransportAddress): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
|
@ -144,7 +206,7 @@ suite "HTTP server testing suite":
|
|||
|
||||
let data = await httpClient(address, "")
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.startsWith("HTTP/1.1 408"))
|
||||
|
||||
check waitFor(testTimeout(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
@ -172,7 +234,7 @@ suite "HTTP server testing suite":
|
|||
|
||||
let data = await httpClient(address, "\r\n\r\n")
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.startsWith("HTTP/1.1 400"))
|
||||
|
||||
check waitFor(testEmpty(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
@ -202,11 +264,57 @@ suite "HTTP server testing suite":
|
|||
|
||||
let data = await httpClient(address, "GET / HTTP/1.1\r\n\r\n")
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.startsWith("HTTP/1.1 413"))
|
||||
|
||||
check waitFor(testTooBig(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
||||
test "Too big request body test (content-length)":
|
||||
proc testTooBigBody(address: TransportAddress): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||
async.} =
|
||||
if r.isOk():
|
||||
discard
|
||||
else:
|
||||
if r.error().error == HTTPServerError.CriticalError:
|
||||
serverRes = true
|
||||
return dumbResponse()
|
||||
|
||||
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
let res = HttpServerRef.new(address, process,
|
||||
maxRequestBodySize = 10,
|
||||
socketFlags = socketFlags)
|
||||
if res.isErr():
|
||||
return false
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
|
||||
let request = "GET / HTTP/1.1\r\nContent-Length: 20\r\n\r\n"
|
||||
let data = await httpClient(address, request)
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.startsWith("HTTP/1.1 413"))
|
||||
|
||||
check waitFor(testTooBigBody(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
||||
test "Too big request body test (getBody()/chunked encoding)":
|
||||
check:
|
||||
waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 0)) == true
|
||||
|
||||
test "Too big request body test (consumeBody()/chunked encoding)":
|
||||
check:
|
||||
waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 1)) == true
|
||||
|
||||
test "Too big request body test (post()/urlencoded/chunked encoding)":
|
||||
check:
|
||||
waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 2)) == true
|
||||
|
||||
test "Too big request body test (post()/multipart/chunked encoding)":
|
||||
check:
|
||||
waitFor(testTooBigBodyChunked(initTAddress("127.0.0.1:30080"), 3)) == true
|
||||
|
||||
test "Query arguments test":
|
||||
proc testQuery(address: TransportAddress): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
|
@ -239,7 +347,7 @@ suite "HTTP server testing suite":
|
|||
let data2 = await httpClient(address,
|
||||
"GET /?a=%D0%9F&%D0%A4=%D0%91&b=%D0%A6&c=%D0%AE HTTP/1.0\r\n\r\n")
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
let r = serverRes and
|
||||
(data1.find("TEST_OK:a:1:a:2:b:3:c:4") >= 0) and
|
||||
(data2.find("TEST_OK:a:П:b:Ц:c:Ю:Ф:Б") >= 0)
|
||||
|
@ -285,12 +393,12 @@ suite "HTTP server testing suite":
|
|||
":expect:100-continue:host:www.google.com"
|
||||
let data = await httpClient(address, message)
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.find(expect) >= 0)
|
||||
|
||||
check waitFor(testHeaders(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
||||
test "POST arguments (application/x-www-form-urlencoded) test":
|
||||
test "POST arguments (urlencoded/content-length) test":
|
||||
proc testPostUrl(address: TransportAddress): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||
|
@ -328,12 +436,56 @@ suite "HTTP server testing suite":
|
|||
let data = await httpClient(address, message)
|
||||
let expect = "TEST_OK:a:a:b:b:c:c:d:П"
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.find(expect) >= 0)
|
||||
|
||||
check waitFor(testPostUrl(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
||||
test "POST arguments (multipart/form-data) test":
|
||||
test "POST arguments (urlencoded/chunked encoding) test":
|
||||
proc testPostUrl2(address: TransportAddress): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||
async.} =
|
||||
if r.isOk():
|
||||
var kres = newSeq[string]()
|
||||
let request = r.get()
|
||||
if request.meth in PostMethods:
|
||||
let post = await request.post()
|
||||
for k, v in post.stringItems():
|
||||
kres.add(k & ":" & v)
|
||||
sort(kres)
|
||||
serverRes = true
|
||||
return await request.respond(Http200, "TEST_OK:" & kres.join(":"),
|
||||
HttpTable.init())
|
||||
else:
|
||||
serverRes = false
|
||||
return dumbResponse()
|
||||
|
||||
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
|
||||
let res = HttpServerRef.new(address, process,
|
||||
socketFlags = socketFlags)
|
||||
if res.isErr():
|
||||
return false
|
||||
|
||||
let server = res.get()
|
||||
server.start()
|
||||
|
||||
let message =
|
||||
"POST / HTTP/1.0\r\n" &
|
||||
"Content-Type: application/x-www-form-urlencoded\r\n" &
|
||||
"Transfer-Encoding: chunked\r\n" &
|
||||
"Cookie: 2\r\n\r\n" &
|
||||
"5\r\na=a&b\r\n5\r\n=b&c=\r\n4\r\nc&d=\r\n4\r\n%D0%\r\n" &
|
||||
"2\r\n9F\r\n0\r\n\r\n"
|
||||
let data = await httpClient(address, message)
|
||||
let expect = "TEST_OK:a:a:b:b:c:c:d:П"
|
||||
await server.stop()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.find(expect) >= 0)
|
||||
|
||||
check waitFor(testPostUrl2(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
||||
test "POST arguments (multipart/content-length) test":
|
||||
proc testPostMultipart(address: TransportAddress): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||
|
@ -383,12 +535,12 @@ suite "HTTP server testing suite":
|
|||
let data = await httpClient(address, message)
|
||||
let expect = "TEST_OK:key1:value1:key2:value2:key2:value4"
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.find(expect) >= 0)
|
||||
|
||||
check waitFor(testPostMultipart(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
||||
test "POST arguments (multipart/form-data + chunked encoding) test":
|
||||
test "POST arguments (multipart/chunked encoding) test":
|
||||
proc testPostMultipart2(address: TransportAddress): Future[bool] {.async.} =
|
||||
var serverRes = false
|
||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||
|
@ -447,7 +599,7 @@ suite "HTTP server testing suite":
|
|||
"BBB:key2:CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" &
|
||||
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.find(expect) >= 0)
|
||||
|
||||
check waitFor(testPostMultipart2(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
@ -484,7 +636,7 @@ suite "HTTP server testing suite":
|
|||
let data = await httpsClient(address, message)
|
||||
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and (data.find("TEST_OK:GET") >= 0)
|
||||
|
||||
check waitFor(testHTTPS(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
@ -523,7 +675,7 @@ suite "HTTP server testing suite":
|
|||
let data = await httpsClient(address, message, {NoVerifyServerName})
|
||||
await testFut
|
||||
await server.stop()
|
||||
await server.close()
|
||||
await server.closeWait()
|
||||
return serverRes and data == "EXCEPTION"
|
||||
|
||||
check waitFor(testHTTPS2(initTAddress("127.0.0.1:30080"))) == true
|
||||
|
|
Loading…
Reference in New Issue