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)
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
||||||
# MIT license (LICENSE-MIT)
|
# MIT license (LICENSE-MIT)
|
||||||
import stew/results, httputils, strutils, uri
|
import stew/results, httputils, strutils, uri
|
||||||
|
import ../../asyncloop, ../../asyncsync
|
||||||
|
import ../../streams/[asyncstream, boundstream]
|
||||||
export results, httputils, strutils
|
export results, httputils, strutils
|
||||||
|
|
||||||
const
|
const
|
||||||
|
@ -30,6 +32,35 @@ type
|
||||||
ContentEncodingFlags* {.pure.} = enum
|
ContentEncodingFlags* {.pure.} = enum
|
||||||
Identity, Br, Compress, Deflate, Gzip
|
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 =
|
proc newHttpDefect*(msg: string): ref HttpDefect =
|
||||||
newException(HttpDefect, msg)
|
newException(HttpDefect, msg)
|
||||||
|
|
||||||
|
|
|
@ -316,7 +316,7 @@ proc prepareRequest(conn: HttpConnectionRef,
|
||||||
|
|
||||||
ok(request)
|
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.
|
## Returns stream's reader instance which can be used to read request's body.
|
||||||
##
|
##
|
||||||
## Please be sure to handle ``Expect`` header properly.
|
## 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
|
## Streams which was obtained using this procedure must be closed to avoid
|
||||||
## leaks.
|
## leaks.
|
||||||
if HttpRequestFlags.BoundBody in request.requestFlags:
|
if HttpRequestFlags.BoundBody in request.requestFlags:
|
||||||
ok(newBoundedStreamReader(request.connection.reader,
|
let bstream = newBoundedStreamReader(request.connection.reader,
|
||||||
request.contentLength))
|
request.contentLength)
|
||||||
|
ok(newHttpBodyReader(bstream))
|
||||||
elif HttpRequestFlags.UnboundBody in request.requestFlags:
|
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:
|
else:
|
||||||
err("Request do not have body available")
|
err("Request do not have body available")
|
||||||
|
|
||||||
|
@ -347,21 +352,25 @@ proc handleExpect*(request: HttpRequestRef) {.async.} =
|
||||||
|
|
||||||
proc getBody*(request: HttpRequestRef): Future[seq[byte]] {.async.} =
|
proc getBody*(request: HttpRequestRef): Future[seq[byte]] {.async.} =
|
||||||
## Obtain request's body as sequence of bytes.
|
## Obtain request's body as sequence of bytes.
|
||||||
let res = request.getBodyStream()
|
let res = request.getBodyReader()
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
return @[]
|
return @[]
|
||||||
else:
|
else:
|
||||||
|
let reader = res.get()
|
||||||
try:
|
try:
|
||||||
await request.handleExpect()
|
await request.handleExpect()
|
||||||
return await read(res.get())
|
return await reader.read()
|
||||||
except AsyncStreamError:
|
except AsyncStreamError:
|
||||||
|
if reader.atBound():
|
||||||
|
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||||
|
else:
|
||||||
raise newHttpCriticalError("Unable to read request's body")
|
raise newHttpCriticalError("Unable to read request's body")
|
||||||
finally:
|
finally:
|
||||||
await closeWait(res.get())
|
await closeWait(res.get())
|
||||||
|
|
||||||
proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} =
|
proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} =
|
||||||
## Consume/discard request's body.
|
## Consume/discard request's body.
|
||||||
let res = request.getBodyStream()
|
let res = request.getBodyReader()
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
|
@ -370,7 +379,10 @@ proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} =
|
||||||
await request.handleExpect()
|
await request.handleExpect()
|
||||||
discard await reader.consume()
|
discard await reader.consume()
|
||||||
except AsyncStreamError:
|
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:
|
finally:
|
||||||
await closeWait(res.get())
|
await closeWait(res.get())
|
||||||
|
|
||||||
|
@ -413,7 +425,7 @@ proc getRequest(conn: HttpConnectionRef): Future[HttpRequestRef] {.async.} =
|
||||||
else:
|
else:
|
||||||
let res = prepareRequest(conn, header)
|
let res = prepareRequest(conn, header)
|
||||||
if res.isErr():
|
if res.isErr():
|
||||||
raise newHttpCriticalError("Invalid request received")
|
raise newHttpCriticalError("Invalid request received", res.error)
|
||||||
else:
|
else:
|
||||||
return res.get()
|
return res.get()
|
||||||
except AsyncStreamIncompleteError:
|
except AsyncStreamIncompleteError:
|
||||||
|
@ -460,7 +472,7 @@ proc new(ht: typedesc[HttpConnectionRef], server: HttpServerRef,
|
||||||
writer: writer
|
writer: writer
|
||||||
)
|
)
|
||||||
|
|
||||||
proc close(conn: HttpConnectionRef) {.async.} =
|
proc closeWait(conn: HttpConnectionRef) {.async.} =
|
||||||
if HttpServerFlags.Secure in conn.server.flags:
|
if HttpServerFlags.Secure in conn.server.flags:
|
||||||
# First we will close TLS streams.
|
# First we will close TLS streams.
|
||||||
await allFutures(conn.reader.closeWait(), conn.writer.closeWait())
|
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(),
|
await allFutures(conn.mainReader.closeWait(), conn.mainWriter.closeWait(),
|
||||||
conn.transp.closeWait())
|
conn.transp.closeWait())
|
||||||
|
|
||||||
proc close(req: HttpRequestRef) {.async.} =
|
proc closeWait(req: HttpRequestRef) {.async.} =
|
||||||
if req.response.isSome():
|
if req.response.isSome():
|
||||||
let resp = req.response.get()
|
let resp = req.response.get()
|
||||||
if (HttpResponseFlags.Chunked in resp.flags) and
|
if (HttpResponseFlags.Chunked in resp.flags) and
|
||||||
|
@ -488,10 +500,10 @@ proc createConnection(server: HttpServerRef,
|
||||||
await handshake(conn.tlsStream)
|
await handshake(conn.tlsStream)
|
||||||
return conn
|
return conn
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
await conn.close()
|
await conn.closeWait()
|
||||||
raise exc
|
raise exc
|
||||||
except TLSStreamError:
|
except TLSStreamError:
|
||||||
await conn.close()
|
await conn.closeWait()
|
||||||
raise newHttpCriticalError("Unable to establish secure connection")
|
raise newHttpCriticalError("Unable to establish secure connection")
|
||||||
|
|
||||||
proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
||||||
|
@ -557,14 +569,18 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
||||||
break
|
break
|
||||||
|
|
||||||
breakLoop = false
|
breakLoop = false
|
||||||
var lastError: ref CatchableError
|
var lastErrorCode: Option[HttpCode]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
resp = await conn.server.processCallback(arg)
|
resp = await conn.server.processCallback(arg)
|
||||||
except CancelledError:
|
except CancelledError:
|
||||||
breakLoop = true
|
breakLoop = true
|
||||||
except CatchableError as exc:
|
except HttpCriticalError as exc:
|
||||||
lastError = exc
|
lastErrorCode = some(exc.code)
|
||||||
|
except HttpRecoverableError as exc:
|
||||||
|
lastErrorCode = some(exc.code)
|
||||||
|
except CatchableError:
|
||||||
|
lastErrorCode = some(Http503)
|
||||||
|
|
||||||
if breakLoop:
|
if breakLoop:
|
||||||
break
|
break
|
||||||
|
@ -584,7 +600,7 @@ proc processLoop(server: HttpServerRef, transp: StreamTransport) {.async.} =
|
||||||
else:
|
else:
|
||||||
let request = arg.get()
|
let request = arg.get()
|
||||||
var keepConn = if request.version == HttpVersion11: true else: false
|
var keepConn = if request.version == HttpVersion11: true else: false
|
||||||
if isNil(lastError):
|
if lastErrorCode.isNone():
|
||||||
if isNil(resp):
|
if isNil(resp):
|
||||||
# Response was `nil`.
|
# Response was `nil`.
|
||||||
discard await conn.sendErrorResponse(HttpVersion11, Http404,
|
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.
|
# some data was already sent to the client.
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
discard await conn.sendErrorResponse(HttpVersion11, Http503, true)
|
discard await conn.sendErrorResponse(HttpVersion11, lastErrorCode.get(),
|
||||||
|
false)
|
||||||
# Closing and releasing all the request resources.
|
# Closing and releasing all the request resources.
|
||||||
await request.close()
|
await request.closeWait()
|
||||||
|
|
||||||
if not(keepConn):
|
if not(keepConn):
|
||||||
break
|
break
|
||||||
|
|
||||||
# Connection could be `nil` only when secure handshake is failed.
|
# Connection could be `nil` only when secure handshake is failed.
|
||||||
if not(isNil(conn)):
|
if not(isNil(conn)):
|
||||||
await conn.close()
|
await conn.closeWait()
|
||||||
|
|
||||||
server.connections.del(transp.getId())
|
server.connections.del(transp.getId())
|
||||||
# if server.maxConnections > 0:
|
# if server.maxConnections > 0:
|
||||||
|
@ -674,7 +690,7 @@ proc drop*(server: HttpServerRef) {.async.} =
|
||||||
if server.state in {ServerStopped, ServerRunning}:
|
if server.state in {ServerStopped, ServerRunning}:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc close*(server: HttpServerRef) {.async.} =
|
proc closeWait*(server: HttpServerRef) {.async.} =
|
||||||
## Stop HTTP server and drop all the pending connections.
|
## Stop HTTP server and drop all the pending connections.
|
||||||
if server.state != ServerClosed:
|
if server.state != ServerClosed:
|
||||||
await server.stop()
|
await server.stop()
|
||||||
|
@ -713,7 +729,7 @@ proc getMultipartReader*(req: HttpRequestRef): HttpResult[MultiPartReaderRef] =
|
||||||
let boundary = ? getMultipartBoundary(
|
let boundary = ? getMultipartBoundary(
|
||||||
req.headers.getList("content-type")
|
req.headers.getList("content-type")
|
||||||
)
|
)
|
||||||
var stream = ? req.getBodyStream()
|
var stream = ? req.getBodyReader()
|
||||||
ok(MultiPartReaderRef.new(stream, boundary))
|
ok(MultiPartReaderRef.new(stream, boundary))
|
||||||
else:
|
else:
|
||||||
err("Request's data is not multipart encoded")
|
err("Request's data is not multipart encoded")
|
||||||
|
@ -732,7 +748,8 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} =
|
||||||
var table = HttpTable.init()
|
var table = HttpTable.init()
|
||||||
# getBody() will handle `Expect`.
|
# getBody() will handle `Expect`.
|
||||||
var body = await req.getBody()
|
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))
|
var strbody = newString(len(body))
|
||||||
if len(body) > 0:
|
if len(body) > 0:
|
||||||
copyMem(addr strbody[0], addr body[0], len(body))
|
copyMem(addr strbody[0], addr body[0], len(body))
|
||||||
|
@ -751,10 +768,10 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} =
|
||||||
try:
|
try:
|
||||||
await req.handleExpect()
|
await req.handleExpect()
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
await mpreader.close()
|
await mpreader.closeWait()
|
||||||
raise exc
|
raise exc
|
||||||
except HttpCriticalError as exc:
|
except HttpCriticalError as exc:
|
||||||
await mpreader.close()
|
await mpreader.closeWait()
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
# Reading multipart/form-data parts.
|
# Reading multipart/form-data parts.
|
||||||
|
@ -764,25 +781,26 @@ proc post*(req: HttpRequestRef): Future[HttpTable] {.async.} =
|
||||||
try:
|
try:
|
||||||
part = await mpreader.readPart()
|
part = await mpreader.readPart()
|
||||||
var value = await part.getBody()
|
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))
|
var strvalue = newString(len(value))
|
||||||
if len(value) > 0:
|
if len(value) > 0:
|
||||||
copyMem(addr strvalue[0], addr value[0], len(value))
|
copyMem(addr strvalue[0], addr value[0], len(value))
|
||||||
table.add(part.name, strvalue)
|
table.add(part.name, strvalue)
|
||||||
await part.close()
|
await part.closeWait()
|
||||||
except MultiPartEoM:
|
except MultipartEOMError:
|
||||||
runLoop = false
|
runLoop = false
|
||||||
|
except HttpCriticalError as exc:
|
||||||
|
if not(part.isEmpty()):
|
||||||
|
await part.closeWait()
|
||||||
|
await mpreader.closeWait()
|
||||||
|
raise exc
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
if not(part.isEmpty()):
|
if not(part.isEmpty()):
|
||||||
await part.close()
|
await part.closeWait()
|
||||||
await mpreader.close()
|
await mpreader.closeWait()
|
||||||
raise exc
|
raise exc
|
||||||
except MultipartError as exc:
|
await mpreader.closeWait()
|
||||||
if not(part.isEmpty()):
|
|
||||||
await part.close()
|
|
||||||
await mpreader.close()
|
|
||||||
raise exc
|
|
||||||
await mpreader.close()
|
|
||||||
req.postTable = some(table)
|
req.postTable = some(table)
|
||||||
return table
|
return table
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -21,7 +21,7 @@ type
|
||||||
MultiPartReader* = object
|
MultiPartReader* = object
|
||||||
case kind: MultiPartSource
|
case kind: MultiPartSource
|
||||||
of MultiPartSource.Stream:
|
of MultiPartSource.Stream:
|
||||||
stream*: AsyncStreamReader
|
stream*: HttpBodyReader
|
||||||
of MultiPartSource.Buffer:
|
of MultiPartSource.Buffer:
|
||||||
discard
|
discard
|
||||||
firstTime: bool
|
firstTime: bool
|
||||||
|
@ -35,6 +35,7 @@ type
|
||||||
MultiPart* = object
|
MultiPart* = object
|
||||||
case kind: MultiPartSource
|
case kind: MultiPartSource
|
||||||
of MultiPartSource.Stream:
|
of MultiPartSource.Stream:
|
||||||
|
breader: HttpBodyReader
|
||||||
stream: BoundedStreamReader
|
stream: BoundedStreamReader
|
||||||
of MultiPartSource.Buffer:
|
of MultiPartSource.Buffer:
|
||||||
discard
|
discard
|
||||||
|
@ -45,16 +46,10 @@ type
|
||||||
filename*: string
|
filename*: string
|
||||||
|
|
||||||
MultipartError* = object of HttpCriticalError
|
MultipartError* = object of HttpCriticalError
|
||||||
MultipartEoM* = object of MultipartError
|
MultipartEOMError* = object of MultipartError
|
||||||
MultipartIncorrectError* = object of MultipartError
|
|
||||||
MultipartIncompleteError* = object of MultipartError
|
|
||||||
MultipartReadError* = object of MultipartError
|
|
||||||
|
|
||||||
BChar* = byte | char
|
BChar* = byte | char
|
||||||
|
|
||||||
proc newMultipartReadError(msg: string): ref MultipartReadError =
|
|
||||||
newException(MultipartReadError, msg)
|
|
||||||
|
|
||||||
proc startsWith*(s, prefix: openarray[byte]): bool =
|
proc startsWith*(s, prefix: openarray[byte]): bool =
|
||||||
var i = 0
|
var i = 0
|
||||||
while true:
|
while true:
|
||||||
|
@ -97,7 +92,7 @@ proc init*[A: BChar, B: BChar](mpt: typedesc[MultiPartReader],
|
||||||
buffer: buf, offset: 0, boundary: fboundary)
|
buffer: buf, offset: 0, boundary: fboundary)
|
||||||
|
|
||||||
proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef],
|
proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef],
|
||||||
stream: AsyncStreamReader,
|
stream: HttpBodyReader,
|
||||||
boundary: openarray[B],
|
boundary: openarray[B],
|
||||||
partHeadersMaxSize = 4096): MultiPartReaderRef =
|
partHeadersMaxSize = 4096): MultiPartReaderRef =
|
||||||
## Create new MultiPartReader instance with `stream` interface.
|
## Create new MultiPartReader instance with `stream` interface.
|
||||||
|
@ -149,14 +144,14 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
||||||
mpr.firstTime = false
|
mpr.firstTime = false
|
||||||
if not(startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 3),
|
if not(startsWith(mpr.buffer.toOpenArray(0, len(mpr.boundary) - 3),
|
||||||
mpr.boundary.toOpenArray(2, len(mpr.boundary) - 1))):
|
mpr.boundary.toOpenArray(2, len(mpr.boundary) - 1))):
|
||||||
raise newException(MultiPartIncorrectError,
|
raise newHttpCriticalError("Unexpected boundary encountered")
|
||||||
"Unexpected boundary encountered")
|
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
raise exc
|
raise exc
|
||||||
except AsyncStreamIncompleteError:
|
except AsyncStreamError:
|
||||||
raise newMultipartReadError("Error reading multipart message")
|
if mpr.stream.atBound():
|
||||||
except AsyncStreamReadError:
|
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||||
raise newMultipartReadError("Error reading multipart message")
|
else:
|
||||||
|
raise newHttpCriticalError("Unable to read multipart body")
|
||||||
|
|
||||||
# Reading part's headers
|
# Reading part's headers
|
||||||
try:
|
try:
|
||||||
|
@ -167,26 +162,26 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
||||||
await mpr.stream.readExactly(addr mpr.buffer[0], 2)
|
await mpr.stream.readExactly(addr mpr.buffer[0], 2)
|
||||||
if mpr.buffer[0] == 0x0D'u8 and mpr.buffer[1] == 0x0A'u8:
|
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.
|
# 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")
|
"End of multipart message")
|
||||||
else:
|
else:
|
||||||
raise newException(MultiPartIncorrectError,
|
raise newHttpCriticalError("Incorrect multipart header found")
|
||||||
"Incorrect part headers found")
|
|
||||||
if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8:
|
if mpr.buffer[0] != 0x0D'u8 or mpr.buffer[1] != 0x0A'u8:
|
||||||
raise newException(MultiPartIncorrectError,
|
raise newHttpCriticalError("Incorrect multipart boundary found")
|
||||||
"Unexpected boundary suffix")
|
|
||||||
# If two bytes are CRLF we are at the part beginning.
|
# If two bytes are CRLF we are at the part beginning.
|
||||||
# Reading part's headers
|
# Reading part's headers
|
||||||
let res = await mpr.stream.readUntil(addr mpr.buffer[0], len(mpr.buffer),
|
let res = await mpr.stream.readUntil(addr mpr.buffer[0], len(mpr.buffer),
|
||||||
HeadersMark)
|
HeadersMark)
|
||||||
var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false)
|
var headersList = parseHeaders(mpr.buffer.toOpenArray(0, res - 1), false)
|
||||||
if headersList.failed():
|
if headersList.failed():
|
||||||
raise newException(MultiPartIncorrectError,
|
raise newHttpCriticalError("Incorrect multipart's headers found")
|
||||||
"Incorrect part headers found")
|
|
||||||
inc(mpr.counter)
|
inc(mpr.counter)
|
||||||
|
|
||||||
var part = MultiPart(
|
var part = MultiPart(
|
||||||
kind: MultiPartSource.Stream,
|
kind: MultiPartSource.Stream,
|
||||||
headers: HttpTable.init(),
|
headers: HttpTable.init(),
|
||||||
|
breader: mpr.stream,
|
||||||
stream: newBoundedStreamReader(mpr.stream, -1, mpr.boundary),
|
stream: newBoundedStreamReader(mpr.stream, -1, mpr.boundary),
|
||||||
counter: mpr.counter
|
counter: mpr.counter
|
||||||
)
|
)
|
||||||
|
@ -196,17 +191,20 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
|
||||||
|
|
||||||
let sres = part.setPartNames()
|
let sres = part.setPartNames()
|
||||||
if sres.isErr():
|
if sres.isErr():
|
||||||
raise newException(MultiPartIncorrectError, sres.error)
|
raise newHttpCriticalError(sres.error)
|
||||||
return part
|
return part
|
||||||
|
|
||||||
except CancelledError as exc:
|
except CancelledError as exc:
|
||||||
raise exc
|
raise exc
|
||||||
except AsyncStreamIncompleteError:
|
except AsyncStreamError:
|
||||||
raise newMultipartReadError("Error reading multipart message")
|
if mpr.stream.atBound():
|
||||||
except AsyncStreamLimitError:
|
raise newHttpCriticalError("Maximum size of body reached", Http413)
|
||||||
raise newMultipartReadError("Multipart message headers size too big")
|
else:
|
||||||
except AsyncStreamReadError:
|
raise newHttpCriticalError("Unable to read multipart body")
|
||||||
raise newMultipartReadError("Error reading multipart message")
|
|
||||||
|
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.} =
|
proc getBody*(mp: MultiPart): Future[seq[byte]] {.async.} =
|
||||||
## Get multipart's ``mp`` value as sequence of bytes.
|
## 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()
|
let res = await mp.stream.read()
|
||||||
return res
|
return res
|
||||||
except AsyncStreamError:
|
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:
|
of MultiPartSource.Buffer:
|
||||||
return mp.buffer
|
return mp.buffer
|
||||||
|
|
||||||
|
@ -225,9 +226,12 @@ proc consumeBody*(mp: MultiPart) {.async.} =
|
||||||
case mp.kind
|
case mp.kind
|
||||||
of MultiPartSource.Stream:
|
of MultiPartSource.Stream:
|
||||||
try:
|
try:
|
||||||
await mp.stream.consume()
|
discard await mp.stream.consume()
|
||||||
except AsyncStreamError:
|
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:
|
of MultiPartSource.Buffer:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
@ -240,7 +244,7 @@ proc getBodyStream*(mp: MultiPart): HttpResult[AsyncStreamReader] =
|
||||||
else:
|
else:
|
||||||
err("Could not obtain stream from buffer-like part")
|
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.
|
## Close and release MultiPart's ``mp`` stream and resources.
|
||||||
case mp.kind
|
case mp.kind
|
||||||
of MultiPartSource.Stream:
|
of MultiPartSource.Stream:
|
||||||
|
@ -248,7 +252,7 @@ proc close*(mp: MultiPart) {.async.} =
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
proc close*(mpr: MultiPartReaderRef) {.async.} =
|
proc closeWait*(mpr: MultiPartReaderRef) {.async.} =
|
||||||
## Close and release MultiPartReader's ``mpr`` stream and resources.
|
## Close and release MultiPartReader's ``mpr`` stream and resources.
|
||||||
case mpr.kind
|
case mpr.kind
|
||||||
of MultiPartSource.Stream:
|
of MultiPartSource.Stream:
|
||||||
|
@ -412,6 +416,8 @@ func getMultipartBoundary*(ch: openarray[string]): HttpResult[string] =
|
||||||
if len(ch) == 0:
|
if len(ch) == 0:
|
||||||
err("Content-Type header is missing")
|
err("Content-Type header is missing")
|
||||||
else:
|
else:
|
||||||
|
if len(ch[0]) == 0:
|
||||||
|
return err("Content-Type header has empty value")
|
||||||
let mparts = ch[0].split(";")
|
let mparts = ch[0].split(";")
|
||||||
if strip(mparts[0]).toLowerAscii() != "multipart/form-data":
|
if strip(mparts[0]).toLowerAscii() != "multipart/form-data":
|
||||||
return err("Content-Type is not multipart")
|
return err("Content-Type is not multipart")
|
||||||
|
|
|
@ -219,7 +219,7 @@ proc boundedWriteLoop(stream: AsyncStreamWriter) {.async.} =
|
||||||
|
|
||||||
proc bytesLeft*(stream: BoundedStreamRW): uint64 =
|
proc bytesLeft*(stream: BoundedStreamRW): uint64 =
|
||||||
## Returns number of bytes left in stream.
|
## Returns number of bytes left in stream.
|
||||||
stream.boundSize - stream.bytesCount
|
uint64(stream.boundSize) - stream.bytesCount
|
||||||
|
|
||||||
proc init*[T](child: BoundedStreamReader, rsource: AsyncStreamReader,
|
proc init*[T](child: BoundedStreamReader, rsource: AsyncStreamReader,
|
||||||
bufferSize = BoundedBufferSize, udata: ref T) =
|
bufferSize = BoundedBufferSize, udata: ref T) =
|
||||||
|
|
|
@ -491,6 +491,22 @@ suite "ChunkedStream test suite":
|
||||||
"Wikipedia in\r\n\r\nchunks."],
|
"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",
|
["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."],
|
"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,
|
proc checkVector(address: TransportAddress,
|
||||||
inputstr: string): Future[string] {.async.} =
|
inputstr: string): Future[string] {.async.} =
|
||||||
|
|
|
@ -69,7 +69,6 @@ N8r5CwGcIX/XPC3lKazzbZ8baA==
|
||||||
-----END CERTIFICATE-----
|
-----END CERTIFICATE-----
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
suite "HTTP server testing suite":
|
suite "HTTP server testing suite":
|
||||||
proc httpClient(address: TransportAddress,
|
proc httpClient(address: TransportAddress,
|
||||||
data: string): Future[string] {.async.} =
|
data: string): Future[string] {.async.} =
|
||||||
|
@ -120,6 +119,69 @@ suite "HTTP server testing suite":
|
||||||
await allFutures(reader.closeWait(), writer.closeWait(),
|
await allFutures(reader.closeWait(), writer.closeWait(),
|
||||||
transp.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":
|
test "Request headers timeout test":
|
||||||
proc testTimeout(address: TransportAddress): Future[bool] {.async.} =
|
proc testTimeout(address: TransportAddress): Future[bool] {.async.} =
|
||||||
var serverRes = false
|
var serverRes = false
|
||||||
|
@ -144,7 +206,7 @@ suite "HTTP server testing suite":
|
||||||
|
|
||||||
let data = await httpClient(address, "")
|
let data = await httpClient(address, "")
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.startsWith("HTTP/1.1 408"))
|
return serverRes and (data.startsWith("HTTP/1.1 408"))
|
||||||
|
|
||||||
check waitFor(testTimeout(initTAddress("127.0.0.1:30080"))) == true
|
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")
|
let data = await httpClient(address, "\r\n\r\n")
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.startsWith("HTTP/1.1 400"))
|
return serverRes and (data.startsWith("HTTP/1.1 400"))
|
||||||
|
|
||||||
check waitFor(testEmpty(initTAddress("127.0.0.1:30080"))) == true
|
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")
|
let data = await httpClient(address, "GET / HTTP/1.1\r\n\r\n")
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.startsWith("HTTP/1.1 413"))
|
return serverRes and (data.startsWith("HTTP/1.1 413"))
|
||||||
|
|
||||||
check waitFor(testTooBig(initTAddress("127.0.0.1:30080"))) == true
|
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":
|
test "Query arguments test":
|
||||||
proc testQuery(address: TransportAddress): Future[bool] {.async.} =
|
proc testQuery(address: TransportAddress): Future[bool] {.async.} =
|
||||||
var serverRes = false
|
var serverRes = false
|
||||||
|
@ -239,7 +347,7 @@ suite "HTTP server testing suite":
|
||||||
let data2 = await httpClient(address,
|
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")
|
"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.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
let r = serverRes and
|
let r = serverRes and
|
||||||
(data1.find("TEST_OK:a:1:a:2:b:3:c:4") >= 0) and
|
(data1.find("TEST_OK:a:1:a:2:b:3:c:4") >= 0) and
|
||||||
(data2.find("TEST_OK:a:П:b:Ц:c:Ю:Ф:Б") >= 0)
|
(data2.find("TEST_OK:a:П:b:Ц:c:Ю:Ф:Б") >= 0)
|
||||||
|
@ -285,12 +393,12 @@ suite "HTTP server testing suite":
|
||||||
":expect:100-continue:host:www.google.com"
|
":expect:100-continue:host:www.google.com"
|
||||||
let data = await httpClient(address, message)
|
let data = await httpClient(address, message)
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.find(expect) >= 0)
|
return serverRes and (data.find(expect) >= 0)
|
||||||
|
|
||||||
check waitFor(testHeaders(initTAddress("127.0.0.1:30080"))) == true
|
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.} =
|
proc testPostUrl(address: TransportAddress): Future[bool] {.async.} =
|
||||||
var serverRes = false
|
var serverRes = false
|
||||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||||
|
@ -328,12 +436,56 @@ suite "HTTP server testing suite":
|
||||||
let data = await httpClient(address, message)
|
let data = await httpClient(address, message)
|
||||||
let expect = "TEST_OK:a:a:b:b:c:c:d:П"
|
let expect = "TEST_OK:a:a:b:b:c:c:d:П"
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.find(expect) >= 0)
|
return serverRes and (data.find(expect) >= 0)
|
||||||
|
|
||||||
check waitFor(testPostUrl(initTAddress("127.0.0.1:30080"))) == true
|
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.} =
|
proc testPostMultipart(address: TransportAddress): Future[bool] {.async.} =
|
||||||
var serverRes = false
|
var serverRes = false
|
||||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||||
|
@ -383,12 +535,12 @@ suite "HTTP server testing suite":
|
||||||
let data = await httpClient(address, message)
|
let data = await httpClient(address, message)
|
||||||
let expect = "TEST_OK:key1:value1:key2:value2:key2:value4"
|
let expect = "TEST_OK:key1:value1:key2:value2:key2:value4"
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.find(expect) >= 0)
|
return serverRes and (data.find(expect) >= 0)
|
||||||
|
|
||||||
check waitFor(testPostMultipart(initTAddress("127.0.0.1:30080"))) == true
|
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.} =
|
proc testPostMultipart2(address: TransportAddress): Future[bool] {.async.} =
|
||||||
var serverRes = false
|
var serverRes = false
|
||||||
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
proc process(r: RequestFence[HttpRequestRef]): Future[HttpResponseRef] {.
|
||||||
|
@ -447,7 +599,7 @@ suite "HTTP server testing suite":
|
||||||
"BBB:key2:CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" &
|
"BBB:key2:CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" &
|
||||||
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
|
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.find(expect) >= 0)
|
return serverRes and (data.find(expect) >= 0)
|
||||||
|
|
||||||
check waitFor(testPostMultipart2(initTAddress("127.0.0.1:30080"))) == true
|
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)
|
let data = await httpsClient(address, message)
|
||||||
|
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and (data.find("TEST_OK:GET") >= 0)
|
return serverRes and (data.find("TEST_OK:GET") >= 0)
|
||||||
|
|
||||||
check waitFor(testHTTPS(initTAddress("127.0.0.1:30080"))) == true
|
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})
|
let data = await httpsClient(address, message, {NoVerifyServerName})
|
||||||
await testFut
|
await testFut
|
||||||
await server.stop()
|
await server.stop()
|
||||||
await server.close()
|
await server.closeWait()
|
||||||
return serverRes and data == "EXCEPTION"
|
return serverRes and data == "EXCEPTION"
|
||||||
|
|
||||||
check waitFor(testHTTPS2(initTAddress("127.0.0.1:30080"))) == true
|
check waitFor(testHTTPS2(initTAddress("127.0.0.1:30080"))) == true
|
||||||
|
|
Loading…
Reference in New Issue