This module contains a definition of HTTP header fields. A distinct table, HeaderFields, which represents the set of header fields of a message.
Overview
HTTP header fields are components of the header section of request and response messages. They define the operating parameters of an HTTP transaction.
Header field names are case-insensitive. Most HTTP header field values are defined using common syntax components (token, quoted-string, and comment) separated by whitespace or specific delimiting characters.
Formatting rules
The format of the value of each header field varies widely. There are five different formatting rules:
1. Represented as a single line, a single value, no-parameters
Content-Length: 0
2. Represented as a single line, a single value, optional parameters separated by ';'
Content-Type: application/json
or:
Content-Type: application/json; charset=utf8
3. Represented as a single line, multiple values separated by ';', no-parameters
Cookie: SID=123abc; language=en
4. Represented as a single line or multiple lines, multiple values separated by ',', each value has optional parameters separated by ';'
Accept: text/html; q=1; level=1, text/plain
Multiple lines:
Accept: text/html; q=1; level=1 Accept: text/plain
5. Set-Cookie is a special case, represented as multiple lines, each line is a value that separated by ';', no-parameters
Set-Cookie: SID=123abc; path=/ Set-Cookie: language=en; path=/
To simplify these complex representations, this module provides two special tools, parseSingleRule and parseMultiRule, which combine the above 5 rules into 2 rules: single-line-rule (SLR) and multiple-lines-rule (MLR).
Usage
Access fields
import netkit/http/headerfield let fields = initHeaderFields({ "Host": @["www.iocrate.com"], "Content-Length": @["16"], "Content-Type": @["application/json; charset=utf8"], "Cookie": @["SID=123; language=en"], "Accept": @["text/html; q=1; level=1", "text/plain"] }) fields.add("Connection", "keep-alive") fields.add("Accept", "text/*") assert fields["Content-Length"][0] = "16" assert fields["content-length"][0] = "16" assert fields["Accept"][0] = "text/html; q=1; level=1" assert fields["Accept"][1] = "text/plain" assert fields["Accept"][2] = "text/*"
Access values by SLR
Uses parseSingleRule to parse the header fields, which follows the 1,2,3 rules listed above, and returns a set of (key, value) pairs.
1. Represented as a single line, a single value, no-parameters
let fields = initHeaderFields({ "Content-Length": @["0"] }) let values = fields.parseSingleRule("Content-Length") assert values[0].key = "0"
The returned result should have at most one item, and the key of the first item indicates the value of this header field, if any.
Note: When using this proc, you must ensure that the values is represented as a single line. If the values is represented as multiple-lines like Accept, there may lose values. If more than one value is found, an exception will be raised.
2. Represented as a single line, a single value, optional parameters separated by ';'
let fields = initHeaderFields({ "Content-Type": @["application/json; charset=utf8"] }) let values = fields.parseSingleRule("Content-Type") assert values[0].key = "application/json" assert values[1].key = "charset" assert values[1].value = "utf8"
If the returned result is not empty, the key of the first item indicates the value of this header field, and the other items indicates the parameters of this value.
Note: When using this proc, you must ensure that the values is represented as a single line. If the values is represented as multiple-lines like Accept, there may lose values. If more than one value is found, an exception will be raised.
3. Represented as a single line, multiple values separated by ';', no-parameters
let fields = initHeaderFields({ "Cookie": @["SID=123abc; language=en"] }) let values = fields.parseSingleRule("Cookie") assert values[0].key = "SID" assert values[0].value = "123abc" assert values[1].key = "language" assert values[1].value = "en"
If the returned result is not empty, then each item indicates a value, that mean a (key, value) pair.
Note: When using this proc, you must ensure that the values is represented as a single line. If the values is represented as multiple-lines like Accept, there may lose values. If more than one value is found, an exception will be raised.
Access values by MLR
Uses parseMultiRule to parse the header fields, which follows the 4,5 rules listed above, and returns a set of seq[(key, value)].
4. Represented as a single line or multiple lines, multiple values separated by ',', each value has optional parameters separated by ';'
let fields = initHeaderFields({ "Accept": @["text/html; q=1; level=1, text/plain"] }) let values = fields.parseMultiRule("Accept") assert values[0][0].key = "text/html" assert values[0][1].key = "q" assert values[0][1].value = "1" assert values[0][2].key = "level" assert values[0][2].value = "1" assert values[1][0].key = "text/plain"
the same below:
let fields = initHeaderFields({ "Accept": @["text/html; q=1; level=1", "text/plain"] }) let values = fields.parseMultiRule("Accept")
If the returned result is not empty, then each item indicates a value. The key of the first item of each seq indicates the value itself, and the other items indicate parameters of that value.
Note: When using this proc, you must ensure that the values is represented as multiple lines. If the values is represented as a single-line field like Date, you may get wrong results. Because Date takes ',' as part of its value, for example, Date: Thu, 23 Apr 2020 07:41:15 GMT.
5. Set-Cookie is a special case, represented as multiple lines, each line is a value that separated by ';', no-parameters
let fields = initHeaderFields({ "Set-Cookie": @["SID=123abc; path=/", "language=en; path=/"] }) let values = fields.parseMultiRule("Content-Type") assert values[0][0].key = "SID" assert values[0][0].value = "123abc" assert values[0][1].key = "path" assert values[0][1].value = "/" assert values[1][0].key = "language" assert values[1][0].value = "en" assert values[1][1].key = "path" assert values[1][1].value = "/"
If the returned result is not empty, then each item indicates a value.
Note: When using this proc, you must ensure that the values is represented as multiple lines. If the values is represented as a single-line field like Date, you may get wrong results. Because Date takes ',' as part of its value, for example, Date: Thu, 23 Apr 2020 07:41:15 GMT.
Types
HeaderFields = distinct Table[string, seq[string]]
- Represents the header fields of a HTTP message. Source Edit
Procs
proc initHeaderFields(): HeaderFields {...}{.raises: [], tags: [].}
- Initializes a HeaderFields. Source Edit
proc initHeaderFields(pairs: openArray[tuple[name: string, value: seq[string]]]): HeaderFields {...}{. raises: [KeyError], tags: [].}
-
Initializes a HeaderFields. pairs is a container consisting of (key, value) tuples.
The following example demonstrates how to deal with a single value, such as Content-Length:
let fields = initHeaderFields({ "Content-Length": @["1"], "Content-Type": @["text/plain"] "Cookie": @["SID=123; language=en"] })
The following example demonstrates how to deal with Set-Cookie or a comma-separated list of values such as Accept:
Source Editlet fields = initHeaderFields({ "Set-Cookie": @["SID=123; path=/", "language=en"], "Accept": @["audio/\*; q=0.2", "audio/basic"] })
proc initHeaderFields(pairs: openArray[tuple[name: string, value: string]]): HeaderFields {...}{. raises: [KeyError], tags: [].}
-
Initializes a HeaderFields. pairs is a container consisting of (key, value) tuples.
The following example demonstrates how to deal with a single value, such as Content-Length:
let fields = initHeaderFields({ "Content-Length": "16", "Content-Type": "text/plain" "Cookie": "SID=123; language=en" })
Source Edit proc clear(fields: var HeaderFields) {...}{.raises: [], tags: [].}
- Resets this fields so that it is empty. Source Edit
proc `[]`(fields: HeaderFields; name: string): seq[string] {...}{.raises: [KeyError], tags: [].}
-
Returns the value of the field associated with name. If name is not in this fields, the KeyError exception is raised.
Examples:
let fields = initHeaderFields({ "Content-Length": "16" }) assert fields["Content-Length"][0] == "16"
Source Edit proc `[]=`(fields: var HeaderFields; name: string; value: seq[string]) {...}{.raises: [], tags: [].}
-
Sets value to the field associated with name. Replaces any existing value.
Examples:
let fields = initHeaderFields({ "Content-Length": "16" }) fields["Content-Length"] == @["100"]
Source Edit proc add(fields: var HeaderFields; name: string; value: string) {...}{.raises: [KeyError], tags: [].}
-
Adds value to the field associated with name. If name does not exist then create a new one.
Examples:
let fields = initHeaderFields() fields.add("Content-Length", "16") fields.add("Cookie", "SID=123") fields.add("Cookie", "language=en") fields.add("Accept", "audio/\*; q=0.2") fields.add("Accept", "audio/basic")
Source Edit proc del(fields: var HeaderFields; name: string) {...}{.raises: [], tags: [].}
-
Deletes the field associated with name.
Examples:
fields.del("Content-Length") fields.del("Cookie") fields.del("Accept")
Source Edit proc hasKey(fields: HeaderFields; name: string): bool {...}{.raises: [], tags: [].}
-
Returns true if this fields contains the specified name.
Examples:
let fields = initHeaderFields({ "Content-Length": "16" }) assert fields.hasKey("Content-Length") == true assert fields.hasKey("content-length") == true assert fields.hasKey("ContentLength") == false
Source Edit proc contains(fields: HeaderFields; name: string): bool {...}{.raises: [], tags: [].}
-
Returns true if this fields contains the specified name. Alias of hasKey for use with the in operator.
Examples:
let fields = initHeaderFields({ "Content-Length": "16" }) assert fields.contains("Content-Length") == true assert fields.contains("content-length") == true assert fields.contains("ContentLength") == false assert "content-length" in fields
Source Edit proc len(fields: HeaderFields): int {...}{.raises: [], tags: [].}
- Returns the number of names in this fields. Source Edit
proc getOrDefault(fields: HeaderFields; name: string; default = @[""]): seq[string] {...}{. raises: [KeyError], tags: [].}
- Returns the value of the field associated with name. If name is not in this fields, then default is returned. Source Edit
proc `$`(fields: HeaderFields): string {...}{.raises: [], tags: [].}
- Converts this fields to a string that follows the HTTP Protocol. Source Edit
proc parseSingleRule(fields: HeaderFields; name: string): seq[ tuple[key: string, value: string]] {...}{.raises: [ValueError], tags: [].}
- Parses the field value that matches single-line-rule. Source Edit
proc parseMultiRule(fields: HeaderFields; name: string; default = ""): seq[ seq[tuple[key: string, value: string]]] {...}{.raises: [KeyError], tags: [].}
- Parses the field value that matches multiple-lines-rule. Source Edit
Iterators
iterator pairs(fields: HeaderFields): tuple[name, value: string] {...}{.raises: [], tags: [].}
- Yields each (name, value) pair. Source Edit
iterator names(fields: HeaderFields): string {...}{.raises: [], tags: [].}
- Yields each field name. Source Edit