Files
romm/backend/utils/nginx.py
Michael Manganiello 0fad8ac282 feat: Use nginx mod_zip to generate multi-file zip downloads
This change installs and configures the `mod_zip` nginx module [1],
which allows nginx to stream ZIP files directly.

It includes a workaround needed to correctly calculate CRC-32 values for
included files, by including a new `server` section listening at port
8081, only used for the file requests to be upstream subrequests that
correctly trigger the CRC-32 calculation logic.

Also, to be able to provide a `m3u` file generated on the fly, we add a
`/decode` endpoint fully implemented in nginx using NJS, which receives
a `value` URL param, and decodes it using base64. The decoded value is
returned as the response.

That way, the contents of the `m3u` file is base64-encoded, and set as
part of the response, for `mod_zip` to include it in the ZIP file.

[1] https://github.com/evanmiller/mod_zip
2024-08-20 22:39:33 -03:00

50 lines
1.3 KiB
Python

import dataclasses
from collections.abc import Collection
from typing import Any
from fastapi.responses import Response
@dataclasses.dataclass(frozen=True)
class ZipContentLine:
"""Dataclass for lines returned in the response body, for usage with the `mod_zip` module.
Reference:
https://github.com/evanmiller/mod_zip?tab=readme-ov-file#usage
"""
crc32: str | None
size_bytes: int
encoded_location: str
filename: str
def __str__(self) -> str:
crc32 = self.crc32 or "-"
return f"{crc32} {self.size_bytes} {self.encoded_location} {self.filename}"
class ZipResponse(Response):
"""Response class for returning a ZIP archive with multiple files, using the `mod_zip` module."""
def __init__(
self,
*,
content_lines: Collection[ZipContentLine],
filename: str,
**kwargs: Any,
):
if kwargs.get("content"):
raise ValueError(
"Argument 'content' must not be provided, as it is generated from 'content_lines'"
)
kwargs["content"] = "\n".join(str(line) for line in content_lines)
kwargs.setdefault("headers", {}).update(
{
"Content-Disposition": f'attachment; filename="{filename}"',
"X-Archive-Files": "zip",
}
)
super().__init__(**kwargs)