Skip to content

Commit

Permalink
commit something
Browse files Browse the repository at this point in the history
  • Loading branch information
Kludex committed Dec 16, 2023
1 parent a3b77d1 commit 1c6254e
Showing 1 changed file with 117 additions and 5 deletions.
122 changes: 117 additions & 5 deletions starlette/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ def set_stat_headers(self, stat_result: os.stat_result) -> None:
self.headers.setdefault("etag", etag)

async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
send_header_only: bool = scope["method"].upper() == "HEAD"
if self.stat_result is None:
try:
stat_result = await anyio.to_thread.run_sync(os.stat, self.path)
Expand All @@ -336,17 +337,33 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
http_range = headers.get("range")
# http_if_range = headers.get("if-range")

ranges = self._parse_range_header(http_range, stat_result.st_size)
print(ranges)
if http_range is None:
await self._handle_simple(send, send_header_only)
else:
ranges = self._parse_range_header(http_range, stat_result.st_size)

if len(ranges) == 1:
start, end = ranges[0]
await self._handle_single_range(
send, start, end, stat_result.st_size, send_header_only
)
else:
await self._handle_multiple_ranges(
send, ranges, stat_result.st_size, send_header_only
)

if self.background is not None:
await self.background()

async def _handle_simple(self, send: Send, send_header_only: bool) -> None:
await send(
{
"type": "http.response.start",
"status": self.status_code,
"headers": self.raw_headers,
}
)
if scope["method"].upper() == "HEAD":
if send_header_only:
await send({"type": "http.response.body", "body": b"", "more_body": False})
else:
async with await anyio.open_file(self.path, mode="rb") as file:
Expand All @@ -361,8 +378,103 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
"more_body": more_body,
}
)
if self.background is not None:
await self.background()

async def _handle_single_range(
self, send: Send, start: int, end: int, file_size: int, send_header_only: bool
) -> None:
self.headers["content-range"] = f"bytes {start}-{end - 1}/{file_size}"
await send(
{
"type": "http.response.start",
"status": 206,
"headers": self.raw_headers,
}
)
if send_header_only:
await send({"type": "http.response.body", "body": b"", "more_body": False})
else:
async with await anyio.open_file(self.path, mode="rb") as file:
await file.seek(start)
more_body = True
while more_body:
chunk = await file.read(min(self.chunk_size, end - start))
start += len(chunk)
more_body = len(chunk) == self.chunk_size and start < end
await send(
{
"type": "http.response.body",
"body": chunk,
"more_body": more_body,
}
)

async def _handle_multiple_ranges(
self,
send: Send,
ranges: typing.List[typing.Tuple[int, int]],
file_size: int,
send_header_only: bool,
) -> None:
boundary = md5_hexdigest(os.urandom(16), usedforsecurity=False)
content_type = f"multipart/byteranges; boundary={boundary}"
self.headers["content-type"] = content_type
await send(
{
"type": "http.response.start",
"status": 206,
"headers": self.raw_headers,
}
)
if send_header_only:
await send({"type": "http.response.body", "body": b"", "more_body": False})
else:
async with await anyio.open_file(self.path, mode="rb") as file:
for start, end in ranges:
await send(
{
"type": "http.response.body",
"body": b"\r\n--" + boundary.encode("ascii") + b"\r\n",
"more_body": True,
}
)
await send(
{
"type": "http.response.body",
"body": f"Content-Type: {self.media_type}\r\n".encode(
"utf-8"
),
"more_body": True,
}
)
await send(
{
"type": "http.response.body",
"body": f"Content-Range: bytes {start}-{end - 1}/{file_size}\r\n\r\n".encode(
"utf-8"
),
"more_body": True,
}
)
await file.seek(start)
more_body = True
while more_body:
chunk = await file.read(min(self.chunk_size, end - start))
start += len(chunk)
more_body = len(chunk) == self.chunk_size and start < end
await send(
{
"type": "http.response.body",
"body": chunk,
"more_body": more_body,
}
)
await send(
{
"type": "http.response.body",
"body": b"\r\n--" + boundary.encode("ascii") + b"--\r\n",
"more_body": False,
}
)

def _parse_range_header(
self, http_range: typing.Optional[str], file_size: int
Expand Down

0 comments on commit 1c6254e

Please sign in to comment.