Asyncraises HTTP layer V3 (#482)

* No Critical and Recoverable errors anymore.

* Recover raiseHttpCriticalError()

* Post-rebase fixes.

* Remove deprecated ResponseFence and getResponseFence().

* HttpProcessCallback and 2.

* Fix callback holder.

* Fix test issue.

* Fix backwards compatibility of `HttpResponse.state` field.
This commit is contained in:
Eugene Kabanov 2023-12-09 06:50:35 +02:00 committed by GitHub
parent e38ceb5378
commit c41599a6d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 840 additions and 817 deletions

View File

@ -43,30 +43,48 @@ const
ServerHeader* = "server" ServerHeader* = "server"
LocationHeader* = "location" LocationHeader* = "location"
AuthorizationHeader* = "authorization" AuthorizationHeader* = "authorization"
ContentDispositionHeader* = "content-disposition"
UrlEncodedContentType* = MediaType.init("application/x-www-form-urlencoded") UrlEncodedContentType* = MediaType.init("application/x-www-form-urlencoded")
MultipartContentType* = MediaType.init("multipart/form-data") MultipartContentType* = MediaType.init("multipart/form-data")
type type
HttpMessage* = object
code*: HttpCode
contentType*: MediaType
message*: string
HttpResult*[T] = Result[T, string] HttpResult*[T] = Result[T, string]
HttpResultCode*[T] = Result[T, HttpCode] HttpResultCode*[T] = Result[T, HttpCode]
HttpResultMessage*[T] = Result[T, HttpMessage]
HttpDefect* = object of Defect
HttpError* = object of AsyncError HttpError* = object of AsyncError
HttpResponseError* = object of HttpError
code*: HttpCode
HttpCriticalError* = object of HttpResponseError
HttpRecoverableError* = object of HttpResponseError
HttpDisconnectError* = object of HttpError
HttpConnectionError* = object of HttpError
HttpInterruptError* = object of HttpError HttpInterruptError* = object of HttpError
HttpReadError* = object of HttpError
HttpWriteError* = object of HttpError HttpTransportError* = object of HttpError
HttpProtocolError* = object of HttpError HttpAddressError* = object of HttpTransportError
HttpRedirectError* = object of HttpError HttpRedirectError* = object of HttpTransportError
HttpAddressError* = object of HttpError HttpConnectionError* = object of HttpTransportError
HttpUseClosedError* = object of HttpError HttpReadError* = object of HttpTransportError
HttpReadLimitError* = object of HttpReadError HttpReadLimitError* = object of HttpReadError
HttpDisconnectError* = object of HttpReadError
HttpWriteError* = object of HttpTransportError
HttpProtocolError* = object of HttpError
code*: HttpCode
HttpCriticalError* = object of HttpProtocolError # deprecated
HttpRecoverableError* = object of HttpProtocolError # deprecated
HttpRequestError* = object of HttpProtocolError
HttpRequestHeadersError* = object of HttpRequestError
HttpRequestBodyError* = object of HttpRequestError
HttpRequestHeadersTooLargeError* = object of HttpRequestHeadersError
HttpRequestBodyTooLargeError* = object of HttpRequestBodyError
HttpResponseError* = object of HttpProtocolError
HttpInvalidUsageError* = object of HttpError
HttpUseClosedError* = object of HttpInvalidUsageError
KeyValueTuple* = tuple KeyValueTuple* = tuple
key: string key: string
@ -127,6 +145,11 @@ func toString*(error: HttpAddressErrorType): string =
of HttpAddressErrorType.NoAddressResolved: of HttpAddressErrorType.NoAddressResolved:
"No address has been resolved" "No address has been resolved"
proc raiseHttpRequestBodyTooLargeError*() {.
noinline, noreturn, raises: [HttpRequestBodyTooLargeError].} =
raise (ref HttpRequestBodyTooLargeError)(
code: Http413, msg: MaximumBodySizeError)
proc raiseHttpCriticalError*(msg: string, code = Http400) {. proc raiseHttpCriticalError*(msg: string, code = Http400) {.
noinline, noreturn, raises: [HttpCriticalError].} = noinline, noreturn, raises: [HttpCriticalError].} =
raise (ref HttpCriticalError)(code: code, msg: msg) raise (ref HttpCriticalError)(code: code, msg: msg)
@ -135,9 +158,6 @@ proc raiseHttpDisconnectError*() {.
noinline, noreturn, raises: [HttpDisconnectError].} = noinline, noreturn, raises: [HttpDisconnectError].} =
raise (ref HttpDisconnectError)(msg: "Remote peer disconnected") raise (ref HttpDisconnectError)(msg: "Remote peer disconnected")
proc raiseHttpDefect*(msg: string) {.noinline, noreturn.} =
raise (ref HttpDefect)(msg: msg)
proc raiseHttpConnectionError*(msg: string) {. proc raiseHttpConnectionError*(msg: string) {.
noinline, noreturn, raises: [HttpConnectionError].} = noinline, noreturn, raises: [HttpConnectionError].} =
raise (ref HttpConnectionError)(msg: msg) raise (ref HttpConnectionError)(msg: msg)
@ -152,7 +172,15 @@ proc raiseHttpReadError*(msg: string) {.
proc raiseHttpProtocolError*(msg: string) {. proc raiseHttpProtocolError*(msg: string) {.
noinline, noreturn, raises: [HttpProtocolError].} = noinline, noreturn, raises: [HttpProtocolError].} =
raise (ref HttpProtocolError)(msg: msg) raise (ref HttpProtocolError)(code: Http400, msg: msg)
proc raiseHttpProtocolError*(code: HttpCode, msg: string) {.
noinline, noreturn, raises: [HttpProtocolError].} =
raise (ref HttpProtocolError)(code: code, msg: msg)
proc raiseHttpProtocolError*(msg: HttpMessage) {.
noinline, noreturn, raises: [HttpProtocolError].} =
raise (ref HttpProtocolError)(code: msg.code, msg: msg.message)
proc raiseHttpWriteError*(msg: string) {. proc raiseHttpWriteError*(msg: string) {.
noinline, noreturn, raises: [HttpWriteError].} = noinline, noreturn, raises: [HttpWriteError].} =
@ -178,6 +206,23 @@ template newHttpWriteError*(message: string): ref HttpWriteError =
template newHttpUseClosedError*(): ref HttpUseClosedError = template newHttpUseClosedError*(): ref HttpUseClosedError =
newException(HttpUseClosedError, "Connection was already closed") newException(HttpUseClosedError, "Connection was already closed")
func init*(t: typedesc[HttpMessage], code: HttpCode, message: string,
contentType: MediaType): HttpMessage =
HttpMessage(code: code, message: message, contentType: contentType)
func init*(t: typedesc[HttpMessage], code: HttpCode, message: string,
contentType: string): HttpMessage =
HttpMessage(code: code, message: message,
contentType: MediaType.init(contentType))
func init*(t: typedesc[HttpMessage], code: HttpCode,
message: string): HttpMessage =
HttpMessage(code: code, message: message,
contentType: MediaType.init("text/plain"))
func init*(t: typedesc[HttpMessage], code: HttpCode): HttpMessage =
HttpMessage(code: code)
iterator queryParams*(query: string, iterator queryParams*(query: string,
flags: set[QueryParamsFlag] = {}): KeyValueTuple = flags: set[QueryParamsFlag] = {}): KeyValueTuple =
## Iterate over url-encoded query string. ## Iterate over url-encoded query string.

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,8 @@ import "."/[httptable, httpcommon, httpbodyrw]
export asyncloop, httptable, httpcommon, httpbodyrw, asyncstream, httputils export asyncloop, httptable, httpcommon, httpbodyrw, asyncstream, httputils
const const
UnableToReadMultipartBody = "Unable to read multipart message body" UnableToReadMultipartBody = "Unable to read multipart message body, reason: "
UnableToSendMultipartMessage = "Unable to send multipart message, reason: "
type type
MultiPartSource* {.pure.} = enum MultiPartSource* {.pure.} = enum
@ -69,7 +70,7 @@ type
name*: string name*: string
filename*: string filename*: string
MultipartError* = object of HttpCriticalError MultipartError* = object of HttpProtocolError
MultipartEOMError* = object of MultipartError MultipartEOMError* = object of MultipartError
BChar* = byte | char BChar* = byte | char
@ -105,7 +106,7 @@ func setPartNames(part: var MultiPart): HttpResult[void] =
return err("Content-Disposition header value is incorrect") return err("Content-Disposition header value is incorrect")
let dtype = disp.dispositionType(header.toOpenArrayByte(0, len(header) - 1)) let dtype = disp.dispositionType(header.toOpenArrayByte(0, len(header) - 1))
if dtype.toLowerAscii() != "form-data": if dtype.toLowerAscii() != "form-data":
return err("Content-Disposition type is incorrect") return err("Content-Disposition header type is incorrect")
for k, v in disp.fields(header.toOpenArrayByte(0, len(header) - 1)): for k, v in disp.fields(header.toOpenArrayByte(0, len(header) - 1)):
case k.toLowerAscii() case k.toLowerAscii()
of "name": of "name":
@ -171,8 +172,17 @@ proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef],
stream: stream, offset: 0, boundary: fboundary, stream: stream, offset: 0, boundary: fboundary,
buffer: newSeq[byte](partHeadersMaxSize)) buffer: newSeq[byte](partHeadersMaxSize))
template handleAsyncStreamReaderError(targ, excarg: untyped) =
if targ.hasOverflow():
raiseHttpRequestBodyTooLargeError()
raiseHttpReadError(UnableToReadMultipartBody & $excarg.msg)
template handleAsyncStreamWriterError(targ, excarg: untyped) =
targ.state = MultiPartWriterState.MessageFailure
raiseHttpWriteError(UnableToSendMultipartMessage & $excarg.msg)
proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {. proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpReadError, HttpProtocolError]).} =
doAssert(mpr.kind == MultiPartSource.Stream) doAssert(mpr.kind == MultiPartSource.Stream)
if mpr.firstTime: if mpr.firstTime:
try: try:
@ -181,14 +191,11 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.
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))):
raiseHttpCriticalError("Unexpected boundary encountered") raiseHttpProtocolError(Http400, "Unexpected boundary encountered")
except CancelledError as exc: except CancelledError as exc:
raise exc raise exc
except AsyncStreamError: except AsyncStreamError as exc:
if mpr.stream.hasOverflow(): handleAsyncStreamReaderError(mpr.stream, exc)
raiseHttpCriticalError(MaximumBodySizeError, Http413)
else:
raiseHttpCriticalError(UnableToReadMultipartBody)
# Reading part's headers # Reading part's headers
try: try:
@ -202,9 +209,9 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.
raise newException(MultipartEOMError, raise newException(MultipartEOMError,
"End of multipart message") "End of multipart message")
else: else:
raiseHttpCriticalError("Incorrect multipart header found") raiseHttpProtocolError(Http400, "Incorrect multipart header 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:
raiseHttpCriticalError("Incorrect multipart boundary found") raiseHttpProtocolError(Http400, "Incorrect multipart boundary found")
# 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
@ -212,7 +219,7 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.
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():
raiseHttpCriticalError("Incorrect multipart's headers found") raiseHttpProtocolError(Http400, "Incorrect multipart's headers found")
inc(mpr.counter) inc(mpr.counter)
var part = MultiPart( var part = MultiPart(
@ -228,45 +235,35 @@ proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.
let sres = part.setPartNames() let sres = part.setPartNames()
if sres.isErr(): if sres.isErr():
raiseHttpCriticalError($sres.error) raiseHttpProtocolError(Http400, $sres.error)
return part return part
except CancelledError as exc: except CancelledError as exc:
raise exc raise exc
except AsyncStreamError: except AsyncStreamError as exc:
if mpr.stream.hasOverflow(): handleAsyncStreamReaderError(mpr.stream, exc)
raiseHttpCriticalError(MaximumBodySizeError, Http413)
else:
raiseHttpCriticalError(UnableToReadMultipartBody)
proc getBody*(mp: MultiPart): Future[seq[byte]] {. proc getBody*(mp: MultiPart): Future[seq[byte]] {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpReadError, HttpProtocolError]).} =
## Get multipart's ``mp`` value as sequence of bytes. ## Get multipart's ``mp`` value as sequence of bytes.
case mp.kind case mp.kind
of MultiPartSource.Stream: of MultiPartSource.Stream:
try: try:
let res = await mp.stream.read() await mp.stream.read()
return res except AsyncStreamError as exc:
except AsyncStreamError: handleAsyncStreamReaderError(mp.breader, exc)
if mp.breader.hasOverflow():
raiseHttpCriticalError(MaximumBodySizeError, Http413)
else:
raiseHttpCriticalError(UnableToReadMultipartBody)
of MultiPartSource.Buffer: of MultiPartSource.Buffer:
return mp.buffer mp.buffer
proc consumeBody*(mp: MultiPart) {. proc consumeBody*(mp: MultiPart) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpReadError, HttpProtocolError]).} =
## Discard multipart's ``mp`` value. ## Discard multipart's ``mp`` value.
case mp.kind case mp.kind
of MultiPartSource.Stream: of MultiPartSource.Stream:
try: try:
discard await mp.stream.consume() discard await mp.stream.consume()
except AsyncStreamError: except AsyncStreamError as exc:
if mp.breader.hasOverflow(): handleAsyncStreamReaderError(mp.breader, exc)
raiseHttpCriticalError(MaximumBodySizeError, Http413)
else:
raiseHttpCriticalError(UnableToReadMultipartBody)
of MultiPartSource.Buffer: of MultiPartSource.Buffer:
discard discard
@ -533,7 +530,7 @@ proc new*[B: BChar](mpt: typedesc[MultiPartWriterRef],
proc prepareHeaders(partMark: openArray[byte], name: string, filename: string, proc prepareHeaders(partMark: openArray[byte], name: string, filename: string,
headers: HttpTable): string = headers: HttpTable): string =
const ContentDisposition = "Content-Disposition" const ContentDispositionHeader = "Content-Disposition"
let qname = let qname =
block: block:
let res = quoteCheck(name) let res = quoteCheck(name)
@ -546,10 +543,10 @@ proc prepareHeaders(partMark: openArray[byte], name: string, filename: string,
res.get() res.get()
var buffer = newString(len(partMark)) var buffer = newString(len(partMark))
copyMem(addr buffer[0], unsafeAddr partMark[0], len(partMark)) copyMem(addr buffer[0], unsafeAddr partMark[0], len(partMark))
buffer.add(ContentDisposition) buffer.add(ContentDispositionHeader)
buffer.add(": ") buffer.add(": ")
if ContentDisposition in headers: if ContentDispositionHeader in headers:
buffer.add(headers.getString(ContentDisposition)) buffer.add(headers.getString(ContentDispositionHeader))
buffer.add("\r\n") buffer.add("\r\n")
else: else:
buffer.add("form-data; name=\"") buffer.add("form-data; name=\"")
@ -562,7 +559,7 @@ proc prepareHeaders(partMark: openArray[byte], name: string, filename: string,
buffer.add("\r\n") buffer.add("\r\n")
for k, v in headers.stringItems(): for k, v in headers.stringItems():
if k != toLowerAscii(ContentDisposition): if k != ContentDispositionHeader:
if len(v) > 0: if len(v) > 0:
buffer.add(k) buffer.add(k)
buffer.add(": ") buffer.add(": ")
@ -572,7 +569,7 @@ proc prepareHeaders(partMark: openArray[byte], name: string, filename: string,
buffer buffer
proc begin*(mpw: MultiPartWriterRef) {. proc begin*(mpw: MultiPartWriterRef) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpWriteError]).} =
## Starts multipart message form and write approprate markers to output ## Starts multipart message form and write approprate markers to output
## stream. ## stream.
doAssert(mpw.kind == MultiPartSource.Stream) doAssert(mpw.kind == MultiPartSource.Stream)
@ -580,10 +577,9 @@ proc begin*(mpw: MultiPartWriterRef) {.
# write "--" # write "--"
try: try:
await mpw.stream.write(mpw.beginMark) await mpw.stream.write(mpw.beginMark)
except AsyncStreamError: mpw.state = MultiPartWriterState.MessageStarted
mpw.state = MultiPartWriterState.MessageFailure except AsyncStreamError as exc:
raiseHttpCriticalError("Unable to start multipart message") handleAsyncStreamWriterError(mpw, exc)
mpw.state = MultiPartWriterState.MessageStarted
proc begin*(mpw: var MultiPartWriter) = proc begin*(mpw: var MultiPartWriter) =
## Starts multipart message form and write approprate markers to output ## Starts multipart message form and write approprate markers to output
@ -596,7 +592,7 @@ proc begin*(mpw: var MultiPartWriter) =
proc beginPart*(mpw: MultiPartWriterRef, name: string, proc beginPart*(mpw: MultiPartWriterRef, name: string,
filename: string, headers: HttpTable) {. filename: string, headers: HttpTable) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpWriteError]).} =
## Starts part of multipart message and write appropriate ``headers`` to the ## Starts part of multipart message and write appropriate ``headers`` to the
## output stream. ## output stream.
## ##
@ -611,9 +607,8 @@ proc beginPart*(mpw: MultiPartWriterRef, name: string,
try: try:
await mpw.stream.write(buffer) await mpw.stream.write(buffer)
mpw.state = MultiPartWriterState.PartStarted mpw.state = MultiPartWriterState.PartStarted
except AsyncStreamError: except AsyncStreamError as exc:
mpw.state = MultiPartWriterState.MessageFailure handleAsyncStreamWriterError(mpw, exc)
raiseHttpCriticalError("Unable to start multipart part")
proc beginPart*(mpw: var MultiPartWriter, name: string, proc beginPart*(mpw: var MultiPartWriter, name: string,
filename: string, headers: HttpTable) = filename: string, headers: HttpTable) =
@ -632,7 +627,7 @@ proc beginPart*(mpw: var MultiPartWriter, name: string,
mpw.state = MultiPartWriterState.PartStarted mpw.state = MultiPartWriterState.PartStarted
proc write*(mpw: MultiPartWriterRef, pbytes: pointer, nbytes: int) {. proc write*(mpw: MultiPartWriterRef, pbytes: pointer, nbytes: int) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpWriteError]).} =
## Write part's data ``data`` to the output stream. ## Write part's data ``data`` to the output stream.
doAssert(mpw.kind == MultiPartSource.Stream) doAssert(mpw.kind == MultiPartSource.Stream)
doAssert(mpw.state == MultiPartWriterState.PartStarted) doAssert(mpw.state == MultiPartWriterState.PartStarted)
@ -640,12 +635,10 @@ proc write*(mpw: MultiPartWriterRef, pbytes: pointer, nbytes: int) {.
# write <chunk> of data # write <chunk> of data
await mpw.stream.write(pbytes, nbytes) await mpw.stream.write(pbytes, nbytes)
except AsyncStreamError as exc: except AsyncStreamError as exc:
mpw.state = MultiPartWriterState.MessageFailure handleAsyncStreamWriterError(mpw, exc)
raiseHttpCriticalError(
"Unable to write multipart data, reason: " & $exc.msg)
proc write*(mpw: MultiPartWriterRef, data: seq[byte]) {. proc write*(mpw: MultiPartWriterRef, data: seq[byte]) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpWriteError]).} =
## Write part's data ``data`` to the output stream. ## Write part's data ``data`` to the output stream.
doAssert(mpw.kind == MultiPartSource.Stream) doAssert(mpw.kind == MultiPartSource.Stream)
doAssert(mpw.state == MultiPartWriterState.PartStarted) doAssert(mpw.state == MultiPartWriterState.PartStarted)
@ -653,12 +646,10 @@ proc write*(mpw: MultiPartWriterRef, data: seq[byte]) {.
# write <chunk> of data # write <chunk> of data
await mpw.stream.write(data) await mpw.stream.write(data)
except AsyncStreamError as exc: except AsyncStreamError as exc:
mpw.state = MultiPartWriterState.MessageFailure handleAsyncStreamWriterError(mpw, exc)
raiseHttpCriticalError(
"Unable to write multipart data, reason: " & $exc.msg)
proc write*(mpw: MultiPartWriterRef, data: string) {. proc write*(mpw: MultiPartWriterRef, data: string) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpWriteError]).} =
## Write part's data ``data`` to the output stream. ## Write part's data ``data`` to the output stream.
doAssert(mpw.kind == MultiPartSource.Stream) doAssert(mpw.kind == MultiPartSource.Stream)
doAssert(mpw.state == MultiPartWriterState.PartStarted) doAssert(mpw.state == MultiPartWriterState.PartStarted)
@ -666,9 +657,7 @@ proc write*(mpw: MultiPartWriterRef, data: string) {.
# write <chunk> of data # write <chunk> of data
await mpw.stream.write(data) await mpw.stream.write(data)
except AsyncStreamError as exc: except AsyncStreamError as exc:
mpw.state = MultiPartWriterState.MessageFailure handleAsyncStreamWriterError(mpw, exc)
raiseHttpCriticalError(
"Unable to write multipart data, reason: " & $exc.msg)
proc write*(mpw: var MultiPartWriter, pbytes: pointer, nbytes: int) = proc write*(mpw: var MultiPartWriter, pbytes: pointer, nbytes: int) =
## Write part's data ``data`` to the output stream. ## Write part's data ``data`` to the output stream.
@ -692,7 +681,7 @@ proc write*(mpw: var MultiPartWriter, data: openArray[char]) =
mpw.buffer.add(data.toOpenArrayByte(0, len(data) - 1)) mpw.buffer.add(data.toOpenArrayByte(0, len(data) - 1))
proc finishPart*(mpw: MultiPartWriterRef) {. proc finishPart*(mpw: MultiPartWriterRef) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpWriteError]).} =
## Finish multipart's message part and send proper markers to output stream. ## Finish multipart's message part and send proper markers to output stream.
doAssert(mpw.state == MultiPartWriterState.PartStarted) doAssert(mpw.state == MultiPartWriterState.PartStarted)
try: try:
@ -700,9 +689,7 @@ proc finishPart*(mpw: MultiPartWriterRef) {.
await mpw.stream.write(mpw.finishPartMark) await mpw.stream.write(mpw.finishPartMark)
mpw.state = MultiPartWriterState.PartFinished mpw.state = MultiPartWriterState.PartFinished
except AsyncStreamError as exc: except AsyncStreamError as exc:
mpw.state = MultiPartWriterState.MessageFailure handleAsyncStreamWriterError(mpw, exc)
raiseHttpCriticalError(
"Unable to finish multipart message part, reason: " & $exc.msg)
proc finishPart*(mpw: var MultiPartWriter) = proc finishPart*(mpw: var MultiPartWriter) =
## Finish multipart's message part and send proper markers to output stream. ## Finish multipart's message part and send proper markers to output stream.
@ -713,7 +700,7 @@ proc finishPart*(mpw: var MultiPartWriter) =
mpw.state = MultiPartWriterState.PartFinished mpw.state = MultiPartWriterState.PartFinished
proc finish*(mpw: MultiPartWriterRef) {. proc finish*(mpw: MultiPartWriterRef) {.
async: (raises: [CancelledError, HttpCriticalError]).} = async: (raises: [CancelledError, HttpWriteError]).} =
## Finish multipart's message form and send finishing markers to the output ## Finish multipart's message form and send finishing markers to the output
## stream. ## stream.
doAssert(mpw.kind == MultiPartSource.Stream) doAssert(mpw.kind == MultiPartSource.Stream)
@ -723,9 +710,7 @@ proc finish*(mpw: MultiPartWriterRef) {.
await mpw.stream.write(mpw.finishMark) await mpw.stream.write(mpw.finishMark)
mpw.state = MultiPartWriterState.MessageFinished mpw.state = MultiPartWriterState.MessageFinished
except AsyncStreamError as exc: except AsyncStreamError as exc:
mpw.state = MultiPartWriterState.MessageFailure handleAsyncStreamWriterError(mpw, exc)
raiseHttpCriticalError(
"Unable to finish multipart message, reason: " & $exc.msg)
proc finish*(mpw: var MultiPartWriter): seq[byte] = proc finish*(mpw: var MultiPartWriter): seq[byte] =
## Finish multipart's message form and send finishing markers to the output ## Finish multipart's message form and send finishing markers to the output

View File

@ -164,22 +164,21 @@ proc new*(htype: typedesc[SecureHttpServerRef],
maxRequestBodySize: int = 1_048_576, maxRequestBodySize: int = 1_048_576,
dualstack = DualStackType.Auto dualstack = DualStackType.Auto
): HttpResult[SecureHttpServerRef] {. ): HttpResult[SecureHttpServerRef] {.
deprecated: "raises missing from process callback".} = deprecated: "Callback could raise only CancelledError, annotate with " &
proc processCallback2(req: RequestFence): Future[HttpResponseRef] {. "{.async: (raises: [CancelledError]).}".} =
async: (raises: [CancelledError, HttpResponseError]).} =
try: proc wrap(req: RequestFence): Future[HttpResponseRef] {.
await processCallback(req) async: (raises: [CancelledError]).} =
except CancelledError as exc: try:
raise exc await processCallback(req)
except HttpResponseError as exc: except CancelledError as exc:
raise exc raise exc
except CatchableError as exc: except CatchableError as exc:
# Emulate 3.x behavior defaultResponse(exc)
raise (ref HttpCriticalError)(msg: exc.msg, code: Http503)
SecureHttpServerRef.new( SecureHttpServerRef.new(
address = address, address = address,
processCallback = processCallback2, processCallback = wrap,
tlsPrivateKey = tlsPrivateKey, tlsPrivateKey = tlsPrivateKey,
tlsCertificate = tlsCertificate, tlsCertificate = tlsCertificate,
serverFlags = serverFlags, serverFlags = serverFlags,
@ -194,4 +193,4 @@ proc new*(htype: typedesc[SecureHttpServerRef],
maxHeadersSize = maxHeadersSize, maxHeadersSize = maxHeadersSize,
maxRequestBodySize = maxRequestBodySize, maxRequestBodySize = maxRequestBodySize,
dualstack = dualstack dualstack = dualstack
) )

View File

@ -85,7 +85,8 @@ suite "HTTP client testing suite":
res res
proc createServer(address: TransportAddress, proc createServer(address: TransportAddress,
process: HttpProcessCallback2, secure: bool): HttpServerRef = process: HttpProcessCallback2,
secure: bool): HttpServerRef =
let let
socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
serverFlags = {HttpServerFlags.Http11Pipeline} serverFlags = {HttpServerFlags.Http11Pipeline}
@ -128,18 +129,24 @@ suite "HTTP client testing suite":
(MethodPatch, "/test/patch") (MethodPatch, "/test/patch")
] ]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path case request.uri.path
of "/test/get", "/test/post", "/test/head", "/test/put", of "/test/get", "/test/post", "/test/head", "/test/put",
"/test/delete", "/test/trace", "/test/options", "/test/connect", "/test/delete", "/test/trace", "/test/options", "/test/connect",
"/test/patch", "/test/error": "/test/patch", "/test/error":
return await request.respond(Http200, request.uri.path) try:
await request.respond(Http200, request.uri.path)
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return await request.respond(Http404, "Page not found") try:
await request.respond(Http404, "Page not found")
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -195,7 +202,7 @@ suite "HTTP client testing suite":
"LONGCHUNKRESPONSE") "LONGCHUNKRESPONSE")
] ]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path case request.uri.path
@ -203,46 +210,58 @@ suite "HTTP client testing suite":
var response = request.getResponse() var response = request.getResponse()
var data = createBigMessage(ResponseTests[0][4], ResponseTests[0][2]) var data = createBigMessage(ResponseTests[0][4], ResponseTests[0][2])
response.status = Http200 response.status = Http200
await response.sendBody(data) try:
return response await response.sendBody(data)
except HttpWriteError as exc:
return defaultResponse(exc)
response
of "/test/long_size_response": of "/test/long_size_response":
var response = request.getResponse() var response = request.getResponse()
var data = createBigMessage(ResponseTests[1][4], ResponseTests[1][2]) var data = createBigMessage(ResponseTests[1][4], ResponseTests[1][2])
response.status = Http200 response.status = Http200
await response.sendBody(data) try:
return response await response.sendBody(data)
except HttpWriteError as exc:
return defaultResponse(exc)
response
of "/test/short_chunked_response": of "/test/short_chunked_response":
var response = request.getResponse() var response = request.getResponse()
var data = createBigMessage(ResponseTests[2][4], ResponseTests[2][2]) var data = createBigMessage(ResponseTests[2][4], ResponseTests[2][2])
response.status = Http200 response.status = Http200
await response.prepare() try:
var offset = 0 await response.prepare()
while true: var offset = 0
if len(data) == offset: while true:
break if len(data) == offset:
let toWrite = min(1024, len(data) - offset) break
await response.sendChunk(addr data[offset], toWrite) let toWrite = min(1024, len(data) - offset)
offset = offset + toWrite await response.sendChunk(addr data[offset], toWrite)
await response.finish() offset = offset + toWrite
return response await response.finish()
except HttpWriteError as exc:
return defaultResponse(exc)
response
of "/test/long_chunked_response": of "/test/long_chunked_response":
var response = request.getResponse() var response = request.getResponse()
var data = createBigMessage(ResponseTests[3][4], ResponseTests[3][2]) var data = createBigMessage(ResponseTests[3][4], ResponseTests[3][2])
response.status = Http200 response.status = Http200
await response.prepare() try:
var offset = 0 await response.prepare()
while true: var offset = 0
if len(data) == offset: while true:
break if len(data) == offset:
let toWrite = min(1024, len(data) - offset) break
await response.sendChunk(addr data[offset], toWrite) let toWrite = min(1024, len(data) - offset)
offset = offset + toWrite await response.sendChunk(addr data[offset], toWrite)
await response.finish() offset = offset + toWrite
return response await response.finish()
except HttpWriteError as exc:
return defaultResponse(exc)
response
else: else:
return await request.respond(Http404, "Page not found") defaultResponse()
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -311,21 +330,26 @@ suite "HTTP client testing suite":
(MethodPost, "/test/big_request", 262400) (MethodPost, "/test/big_request", 262400)
] ]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path case request.uri.path
of "/test/big_request": of "/test/big_request":
if request.hasBody(): try:
let body = await request.getBody() if request.hasBody():
let digest = $secureHash(string.fromBytes(body)) let body = await request.getBody()
return await request.respond(Http200, digest) let digest = $secureHash(string.fromBytes(body))
else: await request.respond(Http200, digest)
return await request.respond(Http400, "Missing content body") else:
await request.respond(Http400, "Missing content body")
except HttpProtocolError as exc:
defaultResponse(exc)
except HttpTransportError as exc:
defaultResponse(exc)
else: else:
return await request.respond(Http404, "Page not found") defaultResponse()
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -381,21 +405,27 @@ suite "HTTP client testing suite":
(MethodPost, "/test/big_chunk_request", 262400) (MethodPost, "/test/big_chunk_request", 262400)
] ]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path case request.uri.path
of "/test/big_chunk_request": of "/test/big_chunk_request":
if request.hasBody(): try:
let body = await request.getBody() if request.hasBody():
let digest = $secureHash(string.fromBytes(body)) let
return await request.respond(Http200, digest) body = await request.getBody()
else: digest = $secureHash(string.fromBytes(body))
return await request.respond(Http400, "Missing content body") await request.respond(Http200, digest)
else:
await request.respond(Http400, "Missing content body")
except HttpProtocolError as exc:
defaultResponse(exc)
except HttpTransportError as exc:
defaultResponse(exc)
else: else:
return await request.respond(Http404, "Page not found") defaultResponse()
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -455,23 +485,28 @@ suite "HTTP client testing suite":
] ]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path case request.uri.path
of "/test/post/urlencoded_size", "/test/post/urlencoded_chunked": of "/test/post/urlencoded_size", "/test/post/urlencoded_chunked":
if request.hasBody(): try:
var postTable = await request.post() if request.hasBody():
let body = postTable.getString("field1") & ":" & var postTable = await request.post()
postTable.getString("field2") & ":" & let body = postTable.getString("field1") & ":" &
postTable.getString("field3") postTable.getString("field2") & ":" &
return await request.respond(Http200, body) postTable.getString("field3")
else: await request.respond(Http200, body)
return await request.respond(Http400, "Missing content body") else:
await request.respond(Http400, "Missing content body")
except HttpTransportError as exc:
defaultResponse(exc)
except HttpProtocolError as exc:
defaultResponse(exc)
else: else:
return await request.respond(Http404, "Page not found") defaultResponse()
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -554,23 +589,28 @@ suite "HTTP client testing suite":
] ]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path case request.uri.path
of "/test/post/multipart_size", "/test/post/multipart_chunked": of "/test/post/multipart_size", "/test/post/multipart_chunked":
if request.hasBody(): try:
var postTable = await request.post() if request.hasBody():
let body = postTable.getString("field1") & ":" & var postTable = await request.post()
postTable.getString("field2") & ":" & let body = postTable.getString("field1") & ":" &
postTable.getString("field3") postTable.getString("field2") & ":" &
return await request.respond(Http200, body) postTable.getString("field3")
else: await request.respond(Http200, body)
return await request.respond(Http400, "Missing content body") else:
await request.respond(Http400, "Missing content body")
except HttpProtocolError as exc:
defaultResponse(exc)
except HttpTransportError as exc:
defaultResponse(exc)
else: else:
return await request.respond(Http404, "Page not found") defaultResponse()
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -649,26 +689,29 @@ suite "HTTP client testing suite":
var lastAddress: Uri var lastAddress: Uri
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path try:
of "/": case request.uri.path
return await request.redirect(Http302, "/redirect/1") of "/":
of "/redirect/1": await request.redirect(Http302, "/redirect/1")
return await request.redirect(Http302, "/next/redirect/2") of "/redirect/1":
of "/next/redirect/2": await request.redirect(Http302, "/next/redirect/2")
return await request.redirect(Http302, "redirect/3") of "/next/redirect/2":
of "/next/redirect/redirect/3": await request.redirect(Http302, "redirect/3")
return await request.redirect(Http302, "next/redirect/4") of "/next/redirect/redirect/3":
of "/next/redirect/redirect/next/redirect/4": await request.redirect(Http302, "next/redirect/4")
return await request.redirect(Http302, lastAddress) of "/next/redirect/redirect/next/redirect/4":
of "/final/5": await request.redirect(Http302, lastAddress)
return await request.respond(Http200, "ok-5") of "/final/5":
else: await request.respond(Http200, "ok-5")
return await request.respond(Http404, "Page not found") else:
await request.respond(Http404, "Page not found")
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -706,8 +749,8 @@ suite "HTTP client testing suite":
proc testSendCancelLeaksTest(secure: bool): Future[bool] {.async.} = proc testSendCancelLeaksTest(secure: bool): Future[bool] {.async.} =
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -756,8 +799,8 @@ suite "HTTP client testing suite":
proc testOpenCancelLeaksTest(secure: bool): Future[bool] {.async.} = proc testOpenCancelLeaksTest(secure: bool): Future[bool] {.async.} =
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()
@ -868,20 +911,23 @@ suite "HTTP client testing suite":
(data2.status, data2.data.bytesToString(), count)] (data2.status, data2.data.bytesToString(), count)]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path try:
of "/keep": case request.uri.path
let headers = HttpTable.init([("connection", "keep-alive")]) of "/keep":
return await request.respond(Http200, "ok", headers = headers) let headers = HttpTable.init([("connection", "keep-alive")])
of "/drop": await request.respond(Http200, "ok", headers = headers)
let headers = HttpTable.init([("connection", "close")]) of "/drop":
return await request.respond(Http200, "ok", headers = headers) let headers = HttpTable.init([("connection", "close")])
else: await request.respond(Http200, "ok", headers = headers)
return await request.respond(Http404, "Page not found") else:
await request.respond(Http404, "Page not found")
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, false) var server = createServer(initTAddress("127.0.0.1:0"), process, false)
server.start() server.start()
@ -1004,16 +1050,19 @@ suite "HTTP client testing suite":
return (data.status, data.data.bytesToString(), 0) return (data.status, data.data.bytesToString(), 0)
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path try:
of "/test": case request.uri.path
return await request.respond(Http200, "ok") of "/test":
else: await request.respond(Http200, "ok")
return await request.respond(Http404, "Page not found") else:
await request.respond(Http404, "Page not found")
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, false) var server = createServer(initTAddress("127.0.0.1:0"), process, false)
server.start() server.start()
@ -1064,19 +1113,22 @@ suite "HTTP client testing suite":
return (data.status, data.data.bytesToString(), 0) return (data.status, data.data.bytesToString(), 0)
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
case request.uri.path try:
of "/test": case request.uri.path
return await request.respond(Http200, "ok") of "/test":
of "/keep-test": await request.respond(Http200, "ok")
let headers = HttpTable.init([("Connection", "keep-alive")]) of "/keep-test":
return await request.respond(Http200, "not-alive", headers) let headers = HttpTable.init([("Connection", "keep-alive")])
else: await request.respond(Http200, "not-alive", headers)
return await request.respond(Http404, "Page not found") else:
await request.respond(Http404, "Page not found")
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, false) var server = createServer(initTAddress("127.0.0.1:0"), process, false)
server.start() server.start()
@ -1180,58 +1232,61 @@ suite "HTTP client testing suite":
true true
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
if request.uri.path.startsWith("/test/single/"): try:
let index = if request.uri.path.startsWith("/test/single/"):
block: let index =
var res = -1 block:
for index, value in SingleGoodTests.pairs(): var res = -1
if value[0] == request.uri.path: for index, value in SingleGoodTests.pairs():
res = index if value[0] == request.uri.path:
break res = index
res break
if index < 0: res
return await request.respond(Http404, "Page not found") if index < 0:
var response = request.getResponse() return await request.respond(Http404, "Page not found")
response.status = Http200 var response = request.getResponse()
await response.sendBody(SingleGoodTests[index][1]) response.status = Http200
return response await response.sendBody(SingleGoodTests[index][1])
elif request.uri.path.startsWith("/test/multiple/"): response
let index = elif request.uri.path.startsWith("/test/multiple/"):
block: let index =
var res = -1 block:
for index, value in MultipleGoodTests.pairs(): var res = -1
if value[0] == request.uri.path: for index, value in MultipleGoodTests.pairs():
res = index if value[0] == request.uri.path:
break res = index
res break
if index < 0: res
return await request.respond(Http404, "Page not found") if index < 0:
var response = request.getResponse() return await request.respond(Http404, "Page not found")
response.status = Http200 var response = request.getResponse()
await response.sendBody(MultipleGoodTests[index][1]) response.status = Http200
return response await response.sendBody(MultipleGoodTests[index][1])
elif request.uri.path.startsWith("/test/overflow/"): response
let index = elif request.uri.path.startsWith("/test/overflow/"):
block: let index =
var res = -1 block:
for index, value in OverflowTests.pairs(): var res = -1
if value[0] == request.uri.path: for index, value in OverflowTests.pairs():
res = index if value[0] == request.uri.path:
break res = index
res break
if index < 0: res
return await request.respond(Http404, "Page not found") if index < 0:
var response = request.getResponse() return await request.respond(Http404, "Page not found")
response.status = Http200 var response = request.getResponse()
await response.sendBody(OverflowTests[index][1]) response.status = Http200
return response await response.sendBody(OverflowTests[index][1])
else: response
return await request.respond(Http404, "Page not found") else:
defaultResponse()
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
var server = createServer(initTAddress("127.0.0.1:0"), process, secure) var server = createServer(initTAddress("127.0.0.1:0"), process, secure)
server.start() server.start()

View File

@ -64,7 +64,7 @@ suite "HTTP server testing suite":
proc testTooBigBodyChunked(operation: TooBigTest): Future[bool] {.async.} = proc testTooBigBodyChunked(operation: TooBigTest): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
try: try:
@ -77,13 +77,15 @@ suite "HTTP server testing suite":
let ptable {.used.} = await request.post() let ptable {.used.} = await request.post()
of PostMultipartTest: of PostMultipartTest:
let ptable {.used.} = await request.post() let ptable {.used.} = await request.post()
except HttpCriticalError as exc: defaultResponse()
except HttpTransportError as exc:
defaultResponse(exc)
except HttpProtocolError as exc:
if exc.code == Http413: if exc.code == Http413:
serverRes = true serverRes = true
# Reraising exception, because processor should properly handle it. defaultResponse(exc)
raise exc
else: else:
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -128,14 +130,17 @@ suite "HTTP server testing suite":
proc testTimeout(): Future[bool] {.async.} = proc testTimeout(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init()) try:
await request.respond(Http200, "TEST_OK", HttpTable.init())
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
if r.error.kind == HttpServerError.TimeoutError: if r.error.kind == HttpServerError.TimeoutError:
serverRes = true serverRes = true
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), let res = HttpServerRef.new(initTAddress("127.0.0.1:0"),
@ -158,14 +163,17 @@ suite "HTTP server testing suite":
proc testEmpty(): Future[bool] {.async.} = proc testEmpty(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init()) try:
await request.respond(Http200, "TEST_OK", HttpTable.init())
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
if r.error.kind == HttpServerError.CriticalError: if r.error.kind == HttpServerError.ProtocolError:
serverRes = true serverRes = true
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), let res = HttpServerRef.new(initTAddress("127.0.0.1:0"),
@ -188,14 +196,17 @@ suite "HTTP server testing suite":
proc testTooBig(): Future[bool] {.async.} = proc testTooBig(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init()) try:
await request.respond(Http200, "TEST_OK", HttpTable.init())
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
if r.error.error == HttpServerError.CriticalError: if r.error.error == HttpServerError.ProtocolError:
serverRes = true serverRes = true
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -219,13 +230,11 @@ suite "HTTP server testing suite":
proc testTooBigBody(): Future[bool] {.async.} = proc testTooBigBody(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isErr():
discard if r.error.error == HttpServerError.ProtocolError:
else:
if r.error.error == HttpServerError.CriticalError:
serverRes = true serverRes = true
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -266,7 +275,7 @@ suite "HTTP server testing suite":
proc testQuery(): Future[bool] {.async.} = proc testQuery(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
var kres = newSeq[string]() var kres = newSeq[string]()
@ -274,11 +283,14 @@ suite "HTTP server testing suite":
kres.add(k & ":" & v) kres.add(k & ":" & v)
sort(kres) sort(kres)
serverRes = true serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"), try:
HttpTable.init()) await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false defaultResponse()
return defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -296,10 +308,9 @@ suite "HTTP server testing suite":
"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.closeWait() await server.closeWait()
let r = serverRes and 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)
return r
check waitFor(testQuery()) == true check waitFor(testQuery()) == true
@ -307,7 +318,7 @@ suite "HTTP server testing suite":
proc testHeaders(): Future[bool] {.async.} = proc testHeaders(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
var kres = newSeq[string]() var kres = newSeq[string]()
@ -315,11 +326,14 @@ suite "HTTP server testing suite":
kres.add(k & ":" & v) kres.add(k & ":" & v)
sort(kres) sort(kres)
serverRes = true serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"), try:
HttpTable.init()) await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false defaultResponse()
return defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -351,21 +365,30 @@ suite "HTTP server testing suite":
proc testPostUrl(): Future[bool] {.async.} = proc testPostUrl(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
var kres = newSeq[string]() var kres = newSeq[string]()
let request = r.get() let request = r.get()
if request.meth in PostMethods: if request.meth in PostMethods:
let post = await request.post() let post =
try:
await request.post()
except HttpProtocolError as exc:
return defaultResponse(exc)
except HttpTransportError as exc:
return defaultResponse(exc)
for k, v in post.stringItems(): for k, v in post.stringItems():
kres.add(k & ":" & v) kres.add(k & ":" & v)
sort(kres) sort(kres)
serverRes = true serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"), try:
HttpTable.init()) await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false defaultResponse()
return defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -395,21 +418,30 @@ suite "HTTP server testing suite":
proc testPostUrl2(): Future[bool] {.async.} = proc testPostUrl2(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
var kres = newSeq[string]() var kres = newSeq[string]()
let request = r.get() let request = r.get()
if request.meth in PostMethods: if request.meth in PostMethods:
let post = await request.post() let post =
try:
await request.post()
except HttpProtocolError as exc:
return defaultResponse(exc)
except HttpTransportError as exc:
return defaultResponse(exc)
for k, v in post.stringItems(): for k, v in post.stringItems():
kres.add(k & ":" & v) kres.add(k & ":" & v)
sort(kres) sort(kres)
serverRes = true serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"), try:
HttpTable.init()) await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false defaultResponse()
return defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -440,21 +472,30 @@ suite "HTTP server testing suite":
proc testPostMultipart(): Future[bool] {.async.} = proc testPostMultipart(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
var kres = newSeq[string]() var kres = newSeq[string]()
let request = r.get() let request = r.get()
if request.meth in PostMethods: if request.meth in PostMethods:
let post = await request.post() let post =
try:
await request.post()
except HttpProtocolError as exc:
return defaultResponse(exc)
except HttpTransportError as exc:
return defaultResponse(exc)
for k, v in post.stringItems(): for k, v in post.stringItems():
kres.add(k & ":" & v) kres.add(k & ":" & v)
sort(kres) sort(kres)
serverRes = true serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"), try:
HttpTable.init()) await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false defaultResponse()
return defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -496,21 +537,31 @@ suite "HTTP server testing suite":
proc testPostMultipart2(): Future[bool] {.async.} = proc testPostMultipart2(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
var kres = newSeq[string]() var kres = newSeq[string]()
let request = r.get() let request = r.get()
if request.meth in PostMethods: if request.meth in PostMethods:
let post = await request.post() let post =
try:
await request.post()
except HttpProtocolError as exc:
return defaultResponse(exc)
except HttpTransportError as exc:
return defaultResponse(exc)
for k, v in post.stringItems(): for k, v in post.stringItems():
kres.add(k & ":" & v) kres.add(k & ":" & v)
sort(kres) sort(kres)
serverRes = true serverRes = true
return await request.respond(Http200, "TEST_OK:" & kres.join(":"), try:
HttpTable.init()) await request.respond(Http200, "TEST_OK:" & kres.join(":"),
HttpTable.init())
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false serverRes = false
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -566,16 +617,19 @@ suite "HTTP server testing suite":
var count = 0 var count = 0
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
inc(count) inc(count)
if count == ClientsCount: if count == ClientsCount:
eventWait.fire() eventWait.fire()
await eventContinue.wait() await eventContinue.wait()
return await request.respond(Http404, "", HttpTable.init()) try:
await request.respond(Http404, "", HttpTable.init())
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -1230,23 +1284,26 @@ suite "HTTP server testing suite":
proc testPostMultipart2(): Future[bool] {.async.} = proc testPostMultipart2(): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
let response = request.getResponse() let response = request.getResponse()
await response.prepareSSE() try:
await response.send("event: event1\r\ndata: data1\r\n\r\n") await response.prepareSSE()
await response.send("event: event2\r\ndata: data2\r\n\r\n") await response.send("event: event1\r\ndata: data1\r\n\r\n")
await response.sendEvent("event3", "data3") await response.send("event: event2\r\ndata: data2\r\n\r\n")
await response.sendEvent("event4", "data4") await response.sendEvent("event3", "data3")
await response.send("data: data5\r\n\r\n") await response.sendEvent("event4", "data4")
await response.sendEvent("", "data6") await response.send("data: data5\r\n\r\n")
await response.finish() await response.sendEvent("", "data6")
serverRes = true await response.finish()
return response serverRes = true
response
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false defaultResponse()
return defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process, let res = HttpServerRef.new(initTAddress("127.0.0.1:0"), process,
@ -1306,12 +1363,15 @@ suite "HTTP server testing suite":
] ]
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init()) try:
await request.respond(Http200, "TEST_OK", HttpTable.init())
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
for test in TestMessages: for test in TestMessages:
let let
@ -1360,12 +1420,15 @@ suite "HTTP server testing suite":
TestRequest = "GET /httpdebug HTTP/1.1\r\nConnection: keep-alive\r\n\r\n" TestRequest = "GET /httpdebug HTTP/1.1\r\nConnection: keep-alive\r\n\r\n"
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
return await request.respond(Http200, "TEST_OK", HttpTable.init()) try:
await request.respond(Http200, "TEST_OK", HttpTable.init())
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
return defaultResponse() defaultResponse()
proc client(address: TransportAddress, proc client(address: TransportAddress,
data: string): Future[StreamTransport] {.async.} = data: string): Future[StreamTransport] {.async.} =

View File

@ -108,15 +108,18 @@ suite "Secure HTTP server testing suite":
proc testHTTPS(address: TransportAddress): Future[bool] {.async.} = proc testHTTPS(address: TransportAddress): Future[bool] {.async.} =
var serverRes = false var serverRes = false
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
serverRes = true serverRes = true
return await request.respond(Http200, "TEST_OK:" & $request.meth, try:
HttpTable.init()) await request.respond(Http200, "TEST_OK:" & $request.meth,
HttpTable.init())
except HttpWriteError as exc:
serverRes = false
defaultResponse(exc)
else: else:
serverRes = false defaultResponse()
return defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let serverFlags = {Secure} let serverFlags = {Secure}
@ -146,16 +149,18 @@ suite "Secure HTTP server testing suite":
var serverRes = false var serverRes = false
var testFut = newFuture[void]() var testFut = newFuture[void]()
proc process(r: RequestFence): Future[HttpResponseRef] {. proc process(r: RequestFence): Future[HttpResponseRef] {.
async: (raises: [CancelledError, HttpResponseError]).} = async: (raises: [CancelledError]).} =
if r.isOk(): if r.isOk():
let request = r.get() let request = r.get()
serverRes = false try:
return await request.respond(Http200, "TEST_OK:" & $request.meth, await request.respond(Http200, "TEST_OK:" & $request.meth,
HttpTable.init()) HttpTable.init())
except HttpWriteError as exc:
defaultResponse(exc)
else: else:
serverRes = true serverRes = true
testFut.complete() testFut.complete()
return defaultResponse() defaultResponse()
let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr} let socketFlags = {ServerFlags.TcpNoDelay, ServerFlags.ReuseAddr}
let serverFlags = {Secure} let serverFlags = {Secure}