Ласкаво просимо, розробники! Трохи про середовище веб-фреймворків та middleware. Багато вражаючих розробок, якими пишаються девелопери, відбувається в Node.js — або просто Node. Незалежно від вашої вимови, Node дозволяє виконувати код JavaScript поза межами браузера. Node — це кросплатформне, з відкритим вихідним кодом середовище виконання для JavaScript.
Rails — це не Ruby. Rails є фреймворком із використанням архітектури модель-подання-контролер (MVC), який підтримує створення баз даних, веб-сервісів і вебсторінок. Rails базується на Ruby та підтримує такі функції, як формати JSON та XML, HTML і CSS. Платформа відома простим створенням таблиць баз даних, міграціями та scaffolding.
Rails заохочує розробників до використання підходу Convention over Configuration (Конвенція над конфігурацією), що зменшує кількість рішень, які розробник має приймати, зберігаючи функціональність. Як наслідок, цей високорівневий підхід сприяє дотриманню принципу DRY (Don’t Repeat Yourself — не повторюйся) у проєктах. Rails також є фреймворком для full-stack розробки, що забезпечує швидкий цикл розгортання.
Окрім логіки обробки даних, яку реалізують розробники, вебдодатки потребують способу взаємодії із зовнішнім світом, а не лише з локальною машиною. Для цього потрібні методи форматування, розповсюдження та інтерпретації повідомлень — саме тут з’являється middleware!
Middleware описує набір функцій, які мають доступ до об’єктів запиту req
і відповіді res
, що є критичними компонентами передачі даних через HTTP. Об'єкт запиту містить усі дані, пов'язані з рядком запиту, параметрами, HTTP-заголовками та тілом запиту, тоді як об'єкт відповіді містить дані, які ваш застосунок Express передає клієнту.
Middleware-функції допомагають розробникам вирішувати різноманітні задачі веброзробки. Зокрема, вони можуть:
- Виконувати стандартний JavaScript-код.
- Модифікувати об’єкти запиту та відповіді.
- Завершувати цикл запиту-відповіді.
- Викликати додаткові функції у стеку.
Express.js — або просто Express — це фреймворк для створення вебзастосунків, орієнтований на середовище виконання Node.js. Він обробляє всі низькорівневі процеси та протоколи, пов’язані із підключенням вашого застосунку до вебу. Express вважається "неупередженим" фреймворком, тобто він не змушує розробника реалізовувати рішення певним способом.
Express надає розробникам гнучкі функції та можливість інтегрувати додаткові модулі за потреби. Рекламований як легкий і мінімалістичний шар базових функцій, Express багатьма вважається стандартом серед фреймворків вебсерверів.
Що саме робить Express?
Express створює міст між складною обробкою даних на бекенді та клієнтським відображенням яскравих сторінок із персоналізованим контентом для користувачів. Коли користувач запитує сторінку, потрібно прийняти рішення, як цей запит обробити та що відобразити.
Зазвичай, обробка таких запитів включає запис або зчитування з бази даних, а також відповідь із цими даними. Цей робочий процес, відомий як цикл запиту-відповіді (Request/Response cycle), є основою роботи вебзастосунків і описує основну сферу застосування Express.
Розглянемо ситуацію, коли користувач намагається отримати доступ до вашої головної сторінки: клієнт надсилає запит до певного шляху URL, і сервер має розуміти, що це означає, та генерувати відповідний контент.
— — — — — — — — — — — — — — — — — — — — — — — — — —
Не потрібно бути Френком. Для багатьох Rails може забезпечити всі потреби у фреймворку з багатим функціоналом. Як уже згадувалося, Ruby on Rails вважається повноцінним фреймворком для веброзробки. Натомість представляємо Sinatra — легкий фреймворк, спрямований на швидку розробку, який пропонує лише мінімально необхідний функціонал для створення вебзастосунків.
Що ж робить Sinatra?
Sinatra побудовано на системі Rack, яка забезпечує модульний інтерфейс між вебзастосунками та вебсерверами — подібно до Rails. Це дозволяє API вебзастосунків і middleware об'єднуватися в єдиний метод, що обробляє всі базові HTTP-запити й відповіді. Sinatra забезпечує зв'язок між локальною машиною та зовнішнім світом.
Типи дій HTTP слідують моделі REST — це цикл запит/відповідь між клієнтом і сервером. Запити можуть бути представлені через GET, PUT, POST і DELETE, а відповіді формуються логікою сервера після обробки.
Розробники, використовуючи Express, можуть управляти потоком даних у застосунку, створюючи обробники для цих дій. Наприклад, якщо надходить певний запит (GET для головної сторінки), обробник перенаправляє цей запит на певну кінцеву точку (endpoint) у логіці сервера. Звідти відповідний контент і дані передаються клієнту. Легко!
Процес роботи:
- Користувач намагається переглянути ваш сайт (вводить URL у браузері).
- Браузер ініціює запит на вказаний URL.
- Запит потрапляє на ваш сервер, і маршрутизатор направляє його до кінцевої точки.
- Кінцева точка генерує HTML і дані.
- Сервер відповідає згенерованим контентом.
const PORT = process.env.PORT || 3000;
const express = require('express');
const app = express();
app.get('/', (req, res) => res.send('Hello, World!'));
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Цей код представляє абсолютний мінімум для налаштування сервера з використанням middleware Express. Порт, на якому працює сервер, можна задати у файлі або через змінну середовища, як показано вище.
Sinatra
Наш базовий вебзастосунок у цьому альтернативному фреймворку виглядатиме так:
require 'sinatra'
class App < Sinatra::Base
get '/' do
"Hello, World!"
end
end
Ми створили кінцеву точку для нашого вебзастосунку, і всі запити до неї оброблятимуться однаково — з поверненням «Hello, World!» користувачу. Будь-який інший запит, наприклад tweetbook.com/coolstuff
, поверне помилку 404, оскільки у нас налаштований лише один маршрут (кореневий '/'
).
Тепер уявімо, що ваш вебзастосунок має декілька сторінок, а не є односторінковим (SPA). Вам потрібно налаштувати кілька маршрутів для обробки різного контенту на кожній сторінці.
Код виглядав би приблизно так:
// ...конфігурація сервера з прикладу вищеapp.get('/', (req, res) => res.send('Hello, World!'));
app.get('/moon', (req, res) => res.send('Hello, Moon!'));
app.get('/venus', (req, res) => res.send('Hello, Venus!'));
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Хоча цей підхід є функціональним, файл швидко наповниться маршрутами та логікою для генерації даних, які відправляються клієнту. Тепер ми вводимо поняття маршрутизації.
Маршрутизація
Express дозволяє розробникам налаштувати модульний спосіб обробки вхідних запитів сервера. Маршрутизація використовує методи об'єкта app
у Express, які відповідають HTTP-методам, таким як GET і POST.
Методи маршрутизації визначають зворотний виклик — обробник для певного запиту. Вони дозволяють "слухати" запит, який відповідає зазначеним кінцевим точкам вашого застосунку. Коли знаходиться відповідність, зворотний виклик виконується.
Розглянемо нашу попередню конфігурацію сервера з реалізованою маршрутизацією:
// server.jsconst PORT = process.env.PORT || 3000;
const express = require('express');
const app = express();
const userRoutes = require('./userRoutes');
app.use(userRoutes());
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Middleware на рівні додатка
Ми вводимо нову реалізацію для app
— middleware-функцію рівня додатка без вказаного шляху монтування, що означає виконання коду для кожного запиту. Middleware на рівні додатка прив’язується до екземпляра об'єкта app
через функцію app.use(..)
.
Ми також ввели userRoutes
— це представляє нашу логіку зворотного виклику, функцію, яка посилається на зовнішній файл, що містить певні шляхи монтування, які оброблятиме вебзастосунок.
// userRoutes.jsconst express = require('express');
const router = express.Router();
module.exports = () => {
router.get('/', (req, res) => {
res.send('Hello, World!');
});
router.get('/moon', (req, res) => {
res.send('Hello, Moon!');
});
router.get('/venus', (req, res) => {
res.send('Hello, Venus!');
});
return router;
};
Middleware на рівні маршрутизатора
Працює так само, як і middleware на рівні додатка, але прив’язаний до екземпляра об'єкта express.Router()
. Принципи реалізації обробки маршрутів у Express або Sinatra схожі, але їхня композиція досить різна.
get '/' do
"Hello World!"
end
get '/venus' do
"Hello Venus!"
end
post '/' do
.. створити щось ..
end
delete '/' do
.. видалити щось ..
end
Зверніть увагу, що хоча існує значна лексична різниця між двома мовами, очікування щодо їхньої поведінки залишаються постійними. HTTP-протоколи стандартизовані, що дозволяє широкому спектру платформ і кодових баз взаємодіяти в колективній екосистемі вебпростору.
З огляду на це, Ruby і Sinatra мають доступ до тих самих об'єктів request
(запит) і response
(відповідь), що й Express. І, як і в Express, маршрути обробляються в порядку, у якому вони визначені, із доступом до шаблонів маршрутів через хеш params
.
get '/:name' do |n|
# обробляє "GET /moon" і "GET /venus"
# params['name'] дорівнює 'moon' або 'venus'
# n зберігає params['name']
"Hello #{n}!"
end
Від базового до повноцінного: Ми побачили, що потрібно для запуску простого вебзастосунку, тепер розглянемо, як може виглядати його реалізація у реальному світі.
Наш код розширить основу, створену раніше, включивши в себе різноманітні практичні модулі та middleware (проміжне програмне забезпечення).
Наш реальний (sub)stack покаже:
- Morgan: реєстратор HTTP-запитів
- Body Parser: аналізує вхідні тіла запитів до їх обробки
- Cookie Session: зберігає зашифровані дані сесії на стороні клієнта
- Bcrypt: шифрування паролів для збереження на сервері
// server.jsconst PORT = process.env.PORT || 3000;
const express = require('express');
const app = express();
const bodyParser = require("body-parser");
const cookieSession = require('cookie-session');
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieSession({
name: 'session',
keys: ['blahblah'], // ніколи не додавайте в публічний код
maxAge: 24 * 60 * 60 * 1000
}));
const userRoutes = require('./userRoutes');
app.use(userRoutes());
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Подивімося на наш файл маршрутів ще раз:
// userRoutes.jsconst bcrypt = require('bcrypt');
const express = require('express');
const router = express.Router();
module.exports = () => {
router.get('/', (req, res) => {
res.send('Hello, World!');
});
router.get('/moon', (req, res) => {
res.send('Hello, Moon!');
});
router.get('/venus', (req, res) => {
res.redirect('/login');
});
router.post("/login", (req, res) => {
const pwClient = req.body['password'];
const email = req.body['emailAddress'];
const pwServer = getPW(users, email); (...перевірка користувача в БД)
if(user) {
req.session.user_id = userID;
res.redirect('/venus');
} else {
res.redirect('/');
};
});
return router;
};
Ми можемо досягти подібного результату до коду на Javascript у Sinatra, але цього разу покажемо деякі функції валідації користувачів:
gemfile(true) do
source 'https://rubygems.org'
gem 'sinatra', '~> 1.4'
gem 'bcrypt', '~> 3.1'
end
require 'sinatra/base'
require 'bcrypt'
def hash_password(password)
BCrypt::Password.create(password).to_s
end
def test_password(password, hash)
BCrypt::Password.new(hash) == password
end
class AuthExample < Sinatra::Base
enable :inline_templates
enable :sessions
get '/sign_in' do
erb :sign_in
end
post '/sign_in' do
user = USERS.find { |u| u.username == params[:username] } (...перевірка користувача в БД)
if user
session.clear
session[:user_id] = user.id
redirect '/'
else
@error = 'Username or password was incorrect'
erb :sign_in
end
end
run!
end
Хоча приклади не є повними, вони ілюструють типовий робочий процес, який можна зустріти під час створення вебзастосунку на основі Node.js і Express або Rails та фреймворку Sinatra. Ми побачили, як обидва надають спосіб структурувати наші проєкти та засоби для взаємодії з HTTP-об'єктами, такими як запит або відповідь. Sinatra пропонує трохи більше "магії", оскільки деякі більш ручні або "явні" декларації виконуються за кулісами. Обидва інструменти надають розробникам можливість... розробляти! Готово.
KA.Bica
Перекладено з: ExpressJS vs Sinatra