• Час читання ~2 хв
  • 01.03.2023

Сьогодні ми завантажуємо файл шматками за допомогою Livewire. Завантажте свої файли близько до своїх користувачів за допомогою Fly.io, ви можете запустити свій додаток Laravel!

Сервери налаштовані таким чином, щоб обмежити розмір запитів, які вони можуть приймати. Це робиться, щоб уникнути тривалого часу обробки, що призводить до недоступності та потенційних ризиків безпеки, пов'язаних з обробкою великих запитів за один раз

Що відбувається, коли користувач просить завантажити файл, що перевищує встановлені ліміти? Влучно, завантаження не вдасться, або з користувацьким повідомленням, яке ми пишемо, або з помилкою коду стану за замовчуванням 413, повернутою нашим сервером.

Ми зіткнулися зі скрутним становищем, яке стосувалося розробників до нас, і буде розбиратися з розробниками ще довго після себе: обробка великих завантажень файлів.

Проблема

Очевидним підходом, який слід застосувати, є оновлення наших обмежень конфігурації. Ми можемо збільшити кілька обмежень конфігурації в самому нашому сервері та PHP.

Проблема в тому, що це не зовсім динамічно. Завантаження за розміром файлу може збільшити понаднормову роботу, і це змушує нас переналаштовувати наші обмеження. А зі збільшеними обмеженнями також збільшується час обробки запитів.

Хіба ми не можемо уникнути цього постійного перенастроювання та збільшення часу обробки запитів? Рішення

 

Рішення не завжди стосується збільшення обмежень, іноді просте коригування діючого підходу - це все, що потрібно, щоб заощадити день. Замість того, щоб відправляти весь файл, чому б не відправити його пакетами?

Саме так! Сьогодні ми не будемо змінювати нашу конфігурацію під тиском обмежень, що розвиваються. Натомість ми вирішимо це без змін у нашій конфігурації.

Сьогодні ми наріжемо, об'єднаємо кубики та об'єднаємо фрагменти файлів за допомогою Livewire!

План

У нас є триетапний план нарізки та об'єднання:

  1. Спочатку ми повідомимо Livewire очікувану загальну $fileSize, яку ми отримаємо від усіх об'єднаних шматків.
  2. Потім ми починаємо нарізати, завантажувати та об'єднувати шматки в «остаточний файл» на нашому сервері один за одним за допомогою Livewire.
  3. Як тільки розмір нашого остаточного файлу досягне заданої $fileSize, це означає, що всі шматки були об'єднані. Тому ми подаємо остаточний файл до TemporaryUploadedFile, щоб використовувати завантажений файл Livewire особливості.

Щоб зібрати все разом тут, ви можете відвідати наш repository'readme та оглянути відповідні файли.

The View

Почнемо зі створення компонента Livewire, запустивши команду php artisan make:livewire chunked-file-upload. Після цього оновіть наш Livewire, щоб включити тег форми, що містить як елемент введення файлу, так і кнопку для надсилання.

<form wire:submit.prevent="submit">
  <input type="file" id="myFile"/>
  <button type="button" onClick="uploadChunks()">Submit</button>
</form>

Щоразу, коли користувач натискає кнопку «Надіслати», наша користувацька функція JavaScript uploadChunks() розріже вибраний файл на шматки та попросить Livewire завантажити кожен фрагмент.

Спільні очікування

Щоб завантажити великий файл, ми наріжемо його на менші частини, які знаходяться в межах розміру запиту нашого сервера. Ми завантажимо кожен шматок один за одним, щоб ми могли негайно об'єднати завантажений шматок у "остаточний файл".

Але як саме сервер дізнається, що всі шматки були об'єднані в наш остаточний файл? Звичайно, потрібно буде знати очікуваний остаточний розмір нашого остаточного файлу!

<a href="https://laravel-livewire.com/docs/2.x/properties">Властивості Livewire ідеально підходять для обміну інформацією від клієнта до сервера, тому давайте включимо інформацію про наш файл, як-от його $fileName та $fileSize як загальнодоступні атрибути, у наш компонент Livewire . Сьогодні ми розділимо наш файл на шматки по 1 Мб, тому давайте оголосимо окремий атрибут для завантаженого фрагмента $fileChunk та очікуваний максимальний розмір шматка $chunkSize:

// app/Http/Livewire/ChunkedFileUpload.php
public $chunkSize = 1000000; // 1 MB
public $fileChunk; 

public $fileName;
public $fileSize; 

Давайте повернемося до нашого Livewire view та переглянемо завантаженняChunks() функція, ініційована нашою кнопкою «Надіслати». Щоразу, коли користувач надсилатиме файл для завантаження, ми встановлюватимемо значення для $fileName та $fileSize, які пізніше надсилатимуться до нашого компонента Livewire:

// resources/views/livewire/chunked-file-upload.blade.php
function uploadChunks()
{
    const file = document.querySelector('#myFile').files[0];

    // Send the following later at the next available call to component
    @this.set('fileName', file.name, true);
    @this.set('fileSize', file.size, true);

Зверніть увагу, що ми використовуємо метод набору Livewire тут. Це дозволяє нам встановити публічний атрибут у нашому клієнті, але не здійснювати негайний дзвінок на сервер. Зміни до $fileName та $fileSize будуть надіслані до Livewire у наступному негайному запиті на компонент.

Тепер, коли наші остаточні дані про файл готові до обміну з нашим компонентом Livewire, ми можемо взяти фрагмент на нашому першому шматку, починаючи з 0-го байту файлу:

    livewireUploadChunk( file, 0 );
}

Нарізка шматка

Як ми вирізаємо шматок з нашого файлу?

Що ж, нам потрібно буде знати, де починається шматок, а де закінчується. Для першого шматка нашого файлу відправною точкою є даність: 0. Але як щодо того, де закінчується шматок?

Кінець фрагмента завжди становитиме 1 МБ (наш $chunkSize ) від початкової точки шматка, або розмір файлу — залежно від того, що менше між ними:

// resources/views/livewire/chunked-file-upload.blade.php
function livewireUploadChunk( file, start ){
    const chunkEnd  = Math.min( start + @js($chunkSize), file.size );
    const chunk     = file.slice( start, chunkEnd ); 

Тепер, коли у нас є наш шматок, нам доведеться надіслати його на наш сервер. Ми можемо використовувати функцію JavaScript від Livewire upload JavaScript, щоб завантажити та пов'язати фрагмент із нашим атрибутом $fileChunk, оголошеним вище:

    @this.upload('fileChunk', chunk);

Після завантаження першого фрагмента давайте також надішлемо наступний. Нам потрібно буде переконатися, що поточна частина повністю завантажена, для цього ми можемо підключитися до зворотного виклику прогресу події функції завантаження:

-    @this.upload('fileChunk', chunk);
+    @this.upload('fileChunk', chunk,(uName)=>{}, ()=>{}, (event)=>{
+        if( event.detail.progress == 100 ){
+          // We recursively call livewireUploadChunk from within itself
+          start = chunkEnd;
+          if( start < file.size ){
+            livewireUploadChunk( file, start );
+          }
+        }
+    });
}

Завантаження завершується, коли значення event.detail.progress досягає 100. Як тільки це станеться, ми рекурсивно викликаємо поточну функцію livewireUploadChunk(), щоб завантажити наш наступний шматок.

Діапазон методу file.slice становить, за винятком фрагментаEnd. Наприклад, діапазон зрізів(0,10) фактично означає 0 до 9, але не 10! Це означає, що нашою наступною відправною точкою буде chunkEnd.

Збереження та об'єднання Тепер, коли наш JavaScript перегляду Livewire налаштований на нарізку та завантаження фрагментів, ми підійшли до останньої частини нашої подорожі скибочками та кубиками: збереження та об'єднання

шматків у наш компонент Livewire!

Ми будемо використовувати рису Livewire WithFileUploads, щоб полегшити завантаження нашого файлу. Ця риса дозволяє нам оголосити атрибут, який можна завантажити - $fileChunk у нашому випадку!

// app/Http/Livewire/ChunkedFileUpload.php

+ use WithFileUploads;

// Chunks info
public $chunkSize = 1000000; // 1M
public $fileChunk;

// Final file 
public $fileName;
public $fileSize;

+ public $finalFile;

Після завантаження фрагмента Livewire повинен об'єднати його в «остаточний файл». Для цього нам доведеться перехопити потік Livewire після того, як наш шматок буде завантажено.

На щастя для нас, Livewire надає "hooks", які ми можемо використовувати для перехоплення потоку життєвого циклу Livewire для наших загальнодоступних атрибутів. У нашому конкретному випадку ми можемо зачепитися за оновлений гачок для нашого атрибута $fileChunk.

З нашого оновленогоFileChunk hook ми отримаємо ім'я файлу, створене Livewire для поточного фрагмента за допомогою методу getFileName():

public function updatedFileChunk()
{
    $chunkFileName = $this->fileChunk->getFileName();

Потім ми об'єднаємо цей шматок у наш остаточний файл і видалимо шматок після об'єднання:

      $finalPath = Storage::path('/livewire-tmp/'.$this->fileName);
      $tmpPath   = Storage::path('/livewire-tmp/'.$chunkFileName);
      $file = fopen($tmpPath, 'rb');
      $buff = fread($file, $this->chunkSize);
      fclose($file);

      $final = fopen($finalPath, 'ab');
      fwrite($final, $buff);
      fclose($final);
      unlink($tmpPath);

Зрештою всі шматки прибудуть один за одним і будуть об'єднані в наш остаточний файл. Щоб визначити, чи всі шматки були об'єднані, ми просто порівняємо кінцевий розмір файлу з очікуваним $fileSize.

Звичайно, цей нещодавно згенерований файл є нашим користувацьким файлом. Нам потрібно буде включити його в TemporaryUploadedFile, щоб використовувати завантажений файл Livewire особливості.

      $curSize = Storage::size('/livewire-tmp/'.$this->fileName);
      if( $curSize == $this->fileSize ){
          $this->finalFile = 
          TemporaryUploadedFile::createFromLivewire('/'.$this->fileName);
      }
}

Не забудьте імпортувати клас TemporaryUploadedFile і оголосити новий публічний атрибут $finalFile!

Скажімо, наприклад, попередній перегляд нового, тимчасового зображення, на наш погляд, наприклад:

@if ($finalFile)
    Photo Preview:
    <img src="{{ $finalFile->temporaryUrl() }}">
@endif

Реалізації, як правило, набагато плавніші з Livewire, завантаження фрагментів файлів нічим не відрізняється!

Останнє оновлення 5 днів тому.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

Про мене

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

Про автора CrazyBoy49z
WORK EXPERIENCE
Контакти
Ukraine, Lutsk
+380979856297