Properly fix case when request body size exceeds maximum allowed size.

This commit is contained in:
cheatfate 2021-02-10 10:43:05 +02:00 committed by zah
parent 970e5641d7
commit 3e9ffae407
6 changed files with 310 additions and 87 deletions

View File

@ -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)

View File

@ -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:
raise newHttpCriticalError("Unable to read 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())
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:

View File

@ -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")

View File

@ -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) =

View File

@ -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.} =

View File

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