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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With