Headers (including cookies)¶
As you might expect, the support for sending and receiving headers is pretty bare!
The ASGI servers send and receive headers as a list of 2-tuples of bytes, e.g.
headers = [
(b'content-type': b'application/json'),
(b'set-cookie', b'first=first cookie')
]
Most templates preprocess these to dict fo lists of strings. However, I found that in many cases I never even need to look at the headers, so it seemed like an unnecessary overhead. The approach taken by this framework is to provide some utility functions.
bareUtils¶
The bareASGI package includes bareUtils
as a dependency. Amongst other things
this provides utilities for headers. These are all pretty basic and I encourage
you to build your own.
The module I use the most is bareUtils.header
.
import bareUtils.header as header
accept = header.find(b'accept', scope['headers'])
content_type = header.find(b'content-type', scope['headers'], b'application/json')
cookies = header.find_all(b'cookie', scope['headers'])
In the above example the accept
header would be searched for, or None if not
present. With the find
function, the first matching entry is returned.
For content-type
a default of b'application/json'
is provided. In
the last example all headers of name cookie
are returned in a list.
As you can see, no attempt is made to process the headers into dictionaries or strings.
Header Helpers¶
There are a number of helper methods written for the more common headers.
For example to retrieve the content-type
the following function can be used.
>>> import bareutils.header as header
>>> header.content_type([(b'content-type', b'text/html; charset=UTF-8')])
(b'text/html', {b'charset': b'UTF-8'})
Cookies¶
The source code for the following example can be found here (and here here with typing).
The key part of the code is where we set the cookies.
async def post_form(request):
content_type = header.find(b'content-type', request.scope['headers'])
if content_type != b'application/x-www-form-urlencoded':
return 500
variables = await text_reader(content)
values = dict(parse_qsl(variables))
first_name = values['first_name']
last_name = values['last_name']
headers = [
(b'location', b'/get_form'),
(b'set-cookie', f'first_name={first_name}'.encode()),
(b'set-cookie', f'last_name={last_name}'.encode()),
]
return HttpResponse(303, headers)
This handler receives the POST
from the form. First it checks that the content
type is appropriate for form data.
Then it reads the body and unpacks it. Note that there is no special support for
unpacking, we simply use the urllib.parse.parse_qsl
function from the standard
library.
Finally it sets the cookies with set-cookie
in the headers. The bareUtils
package provides a utility function encode_set_cookie
which we could have
used, but in this case it was unnecessary.
Here is the prototype for encode_set_cookie
.
def encode_set_cookie(
name: bytes,
value: bytes,
*,
expires: Optional[datetime] = None,
max_age: Optional[Union[int, timedelta]] = None,
path: Optional[bytes] = None,
domain: Optional[bytes] = None,
secure: bool = False,
http_only: bool = False,
same_site: Optional[bool] = None
) -> bytes:
...
There is a complimentary decode function.
def decode_set_cookie(set_cookie: bytes) -> Mapping[str, Any]:
The form handler which presents the form looks like this:
async def get_form(request):
cookies = header.cookie(request.scope['headers'])
first_name = cookies.get(b'first_name', [b'Micky'])[0]
last_name = cookies.get(b'last_name', [b'Mouse'])[0]
html_list = '<dl>'
for name, values in cookies.items():
for value in values:
html_list += f'<dt>{name.decode()}</dt><dd>{value.decode()}</dd>'
html_list += '</dl>'
html = FORM_HTML.format(
first_name=first_name.decode(),
last_name=last_name.decode(),
cookies=html_list
)
headers = [
(b'content-type', b'text/html'),
]
return HttpResponse(200, headers, text_writer(html))
It uses another utility function header.cookies
which simply packs the cookies
into a dict. Note that multiple cookies of the same name may be passed.
What next?¶
Either go back to the table of contents or go to the cors tutorial.