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:
parent
3a9cc6bfc9
commit
ef2430d08d
|
@ -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"
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue