From a5381ba7648f7308145c78c248e29118e18dc530 Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Sat, 25 Sep 2021 12:58:44 +0100 Subject: [PATCH] re-implement simple CORS middleware for blackd (#2500) * re-implement simple CORS middleware for blackd * remove aiohttp-cors from setup.py * Remove aiohttp-cors from Pipfile.lock Co-authored-by: Richard Si <63936253+ichard26@users.noreply.github.com> --- CHANGES.md | 6 ++++++ Pipfile | 1 - Pipfile.lock | 42 ++++++++++++++++++++++----------------- setup.py | 2 +- src/blackd/__init__.py | 19 +++++------------- src/blackd/middlewares.py | 34 +++++++++++++++++++++++++++++++ tests/test_blackd.py | 21 ++++++++++++++++++++ 7 files changed, 91 insertions(+), 34 deletions(-) create mode 100644 src/blackd/middlewares.py diff --git a/CHANGES.md b/CHANGES.md index 6ff488b..2a9b649 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # Change Log +## Unreleased + +### _Blackd_ + +- Remove dependency on aiohttp-cors (#2500) + ## 21.9b0 ### Packaging diff --git a/Pipfile b/Pipfile index 824beda..66ded8b 100644 --- a/Pipfile +++ b/Pipfile @@ -41,7 +41,6 @@ black = {editable = true, extras = ["d", "jupyter"], path = "."} [packages] aiohttp = ">=3.6.0" -aiohttp-cors = ">=0.4.0" platformdirs= ">=2" click = ">=8.0.0" mypy_extensions = ">=0.4.3" diff --git a/Pipfile.lock b/Pipfile.lock index 30a4def..0cbe901 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ebf216584cfb2c962a1792d0682f3c08b44c7ae27305a03a54eacd6f42df27db" + "sha256": "8b28e41c5a63f0c30361d2a0ed29dc1e3f0468223ef150ae68586839e2ccf1c9" }, "pipfile-spec": 6, "requires": {}, @@ -57,14 +57,6 @@ "index": "pypi", "version": "==3.7.4.post0" }, - "aiohttp-cors": { - "hashes": [ - "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e", - "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d" - ], - "index": "pypi", - "version": "==0.7.0" - }, "async-timeout": { "hashes": [ "sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f", @@ -188,6 +180,14 @@ "index": "pypi", "version": "==0.4.3" }, + "packaging": { + "hashes": [ + "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7", + "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14" + ], + "markers": "python_version >= '3.6'", + "version": "==21.0" + }, "pathspec": { "hashes": [ "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a", @@ -204,6 +204,14 @@ "index": "pypi", "version": "==2.2.0" }, + "pyparsing": { + "hashes": [ + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.4.7" + }, "regex": { "hashes": [ "sha256:03840a07a402576b8e3a6261f17eb88abd653ad4e18ec46ef10c9a63f8c99ebd", @@ -252,6 +260,9 @@ "version": "==2021.8.21" }, "setuptools-scm": { + "extras": [ + "toml" + ], "hashes": [ "sha256:c3bd5f701c8def44a5c0bfe8d407bef3f80342217ef3492b951f3777bd2d915c", "sha256:d1925a69cb07e9b29416a275b9fadb009a23c148ace905b2fb220649a6c18e92" @@ -410,14 +421,6 @@ "index": "pypi", "version": "==3.7.4.post0" }, - "aiohttp-cors": { - "hashes": [ - "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e", - "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d" - ], - "index": "pypi", - "version": "==0.7.0" - }, "alabaster": { "hashes": [ "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", @@ -769,7 +772,7 @@ "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977", "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b" ], - "markers": "python_version < '3.7' and python_version < '3.7'", + "markers": "python_version < '3.7'", "version": "==5.2.2" }, "iniconfig": { @@ -1336,6 +1339,9 @@ "version": "==3.3.1" }, "setuptools-scm": { + "extras": [ + "toml" + ], "hashes": [ "sha256:c3bd5f701c8def44a5c0bfe8d407bef3f80342217ef3492b951f3777bd2d915c", "sha256:d1925a69cb07e9b29416a275b9fadb009a23c148ace905b2fb220649a6c18e92" diff --git a/setup.py b/setup.py index 929096a..19df9be 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ setup( "mypy_extensions>=0.4.3", ], extras_require={ - "d": ["aiohttp>=3.6.0", "aiohttp-cors>=0.4.0"], + "d": ["aiohttp>=3.6.0"], "colorama": ["colorama>=0.4.3"], "python2": ["typed-ast>=1.4.2"], "uvloop": ["uvloop>=0.15.2"], diff --git a/src/blackd/__init__.py b/src/blackd/__init__.py index d062303..cc96640 100644 --- a/src/blackd/__init__.py +++ b/src/blackd/__init__.py @@ -8,7 +8,7 @@ from typing import Set, Tuple try: from aiohttp import web - import aiohttp_cors + from .middlewares import cors except ImportError as ie: raise ImportError( f"aiohttp dependency is not installed: {ie}. " @@ -67,20 +67,11 @@ def main(bind_host: str, bind_port: int) -> None: def make_app() -> web.Application: - app = web.Application() - executor = ProcessPoolExecutor() - - cors = aiohttp_cors.setup(app) - resource = cors.add(app.router.add_resource("/")) - cors.add( - resource.add_route("POST", partial(handle, executor=executor)), - { - "*": aiohttp_cors.ResourceOptions( - allow_headers=(*BLACK_HEADERS, "Content-Type"), expose_headers="*" - ) - }, + app = web.Application( + middlewares=[cors(allow_headers=(*BLACK_HEADERS, "Content-Type"))] ) - + executor = ProcessPoolExecutor() + app.add_routes([web.post("/", partial(handle, executor=executor))]) return app diff --git a/src/blackd/middlewares.py b/src/blackd/middlewares.py new file mode 100644 index 0000000..97994ec --- /dev/null +++ b/src/blackd/middlewares.py @@ -0,0 +1,34 @@ +from typing import Iterable, Awaitable, Callable +from aiohttp.web_response import StreamResponse +from aiohttp.web_request import Request +from aiohttp.web_middlewares import middleware + +Handler = Callable[[Request], Awaitable[StreamResponse]] +Middleware = Callable[[Request, Handler], Awaitable[StreamResponse]] + + +def cors(allow_headers: Iterable[str]) -> Middleware: + @middleware + async def impl(request: Request, handler: Handler) -> StreamResponse: + is_options = request.method == "OPTIONS" + is_preflight = is_options and "Access-Control-Request-Method" in request.headers + if is_preflight: + resp = StreamResponse() + else: + resp = await handler(request) + + origin = request.headers.get("Origin") + if not origin: + return resp + + resp.headers["Access-Control-Allow-Origin"] = "*" + resp.headers["Access-Control-Expose-Headers"] = "*" + if is_options: + resp.headers["Access-Control-Allow-Headers"] = ", ".join(allow_headers) + resp.headers["Access-Control-Allow-Methods"] = ", ".join( + ("OPTIONS", "POST") + ) + + return resp + + return impl # type: ignore diff --git a/tests/test_blackd.py b/tests/test_blackd.py index 9ca19d4..cc750b4 100644 --- a/tests/test_blackd.py +++ b/tests/test_blackd.py @@ -164,3 +164,24 @@ class BlackDTestCase(AioHTTPTestCase): async def test_blackd_response_black_version_header(self) -> None: response = await self.client.post("/") self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER)) + + @unittest_run_loop + async def test_cors_preflight(self) -> None: + response = await self.client.options( + "/", + headers={ + "Access-Control-Request-Method": "POST", + "Origin": "*", + "Access-Control-Request-Headers": "Content-Type", + }, + ) + self.assertEqual(response.status, 200) + self.assertIsNotNone(response.headers.get("Access-Control-Allow-Origin")) + self.assertIsNotNone(response.headers.get("Access-Control-Allow-Headers")) + self.assertIsNotNone(response.headers.get("Access-Control-Allow-Methods")) + + @unittest_run_loop + async def test_cors_headers_present(self) -> None: + response = await self.client.post("/", headers={"Origin": "*"}) + self.assertIsNotNone(response.headers.get("Access-Control-Allow-Origin")) + self.assertIsNotNone(response.headers.get("Access-Control-Expose-Headers")) -- 2.39.2