Работа с файлами и шаблонизация
Цель: изучить возможности работы с файловой системой, отправки ответа с содержимым файлов и правильными заголовками.
Содержание и порядок выполнения лабораторной работы
Изучить
Документацию модуля node:fs и работу с ним, модуля path и dirname, работу со строками, санитизацию html.
Источники
- Документация node
- Node.js: работа с файловой системой - Хабр
- Документация path
- Документация dirname
- Официальный сайт Node
- Введение в Node на английском
- Введение в Node на русском
- Руководство по Node на русском (PDF)
- Курс по Node
- JSON
Чтение и отправка данных из файлов
- Продолжите работу на ветке из прошлого занятия и допишите новый функционал.
- Добавьте в проект папку public. В ней разместите index.html, style.css и normalize.css/reset.css, взяв файлы из работ прошлого семестра. Другой вариант - добавить свои файлы index.html, style.css с минимальным содержимым, но корректным содержимым.
- Также добавьте страницу 404.html с заголовком об ошибке.
- Синхронно считайте файлы в память с помощью модуля fs. Вы получите объект
Buffer
, что работать с ним как со строкой используйте метод.toString()
. - С помощью статического метода Buffer.byteLength(string[, encoding]) посчитать длину строки.
- Создайте объект
Map
, в котором ключами являются пути до файлов от корня, например, /index.html, а значениями вложенные объекты, в которых содержится само содержимое файла, его длина и тип контента (text/html, text/css). - Напишите функцию, которая из входящего запроса по пути ищет совпадение в объекте с содержимым файлов, при нахождении совпадений, подготавливает ответ устанавливая заголовки ‘content-type’, ‘content-length’ и тело ответа. Если путь указывает на корень /, то в ответе отправляйте index.html.
- Протестируйте работу сервера с помощью браузера, REST Client или аналога.
Шаблонизация
- Создайте папку templates. В ней разместите файл lastcomment.html. К этому файлу подключите style.css. В lastcomment.html добавьте разметку под последний комментарий с именем и текстовым содержимым. В шаблоне, где должны появиться имя и комментарий, выделите с двух сторон обозначение редко встречаемой комбинацией символов, например, %% обозначение %% или {{ обозначение }}.
- Используйте модуль path для формирования путей к файлам. Например, path.join(__dirname, ‘public’, ‘index.html’).
- Файл lastcomment.html пусть читается асинхронно при каждом запросе, чтобы можно было без перезапуска сервера менять его структуру.
- Добавьте проверку на наличие файла перед его чтением с помощью fs.existsSync или fs.promises.access.
- Данная страница должна возвращаться по GET-запросу на путь /lastcomment.html. Перед тем как ее отправить в ответе, замените с помощью метода строки replace обозначения на реальные данные из массива комментариев, выполнив минимальную санитизацию данных с помощью функции ниже.
- Рассчитайте длину тела ответа, подготовьте заголовки и отправьте ответ.
- Если возникают проблемы с чтением файлов или заменой по шаблону, то сервер должен возвращать ошибку 500 Internal Server Error.
- Протестируйте работу сервера с помощью браузера, REST Client или аналога.
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
Общая логика
- Сервер должен последовательно искать совпадения путей. Сначала в объекте со статическими файлами, потом с шаблонами, затем по оставшимся вариантам. GET-запросы на любые другие пути должны передавать страницу 404 с соответствующим кодом статуса http.
- Если вы сталкиваетесь с ситуацией, когда вы пишете схожую логику в разных местах проекта, подумайте как ее можно обобщить и выносите в отдельные вспомогательные функции и отдельные файлы.
Нагрузочное тестирование
Добавьте в ваш проект файл с кодом ниже. В нем вы можете подставлять в TARGET_URL url для тестирования одного из путей. Протестируйте основные пути с get-запросами, выпишите результаты, оцените на сколько отличается обработка разных путей.
import http from 'node:http';
const TOTAL_REQUESTS = 1000;
const CONCURRENCY = 100;
const TARGET_URL = "http://localhost:4321";
let completedRequests = 0;
let startTime;
async function sendRequest() {
return new Promise((resolve, reject) => {
http
.get(TARGET_URL, (res) => {
res.on("data", () => {});
res.on("end", () => {
completedRequests++;
resolve();
});
})
.on("error", (err) => {
reject(err);
});
});
}
async function runLoadTest() {
startTime = Date.now();
const workers = Array.from({ length: CONCURRENCY }, async () => {
while (completedRequests < TOTAL_REQUESTS) {
await sendRequest();
}
});
await Promise.all(workers);
const endTime = Date.now();
const duration = (endTime - startTime) / 1000;
const rps = TOTAL_REQUESTS / duration;
console.log(`Requests per second: ${rps.toFixed(2)}`);
}
runLoadTest().catch((err) => {
console.error("Load test failed:", err);
});
Требования к коду и результат выполнения
- Код отформатирован, не содержит ошибок и замечаний от статического анализа кода ESLint, сохранен в системе контроля версий.
- Отсутствуют ошибки в консоли.
- Работающий локально сервер с логикой, которая соответствует требованиям задания.
- Сервер проходит все тесты.
Вопросы для защиты
- Заголовки Content-Type, Transfer-Encoding и Content-length. Связь и назначение.
- Чтение файлов. Методы, параметры, возвращаемые значения. Синхронный и асинхронный подходы.
- Объект Buffer, преобразование в строку, кодировки.
- Санитизация html. Цель, методы.
- Объект Map и обычный объект. Разница, области применения.
- Метрика RPS. Назначение, вычисление. Выводы по вашим результатам.