Доброго времени суток, друзья!

Представляю вашему вниманию перевод второй части Руководства по Express — веб-феймворку для Node.js автора Flavio Copes.

Предполагается, что вы знакомы с Node.js. Если нет, то прошу сюда.

Без дальнейших предисловий.


11. Управление куки


Для управления куки используйте метод Response.cookie().

Например:

res.cookie('username', 'John')

Данный метод принимает третий аргумент, содержащий определенные настройки:

res.cookie('username', 'John', { domain: '.exmaple.com', path: '/admin', secure: true })

res.cookie('username', 'John', { expires: new Date(Date.now() + 900000), httpOnly: true })

Самыми полезными настройками являются следующие:
Значение Описание
domain название домена куки
expires определяет время жизни куки. При отсутствии или при значении, равном 0, куки будет удалена по окончанию сессии (при закрытии вкладки браузера)
httpOnly если true, то куки будет доступна только через веб-сервер
maxAge время жизни куки относительно текущего времени, определяется в миллисекундах
path путь к куки. По умолчанию /
secure если true, то куки будет доступна только по протоколу HTTPS
signed делает куки подписанной
sameSite если true, то куки доступна только запросам из одного источника

Куки можно удалить с помощью:

res.clearCookie('username')

12. Работа с HTTP-заголовками


Получение заголовков из запроса


Заголовки запроса можно получить через свойство Request.headers:

app.get('/', (req, res) => {
    console.log(req.headers)
})

Метод Request.header() используется для получения конкретного заголовка:

app.get('/', (req, res) => {
    req.header('User-Agent')
})

Изменение значения заголовка в ответе


Значение любого заголовка можно изменить с помощью метода Response.set():

res.set('Content-Type': 'text/html')

Для заголовка Content-Type имеется сокращенный вариант:

res.type('.html') // => 'text/html'

res.type('html') // => 'text/html'

res.type('json') // => 'application/json'

res.type('application/json') // => 'application/json'

res.type('png') // => 'image/png'

13. Перенаправления


Перенаправления являются распространенным явлением в веб-разработке. Для перенаправления используется метод Response.redirect():

res.redirect('/go-there')

Это создает постоянное перенаправление (302).

Временное перенаправление (301) можно сделать так:

res.redirect(301, '/go-there')

В качестве аргумента может использоваться абсолютный путь (/go-there), абсолютный URL (https://anothersite.com), относительный путь (go-there) или… для того, чтобы подняться на один уровень выше.

res.redirect('../go-there')
res.redirect('..')

Также можно вернуться к странице, указанной в заголовке Referer (по умолчанию имеет значение /):

res.redirect('back')

14. Маршрутизация


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

В примере Hello World мы использовали следующее:

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

Здесь обрабатываем GET-запросы к корневому домену /, отправляя определенный ответ.

Именованные параметры


Что если мы хотим обрабатывать определенные запросы. Допустим, мы хотим принимать строку и возвращать эту же строку, но состоящую только из заглавных букв и, при этом, не хотим использовать строку запроса. В этом случае нам на помощь приходят именованные параметры:
app.get('/uppercase/:theValue', (req, res) => res.send(req.params.theValue.toUpperCase()))

Если мы отправим такой запрос: /uppercase/test, то получим TEST в теле ответа.

В одном URL можно указывать несколько именованных параметров, все они будут сохранены в свойстве req.params.

Использлование регулярного выражения для определения пути


Для обработки нескольких запросов одним роутером можно использовать регулярные выражения:

app.get(/post/, (req, res) => {/* */})

В данном случае совпадения будут найдены для /post, /post/first, /thepost, /posting/something и т.д.

15. SOP (политика общего происхождения или одного источника)


JavaScript-приложения, запущенные в браузере, как правило, могут получать только ресурсы, находящиеся в том же источнике (origin — протокол, хост и порт).

Загрузка изображений или стилей/скриптов, обычно, работает, но запросы XHR или Fetch к другому серверу терпят неудачу до тех пор, пока сервер не разрешит совместное использование ресурсов.

Данный механизм называется CORS (Cross-Origin Resource Sharing).

Загрузка веб-шрифтов с помощью @font-face также следует этой политике, впрочем, как и загрузка других вещей (таких как текстуры WebGL или ресурсы для метода drawImage Canvas API).

Более того, CORS применяется и в отношении ES6 модулей.

Если вы не определите CORS на сервере для отправки ответов на запросы из других источников, эти запросы будут отклонены.

Пример отклонения Fetch-запроса:



Пример отклонения XHR-запроса:



Запросы из другого источника завершаются неудачно, если обращение осуществляется:

  • К другому домену
  • К другому поддомену
  • К другому порту
  • К другому протоколу

Это делается для вашей безопасности: для того, чтобы злонамеренные пользователи не сломали приложение.

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

Как это сделать?

Это зависит от того, какие технологии используются на сервере.

Пример с Express


Если вы используете Node.js и фреймворк Express, ППО CORS — это то, что вам нужно.

Вот реализация простого сервера:

const express = require('express')
const app = express()

app.get('/without-cors', (req, res, next) => {
    res.json({ msg: ':( no cors, no party!' })
})

const server = app.listen(3000, () => {
    console.log('Listening on port %s', server.address().port)
})

Если вы отправите запрос к этому серверу из другого источника, то получите ошибку.

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

const express = require('express')
const cors = require('cors')
const app = express()

app.get('/with-cors', cors(), (req, res, next) => {
    res.json({ msg: 'With cors it work! :)' })
})

Я сделал небольшой пример. Вот код клиента: https://glitch.com/edit/#!/flavio-cors-client

А вот код сервера: https://glitch.com/edit/#!/flaviocopes-cors-example-express

Обратите внимание, что ответы на неудачные запросы все равно отправляются. В соответствующем разделе инструментов разработчика (Network, сеть) можно увидеть сообщение от сервера:



Разрешаем только запросы из определенного источника


У рассмотренного подхода есть один существенный недостаток: любой запрос будет рассматриваться сервером как допустимый.

Как видите, ответ содержит заголовок Access-Control-Allow-Origin со значением *:



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

Перепишем наш код:

const cors = require('cors')

const corsOptions = {
    origin: 'https://yourdomain.com'
}

app.get('/products/:id', cors(corsOptions), (req, res, next) => {
    // ...
})

Также можно определить несколько допустимых источников:

const whitelist = ['https://example.com', 'https://example2.com']
const corsOptions = {
    origin: (origin, cb) =>
        (whitelist.indexOf(origin) !== -1)
            ? cb(null, true)
            : cb(new Error('Not allowed by CORS'))
}

Отправка предаврительных запросов


Некоторые запросы являются простыми, например, GET, POST, HEAD.

Что касается POST-запроса, то он является простым только в том случае, если его заголовок Content-Type имеет одно из следующих значений:

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

Все остальные запросы нуждаются в отправке предварительного запроса. Это требуется браузеру для предоставления или отказа в доступе на основании информации из OPTIONS-запроса.

Предварительный запрос содержит несколько заголовков, позволяющих браузеру определить права на доступ:

OPTIONS /the/resource/you/request
Access-Control-Request-Method: POST
Access-Control-Request-Headers: origin, x-requested-with, accept
Origin: https://yourdomain.com

Ответ сервера выглядит примерно так:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://yourdomain.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE

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

Возвращаясь к Express, вот как сервер должен обрабатывать OPTIONS-запросы:

const express = require('express')
const cors = require('cors')
const app = express()

// разрешаем OPTIONS для одного ресурса
app.options('the/resource/you/request', cors())

// разрешаем OPTIONS для всех ресурсов
app.options('*', cors())

На сегодня это все. В следующей части мы поговорим о шаблонизации (Pug), промежуточном программном обеспечении (промежуточном слое), работе со статическими файлами, отправке файлов и сессиях.

Следите за обновлениями. Благодарю за внимание и хорошего дня.