Moduł 5 - Zajęcia 8 - Praca z plikami

1.1 Wprowadzenie. Ładowanie plików

Formularz HTTP wysyłany na serwer przez przeglądarkę powinien wykorzystywać jakąś formę kodowania. Jeśli jej nie wskażemy, to domyślnie wykorzystuje się application/x-www-form-urlencoded. Spotkaliśmy się już z tą metodą kodowania, gdy analizowaliśmy przekazywanie danych na serwer i wiemy, że jest wspierana przez Express przy użyciu wbudowanego parsera.

Jeżeli pojawia się potrzeba załadowania plików na serwerze, zadanie staje się trochę trudniejsze. Niezbędne jest dla nas do wysyłania plików, przyjmowanie typu kodowania multipart/form-data które nie jest opracowywane przez Express bezpośrednio.

Jest kilka pakietów służących do ładowania plików na serwer. Przeanalizujemy pracę z jednym z nich: Multer.

Zanim przejdziemy do przykładów tego, jak korzystać z tego pakietu, sprecyzujmy jedną ogólną kwestię. Co powinniśmy zrobić po opracowaniu formularza? Po tym, jak zakończymy pracować z danymi z formularza należy zwrócić odpowiedź do przeglądarki. Istnieją dwa rozpowszechnione podejścia:

  • Pierwsze - przyjmujemy dane, po opracowaniu formularza możemy wysłać HTML lub JSON bezpośrednio do przeglądarki.
  • Drugie podejście to przeadresowanie. Jest bardziej popularne niż pierwsze i wykorzystuje w zasadzie dwa kody odpowiedzi. Pierwszy kod odpowiedzi 302 (Found — Znaleziono) i drugi kod odpowiedzi 303 (See Other — Zobacz inne), który w naszej opinii powinien zadziałać lepiej. Express domyślnie wykorzystuje dla statusu kod odpowiedzi 302.

2.1 Multer

Multer działa jak program pośredniczący (middleware) dla frameworka Express, który jest wykorzystywany przy ładowaniu plików do opracowywania multipart/form-data. W gruncie rzeczy jest tak naprawdę warstwą abstrakcji dla pakietu niższego poziomu busboy i jest od niego prostszy w użyciu. Multer nie obsługuje żadnego innego typu formularza, niż multipart/form-data.

Instalacja:

            npm install -S multer
          

Multer dodaje do obiektu request.body obiekt file (lub files). Obiekt body zawiera wartość pól tekstowych formularza, obiekt file (files) zawiera plik lub pliki ładowane przez formularz.

Pełen kod naszej aplikacji jest następujący:

            const createError = require('http-errors');
            const express = require('express');
            const path = require('path');
            const fs = require('fs').promises;
            const app = express();
            const multer = require('multer');
            const uploadDir = path.join(process.cwd(), 'uploads');
            const storeImage = path.join(process.cwd(), 'images');

            const storage = multer.diskStorage({
              destination: (req, file, cb) => {
                cb(null, uploadDir);
              },
              filename: (req, file, cb) => {
                cb(null, file.originalname);
              },
              limits: {
                fileSize: 1048576,
              },
            });

            const upload = multer({
              storage: storage,
            });

            app.post('/upload', upload.single('picture'), async (req, res, next) => {
              const { description } = req.body;
              const { path: temporaryName, originalname } = req.file;
              const fileName = path.join(storeImage, originalname);
              try {
                await fs.rename(temporaryName, fileName);
              } catch (err) {
                await fs.unlink(temporaryName);
                return next(err);
              }
              res.json({ description, message: 'Plik załadowany pomyślnie', status: 200 });
            });

            // catch 404 and forward to error handler
            app.use((req, res, next) => {
              next(createError(404));
            });

            app.use((err, req, res, next) => {
              res.status(err.status || 500);
              res.json({ message: err.message, status: err.status });
            });

            const isAccessible = path => {
              return fs
                .access(path)
                .then(() => true)
                .catch(() => false);
            };

            const createFolderIsNotExist = async folder => {
              if (!(await isAccessible(folder))) {
                await fs.mkdir(folder);
              }
            };

            const PORT = process.env.PORT || 3000;

            app.listen(PORT, async () => {
              createFolderIsNotExist(uploadDir);
              createFolderIsNotExist(storeImage);
              console.log(`Server running. Use on port:${PORT}`);
            });
          

Pakiet dostarcza następujących informacji dla każdego załadowanego pliku:

Klucz Opis Uwagi
fieldname Nazwa pola, wprowadzona w formularzu
originalname Nazwa pliku na komputerze użytkownika
encoding Kodowanie pliku
mimetype Mime-typ pliku
size Rozmiar pliku w bajtach
destination Katalog, w którym będzie zapisany plik DiskStorage
filename Nazwa pliku bez destination DiskStorage
path Pełna ścieżka do ładowanego pliku DiskStorage
buffer Buffer z całego pliku MemoryStorage

Tworzymy opcje przestrzeni dyskowej DiskStorage w której przechowywane będą pliki:

            const storage = multer.diskStorage({
              destination: (req, file, cb) => {
                cb(null, uploadDir);
              },
              filename: (req, file, cb) => {
                cb(null, file.originalname);
              },
              limits: {
                fileSize: 1048576,
              },
            });
          

Przy pomocy opcji, definiujemy funkcje dla miejsca docelowego pliku destination i nazwy pliku filename i określamy, gdzie będzie znajdował się plik po ładowaniu:

  • destination wykorzystuje się, aby określić katalog, w którym będą umieszczone pliki;
  • filename wykorzystuje się, aby określić, jak będzie nazwany plik wewnątrz katalogu. Jeśli nazwa pliku filename nie jest określona, to do każdego pliku będzie przypisana losowa nazwa bez rozszerzenia pliku.

W parametrach każdej funkcji jest zapytanie (req) i zbiór informacji o pliku (file) np:

            {
              fieldname: 'picture',
              originalname: 'nazwa_pliku.jpg',
              encoding: '7bit',
              mimetype: 'image/jpeg'
            }
          

Istnieje również obiekt limits ustanawiający ograniczenia rozmiaru pliku. W pełni pokrywa się on z metodami pakietu busboy, a tutaj możesz zobaczyć pełną listę metod.

W naszym przykładzie ustawiliśmy maksymalny rozmiar pliku na 1 MB (1024 * 1024 bajty = 1048576 = 1MB).

Tworzymy egzemplarz multer:

            const upload = multer({
              storage: storage,
            });
          

Dalej wykorzystujemy program pośredniczący:

            upload.single('picture');
          

Ładuje on jeden plik o nazwie picture do tymczasowego folderu uploads, a informacja o pliku będzie zapisana w req.file.

Wewnątrz middleware przenosimy plik do folderu stałego zapisu images.

Pełny kod przykładu znajduje się na Github Gist