netkit/http/headerfield

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

For example:
Content-Length: 0

2. Represented as a single line, a single value, optional parameters separated by ';'

For example:
Content-Type: application/json

or:

Content-Type: application/json; charset=utf8

3. Represented as a single line, multiple values separated by ';', no-parameters

For example:
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 ';'

A single line:
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:

let fields = initHeaderFields({
  "Set-Cookie": @["SID=123; path=/", "language=en"],
  "Accept": @["audio/\*; q=0.2", "audio/basic"]
})

  Source Edit
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