From 2ef2a55a95b350672d72cf5effeb0e33e0442c09 Mon Sep 17 00:00:00 2001 From: ghandic Date: Sat, 12 Sep 2020 14:48:22 +1000 Subject: [PATCH 1/3] Added in exception handling into router to allow execptions raised by middleware to surface up through the use of HTTPException, tests to complement it as added additional exception branching --- fastapi/routing.py | 2 + tests/test_custom_middleware_exception.py | 95 +++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 tests/test_custom_middleware_exception.py diff --git a/fastapi/routing.py b/fastapi/routing.py index e455f81c95387..9680b72b3dc03 100644 --- a/fastapi/routing.py +++ b/fastapi/routing.py @@ -165,6 +165,8 @@ async def app(request: Request) -> Response: body = await request.json() except json.JSONDecodeError as e: raise RequestValidationError([ErrorWrapper(e, ("body", e.pos))], body=e.doc) + except HTTPException: + raise except Exception as e: raise HTTPException( status_code=400, detail="There was an error parsing the body" diff --git a/tests/test_custom_middleware_exception.py b/tests/test_custom_middleware_exception.py new file mode 100644 index 0000000000000..9b4a7a9a99ceb --- /dev/null +++ b/tests/test_custom_middleware_exception.py @@ -0,0 +1,95 @@ +import os +from typing import Optional + +from fastapi import APIRouter, FastAPI, File, UploadFile +from fastapi.exceptions import HTTPException +from fastapi.testclient import TestClient + +app = FastAPI() + +router = APIRouter() + + +class ContentSizeLimitMiddleware: + """ Content size limiting middleware for ASGI applications + Args: + app (ASGI application): ASGI application + max_content_size (optional): the maximum content size allowed in bytes, None for no limit + """ + + def __init__(self, app: APIRouter, max_content_size: Optional[int] = None): + self.app = app + self.max_content_size = max_content_size + + def receive_wrapper(self, receive): + received = 0 + + async def inner(): + nonlocal received + message = await receive() + if message["type"] != "http.request": + return message # pragma: no cover + + body_len = len(message.get("body", b"")) + received += body_len + if received > self.max_content_size: + raise HTTPException( + 422, + detail={ + "name": "ContentSizeLimitExceeded", + "code": 999, + "message": "File limit exceeded", + }, + ) + return message + + return inner + + async def __call__(self, scope, receive, send): + if scope["type"] != "http" or self.max_content_size is None: + await self.app(scope, receive, send) + return + + wrapper = self.receive_wrapper(receive) + await self.app(scope, wrapper, send) + + +@router.post("/middleware") +def run_middleware(file: UploadFile = File(..., description="Big File")): + return {"message": "OK"} + + +app.include_router(router) +app.add_middleware(ContentSizeLimitMiddleware, max_content_size=2 ** 8) + + +client = TestClient(app) + + +def test_custom_middleware_exception(tmpdir): + default_pydantic_max_size = 2 ** 16 + path = os.path.join(tmpdir, "test.txt") + with open(path, "wb") as file: + file.write(b"x" * (default_pydantic_max_size + 1)) + + with client: + response = client.post("/middleware", files={"file": open(path, "rb")}) + assert response.status_code == 422, response.text + assert response.json() == { + "detail": { + "name": "ContentSizeLimitExceeded", + "code": 999, + "message": "File limit exceeded", + } + } + + +def test_custom_middleware_exception_not_raised(tmpdir): + path = os.path.join(tmpdir, "test.txt") + with open(path, "wb") as file: + file.write(b"") + + with client: + response = client.post("/middleware", files={"file": open(path, "rb")}) + assert response.status_code == 200, response.text + assert response.json() == {"message": "OK"} From 0111167c17dba8a642db2bcaa98eaea6878859b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Aug 2022 10:05:21 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=8E=A8=20[pre-commit.ci]=20Auto=20for?= =?UTF-8?q?mat=20from=20pre-commit.com=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_custom_middleware_exception.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_custom_middleware_exception.py b/tests/test_custom_middleware_exception.py index 9b4a7a9a99ceb..04cc350bc039a 100644 --- a/tests/test_custom_middleware_exception.py +++ b/tests/test_custom_middleware_exception.py @@ -11,7 +11,7 @@ class ContentSizeLimitMiddleware: - """ Content size limiting middleware for ASGI applications + """Content size limiting middleware for ASGI applications Args: app (ASGI application): ASGI application max_content_size (optional): the maximum content size allowed in bytes, None for no limit @@ -60,14 +60,14 @@ def run_middleware(file: UploadFile = File(..., description="Big File")): app.include_router(router) -app.add_middleware(ContentSizeLimitMiddleware, max_content_size=2 ** 8) +app.add_middleware(ContentSizeLimitMiddleware, max_content_size=2**8) client = TestClient(app) def test_custom_middleware_exception(tmpdir): - default_pydantic_max_size = 2 ** 16 + default_pydantic_max_size = 2**16 path = os.path.join(tmpdir, "test.txt") with open(path, "wb") as file: file.write(b"x" * (default_pydantic_max_size + 1)) From 4a4a54579d1e014ae5e290f4039e2a73bb82261c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebasti=C3=A1n=20Ram=C3=ADrez?= Date: Thu, 25 Aug 2022 23:39:03 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=F0=9F=90=9B=20Fix=20and=20refactor=20tests?= =?UTF-8?q?=20for=20custom=20middleware=20raising=20HTTPExceptions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_custom_middleware_exception.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/test_custom_middleware_exception.py b/tests/test_custom_middleware_exception.py index 04cc350bc039a..d9b81e7c2e74f 100644 --- a/tests/test_custom_middleware_exception.py +++ b/tests/test_custom_middleware_exception.py @@ -1,4 +1,4 @@ -import os +from pathlib import Path from typing import Optional from fastapi import APIRouter, FastAPI, File, UploadFile @@ -66,14 +66,14 @@ def run_middleware(file: UploadFile = File(..., description="Big File")): client = TestClient(app) -def test_custom_middleware_exception(tmpdir): +def test_custom_middleware_exception(tmp_path: Path): default_pydantic_max_size = 2**16 - path = os.path.join(tmpdir, "test.txt") - with open(path, "wb") as file: - file.write(b"x" * (default_pydantic_max_size + 1)) + path = tmp_path / "test.txt" + path.write_bytes(b"x" * (default_pydantic_max_size + 1)) with client: - response = client.post("/middleware", files={"file": open(path, "rb")}) + with open(path, "rb") as file: + response = client.post("/middleware", files={"file": file}) assert response.status_code == 422, response.text assert response.json() == { "detail": { @@ -84,12 +84,12 @@ def test_custom_middleware_exception(tmpdir): } -def test_custom_middleware_exception_not_raised(tmpdir): - path = os.path.join(tmpdir, "test.txt") - with open(path, "wb") as file: - file.write(b"") +def test_custom_middleware_exception_not_raised(tmp_path: Path): + path = tmp_path / "test.txt" + path.write_bytes(b"") with client: - response = client.post("/middleware", files={"file": open(path, "rb")}) + with open(path, "rb") as file: + response = client.post("/middleware", files={"file": file}) assert response.status_code == 200, response.text assert response.json() == {"message": "OK"}