Add optimized implementation for preferredContentType() which avoid sorting and double iteration. (#299)
Fix tests to use 80cpl.
This commit is contained in:
parent
939195626f
commit
15d7e0ebb7
|
@ -471,9 +471,10 @@ proc preferredContentMediaType*(acceptHeader: string): MediaType =
|
|||
MediaType.init("*", "*")
|
||||
|
||||
proc preferredContentType*(acceptHeader: string,
|
||||
types: varargs[MediaType]): Result[MediaType, cstring] =
|
||||
## Match or obtain preferred content-type using ``Accept`` header specified by
|
||||
## string ``acceptHeader``.
|
||||
types: varargs[MediaType]
|
||||
): Result[MediaType, cstring] =
|
||||
## Match or obtain preferred content type using ``Accept`` header specified by
|
||||
## string ``acceptHeader`` and server preferred content types ``types``.
|
||||
##
|
||||
## If ``Accept`` header is missing in client's request - ``types[0]`` or
|
||||
## ``*/*`` value will be returned as result.
|
||||
|
@ -481,10 +482,15 @@ proc preferredContentType*(acceptHeader: string,
|
|||
## 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.
|
||||
## If ``Accept`` header is present in request to server and it has one or more
|
||||
## content types supported by client, the best value will be selected from
|
||||
## ``types`` using position and quality value (weight) reported in ``Accept``
|
||||
## header. If client do not support any methods in ``types`` error
|
||||
## will be returned.
|
||||
##
|
||||
## Note: Quality value (weight) for content type has priority over server's
|
||||
## preferred content-type.
|
||||
doAssert(len(types) < 99, "Maximum number of types is 99")
|
||||
if len(types) == 0:
|
||||
if len(acceptHeader) == 0:
|
||||
# If `Accept` header is missing, return `*/*`.
|
||||
|
@ -496,10 +502,19 @@ proc preferredContentType*(acceptHeader: string,
|
|||
ok(wildCardMediaType)
|
||||
else:
|
||||
let mediaTypes = res.get().data
|
||||
if len(mediaTypes) > 0:
|
||||
ok(mediaTypes[0].mediaType)
|
||||
else:
|
||||
var
|
||||
currentType = MediaType()
|
||||
currentWeight = 0.0
|
||||
# `Accept` header values array is not sorted, so we need to find value
|
||||
# with the biggest ``q-value``.
|
||||
for item in mediaTypes:
|
||||
if currentWeight < item.qvalue:
|
||||
currentType = item.mediaType
|
||||
currentWeight = item.qvalue
|
||||
if len(currentType.media) == 0 and len(currentType.subtype) == 0:
|
||||
ok(wildCardMediaType)
|
||||
else:
|
||||
ok(currentType)
|
||||
else:
|
||||
if len(acceptHeader) == 0:
|
||||
# If `Accept` header is missing, client accepts any type of content.
|
||||
|
@ -510,7 +525,46 @@ proc preferredContentType*(acceptHeader: string,
|
|||
# If `Accept` header is incorrect, client accepts any type of content.
|
||||
ok(types[0])
|
||||
else:
|
||||
selectContentType(ares.get().data, types)
|
||||
# This algorithm exploits ``q-value`` range which is [0.000, 1.000],
|
||||
# by using smaller values for sorting server's preferred content types.
|
||||
# So between 0.000 and 0.001 there present 99 possible values
|
||||
# [0.00001, 0.00099] which can be used.
|
||||
# Server's preferred types marked with `preferred-value` which is
|
||||
# has highest value for first index and the lowest for the last.
|
||||
|
||||
# ``maxWeight`` represents maximum possible weight value which can be
|
||||
# obtained.
|
||||
let maxWeight = 1.0 + float(len(types)) / float(100_000)
|
||||
var
|
||||
currentType = MediaType()
|
||||
currentWeight = 0.0
|
||||
for itemType in ares.get().data:
|
||||
let
|
||||
preferredIndex = types.find(itemType.mediaType)
|
||||
preferredWeight =
|
||||
if preferredIndex == -1:
|
||||
0.0
|
||||
else:
|
||||
# Calculate weight which depends on position in ``types``
|
||||
# array. Because ``preferredIndex`` is always less than
|
||||
# ``len(types)`` this weight could not be ``0.0``.
|
||||
float(len(types) - preferredIndex) / float(100_000)
|
||||
|
||||
if preferredWeight != 0:
|
||||
let weight = itemType.qvalue + preferredWeight
|
||||
if currentWeight < weight:
|
||||
currentType = types[preferredIndex]
|
||||
currentWeight = weight
|
||||
|
||||
if currentWeight == maxWeight:
|
||||
# There is no reason to continue search, because maximum possible
|
||||
# weight is already achieved, so this is the best match.
|
||||
break
|
||||
|
||||
if currentWeight == 0.0:
|
||||
err("Preferred content type not found")
|
||||
else:
|
||||
ok(currentType)
|
||||
|
||||
proc preferredContentMediaType*(request: HttpRequestRef): MediaType =
|
||||
## Returns preferred content-type using ``Accept`` header specified by
|
||||
|
@ -518,7 +572,8 @@ proc preferredContentMediaType*(request: HttpRequestRef): MediaType =
|
|||
preferredContentMediaType(request.headers.getString(AcceptHeaderName))
|
||||
|
||||
proc preferredContentType*(request: HttpRequestRef,
|
||||
types: varargs[MediaType]): Result[MediaType, cstring] =
|
||||
types: varargs[MediaType]
|
||||
): Result[MediaType, cstring] =
|
||||
## Match or obtain preferred content-type using ``Accept`` header specified by
|
||||
## client in request ``request``.
|
||||
preferredContentType(request.headers.getString(AcceptHeaderName), types)
|
||||
|
|
|
@ -866,7 +866,7 @@ suite "HTTP server testing suite":
|
|||
table.add(key, value)
|
||||
check toString(table) == vector[2]
|
||||
|
||||
test "Preferred Accept handling test":
|
||||
test "preferredContentType() test":
|
||||
const
|
||||
jsonMediaType = MediaType.init("application/json")
|
||||
sszMediaType = MediaType.init("application/octet-stream")
|
||||
|
@ -999,7 +999,8 @@ suite "HTTP server testing suite":
|
|||
]
|
||||
),
|
||||
(
|
||||
createRequest("text/plain, application/json;q=0.8, application/octet-stream;q=0.8"),
|
||||
createRequest("text/plain, application/json;q=0.8, " &
|
||||
"application/octet-stream;q=0.8"),
|
||||
@[
|
||||
"text/plain",
|
||||
"application/json",
|
||||
|
@ -1011,7 +1012,8 @@ suite "HTTP server testing suite":
|
|||
]
|
||||
),
|
||||
(
|
||||
createRequest("text/plain, application/json;q=0.8, application/octet-stream;q=0.5"),
|
||||
createRequest("text/plain, application/json;q=0.8, " &
|
||||
"application/octet-stream;q=0.5"),
|
||||
@[
|
||||
"text/plain",
|
||||
"application/json",
|
||||
|
@ -1023,7 +1025,8 @@ suite "HTTP server testing suite":
|
|||
]
|
||||
),
|
||||
(
|
||||
createRequest("text/plain;q=0.8, application/json, application/octet-stream;q=0.8"),
|
||||
createRequest("text/plain;q=0.8, application/json, " &
|
||||
"application/octet-stream;q=0.8"),
|
||||
@[
|
||||
"application/json",
|
||||
"application/json",
|
||||
|
@ -1035,7 +1038,8 @@ suite "HTTP server testing suite":
|
|||
]
|
||||
),
|
||||
(
|
||||
createRequest("text/*, application/json;q=0.8, application/octet-stream;q=0.8"),
|
||||
createRequest("text/*, application/json;q=0.8, " &
|
||||
"application/octet-stream;q=0.8"),
|
||||
@[
|
||||
"text/*",
|
||||
"application/json",
|
||||
|
@ -1047,7 +1051,8 @@ suite "HTTP server testing suite":
|
|||
]
|
||||
),
|
||||
(
|
||||
createRequest("text/*, application/json;q=0.8, application/octet-stream;q=0.5"),
|
||||
createRequest("text/*, application/json;q=0.8, " &
|
||||
"application/octet-stream;q=0.5"),
|
||||
@[
|
||||
"text/*",
|
||||
"application/json",
|
||||
|
@ -1058,7 +1063,8 @@ suite "HTTP server testing suite":
|
|||
"text/plain"
|
||||
]
|
||||
),
|
||||
(createRequest("image/jpg, text/plain, application/octet-stream, application/json"),
|
||||
(createRequest("image/jpg, text/plain, application/octet-stream, " &
|
||||
"application/json"),
|
||||
@[
|
||||
"image/jpg",
|
||||
"application/json",
|
||||
|
@ -1069,7 +1075,9 @@ suite "HTTP server testing suite":
|
|||
"image/jpg"
|
||||
]
|
||||
),
|
||||
(createRequest("image/jpg;q=1, text/plain;q=0.2, application/octet-stream;q=0.2, application/json;q=0.2"),
|
||||
(createRequest("image/jpg;q=1, text/plain;q=0.2, " &
|
||||
"application/octet-stream;q=0.2, " &
|
||||
"application/json;q=0.2"),
|
||||
@[
|
||||
"image/jpg",
|
||||
"application/json",
|
||||
|
@ -1081,7 +1089,8 @@ suite "HTTP server testing suite":
|
|||
]
|
||||
),
|
||||
(
|
||||
createRequest("*/*, application/json;q=0.8, application/octet-stream;q=0.5"),
|
||||
createRequest("*/*, application/json;q=0.8, " &
|
||||
"application/octet-stream;q=0.5"),
|
||||
@[
|
||||
"*/*",
|
||||
"application/json",
|
||||
|
|
Loading…
Reference in New Issue