Создание веб-сервера в Node.js с помощью модуля HTTP Часть 2

Создание веб-сервера в Node.js с помощью модуля HTTP Часть 2

AlexxSafonov

https://t.me/AlexxSafonov

Шаг 3 — Вывод страницы HTML из файла

Код HTML можно выводить пользователю в виде строк Node.js, но желательно загружать файлы HTML и выводить их содержимое. Так нам не нужно хранить длинные строки кода HTML в файле Node.js, за счет чего код становится более компактным, и мы получаем возможность независимо работать с разными частями сайта. Такая концепция разделения часто используется в веб-разработке, поэтому важно знать, как правильно загружать файлы HTML для их поддержки в Node.js.

Для вывода файлов HTML мы загружаем их с помощью модуля fs и используем их данные при написании ответа HTTP.

Вначале создадим файл HTML, который будет возвращать наш веб-сервер. Создайте новый файл HTML:

touch index.html

Copy

Откройте файл index.html в текстовом редакторе:

nano index.html

Copy

Наша веб-страница будет минимальной. Она будет иметь оранжевый фон и содержать текст приветствия в центре. Добавьте в файл следующий код:

first-servers/index.html

<!DOCTYPE html>

<head>
    <title>My Website</title>
    <style>
        *,
        html {
            margin: 0;
            padding: 0;
            border: 0;
        }

        html {
            width: 100%;
            height: 100%;
        }

        body {
            width: 100%;
            height: 100%;
            position: relative;
            background-color: rgb(236, 152, 42);
        }

        .center {
            width: 100%;
            height: 50%;
            margin: 0;
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            color: white;
            font-family: "Trebuchet MS", Helvetica, sans-serif;
            text-align: center;
        }

        h1 {
            font-size: 144px;
        }

        p {
            font-size: 64px;
        }
    </style>
</head>

<body>
    <div class="center">
        <h1>Hello Again!</h1>
        <p>This is served from a file</p>
    </div>
</body>

</html>

Copy

На этой веб-странице отображается две строки текста: Hello Again! и This is served from a file. Строки отображаются друг над другом в центре страницы. Первая строка текста отображается как заголовок, то есть она будет больше. Вторая строка текста будет немного меньше. Весь текст будет выводиться белым цветом на оранжевом фоне страницы.

Хотя это не относится к настоящей статье и серии статей, вы можете узнать больше об HTML, CSS и других технологиях создания веб-клиентов с помощью руководства Введение в веб-технологии от Mozilla.

Это весь код HTML, который нам нужен, так что теперь можно сохранить и закрыть файл, нажав CTRL+X. Теперь мы можем перейти к коду сервера.

В этом упражнении мы будем работать с файлом htmlFile.js. Откройте этот файл в текстовом редакторе:

nano htmlFile.js

Copy

Поскольку нам нужно прочитать файл, для начала импортируем модуль fs:

first-servers/htmlFile.js

const http = require("http");
const fs = require('fs').promises;
...

Copy

Этот модуль содержит функцию readFile(), которую мы будем использовать для загрузки файла HTML. Мы импортируем вариант обещания в соответствии с современными передовыми практиками работы с JavaScript. Мы используем обещания, поскольку с синтаксической точки зрения они лучше функций обратного вызова, к которым нам пришлось бы прибегнуть, если бы мы назначили fs как require('fs'). Дополнительную информацию о лучших практиках асинхронного программирования можно найти в нашем руководстве Написание асинхронного кода в Node.js.

Нам нужно, чтобы при отправке пользователем запроса к системе считывался наш файл HTML. Для начала изменим requestListener() для чтения файла:

first-servers/htmlFile.js

...
const requestListener = function (req, res) {
    fs.readFile(__dirname + "/index.html")
};
...

Copy

Мы используем метод fs.readFile() для загрузки файла. Он использует аргумент __dirname + "/index.html". Специальная переменная __dirname содержит абсолютный путь к директории запуска кода Node.js. В конце мы добавляем /index.html, чтобы мы могли загрузить ранее созданный файл HTML.

Возвратим страницу HTML после ее загрузки:

first-servers/htmlFile.js

...
const requestListener = function (req, res) {
    fs.readFile(__dirname + "/index.html")
        .then(contents => {
            res.setHeader("Content-Type", "text/html");
            res.writeHead(200);
            res.end(contents);
        })
};
...

Copy

Если обещание fs.readFile() успешно выполняется, оно возвращает свои данные. Для этого случая мы используем метод then(). Параметр contents содержит данные файла HTML.

Вначале мы задаем для заголовка Content-Type значение text/html, чтобы сообщить клиенту, что мы возвращаем данные HTML. Затем мы пишем код состояния, показывая, что запрос выполнен успешно. В заключение мы отправляем на клиент загруженную страницу HTML с данными в переменной contents.

Иногда метод fs.readFile() может выполняться с ошибками, и нам нужно предусмотреть подобные случаи. Добавьте в функцию requestListener() следующее:

first-servers/htmlFile.js

...
const requestListener = function (req, res) {
    fs.readFile(__dirname + "/index.html")
        .then(contents => {
            res.setHeader("Content-Type", "text/html");
            res.writeHead(200);
            res.end(contents);
        })
        .catch(err => {
            res.writeHead(500);
            res.end(err);
            return;
        });
};
...

Copy

Сохраните файл и закройте nano, нажав CTRL+X.

Когда в обещании возникает ошибка, оно отклоняется. Эта ситуация обрабатывается с помощью метода catch(). Он принимает ошибку, возвращаемую fs.readFile(), устанавливает код состояния 500, сигнализирующий о внутренней ошибке, и возвращает пользователю сообщение об ошибке.

Запустите наш сервер с помощью команды node:

node htmlFile.js

Copy

Откройте в браузере адрес http://localhost:8000. Вы увидите следующую страницу:


Мы вывели пользователю страницу HTML с сервера. Теперь мы можем закрыть запущенный сервер, нажав CTRL+C. Сделав это, мы увидим командную строку терминала.

При написании такого кода в производственной среде не всегда желательно загружать страницу HTML при каждом получении запроса HTTP. В нашем случае страница HTML занимает всего 800 байт, но на сложных сайтах размер страниц может доходить до нескольких мегабайт. Загрузка больших файлов занимает много времени. Если на вашем сайте ожидается большой трафик, лучше всего загружать файлы HTML при запуске и сохранять их содержимое. После их загрузки вы можете настроить сервер так, чтобы он прослушивал запросы адреса.

Чтобы продемонстрировать этот метод, покажем, как можно сделать сервер более эффективным и масштабируемым.

Эффективный вывод кода HTML

Вместо того чтобы загружать страницу HTML для каждого запроса, мы загрузим ее только один раз, в самом начале. Запрос будет возвращать данные, загруженные нами при запуске.

Откройте в терминале скрипт Node.js с помощью текстового редактора:

nano htmlFile.js

Copy

Добавим новую переменную, прежде чем создавать функцию requestListener():

first-servers/htmlFile.js

...
let indexFile;

const requestListener = function (req, res) {
...

Copy

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

Изменим функцию requestListener(). Теперь вместо загрузки файла она будет возвращать содержимое indexFile:

first-servers/htmlFile.js

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "text/html");
    res.writeHead(200);
    res.end(indexFile);
};
...

Copy

Далее мы изменим логику чтения файла с функции requestListener() на момент запуска нашего сервера. Внесите следующие изменения при создании сервера:

first-servers/htmlFile.js

...

const server = http.createServer(requestListener);

fs.readFile(__dirname + "/index.html")
    .then(contents => {
        indexFile = contents;
        server.listen(port, host, () => {
            console.log(`Server is running on http://${host}:${port}`);
        });
    })
    .catch(err => {
        console.error(`Could not read index.html file: ${err}`);
        process.exit(1);
    });

Copy

Сохраните файл и закройте nano, нажав CTRL+X.

Код, считывающий файл, похож на написанный нами при первой попытке. Однако при успешном чтении файла мы можем сохранить его содержимое в глобальной переменной indexFile. Мы запустим сервер с методом listen(). Главное — загрузить файл до запуска сервера. Так функция requestListener() гарантированно возвращает страницу HTML, поскольку переменная indexFile больше не пустая.

Блок обработки ошибок также изменился. Если файл не удается загрузить, мы записываем ошибку и выводим ее на консоль. Затем мы закрываем программу Node.js с помощью функции exit() без запуска сервера. Так мы видим, почему не удалось прочитать файл, и можем решить проблему и снова запустить сервер.

Мы создали разные веб-серверы, возвращающие пользователю разные типы данных. Пока что мы не использовали данные запросов для определения конкретного возвращаемого контента. Нам потребуется использовать данные запросов при настройке маршрутов или путей сервера Node.js, так что теперь мы посмотрим, как это работает.

Шаг 4 — Управление маршрутами с использованием объекта HTTP Request

Большинство посещаемых нами сайтов и используемых нами API имеют несколько конечных точек, что позволяет получать доступ к разным ресурсам. Хорошим примером является система управления книгами, которая может использоваться в библиотеке. Ей нужно будет не только управлять данными книг, но и управлять данными авторов для составления каталогов и обеспечения удобства поиска.

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

Создадим новый сервер для небольшой библиотеки, который будет возвращать два разных типа данных. Если пользователь откроет адрес сервера с /books, он получит список книг в формате JSON. Если пользователь откроет раздел /authors, он получит список с информацией об авторах в формате JSON.

До сих пор мы возвращали одинаковые ответы на каждый получаемый запрос. Рассмотрим небольшой пример.

Запустим заново наш пример с ответом JSON:

node json.js

Copy

Отправим на другом терминале запрос cURL, как мы делали ранее:

curl http://localhost:8000

Copy

Вы увидите следующее:

Output
{"message": "This is a JSON response"}

Теперь попробуем другую команду curl:

curl http://localhost:8000/todos

Copy

После нажатия Enter вы увидите тот же самый результат:

Output
{"message": "This is a JSON response"}

Мы не встраивали в функцию requestListener() никакую специальную логику дял обработки запроса, URL которого содержит /todos, и поэтому Node.js по умолчанию возвращает то же сообщение JSON.

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

Для начала закройте сервер, нажав CTRL+C.

Откройте файл routes.js в своем текстовом редакторе:

nano routes.js

Copy

Начнем с сохранения наших данных JSON в переменных перед функцией requestListener():

first-servers/routes.js

...
const books = JSON.stringify([
    { title: "The Alchemist", author: "Paulo Coelho", year: 1988 },
    { title: "The Prophet", author: "Kahlil Gibran", year: 1923 }
]);

const authors = JSON.stringify([
    { name: "Paulo Coelho", countryOfBirth: "Brazil", yearOfBirth: 1947 },
    { name: "Kahlil Gibran", countryOfBirth: "Lebanon", yearOfBirth: 1883 }
]);
...

Copy

Переменная books — это строка, содержащая данные JSON для массива объектов книг. Каждая книга имеет заголовок или название, автора и год издания.

Переменная authors — это строка, содержащая данные JSON для массива объектов авторов. Каждый автор имеет имя, страну рождения и год рождения.

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

Вначале нужно убедиться, что все ответы нашего сервера будут иметь правильный заголовок Content-Type:

first-servers/routes.js

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "application/json");
}
...

Copy

Далее нам нужно возвращать подходящие данные JSON в зависимости от URL, используемого пользователем. Создадим выражение switch для URL запроса:

first-servers/routes.js

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "application/json");
    switch (req.url) {}
}
...

Copy

Чтобы получить путь URL от объекта request, нам потребуется доступ к его свойству url. Теперь мы можем добавить в выражение switch варианты для возврата подходящего кода JSON.

Выражение switch в JavaScript позволяет определять, какой код будет выполняться в зависимости от значения объекта или выражения JavaScript (например, от результата математической операции). Если вы не знаете или забыли, как использовать такие выражения, воспользуйтесь нашим руководством Использование выражения switch в JavaScript.

Продолжим и добавим вариант, когда пользователь хочет получить список книг:

first-servers/routes.js

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "application/json");
    switch (req.url) {
        case "/books":
            res.writeHead(200);
            res.end(books);
            break
    }
}
...

Copy

Мы устанавливаем код состояния 200, указывая, что запрос обработан правильно, и возвращаем данные JSON, содержащие список книг. Добавим еще один вариант для авторов:

first-servers/routes.js

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "application/json");
    switch (req.url) {
        case "/books":
            res.writeHead(200);
            res.end(books);
            break
        case "/authors":
            res.writeHead(200);
            res.end(authors);
            break
    }
}
...

Copy

Как и раньше, мы используем код состояния 200 для подтверждения правильного выполнения запроса. Теперь мы возвращаем данные JSON со списком авторов.

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

routes.js

...
const requestListener = function (req, res) {
    res.setHeader("Content-Type", "application/json");
    switch (req.url) {
        case "/books":
            res.writeHead(200);
            res.end(books);
            break
        case "/authors":
            res.writeHead(200);
            res.end(authors);
            break
        default:
            res.writeHead(404);
            res.end(JSON.stringify({error:"Resource not found"}));
    }
}
...

Copy

Мы используем ключевое слово default в выражении switch, чтобы учесть все остальные сценарии, кроме описанных в предыдущих вариантах. Мы устанавливаем код состояния 404, указывающий, что запрошенный URL не найден. Далее мы задаем объект JSON, содержащий сообщение об ошибке.

Протестируем поведение нашего сервера. Запустите на другом терминале команду, чтобы проверить, получим ли мы список книг:

curl http://localhost:8000/books

Copy

Нажмите Enter, чтобы получить следующий результат:

Output
[{"title":"The Alchemist","author":"Paulo Coelho","year":1988},{"title":"The Prophet","author":"Kahlil Gibran","year":1923}]

Пока все хорошо. Попробуем то же самое для /authors. Введите в терминале следующую команду:

curl http://localhost:8000/authors

Copy

После выполнения команды вы увидите следующее:

Output
[{"name":"Paulo Coelho","countryOfBirth":"Brazil","yearOfBirth":1947},{"name":"Kahlil Gibran","countryOfBirth":"Lebanon","yearOfBirth":1883}]

В заключение попробуем ввести ошибочный URL, чтобы функция requestListener() вывела сообщение об ошибке:

curl http://localhost:8000/notreal

Copy

При вводе этой команды будет выведено следующее сообщение:

Output
{"error":"Resource not found"}

Закройте работающий сервер, нажав CTRL+C.

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

Заключение

В этом обучающем руководстве мы создали несколько серверов HTTP Node.js. Вначале мы сформировали простой текстовый ответ. Затем мы перешли к возврату с сервера разных типов данных: JSON, CSV и HTML. Затем мы рассмотрели комбинирование загрузки файлов с ответами HTTP для вывода пользователю страниц HTML с сервера и создания API, использующего данные запроса пользователя для определения ответа.

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


проект можно скачать гит хаб

Код на гит хаб





Report Page