• Czas czytania ~6 min
  • 01.03.2023

Dzisiaj przesyłamy plik w kawałkach za pomocą Livewire. Prześlij swoje pliki blisko użytkowników za pomocą Fly.io, możesz uruchomić aplikację < href = "https://fly.io/docs/laravel/" > Laravel!

Serwery są skonfigurowane tak, aby ograniczać rozmiar żądań, które mogą akceptować. Ma to na celu uniknięcie długiego czasu przetwarzania, wynikającej z tego niedostępności i potencjalnych zagrożeń bezpieczeństwa związanych z przetwarzaniem dużych żądań za jednym razem

Co się stanie, gdy użytkownik zażąda przesłania pliku przekraczającego skonfigurowane limity? Trafnie, przesyłanie nie powiedzie się, albo z niestandardową wiadomością, którą piszemy, albo domyślnym błędem kodu stanu < href = "https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413" > 413 zwróconym przez nasz serwer.

Mamy do czynienia z kłopotliwą sytuacją dla programistów przed nami i będziemy mieli do czynienia z programistami długo po nas: przetwarzanie dużych plików przesyłanych.

Problem

Oczywistym podejściem jest aktualizacja naszych limitów konfiguracji. Możemy zwiększyć kilka ograniczeń konfiguracyjnych w samym naszym serwerze i PHP.

Problem polega na tym, że nie jest to dość dynamiczne. Przesyłanie plików może zwiększyć liczbę nadgodzin, a to pozostawia nam ponowne skonfigurowanie naszych ograniczeń. A wraz ze zwiększonymi ograniczeniami wydłuża się również czas przetwarzania żądań.

Czy nie ma sposobu, aby uniknąć tej ciągłej rekonfiguracji i zwiększonego czasu przetwarzania żądań? Rozwiązanie

Rozwiązanie nie zawsze polega na zwiększaniu ograniczeń, czasami wystarczy po prostu dostosować istniejące podejście, aby uratować sytuację. Zamiast wysyłać cały plik, dlaczego nie wysłać go partiami?

Zgadza się! Dzisiaj nie zmienimy naszej konfiguracji pod presją ewoluujących ograniczeń. Zamiast tego rozwiążemy ten problem bez zmiany naszej konfiguracji.

Dzisiaj będziemy kroić, kroić w kostkę i scalać fragmenty plików za pomocą Livewire!

Plan

Mamy trzyetapowy plan krojenia i scalania:

  1. Najpierw poinformujemy Livewire o oczekiwanych $fileSize do otrzymania ze wszystkich połączonych części.
  2. Następnie zaczynamy kroić, przesyłać i scalać fragmenty w "ostateczny plik" na naszym serwerze, jeden po drugim, za pomocą Livewire.
  3. Gdy ostateczny rozmiar pliku osiągnie dany $fileSize, oznacza to, że wszystkie fragmenty zostały połączone. Dlatego wprowadzamy ostateczny plik do klasy < href="https://github.com/livewire/livewire/blob/master/src/WithFileUploads.php#L37>TemporaryUploadedFile Livewire, aby wykorzystać przesłany plik Livewire features.

Aby złożyć wszystko tutaj, możesz odwiedzić plik readme naszego repozytorium < href="https://github.com/KTanAug21/fly.io-livewire-snippets/blob/master/_readme/chunked_file_upload_livewire.md"> i sprawdzić odpowiednie pliki.

Widok

Zacznijmy od tworzenie komponentu Livewire, uruchamiając polecenie php artisan make:livewire chunked-file-upload. Następnie zaktualizuj nasz < widok href="https://github.com/KTanAug21/fly.io-livewire-snippets/blob/master/resources/views/livewire/chunked-file-upload.blade.php>Livewire, aby zawierał znacznik formularza zawierający zarówno element wejściowy pliku, jak i przycisk do przesyłania.

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

Za każdym razem, gdy użytkownik kliknie przycisk przesyłania, nasza niestandardowa funkcja JavaScript uploadChunks() podzieli wybrany plik na kawałki i poprosi Livewire o przesłanie każdego fragmentu.

Aby

przesłać duży plik, podzielimy go na mniejsze części, które mieszczą się w limitach rozmiaru żądań naszego serwera. Prześlemy każdy fragment jeden po drugim, abyśmy mogli natychmiast scalić przesłany fragment w "ostateczny plik".

Ale skąd dokładnie serwer będzie wiedział, że wszystkie fragmenty zostały scalone z naszym ostatecznym plikiem? Oczywiście musi znać oczekiwany ostateczny rozmiar naszego ostatecznego pliku!

href="https://laravel-livewire.com/docs/2.x/properties">Livewire są idealne do udostępniania informacji od klienta do serwera, więc dołączmy informacje o naszym pliku, takie jak jego $fileName i $fileSize jako atrybuty publiczne w naszym składniku < href="https://github.com/KTanAug21/fly.io-livewire-snippets/blob/master/app/Http/Livewire/ChunkedFileUpload.php">Livewire . Dzisiaj podzielimy nasz plik na kawałki o rozmiarze 1 MB, więc zadeklarujmy osobny atrybut dla fragmentu przesłanego $fileChunk i oczekiwany maksymalny rozmiar fragmentu $chunkSize:

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

public $fileName;
public $fileSize; 

Wróćmy do naszego widoku < href="https://github.com/KTanAug21/fly.io-livewire-snippets/blob/master/resources/views/livewire/chunked-file-upload.blade.php">Livewire i popraw uploadChunks() funkcja uruchamiana przez nasz przycisk przesyłania. Za każdym razem, gdy użytkownik przesyła plik do przesłania, ustawiamy wartości $fileName i $fileSize, które zostaną później wysłane do naszego komponentu Livewire:Zauważ, że

// 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);

używamy metody zestawu Livewire tutaj. Pozwala nam to ustawić atrybut publiczny w naszym kliencie, ale nie wykonywać natychmiastowego wywołania serwera. Zmiany w $fileName i $fileSize zostaną wysłane do Livewire w następnym natychmiastowym żądaniu komponentu.

Teraz, gdy nasze ostateczne szczegóły pliku są gotowe do udostępnienia naszemu komponentowi Livewire, możemy wziąć kawałek na naszym pierwszym kawałku, zaczynając od bajtu 0 pliku: Cięcie fragmentu

    livewireUploadChunk( file, 0 );
}

Jak wyciąć fragment z naszego pliku?

Cóż, musimy wiedzieć, gdzie zaczyna się kawałek i gdzie się kończy. Dla pierwszej części naszego pliku punktem wyjścia jest podany: 0. Ale co powiesz na to, gdzie kończy się fragment? Koniec fragmentu

zawsze będzie wynosił 1 MB (nasz $chunkSize) od punktu początkowego fragmentu lub rozmiaru pliku - w zależności od tego, który jest mniejszy między nimi:

// 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 ); 

Teraz, gdy mamy nasz kawałek, będziemy musieli wysłać go na nasz serwer. Możemy użyć funkcji < href="https://laravel-livewire.com/docs/2.x/file-uploads#js-api>upload JavaScript, aby przesłać i powiązać fragment z naszym atrybutem $fileChunk zadeklarowanym powyżej:

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

Po przesłaniu pierwszego kawałka wyślijmy również następny. Musimy upewnić się, że bieżący fragment został całkowicie przesłany, ale w tym celu możemy podłączyć się do wywołania zwrotnego postępu zdarzenia funkcji przesyłania:

-    @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 );
+          }
+        }
+    });
}

Przesyłanie zostanie zakończone, gdy wartość event.detail.progress osiągnie 100. Gdy to zrobimy, rekurencyjnie wywołujemy bieżącą funkcję livewireUploadChunk() w celu przesłania następnego fragmentu.

Zakres metody file.slice to z wyłączeniem elementu chunkEnd. Na przykład zakres plasterka(0,10) w rzeczywistości oznacza od 0 do 9, ale nie 10! Oznacza to, że naszym następnym punktem wyjścia będzie chunkEnd.

Zapisywanie i scalanie

Teraz, gdy nasz <a href="https://github.com/KTanAug21/fly.io-livewire-snippets/blob/master/resources/views/livewire/chunked-file-upload.blade.php">Livewire view JavaScript jest skonfigurowany do krojenia i przesyłania fragmentów, doszliśmy do ostatniej części naszej podróży po plasterkach i kościach: zapisywania i scalania fragmentów w nasz komponent Livewire!

Będziemy używać cechy Livewire WithFileUploads, aby nasze przesyłanie plików było dziecinnie proste. Ta cecha pozwala nam zadeklarować atrybut, który można przesłać – $fileChunk w naszym przypadku!

// app/Http/Livewire/ChunkedFileUpload.php

+ use WithFileUploads;

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

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

+ public $finalFile;

Po przesłaniu fragmentu Livewire musi scalić go w "plik końcowy". Aby to zrobić, będziemy musieli przechwycić przepływ Livewire po przesłaniu naszego fragmentu.

Na szczęście dla nas Livewire zapewnia "hooks", którego możemy użyć do przechwycenia przepływu życia Livewire dla naszych atrybutów publicznych. W naszym konkretnym przypadku możemy podpiąć się do zaktualizowanego haka dla naszego atrybutu $fileChunk.

Z naszego zaktualizowanego FileChunk hook pobierzemy nazwę pliku wygenerowaną przez Livewire dla bieżącego fragmentu za pomocą metody getFileName():

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

Następnie scalimy ten fragment z naszym ostatecznym plikiem i usuniemy fragment po scaleniu:

      $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);

Ostatecznie wszystkie kawałki pojawią się jeden po drugim i zostaną połączone z naszym ostatecznym plikiem. Aby ustalić, czy wszystkie fragmenty zostały połączone, po prostu porównujemy ostateczny rozmiar pliku z oczekiwanym $fileSize.

Oczywiście ten nowo wygenerowany plik jest naszym plikiem niestandardowym. Będziemy musieli zamknąć go w klasie < Livewire href="https://github.com/livewire/livewire/blob/master/src/WithFileUploads.php#L37">TemporaryUploadedFile, aby wykorzystać przesłany plik Livewire features.

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

Nie zapomnij zaimportować klasy TemporaryUploadedFile i zadeklarować nowy atrybut publiczny $finalFile!

Załóżmy na przykład, że podgląd nowego, tymczasowego obrazu w naszym widoku w następujący sposób:

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

Implementacje są generalnie o wiele bardziej płynne dzięki Livewire, przesyłanie fragmentów plików nie jest inne!

Ostatnia aktualizacja 5 dni temu.

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

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...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297