Ejemplo n.º 1
0
 async def head(self,
                disposition=None,
                filename=None,
                content_type=None,
                size=None,
                extra_headers=None,
                **kwargs):
     try:
         if hasattr(self.file_storage_manager, "exists"):
             # does not need to implement but can be a way to verify
             # file exists on cloud platform still
             if not await apply_coroutine(self.file_storage_manager.exists):
                 raise HTTPNotFound(
                     content={"message": "File object does not exist"})
         download_resp = await self.prepare_download(
             disposition, filename, content_type, size, extra_headers,
             **kwargs)
         await download_resp.write(eof=True)
     except (ConnectionRefusedError,
             ConnectionResetError):  # pragma: no cover
         logger.info(f"Head cancelled: {self.request}")
         # when supporting range headers, the browser will
         # cancel downloads. This is fine.
         raise HTTPClientClosedRequest()
     return download_resp
Ejemplo n.º 2
0
    async def receive(self) -> bytes:
        payload = await self._asgi_receive()
        if payload["type"] == "http.request":
            self._eof = not payload.get("more_body", False)
            return payload["body"]
        elif payload["type"] == "http.disconnect":
            from guillotina.response import HTTPClientClosedRequest

            raise HTTPClientClosedRequest()
        else:
            raise RuntimeError(str(payload))
Ejemplo n.º 3
0
 async def _full_download(
     self,
     disposition=None,
     filename=None,
     content_type=None,
     size=None,
     extra_headers=None,
     range_supported=True,
     **kwargs,
 ):
     download_resp = None
     try:
         async for chunk in self.file_storage_manager.iter_data(**kwargs):
             if download_resp is None:
                 # defer to make sure we do http exception handling
                 # before data starts streaming properly
                 download_resp = await self.prepare_download(
                     disposition,
                     filename,
                     content_type,
                     size,
                     extra_headers,
                     range_supported=range_supported,
                     **kwargs,
                 )
             await download_resp.write(chunk)
     except (asyncio.CancelledError,
             ConnectionResetError):  # pragma: no cover
         logger.info(f"Download cancelled: {self.request}")
         # when supporting range headers, the browser will
         # cancel downloads. This is fine.
         if download_resp is None:
             raise HTTPClientClosedRequest()
     else:
         if download_resp is None:
             # deferred
             download_resp = await self.prepare_download(
                 disposition,
                 filename,
                 content_type,
                 size,
                 extra_headers,
                 range_supported=range_supported,
                 **kwargs,
             )
         await download_resp.write(eof=True)
         return download_resp
Ejemplo n.º 4
0
    async def _range_download(
        self,
        disposition=None,
        filename=None,
        content_type=None,
        size=None,
        extra_headers=None,
        range_supported=True,
        **kwargs,
    ):
        try:
            file = self.field.get(self.field.context or self.context)
        except AttributeError:  # pragma: no cover
            raise HTTPNotFound()

        range_request = self.request.headers["Range"]
        try:
            start, _, end = range_request.split("bytes=")[-1].partition("-")
            start = int(start)
            if len(end) == 0:
                # bytes=0- is valid
                end = file.size - 1
            end = int(end) + 1  # python is inclusive, http is exclusive
        except (IndexError, ValueError):
            # range errors fallback to full download
            raise HTTPRequestRangeNotSatisfiable(
                content={
                    "reason": "rangeNotParsable",
                    "range": range_request
                },
                headers={"Content-Range": f"bytes */{file.size}"},
            )
        if start > end or start < 0:
            raise HTTPRequestRangeNotSatisfiable(
                content={
                    "reason": "invalidRange",
                    "range": range_request,
                    "message": "Invalid range"
                },
                headers={"Content-Range": f"bytes */{file.size}"},
            )
        if end > file.size:
            raise HTTPRequestRangeNotSatisfiable(
                content={
                    "reason": "invalidRange",
                    "range": range_request,
                    "message": "Invalid range, too large end value",
                },
                headers={"Content-Range": f"bytes */{file.size}"},
            )

        logger.debug(f"Range request: {range_request} {self.request}")
        if extra_headers is None:
            extra_headers = {}
        extra_headers["Content-Range"] = f"bytes {start}-{end - 1}/{file.size}"

        try:
            download_resp = await self.prepare_download(
                disposition,
                filename,
                content_type,
                end - start,
                extra_headers,
                status=206,
                range_supported=range_supported,
                **kwargs,
            )

            found = 0
            try:
                async for chunk in self.file_storage_manager.read_range(
                        start, end):
                    found += len(chunk)
                    logger.info(
                        f"Got chunk {range_request}: {len(chunk)}/{end - start}|{found}"
                    )
                    await download_resp.write(chunk)
            except RangeException:
                raise HTTPRequestRangeNotSatisfiable(
                    content={
                        "reason": "rangeNotFound",
                        "range": range_request,
                        "message": "Could not read range correctly",
                    },
                    headers={"Content-Range": f"bytes */{file.size}"},
                )
        except (asyncio.CancelledError, ConnectionRefusedError,
                ConnectionResetError):  # pragma: no cover
            logger.info(f"Range cancelled: {range_request} {self.request}")
            # when supporting range headers, the browser will
            # cancel downloads. This is fine.
            raise HTTPClientClosedRequest()
        finally:
            await download_resp.write(eof=True)
            return download_resp