Skip to content

Storing compressed files

In many applications we allow our users to upload files. Storing them can take up a lot of space.

Instead of storing these files as is, we could store them gzipped to save space and return them gzipped or uncompressed depending of the supported features.

Here's an example:

use Innmind\OperatingSystem\Factory;
use Innmind\Http\Factory\ServerRequest\ServerRequestFactory;
use Innmind\Filesystem\Name;
use Innmind\Encoding\Gzip;
use Innmind\Url\Path;
use Innmind\Http\{
    Response,
    Response\StatusCode,
    ResponseSender,
};

$os = Factory::build();
$serverRequest = ServerRequestFactory::default($os->clock())();

$response = $serverRequest
    ->files()
    ->under('tsv')
    ->get('users')
    ->map(static fn($file) => $file->content()
    ->map(Gzip::compress())
    ->map(static fn($content) => File::named('users.tsv.gz', $content))
    ->map(
        static fn($file) => $os
            ->filesystem()
            ->mount(Path::of('path/to/stored/data/'))
            ->add($file)),
    )
    ->match(
        static fn() => Response::of(
            StatusCode::created,
            $serverRequest->protocolVersion(),
        ),
        static fn() => Response::of(
            StatusCode::badRequest,
            $serverRequest->protocolVersion(),
        ),
    );

(new ResponseSender($os->clock()))($response);

This code will take any file uploaded in the key tsv[users], gzip it and write it in the path/to/stored/data/ directory under the name users.tsv.gz and return a 201 HTTP response. If the upload failed it will return a 400 response.

And for the code streaming this file:

use Innmind\OperatingSystem\Factory;
use Innmind\Http\Factory\ServerRequest\ServerRequestFactory;
use Innmind\Filesystem\Name;
use Innmind\Encoding\Gzip;
use Innmind\Url\Path;
use Innmind\Http\{
    Response,
    Response\StatusCode,
    ResponseSender,
    Headers,
    Header\ContentType,
    Header\ContentEncoding,
};

$os = Factory::build();
$serverRequest = ServerRequestFactory::default($os->clock())();

$acceptGzip = $serverRequest
    ->headers()
    ->get('accept-encoding')
    ->map(static fn($header): bool => $header->values()->any(
        static fn($value) => $value->toString() === 'gzip',
    ))
    ->match(
        static fn(bool $accept): bool => $accept,
        static fn() => false,
    );

$response = $os
    ->filesystem()
    ->mount(Path::of('path/to/stored/data/'))
    ->get(Name::of('users.tsv.gz'))
    ->map(static fn($file) => match ($acceptGzip) {
        true => Response::of(
            StatusCode::ok,
            $serverRequest->protocolVersion(),
            Headers::of(
                ContentEncoding::of('gzip'),
                ContentType::of('text', 'tab-separated-values'),
            ),
            $file->content(),
        ),
        false => Response::of(
            StatusCode::ok,
            $serverRequest->protocolVersion(),
            Headers::of(
                ContentType::of('text', 'tab-separated-values'),
            ),
            Gzip::decompress()($file->content()),
        ),
    })
    ->match(
        static fn($response) => $response,
        static fn() => Response::of(
            StatusCode::notFound,
            $serverRequest->protocolVersion(),
        ),
    );

(new ResponseSender($os->clock()))($response);

Here we try to load the users.tsv.gz file, we check if the caller accepts a gzipped content, if so we return the file as is via a 200 HTTP response and if not we decompress the file and return it. And if the file doesn't exist we return a 400 response.