Node работа с файлами

NodeJS работа с файлами

49

Доброго времени суток, друзья. Поговорим сегодня о работе с файлами в Node.js. Для работы с файлами используется модуль fs (сокращение от File System).

В этой статье мы рассмотрим следующие темы:

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

— Работа с директориями;

— Создание новой папки;

— Чтение содержимого папки;

— Удаление папки;

— Получение системной информации о файле;

— Модуль path и путь к файлу;

— Получение имени, пути и расширения файла;

— Работа с путями файла;

— Переименование файла или директории;

— Чтение файла;

— Запись файла;

— Копирование файла;

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

Для начала работы нам потребуется установленная Node.js. Подробную инструкцию по ее установке вы можете получить (тут). 

Прежде чем начать работу с модулем, его следует импортировать в рабочем файле. 

Пример:


const fs = require(‘fs');

Пробежимся по основным методам этого модуля и посмотрим на примерах как с ними можно работать.

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

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

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

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

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


const fs = require('fs')

// асинхронный
fs.open('template.txt', 'r', (err, fd) => {
    if (err) throw err;
    //fd - это дескриптор файла
    console.log(fd)
})

// синхронный 
try {
  const fd = fs.openSync('template.txt', 'r')
  console.log(fd)
} catch (err) {
  console.error(err)
}

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

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

r — чтение;

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

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

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

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

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

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

Работа с директориями 

Для проверки существующей директории (файла) и доступов к нему в модуле fs применяется метод access

Пример:


const fs = require('fs')
const file = 'package.json';

// проверка существования файла
fs.access(file, fs.constants.F_OK, (err) => {
  console.log(`${file} ${err ? 'не существует' : 'существует'}`);
});

// проверка на возможность прочитать файл
fs.access(file, fs.constants.R_OK, (err) => {
    console.log(`${file} ${err ? 'не прочитать' : 'прочитать'}`);
});
  
// проверка на возможность перезаписать файл
fs.access(file, fs.constants.W_OK, (err) => {
    console.log(`${file} ${err ? 'не перезаписать' : 'перезаписать'}`);
});

Вторым параметром устанавливается константа режима проверки:

F_OK — существование файла без проверки прав доступа;

R_OK — может ли файл быть прочитан текущим процессом;

W_OK — может ли файл быть записан текущим процессом;

X_OK — может ли файл быть выполнен текущим процессом (в Windows не работает).

Создание новой папки

Для создания каталогов присутствует асинхронный метод mkdir и синхронный mkdirSync.

Пример:


fs.mkdir(__dirname + dir, { recursive: true }, (err) => {
    if (err) {
        console.error(err)
        return
    }
})

if (!fs.existsSync(dir)) {
    try {
      fs.mkdirSync(__dirname + dir, { recursive: true })
    } catch (error) {
        console.error(error)
    }
} else {
    console.log('Папка существует')
}

Для создания в текущей директории нового каталога, перед путем следует указать переменную __dirname как вариант реализации абсолютного пути, либо воспользоваться метом resolve модуля path.

Чтение содержимого папки 

Для получения содержимого директории используются два метода readdir и readdirSync. Первым параметром для методов передается путь директории, содержимое которой нужно получить.

Пример:


const fs = require('fs')
const dir =  __dirname + '/'

fs.readdir(dir, (err, files) => {
    if (err) {
      console.error(err)
      return
    }
    console.log(files)
})

try {
    const files = fs.readdirSync(dir)
    console.log(files)
} catch (error) {
      console.error(error)
}

Удаление папки

Удаление директории производится с помощью методов rmdir и rmdirSync. Первым параметром методов является путь удаляемой директории.

Пример:


const fs = require('fs')

fs.rmdir(__dirname +'/testDel', () => { 
    console.log('Файл успешно удален')
})

try {
    fs.rmdirSync(__dirname + '/testDel')
    console.log('Файл успешно удален')
} catch (error) {
    console.error(error)
}

Получение системной информации о файле 

Каждый файл, который мы загружаем, помимо данных хранит в себе системную информацию. Для получения этой информации можно воспользоваться методом stat() или выбрать синхронный вариант statSync()

Пример:


// асинхронный
fs.stat('template.txt', (err, stats) => {
    if (err) {
        console.error(err)
        return
    }
    console.log(stats)
})

// синхронный
try {
  const stats = fs.statSync('template.txt')
  console.log(stats)
} catch (err) {
  console.error(err)
}

Вся информация хранится в получаемом объекте stats. Данный объект хранит в себе методы для получения дополнительной полезной информации.

Перечислю некоторые из этих свойств:

stats.isDirectory() метод позволяет узнать, является ли файл директорией;

stats.isFile() метод возвращает true, если это файл;

stats.isSocket() метод возвращает true, если это сокет;

stats.isSymbolicLink() метод возвращает true, если файл является символьной ссылкой;

stats.size свойство, которое возвращает размер файла;

stats.birthtime возвращает время и дату, когда данный файл был создан.

Пример: 


fs.stat('template.txt', (err, stats) => {
    if (err) {
        console.error(err)
        return
    }
    console.log(stats.isDirectory())
    console.log(stats.isFile())
    console.log(stats.isSocket())
    console.log(stats.isSymbolicLink())
    console.log(stats.size)
    console.log(stats.mode)
    console.log(stats.birthtime)
})

Модуль path и путь к файлу Node.js

Основной проблемой при работе с файлами и папками в операционных системах является разный синтаксис написания путей их расположения. Для решения этой проблемы в Node.js есть модуль path c набором полезных методов.

Для началы работы с модулем его нужно импортировать.

Пример:


const path = require(‘path');

Получение имени, пути и расширения файла

Предположим, что в папке /temp лежит файл template.txt. Воспользуемся методами модуля path для получения имени файла, пути к нему, а так же его расширения. 

Пример:


const file = ‘/temp/tempalate.txt'

path.basename(file) // tempalate.txt

Метод basename возвращает наименование файла. Первым параметром передается путь к файлу, вторым параметром (опционально) передается расширение файла, если необходимо получить наименование файла без расширения.

Пример:


path.basename(file, ‘.txt’) // tempalate

path.basename(file, ‘.txt’) // tempalate

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

Пример:


path.dirname(file) // /temp

Метод extname возвращает расширение переданного файла.

Пример:


path.extname(file) // .txt

Работа с путями файла

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

Метод join принимает список параметров, объединяет их в одну строку, используя разделитель, подходящий к конкретной операционной системе, в которой будет исполнятся код.

Пример:


const folderName = 'temp'
path.join('/', folderName, ‘template.txt') // /temp/template.txt

Метод resolve используется для нахождения абсолютных путей к файлу.

Пример:


path.resolve(‘template.txt') // Users/Desktop/dev/node-fs/template.txt
path.resolve(folderName, ‘template.txt’) // /Users/Desktop/dev/node-fs/temp/template.txt
path.resolve('/temp', ‘template.txt') // /temp/template.txt

Метод normalize позволяет найти путь к файлу, используя синтаксис переходов (.. и .) по папкам.

Пример:


path.normalize(‘/users/../temp/template.txt')
// temp/template.txt

Переименование файла или директории

Методы rename() и renameSync() первым параметром принимают путь к файлу, который нужно переименовать. Второй параметр отвечает за новое наименование файла. 

Пример:


const fs = require('fs')

// переименовываем файла
fs.rename('oldFile.txt', 'newFile.txt', (err) => {
    if (err) throw err;
    console.log('Файл переименован');
});

// переименовываем директорию
try {
    fs.renameSync('./oldDir', './newDir')
    console.log('Папка переименован');
} catch (error) {
      console.error(error)
}

Чтение файла

Пример:


const fs = require('fs')

// асинхронное
fs.readFile(‘template.txt', ‘utf-8’, (err, data) => {
    if (err) {
      console.error(err)
      return
    }
    console.log(data)
})

// синхронное
try {
    const data = fs.readFileSync(‘template.txt', 'utf-8')
    console.log(data)
} catch (err) {
    console.error(err)
}

Если вторым параметром не указана кодировка, то возвращается Buffer. Эти методы полностью загружают файлы в память компьютера, что может сильно отразиться на производительности. Если размер файла большой, то стоит воспользоваться потоками fs.createReadStream()

Запись файла 

Чтобы перезаписать контент файлов, используются методы writeFile и writeFileSync. Важный момент! Если файла, контент которого нужно перезаписать, не существует, то он будет создан автоматически.

Пример:


const fs = require('fs')
const content = 'Новый текст'

fs.writeFile('newText.txt', content, (err) => {
  if (err) {
    console.error(err)
    return
  }
  console.log('файл успешно перезаписан')
})

try {
    fs.writeFileSync('newTextTwo.txt', content)
    console.log('файл успешно перезаписан')
} catch (err) {
    console.error(err)
}

Копирование файла 

Методы copyFile() и copyFileSync() первым параметром принимают путь файла для копирования. Второй параметр принимает название пути и нового файла. Третьим параметром является колбэк функция, которая возвращает ошибку.

Пример:


const fs = require('fs')

fs.copyFile('oldFile.txt', 'oldFileTwo.txt', (err) => {
    if (err) {
        console.error(err)
        return
    }
    console.log('Файл успешно копирован')
});

try {
    fs.copyFileSync('oldFile.txt', 'oldFileTwo.txt')
    console.log('Файл успешно копирован')
} catch (error) {
    console.error(error)
}

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

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

Пример:


const fs = require('fs')

fs.unlink('file.txt', (err) => {
    if (err) throw err;
    console.log('Файл успешно удален');
});

try {
    fs.unlinkSync('file.txt')
    console.log('Файл успешно удален');
} catch (error) {
    console.error(error)
}

Заключение

В данной статье мы разобрали работу Node.js с файлами, на примерах посмотрели основные полезные методы модулей fs и path. Исходный код вы сможете найти тут. Надеюсь данная статья была вам полезна. Учитесь, думайте, пишите код. Удачного кодинга, друзья!

Полезные ссылки:

https://nodejs.org/en/ — официальный сайт node.js

Подписывайтесь на наш канал в Telegram для получения самой последней и актуальной информации. 

0
HTTP Server

NodeJS пишем свой HTTP Server

97

Node.js — программная платформа, основанная на движке V8. (транслирующем JavaScript в машинный код), превращающая JavaScript из узкоспециализированного языка в язык общего назначения 

Wikipedia

Доброго времени суток, друзья.

Поговорим сегодня о Node.js, а конкретнее о разработке статического сервера, который будет отдавать нам index.html по всем роутам. Это задача базового уровня, поэтому данный материл предназначен именно для начинающих разработчиков, которые только осваивают Node.js. В этой статье я буду идти от простого к сложному и постараюсь преподнести информацию наиболее подробным образом. Если вы еще не сталкивались с установкой Node.js и npm и вам необходимо написать HTTP Server, то следуйте инструкциям ниже.

Установка NodeJS

Для работы нам понадобится сама Node.js, установить ее можно, используя официальный сайт https://nodejs.org/en/

На сайте предлагается две версии для скачивания: LTS и Current(последняя), из которых можно выбрать любую, т.к это не повлияет на дальнейшую разработку. После скачивания дистрибутива, запустите установку. Чтобы проверить, что установка была произведена корректно, введите в консоли команду node -v, в качестве подтверждения терминал укажет установленную версию Node.js

Пример:

node -v

v12.14.0

NPM и создание package.json

В рабочей директории создадим новую папку и назовем ее http-server. Для работы c пакетами понадобится npm. Если у вас не установлен npm, то инсталлировать его можно, используя следующую команду: npm install npm@latest -g

Далее требуется создать файл package.json — для этого введите команду: npm init. В результате будет вызван bin npm — консольное приложение, через которое можно получить package.json. Вам будет предложен ряд вопросов, отвечая на которые вы cконфигурируете конечный файл.

Вот пример уже готового package.json


{
  "name": "http-server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/Web-Tricks-Master/node-http-server.git"
  },
  "keywords": [
    "node",
    "http-server"
  ],
  "author": "Kalachev Vlad",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/Web-Tricks-Master/node-http-server/issues"
  },
  "homepage": "https://github.com/Web-Tricks-Master/node-http-server#readme"
}

Файл server.js

Весь основной код мы будем писать в файле server.js. Для этого в корне проекта создадим файл с названием server.js, написав в нем следующий код:


console.log(‘Hello Node’);

Чтобы выполнить код данного файла, необходимо вызвать его через Node.js. В корне проекта в терминале вызовем команду: node server.js

Пример:

node server.js

// Hello Node

Hello World Server

Напишем первый сервер на Node.js c получением реального ответа по указанному адресу c телом в виде текста: Hello World!

Пример:


// server.js
const http = require('http');

http.createServer((req, res)=> {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World!');
}).listen(8080);

В указанном примере мы импортируем модуль http, с которым будем работать в дальнейшем. Для создания http сервера воспользуемся методом createServer, в первом аргументе которого пишем функцию (в примере использован синтаксис стрелочной функции, но можно также писать данный код с помощью function). Функция принимает два параметра: req и res (можно присваивать наименования по желанию). Первый параметр хранит всю информацию о входящем запросе, второй используется для отправки ответов серверу. В строке res.writeHead в ответе сервера добавляем статус 200 (запрос выполнен успешно) и вторым параметром тип контента (здесь достаточно указать, что мы возвращаем текст).

В строке res.end указываем само тело ответа. После того как сервер был создан, воспользуемся методом listen, чтобы развернуть входящее подключение по нужному нам адресу. Метод listen имеет три аргумента, первый из них отвечает за порт, второй — за локальный адрес, третий является функцией, которая вызывается после старта сервера.  В примере мы используем только первый параметр, второй будет назначен по умолчанию, как localhost.

Запустим наш сервер. В консоли наберем команду: node server.js и откроем в браузере адрес http://localhost:8080/ , после чего мы увидим выполнение нашего файла:

Отлично. Вот мы и реализовали наш HelloWorld на Node.js. Давайте усложним нашу систему и добавим работу с файлами, но прежде чуть автоматизируем процесс разработки.

Nodemon

Если попытаться внести изменения в код при развернутом сервере, то окажется, что новые правки не будут отображены. Для решения этой проблемы установим пакет nodemon, который будет следить за вашими изменениями в коде и автоматически перезагружать сервер. В корневой директории проекта введите следующую команду: npm i -D nodemon. Данный пакет нужен только для разработки, поэтому мы устанавливаем его в директорию devDependencies (используем флаг D). После установки пакета добавим в package.json в поле scripts следующие инструкции:


"scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js"
 }

Поле test можно удалить, т.к. мы не будем рассматривать его в данной статье. Теперь для запуска нашего сервера мы будем использовать npm. Чтобы запустить сервис с nodemon, введем команду: npm run dev, для режима демонстрации: npm start или npm run start (также разрешено конвенцией). Продолжим разработку в режиме dev.

HTTP Server

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

Пример:


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Demo</title>
</head>
<body>
    Node HTTP Server
</body>
</html>

В файле server.js напишем следующий код:


const http = require('http');
const fs = require(‘fs');
const path = require('path');
const PORT = 8080;

const server = http.createServer((req, res) => {
    res.writeHead(200, {
        'Content-Type': 'text/html',
        'Expires': '0'
    });
    fs.createReadStream(path.resolve(__dirname, 'index.html')).pipe(res)
})

server.listen(PORT, '0.0.0.0', () => {
    console.log('Server started!');
});

Для работы с файлами в Node.js используется модуль fs, импортируем его. Также, чтобы наше решение работало в разных операционных системах потребуется модуль path, благодаря которому мы сможем писать правильные пути до файлов. Чуть усложним наш метод listen. Вынесем порт в константу, вторым аргументом укажем адрес ‘0.0.0.0’ (он же localhost), и в третий добавим функцию, которая будет вызываться на старте сервера. В res.writeHead укажем статус и заголовок, в котором мы будем отдавать данные (text/html). Строка   ‘Expires’: ‘0′ означает, что каждый раз в момент получения ответа сервера мы будем чистить куки (делается это для того, чтобы всегда видеть корректные изменения в html файле без кэширования). В строке fs.createReadStream(path.resolve(__dirname, ‘index.html’)).pipe(res) происходит чтение файла из потока в виде стрима и связывание его через метод .pipe с выходящем потоком ответа.

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

После запуска нашего кода  получим следующий результат:

Мы пришли к поставленной цели. Теперь Node.js отдает нам index.html, на основе которого в дальнейшем мы сможем создавать статические сайты.

Заключение

Надеюсь данная статья была вам полезна. Исходный код вы сможете найти тут. Прекрасно понимаю, что для раздачи статики лучше всего использовать nginx как более простое и эффективное решение подобной задачи, но целью данной статьи является разработка именно на Node.js и объяснения базовых тем, на мой взгляд такой пример является более понятным для начинающих разработчиков. Учитесь, думайте, пишите код. Удачного кодинга, друзья!

Полезные ссылки:

https://nodejs.org/en/ — официальный сайт node.js  

https://github.com/nodejs — node.js на github

https://www.npmjs.com/ — официальный сайт 7pm

Подписывайтесь на наш канал в Telegram для получения самой последней и актуальной информации. 

0