Руководство по node.js, часть 9: работа с файловой системой

Обзор

Программный интерфейс файлов и каталогов включает асинхронные и синхронные методы. Асинхронное API может быть использовано в тех случаях, когда нежелательно, чтобы длительные вычисления блокировали весь пользовательский интерфейс. В свою очередь синхронное API предлагает более простую модель программирования, однако оно должно использоваться только с объектами WebWorkers.

API файлов и каталогов является важным программным интерфейсом по следующим причинам:

  • Благодаря ему у приложений могут быть автономные (offline) функции, а также функции хранения, позволяющие обрабатывать большие наборы двоичных данных.
  • У приложений появляется возможность предварительной загрузки и кеширования ресурсов, что повышает из производительность.
  • У пользователя приложения появляется возможность напрямую редактировать файлы, расположенные у него на компьютере.
  • Пользователям предоставляется интерфейс работы с файловым хранилищем, который схож с файловой системой.

Примеры таких приложений приведены в разделе .

API файлов и каталогов является альтернативой для других интерфейсов хранения данных, таких как IndexedDB, WebSQL (признано устаревшим с 18 ноября 2010 г.) и AppCache. Тем не менее данное API является более хорошим выбором для приложений, обрабатывающим большие объёмы данных, по следующим причинам:

  • Данное API предлагает возможность хранения данных на стороне клиента в вариантах использования, которые не могут быть решены с помощью баз данных. Например, данное API является намного более производительным в случае хранения и обработки больших файлов.
  • Firefox поддерживает хранение бинарных данных в IndexedDB, в то время как в Chrome эта функция по-прежнему находится на стадии разработки. Если Chrome является одним из целевых браузеров для вашего приложения и у вас есть необходимость хранения бинарных данных, то вы можете использовать только либо данное API, либо AppCache. В свою очередь хранилище AppCache не предоставляет возможности локальных изменений, а также тонкой настройки на стороне клиента.
  • В Chrome у вас есть возможность использования данного API вместе с программным интерфейсом управления квотами, позволяющее управлять квотами хранилища.

Примеры использования

Далее приведены лишь некоторые случаи, в которых можно использовать API файлов и каталогов:

  • Приложения с постоянной загрузкой.
    • Когда файл или каталог выбран для загрузки на сервер, есть возможность скопировать файлы в локальную песочницу и начать их поочерёдную загрузку. 
    • Приложение может продолжить загрузку после закрытия или аварийного сбоя браузера, прерывание связи или завершения работы компьютера.
  • Видеоигры или другие приложения с большим количеством медиа ресурсов.
    • Приложение скачивает один или несколько больших архивов с ресурсами и локально его распаковывает.
    • Приложение осуществляет предварительную фоновую загрузку ресурсов, благодаря чему у пользователя нет необходимости ожидать их загрузки для начала следующего уровня.
  • Аудио- или фоторедактор с возможностью автономной работы или локальным кешем.
  • Offline видеопроигрыватель.
    • Приложение может загружать большие (> 1 ГБ) файлы.
    • Приложение может начать воспроизведение частично загруженного файла.
  • Offline клиент электронной почты.
    • Возможность загрузки и локального хранения прикреплений.
    • Возможность кеширования прикреплений для более поздней отправки.

Жизненный цикл и методы интерфейса FileReader

К FileReader прикреплено 6 основных событий:

  1. loadstart: срабатывает, когда мы начинаем загрузку файла.
  2. progress: срабатывает при чтении большого двоичного объекта в памяти.
  3. abort: срабатывает при вызове .abort.
  4. error: запускается при возникновении ошибки.
  5. load: запускается, если чтение прошло успешно.
  6. loadend: срабатывает, когда файл загружается, а error или abort не вызывается или load начинает новую операцию чтения.

Чтобы начать загрузку файла, у нас есть четыре метода:

  • readAsArrayBuffer(file): читает файл или большой двоичный объект как массив ArrayBuffer. Один из вариантов использования – отправка больших файлов рабочему процессу сервиса.
  • readAsBinaryString(file): читает файл как двоичную строку.
  • readAsText(file, format): читает файл как USVString (почти как строку); здесь вы можете дополнительно указать формат.
  • readAsDataURL(file): возвращает URL-адрес, по которому вы можете получить доступ к содержимому файла, он закодирован в Base64 и готов к отправке на ваш сервер.

Создайте новый файл filereader-example.html и поместите в него код, который использует метод readAsDataURL():

<!DOCTYPE html>
<html>
<head>
  <style>
    body {
      background: #000;
      color: white;
    }

    #progress-bar {
      margin-top: 1em;
      width: 100vw;
      height: 1em;
      background: red;
      transition: 0.3s;
    }
  </style>
</head>
<body>
  <input type="file" id="input" />
  <progress value="0" max="100" id="progress-bar"></progress>
  <div id="status"></div>
  <script>
    const changeStatus = (status) => {
      document.getElementById('status').innerHTML = status;
    }

    const setProgress = (e) => {
      const fr = e.target;
      const loadingPercentage = 100 * e.loaded / e.total;

      document.getElementById('progress-bar').value = loadingPercentage;
    }

    const loaded = (e) => {
      const fr = e.target;
      var result = fr.result;

      changeStatus('Finished Loading!');
      console.log('Result:', result);
    }

    const errorHandler = (e) => {
      changeStatus('Error: ' + e.target.error.name);
    }

    const processFile = (file) => {
      const fr = new FileReader();

      fr.readAsDataURL(file);
      fr.addEventListener('loadstart', changeStatus('Start Loading'));
      fr.addEventListener('load', changeStatus('Loaded'));
      fr.addEventListener('loadend', loaded);
      fr.addEventListener('progress', setProgress);
      fr.addEventListener('error', errorHandler);
      fr.addEventListener('abort', changeStatus('Interrupted'));
    }

    document.getElementById('input').addEventListener('change', (e) => {
      const file = document.getElementById('input').files;

      if (file) {
        processFile(file);
      }
    });
  </script>
</body>
</html>

Откройте filereader-example.html в своем веб-браузере и добавьте файл myFile.txt в input. На экране появится индикатор выполнения, визуализирующий процесс обработки файла. Если загрузка прошла успешно, появится сообщение Start Loading, затем Loaded и Finished Loading.

Поток Writable¶

Поток отвечает за запись данных в указанное место. Примеры потоков записи:

  • отправка HTTP запроса с клиента на сервер;
  • отправка ответа на HTTP запрос с сервера на клиент;
  • запись данных в файл;
  • отправка данных по сокету.

Рассмотрим пример работы с потоком Node.js .

writable.js

Здесь в примере создание потока создается с помощью метода , в котором указывается, что все данные должны быть записаны в файл .

Запись в поток данных осуществляется с помощью метода , который принимает следующие параметры:

  • данные;
  • кодировку (если данные строкового типа);
  • callback-функцию, которая будет вызвана после записи переданных данных.

Метод возвращает булевое значение, если — данные успешно записались, если — значит запись данных необходимо продолжить после возникновения события .

Метод может вернуть в том случае, если данные на запись будут поступать в поток слишком быстро, тогда данные не будут успевать записываться и начнут накапливаться во внутреннем буфере. По достижению лимита буфера вернет , тем самым говоря. что необходимо подождать записи поступивших данных. Когда буфер разгрузится — будет сгенерировано событие и тогда можно будет продолжить запись.

По окончанию работы с потоком вызовите метод , чтобы указать, что все необходимые данные были записаны. Вызов метода инициирует событие .

Если неправильно указать путь к файлу для записи данных, будет сгенерировано событие , обработчику которого аргументом будет передан объект ошибки.

События Node.js потока :

  • — сигнализирует в возможности продолжить запись после переполнения внутреннего буфера;
  • — инициируется после вызова метода и записи в указанное место всех данных;
  • — возникает при закрытии указанного ресурса назначения данных или самого потока;
  • — возникновение в потоке ошибки, обработчику аргументом передается объект ошибки.

/tmp

Самый простой способ вывести все виртуальные файловые системы, это выполнить такую команду:

Она выведет все смонтированные файловые системы, которые не связанны с физическим или сетевым диском. Оной из первых точек монтирования виртуальных файловых систем будет /tmp. Так почему не рекомендуется хранить содержимое /tmp на диске? Потому что файлы из /tmp временные, а постоянные хранилища намного медленнее памяти, где находится tmpfs. Кроме того, физические устройства более подвержены износу от частой записи, в отличие от оперативной памяти. И наконец, файлы в /tmp могут содержать конфиденциальную информацию, поэтому их лучше удалять при каждой перезагрузке.

История версий и совместимость

ReFS имеет несколько разных версий с разной степенью совместимости между версиями операционной системы. Помимо разрабатываемых версий файловой системы, обычно более поздние версии операционной системы могут монтировать файловые системы, созданные в более ранних версиях ОС (обратная совместимость). Некоторые функции могут быть несовместимы с набором функций ОС. Версию, размер кластера и другие особенности файловой системы можно узнать с помощью команды fsutil fsinfo refsinfo volumename .

  • 1.1 : исходная версия, отформатированная Windows Server 2012.
  • 1.2 : версия по умолчанию, если она отформатирована в Windows 8.1, Windows 10 v1507 to v1607, Windows Server 2012 R2 и если указано ReFSv1 в Windows Server 2016. Может использовать альтернативные потоки данных в Windows Server 2012 R2.
  • 2.2 : версия по умолчанию, отформатированная Windows 10 Preview build 10049 или более ранней. Не может быть установлен в 10061 и более поздних версиях.
  • 2.0 : версия по умолчанию, отформатированная Windows Server 2016 TP2 и TP3. Не удалось подключить в Windows 10 Build 10130 и новее или Windows Server 2016 TP4 и новее.
  • 3.0 : версия по умолчанию, отформатированная Windows Server 2016 TP4 и TP5.
  • 3.1 : версия по умолчанию, отформатированная Windows Server 2016 RTM.
  • 3.2 : версия по умолчанию, отформатированная Windows 10 v1703 и сборкой Windows Server Insider Preview 16237. Может быть отформатирована с помощью Windows 10 Insider Preview 15002 или более поздней версии (хотя стала по умолчанию только где-то между 15002 и 15019). Поддерживает дедупликацию в серверной версии.
  • 3.3 : версия по умолчанию, отформатированная в Windows 10 Enterprise v1709 (возможность создания тома ReFS удалена из всех выпусков, кроме Enterprise и Pro для рабочих станций, начиная со сборки 16226; возможность чтения / записи остается) и Windows Server версии 1709 (начиная со сборки Windows 10 Enterprise Insider Preview 16257 и сборка Windows Server Insider Preview 16257).
  • 3.4 : версия по умолчанию, отформатированная Windows 10 Pro для рабочих станций / Enterprise v1803 и новее, а также серверные версии (включая версию с долгосрочной поддержкой Windows Server 2019).
  • 3.5 : версия по умолчанию, отформатированная Windows 10 Enterprise Insider Preview (сборка 19536 или новее); Добавлена ​​поддержка жестких ссылок (только для свежего отформатированного тома; не поддерживается для томов, обновленных с предыдущих версий).
  • 3.6 : версия по умолчанию, отформатированная Windows 10 Enterprise Insider Preview (сборка 21292 или новее) и Windows Server Insider Preview (сборка 20282 или новее)
  • 3.7 : версия по умолчанию, отформатированная Windows 10 Enterprise Insider Preview (сборка 21313 или новее) и Windows Server Insider Preview (сборка 20303 или новее). Также версия, используемая в Windows Server 2022 и Windows 11 .
Поддержка разных версий ReFS каждой версией Windows
ReFS Windows Server 2012 Windows 8.1, Server 2012 R2 Windows 10 v1507 — v1607 Windows Server 2016 TP2, TP3 Windows Server 2016 TP4, TP5 Окончательная первоначальная версия Windows Server 2016 Windows 10 v1703 Windows 10 v1709, Windows Server 1709 5 Windows 10 v1803 — v1809, Windows Server 2019, 1803 — 1809 5
1.1 Дефолт Да 1 Да 1 Да 1 Да 1 Да 1 Да 1 ? Да 1
1.2 да Дефолт Дефолт да да да да да да
2.0 Нет Нет Нет Нет Дефолт Нет Нет Нет Нет
3.0 Нет Нет Нет Нет Нет Да 2 Да 3 Да 4 Да 6
3.1 Нет Нет Нет Нет Нет Дефолт Да 3 Да 4 Да 6
3.2 Нет Нет Нет Нет Нет Нет Дефолт Да 4 Да 6
3.3 Нет Нет Нет Нет Нет Нет Нет Дефолт Да 6
3,4 Нет Нет Нет Нет Нет Нет Нет Нет Дефолт

Примечания:

1 : В журнал событий записывается следующее сообщение: «Том«?: »Был смонтирован в более старой версии Windows. Некоторые функции могут быть потеряны ».
2 : Windows обновляет его до версии 3.1, когда том монтируется с доступом для записи.
3 : Windows обновляет его до версии 3.2, когда том монтируется с доступом для записи.
4 : Windows обновляет его до версии 3.3, когда том монтируется с доступом для записи.
5. Возможность создания тома ReFS удалена в Windows 10 v1709 (Fall Creators Update 2017), за исключением выпусков Enterprise и Pro для рабочих станций.
6 : Windows обновляет его до версии 3.4, когда том монтируется с доступом для записи.

Ограничения

For security reasons, browsers impose restrictions on file access. If you ignore them, you will get security errors.

An origin is the domain, application layer protocol, and port of a URL of the document where the script is being executed. Each origin has its own associated set of file systems.

The security boundary imposed on file system prevents applications from accessing data with a different origin. This protects private data by preventing access and deletion. For example, while an app or a page in http://www.example.com/app/ can access files from http://www.example.com/dir/, because they have the same origin, it cannot retrieve files from http://www.example.com:8080/dir/ (different port) or https://www.example.com/dir/ (different protocol).

To prevent malicious apps from running hostile executables, you cannot create executable files within the sandbox of the File and Directory Entries API. 

Because the file system is sandboxed, a web app cannot access another app’s files. You also cannot read or write files to an arbitrary folder (for example, My Pictures and My Documents) on the user’s hard drive.

You cannot run your app locally from . If you do so, the browser throws errors or your app fails silently. This restriction also applies to many of the file APIs, including BlobBuilder and FileReader.

For testing purposes, you can bypass the restriction on Chrome by starting the browser with the flag. Use this flag only for this purpose.

API пользовательского пространства

API находится в пользовательском пространстве, когда файловая система не использует напрямую средства ядра, а обращается к дискам с помощью функций операционной системы высокого уровня и предоставляет функции в библиотеке, которые серия утилит использует для доступа к файловой системе.

Это полезно для работы с образами дисков.

Преимущество состоит в том, что файловую систему можно сделать переносимой между операционными системами, поскольку функции операционной системы высокого уровня, которые она использует, могут быть такими же общими, как ANSI C, но недостатком является то, что API является уникальным для каждого приложения, которое его реализует.

Объект module

  • Объект
  • Модуль или приложение?
  • Модуль-функция
  • Кеширование модулей (заново модуль никогда не читается)
  • Расположение модулей: порядок поиска
  • Передаем параметры: модуль-фабрика

Объект (основополагающий объект для модулей) является переменной, которая существует в каждом модуле (файле, можно вывести
).
. Содержимое: свойство – как правило путь к файлу, – ссылка на родительский модуль ( — ссылка на родительский модуль, который данный), (), свойство и другие.

Модуль или приложение? module.parent

Модуль может быть запущен напрямую, а если нет, если функционал подключен к другому модулю,
то пусть он этот функционал экспортирует. Разделить эти два случая можно при помощи проверки:

Правильное использование module.exports

В контексте модуля:

(данные конструкции равнозначны)

и ссылки на .

Так как и это всего лишь ссылки на объект, то если у них поменять значение, то они никак не повлияют на . Поэтому ранее мы использовали такой код: ;

Если вы хотите передать функцию не в объекте, а напрямую, то используйте следующий синтаксис:

Кеширование модулей

Когда Nodejs загружает модуль он полностью создает соответствующий объект (с учетом , и других аналогичных свойств) и запоминает его у себя ( (полный путь к файлу) служит идентификатором для внутреннего кэша) и в следующий раз, когда мы повторно обращаемся (подключаем) к какому-либо модулю (файлу), nodejs берет все тот же объект из кэша. То есть, например, инициализировать модуль достаточно 1 раз в каком-либо файле, в дальнейшем его можно просто использовать.

В нашем случае для
и для берется один и тот же объект. Поэтому принцип следующий: в первый раз, когда используется модуль, он инициализируется и в дальнейшем мы его только подключаем и пользуемся им (то есть в нашем случае нам не нужно использовать дважды, то есть в разных файлах).

Расположение модулей: порядок поиска модулей в nodejs

Как сделать так, чтобы db подключалась всегда без указания специфичного пути:

а вот так:

в независимости от того в каком файле подключается .

Для этого нужно понимать порядок поиска модулей в nodejs (то что происходит, когда вызывается require). В nodejs существует множество встроенных модулей, например, , который будет подключен без проблем. Если же в require указать конкретный путь, например, , то поиск будет вестись исходя из заданного пути и файл будет найден, либо nodejs попытается получить этот файл как директорию (и будет искать index.js внутри категории).

Если же указать и при этом модуль не является встроенным, то будет произведен поиск директории относительно текущего положения (если найдет, то попытается взять модуль из нее). Если директория отсутствует, то директория будет искаться выше и т.д.

Помимо указания конкретного пути для модуля, nodejs может искать модули следующим образом:

Paths¶

()

Gets the current working directory.

Returns

The current working directory.

(path)

Sets the current working directory.

Arguments
(path, opts)

Looks up the incoming path and returns an object containing both the resolved path and node.

The options () allow you to specify whether the object, its parent component, a symlink, or the item the symlink points to are returned. For example:

var lookup = FS.lookupPath(path, { parent true });
Arguments
  • path (string) – The incoming path.

  • opts (object) –

    Options for the path:

    • parent (bool)
      If true, stop resolving the path once the penultimate component is reached.
      For example, the path with would return an object representing . The default is .

    • follow (bool)
      If true, follow the last component if it is a symlink.
      For example, consider a symlink that links to . If , an object representing would be returned. If , an object representing the symlink file would be returned. The default is .

Returns

an object with the format:

{
  path resolved_path,
  node resolved_node
}
(path, dontResolveLastLink)

Looks up the incoming path and returns an object containing information about
file stats and nodes. Built on top of and provides more
information about given path and its parent. If any error occurs it won’t
throw but returns an property.

Arguments
  • path (string) – The incoming path.

  • dontResolveLastLink (boolean) – If true, don’t follow the last component
    if it is a symlink.

Returns

an object with the format:

{
  isRoot boolean,
  exists boolean,
  error Error,
  name string,
  path resolved_path,
  object resolved_node,
  parentExists boolean,
  parentPath resolved_parent_path,
  parentObject resolved_parent_node
}
(node)

Gets the absolute path to , accounting for mounts.

Arguments
Returns

The absolute path to .

Работа с файлами

Последнее обновление: 23.05.2019

Чтение из файла

Допустим, в одной папке с файлом приложения app.js расположен текстовый файл hello.txt с простейшим текстом, например:

Hello Node JS!

Для чтения файла в синхронном варианте применяется функция fs.readFileSync():

let fileContent = fs.readFileSync("hello.txt", "utf8");

В метод передается путь к файлу относительно файла приложения app.js, а в качестве второго параметра указывается кодировка для получения текстового содержимого файла.
На выходе получаем считанный текст.

Для асинхронного чтения файла применяется функция fs.readFile:

fs.readFile("hello.txt", "utf8", function(error,data){ });

Первый и второй параметр функции опять же соответственно путь к файлу и кодировка. А в качестве третьего параметра передается функция обратного вызова,
которая выполняется после завершения чтения. Первый параметр этой функции хранит информацию об ошибке при наличии, а второй — собственно считанные данные.

Для чтения файла определим в файле app.js следующий код:

const fs = require("fs");

// асинхронное чтение
fs.readFile("hello.txt", "utf8", 
			function(error,data){
				console.log("Асинхронное чтение файла");
				if(error) throw error; // если возникла ошибка
				console.log(data);	// выводим считанные данные
});

// синхронное чтение
console.log("Синхронное чтение файла")
let fileContent = fs.readFileSync("hello.txt", "utf8");
console.log(fileContent);

И здесь стоит обратить внимание, что несмотря на то, что функция вызывается первой, но так как она асинхронная, она не блокирует
поток выполнения, поэтому ее результат выводится в самом конце

Запись файла

Для записи файла в синхронном варианте используется функция fs.writeFileSync(), которая в качестве параметра принимает путь к файлу
и записываемые данные:

fs.writeFileSync("hello.txt", "Привет ми ми ми!")

Также для записи файла можно использовать асинхронную функцию fs.writeFile(), которая принимает те же параметры:

fs.writeFile("hello.txt", "Привет МИГ-29!")

В качестве вспомогательного параметра в функцию может передаваться функция обратного вызова, которая выполняется после завершения записи:

const fs = require("fs");

fs.writeFile("hello.txt", "Hello мир!", function(error){

	if(error) throw error; // если возникла ошибка
	console.log("Асинхронная запись файла завершена. Содержимое файла:");
	let data = fs.readFileSync("hello.txt", "utf8");
	console.log(data);	// выводим считанные данные
});

Следует отметить, что эти методы полностью перезаписывают файл. Если надо дозаписать файл, то применяются методы fs.appendFile()/fs.appendFileSync():

const fs = require("fs");

fs.appendFileSync("hello.txt", "Привет ми ми ми!");

fs.appendFile("hello.txt", "Привет МИД!", function(error){
	if(error) throw error; // если возникла ошибка
				
	console.log("Запись файла завершена. Содержимое файла:");
	let data = fs.readFileSync("hello.txt", "utf8");
	console.log(data);	// выводим считанные данные
});

Удаление файла

Для удаления файла в синхронном варианте используется функция fs.unlinkSync(), которая в качестве параметра принимает путь к удаляемому файлу:

fs.unlinkSync("hello.txt")

Также для удаления файла можно использовать асинхронную функцию fs.unlink(), которая принимает путь к файлу и функцию, вызываемую при завершении удаления:

fs.unlink("hello.txt", (err) => {
  if (err) console.log(err); // если возникла ошибка	
  else console.log("hello.txt was deleted");
});

НазадВперед

Работа с файловыми дескрипторами

Прежде чем двигаться дальше, давайте посмотрим на термины, о которых описано в статье ниже.

Файловый дескриптор — это неотрицательное целое число. Когда создается новый поток ввода-вывода, ядро возвращает процессу, создавшему поток ввода-вывода, его файловый дескриптор (Wikipedia).

Если излагать простыми словами, то при открытии файла мы получаем его дескриптор — это уникальный индикатор, благодаря которому мы можем отслеживать каждый файл отдельно и работать с ним. Для получения дескриптора файла в Node.js используются два метода: (1) асинхронный метод fs.open() и (2) синхронный fs.openSync(). К слову все методы в Node.js имеют синхронные и асинхронные реализации. Синхронные методы отмечены в название метода с окончанием Sync. В чем же разница между синхронным и асинхронным методами? Главное отличие — это порядок их выполнения. Синхронный метод будет выполнен путем блокирования основного потока, в тоже время, если для того же действия использовать ассинхронный метод, то операции могут быть выполнены в произвольном порядке по истечению их выполнения (т.к. основной поток не будет блокироваться).

Перейдем к примерам.

В этих примерах при выполнении кода, мы получим уникальный дескриптор файла. Пройдемся по параметрам методов. Первый — это путь к файлу, который читаем. Второй параметр предназначен для передачи флагов доступа к файлам. В данном примере мы указали параметр ‘r’, что означает этот файл открыт для чтения.

Ниже я приведу перечень флагов доступа к файлам

r — чтение;

r+ — чтение и запись;

w+ — чтение и запись, запись с начала файла. Файл создается если его нет;

a —  запись, запись с конца файла. Файл создается если его нет;

a + — чтение и запись, запись с конца файла. Файл создается если его нет;

Третий параметр отличается в зависимости от типа метода. В асинхронном варианте это колбэк функция, первый аргумент которой возвращает ошибку (в случае ее возникновения), а второй — сам дескриптор. Если метод синхронный, то вызов происходит в блоке try/catch для отслеживания ошибок и получения ответа путем присвоения вызова переменной.

Следует помнить, что дескрипторы файлов необходимо закрывать для предотвращения переполнения памяти, а также проблем с производительностью. Выполним данную задачу с помощью метода close() и его синхронного аналога closeSync().

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Все про сервера
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: