floppy / server
Floppy is a file storage library. This library allows you to create your own storage file server that simplify file uploading and usage uploaded file.
Requires
- floppy/common: >=0.1.0,<0.2.0
- imagine/imagine: >=0.6.0,<0.7.0
- pimple/pimple: 2.0.*
- ps/image-optimizer: ~1.0
- ps/imagine-extra: >=1.0.0,<2.0.0
- psr/log: 1.0.0
- symfony/event-dispatcher: >=2.1,<2.5
- symfony/filesystem: >=2.1,<2.5
- symfony/http-foundation: >=2.1,<2.5
This package is not auto-updated.
Last update: 2024-12-17 03:51:11 UTC
README
FloppyServer is a file storage library. There is also FloppyClient library and symfony2 bundle that adds few integration points to symfony2 applications.
The main goal of Floppy-family libraries is to make dealing with every aspect of files as easy as possible. Floppy-family libraries improve dealing with: file uploading, file storing, generating various file versions (thumbnails, watermarks etc.), representing files in Entities and more.
When your application uses images and other files intensively, there is a problem with generation required thumbnails, code to handle file upload is full of boilerplate, dealing with files is awkward. There are very good bundles to Imagine that partially resolves first problem, but not other problems. This documentation covers only FloppyServer library, if you want to see how to use FloppyServer from the client side, check FloppyBundle and FloppyClient libraries.
File optimizations
FloppyServer is able to do some extra processing on uploaded file. It is able to resize large images before store it to
storage and execute various optimizations on images thanks to ImageOptimizer library. Resizing images greater
that Full HD size is enabled by default, but other optimizations are disabled and you could enable it manually thanks to
fileHandlers.image.enableOptimizations
option. Be aware of that, image optimizations could be cpu extensive, however
default optimizers are as lightweight as possible.
File filters
Floppy supports applying filters on files, especially on images. Original, optimized files are stored in storage, but it is possibility to generate different version of the file on the fly when users are requesting for the file. Different version of the file could be thumbnails, images with watermark, cropped images and more. See FloppyClient docs for more details about filters, supported filters are similar to filters from LiipImagineBundle. After generating thumbnail or other version of the file, it is cached and on the next request for the same version, file would be fetched from cache, so processing is done once.
Extensible architecture
FloppyServer is designed to handle multiple clients, so you can setup one instance of FloppyServer and use it in many applications. This library is fully customizable and extensible, you can define what file types can be stored, what file types would be processed before sending to client (for example generating thumbnails, adding watermarks etc), what file types would and how to be optimized, what security credentials would be required to upload / download files and more.
CORS / crossdomain.xml / clientaccesspolicy.xml are supported by adding simple entry into configuration so you can upload to FloppyServer directly from web browser even if FloppyServer instance is running on different host than your app. There is also nice symfony2 integration (see FloppyBundle documentation), so uploading files and using files in application is very simple and elegant.
Documentation
ToC
Simple setup example
To create your floppy server application you should create empty composer project, add dependency to floppy/server package and create following index.php file.
composer.json file:
"require": {
"floppy/server": "*"
}
web/index.php file:
require_once __DIR__.'/../vendor/autoload.php'; $requestHandlerFactory = new \Floppy\Server\RequestHandler\RequestHandlerFactory(); $requestHandler = $requestHandlerFactory->createRequestHandler(array( 'storage.dir' => __DIR__.'/../storage', 'secretKey' => 'super-secret-key', 'cors.allowedOriginHosts' => array( '*.your-client-host.com', ), //enable images optimizations 'fileHandlers.image.enableOptimizations' => true, )); $request = \Symfony\Component\HttpFoundation\Request::createFromGlobals(); $response = $requestHandler->handle($request); $response->send();
Virtual host should be created in web directory, storage will be in web/../storage dir.
Detailed configuration and extension points
Security
There are two levels to define security rules:
- firewall - deny or allow to execute certain action, you can restrict for example access to upload action to certain ip address. By default there are no firewalls so everyone is able to upload files.
- security rules - more specific and customizable security rules, you can take decision to grand or deny access to action having information about accessed file, extra information provided by client etc.
To define firewall for given action, use this code:
$requestHandler = $requestHandlerFactory->createRequestHandler(array( //other options... 'requestHandler.firewall.download' => function($container){ //it can be also requestHandler.firewall.upload return function(\Symfony\Component\HttpFoundation\Request $request){ if(/* if access should be denied */) { throw new \Floppy\Server\RequestHandler\Exception\AccessDeniedException(); } }; }, //... ));
FloppyServer uses Pimple2 library as dependency injection container, so $container variable in Closure param is Pimple instance.
You can also use security rules to have access to information about file that is being uploaded or downloaded while taking decision about permissions:
$requestHandler = $requestHandlerFactory->createRequestHandler(array( //other options... 'action.upload.securityRule' => function($container){ //it can be also action.download.securityRule return //implementation of Floppy\Server\RequestHandler\Security\Rule interface }, //... ));
Floppy\Server\RequestHandler\Security\Rule
interface has one method: processRule(Request $request, HasFileInfo $object)
.
$object
can be a Floppy\Common\FileSource when action is upload, Floppy\Common\FileId when action is download. Current
only implementation of Security\Rule
interface is PolicyRule
that is able to check expiration of request and type of uploaded
file. PolicyRule
checks two parameters from Request
: policy
and signature
. Policy
contains information about
request expiration time and supported file types of the request, signature
ensures policy was send by approved client
and was not modified by third-party entity. PolicyRule
is default security rule. By default when in Request
policy is
missing, the rule allows perform requested action (download / upload). You can change this behaviour and configure
PolicyRule
to throw security exception when policy is missing. This is able thanks to two options:
$requestHandler = $requestHandlerFactory->createRequestHandler(array( //... 'action.upload.securityRule.requirePolicy' => true, 'action.download.securityRule.requirePolicy' => true, //... ));
When you define PolicyRule to upload (or/and download) action to require policy, every upload (or/and download) request should contains
security credentials. To generate proper url to file from FloppyClient, you should provide security credentials in third
argument of Floppy\Client\UrlGenerator
::generate($fileId, $fileType = null, $credentialAttributes = array())
method,
example:
$url = $urlGenerator->generate(new \Floppy\Common\FileId('someid.jpg'), 'image', array('expiration' => time() + 60));
To attach security credentials to upload action using Floppy\Client\FloppyClient class you should use second argument of
FloppyClient
::upload
method:
$flieId = $client->upload($someFileSource, array('expiration' => time() + 60, 'file_types' => array('image')));
If you use FloppyBundle you have possibility to provide security credentials in floppy_file
form type:
//for example in controller $form = $this->createFormBuilder($document) ->add('file', 'floppy_file', array('credentials' => array('expiration' => time()+500, 'file_types' => array('image', 'file')))) ->getForm();
You can also provide credentials while generating url from floppy_url twig function:
<img src="{{ floppy_url(document.file, {
"expiration": date().timestamp + 60, "some-custom-attr": "custom-value"
}) }}" />
If you want to add extra security attributes, you can extend PolicyRule and add support for your extra attributes, for example user id etc.
File handlers
There are two file groups by default: images and other files. Images has its own FileHandler
that adds support for
processing before storing and sending file to a client. If you want to create your own files' group you should create
Floppy\Server\FileHandler\FileHandler
implementation (Floppy\Server\FileHandler\AbstractFileHandler
implements the most
part of file handler).
By default file will be processed by image
file handler when file has jpg, jpeg, png or gif extension and has
image/png, image/jpg, image/jpeg, image/pjpeg or image/gif mime type. file
file handler is turned off by default, so
you should explicitly configure what extensions and mime-types are supported. Extensions and mime types can be configured
by options: fileHandlers.image.mimeTypes
, fileHandlers.image.extensions
, fileHandlers.file.mimeTypes
,
fileHandlers.file.extensions
. Example:
//add support for text files - both mimeTypes and extensions must be configured $requestHandler = $requestHandlerFactory->createRequestHandler(array( 'fileHandlers.file.mimeTypes' => array('text/plain'), 'fileHandlers.file.extensions' => array('txt'), ));
There is an example how to configure your own file handler:
//add support for text files - both mimeTypes and extensions must be configured $requestHandler = $requestHandlerFactory->createRequestHandler(array( 'fileHandlers' => function($container){ return array( //default "image" file handler $container['fileHandlers.image.name'] => $container['fileHandlers.image'], 'custom' => new YourCustomFileHandler(new CustomPathMatcher(), array()), //default "file" file handler $container['fileHandlers.file.name'] => $container['fileHandlers.file'], ); } ));
Recommended setups
FloppyServer can be setup in few ways, there are 4 recommended setups. Before reading this section read Simple setup example section, because it says how composer.json and index.php files should to be.
Public directory + apache htaccess (or nginx replacement) fallback for unexisting files
The idea is to put your storage directory to public directory and configure htaccess to fallback requests to unexisting files to your index.php file. This is the most efficient way to setup Floppy. When requested thumbnail exists (because it was generated in the past) server directly send file without running php code. When thumbnail is missing, it will be created by FloppyServer.
Advantages:
- performance
- easy to setup
Disadvantages:
- uploaded and generated files are publicly visible (but it names are difficult to guess), even if you configured security credentials to access to files - so it is security issue
Example:
//part of web/index.php file in public directory $requestHandler = $requestHandlerFactory->createRequestHandler(array( 'storage.dir' => __DIR__,//path to public directory 'secretKey' => 'super-secret-key', 'action.cors.allowedOriginHosts' => array( '*.your-client-host.com', ), )); //example of minimal web/.htaccess file RewriteEngine On RewriteBase / RewriteCond %{REQUEST_FILENAME} !-f RewriteRule (.*) index.php/$1
Non-public directory + apache x-sendfile (or nginx replacement)
The alternative is to setup FloppyServer in non-public directory and adds x-sendfile (or nginx replacement) support to gain performance.
Advantages:
- more secure than "public directory" solution, every request runs php code so eventual security checks are done always
Disadvantages:
- performance is worst than in "public directory" solution, every request runs php code ;) But x-sendfile improves performance
- you should have configured x-sendfile module (or nginx replacement) on your server
Example:
//part of web/index.php file in public directory $requestHandler = $requestHandlerFactory->createRequestHandler(array( 'storage.dir' => __DIR__.'/../storage', //path to private directory 'secretKey' => 'super-secret-key', 'action.cors.allowedOriginHosts' => array( '*.your-client-host.com', ), 'action.download.responseFactory' => new \Floppy\Server\RequestHandler\XSendFileDownloadResponseFactory(), )); //web/.htaccess file <IfModule mod_xsendfile.c> <files index.php> XSendFile on </files> </IfModule> //your virtual host configuration <VirtualHost ...> XSendFilePath /absolute/path/to/your/storage/directory </VirtualHost>
More about x-sendfile configuration you can read in documentation
If you have no x-sendfile mod on your webserver, you should use configuration from Simple setup example section. It will be working but would be inefficient.
Both non-public and public directory
You can also setup your floppy server to store files in two root directories. One of those directories should be non-public. Client that is uploading file says whether file should be stored in public or non-public directory.
Example:
//part of web/index.php file in public directory $requestHandler = $requestHandlerFactory->createRequestHandler(array( 'storage.dir' => __DIR__, //path to public directory 'storage.dir.private' => __DIR__.'/../storage', //path to private directory 'secretKey' => 'super-secret-key', 'action.cors.allowedOriginHosts' => array( '*.your-client-host.com', ), //if you have xsendfile on your webserver this response factory is recommended 'action.download.responseFactory' => new \Floppy\Server\RequestHandler\XSendFileDownloadResponseFactory(), )); //eventual xsendfile configuration as in previous example
On the client side you should tell do you want to upload private or public file (default value is public):
(in Security section is more info about usage $client, floppy form and twig function in symfony2 bundle)
//using Floppy\Client\FloppyClient $client->upload($someFileSource, array('access' => 'private')); //using symfony2 form $form = $this->createFormBuilder($document) ->add('file', 'floppy_file', array('credentials' => array('access' => 'private'))) ->getForm(); //generate url to private file using twig function from symfony2 bundle <img src="{{ floppy_url(document.file.with({ "thumbnail": { "size": [50, 50] } }), { "access": "private" }) }}" />
Bundle FloppyServer into your symfony2 application
Not yet available ;)
License
This project is under MIT license.