improvements for httpcore (#12228)

* improvements for httpcore
* further improvements, now stable API but needs extensions later on
This commit is contained in:
Andreas Rumpf 2019-09-23 12:38:35 +02:00 • committed by GitHub
commit b865c2a54b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 46 additions and 44 deletions

View file

@ -83,7 +83,6 @@ type
- concurrency/threadpool - concurrency/threadpool
- coro - coro
- endians - endians
- httpcore
- parsesql - parsesql
- pathnorm - pathnorm
- reservedmem - reservedmem

View file

@ -78,7 +78,7 @@ proc sendHeaders*(req: Request, headers: HttpHeaders): Future[void] =
return req.client.send(msg) return req.client.send(msg)
proc respond*(req: Request, code: HttpCode, content: string, proc respond*(req: Request, code: HttpCode, content: string,
headers: HttpHeaders = nil): Future[void] = headers = EmptyHttpHeaders): Future[void] =
## Responds to the request with the specified ``HttpCode``, headers and ## Responds to the request with the specified ``HttpCode``, headers and
## content. ## content.
## ##
@ -97,7 +97,7 @@ proc respond*(req: Request, code: HttpCode, content: string,
## await req.respond(Http404, "Not Found") ## await req.respond(Http404, "Not Found")
var msg = "HTTP/1.1 " & $code & "\c\L" var msg = "HTTP/1.1 " & $code & "\c\L"
if headers != nil: if isEmpty(headers):
msg.addHeaders(headers) msg.addHeaders(headers)
msg.add("Content-Length: ") msg.add("Content-Length: ")
# this particular way saves allocations: # this particular way saves allocations:

View file

@ -847,7 +847,7 @@ proc newConnection(client: HttpClient | AsyncHttpClient,
proc override(fallback, override: HttpHeaders): HttpHeaders = proc override(fallback, override: HttpHeaders): HttpHeaders =
# Right-biased map union for `HttpHeaders` # Right-biased map union for `HttpHeaders`
if override.isNil: if override.isEmpty:
return fallback return fallback
result = newHttpHeaders() result = newHttpHeaders()
@ -858,7 +858,7 @@ proc override(fallback, override: HttpHeaders): HttpHeaders =
proc requestAux(client: HttpClient | AsyncHttpClient, url: string, proc requestAux(client: HttpClient | AsyncHttpClient, url: string,
httpMethod: string, body = "", httpMethod: string, body = "",
headers: HttpHeaders = nil): Future[Response | AsyncResponse] headers = EmptyHttpHeaders): Future[Response | AsyncResponse]
{.multisync.} = {.multisync.} =
# Helper that actually makes the request. Does not handle redirects. # Helper that actually makes the request. Does not handle redirects.
let requestUrl = parseUri(url) let requestUrl = parseUri(url)
@ -892,7 +892,7 @@ proc requestAux(client: HttpClient | AsyncHttpClient, url: string,
proc request*(client: HttpClient | AsyncHttpClient, url: string, proc request*(client: HttpClient | AsyncHttpClient, url: string,
httpMethod: string, body = "", httpMethod: string, body = "",
headers: HttpHeaders = nil): Future[Response | AsyncResponse] headers = EmptyHttpHeaders): Future[Response | AsyncResponse]
{.multisync.} = {.multisync.} =
## Connects to the hostname specified by the URL and performs a request ## Connects to the hostname specified by the URL and performs a request
## using the custom method string specified by ``httpMethod``. ## using the custom method string specified by ``httpMethod``.
@ -917,7 +917,7 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string,
proc request*(client: HttpClient | AsyncHttpClient, url: string, proc request*(client: HttpClient | AsyncHttpClient, url: string,
httpMethod = HttpGet, body = "", httpMethod = HttpGet, body = "",
headers: HttpHeaders = nil): Future[Response | AsyncResponse] headers = EmptyHttpHeaders): Future[Response | AsyncResponse]
{.multisync.} = {.multisync.} =
## Connects to the hostname specified by the URL and performs a request ## Connects to the hostname specified by the URL and performs a request
## using the method specified. ## using the method specified.

View file

@ -9,14 +9,12 @@
## Contains functionality shared between the ``httpclient`` and ## Contains functionality shared between the ``httpclient`` and
## ``asynchttpserver`` modules. ## ``asynchttpserver`` modules.
##
## Unstable API.
import tables, strutils, parseutils import tables, strutils, parseutils
type type
HttpHeaders* = ref object HeadersImpl = TableRef[string, seq[string]]
table*: TableRef[string, seq[string]] HttpHeaders* = distinct HeadersImpl
HttpHeaderValues* = distinct seq[string] HttpHeaderValues* = distinct seq[string]
@ -97,25 +95,32 @@ const
Http504* = HttpCode(504) Http504* = HttpCode(504)
Http505* = HttpCode(505) Http505* = HttpCode(505)
const headerLimit* = 10_000 const
headerLimit* = 10_000 ## The limit of HTTP headers in bytes. This limit
## is not enforced by httpcore but by modules using
## httpcore.
EmptyHttpHeaders* = HttpHeaders(nil) ## Constant that represents empty
## http headers.
template table*(x: HttpHeaders): TableRef[string, seq[string]] {.
deprecated: "use the other accessor procs instead".} =
TableRef[string, seq[string]](x)
proc newHttpHeaders*(): HttpHeaders = proc newHttpHeaders*(): HttpHeaders =
new result result = HttpHeaders newTable[string, seq[string]]()
result.table = newTable[string, seq[string]]()
proc newHttpHeaders*(keyValuePairs: proc newHttpHeaders*(keyValuePairs:
openArray[tuple[key: string, val: string]]): HttpHeaders = openArray[tuple[key: string, val: string]]): HttpHeaders =
var pairs: seq[tuple[key: string, val: seq[string]]] = @[] result = HttpHeaders newTable[string, seq[string]]()
for pair in keyValuePairs: for pair in keyValuePairs:
pairs.add((pair.key.toLowerAscii(), @[pair.val])) HeadersImpl(result)[pair.key.toLowerAscii()] = @[pair.val]
new result
result.table = newTable[string, seq[string]](pairs)
proc `$`*(headers: HttpHeaders): string = proc `$`*(headers: HttpHeaders): string = $(HeadersImpl(headers))
return $headers.table
proc isEmpty*(a: HttpHeaders): bool = HeadersImpl(a) == nil
proc clear*(headers: HttpHeaders) = proc clear*(headers: HttpHeaders) =
headers.table.clear() HeadersImpl(headers).clear()
proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues = proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
## Returns the values associated with the given ``key``. If the returned ## Returns the values associated with the given ``key``. If the returned
@ -125,43 +130,44 @@ proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
## ##
## To access multiple values of a key, use the overloaded ``[]`` below or ## To access multiple values of a key, use the overloaded ``[]`` below or
## to get all of them access the ``table`` field directly. ## to get all of them access the ``table`` field directly.
return headers.table[key.toLowerAscii].HttpHeaderValues result = HeadersImpl(headers)[key.toLowerAscii].HttpHeaderValues
converter toString*(values: HttpHeaderValues): string = converter toString*(values: HttpHeaderValues): string =
return seq[string](values)[0] result = seq[string](values)[0]
proc `[]`*(headers: HttpHeaders, key: string, i: int): string = proc `[]`*(headers: HttpHeaders, key: string, i: int): string =
## Returns the ``i``'th value associated with the given key. If there are ## Returns the ``i``'th value associated with the given key. If there are
## no values associated with the key or the ``i``'th value doesn't exist, ## no values associated with the key or the ``i``'th value doesn't exist,
## an exception is raised. ## an exception is raised.
return headers.table[key.toLowerAscii][i] result = HeadersImpl(headers)[key.toLowerAscii][i]
proc `[]=`*(headers: HttpHeaders, key, value: string) = proc `[]=`*(headers: HttpHeaders, key, value: string) =
## Sets the header entries associated with ``key`` to the specified value. ## Sets the header entries associated with ``key`` to the specified value.
## Replaces any existing values. ## Replaces any existing values.
headers.table[key.toLowerAscii] = @[value] HeadersImpl(headers)[key.toLowerAscii] = @[value]
proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) = proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) =
## Sets the header entries associated with ``key`` to the specified list of ## Sets the header entries associated with ``key`` to the specified list of
## values. ## values.
## Replaces any existing values. ## Replaces any existing values.
headers.table[key.toLowerAscii] = value HeadersImpl(headers)[key.toLowerAscii] = value
proc add*(headers: HttpHeaders, key, value: string) = proc add*(headers: HttpHeaders, key, value: string) =
## Adds the specified value to the specified key. Appends to any existing ## Adds the specified value to the specified key. Appends to any existing
## values associated with the key. ## values associated with the key.
if not headers.table.hasKey(key.toLowerAscii): let k = key.toLowerAscii
headers.table[key.toLowerAscii] = @[value] if not HeadersImpl(headers).hasKey(k):
HeadersImpl(headers)[k] = @[value]
else: else:
headers.table[key.toLowerAscii].add(value) HeadersImpl(headers)[k].add(value)
proc del*(headers: HttpHeaders, key: string) = proc del*(headers: HttpHeaders, key: string) =
## Delete the header entries associated with ``key`` ## Delete the header entries associated with ``key``
headers.table.del(key.toLowerAscii) HeadersImpl(headers).del(key.toLowerAscii)
iterator pairs*(headers: HttpHeaders): tuple[key, value: string] = iterator pairs*(headers: HttpHeaders): tuple[key, value: string] =
## Yields each key, value pair. ## Yields each key, value pair.
for k, v in headers.table: for k, v in HeadersImpl(headers):
for value in v: for value in v:
yield (k, value) yield (k, value)
@ -172,18 +178,15 @@ proc contains*(values: HttpHeaderValues, value: string): bool =
if val.toLowerAscii == value.toLowerAscii: return true if val.toLowerAscii == value.toLowerAscii: return true
proc hasKey*(headers: HttpHeaders, key: string): bool = proc hasKey*(headers: HttpHeaders, key: string): bool =
return headers.table.hasKey(key.toLowerAscii()) result = HeadersImpl(headers).hasKey(key.toLowerAscii())
proc getOrDefault*(headers: HttpHeaders, key: string, proc getOrDefault*(headers: HttpHeaders, key: string,
default = @[""].HttpHeaderValues): HttpHeaderValues = default = @[""].HttpHeaderValues): HttpHeaderValues =
## Returns the values associated with the given ``key``. If there are no ## Returns the values associated with the given ``key``. If there are no
## values associated with the key, then ``default`` is returned. ## values associated with the key, then ``default`` is returned.
if headers.hasKey(key): result = HttpHeaderValues(HeadersImpl(headers).getOrDefault(key, seq[string](default)))
return headers[key]
else:
return default
proc len*(headers: HttpHeaders): int = return headers.table.len proc len*(headers: HttpHeaders): int = result = HeadersImpl(headers).len
proc parseList(line: string, list: var seq[string], start: int): int = proc parseList(line: string, list: var seq[string], start: int): int =
var i = 0 var i = 0
@ -224,7 +227,7 @@ proc `==`*(protocol: tuple[orig: string, major, minor: int],
result = protocol.major == major and protocol.minor == minor result = protocol.major == major and protocol.minor == minor
proc contains*(methods: set[HttpMethod], x: string): bool = proc contains*(methods: set[HttpMethod], x: string): bool =
return parseEnum[HttpMethod](x) in methods result = parseEnum[HttpMethod](x) in methods
proc `$`*(code: HttpCode): string = proc `$`*(code: HttpCode): string =
## Converts the specified ``HttpCode`` into a HTTP status. ## Converts the specified ``HttpCode`` into a HTTP status.
@ -286,26 +289,26 @@ proc `$`*(code: HttpCode): string =
proc `==`*(a, b: HttpCode): bool {.borrow.} proc `==`*(a, b: HttpCode): bool {.borrow.}
proc `==`*(rawCode: string, code: HttpCode): bool = proc `==`*(rawCode: string, code: HttpCode): bool =
return cmpIgnoreCase(rawCode, $code) == 0 result = cmpIgnoreCase(rawCode, $code) == 0
proc is2xx*(code: HttpCode): bool = proc is2xx*(code: HttpCode): bool =
## Determines whether ``code`` is a 2xx HTTP status code. ## Determines whether ``code`` is a 2xx HTTP status code.
return code.int in {200 .. 299} result = code.int in {200 .. 299}
proc is3xx*(code: HttpCode): bool = proc is3xx*(code: HttpCode): bool =
## Determines whether ``code`` is a 3xx HTTP status code. ## Determines whether ``code`` is a 3xx HTTP status code.
return code.int in {300 .. 399} result = code.int in {300 .. 399}
proc is4xx*(code: HttpCode): bool = proc is4xx*(code: HttpCode): bool =
## Determines whether ``code`` is a 4xx HTTP status code. ## Determines whether ``code`` is a 4xx HTTP status code.
return code.int in {400 .. 499} result = code.int in {400 .. 499}
proc is5xx*(code: HttpCode): bool = proc is5xx*(code: HttpCode): bool =
## Determines whether ``code`` is a 5xx HTTP status code. ## Determines whether ``code`` is a 5xx HTTP status code.
return code.int in {500 .. 599} result = code.int in {500 .. 599}
proc `$`*(httpMethod: HttpMethod): string = proc `$`*(httpMethod: HttpMethod): string =
return (system.`$`(httpMethod))[4 .. ^1].toUpperAscii() result = (system.`$`(httpMethod))[4 .. ^1].toUpperAscii()
when isMainModule: when isMainModule:
var test = newHttpHeaders() var test = newHttpHeaders()