🐛 fix middleware and add form handling
This commit is contained in:
parent
23360a3d91
commit
7b60e2fd32
14
example.py
14
example.py
@ -6,6 +6,7 @@ from spiderweb.response import HttpResponse, JsonResponse, TemplateResponse, Red
|
|||||||
app = WebServer(
|
app = WebServer(
|
||||||
templates_dirs=["templates"],
|
templates_dirs=["templates"],
|
||||||
middleware=[
|
middleware=[
|
||||||
|
"spiderweb.middleware.csrf.CSRFMiddleware",
|
||||||
"example_middleware.TestMiddleware",
|
"example_middleware.TestMiddleware",
|
||||||
"example_middleware.RedirectMiddleware",
|
"example_middleware.RedirectMiddleware",
|
||||||
"example_middleware.ExplodingMiddleware",
|
"example_middleware.ExplodingMiddleware",
|
||||||
@ -46,6 +47,19 @@ def example(request, id):
|
|||||||
return HttpResponse(body=f"Example with id {id}")
|
return HttpResponse(body=f"Example with id {id}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.error(405)
|
||||||
|
def http405(request) -> HttpResponse:
|
||||||
|
return HttpResponse(body="Method not allowed", status_code=405)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/form", allowed_methods=["POST"])
|
||||||
|
def form(request):
|
||||||
|
if request.method == "POST":
|
||||||
|
return JsonResponse(data=request.POST)
|
||||||
|
else:
|
||||||
|
return TemplateResponse(request, "form.html")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# can also add routes like this:
|
# can also add routes like this:
|
||||||
# app.add_route("/", index)
|
# app.add_route("/", index)
|
||||||
|
@ -5,20 +5,20 @@ from spiderweb.response import HttpResponse, RedirectResponse
|
|||||||
|
|
||||||
|
|
||||||
class TestMiddleware(SpiderwebMiddleware):
|
class TestMiddleware(SpiderwebMiddleware):
|
||||||
def process_request(self, request: Request) -> HttpResponse | None:
|
def process_request(self, request: Request) -> None:
|
||||||
# example of a middleware that sets a flag on the request
|
# example of a middleware that sets a flag on the request
|
||||||
request.spiderweb = True
|
request.spiderweb = True
|
||||||
|
|
||||||
def process_response(
|
def process_response(
|
||||||
self, request: Request, response: HttpResponse
|
self, request: Request, response: HttpResponse
|
||||||
) -> HttpResponse | None:
|
) -> None:
|
||||||
# example of a middleware that sets a header on the resp
|
# example of a middleware that sets a header on the resp
|
||||||
if hasattr(request, "spiderweb"):
|
if hasattr(request, "spiderweb"):
|
||||||
response.headers["X-Spiderweb"] = "true"
|
response.headers["X-Spiderweb"] = "true"
|
||||||
|
|
||||||
|
|
||||||
class RedirectMiddleware(SpiderwebMiddleware):
|
class RedirectMiddleware(SpiderwebMiddleware):
|
||||||
def process_request(self, request: Request) -> HttpResponse | None:
|
def process_request(self, request: Request) -> HttpResponse:
|
||||||
if request.path == "/middleware":
|
if request.path == "/middleware":
|
||||||
return RedirectResponse("/")
|
return RedirectResponse("/")
|
||||||
|
|
||||||
|
143
poetry.lock
generated
143
poetry.lock
generated
@ -1,4 +1,4 @@
|
|||||||
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "black"
|
name = "black"
|
||||||
@ -44,6 +44,85 @@ d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
|
|||||||
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
|
||||||
uvloop = ["uvloop (>=0.15.2)"]
|
uvloop = ["uvloop (>=0.15.2)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cffi"
|
||||||
|
version = "1.17.0"
|
||||||
|
description = "Foreign Function Interface for Python calling C code."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"},
|
||||||
|
{file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"},
|
||||||
|
{file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"},
|
||||||
|
{file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"},
|
||||||
|
{file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"},
|
||||||
|
{file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"},
|
||||||
|
{file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"},
|
||||||
|
{file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pycparser = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "click"
|
name = "click"
|
||||||
version = "8.1.7"
|
version = "8.1.7"
|
||||||
@ -69,6 +148,55 @@ files = [
|
|||||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cryptography"
|
||||||
|
version = "43.0.0"
|
||||||
|
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"},
|
||||||
|
{file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"},
|
||||||
|
{file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"},
|
||||||
|
{file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"},
|
||||||
|
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"},
|
||||||
|
{file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"},
|
||||||
|
{file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"},
|
||||||
|
{file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"},
|
||||||
|
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"},
|
||||||
|
{file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"},
|
||||||
|
{file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"},
|
||||||
|
{file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""}
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||||
|
docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||||
|
nox = ["nox"]
|
||||||
|
pep8test = ["check-sdist", "click", "mypy", "ruff"]
|
||||||
|
sdist = ["build"]
|
||||||
|
ssh = ["bcrypt (>=3.1.5)"]
|
||||||
|
test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"]
|
||||||
|
test-randomorder = ["pytest-randomly"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iniconfig"
|
name = "iniconfig"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@ -240,6 +368,17 @@ files = [
|
|||||||
dev = ["pre-commit", "tox"]
|
dev = ["pre-commit", "tox"]
|
||||||
testing = ["pytest", "pytest-benchmark"]
|
testing = ["pytest", "pytest-benchmark"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycparser"
|
||||||
|
version = "2.22"
|
||||||
|
description = "C parser in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
|
||||||
|
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.2"
|
version = "8.3.2"
|
||||||
@ -290,4 +429,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.11"
|
python-versions = "^3.11"
|
||||||
content-hash = "f7533dcd984fcec36a80d3523af159bc6ebe71edb3c315d8bfd01690d910fb76"
|
content-hash = "96cb529cc8a301c9ac0920582fb7ccb26bc789b0c5ccbc4135fc2d8d6936bb75"
|
||||||
|
@ -9,6 +9,7 @@ readme = "README.md"
|
|||||||
python = "^3.11"
|
python = "^3.11"
|
||||||
peewee = "^3.17.6"
|
peewee = "^3.17.6"
|
||||||
jinja2 = "^3.1.4"
|
jinja2 = "^3.1.4"
|
||||||
|
cryptography = "^43.0.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
ruff = "^0.5.5"
|
ruff = "^0.5.5"
|
||||||
|
@ -1 +1,2 @@
|
|||||||
from spiderweb.main import route, WebServer # noqa: F401
|
from spiderweb.main import route, WebServer # noqa: F401
|
||||||
|
from spiderweb.middleware import * # noqa: F401, F403
|
||||||
|
@ -11,5 +11,9 @@ def http404(request):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def http405(request):
|
||||||
|
return JsonResponse(data={"error": "Method not allowed"}, status_code=405)
|
||||||
|
|
||||||
|
|
||||||
def http500(request):
|
def http500(request):
|
||||||
return JsonResponse(data={"error": "Internal server error"}, status_code=500)
|
return JsonResponse(data={"error": "Internal server error"}, status_code=500)
|
||||||
|
@ -52,6 +52,14 @@ class ServerError(SpiderwebNetworkException):
|
|||||||
self.desc = desc if desc else "The server has encountered an error"
|
self.desc = desc if desc else "The server has encountered an error"
|
||||||
|
|
||||||
|
|
||||||
|
class CSRFError(SpiderwebNetworkException):
|
||||||
|
def __init__(self, desc=None):
|
||||||
|
self.code = 403
|
||||||
|
self.msg = "Forbidden"
|
||||||
|
self.desc = desc if desc else "CSRF token is invalid"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigError(SpiderwebException):
|
class ConfigError(SpiderwebException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
# https://gist.github.com/earonesty/ab07b4c0fea2c226e75b3d538cc0dc55
|
# https://gist.github.com/earonesty/ab07b4c0fea2c226e75b3d538cc0dc55
|
||||||
#
|
#
|
||||||
# Extensively modified by @itsthejoker
|
# Extensively modified by @itsthejoker
|
||||||
import json
|
from datetime import datetime, timedelta
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
import time
|
import time
|
||||||
@ -13,10 +13,11 @@ import threading
|
|||||||
import logging
|
import logging
|
||||||
from typing import Callable, Any, NoReturn
|
from typing import Callable, Any, NoReturn
|
||||||
|
|
||||||
|
from cryptography.fernet import Fernet
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
|
|
||||||
from spiderweb.converters import * # noqa: F403
|
from spiderweb.converters import * # noqa: F403
|
||||||
from spiderweb.default_responses import http403, http404, http500 # noqa: F401
|
from spiderweb.default_responses import * # noqa: F403
|
||||||
from spiderweb.exceptions import (
|
from spiderweb.exceptions import (
|
||||||
APIError,
|
APIError,
|
||||||
ConfigError,
|
ConfigError,
|
||||||
@ -32,6 +33,9 @@ from spiderweb.utils import import_by_string
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
|
DEFAULT_ALLOWED_METHODS = ["GET"]
|
||||||
|
DEFAULT_ENCODING = "utf-8"
|
||||||
|
|
||||||
|
|
||||||
def route(path):
|
def route(path):
|
||||||
def outer(func):
|
def outer(func):
|
||||||
@ -68,6 +72,7 @@ class WebServer(HTTPServer):
|
|||||||
templates_dirs: list[str] = None,
|
templates_dirs: list[str] = None,
|
||||||
middleware: list[str] = None,
|
middleware: list[str] = None,
|
||||||
append_slash: bool = False,
|
append_slash: bool = False,
|
||||||
|
secret_key: str = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Create a new server on address, port. Port can be zero.
|
Create a new server on address, port. Port can be zero.
|
||||||
@ -83,13 +88,17 @@ class WebServer(HTTPServer):
|
|||||||
self.append_slash = append_slash
|
self.append_slash = append_slash
|
||||||
self.templates_dirs = templates_dirs
|
self.templates_dirs = templates_dirs
|
||||||
self.middleware = middleware if middleware else []
|
self.middleware = middleware if middleware else []
|
||||||
|
self.secret_key = secret_key if secret_key else self._create_secret_key()
|
||||||
|
self.fernet = Fernet(self.key)
|
||||||
|
self.DEFAULT_ENCODING = DEFAULT_ENCODING
|
||||||
|
self.DEFAULT_ALLOWED_METHODS = DEFAULT_ALLOWED_METHODS
|
||||||
self._thread = None
|
self._thread = None
|
||||||
|
|
||||||
if self.middleware:
|
if self.middleware:
|
||||||
middleware_by_reference = []
|
middleware_by_reference = []
|
||||||
for m in self.middleware:
|
for m in self.middleware:
|
||||||
try:
|
try:
|
||||||
middleware_by_reference.append(import_by_string(m)())
|
middleware_by_reference.append(import_by_string(m)(server=self))
|
||||||
except ImportError:
|
except ImportError:
|
||||||
raise ConfigError(f"Middleware '{m}' not found.")
|
raise ConfigError(f"Middleware '{m}' not found.")
|
||||||
self.middleware = middleware_by_reference
|
self.middleware = middleware_by_reference
|
||||||
@ -105,11 +114,8 @@ class WebServer(HTTPServer):
|
|||||||
class HandlerClass(RequestHandler):
|
class HandlerClass(RequestHandler):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# inject template loader, middleware, and other important things into handler
|
|
||||||
self.handler_class = custom_handler if custom_handler else HandlerClass
|
self.handler_class = custom_handler if custom_handler else HandlerClass
|
||||||
self.handler_class.env = self.env
|
self.handler_class.server = self
|
||||||
self.handler_class.middleware = self.middleware
|
|
||||||
self.handler_class.append_slash = self.append_slash
|
|
||||||
|
|
||||||
# routed methods map into handler
|
# routed methods map into handler
|
||||||
for method in type(self).__dict__.values():
|
for method in type(self).__dict__.values():
|
||||||
@ -148,22 +154,32 @@ class WebServer(HTTPServer):
|
|||||||
if self.convert_path(path) in self.handler_class._routes:
|
if self.convert_path(path) in self.handler_class._routes:
|
||||||
raise ConfigError(f"Route '{path}' already exists.")
|
raise ConfigError(f"Route '{path}' already exists.")
|
||||||
|
|
||||||
def add_route(self, path: str, method: Callable):
|
def add_route(self, path: str, method: Callable, allowed_methods: list[str]):
|
||||||
"""Add a route to the server."""
|
"""Add a route to the server."""
|
||||||
if not hasattr(self.handler_class, "_routes"):
|
if not hasattr(self.handler_class, "_routes"):
|
||||||
setattr(self.handler_class, "_routes", [])
|
setattr(self.handler_class, "_routes", {})
|
||||||
|
|
||||||
if self.append_slash and not path.endswith("/"):
|
if self.append_slash and not path.endswith("/"):
|
||||||
updated_path = path + "/"
|
updated_path = path + "/"
|
||||||
self.check_for_route_duplicates(updated_path)
|
self.check_for_route_duplicates(updated_path)
|
||||||
self.check_for_route_duplicates(path)
|
self.check_for_route_duplicates(path)
|
||||||
self.handler_class._routes[self.convert_path(path)] = DummyRedirectRoute(updated_path)
|
self.handler_class._routes[self.convert_path(path)] = {'func': DummyRedirectRoute(updated_path), 'allowed_methods': allowed_methods}
|
||||||
self.handler_class._routes[self.convert_path(updated_path)] = method
|
self.handler_class._routes[self.convert_path(updated_path)] = {'func': method, 'allowed_methods': allowed_methods}
|
||||||
else:
|
else:
|
||||||
self.check_for_route_duplicates(path)
|
self.check_for_route_duplicates(path)
|
||||||
self.handler_class._routes[self.convert_path(path)] = method
|
self.handler_class._routes[self.convert_path(path)] = {'func': method, 'allowed_methods': allowed_methods}
|
||||||
|
|
||||||
def route(self, path) -> Callable:
|
def add_error_route(self, code: int, method: Callable):
|
||||||
|
"""Add an error route to the server."""
|
||||||
|
if not hasattr(self.handler_class, "_error_routes"):
|
||||||
|
setattr(self.handler_class, "_error_routes", {})
|
||||||
|
|
||||||
|
if code not in self.handler_class._error_routes:
|
||||||
|
self.handler_class._error_routes[code] = method
|
||||||
|
else:
|
||||||
|
raise ConfigError(f"Error route for code {code} already exists.")
|
||||||
|
|
||||||
|
def route(self, path, allowed_methods=None) -> Callable:
|
||||||
"""
|
"""
|
||||||
Decorator for adding a route to a view.
|
Decorator for adding a route to a view.
|
||||||
|
|
||||||
@ -176,15 +192,26 @@ class WebServer(HTTPServer):
|
|||||||
return HttpResponse(content="Hello, world!")
|
return HttpResponse(content="Hello, world!")
|
||||||
|
|
||||||
:param path: str
|
:param path: str
|
||||||
|
:param allowed_methods: list[str]
|
||||||
:return: Callable
|
:return: Callable
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def outer(func):
|
def outer(func):
|
||||||
self.add_route(path, func)
|
self.add_route(
|
||||||
|
path,
|
||||||
|
func,
|
||||||
|
allowed_methods if allowed_methods else DEFAULT_ALLOWED_METHODS
|
||||||
|
)
|
||||||
return func
|
return func
|
||||||
|
|
||||||
return outer
|
return outer
|
||||||
|
|
||||||
|
def error(self, code: int) -> Callable:
|
||||||
|
def outer(func):
|
||||||
|
self.add_error_route(code, func)
|
||||||
|
return func
|
||||||
|
return outer
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def port(self):
|
def port(self):
|
||||||
"""Return current port."""
|
"""Return current port."""
|
||||||
@ -225,15 +252,25 @@ class WebServer(HTTPServer):
|
|||||||
super().shutdown()
|
super().shutdown()
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
|
|
||||||
|
def _create_secret_key(self):
|
||||||
|
self.key = Fernet.generate_key()
|
||||||
|
|
||||||
|
def encrypt(self, data: str):
|
||||||
|
return self.fernet.encrypt(bytes(data, DEFAULT_ENCODING))
|
||||||
|
|
||||||
|
def decrypt(self, data: str):
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
return self.fernet.decrypt(data).decode(DEFAULT_ENCODING)
|
||||||
|
return self.fernet.decrypt(bytes(data, DEFAULT_ENCODING)).decode(DEFAULT_ENCODING)
|
||||||
|
|
||||||
class RequestHandler(BaseHTTPRequestHandler):
|
class RequestHandler(BaseHTTPRequestHandler):
|
||||||
# I can't help the naming convention of these because that's what
|
def __init__(self, *args, **kwargs):
|
||||||
# BaseHTTPRequestHandler uses for some weird reason
|
super().__init__(*args, **kwargs)
|
||||||
|
# These stop pycharm from complaining about these not existing. They're
|
||||||
# These stop pycharm from complaining about these not existing. They're
|
# injected by the WebServer class at runtime
|
||||||
# injected by the WebServer class at runtime
|
self._routes = {}
|
||||||
_routes = {}
|
self._error_routes = {}
|
||||||
middleware = []
|
self.server = None
|
||||||
|
|
||||||
def get_request(self):
|
def get_request(self):
|
||||||
return Request(
|
return Request(
|
||||||
@ -244,8 +281,11 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
path=self.path,
|
path=self.path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# I can't help the naming convention of these because that's what
|
||||||
|
# BaseHTTPRequestHandler uses for some weird reason
|
||||||
def do_GET(self):
|
def do_GET(self):
|
||||||
request = self.get_request()
|
request = self.get_request()
|
||||||
|
request.method = "GET"
|
||||||
self.handle_request(request)
|
self.handle_request(request)
|
||||||
|
|
||||||
def do_POST(self):
|
def do_POST(self):
|
||||||
@ -254,28 +294,23 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
length = int(self.headers["Content-Length"])
|
length = int(self.headers["Content-Length"])
|
||||||
content = self.rfile.read(length)
|
content = self.rfile.read(length)
|
||||||
request = self.get_request()
|
request = self.get_request()
|
||||||
|
request.method = "POST"
|
||||||
request.content = content
|
request.content = content
|
||||||
if content:
|
|
||||||
try:
|
|
||||||
request.json()
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
raise APIError(400, "Invalid JSON", content)
|
|
||||||
self.handle_request(request)
|
self.handle_request(request)
|
||||||
|
|
||||||
def get_route(self, path) -> tuple[Callable, dict[str, Any]]:
|
def get_route(self, path) -> tuple[Callable, dict[str, Any], list[str]]:
|
||||||
for option in self._routes.keys():
|
for option in self._routes.keys():
|
||||||
if match_data := option.match(path):
|
if match_data := option.match(path):
|
||||||
return self._routes[option], convert_match_to_dict(
|
return self._routes[option]['func'], convert_match_to_dict(
|
||||||
match_data.groupdict()
|
match_data.groupdict()
|
||||||
)
|
), self._routes[option]['allowed_methods']
|
||||||
raise NotFound()
|
raise NotFound()
|
||||||
|
|
||||||
def get_error_route(self, code: int) -> Callable:
|
def get_error_route(self, code: int) -> Callable:
|
||||||
try:
|
view = self._error_routes.get(code) or globals().get(f"http{code}")
|
||||||
view = globals()[f"http{code}"]
|
if not view:
|
||||||
return view
|
|
||||||
except KeyError:
|
|
||||||
return http500
|
return http500
|
||||||
|
return view
|
||||||
|
|
||||||
def _fire_response(self, resp: HttpResponse):
|
def _fire_response(self, resp: HttpResponse):
|
||||||
self.send_response(resp.status_code)
|
self.send_response(resp.status_code)
|
||||||
@ -285,7 +320,7 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
for key, value in resp.headers.items():
|
for key, value in resp.headers.items():
|
||||||
self.send_header(key, value)
|
self.send_header(key, value)
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(bytes(content, "utf-8"))
|
self.wfile.write(bytes(content, DEFAULT_ENCODING))
|
||||||
|
|
||||||
def fire_response(self, request: Request, resp: HttpResponse):
|
def fire_response(self, request: Request, resp: HttpResponse):
|
||||||
try:
|
try:
|
||||||
@ -299,11 +334,11 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
self.fire_response(request, self.get_error_route(500)(request))
|
self.fire_response(request, self.get_error_route(500)(request))
|
||||||
|
|
||||||
def process_request_middleware(self, request: Request) -> None | bool:
|
def process_request_middleware(self, request: Request) -> None | bool:
|
||||||
for middleware in self.middleware:
|
for middleware in self.server.middleware:
|
||||||
try:
|
try:
|
||||||
resp = middleware.process_request(request)
|
resp = middleware.process_request(request)
|
||||||
except UnusedMiddleware:
|
except UnusedMiddleware:
|
||||||
self.middleware.remove(middleware)
|
self.server.middleware.remove(middleware)
|
||||||
continue
|
continue
|
||||||
if resp:
|
if resp:
|
||||||
self.process_response_middleware(request, resp)
|
self.process_response_middleware(request, resp)
|
||||||
@ -311,11 +346,11 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
return True # abort further processing
|
return True # abort further processing
|
||||||
|
|
||||||
def process_response_middleware(self, request: Request, response: HttpResponse) -> None:
|
def process_response_middleware(self, request: Request, response: HttpResponse) -> None:
|
||||||
for middleware in self.middleware:
|
for middleware in self.server.middleware:
|
||||||
try:
|
try:
|
||||||
middleware.process_response(request, response)
|
middleware.process_response(request, response)
|
||||||
except UnusedMiddleware:
|
except UnusedMiddleware:
|
||||||
self.middleware.remove(middleware)
|
self.server.middleware.remove(middleware)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def prepare_and_fire_response(self, request, resp) -> None:
|
def prepare_and_fire_response(self, request, resp) -> None:
|
||||||
@ -323,10 +358,10 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
if isinstance(resp, dict):
|
if isinstance(resp, dict):
|
||||||
self.fire_response(request, JsonResponse(data=resp))
|
self.fire_response(request, JsonResponse(data=resp))
|
||||||
if isinstance(resp, TemplateResponse):
|
if isinstance(resp, TemplateResponse):
|
||||||
if hasattr(self, "env"): # injected from above
|
if hasattr(self.server, "env"):
|
||||||
resp.set_template_loader(self.env)
|
resp.set_template_loader(self.server.env)
|
||||||
|
|
||||||
for middleware in self.middleware:
|
for middleware in self.server.middleware:
|
||||||
middleware.process_response(request, resp)
|
middleware.process_response(request, resp)
|
||||||
|
|
||||||
self.fire_response(request, resp)
|
self.fire_response(request, resp)
|
||||||
@ -338,6 +373,9 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
log.error(traceback.format_exc())
|
log.error(traceback.format_exc())
|
||||||
self.fire_response(request, self.get_error_route(500)(request))
|
self.fire_response(request, self.get_error_route(500)(request))
|
||||||
|
|
||||||
|
def is_form_request(self, request: Request) -> bool:
|
||||||
|
return "Content-Type" in request.headers and request.headers["Content-Type"] == "application/x-www-form-urlencoded"
|
||||||
|
|
||||||
def send_error_response(self, request: Request, e: SpiderwebNetworkException):
|
def send_error_response(self, request: Request, e: SpiderwebNetworkException):
|
||||||
try:
|
try:
|
||||||
self.send_error(e.code, e.msg, e.desc)
|
self.send_error(e.code, e.msg, e.desc)
|
||||||
@ -349,17 +387,25 @@ class RequestHandler(BaseHTTPRequestHandler):
|
|||||||
request.url = urlparse.urlparse(request.path)
|
request.url = urlparse.urlparse(request.path)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
handler, additional_args = self.get_route(request.url.path)
|
handler, additional_args, allowed_methods = self.get_route(request.url.path)
|
||||||
except NotFound:
|
except NotFound:
|
||||||
handler = self.get_error_route(404)
|
handler = self.get_error_route(404)
|
||||||
additional_args = {}
|
additional_args = {}
|
||||||
|
allowed_methods = DEFAULT_ALLOWED_METHODS
|
||||||
|
|
||||||
if request.url.query:
|
if request.method not in allowed_methods:
|
||||||
params = urlparse.parse_qs(request.url.query)
|
# replace the potentially valid handler with the error route
|
||||||
else:
|
handler = self.get_error_route(405)
|
||||||
params = {}
|
|
||||||
|
request.query_params = urlparse.parse_qs(request.url.query) if request.url.query else {}
|
||||||
|
|
||||||
|
if self.is_form_request(request):
|
||||||
|
formdata = urlparse.parse_qs(request.content.decode("utf-8"))
|
||||||
|
for key, value in formdata.items():
|
||||||
|
if len(value) == 1:
|
||||||
|
formdata[key] = value[0]
|
||||||
|
setattr(request, request.method, formdata)
|
||||||
|
|
||||||
request.query_params = params
|
|
||||||
try:
|
try:
|
||||||
if handler:
|
if handler:
|
||||||
# middleware is injected from WebServer
|
# middleware is injected from WebServer
|
||||||
|
2
spiderweb/middleware/__init__.py
Normal file
2
spiderweb/middleware/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from .base import SpiderwebMiddleware
|
||||||
|
from .csrf import CSRFMiddleware
|
@ -17,6 +17,8 @@ class SpiderwebMiddleware:
|
|||||||
If `process_request` returns a HttpResponse, the request will be short-circuited
|
If `process_request` returns a HttpResponse, the request will be short-circuited
|
||||||
and the response will be returned immediately. `process_response` will not be called.
|
and the response will be returned immediately. `process_response` will not be called.
|
||||||
"""
|
"""
|
||||||
|
def __init__(self, server):
|
||||||
|
self.server = server
|
||||||
|
|
||||||
def process_request(self, request: Request) -> HttpResponse | None:
|
def process_request(self, request: Request) -> HttpResponse | None:
|
||||||
pass
|
pass
|
37
spiderweb/middleware/csrf.py
Normal file
37
spiderweb/middleware/csrf.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
|
from spiderweb.exceptions import CSRFError
|
||||||
|
from spiderweb.middleware import SpiderwebMiddleware
|
||||||
|
from spiderweb.request import Request
|
||||||
|
from spiderweb.response import HttpResponse
|
||||||
|
|
||||||
|
|
||||||
|
class CSRFMiddleware(SpiderwebMiddleware):
|
||||||
|
CSRF_EXPIRY = 60 * 60 # 1 hour
|
||||||
|
|
||||||
|
def process_request(self, request: Request) -> HttpResponse | None:
|
||||||
|
if request.method == "POST":
|
||||||
|
csrf_token = request.headers.get("X-CSRF-TOKEN") or request.GET.get("csrf_token") or request.POST.get("csrf_token")
|
||||||
|
if self.is_csrf_valid(csrf_token):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
raise CSRFError()
|
||||||
|
return None
|
||||||
|
|
||||||
|
def process_response(self, request: Request, response: HttpResponse) -> None:
|
||||||
|
token = self.get_csrf_token()
|
||||||
|
# do we need it in both places?
|
||||||
|
response.headers["X-CSRF-TOKEN"] = token
|
||||||
|
request.csrf_token = token
|
||||||
|
|
||||||
|
def get_csrf_token(self):
|
||||||
|
return self.server.encrypt(str(datetime.now().isoformat())).decode(self.server.DEFAULT_ENCODING)
|
||||||
|
|
||||||
|
def is_csrf_valid(self, key):
|
||||||
|
try:
|
||||||
|
decoded = self.server.decrypt(key)
|
||||||
|
if datetime.now() - timedelta(seconds=self.CSRF_EXPIRY) > datetime.fromisoformat(decoded):
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
@ -19,6 +19,8 @@ class Request:
|
|||||||
self.path: str = path
|
self.path: str = path
|
||||||
self.url = url
|
self.url = url
|
||||||
self.query_params = query_params
|
self.query_params = query_params
|
||||||
|
self.GET = {}
|
||||||
|
self.POST = {}
|
||||||
|
|
||||||
def json(self):
|
def json(self):
|
||||||
return json.loads(self.content)
|
return json.loads(self.content)
|
||||||
|
18
templates/base.html
Normal file
18
templates/base.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Bootstrap demo</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
{% block content %}There's no content here.{% endblock %}
|
||||||
|
</div>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
||||||
|
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
21
templates/form.html
Normal file
21
templates/form.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<form action="" method="post">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="exampleFormControlInput1" class="form-label">Email address</label>
|
||||||
|
<input type="email" class="form-control" name="email" id="exampleFormControlInput1" placeholder="name@example.com">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="exampleFormControlTextarea1" class="form-label">Example textarea</label>
|
||||||
|
<textarea class="form-control" name="comment" id="exampleFormControlTextarea1" rows="3"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="mb-3 form-check">
|
||||||
|
<input type="checkbox" name="formcheck" class="form-check-input" id="exampleCheck1">
|
||||||
|
<label class="form-check-label" for="exampleCheck1">Check me out</label>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="csrf_token" value="{{ request.csrf_token }}">
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
@ -1,14 +1,7 @@
|
|||||||
<!doctype html>
|
{% extends 'base.html' %}
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
{% block content %}
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>Bootstrap demo</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
|
||||||
integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1 class="mt-5">HI, THIS IS A PAGE</h1>
|
<h1 class="mt-5">HI, THIS IS A PAGE</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -19,9 +12,4 @@
|
|||||||
The value of <code>request.spiderweb</code> is {{ request.spiderweb }}. If this is True,
|
The value of <code>request.spiderweb</code> is {{ request.spiderweb }}. If this is True,
|
||||||
middleware is working.
|
middleware is working.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
{% endblock %}
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
|
|
||||||
integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
|
|
||||||
crossorigin="anonymous"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Loading…
Reference in New Issue
Block a user