Skip to content

Instantly share code, notes, and snippets.

@sentenza
Last active July 17, 2017 21:35
Show Gist options
  • Save sentenza/750f92e4a127b410a725b15f8a2c6537 to your computer and use it in GitHub Desktop.
Save sentenza/750f92e4a127b410a725b15f8a2c6537 to your computer and use it in GitHub Desktop.
PHP and ionic file upload

Transfer-encoding: chunked

When the server needs to send large amount of data, chunked encoding is used by the server because it did not exactly know how big (length) the data is going to be. In HTTP terms, when server sends response Content-Length header is omitted by the server. Instead server writes the length of current chunk in hexadecimal format followed by \r\n and then chunk, followed by \r\n (Content begins with chunk size in hex followed by chunk)

This feature can be used for progressive rendering; however the server needs to flush the data as much as possible so that client can render content progressively (in case of html,css etc)

This feature is often used when server pushes data to the client in large amounts - usually in giga bytes.

Source: https://stackoverflow.com/a/45086785/1977778

Chunked directive explained by Mozilla

Data is sent in a series of chunks. The Content-Length header is omitted in this case and at the beginning of each chunk you need to add the length of the current chunk in hexadecimal format, followed by '\r\n' and then the chunk itself, followed by another '\r\n'. The terminating chunk is a regular chunk, with the exception that its length is zero. It is followed by the trailer, which consists of a (possibly empty) sequence of entity header fields.

HTTP/1.1 200 OK 
Content-Type: text/plain 
Transfer-Encoding: chunked

7\r\n
Mozilla\r\n 
9\r\n
Developer\r\n
7\r\n
Network\r\n
0\r\n 
\r\n

Resources

<?php
define('MAX_FILE_SIZE_UPLOAD', 50000000); // 50Mb
header('Content-Type: application/json');
// Make sure file is not cached (as it happens for example on iOS devices)
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
header('Cache-Control: no-store, no-cache, must-revalidate');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
function validationFail($result, $title = 'Invalid request.', $code = 400) {
header('HTTP/1.1 '.$code.' '.$title);
echo(json_encode(array(
'success' => false,
'result' => $result,
'error' => $title
)));
exit();
}
// We make sure no error occured
if (empty($_FILES['file']) || intval($_FILES['file']['error']) > 0) {
validationFail('bad_request', 'An error occured.');
}
// We only allow CSV files. Of course, you can adapt this to your need
if ($_FILES['file']['type'] !== 'text/csv') {
@unlink($_FILES['file']['tmp_name']);
validationFail('bad_request', 'Please upload a CSV file.');
}
$tmpName = $_FILES['file']['tmp_name'];
$tmpDirectory = ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir();
// Security verification
if (!is_uploaded_file($tmpName)) {
validationFail('bad_request', 'An error occured.');
}
// We start a session.
// This is required in order to track the sender of a file (not mix request)
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
if (isset($_SERVER['HTTP_CONTENT_RANGE']) && !empty($_SERVER['HTTP_CONTENT_RANGE']) && isset($_SERVER['CONTENT_LENGTH']) && is_numeric($_SERVER['CONTENT_LENGTH'])) {
// We get the size of the file uploaded from the client (real, final size)
$filesize = intval(substr($_SERVER['HTTP_CONTENT_RANGE'], strpos($_SERVER['HTTP_CONTENT_RANGE'], '/') + 1));
if ($filesize > MAX_FILE_SIZE_UPLOAD) {
validationFail('bad_request', 'File size allowed must be lower than 10Mb.');
}
if (isset($_SESSION['filename']) && is_file($tmpDirectory.'/'.$_SESSION['filename'])) {
file_put_contents($tmpDirectory.'/'.$_SESSION['filename'], fopen($tmpName, 'r'), FILE_APPEND);
unlink($tmpName); // We delete the file once we copied it, in order to not use unecessary storage
// We stop here if the file is not completely loaded
$currentSize = filesize($tmpDirectory.'/'.$_SESSION['filename']);
if ($currentSize < $filesize) {
exit('{"size": '.$currentSize.'}');
} else {
$tmpName = $tmpDirectory.'/'.$_SESSION['filename'];
}
} else {
$_SESSION['filename'] = uniqid().'.csv.part';
move_uploaded_file($tmpName, $tmpDirectory.'/'.$_SESSION['filename']);
exit('{"size": '.filesize($tmpDirectory.'/'.$_SESSION['filename']).'}');
}
}
// The file has been completely uploaded.
// You can do whatever you want now
// Like moving the file, which is located in $tmpName
// Note: don't use move_uploaded_file because the chuncked part created a new file and this function will fail.
rename($tmpName, dirname(__FILE__).'/files/'.uniqid().'.csv');
exit('{"success": true}');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment