Annotate with `raises:[Defect]`.

This commit is contained in:
cheatfate 2021-02-10 15:13:36 +02:00 committed by zah
parent 45cb009be2
commit b47fcb3e86
4 changed files with 122 additions and 72 deletions

View File

@ -54,28 +54,34 @@ proc closeWait*(bstream: HttpBodyReader) {.async.} =
await allFutures(res)
await procCall(AsyncStreamReader(bstream).closeWait())
proc atBound*(bstream: HttpBodyReader): bool =
proc atBound*(bstream: HttpBodyReader): bool {.
raises: [Defect].} =
## 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 {.
raises: [HttpDefect].} =
newException(HttpDefect, msg)
proc newHttpCriticalError*(msg: string, code = Http400): ref HttpCriticalError =
proc newHttpCriticalError*(msg: string,
code = Http400): ref HttpCriticalError {.
raises: [HttpCriticalError].} =
var tre = newException(HttpCriticalError, msg)
tre.code = code
tre
proc newHttpRecoverableError*(msg: string,
code = Http400): ref HttpRecoverableError =
code = Http400): ref HttpRecoverableError {.
raises: [HttpRecoverableError].} =
var tre = newException(HttpRecoverableError, msg)
tre.code = code
tre
iterator queryParams*(query: string): tuple[key: string, value: string] =
iterator queryParams*(query: string): tuple[key: string, value: string] {.
raises: [Defect].} =
## Iterate over url-encoded query string.
for pair in query.split('&'):
let items = pair.split('=', maxsplit = 1)
@ -85,7 +91,8 @@ iterator queryParams*(query: string): tuple[key: string, value: string] =
yield (decodeUrl(k), decodeUrl(v))
func getTransferEncoding*(ch: openarray[string]): HttpResult[
set[TransferEncodingFlags]] =
set[TransferEncodingFlags]] {.
raises: [Defect].} =
## Parse value of multiple HTTP headers ``Transfer-Encoding`` and return
## it as set of ``TransferEncodingFlags``.
var res: set[TransferEncodingFlags] = {}
@ -106,6 +113,8 @@ func getTransferEncoding*(ch: openarray[string]): HttpResult[
res.incl(TransferEncodingFlags.Deflate)
of "gzip":
res.incl(TransferEncodingFlags.Gzip)
of "x-gzip":
res.incl(TransferEncodingFlags.Gzip)
of "":
res.incl(TransferEncodingFlags.Identity)
else:
@ -113,7 +122,8 @@ func getTransferEncoding*(ch: openarray[string]): HttpResult[
ok(res)
func getContentEncoding*(ch: openarray[string]): HttpResult[
set[ContentEncodingFlags]] =
set[ContentEncodingFlags]] {.
raises: [Defect].} =
## Parse value of multiple HTTP headers ``Content-Encoding`` and return
## it as set of ``ContentEncodingFlags``.
var res: set[ContentEncodingFlags] = {}
@ -134,13 +144,16 @@ func getContentEncoding*(ch: openarray[string]): HttpResult[
res.incl(ContentEncodingFlags.Deflate)
of "gzip":
res.incl(ContentEncodingFlags.Gzip)
of "x-gzip":
res.incl(ContentEncodingFlags.Gzip)
of "":
res.incl(ContentEncodingFlags.Identity)
else:
return err("Incorrect Content-Encoding value")
ok(res)
func getContentType*(ch: openarray[string]): HttpResult[string] =
func getContentType*(ch: openarray[string]): HttpResult[string] {.
raises: [Defect].} =
## Check and prepare value of ``Content-Type`` header.
if len(ch) > 1:
err("Multiple Content-Type values found")

View File

@ -110,7 +110,7 @@ type
proc init(htype: typedesc[HttpProcessError], error: HTTPServerError,
exc: ref CatchableError, remote: TransportAddress,
code: HttpCode): HttpProcessError =
code: HttpCode): HttpProcessError {.raises: [Defect].} =
HttpProcessError(error: error, exc: exc, remote: remote, code: code)
proc new*(htype: typedesc[HttpServerRef],
@ -147,13 +147,16 @@ proc new*(htype: typedesc[HttpServerRef],
)
res.baseUri =
if len(serverUri.hostname) > 0 and isAbsolute(serverUri):
serverUri
else:
if HttpServerFlags.Secure in serverFlags:
parseUri("https://" & $address & "/")
try:
if len(serverUri.hostname) > 0 and isAbsolute(serverUri):
serverUri
else:
parseUri("http://" & $address & "/")
if HttpServerFlags.Secure in serverFlags:
parseUri("https://" & $address & "/")
else:
parseUri("http://" & $address & "/")
except TransportAddressError as exc:
return err(exc.msg)
try:
res.instance = createStreamServer(address, flags = socketFlags,
@ -169,7 +172,7 @@ proc new*(htype: typedesc[HttpServerRef],
except CatchableError as exc:
return err(exc.msg)
proc getResponse*(req: HttpRequestRef): HttpResponseRef =
proc getResponse*(req: HttpRequestRef): HttpResponseRef {.raises: [Defect].} =
if req.response.isNone():
var resp = HttpResponseRef(
status: Http200,
@ -184,7 +187,7 @@ proc getResponse*(req: HttpRequestRef): HttpResponseRef =
else:
req.response.get()
proc dumbResponse*(): HttpResponseRef =
proc dumbResponse*(): HttpResponseRef {.raises: [Defect].} =
## Create an empty response to return when request processor got no request.
HttpResponseRef(state: HttpResponseState.Dumb, version: HttpVersion11)
@ -192,13 +195,14 @@ proc getId(transp: StreamTransport): string {.inline.} =
## Returns string unique transport's identifier as string.
$transp.remoteAddress() & "_" & $transp.localAddress()
proc hasBody*(request: HttpRequestRef): bool =
proc hasBody*(request: HttpRequestRef): bool {.raises: [Defect].} =
## Returns ``true`` if request has body.
request.requestFlags * {HttpRequestFlags.BoundBody,
HttpRequestFlags.UnboundBody} != {}
proc prepareRequest(conn: HttpConnectionRef,
req: HttpRequestHeader): HttpResultCode[HttpRequestRef] =
req: HttpRequestHeader): HttpResultCode[HttpRequestRef] {.
raises: [Defect].}=
var request = HttpRequestRef(connection: conn)
if req.version notin {HttpVersion10, HttpVersion11}:
@ -659,7 +663,7 @@ proc acceptClientLoop(server: HttpServerRef) {.async.} =
if breakLoop:
break
proc state*(server: HttpServerRef): HttpServerState =
proc state*(server: HttpServerRef): HttpServerState {.raises: [Defect].} =
## Returns current HTTP server's state.
if server.lifetime.finished():
ServerClosed
@ -815,22 +819,24 @@ proc `keepalive=`*(resp: HttpResponseRef, value: bool) =
else:
resp.flags.excl(KeepAlive)
proc keepalive*(resp: HttpResponseRef): bool =
proc keepalive*(resp: HttpResponseRef): bool {.raises: [Defect].} =
KeepAlive in resp.flags
proc setHeader*(resp: HttpResponseRef, key, value: string) =
proc setHeader*(resp: HttpResponseRef, key, value: string) {.
raises: [Defect].} =
doAssert(resp.state == HttpResponseState.Empty)
resp.headersTable.set(key, value)
proc addHeader*(resp: HttpResponseRef, key, value: string) =
proc addHeader*(resp: HttpResponseRef, key, value: string) {.
raises: [Defect].}=
doAssert(resp.state == HttpResponseState.Empty)
resp.headersTable.add(key, value)
proc getHeader*(resp: HttpResponseRef, key: string,
default: string = ""): string =
default: string = ""): string {.raises: [Defect].} =
resp.headersTable.getString(key, default)
proc hasHeader*(resp: HttpResponseRef, key: string): bool =
proc hasHeader*(resp: HttpResponseRef, key: string): bool {.raises: [Defect].} =
key in resp.headersTable
template doHeaderDef(buf, resp, name, default) =
@ -849,7 +855,8 @@ template checkPending(t: untyped) =
if t.state != HttpResponseState.Empty:
raise newHttpCriticalError("Response body was already sent")
proc prepareLengthHeaders(resp: HttpResponseRef, length: int): string =
proc prepareLengthHeaders(resp: HttpResponseRef, length: int): string {.
raises: [Defect].}=
var answer = $(resp.version) & " " & $(resp.status) & "\r\n"
answer.doHeaderDef(resp, "Date", httpDate())
answer.doHeaderDef(resp, "Content-Type", "text/html; charset=utf-8")
@ -866,7 +873,8 @@ proc prepareLengthHeaders(resp: HttpResponseRef, length: int): string =
answer.add("\r\n")
answer
proc prepareChunkedHeaders(resp: HttpResponseRef): string =
proc prepareChunkedHeaders(resp: HttpResponseRef): string {.
raises: [Defect].} =
var answer = $(resp.version) & " " & $(resp.status) & "\r\n"
answer.doHeaderDef(resp, "Date", httpDate())
answer.doHeaderDef(resp, "Content-Type", "text/html; charset=utf-8")
@ -1024,7 +1032,8 @@ proc respond*(req: HttpRequestRef, code: HttpCode, content: string,
await response.sendBody(content)
return response
proc requestInfo*(req: HttpRequestRef, contentType = "text/text"): string =
proc requestInfo*(req: HttpRequestRef, contentType = "text/text"): string {.
raises: [Defect].} =
## Returns comprehensive information about request for specific content
## type.
##
@ -1100,8 +1109,19 @@ proc requestInfo*(req: HttpRequestRef, contentType = "text/text"): string =
res.add(kv(k, v))
res.add(h("Connection information"))
res.add(kv("local.address", $req.connection.transp.localAddress()))
res.add(kv("remote.address", $req.connection.transp.remoteAddress()))
let localAddress =
try:
$req.connection.transp.localAddress()
except TransportError:
"incorrect address"
let remoteAddress =
try:
$req.connection.transp.remoteAddress()
except TransportError:
"incorrect address"
res.add(kv("local.address", localAddress))
res.add(kv("remote.address", remoteAddress))
res.add(h("Server configuration"))
let maxConn =

View File

@ -9,6 +9,8 @@
# MIT license (LICENSE-MIT)
import std/[tables, strutils]
{.push raises: [Defect].}
type
HttpTable* = object
table: Table[string, seq[string]]
@ -50,10 +52,11 @@ proc bytesToDec*[T: byte|char](src: openarray[T]): uint64 =
v
proc add*(ht: var HttpTables, key: string, value: string) =
var default: seq[string]
let lowkey = key.toLowerAscii()
var nitem = @[value]
if ht.table.hasKeyOrPut(lowkey, nitem):
var oitem = ht.table[lowkey]
var oitem = ht.table.getOrDefault(lowkey, default)
oitem.add(value)
ht.table[lowkey] = oitem
@ -64,7 +67,8 @@ proc set*(ht: var HttpTables, key: string, value: string) =
let lowkey = key.toLowerAscii()
ht.table[lowkey] = @[value]
proc contains*(ht: var HttpTables, key: string): bool =
proc contains*(ht: var HttpTables, key: string): bool {.
raises: [Defect].} =
ht.table.contains(key.toLowerAscii())
proc getList*(ht: HttpTables, key: string,
@ -147,24 +151,24 @@ proc normalizeHeaderName*(value: string): string =
res
iterator stringItems*(ht: HttpTables,
normalizeKey = false): tuple[key: string, value: string] =
normKey = false): tuple[key: string, value: string] =
## Iterate over HttpTable values.
##
## If ``normalizeKey`` is true, key name value will be normalized using
## If ``normKey`` is true, key name value will be normalized using
## normalizeHeaderName() procedure.
for k, v in ht.table.pairs():
let key = if normalizeKey: normalizeHeaderName(k) else: k
let key = if normKey: normalizeHeaderName(k) else: k
for item in v:
yield (key, item)
iterator items*(ht: HttpTables,
normalizeKey = false): tuple[key: string, value: seq[string]] =
normKey = false): tuple[key: string, value: seq[string]] =
## Iterate over HttpTable values.
##
## If ``normalizeKey`` is true, key name value will be normalized using
## If ``normKey`` is true, key name value will be normalized using
## normalizeHeaderName() procedure.
for k, v in ht.table.pairs():
let key = if normalizeKey: normalizeHeaderName(k) else: k
let key = if normKey: normalizeHeaderName(k) else: k
yield (key, v)
proc `$`*(ht: HttpTables): string =

View File

@ -50,14 +50,16 @@ type
BChar* = byte | char
proc startsWith*(s, prefix: openarray[byte]): bool =
proc startsWith(s, prefix: openarray[byte]): bool {.
raises: [Defect].} =
var i = 0
while true:
if i >= len(prefix): return true
if i >= len(s) or s[i] != prefix[i]: return false
inc(i)
proc parseUntil*(s, until: openarray[byte]): int =
proc parseUntil(s, until: openarray[byte]): int {.
raises: [Defect].} =
var i = 0
while i < len(s):
if len(until) > 0 and s[i] == until[0]:
@ -68,9 +70,33 @@ proc parseUntil*(s, until: openarray[byte]): int =
inc(i)
-1
func setPartNames(part: var MultiPart): HttpResult[void] {.
raises: [Defect].} =
if part.headers.count("content-disposition") != 1:
return err("Content-Disposition header is incorrect")
var header = part.headers.getString("content-disposition")
let disp = parseDisposition(header, false)
if disp.failed():
return err("Content-Disposition header value is incorrect")
let dtype = disp.dispositionType(header.toOpenArrayByte(0, len(header) - 1))
if dtype.toLowerAscii() != "form-data":
return err("Content-Disposition type is incorrect")
for k, v in disp.fields(header.toOpenArrayByte(0, len(header) - 1)):
case k.toLowerAscii()
of "name":
part.name = v
of "filename":
part.filename = v
else:
discard
if len(part.name) == 0:
part.name = $part.counter
ok()
proc init*[A: BChar, B: BChar](mpt: typedesc[MultiPartReader],
buffer: openarray[A],
boundary: openarray[B]): MultiPartReader =
boundary: openarray[B]): MultiPartReader {.
raises: [Defect].} =
## Create new MultiPartReader instance with `buffer` interface.
##
## ``buffer`` - is buffer which will be used to read data.
@ -94,7 +120,8 @@ proc init*[A: BChar, B: BChar](mpt: typedesc[MultiPartReader],
proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef],
stream: HttpBodyReader,
boundary: openarray[B],
partHeadersMaxSize = 4096): MultiPartReaderRef =
partHeadersMaxSize = 4096): MultiPartReaderRef {.
raises: [Defect].} =
## Create new MultiPartReader instance with `stream` interface.
##
## ``stream`` is stream used to read data.
@ -113,28 +140,6 @@ proc new*[B: BChar](mpt: typedesc[MultiPartReaderRef],
stream: stream, offset: 0, boundary: fboundary,
buffer: newSeq[byte](partHeadersMaxSize))
func setPartNames(part: var MultiPart): HttpResult[void] =
if part.headers.count("content-disposition") != 1:
return err("Content-Disposition header is incorrect")
var header = part.headers.getString("content-disposition")
let disp = parseDisposition(header, false)
if disp.failed():
return err("Content-Disposition header value is incorrect")
let dtype = disp.dispositionType(header.toOpenArrayByte(0, len(header) - 1))
if dtype.toLowerAscii() != "form-data":
return err("Content-Disposition type is incorrect")
for k, v in disp.fields(header.toOpenArrayByte(0, len(header) - 1)):
case k.toLowerAscii()
of "name":
part.name = v
of "filename":
part.filename = v
else:
discard
if len(part.name) == 0:
part.name = $part.counter
ok()
proc readPart*(mpr: MultiPartReaderRef): Future[MultiPart] {.async.} =
doAssert(mpr.kind == MultiPartSource.Stream)
if mpr.firstTime:
@ -235,7 +240,8 @@ proc consumeBody*(mp: MultiPart) {.async.} =
of MultiPartSource.Buffer:
discard
proc getBodyStream*(mp: MultiPart): HttpResult[AsyncStreamReader] =
proc getBodyStream*(mp: MultiPart): HttpResult[AsyncStreamReader] {.
raises: [Defect].} =
## Get multipart's ``mp`` stream, which can be used to obtain value of the
## part.
case mp.kind
@ -260,7 +266,8 @@ proc closeWait*(mpr: MultiPartReaderRef) {.async.} =
else:
discard
proc getBytes*(mp: MultiPart): seq[byte] =
proc getBytes*(mp: MultiPart): seq[byte] {.
raises: [Defect].} =
## Returns value for MultiPart ``mp`` as sequence of bytes.
case mp.kind
of MultiPartSource.Buffer:
@ -269,7 +276,8 @@ proc getBytes*(mp: MultiPart): seq[byte] =
doAssert(not(mp.stream.atEof()), "Value is not obtained yet")
mp.buffer
proc getString*(mp: MultiPart): string =
proc getString*(mp: MultiPart): string {.
raises: [Defect].} =
## Returns value for MultiPart ``mp`` as string.
case mp.kind
of MultiPartSource.Buffer:
@ -288,7 +296,8 @@ proc getString*(mp: MultiPart): string =
else:
""
proc atEoM*(mpr: var MultiPartReader): bool =
proc atEoM*(mpr: var MultiPartReader): bool {.
raises: [Defect].} =
## Procedure returns ``true`` if MultiPartReader has reached the end of
## multipart message.
case mpr.kind
@ -297,7 +306,8 @@ proc atEoM*(mpr: var MultiPartReader): bool =
of MultiPartSource.Stream:
mpr.stream.atEof()
proc atEoM*(mpr: MultiPartReaderRef): bool =
proc atEoM*(mpr: MultiPartReaderRef): bool {.
raises: [Defect].} =
## Procedure returns ``true`` if MultiPartReader has reached the end of
## multipart message.
case mpr.kind
@ -306,7 +316,8 @@ proc atEoM*(mpr: MultiPartReaderRef): bool =
of MultiPartSource.Stream:
mpr.stream.atEof()
proc getPart*(mpr: var MultiPartReader): Result[MultiPart, string] =
proc getPart*(mpr: var MultiPartReader): Result[MultiPart, string] {.
raises: [Defect].} =
## Get multipart part from MultiPartReader instance.
##
## This procedure will work only for MultiPartReader with buffer source.
@ -396,11 +407,13 @@ proc getPart*(mpr: var MultiPartReader): Result[MultiPart, string] =
else:
err("Incorrect multipart form")
func isEmpty*(mp: MultiPart): bool =
func isEmpty*(mp: MultiPart): bool {.
raises: [Defect].} =
## Returns ``true`` is multipart ``mp`` is not initialized/filled yet.
mp.counter == 0
func getMultipartBoundary*(ch: openarray[string]): HttpResult[string] =
func getMultipartBoundary*(ch: openarray[string]): HttpResult[string] {.
raises: [Defect].} =
## Returns ``multipart/form-data`` boundary value from ``Content-Type``
## header.
##