Refactor `preferredContentType` proc & add tests

Fix of the following bug:
In case of multiple accept headers with same preference
`preferredContentType` used to select the first match within content types
provided by the application. For example, if user specifies accept headers
`application/octet-stream, application/json` and application provides
`application/json, application/octet-stream`, `application/octet-stream`
will be returned, and that is a bug.

Now the procedure returns the most suitable match according
to the application preferences.
This commit is contained in:
Emil 2022-01-21 17:16:59 +02:00 committed by zah
parent 519ca463df
commit 1f37dac810
2 changed files with 283 additions and 31 deletions

View File

@ -488,7 +488,7 @@ proc preferredContentMediaType*(acceptHeader: string): MediaType =
MediaType.init("*", "*")
proc preferredContentType*(acceptHeader: string,
types: openArray[string] = []): Result[string, cstring] =
types: varargs[MediaType]): Result[MediaType, cstring] =
## Match or obtain preferred content-type using ``Accept`` header specified by
## string ``acceptHeader``.
##
@ -505,18 +505,18 @@ proc preferredContentType*(acceptHeader: string,
if len(types) == 0:
if len(acceptHeader) == 0:
# If `Accept` header is missing, return `*/*`.
ok("*/*")
ok(wildCardMediaType)
else:
let res = getAcceptInfo(acceptHeader)
if res.isErr():
# If `Accept` header is incorrect, client accepts any type of content.
ok("*/*")
ok(wildCardMediaType)
else:
let mediaTypes = res.get().data
if len(mediaTypes) > 0:
ok($mediaTypes[0].mediaType)
ok(mediaTypes[0].mediaType)
else:
ok("*/*")
ok(wildCardMediaType)
else:
if len(acceptHeader) == 0:
# If `Accept` header is missing, client accepts any type of content.
@ -527,17 +527,7 @@ proc preferredContentType*(acceptHeader: string,
# 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 ares.get().data:
for expect in mediaTypes:
if expect == item.mediaType:
return ok($expect)
err("Preferred content type not found")
selectContentType(ares.get().data, types)
proc preferredContentMediaType*(request: HttpRequestRef): MediaType =
## Returns preferred content-type using ``Accept`` header specified by
@ -545,7 +535,7 @@ proc preferredContentMediaType*(request: HttpRequestRef): MediaType =
preferredContentMediaType(request.headers.getString(AcceptHeaderName))
proc preferredContentType*(request: HttpRequestRef,
types: varargs[string]): Result[string, 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)

View File

@ -856,6 +856,12 @@ suite "HTTP server testing suite":
check toString(table) == vector[2]
test "Preferred Accept handling test":
const
jsonMediaType = MediaType.init("application/json")
sszMediaType = MediaType.init("application/octet-stream")
plainTextMediaType = MediaType.init("text/plain")
imageMediaType = MediaType.init("image/jpg")
proc createRequest(acceptHeader: string): HttpRequestRef =
let headers = HttpTable.init([("accept", acceptHeader)])
HttpRequestRef(headers: headers)
@ -863,49 +869,305 @@ suite "HTTP server testing suite":
proc createRequest(): HttpRequestRef =
HttpRequestRef(headers: HttpTable.init())
var requests = @[
var singleHeader = @[
(
createRequest("application/json;q=0.9,application/octet-stream"),
createRequest("application/json"),
@[
"application/octet-stream",
"application/octet-stream",
"application/json"
]
)
]
var complexHeaders = @[
(
createRequest(),
@[
"*/*",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"image/jpg"
]
),
(
createRequest(""),
@[
"*/*",
"*/*",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"image/jpg"
]
),
(
createRequest("application/json, application/octet-stream"),
@[
"application/json",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"application/json"
]
),
(
createRequest(),
createRequest("application/octet-stream, application/json"),
@[
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"application/json"
]
),
(
createRequest("application/json;q=0.9, application/octet-stream"),
@[
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/octet-stream",
"application/octet-stream",
"application/octet-stream",
"application/octet-stream"
]
),
(
createRequest("application/json, application/octet-stream;q=0.9"),
@[
"application/json",
"application/json",
"application/octet-stream",
"application/json",
"application/json",
"application/json",
"application/json"
]
),
(
createRequest("application/json;q=0.9, application/octet-stream;q=0.8"),
@[
"application/json",
"application/json",
"application/octet-stream",
"application/json",
"application/json",
"application/json",
"application/json"
]
),
(
createRequest("application/json;q=0.8, application/octet-stream;q=0.9"),
@[
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/octet-stream",
"application/octet-stream",
"application/octet-stream",
"application/octet-stream"
]
),
(
createRequest("text/plain, application/octet-stream, application/json"),
@[
"text/plain",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"application/json"
]
),
(
createRequest("text/plain, application/json;q=0.8, application/octet-stream;q=0.8"),
@[
"text/plain",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"text/plain",
"text/plain"
]
),
(
createRequest("text/plain, application/json;q=0.8, application/octet-stream;q=0.5"),
@[
"text/plain",
"application/json",
"application/octet-stream",
"application/json",
"application/json",
"text/plain",
"text/plain"
]
),
(
createRequest("text/plain;q=0.8, application/json, application/octet-stream;q=0.8"),
@[
"application/json",
"application/json",
"application/octet-stream",
"application/json",
"application/json",
"application/json",
"application/json"
]
),
(
createRequest("text/*, application/json;q=0.8, application/octet-stream;q=0.8"),
@[
"text/*",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"text/plain",
"text/plain"
]
),
(
createRequest("text/*, application/json;q=0.8, application/octet-stream;q=0.5"),
@[
"text/*",
"application/json",
"application/octet-stream",
"application/json",
"application/json",
"text/plain",
"text/plain"
]
),
(createRequest("image/jpg, text/plain, application/octet-stream, application/json"),
@[
"image/jpg",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"image/jpg"
]
),
(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",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"image/jpg"
]
),
(
createRequest("*/*, application/json;q=0.8, application/octet-stream;q=0.5"),
@[
"*/*",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"image/jpg"
]
),
(
createRequest("*/*"),
@[
"*/*",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"image/jpg"
]
),
(
createRequest("application/*"),
@[
"application/*",
"application/json",
"application/octet-stream",
"application/json",
"application/octet-stream",
"application/json",
"application/json"
]
)
]
for req in requests:
check $req[0].preferredContentMediaType() == req[1][1]
for req in singleHeader:
check $req[0].preferredContentMediaType() == req[1][0]
let r0 = req[0].preferredContentType()
let r1 = req[0].preferredContentType("application/json",
"application/octet-stream")
let r2 = req[0].preferredContentType("application/json")
let r1 = req[0].preferredContentType(jsonMediaType)
let r2 = req[0].preferredContentType(sszMediaType)
let r3 = req[0].preferredContentType(jsonMediaType,
sszMediaType)
let r4 = req[0].preferredContentType(sszMediaType,
jsonMediaType)
let r5 = req[0].preferredContentType(jsonMediaType,
sszMediaType,
plainTextMediaType)
let r6 = req[0].preferredContentType(imageMediaType,
jsonMediaType,
sszMediaType,
plainTextMediaType)
check:
r0.isOk() == true
r1.isOk() == true
r2.isErr() == true
r3.isOk() == true
r4.isOk() == true
r5.isOk() == true
r6.isOk() == true
r0.get() == MediaType.init(req[1][0])
r1.get() == MediaType.init(req[1][0])
r3.get() == MediaType.init(req[1][0])
r4.get() == MediaType.init(req[1][0])
r5.get() == MediaType.init(req[1][0])
r6.get() == MediaType.init(req[1][0])
for req in complexHeaders:
let r0 = req[0].preferredContentType()
let r1 = req[0].preferredContentType(jsonMediaType)
let r2 = req[0].preferredContentType(sszMediaType)
let r3 = req[0].preferredContentType(jsonMediaType,
sszMediaType)
let r4 = req[0].preferredContentType(sszMediaType,
jsonMediaType)
let r5 = req[0].preferredContentType(jsonMediaType,
sszMediaType,
plainTextMediaType)
let r6 = req[0].preferredContentType(imageMediaType,
jsonMediaType,
sszMediaType,
plainTextMediaType)
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]
r3.isOk() == true
r4.isOk() == true
r5.isOk() == true
r6.isOk() == true
r0.get() == MediaType.init(req[1][0])
r1.get() == MediaType.init(req[1][1])
r2.get() == MediaType.init(req[1][2])
r3.get() == MediaType.init(req[1][3])
r4.get() == MediaType.init(req[1][4])
r5.get() == MediaType.init(req[1][5])
r6.get() == MediaType.init(req[1][6])
test "SSE server-side events stream test":
proc testPostMultipart2(address: TransportAddress): Future[bool] {.async.} =