add cookie support

This commit is contained in:
Joe Kaufeld 2024-08-19 11:13:32 -04:00
parent 14e7ad72ee
commit 62f3d650bc
6 changed files with 94 additions and 3 deletions

View File

@ -1,3 +1,5 @@
from datetime import datetime, timedelta
from spiderweb.decorators import csrf_exempt
from spiderweb.main import SpiderwebRouter
from spiderweb.exceptions import ServerError
@ -67,6 +69,21 @@ def form(request):
else:
return TemplateResponse(request, "form.html")
@app.route("/cookies")
def cookies(request):
print("request.COOKIES: ", request.COOKIES)
resp = HttpResponse(body="COOKIES! NOM NOM NOM")
resp.set_cookie(name='nom', value="everyonelovescookies")
resp.set_cookie(name="nom2", value="seriouslycookies")
resp.set_cookie(
name="nom3",
value="yumyum",
partitioned=True,
expires=datetime.utcnow()+timedelta(seconds=10),
max_age=15
)
return resp
if __name__ == "__main__":
# can also add routes like this:

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "spiderweb"
version = "0.8.0"
version = "0.9.0"
description = "A small web framework, just big enough to hold your average spider."
authors = ["Joe Kaufeld <opensource@joekaufeld.com>"]
readme = "README.md"

View File

@ -1,3 +1,6 @@
DEFAULT_ALLOWED_METHODS = ["GET"]
DEFAULT_ENCODING = "ISO-8859-1"
__version__ = "0.8.0"
__version__ = "0.9.0"
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
REGEX_COOKIE_NAME = r'^[a-zA-Z0-9\s\(\)<>@,;:\/\\\[\]\?=\{\}\"\t]*$'

View File

@ -92,7 +92,13 @@ class SpiderwebRouter(
def fire_response(self, start_response, request: Request, resp: HttpResponse):
try:
status = get_http_status_by_code(resp.status_code)
cookies = []
if "Set-Cookie" in resp.headers:
cookies = resp.headers['Set-Cookie']
del resp.headers['Set-Cookie']
headers = list(resp.headers.items())
for c in cookies:
headers.append(("Set-Cookie", c))
start_response(status, headers)

View File

@ -26,9 +26,11 @@ class Request:
self.GET = {}
self.POST = {}
self.META = {}
self.COOKIES = {}
self.populate_headers()
self.populate_meta()
self.populate_cookies()
content_length = int(self.headers.get("CONTENT_LENGTH") or 0)
if content_length:
@ -63,6 +65,10 @@ class Request:
for f in fields:
self.META[f] = self.environ.get(f)
def populate_cookies(self) -> None:
if cookies := self.environ.get("HTTP_COOKIE"):
self.COOKIES = {l.split("=")[0]: l.split("=")[1] for l in cookies.split("; ")}
def json(self):
return json.loads(self.content)

View File

@ -1,10 +1,12 @@
import datetime
import json
import re
from typing import Any
import urllib.parse
import mimetypes
from wsgiref.util import FileWrapper
from spiderweb.constants import DEFAULT_ENCODING
from spiderweb.constants import REGEX_COOKIE_NAME
from spiderweb.exceptions import GeneralException
from spiderweb.request import Request
@ -36,6 +38,63 @@ class HttpResponse:
def __str__(self):
return self.body
def set_cookie(
self,
name: str,
value: str,
domain: str=None,
expires: datetime.datetime = None,
http_only: bool=None,
max_age: int=None,
partitioned: bool=None,
path: str=None,
secure: bool=False,
same_site: str=None
):
if not bool(re.match(REGEX_COOKIE_NAME, name)):
url = "https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes"
raise GeneralException(
f"Cookie name has illegal characters. See {url} for information on"
f" allowed characters."
)
additions = {}
booleans = []
if domain:
additions["Domain"] = domain
if expires:
additions["Expires"] = expires.strftime("%a, %d %b %Y %H:%M:%S GMT")
if max_age:
additions["Max-Age"] = int(max_age)
if path:
additions["Path"] = path
if same_site:
valid_values = ["strict", "lax", "none"]
if same_site.lower() not in valid_values:
raise GeneralException(
f"Invalid value {same_site} for `same_site` cookie attribute. Valid"
f" options are 'strict', 'lax', or 'none'."
)
additions["SameSite"] = same_site.title()
if http_only:
booleans.append("HttpOnly")
if partitioned:
booleans.append("Partitioned")
if secure:
booleans.append("Secure")
attrs = [f"{k}={v}" for k, v in additions.items()]
attrs += booleans
attrs = [urllib.parse.quote_plus(value)] + attrs
cookie = f"{name}={'; '.join(attrs)}"
if "Set-Cookie" in self.headers:
self.headers["Set-Cookie"].append(cookie)
else:
self.headers["Set-Cookie"] = [cookie]
def render(self) -> str:
return str(self.body)