Linux Professional Institute Learning Logo.
Перехід до основного вмісту
  • Головна
    • Усі ресурси
    • LPI Навчальні матеріали
    • Стати дописувачем
    • Видавничі партнери
    • Cтати видавничим партнером
    • Про нас
    • FAQ
    • Дописувачі
    • Дорожня карта
    • Контакти
  • LPI.org
035.2 Урок 2
Тема 031: Розробка програмного забезпечення та вебтехнології
031.1 Основи розробки програмного забезпечення
  • 031.1 Урок 1
031.2 Архітектура вебзастосунків
  • 031.2 Урок 1
031.3 Основи HTTP
  • 031.3 Урок 1
Тема 032: Розмітка HTML-документа
032.1 Анатомія HTML-документа
  • 032.1 Урок 1
032.2 Семантика HTML та ієрархія документів
  • 032.2 Урок 1
032.3 HTML-покликання та вбудовані ресурси
  • 032.3 Урок 1
032.4 HTML-форми
  • 032.4 Урок 1
Тема 033: Дизайн контенту за допомогою CSS
033.1 Основи CSS
  • 033.1 Урок 1
033.2 CSS-селектори та застосування стилів
  • 033.2 Урок 1
033.3 CSS-стилі
  • 033.3 Урок 1
033.4 CSS Box модель і макет
  • 033.4 Урок 1
Тема 034: JavaScript програмування
034.1 JavaScript виконання та синтаксис
  • 034.1 Урок 1
034.2 Структури даних JavaScript
  • 034.2 Урок 1
034.3 Структури та функції керування JavaScript
  • 034.3 Урок 1
  • 034.3 Урок 2
034.4 JavaScript-маніпулювання контентом та стилями вебсайту
  • 034.4 Урок 1
Тема 035: Серверне програмування NodeJS
035.1 Основи NodeJS
  • 035.1 Урок 1
035.2 Основи NodeJS Express
  • 035.2 Урок 1
  • 035.2 Урок 2
035.3 Основи SQL
  • 035.3 Урок 1
How to get certified
  1. Тема 035: Серверне програмування NodeJS
  2. 035.2 Основи NodeJS Express
  3. 035.2 Урок 2

035.2 Урок 2

Сертифікат:

Основи веброзробки

Версія:

1.0

Розділ:

035 Серверне програмування Node.js

Тема:

035.2 Основи NodeJS Express

Урок:

2 з 2

Вступ

Вебсервери мають дуже різноманітні механізми для отримання відповідей на запити клієнтів. Для деяких запитів вебсерверу достатньо надати статичну необроблену відповідь, оскільки запитуваний ресурс однаковий для будь-якого клієнта. Наприклад, коли клієнт запитує зображення, доступне для всіх, серверу достатньо надіслати файл, що містить зображення.

Але коли відповіді генеруються динамічно, може знадобитися структурувати їх краще, ніж прості рядки, записані в сценарії сервера. У таких випадках вебсерверу зручно генерувати повний документ, який може бути інтерпретований та відтворений клієнтом. У контексті розробки вебзастосунків документи HTML зазвичай створюються як шаблони і зберігаються окремо від серверного сценарію, який вставляє динамічні дані у заздалегідь визначені місця у відповідному шаблоні, а потім надсилає відформатовану відповідь клієнту.

Вебзастосунки часто споживають як статичні, так і динамічні ресурси. HTML-документ, навіть якщо він був створений динамічно, може мати покликання на статичні ресурси, як-от CSS-файли та зображення. Щоб продемонструвати, як Express допомагає впоратися з таким викликом, ми спочатку налаштуємо приклад сервера, який доставляє статичні файли, а потім реалізуємо маршрути, які генерують структуровані відповіді на основі шаблонів.

Статичні файли

Першим кроком є створення файлу JavaScript, який працюватиме як сервер. Будемо дотримуватись того ж шаблону, який розглядався в попередніх уроках, щоб створити простий Express-застосунок: спочатку створіть каталог під назвою server, а потім встановіть базові компоненти за допомогою команди npm:

$ mkdir server
$ cd server/
$ npm init
$ npm install express

Для точки входу можна використовувати будь-яке ім’я файлу, але тут ми будемо використовувати ім’я файлу за замовчуванням: index.js. У наступному лістингу показано основний файл index.js, який буде використовуватися як відправна точка для нашого сервера:

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.listen(port, host, () => {
  console.log(`Server ready at http://${host}:${port}`)
})

Вам не потрібно писати явний код, щоб надіслати статичний файл. Express має проміжне програмне забезпечення для цієї мети, яке називається express.static. Якщо вашому серверу потрібно надіслати статичні файли клієнту, просто завантажте проміжне програмне забезпечення express.static на початку сценарію:

app.use(express.static('public'))

Параметр public зазначає каталог, де зберігаються файли, які клієнт може запитати. Шляхи, які запитують клієнти, не повинні містити каталог public, а лише ім’я файлу або шлях до файлу відносно каталогу public. Щоб запитати файл public/layout.css, наприклад, клієнт здійснює запит до /layout.css.

Форматований вивід

Тоді як надсилання статичного вмісту є простим, для динамічно згенерованого вмісту це інакше. Створення динамічних відповідей із короткими повідомленнями дає змогу легко тестувати програми на початкових етапах розробки. Наприклад, нижче наведено тестовий маршрут, який просто повертає клієнту повідомлення, надсилаючи його методом HTTP POST. Відповідь може просто реплікувати вміст повідомлення у вигляді простого тексту, без будь-якого форматування:

app.post('/echo', (req, res) => {
  res.send(req.body.message)
})

Подібний маршрут є хорошим прикладом для використання під час вивчення Express і для діагностичних цілей, коли достатньо «сирої» відповіді, надісланої за допомогою res.send(). Але робочий сервер повинен бути здатним виробляти більш складні відповіді. Зараз ми перейдемо до розробки такого більш складного типу маршруту.

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

Щоб отримувати та зберігати клієнтські повідомлення, надіслані під час поточного сеансу, найперше нам потрібно включити додаткові модулі для обробки файлів cookie та даних, надісланих за допомогою методу HTTP POST. Єдина мета наведеного нижче прикладу сервера – реєструвати повідомлення, надіслані через POST, і відображати раніше надіслані повідомлення, коли клієнт надсилає запит GET. Отже, для шляху / є два маршрути. Перший маршрут виконує запити, здійснені за допомогою методу HTTP POST, а другий виконує запити, здійснені за допомогою методу HTTP GET:

const express = require('express')
const app = express()
const host = "myserver"
const port = 8080

app.use(express.static('public'))

const cookieParser = require('cookie-parser')
app.use(cookieParser())

const { v4: uuidv4 } = require('uuid')

app.use(express.urlencoded({ extended: true }))

// Масив для зберігання повідомлень
let messages = []

app.post('/', (req, res) => {

  // Only JSON enabled requests
  if ( req.headers.accept != "application/json" )
  {
    res.sendStatus(404)
    return
  }

  // Знайти файл cookie в запиті
  let uuid = req.cookies.uuid

  // Якщо немає uuid cookie, створимо його
  if ( uuid === undefined )
    uuid = uuidv4()

  // Додаємо повідомлення на початку масиву повідомлень
  messages.unshift({uuid: uuid, message: req.body.message})

  // Збираємо всі попередні повідомлення для uuid
  let user_entries = []
  messages.forEach( (entry) => {
    if ( entry.uuid == req.cookies.uuid )
      user_entries.push(entry.message)
  })

  // Оновлення дати закінчення терміну дії файлів cookie
  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  // Повертаємо JSON-відповідь
  res.json(user_entries)

})

app.get('/', (req, res) => {

  // Лише запити з підтримкою JSON
  if ( req.headers.accept != "application/json" )
  {
    res.sendStatus(404)
    return
  }

  // Визначаємо наявність файлу cookie в запиті
  let uuid = req.cookies.uuid

  // Власні повідомлення клієнта
  let user_entries = []

  // Якщо немає uuid cookie, створимо його
  if ( uuid === undefined ){
    uuid = uuidv4()
  }
  else {
    // Collect messages for uuid
    messages.forEach( (entry) => {
      if ( entry.uuid == req.cookies.uuid )
        user_entries.push(entry.message)
    })
  }

  // Оновити дату закінчення терміну дії файлів cookie
  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  // Повертаємо JSON-відповідь
  res.json(user_entries)

})

app.listen(port, host, () => {
  console.log(`Server ready at http://${host}:${port}`)
})

Ми залишили конфігурацію статичних файлів у верхній частині, тому що незабаром буде корисно надавати статичні файли, такі як layout.css. На додаток до проміжного програмного забезпечення cookie-parser, представленого в попередньому розділі, приклад також містить проміжне програмне забезпечення uuid для створення унікального ідентифікаційного номера, що передається як файл cookie кожному клієнту, який надсилає повідомлення. Якщо вони ще не встановлені в каталозі прикладу сервера, ці модулі можна встановити за допомогою команди npm install cookie-parser uuid.

Глобальний масив під назвою messages зберігає повідомлення, надіслані всіма клієнтами. Кожен елемент у цьому масиві складається з об’єкта з властивостями uuid та message.

Що дійсно нового в цьому скрипті, так це метод res.json(), який використовується в кінці двох маршрутів для створення відповіді у форматі JSON з масивом, що містить повідомлення, уже надіслані клієнтом:

// Повертаємо JSON-відповідь
res.json(user_entries)

JSON – це звичайний текстовий формат, який дає змогу згрупувати набір даних в одну структуру, яка є асоціативною: тобто вміст виражається як ключі та значення. JSON особливо корисний, коли ми збираємось відповіді обробляти клієнтом. Використовуючи цей формат, об’єкт або масив JavaScript можна легко відновити на стороні клієнта з усіма властивостями та індексами оригінального об’єкта на сервері.

Оскільки ми структуруємо кожне повідомлення в JSON, ми відхиляємо запити, які не містять application/json у accept заголовку:

// Лише запити з підтримкою JSON
if ( req.headers.accept != "application/json" )
{
  res.sendStatus(404)
  return
}

Запит, здійснений за допомогою звичайної команди curl для вставки нового повідомлення, не буде прийнятий, оскільки curl за замовчуванням не визначає application/json у заголовку accept:

$ curl http://myserver:8080/ --data message="My first message" -c cookies.txt -b cookies.txt
Not Found

Параметр -H "accept: application/json" змінює заголовок запиту, щоб вказати формат відповіді, який цього разу буде прийнято та відповісти у зазначеному форматі:

$ curl http://myserver:8080/ --data message="My first message" -c cookies.txt -b cookies.txt -H "accept: application/json"
["My first message"]

Отримання повідомлень за допомогою іншого маршруту здійснюється у такий самий спосіб, але цього разу за допомогою методу HTTP GET:

$ curl http://myserver:8080/ -c cookies.txt -b cookies.txt -H "accept: application/json"
["Another message","My first message"]

Шаблони

Відповіді у таких форматах, як JSON, зручні для обміну даними між програмами, але основна мета більшості серверів вебзастосунків – створювати HTML-контент для використання людиною. Вбудовування HTML-коду до коду JavaScript не є гарною ідеєю, оскільки змішування мов в одному файлі робить програму більш сприйнятливою до помилок і шкодить підтримці коду.

Express може працювати з різними шаблонізаторами, які виділяють HTML для динамічного контенту; повний список можна знайти на https://expressjs.com/en/resources/template-engines.html [сайті з Express-шаблонізаторами]. Одним із найпопулярніших шаблонізаторів є Embedded JavaScript (EJS), який дає змогу створювати HTML-файли зі спеціальними тегами для вставки динамічного контенту.

Як і інші компоненти Express, EJS необхідно встановити до каталогу, де працює сервер:

$ npm install ejs

Далі двигун EJS має бути встановлений як засіб візуалізації за замовчуванням у сценарії сервера (на початку файлу index.js, перед визначеннями маршруту):

app.set('view engine', 'ejs')

Відповідь, згенерована за шаблоном, надсилається клієнту за допомогою функції res.render(), яка отримує в якості параметрів ім’я файлу шаблону та об’єкт, що містить значення, які будуть доступні з шаблону. Маршрути, використані в попередньому прикладі, можна переписати, щоб генерувати як HTML-відповіді, так і JSON-відповіді:

app.post('/', (req, res) => {

  let uuid = req.cookies.uuid

  if ( uuid === undefined )
    uuid = uuidv4()

  messages.unshift({uuid: uuid, message: req.body.message})

  let user_entries = []
  messages.forEach( (entry) => {
    if ( entry.uuid == req.cookies.uuid )
      user_entries.push(entry.message)
  })

  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  if ( req.headers.accept == "application/json" )
    res.json(user_entries)
  else
    res.render('index', {title: "My messages", messages: user_entries})

})

app.get('/', (req, res) => {

  let uuid = req.cookies.uuid

  let user_entries = []

  if ( uuid === undefined ){
    uuid = uuidv4()
  }
  else {
    messages.forEach( (entry) => {
      if ( entry.uuid == req.cookies.uuid )
        user_entries.push(entry.message)
    })
  }

  let expires = new Date(Date.now());
  expires.setDate(expires.getDate() + 30);
  res.cookie('uuid', uuid, { expires: expires })

  if ( req.headers.accept == "application/json" )
    res.json(user_entries)
  else
    res.render('index', {title: "My messages", messages: user_entries})

})

Зверніть увагу, що формат відповіді залежить від заголовка accept, знайденого в запиті:

if ( req.headers.accept == "application/json" )
  res.json(user_entries)
else
  res.render('index', {title: "My messages", messages: user_entries})

Відповідь у форматі JSON надсилається лише в тому випадку, якщо клієнт цього явно вимагає. В іншому випадку відповідь генерується з шаблону index. Той самий масив user_entries забезпечує як вихідні дані JSON, так і шаблон, але об’єкт, який використовується як параметр для останнього, також має властивість title: "My messages", яка буде використовуватися як заголовок всередині шаблону.

Шаблони HTML

Як і статичні файли, файли, що містять шаблони HTML, знаходяться у власному каталозі. За замовчуванням EJS передбачає, що файли шаблонів знаходяться в каталозі views/. У прикладі використовувався шаблон під назвою index, тому EJS шукає файл views/index.ejs. Нижче наведено вміст простого шаблону views/index.ejs, який можна використовувати із цим прикладом коду:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title><%= title %></title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/layout.css">
</head>
<body>

<div id="interface">

<form action="/" method="post">
<p>
  <input type="text" name="message">
  <input type="submit" value="Submit">
</p>
</form>

<ul>
<% messages.forEach( (message) => { %>
<li><%= message %></li>
<% }) %>
</ul>

</div>

</body>
</html>

Перший спеціальний тег EJS — це елемент <title> у розділі <head>:

<%= title %>

Під час процесу візуалізації цей спеціальний тег буде замінено значенням властивості title об’єкта, переданого як параметр функції res.render().

Більшість шаблонів складається зі звичайного HTML-коду, тому шаблон містить HTML-форму для надсилання нових повідомлень. Тестовий сервер відповідає на методи HTTP GET і POST для того самого шляху /, звідси атрибути action="/" і method="post" у тегу форми.

Інші частини шаблону є поєднанням HTML-коду та тегів EJS. EJS має теги для певних цілей у шаблоні:

<% … %>

Вставки контролю потоку. Цей тег безпосередньо не вставляє контент, але його можна використовувати зі структурами JavaScript, щоб вибрати, повторити або приховати розділи HTML. Приклад запуску циклу: <% messages.forEach( (message) => { %>

<%# … %>

Визначає коментар, вміст якого ігнорується синтаксичним аналізатором. На відміну від коментарів, написаних у HTML, ці коментарі не видно клієнту.

<%= … %>

Вставляє екранований вміст змінної. Важливо уникнути невідомого вмісту, щоб уникнути виконання коду JavaScript, яке може відкрити лазівки для атак міжсайтового скриптингу (XSS). Приклад: <%= title %>

<%- … %>

Вставляє вміст змінної без екранування.

Поєднання HTML-коду та тегів EJS є очевидним у фрагменті, де клієнтські повідомлення відображаються у вигляді списку HTML:

<ul>
<% messages.forEach( (message) => { %>
<li><%= message %></li>
<% }) %>
</ul>

У цьому фрагменті перший тег <% …​ %> запускає оператор forEach, який перебирає всі елементи масиву message. Розділювачі <% та %> дають змогу керувати фрагментами HTML. Новий елемент списку HTML, <li><%= message %></li>, буде створено для кожного елемента messages. Із цими змінами сервер надішле відповідь у HTML, коли буде отримано запит, подібний до наступного:

$ curl http://myserver:8080/ --data message="This time" -c cookies.txt -b cookies.txt
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>My messages</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="/layout.css">
</head>
<body>

<div id="interface">

<form action="/" method="post">
<p>
  <input type="text" name="message">
  <input type="submit" value="Submit">
</p>
</form>

<ul>

<li>This time</li>

<li>in HTML</li>

</ul>

</div>

</body>
</html>

Розділення між кодом для обробки запитів і кодом для представлення відповіді робить код більш чистим і дає змогу команді розробників розділити розробку застосунку між людьми з різними спеціалізаціями. Вебдизайнер, наприклад, може зосередитися на файлах шаблонів у views/ та відповідних таблицях стилів, які надаються як статичні файли, що зберігаються в каталозі public/ на сервері прикладу.

Вправи до посібника

  1. Як налаштувати express.static, щоб клієнти могли запитувати файли в каталозі assets?

  2. Як можна визначити тип відповіді, вказаний у заголовку запиту, у межах Express-маршруту?

  3. Який метод параметра маршруту res (response) генерує відповідь у форматі JSON з масиву JavaScript під назвою content?

Дослідницькі вправи

  1. За замовчуванням файли шаблонів Express знаходяться в каталозі views. Як можна змінити це налаштування, щоб файли шаблонів зберігалися в templates?

  2. Припустимо, що клієнт отримує відповідь HTML без заголовка (тобто <title></title>). Після перевірки шаблону EJS розробник знаходить тег <title><% title %></title> у розділі head файлу. Яка ймовірна причина проблеми?

  3. Використайте теги EJS-шаблону, щоб записати HTML-тег <h2></h2> із вмістом змінної JavaScript h2. Цей тег має відображатися лише, якщо змінна h2 не порожня.

Підсумки

У цьому уроці розглядаються основні методи Express.js для генерування статичних і відформатованих, проте динамічних відповідей. Щоб налаштувати HTTP-сервер для статичних файлів, потрібно небагато зусиль, а система EJS-шаблонів забезпечує простий спосіб створення динамічного вмісту з файлів HTML. Цей урок охоплює наступні концепції та процедури:

  • Використання express.static для відповідей у вигляді статичних файлів.

  • Як створити відповідь у відповідності до поля content type в заголовку запиту.

  • JSON-структуровані відповіді.

  • Використання EJS-тегів у шаблонах на основі HTML.

Відповіді до вправ посібника

  1. Як налаштувати express.static, щоб клієнти могли запитувати файли в каталозі assets?

    Слід додати до сценарію сервера виклик до app.use(express.static('assets')) .

  2. Як можна визначити тип відповіді, вказаний у заголовку запиту, у межах Express-маршруту?

    Клієнт встановлює прийнятні типи в полі заголовка accept, яке зіставляється з властивістю req.headers.accept.

  3. Який метод параметра маршруту res (response) генерує відповідь у форматі JSON з масиву JavaScript під назвою content?

    Метод res.json(): res.json(content).

Відповіді до дослідницьких вправ

  1. За замовчуванням файли шаблонів Express знаходяться в каталозі views. Як можна змінити це налаштування, щоб файли шаблонів зберігалися в templates?

    Каталог можна визначити в початкових налаштуваннях сценарію за допомогою app.set('views', './templates').

  2. Припустимо, що клієнт отримує відповідь HTML без заголовка (тобто <title></title>). Після перевірки шаблону EJS розробник знаходить тег <title><% title %></title> у розділі head файлу. Яка ймовірна причина проблеми?

    Тег <%= %> має використовуватися для охоплення вмісту змінної, як у <%= title %>.

  3. Використайте теги EJS-шаблону, щоб записати HTML-тег <h2></h2> із вмістом змінної JavaScript h2. Цей тег має відображатися лише, якщо змінна h2 не порожня.

    <% if ( h2 != "" ) { %>
    <h2><%= h2 %></h2>
    <% } %>

Linux Professional Institute Inc. Всі права захищені. Відвідайте веб-сайт навчальних матеріалів: https://learning.lpi.org
Ця робота ліцензована відповідно до міжнародної ліцензії Creative Commons Attribution-некомерційна-NoDerivatives 4.0.

Наступний Урок

035.3 Основи SQL (035.3 Урок 1)

Прочитайте наступний урок

Linux Professional Institute Inc. Всі права захищені. Відвідайте веб-сайт навчальних матеріалів: https://learning.lpi.org
Ця робота ліцензована відповідно до міжнародної ліцензії Creative Commons Attribution-некомерційна-NoDerivatives 4.0.

LPI є некомерційною організацією.

© 2023 Linux Professional Institute (LPI) - це глобальний стандарт сертифікації та організація підтримки кар'єри для професіоналів з відкритим вихідним кодом. Маючи понад 200 000 власників сертифікатів, це перший і найбільший в світі незалежний від постачальників орган з сертифікації Linux і з відкритим вихідним кодом. LPI має сертифікованих фахівців в більш ніж 180 країнах, здає іспити на декількох мовах і має сотні партнерів по навчанню.

Наша мета-надати економічні та творчі можливості для всіх, зробивши сертифікацію знань і навичок з відкритим вихідним кодом загальнодоступною.

  • LinkedIn
  • flogo-RGB-HEX-Blk-58 Facebook
  • Twitter
  • Зв'яжіться з нами
  • Політика конфіденційності та Cookie-файлів

Помітили помилку або хочете допомогти поліпшити цю сторінку? Просимо дайте нам знати.

© 1999–2023 Linux Professional Institute Inc. Всі права захищені.