Skip to content
Веб-разработка
GitHub

Работа с файлами и шаблонизация

Цель: изучить возможности работы с файловой системой, отправки ответа с содержимым файлов и правильными заголовками.

Содержание и порядок выполнения лабораторной работы

Изучить

Документацию модуля node:fs и работу с ним, модуля path и dirname, работу со строками, санитизацию html.

Источники

  1. Документация node
  2. Node.js: работа с файловой системой - Хабр
  3. Документация path
  4. Документация dirname
  5. Официальный сайт Node
  6. Введение в Node на английском
  7. Введение в Node на русском
  8. Руководство по Node на русском (PDF)
  9. Курс по Node
  10. JSON

Чтение и отправка данных из файлов

  1. Продолжите работу на ветке из прошлого занятия и допишите новый функционал.
  2. Добавьте в проект папку public. В ней разместите index.html, style.css и normalize.css/reset.css, взяв файлы из работ прошлого семестра. Другой вариант - добавить свои файлы index.html, style.css с минимальным содержимым, но корректным содержимым.
  3. Также добавьте страницу 404.html с заголовком об ошибке.
  4. Синхронно считайте файлы в память с помощью модуля fs. Вы получите объект Buffer, что работать с ним как со строкой используйте метод .toString().
  5. С помощью статического метода Buffer.byteLength(string[, encoding]) посчитать длину строки.
  6. Создайте объект Map, в котором ключами являются пути до файлов от корня, например, /index.html, а значениями вложенные объекты, в которых содержится само содержимое файла, его длина и тип контента (text/html, text/css).
  7. Напишите функцию, которая из входящего запроса по пути ищет совпадение в объекте с содержимым файлов, при нахождении совпадений, подготавливает ответ устанавливая заголовки ‘content-type’, ‘content-length’ и тело ответа. Если путь указывает на корень /, то в ответе отправляйте index.html.
  8. Протестируйте работу сервера с помощью браузера, REST Client или аналога.

Шаблонизация

  1. Создайте папку templates. В ней разместите файл lastcomment.html. К этому файлу подключите style.css. В lastcomment.html добавьте разметку под последний комментарий с именем и текстовым содержимым. В шаблоне, где должны появиться имя и комментарий, выделите с двух сторон обозначение редко встречаемой комбинацией символов, например, %% обозначение %% или {{ обозначение }}.
  2. Используйте модуль path для формирования путей к файлам. Например, path.join(__dirname, ‘public’, ‘index.html’).
  3. Файл lastcomment.html пусть читается асинхронно при каждом запросе, чтобы можно было без перезапуска сервера менять его структуру.
  4. Добавьте проверку на наличие файла перед его чтением с помощью fs.existsSync или fs.promises.access.
  5. Данная страница должна возвращаться по GET-запросу на путь /lastcomment.html. Перед тем как ее отправить в ответе, замените с помощью метода строки replace обозначения на реальные данные из массива комментариев, выполнив минимальную санитизацию данных с помощью функции ниже.
  6. Рассчитайте длину тела ответа, подготовьте заголовки и отправьте ответ.
  7. Если возникают проблемы с чтением файлов или заменой по шаблону, то сервер должен возвращать ошибку 500 Internal Server Error.
  8. Протестируйте работу сервера с помощью браузера, REST Client или аналога.
function escapeHtml(unsafe) {
  return unsafe
    .replace(/&/g, "&")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#039;");
}

Общая логика

  1. Сервер должен последовательно искать совпадения путей. Сначала в объекте со статическими файлами, потом с шаблонами, затем по оставшимся вариантам. GET-запросы на любые другие пути должны передавать страницу 404 с соответствующим кодом статуса http.
  2. Если вы сталкиваетесь с ситуацией, когда вы пишете схожую логику в разных местах проекта, подумайте как ее можно обобщить и выносите в отдельные вспомогательные функции и отдельные файлы.

Нагрузочное тестирование

Добавьте в ваш проект файл с кодом ниже. В нем вы можете подставлять в 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, сохранен в системе контроля версий.
  • Отсутствуют ошибки в консоли.
  • Работающий локально сервер с логикой, которая соответствует требованиям задания.
  • Сервер проходит все тесты.

Вопросы для защиты

  1. Заголовки Content-Type, Transfer-Encoding и Content-length. Связь и назначение.
  2. Чтение файлов. Методы, параметры, возвращаемые значения. Синхронный и асинхронный подходы.
  3. Объект Buffer, преобразование в строку, кодировки.
  4. Санитизация html. Цель, методы.
  5. Объект Map и обычный объект. Разница, области применения.
  6. Метрика RPS. Назначение, вычисление. Выводы по вашим результатам.