Add `Accept` header handling to httpserver.nim. (#211)

* Add `Accept` header handling to httpserver.nim.
Add simple test suite.
Bump version to 3.0.6.

* Fix compilation error.
This commit is contained in:
Eugene Kabanov 2021-07-28 17:08:38 +03:00 committed by GitHub
parent 3a9cc6bfc9
commit ef2430d08d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 3 deletions

View File

@ -1,5 +1,5 @@
packageName = "chronos" packageName = "chronos"
version = "3.0.5" version = "3.0.6"
author = "Status Research & Development GmbH" author = "Status Research & Development GmbH"
description = "Chronos" description = "Chronos"
license = "Apache License 2.0 or MIT" license = "Apache License 2.0 or MIT"

View File

@ -856,7 +856,7 @@ proc prepareRequest(request: HttpClientRequestRef): string {.
else: else:
request.headers.add(ConnectionHeader, "keep-alive") request.headers.add(ConnectionHeader, "keep-alive")
# We set `Accept` to accept any content if its not set. # We set `Accept` to accept any content if its not set.
discard request.headers.hasKeyOrPut(AcceptHeader, "*/*") discard request.headers.hasKeyOrPut(AcceptHeaderName, "*/*")
# We will send `Authorization` information only if username or password set, # We will send `Authorization` information only if username or password set,
# and `Authorization` header is not present in request's headers. # and `Authorization` header is not present in request's headers.

View File

@ -22,7 +22,7 @@ const
DateHeader* = "date" DateHeader* = "date"
HostHeader* = "host" HostHeader* = "host"
ConnectionHeader* = "connection" ConnectionHeader* = "connection"
AcceptHeader* = "accept" AcceptHeaderName* = "accept"
ContentLengthHeader* = "content-length" ContentLengthHeader* = "content-length"
TransferEncodingHeader* = "transfer-encoding" TransferEncodingHeader* = "transfer-encoding"
ContentEncodingHeader* = "content-encoding" ContentEncodingHeader* = "content-encoding"

View File

@ -435,6 +435,82 @@ proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} =
finally: finally:
await closeWait(res.get()) await closeWait(res.get())
proc getAcceptInfo*(request: HttpRequestRef): Result[AcceptInfo, cstring] =
## Returns value of `Accept` header as `AcceptInfo` object.
##
## If ``Accept`` header is missing in request headers, ``*/*`` content
## type will be returned.
let acceptHeader = request.headers.getString(AcceptHeaderName)
getAcceptInfo(acceptHeader)
proc preferredContentMediaType*(request: HttpRequestRef): MediaType =
## Returns preferred content-type using ``Accept`` header specified by
## client in request ``request``.
let acceptHeader = request.headers.getString(AcceptHeaderName)
let res = getAcceptInfo(acceptHeader)
if res.isErr():
# If `Accept` header is incorrect, client accepts any type of content.
MediaType.init("*", "*")
else:
let mediaTypes = res.get().data
if len(mediaTypes) > 0:
mediaTypes[0].mediaType
else:
MediaType.init("*", "*")
proc preferredContentType*(request: HttpRequestRef,
types: varargs[string]): Result[string, cstring] =
## Match or obtain preferred content-type using ``Accept`` header specified by
## client in request ``request``.
##
## If ``Accept`` header is missing in client's request - ``types[0]`` or
## ``*/*`` value will be returned as result.
##
## If ``Accept`` header has incorrect format in client's request -
## ``types[0]`` or ``*/*`` value will be returned as result.
##
## If ``Accept`` header is present and has one or more content types supported
## by client, the best value will be selected from ``types`` using
## quality value (weight) reported in ``Accept`` header. If client do not
## support any methods in ``types`` error will be returned.
let acceptHeader = request.headers.getString(AcceptHeaderName)
if len(types) == 0:
if len(acceptHeader) == 0:
# If `Accept` header is missing, return `*/*`.
ok("*/*")
else:
let res = getAcceptInfo(acceptHeader)
if res.isErr():
# If `Accept` header is incorrect, client accepts any type of content.
ok("*/*")
else:
let mediaTypes = res.get().data
if len(mediaTypes) > 0:
ok($mediaTypes[0].mediaType)
else:
ok("*/*")
else:
if len(acceptHeader) == 0:
# If `Accept` header is missing, client accepts any type of content.
ok(types[0])
else:
let res = getAcceptInfo(acceptHeader)
if res.isErr():
# If `Accept` header is incorrect, client accepts any type of content.
ok(types[0])
else:
let mediaTypes =
block:
var res: seq[MediaType]
for item in types:
res.add(MediaType.init(item))
res
for item in res.get().data:
for expect in mediaTypes:
if expect == item.mediaType:
return ok($expect)
err("Preferred content type not found")
proc sendErrorResponse(conn: HttpConnectionRef, version: HttpVersion, proc sendErrorResponse(conn: HttpConnectionRef, version: HttpVersion,
code: HttpCode, keepAlive = true, code: HttpCode, keepAlive = true,
datatype = "text/text", datatype = "text/text",

View File

@ -855,6 +855,58 @@ suite "HTTP server testing suite":
table.add(key, value) table.add(key, value)
check toString(table) == vector[2] check toString(table) == vector[2]
test "Preferred Accept handling test":
proc createRequest(acceptHeader: string): HttpRequestRef =
let headers = HttpTable.init([("accept", acceptHeader)])
HttpRequestRef(headers: headers)
proc createRequest(): HttpRequestRef =
HttpRequestRef(headers: HttpTable.init())
var requests = @[
(
createRequest("application/json;q=0.9,application/octet-stream"),
@[
"application/octet-stream",
"application/octet-stream",
"application/octet-stream",
"application/json",
]
),
(
createRequest(""),
@[
"*/*",
"*/*",
"application/json",
"application/json"
]
),
(
createRequest(),
@[
"*/*",
"*/*",
"application/json",
"application/json"
]
)
]
for req in requests:
check $req[0].preferredContentMediaType() == req[1][1]
let r0 = req[0].preferredContentType()
let r1 = req[0].preferredContentType("application/json",
"application/octet-stream")
let r2 = req[0].preferredContentType("application/json")
check:
r0.isOk() == true
r1.isOk() == true
r2.isOk() == true
r0.get() == req[1][0]
r1.get() == req[1][2]
r2.get() == req[1][3]
test "Leaks test": test "Leaks test":
check: check:
getTracker("async.stream.reader").isLeaked() == false getTracker("async.stream.reader").isLeaked() == false