Virtual hosting: how to upload files larger than 100M in a PHP website program.

Published: 2023-12-26

```html

Problem

  1. The upload file size limit on the virtual host is 100m,

  2. Sometimes very large file uploads occur, and the upload process takes a very long time,

  3. The server limit may be set for the upload file size, returning "413 request entity too large"

Overall Logic

  1. Frontend: When uploading a file, split the file into chunks; when making the request, include the current chunk number and total number of chunks.

  2. Backend: Save the file according to chunks; when the last chunk is uploaded, merge the files and delete the chunk files.


Sample code download:   http://downinfo.myhostadmin.net/upload.zip


This is only a demo; production environments need stronger upload filtering


Frontend upload.html 

<html>
<head>
<meta charset="utf-8">
<title>Chunked Upload</title>
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
</head>
<body>
<form method="post" id="myForm" enctype="multipart/form-data">
    <input type="file" id="file" name="file">
    <input type="submit" id="submit" value="Upload">
</form>
<script type="text/javascript">
    $('#submit').on('click', function(e) {
        // Prevent default form submission
        e.preventDefault();
        // Get attributes
        var myfile = $('#file')[0].files[0];
        var ext = myfile.name.split('.').pop();
        // Check whether ext is a video
        var is_video = ['mp4', 'avi', 'rmvb', 'mkv'].indexOf(ext) > -1;
        var fileId = getFileIdentifier(myfile);
        // Data slicing
        var chunks = fileSlice(myfile, is_video);
        console.log(chunks)
        // Send split data chunks
        sendChunk(fileId, chunks);
    })
    function getFileIdentifier(file){
     // Get file identifier
     return file.name
     //return file.size + file.name;
   }
   function fileSlice(file, is_video) {
     // The chunk size should not be too large; if too large, corresponding configuration of nginx and php is required
        var chunkSize =  1024 * 1024 * 4;  // slice size control
        // 1. Initialize data
        var totalSize = file.size;
        var start = 0;
        var end = start + chunkSize;
        var chunks = [];
        // 2. Use the slice method provided by bolb to slice
        while (start < totalSize) {
            if (is_video) {
                console.log('Video')
                var chunk = file.slice(start, end, 'video/mp4');
            } else {
                console.log('Image')
                var chunk = file.slice(start, end);
            }
            chunks.push(chunk);
            start = end;
            end += chunkSize;
        }
        // 3. Return the chunk array chunk[]
        return chunks;
    }
    function sendChunk(id, chunks){
        // Submit one by one
        // Used to ensure that AJAX sending is completed
        var task = [];
        var totalPage=0;
        var i=0;
        totalPage=chunks.length-1;
        var fileExt = id.substr(id.lastIndexOf('.') + 1);
        chunks.forEach(function(chunk, index){
            var formData = new FormData();
            formData.append('file',chunk);
            formData.append("fileName",id);
            formData.append("totalPage",totalPage);
            formData.append("page",index);
            $.ajax({
                type: "POST",
                url: 'upload.php',
                data: formData,
                contentType: false,
                processData: false,
                dataType:"json",
                async:false,
                success: function(data){
                    // Remove completed task
                    task.pop();
                    if (data['status']==200){
                        console.log(data['downUrl']);
                        alert(data['downUrl']); // Return the uploaded file path
                    }
                }
                })
            task.push('file Working');
        })
    }
</script>
</body>
</html>

Backend upload.php

<?php
if (empty($_POST)) {
    $res = ['status' => 500];
    echo json_encode($res);
    exit;
}
// Create upload directory
if(!is_dir('upload')){
   mkdir('upload', 0777);
 }
// Create upload cache directory
if(!is_dir('tmp')){
   mkdir('upload', 0777);
 }
 
$fileName          = isset($_POST['fileName'])?$_POST['fileName']:'';
$page              = isset($_POST['page'])?$_POST['page']:'';
$totalPage         = isset($_POST['totalPage'])?$_POST['totalPage']:'';
$fileTmpName       = isset($_FILES['file'])?$_FILES['file']['tmp_name']:'';
$status  = 206;
$downUrl = '';
if ($fileName== ''|| $page == '' || $totalPage == '' || $fileTmpName == '') {
    $res = ['status' => 500];
    echo json_encode($res);
    exit();
}

// Path where the uploaded file will be saved
$fname = sprintf('./tmp/%s-%s', $fileName, $page);
$data  = file_get_contents($fileTmpName);
file_put_contents($fname, $data);
// Merge chunk files
//if ($save) {
if ($totalPage ==$page) {
    $uploadFileName = sprintf('./upload/%s%s', time(),$fileName); 
    $status         = 200;
    // Merge files and delete chunk files  
    for ($i = 0; $i<=$totalPage; $i++) {
        $tmp =  sprintf('./tmp/%s-%s', $fileName, $i);
        $data = file_get_contents($tmp);
        file_put_contents($uploadFileName, $data, FILE_APPEND);
        @unlink($tmp);
    }
    $dir = trim(dirname($_SERVER['PHP_SELF']), '/');
    if ($dir!='') {
      $dir .= '/';
    }
    $downUrl = sprintf('%s://%s/%s%s', $_SERVER['REQUEST_SCHEME'], $_SERVER['HTTP_HOST'], $dir,trim($uploadFileName, './'));
    $res = ['status' => $status,'downUrl' => $downUrl];
    echo json_encode($res);
    exit();
}
// Return upload status
$res = ['status' => $status,'downUrl' => $downUrl];
echo json_encode($res);


Running Effect

image.png

```