From b47fcb3e864c032f18b314fdc3dde1fce5341b5a Mon Sep 17 00:00:00 2001 From: cheatfate Date: Wed, 10 Feb 2021 15:13:36 +0200 Subject: [PATCH] Annotate with `raises:[Defect]`. --- chronos/apps/http/httpcommon.nim | 29 ++++++++---- chronos/apps/http/httpserver.nim | 64 ++++++++++++++++--------- chronos/apps/http/httptable.nim | 20 ++++---- chronos/apps/http/multipart.nim | 81 ++++++++++++++++++-------------- 4 files changed, 122 insertions(+), 72 deletions(-) diff --git a/chronos/apps/http/httpcommon.nim b/chronos/apps/http/httpcommon.nim index ebac36f..3718b10 100644 --- a/chronos/apps/http/httpcommon.nim +++ b/chronos/apps/http/httpcommon.nim @@ -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") diff --git a/chronos/apps/http/httpserver.nim b/chronos/apps/http/httpserver.nim index cd92045..3adda89 100644 --- a/chronos/apps/http/httpserver.nim +++ b/chronos/apps/http/httpserver.nim @@ -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 = diff --git a/chronos/apps/http/httptable.nim b/chronos/apps/http/httptable.nim index a1b518a..3dede5a 100644 --- a/chronos/apps/http/httptable.nim +++ b/chronos/apps/http/httptable.nim @@ -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 = diff --git a/chronos/apps/http/multipart.nim b/chronos/apps/http/multipart.nim index 0181f74..2e99c27 100644 --- a/chronos/apps/http/multipart.nim +++ b/chronos/apps/http/multipart.nim @@ -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. ##