nim-chronos/chronos/apps/http/httptable.nim

200 lines
6.2 KiB
Nim

#
# Chronos HTTP/S case-insensitive non-unique
# key-value memory storage
# (c) Copyright 2021-Present
# Status Research & Development GmbH
#
# Licensed under either of
# Apache License, version 2.0, (LICENSE-APACHEv2)
# MIT license (LICENSE-MIT)
import std/[tables, strutils]
import stew/base10
{.push raises: [Defect].}
type
HttpTable* = object
table: Table[string, seq[string]]
HttpTableRef* = ref HttpTable
HttpTables* = HttpTable | HttpTableRef
proc add*(ht: var HttpTables, key: string, value: string) =
## Add string ``value`` to header with key ``key``.
var default: seq[string]
ht.table.mgetOrPut(key.toLowerAscii(), default).add(value)
proc add*(ht: var HttpTables, key: string, value: SomeInteger) =
## Add integer ``value`` to header with key ``key``.
ht.add(key, $value)
proc set*(ht: var HttpTables, key: string, value: string) =
## Set/replace value of header with key ``key`` to value ``value``.
let lowkey = key.toLowerAscii()
ht.table[lowkey] = @[value]
proc hasKeyOrPut*(ht: var HttpTables, key: string, value: string): bool =
## Returns true if ``key`` is in the table ``ht``,
## otherwise inserts ``value``.
ht.table.hasKeyOrPut(key, @[value])
proc contains*(ht: HttpTables, key: string): bool =
## Returns ``true`` if header with name ``key`` is present in HttpTable/Ref.
ht.table.contains(key.toLowerAscii())
proc getList*(ht: HttpTables, key: string,
default: openarray[string] = []): seq[string] =
## Returns sequence of headers with key ``key``.
var defseq = @default
ht.table.getOrDefault(key.toLowerAscii(), defseq)
proc getString*(ht: HttpTables, key: string,
default: string = ""): string =
## Returns concatenated value of headers with key ``key``.
##
## If there multiple headers with the same name ``key`` the result value will
## be concatenation using `,`.
var defseq: seq[string]
let res = ht.table.getOrDefault(key.toLowerAscii(), defseq)
if len(res) == 0:
return default
else:
res.join(",")
proc count*(ht: HttpTables, key: string): int =
## Returns number of headers with key ``key``.
var default: seq[string]
len(ht.table.getOrDefault(key.toLowerAscii(), default))
proc getInt*(ht: HttpTables, key: string): uint64 =
## Parse header with key ``key`` as unsigned integer.
##
## Integers are parsed in safe way, there no exceptions or errors will be
## raised.
##
## Procedure returns `0` value in next cases:
## 1. The value is empty.
## 2. Non-decimal character encountered during the parsing of the value.
## 3. Result exceeds `uint64` maximum allowed value.
let res = Base10.decode(uint64, ht.getString(key))
if res.isOk():
res.get()
else:
0'u64
proc getLastString*(ht: HttpTables, key: string): string =
## Returns "last" value of header ``key``.
##
## If there multiple headers with the same name ``key`` the value of last
## encountered header will be returned.
var default: seq[string]
let item = ht.table.getOrDefault(key.toLowerAscii(), default)
if len(item) == 0:
""
else:
item[^1]
proc getLastInt*(ht: HttpTables, key: string): uint64 =
## Returns "last" value of header ``key`` as unsigned integer.
##
## If there multiple headers with the same name ``key`` the value of last
## encountered header will be returned.
##
## Unsigned integer will be parsed using rules of getInt() procedure.
let res = Base10.decode(uint64, ht.getLastString(key))
if res.isOk():
res.get()
else:
0'u64
proc init*(htt: typedesc[HttpTable]): HttpTable =
## Create empty HttpTable.
HttpTable(table: initTable[string, seq[string]]())
proc new*(htt: typedesc[HttpTableRef]): HttpTableRef =
## Create empty HttpTableRef.
HttpTableRef(table: initTable[string, seq[string]]())
proc init*(htt: typedesc[HttpTable],
data: openArray[tuple[key: string, value: string]]): HttpTable =
## Create HttpTable using array of tuples with header names and values.
var res = HttpTable.init()
for item in data:
res.add(item.key, item.value)
res
proc new*(htt: typedesc[HttpTableRef],
data: openArray[tuple[key: string, value: string]]): HttpTableRef =
## Create HttpTableRef using array of tuples with header names and values.
var res = HttpTableRef.new()
for item in data:
res.add(item.key, item.value)
res
proc isEmpty*(ht: HttpTables): bool =
## Returns ``true`` if HttpTable ``ht`` is empty (do not have any values).
len(ht.table) == 0
proc normalizeHeaderName*(value: string): string =
## Set any header name to have first capital letters in their name
##
## For example:
## "content-length" become "<C>ontent-<L>ength"
## "expect" become "<E>xpect"
var res = value.toLowerAscii()
var k = 0
while k < len(res):
if k == 0:
res[k] = toUpperAscii(res[k])
inc(k, 1)
else:
if res[k] == '-':
if k + 1 < len(res):
res[k + 1] = toUpperAscii(res[k + 1])
inc(k, 2)
else:
break
else:
inc(k, 1)
res
iterator stringItems*(ht: HttpTables,
normKey = false): tuple[key: string, value: string] =
## Iterate over HttpTable/Ref values.
##
## If ``normKey`` is true, key name value will be normalized using
## normalizeHeaderName() procedure.
for k, v in ht.table.pairs():
let key = if normKey: normalizeHeaderName(k) else: k
for item in v:
yield (key, item)
iterator items*(ht: HttpTables,
normKey = false): tuple[key: string, value: seq[string]] =
## Iterate over HttpTable/Ref values.
##
## If ``normKey`` is true, key name value will be normalized using
## normalizeHeaderName() procedure.
for k, v in ht.table.pairs():
let key = if normKey: normalizeHeaderName(k) else: k
yield (key, v)
proc `$`*(ht: HttpTables): string =
## Returns string representation of HttpTable/Ref.
var res = ""
for key, value in ht.table.pairs():
for item in value:
res.add(key.normalizeHeaderName())
res.add(": ")
res.add(item)
res.add("\p")
res
proc toList*(ht: HttpTables, normKey = false): auto =
## Returns sequence of (key, value) pairs.
var res: seq[tuple[key: string, value: string]]
for key, value in ht.stringItems(normKey):
res.add((key, value))
res