Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to download a (large) file with a typo3 extbase controller action

I have a controller with a download action in TYPO3. For some time I have implemented it like this and it is working:

function downloadAction() {
  // ...
  // send headers ...
  // ...
  if ($fh = fopen($this->file, 'r')) {
    while (!feof($fh)) {
      echo fread($fh, $chunkSize); // send file in little chunks to output buffer
      flush();
    }
    fclose($fh);
  }
  exit; // Stopp middlewares and so on.
}

I am wondering if I should/could return an object of type ResponseInterface in TYPO3 11. So it is obviously that exit stops the middleware pipeline and other things and I don't really know if there are any side effects.

I tried the following to return a ResponseInterface:

function downloadAction(): ResponseInterface {
  // ...
  return $this->responseFactory->createResponse();
    ->withAddedHeader(...)
    // ...
    ->withBody($this->streamFactory->createStreamFromFile($this->file))
    ->withStatus(200, 'OK');
}

The problem is that the solution with the ResponseInterface works only with small files. The problem seems to be in Bootstrap::handleFrontendRequest().

protected function handleFrontendRequest(ServerRequestInterface $request): string
{
  // ...
  if (headers_sent() === false) {
    // send headers      
  }
  $body = $response->getBody(); // get the stream
  $body->rewind();
  $content = $body->getContents(); // Problem: Read the hole stream into RAM instead of
                                   // sending it in chunks to the output buffer
  // ...
  return $content;
}

TYPO3 tries to read the whole stream/file into RAM. That crashes the application.

So how should I trigger a file download these days with TYPO3?

like image 713
koalabruder Avatar asked Sep 11 '25 12:09

koalabruder


1 Answers

Use the method \TYPO3\CMS\Core\Resource\ResourceStorage::streamFile to create a response that will emit the file contents without reading them into a variable first and throw a ImmediateResponseException to circumvent the extbase bootstrap response handling.

BTW: This will only work with FAL drivers that support proper streaming. EXT aus_driver_amazon_s3 does not.

Example

<?php

use TYPO3\CMS\Core\Http\ImmediateResponseException;
use TYPO3\CMS\Core\Resource\ResourceStorage;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;

class ExtbaseController extends ActionController
{
    public function emitFile(): never // never return type requires PHP 8.1
    {
        $resourceStorage = GeneralUtility::makeInstance(ResourceStorage::class);
        $file = $resourceStorage->getFile('1:/myBigFile.zip');
        $response = $resourceStorage->streamFile($file, true);
        throw new ImmediateResponseException($response);
    }
}

Edit: Since you are skipping the extbase shutdown procedure, you either must not update any domain model or flush the persistence manager manually.

like image 160
VerteXVaaR Avatar answered Sep 13 '25 11:09

VerteXVaaR