From 0cb317f7959af236b1d8ffb617c0b92a39b62eb9 Mon Sep 17 00:00:00 2001 From: Giovanni Barillari Date: Fri, 30 May 2025 10:41:12 +0200 Subject: [PATCH] Add support for ASGI pathsend --- blacksheep/contents.pxd | 4 ++++ blacksheep/contents.py | 8 ++++++++ blacksheep/contents.pyi | 11 +++++++++++ blacksheep/contents.pyx | 13 +++++++++++++ blacksheep/messages.py | 6 ++++++ blacksheep/messages.pyi | 3 +++ blacksheep/messages.pyx | 5 +++++ blacksheep/scribe.py | 4 +++- blacksheep/server/files/__init__.py | 3 +++ 9 files changed, 56 insertions(+), 1 deletion(-) diff --git a/blacksheep/contents.pxd b/blacksheep/contents.pxd index 64a18c46..4bfdae59 100644 --- a/blacksheep/contents.pxd +++ b/blacksheep/contents.pxd @@ -15,6 +15,10 @@ cdef class StreamedContent(Content): cdef readonly object generator +cdef class FilepathContent(Content): + cdef readonly str path + + cdef class ASGIContent(Content): cdef readonly object receive cpdef void dispose(self) diff --git a/blacksheep/contents.py b/blacksheep/contents.py index f735c495..ac3344a7 100644 --- a/blacksheep/contents.py +++ b/blacksheep/contents.py @@ -45,6 +45,14 @@ async def get_parts(self): yield chunk +class FilepathContent(Content): + def __init__(self, content_type: bytes, path: str): + self.type = content_type + self.body = None + self.length = 0 + self.path = path + + class ASGIContent(Content): def __init__(self, receive): self.type = None diff --git a/blacksheep/contents.pyi b/blacksheep/contents.pyi index 0e56fe2e..4ef3673f 100644 --- a/blacksheep/contents.pyi +++ b/blacksheep/contents.pyi @@ -36,6 +36,17 @@ class StreamedContent(Content): async def get_parts(self) -> AsyncIterable[bytes]: ... +class FilepathContent(Content): + def __init__( + self, + content_type: bytes, + path: str, + ) -> None: + self.type = content_type + self.body = None + self.length = 0 + self.path = path + class ASGIContent(Content): def __init__(self, receive: Callable[[], Awaitable[dict]]): self.type = None diff --git a/blacksheep/contents.pyx b/blacksheep/contents.pyx index 9a94a28b..48100573 100644 --- a/blacksheep/contents.pyx +++ b/blacksheep/contents.pyx @@ -61,6 +61,19 @@ cdef class StreamedContent(Content): yield chunk +cdef class FilepathContent(Content): + + def __init__( + self, + bytes content_type, + str path + ): + self.type = content_type + self.body = None + self.length = 0 + self.path = path + + cdef class ASGIContent(Content): def __init__(self, object receive): diff --git a/blacksheep/messages.py b/blacksheep/messages.py index e111706b..122833d8 100644 --- a/blacksheep/messages.py +++ b/blacksheep/messages.py @@ -470,6 +470,12 @@ async def is_disconnected(self): self._is_disconnected = True return self._is_disconnected + @property + def accept_pathsend(self): + if not isinstance(self.content, ASGIContent) or self.scope is None: + return False + return "http.response.pathsend" in self.scope.get("extensions", {}) + class Response(Message): def __init__(self, status: int, headers=None, content: Content = None): diff --git a/blacksheep/messages.pyi b/blacksheep/messages.pyi index d5d654ca..0570b70f 100644 --- a/blacksheep/messages.pyi +++ b/blacksheep/messages.pyi @@ -147,6 +147,9 @@ class Request(Message): detect if the client closed the original connection. """ + @property + def accept_pathsend(self) -> bool: ... + class Response(Message): def __init__( self, diff --git a/blacksheep/messages.pyx b/blacksheep/messages.pyx index b2484c36..986ec49d 100644 --- a/blacksheep/messages.pyx +++ b/blacksheep/messages.pyx @@ -543,6 +543,11 @@ cdef class Request(Message): return self._is_disconnected + @property + def accept_pathsend(self): + if not isinstance(self.content, ASGIContent) or self.scope is None: + return False + return "http.response.pathsend" in self.scope.get("extensions", {}) cdef class Response(Message): diff --git a/blacksheep/scribe.py b/blacksheep/scribe.py index 6a70c523..7abee645 100644 --- a/blacksheep/scribe.py +++ b/blacksheep/scribe.py @@ -1,7 +1,7 @@ import http import re -from .contents import Content, StreamedContent +from .contents import Content, FilepathContent, StreamedContent from .cookies import Cookie, write_cookie_for_response from .messages import Request, Response @@ -288,6 +288,8 @@ async def send_asgi_response(response: Response, send): await send( {"type": "http.response.body", "body": b"", "more_body": False} ) + elif isinstance(content, FilepathContent): + await send({"type": "http.response.pathsend", "path": content.path}) else: if content.length > MAX_RESPONSE_CHUNK_SIZE: for chunk in get_chunks(content.body): diff --git a/blacksheep/server/files/__init__.py b/blacksheep/server/files/__init__.py index 6c193055..31cd2cbc 100644 --- a/blacksheep/server/files/__init__.py +++ b/blacksheep/server/files/__init__.py @@ -7,6 +7,7 @@ from blacksheep.common.files.asyncfs import FilesHandler from blacksheep.common.files.info import FileInfo from blacksheep.common.files.pathsutils import get_mime_type_from_name +from blacksheep.contents import FilepathContent from blacksheep.exceptions import BadRequest, InvalidArgument, RangeNotSatisfiable from blacksheep.ranges import InvalidRangeValue, Range, RangePart from blacksheep.server.headers.cache import CacheControlHeaderValue @@ -289,6 +290,8 @@ def get_response_for_file( file_type=file_type, ), ) + elif request.accept_pathsend: + content = FilepathContent(mime, resource_path) else: content = StreamedContent( mime, get_file_getter(files_handler, resource_path, info.size)