ALERT: The project is still in its initial stage. Some implementations might change in the future. Feel free to try and report any issues. Pull requests and project recommendations are more than welcome!
Tus PHP
Resumable file upload in PHP using tus resumable upload protocol v1.0.0.
Overview
tus is a HTTP based protocol for resumable file uploads. Resumable means you can carry on where you left off without re-uploading whole data again in case of any interruptions. An interruption may happen willingly if the user wants to pause, or by accident in case of a network issue or server outage.
Resources
Installation
Pull the package via composer.
$ composer require ankitpokhrel/tus-php:dev-master
Usage
Server
This is how a simple server looks like.
// server.php
$server = new \TusPhp\Tus\Server('redis'); // Leave empty for file based cache
$response = $server->serve();
$response->send();
exit(0); // Exit from current PHP process.
You need to rewrite your server to respond to a specific endpoint. For example:
Nginx
# nginx.conf
location /files {
try_files $uri $uri/ /server.php?$query_string;
}
Apache
# .htaccess
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^files/?(.*)?$ /server.php/$1 [QSA,L]
Default max upload size is 0 which means there is no restriction. You can set max upload size as described below.
$server->setMaxUploadSize(100000000); // 100 mb in bytes
Client
The client can be used for creating, resuming and/or deleting uploads.
$client = new \TusPhp\Tus\Client($baseUrl, 'redis'); // Leave second parameter empty for file based cache
// Optional. If a key is not set explicitly, the system will generate a unique uuid.
$key = 'your unique key';
$client->setKey($key)->file('/path/to/file', 'filename.ext');
// Create and upload a chunk of 1mb
$bytesUploaded = $client->upload(1000000);
// Resume, $bytesUploaded = 2mb
$bytesUploaded = $client->upload(1000000);
// To upload whole file, skip length param
$client->file('/path/to/file', 'filename.ext')->upload();
To check if the file was partially uploaded before, you can use getOffset
method. It returns false if the upload
isn't there or invalid, returns total bytes uploaded otherwise.
$offset = $client->getOffset(); // 2000000 bytes or 2mb
Delete partial upload from the cache.
$client->delete($key);
By default, the client uses /files
as an API path. You can change it with setApiPath
method.
$client->setApiPath('/api');
By default, the server will use sha256
algorithm to verify the integrity of the upload. If you want to use a different hash algorithm, you can do so by
using setChecksumAlgorithm
method. To get the list of supported hash algorithms, you can send OPTIONS
request to the server.
$client->setChecksumAlgorithm('crc32');
Extension support
- The Creation extension is mostly implemented and is used for creating the upload. Deferring the upload's length is not possible at the moment.
- The Termination extension is implemented which is used to terminate completed and unfinished uploads allowing the Server to free up used resources.
- The Checksum extension is implemented, the server will use
sha256
algorithm by default to verify the upload. - The Expiration extension is implemented, details below.
- This Concatenation extension is implemented except that the server is not capable of handling unfinished concatenation.
Expiration
The Server is capable of removing expired but unfinished uploads. You can use the following command manually or in a cron job to remove them.
$ ./vendor/bin/tus tus:expired --help
Usage:
tus:expired [<cache-adapter>]
Arguments:
cache-adapter Cache adapter to use, redis or file. Optional, defaults to file based cache.
eg:
$ ./vendor/bin/tus tus:expired redis
Concatenation
The Server is capable of concatenating multiple uploads into a single one enabling Clients to perform parallel uploads and to upload non-contiguous chunks.
// Actual file key
$uploadKey = uniqid();
$client->setKey($uploadKey)->file('/path/to/file', 'chunk_a.ext');
// Upload 10000 bytes starting from 1000 bytes
$bytesUploaded = $client->seek(1000)->upload(10000);
$chunkAkey = $client->getKey();
// Upload 1000 bytes starting from 0 bytes
$bytesUploaded = $client->setFileName('chunk_b.ext')->seek(0)->upload(1000);
$chunkBkey = $client->getKey();
// Upload remaining bytes starting from 11000 bytes (10000 + 1000)
$bytesUploaded = $client->setFileName('chunk_c.ext')->seek(11000)->upload();
$chunkCkey = $client->getKey();
// Concatenate partial uploads
$client->setFileName('actual_file.ext')->concat($uploadKey, $chunkAkey, $chunkBkey, $chunkCkey);
Additionally, the server will verify checksum against the merged file to make sure that the file is not corrupt.
Middleware
You can manipulate request and response of a server using a middleware. Middleware can be used to run a piece of code before a server calls the actual handle method. You can use middleware to authenticate a request, handle CORS, whitelist/blacklist an IP etc.
Creating a Middleware
In order to create a middleware, you need to implement TusMiddleware
interface. The handle method provides request and response object for you to manipulate.
<?php
namespace Your\Namespace;
use TusPhp\Request;
use TusPhp\Response;
use TusPhp\Middleware\TusMiddleware;
class Authenticated implements TusMiddleware
{
// ...
/**
* {@inheritDoc}
*/
public function handle(Request $request, Response $response)
{
// Check if user is authenticated
if (! $this->user->isLoggedIn()) {
throw new UnauthorizedHttpException('User not authenticated');
}
$request->getRequest()->headers->set('Authorization', 'Bearer ' . $this->user->token());
}
// ...
}
Adding a Middleware
To add a middleware, get middleware object from server and simply pass middleware classes.
$server->middleware()->add(Authenticated::class, AnotherMiddleware::class);
Or, you can also pass middleware class objects.
$authenticated = new Your\Namespace\Authenticated(new User());
$server->middleware()->add($authenticated);
Skipping a Middleware
If you wish to skip or ignore any middleware, you can do so by using the skip
method.
$server->middleware()->skip(Cors::class, AnotherMiddleware::class);
Uppy
Compatible withUppy is a sleek, modular file uploader plugin developed by same folks behind tus protocol. You can use uppy to seamlessly integrate official tus-js-client with tus-php server. Check out more details in uppy docs.
uppy.use(Tus, {
endpoint: 'https://tus-server.yoursite.com/files/', // use your tus endpoint here
resume: true,
autoRetry: true,
retryDelays: [0, 1000, 3000, 5000]
})
Setting up a dev environment and/or running examples locally
An ajax based example for this implementation can be found in examples/
folder. You can either build and run it using docker or use kubernetes locally with minikube.
Docker
Make sure that docker and docker-compose are installed in your system. Then, run docker script from project root.
$ bin/docker.sh
Now, the client can be accessed at http://0.0.0.0:8080 and server can be accessed at http://0.0.0.0:8081. Default API endpoint is set to/files
and uploaded files can be found inside uploads
folder. All docker configs can be found in docker/
folder.
Kubernetes with minikube
Make sure you have minikube and kubectl are installed in your system. Then, build and spin up containers using k8s script from project root.
$ bin/k8s.sh
The script will set minikube docker env, build all required docker images locally, create kubernetes objects and serve client at port 30020
. After successful build,
the client can be accessed at http://192.168.99.100:30020 and server can be accessed at http://192.168.99.100:30021.
The script will create 1 client replica and 3 server replicas by default. All kubernetes configs can be found inside k8s/
folder, you can tweak it as required.
You can use another helper script while using minikube to list all uploaded files, login to redis and clear redis cache.
# List all uploads
$ bin/minikube.sh uploads
# Login to redis
$ bin/minikube.sh redis
# Clear redis cache
$ bin/minikube.sh clear-cache
Since the server supports tus expiration extension, a cron job is set to run once a day at midnight to free server resources. You can adjust it as required in k8s/cron.yml
.
Contributing
- Install PHPUnit and composer if you haven't already.
- Install dependencies
$ composer install
- Run tests with phpunit
$ ./vendor/bin/phpunit
- Validate changes against PSR2 Coding Standards
$ ./vendor/bin/php-cs-fixer fix <changes> --rules=@PSR2,not_operator_with_space,single_quote
Questions about this project?
Please feel free to report any bug found. Pull requests, issues, and project recommendations are more than welcome!