diff --git a/chronos.nimble b/chronos.nimble index 7f6f838..9266fce 100644 --- a/chronos.nimble +++ b/chronos.nimble @@ -1,5 +1,5 @@ packageName = "chronos" -version = "3.0.5" +version = "3.0.6" author = "Status Research & Development GmbH" description = "Chronos" license = "Apache License 2.0 or MIT" diff --git a/chronos/apps/http/httpclient.nim b/chronos/apps/http/httpclient.nim index efca2cd..a391dba 100644 --- a/chronos/apps/http/httpclient.nim +++ b/chronos/apps/http/httpclient.nim @@ -856,7 +856,7 @@ proc prepareRequest(request: HttpClientRequestRef): string {. else: request.headers.add(ConnectionHeader, "keep-alive") # 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, # and `Authorization` header is not present in request's headers. diff --git a/chronos/apps/http/httpcommon.nim b/chronos/apps/http/httpcommon.nim index 5692b0f..530b507 100644 --- a/chronos/apps/http/httpcommon.nim +++ b/chronos/apps/http/httpcommon.nim @@ -22,7 +22,7 @@ const DateHeader* = "date" HostHeader* = "host" ConnectionHeader* = "connection" - AcceptHeader* = "accept" + AcceptHeaderName* = "accept" ContentLengthHeader* = "content-length" TransferEncodingHeader* = "transfer-encoding" ContentEncodingHeader* = "content-encoding" diff --git a/chronos/apps/http/httpserver.nim b/chronos/apps/http/httpserver.nim index 47ac280..d116a56 100644 --- a/chronos/apps/http/httpserver.nim +++ b/chronos/apps/http/httpserver.nim @@ -435,6 +435,82 @@ proc consumeBody*(request: HttpRequestRef): Future[void] {.async.} = finally: 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, code: HttpCode, keepAlive = true, datatype = "text/text", diff --git a/tests/testhttpserver.nim b/tests/testhttpserver.nim index de7ea4e..c246783 100644 --- a/tests/testhttpserver.nim +++ b/tests/testhttpserver.nim @@ -855,6 +855,58 @@ suite "HTTP server testing suite": table.add(key, value) 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": check: getTracker("async.stream.reader").isLeaked() == false