madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

re-implement simple CORS middleware for blackd (#2500)
authorZsolt Dollenstein <zsol.zsol@gmail.com>
Sat, 25 Sep 2021 11:58:44 +0000 (12:58 +0100)
committerGitHub <noreply@github.com>
Sat, 25 Sep 2021 11:58:44 +0000 (12:58 +0100)
* 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
Pipfile
Pipfile.lock
setup.py
src/blackd/__init__.py
src/blackd/middlewares.py [new file with mode: 0644]
tests/test_blackd.py

index 6ff488ba74e98ea0e18a4d97c5e55fc1045264de..2a9b649d25cfe0776a34e655dd85b8b36bf20781 100644 (file)
@@ -1,5 +1,11 @@
 # Change Log
 
+## Unreleased
+
+### _Blackd_
+
+- Remove dependency on aiohttp-cors (#2500)
+
 ## 21.9b0
 
 ### Packaging
diff --git a/Pipfile b/Pipfile
index 824beda16ccedac93c8736cfb46a1e2705506f41..66ded8b857010019cc7e26432c6591a3993a4482 100644 (file)
--- 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"
index 30a4defcca35d48f82c91bbe80fd978343f6b65b..0cbe90119810eb229c3a3f4c446a9d4b48d41d63 100644 (file)
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "ebf216584cfb2c962a1792d0682f3c08b44c7ae27305a03a54eacd6f42df27db"
+            "sha256": "8b28e41c5a63f0c30361d2a0ed29dc1e3f0468223ef150ae68586839e2ccf1c9"
         },
         "pipfile-spec": 6,
         "requires": {},
             "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",
             "index": "pypi",
             "version": "==0.4.3"
         },
+        "packaging": {
+            "hashes": [
+                "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
+                "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==21.0"
+        },
         "pathspec": {
             "hashes": [
                 "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
             "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",
             "version": "==2021.8.21"
         },
         "setuptools-scm": {
+            "extras": [
+                "toml"
+            ],
             "hashes": [
                 "sha256:c3bd5f701c8def44a5c0bfe8d407bef3f80342217ef3492b951f3777bd2d915c",
                 "sha256:d1925a69cb07e9b29416a275b9fadb009a23c148ace905b2fb220649a6c18e92"
             "index": "pypi",
             "version": "==3.7.4.post0"
         },
-        "aiohttp-cors": {
-            "hashes": [
-                "sha256:0451ba59fdf6909d0e2cd21e4c0a43752bc0703d33fc78ae94d9d9321710193e",
-                "sha256:4d39c6d7100fd9764ed1caf8cebf0eb01bf5e3f24e2e073fda6234bc48b19f5d"
-            ],
-            "index": "pypi",
-            "version": "==0.7.0"
-        },
         "alabaster": {
             "hashes": [
                 "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359",
                 "sha256:2480d8e07d1890056cb53c96e3de44fead9c62f2ba949b0f2e4c4345f4afa977",
                 "sha256:a65882a4d0fe5fbf702273456ba2ce74fe44892c25e42e057aca526b702a6d4b"
             ],
-            "markers": "python_version < '3.7' and python_version < '3.7'",
+            "markers": "python_version < '3.7'",
             "version": "==5.2.2"
         },
         "iniconfig": {
             "version": "==3.3.1"
         },
         "setuptools-scm": {
+            "extras": [
+                "toml"
+            ],
             "hashes": [
                 "sha256:c3bd5f701c8def44a5c0bfe8d407bef3f80342217ef3492b951f3777bd2d915c",
                 "sha256:d1925a69cb07e9b29416a275b9fadb009a23c148ace905b2fb220649a6c18e92"
index 929096a2098f97a6de86b2cb09b5e817a0a4ca60..19df9beb24eb8369250f8af7d2a915236e6528ea 100644 (file)
--- 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"],
index d062303fd10d4f3190de861cfb5a4ddd42f2ac0a..cc966404a743a37bd2059c89732e7ad9d3b8b384 100644 (file)
@@ -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 (file)
index 0000000..97994ec
--- /dev/null
@@ -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
index 9ca19d49dc6b79a86314100a0e37e3df23f16e58..cc750b40567d7c4c0f393a272780f41115162b4e 100644 (file)
@@ -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"))