Знакомство с фреймворком Connect
Russian (Pусский) translation by Ilya Nikov (you can also view the original English article)
Новичкам в NodeJS его API обычно кажется сложным. К счастью, многие разработчики создали фреймворки, которые упрощают работу с node. Connect - один из таких фреймворков. Он находится поверх API Node и рисует линию между комфортом и контролем.
Думайте о Connect как стеке middleware. С каждым запросом подключайте фильтры через слои middleware, каждый из которых имеет возможность обрабатывать HTTP-запрос. Когда T.J. Holowaychuk объявил Connect, он сказал, что существует два типа middleware. Первый - это фильтр.
Фильтры обрабатывают запрос, но они не реагируют на него (подумайте о регистрации сервера).
Другой тип - это провайдер, который отвечает на запрос. Вы можете включить столько слоев middleware, сколько захотите; запрос проходит через каждый уровень, пока один из middleware не ответит на запрос.
Основной синтаксис
Во-первых, вам нужно установить пакет Connect через npm:
npm install connect
Теперь создайте файл server.js
и добавьте следующий код:
var connect = require("connect");
Переменная connect
- это функция, которая возвращает новое приложение Connect. Итак, наш следующий шаг - создать это приложение:
var app = connect();
Вам не нужно создавать переменную app
для большинства ваших приложений. Функции, связанные с созданием приложения (connect()
и use()
), можно связать:
connect() .use(/* middleware */) .use(/* middleware */) .listen(3000);
Функция use()
добавляет в приложение слой middleware, а функция listen()
сообщает нашему приложению начать прием соединений на указанном порту (3000 в этом примере).
Начнем с чего-то простого: logging. Код приложения Connect, использующего только middleware для ведения логов, довольно прост:
connect() .use(connect.logger()) .listen(3000);
По умолчанию Node очень мало обрабатывает входящий запрос.
Добавьте этот код в свой файл и запустите сервер, выполнив команду node server.js
. Перейдите к любому пути в вашем браузере и проигнорируйте результаты «Can not GET ...». Нам неинтересно, что сервер отправил обратно в браузер; нас интересует журнал сервера. Посмотрите на терминал, и вы увидите логи ваших запросов. Обязательно ознакомьтесь с документацией логера для получения информации о других его функциях и настройках.
Это был фильтр; теперь давайте посмотрим на провайдера. Простейшим провайдером является статический провайдер; он служит для статических файлов из указанной папки. Вот его синтаксис:
.use(connect.static(__dirname + "/public")
Вероятно, вы можете угадать цель переменной Node __dirname
: это путь к текущему каталогу. Это middleware статически обслуживает все, что угодно, из папки public
в текущем каталоге. Итак, создайте public/page.html
и добавьте элемент <h1>.
Перезагрузите сервер (node server.js
) и перейдите к localhost:3000/page.html
в своем браузере. Вы должны увидеть page.html
в браузере.
Давайте теперь рассмотрим некоторые другие варианты middleware Connect.
Парсинг тела запроса
По умолчанию Node обрабатывает очень мало входящего запроса, но вы можете включить несколько разных фильтров для синтаксического анализа запроса, если вам нужно справиться с большей сложностью. Есть четыре фильтра:
-
connect.json()
анализирует тела запроса JSON (гдеcontent-type
-application/json
). -
connect.urlencoded()
разобратьx-www-form-urlencoded
тело запроса. -
connect.multipart()
анализирует объекты запросаmultipart/form-data
. -
connect.bodyParser()
- это ярлык для включения всех трех из них.
Использование любого из этих фильтров дает вам возможность получить доступ к вашему анализируемому телу через request.body
(мы поговорим о том, как быстро получить этот объект request
).
Я думаю, что эти фильтры - хороший пример того, как настроить ваш контроль с помощью Connect. Вы можете использовать очень небольшую обработку, чтобы оптимизировать ваше приложение.
Парсинг файлов cookie и сессий
Файлы cookie и сеансы являются важной частью любого веб-приложения, и есть несколько middleware, которые помогают им управлять. Connection.cookieParser()
анализирует файлы cookie для вас, и вы можете получить куки и их значения через объект request.cookies
. Это более полезно, если вы добавите фильтр connect.session()
в свое приложение. Этот фильтр требует, чтобы парсер cookie уже был на месте. Вот небольшой пример:
connect() .use(connect.cookieParser()) .use(connect.session({ secret: 'some secret text', cookie: { maxAge: 30000 }})) .use(function(req, res) { var sess = req.session, url = req.url.split("/"); if (url[1] == "name" && url[2]) { sess.name = url[2]; res.end("name saved: " + url[2]); } else if (sess.name) { res.write("session-stored name: " + sess.name); res.end("stored for another: " + (sess.cookie.maxAge / 1000) + " seconds"); } else { res.end("no stored name; go to /name/{name} to save a name"); } }).listen(3000);
Каждая функция middleware, которую вы пишете, должна либо передать запрос на
next
уровень, либо ответить на запрос.
После cookieParser
мы включаем фильтр session
и передаем ему две опции:
-
secret
создает подписанный файл cookie, который отслеживает сеанс. -
cookie.maxAge
определяет продолжительность жизни файла куки в миллисекундах; 30000 в этом коде составляет 30 секунд.
В финальном вызове use()
мы передаем функцию, которая отвечает на запрос. Мы используем два свойства объекта request
: req.session
для данных сеанса и req.url
для URL-адреса запроса.
Если приложение получает запрос для /name/some_name
, оно сохраняет значение some_name
в req.session.name
. Все, что хранится в сеансе, можно получить в последующих запросах на длину нашего сеанса. Любые запросы, сделанные к /name/other
, заменяют переменную сеанса, и любые запросы к другим URL-адресам выводят значение переменной сеанса и время, оставшееся для сеанса.
Итак, вы можете перейти к localhost:3000/name/your_name
, а затем перейти на localhost:3000
, чтобы увидеть your_name
. Обновите страницу несколько раз и просмотрите отсчет секунд. По окончании сеанса вы увидите сообщение «no stored name» по умолчанию.
Я упомянул, что фильтр cookieParser
должен появиться перед session
.
Порядок включения имеет важное значение для middleware, потому что запрос передается по порядку от слоя к слою.
Поскольку для session
нужны проанализированные данные cookie, запрос должен проходить через cookieParser
перед session
.
Я мог бы объяснить каждый другой встроенный компонент промежуточного программного обеспечения, но я просто упомянул еще несколько, прежде чем писать собственный код для взаимодействия с Connect.
- compress: middlware для сжатия Gzip
- basicAuth: базовая HTTP-аутентификация
- directory: список каталогов
- errorHandler: гибкий обработчик ошибок
Написание собственного middleware
Вы только что научились писать свой собственный код с помощью Connect. Вот основной синтаксис еще раз:
.use(function (req, res, next) { })
Важны три параметра функции; они обеспечивают доступ к внешнему миру. Параметр req
- это, конечно, объект запроса, а res
- ответ. Третий параметр, next
, является ключом к созданию функций, которые хорошо работают в стеке middleware. Это функция, которая передает запрос на следующее middleware в стеке:
connect() .use(function (req, res, next) { if (req.method === 'POST') { res.end("This is a POST request"); } else { next(); } }) .use(function (req, res) { res.end("This is not a POST request (probably a GET request)"); }).listen(3000);
В этом коде используются две функции middleware. Первая функция проверяет метод запроса, чтобы узнать, является ли это POST-запросом. Если это так, она отвечает так. В противном случае мы вызываем next()
и передаем запрос следующей функции, которая отвечает всегда. Используйте curl
для тестирования обоих слоев в терминале:
$ curl http://localhost:3000 This is not a POST request (probably a GET request) $ curl -X POST http://localhost:3000 This is a POST request
Если вам не нравится терминал, попробуйте этот удобный плагин Chrome.
Важно помнить, что каждая функция middleware, которую вы пишете, должна либо передать запрос на next
уровень, либо ответить на запрос. Если у вашей функции есть несколько ветвей (через операторы if или другие условия), вы должны убедиться, что каждая ветка передает запрос или отвечает на него. Если ваше приложение зависает в браузере, возможно, потому, что вы забыли вызвать next()
в какой-то момент.
Теперь, как насчет этих параметров request
и response
? Это те же объекты запроса и ответа, которые вы получаете при использовании «необработанного» сервера node:
require("http").createServer(function (req, res) { // ... }).listen(3000);
Если вы раньше не использовали API-интерфейс Node, позвольте мне показать вам, что вы можете с ним сделать.
Объект запроса
Объект request
фактически является объектом http.IncomingMessage
, а его важные свойства перечислены ниже:
-
req.method
сообщает вам, какой метод HTTP использовался. -
req.url
сообщает вам, какой URL был запрошен. -
req.headers
- это объект с именами и значениями заголовков. -
req.query
- это объект с любыми данными в строке запроса (для синтаксического анализа вам понадобится middlewareconnect.query()
). -
req.body
- это объект данных формы (вам понадобится middleware для разбора тела). -
req.cookies
- это объект данных cookie (требуется разбор файлов cookie). -
req.session
является объектом данных сеанса (опять же, вам понадобится разбор cookie и middleware session)
Вы можете увидеть все это при работе со следующим кодом:
connect() .use(connect.query()) // gives us req.query .use(connect.bodyParser()) // gives us req.body .use(connect.cookieParser()) // for session .use(connect.session({ secret: "asdf" })) // gives us req.session .use(function (req, res) { res.write("req.url: " + req.url + "\n\n"); res.write("req.method: " + req.method + "\n\n"); res.write("req.headers: " + JSON.stringify(req.headers) + "\n\n"); res.write("req.query: " + JSON.stringify(req.query) + "\n\n"); res.write("req.body: " + JSON.stringify(req.body) + "\n\n"); res.write("req.cookies: " + JSON.stringify(req.cookies) + "\n\n"); res.write("req.session: " + JSON.stringify(req.session)); res.end(); }).listen(3000);
Чтобы увидеть что-то для каждого из этих значений, вам нужно отправить некоторые данные в URL с строкой запроса. Этого должно быть достаточно:
curl -X POST -d "name=YourName" "http://localhost:3000/some/url?some=data"
С помощью этих семи свойств вы можете управлять практически любым запросом, который вы получите. Я не думаю, что трейлеры часто используются (я никогда не видел их в своем опыте), но вы можете использовать req.trailers
, если вы ожидаете их в своих запросах (трейлеры похожи на заголовки, но после тела).
Итак, как насчет вашего ответа?
Объект ответа
Необработанный объект ответа не обеспечивает роскошь, которую предоставляют библиотеки (например, Express). Например, вы не можете отвечать простым вызовом рендеринга на готовый шаблон - по крайней мере, не по умолчанию. В ответе очень мало, поэтому вам нужно заполнить все мелкие детали.
Мы начнем с кода состояния и заголовков ответов. Вы можете установить их все сразу, используя метод writeHead()
. Вот пример из документов Node:
var body = 'hello world'; response.writeHead(200, { 'Content-Length': body.length, 'Content-Type': 'text/plain' });
Если вам нужно индивидуально настроить заголовки, вы можете использовать метод setHeader()
:
connect() .use(function (req, res) { var accept = req.headers.accept.split(","), body, type; console.log(accept); if (accept.indexOf("application/json") > -1) { type = "application/json"; body = JSON.stringify({ message: "hello" }); } else if (accept.indexOf("text/html") > -1) { type = "text/html"; body = "<h1> Hello! </h1>"; } else { type = "text/plain"; body = "hello!"; } res.statusCode = 200; res.setHeader("Content-Type", type); res.end(body); }).listen(3000);
Добавьте этот код в файл, запустите сервер и запросите его в браузере. У вас есть HTML! Теперь запустите:
curl http://localhost:3000
И вы получите простой текст. Для JSON попробуйте следующее:
curl -H "accept:application/json" http://localhost:3000
Все с одного и того же URL!
Используйте res.getHeader(name)
, если вам нужно знать, какие заголовки уже установлены. Вы также можете использовать res.removeHeader(name)
, чтобы удалить заголовок.
Конечно, ответ бесполезен без тела. Как вы видели в этом руководстве, вы можете записать куски данных в тело с помощью метода res.write()
. Это принимает строку или буферный объект в качестве аргумента. Если это строка, вторым параметром является тип кодировки (по умолчанию используется utf8).
Метод res.end()
закрывает тело, но вы можете передавать ему данные для записи в поток ответов. Это полезно в ситуациях, когда вам нужно выводить только одну строку.
Сторонние middleware
Сложно ответить на большие тела HTML в обычном старом node и Connect. Это хорошее место, чтобы использовать middleware. Вы можете найти список сторонних middleware в вики Connect Github. В качестве примера мы собираемся использовать пакет connect-jade, который позволяет нам отображать jade представления.
Сначала установите connect-jade
:
npm install connect-jade
Затем необходимо запросить и добавить его в качестве middleware. Вы хотите установить несколько значений по умолчанию:
var connect = require("connect"), connectJade = require("connect-jade"); connect() .use(connectJade({ root: __dirname + "/views", defaults: { title: "MyApp" } })) .use(function (req, res) { res.render("index", { heading: "Welcome to My App" }); }).listen(3000);
Задайте корень как каталог, содержащий файлы отображений. Вы также можете установить defaults
; это переменные, доступные во всех представлениях, если мы не переопределим их позже при вызове render()
.
Последняя функция в этом коде вызывает вызов res.render()
. Этот метод предоставляется пакетом connect-jade
.
Первым аргументом, который он принимает, является имя представления для рендеринга.
Это путь к представлению, без пути, который мы определили при добавлении middleware, без расширения jade-файла. Для этого кода нам нужен шаблон views/index.jade
для рендеринга. Мы будем держать это просто:
html head title= title body h1= heading
Если вы не знакомы с jade, у нас есть имена тегов отступа, чтобы создать структуру HTML. Знак равенства получает значение переменной JavaScript. Эти переменные исходят из defaults
, которые мы установили, плюс (необязательный) второй объект параметра, переданный в res.render()
.
Есть много других сторонних middleware, но они работают аналогично друг другу. Вы устанавливаете их через npm, требуете их и вводите их в действие.
Модули как middleware
Если вы вникнете в работу Connect, вы обнаружите, что каждый слой на самом деле является модулем node - очень интеллектуальный дизайн. Если вы используете Connect для крупных приложений, было бы идеально написать ваш код в формате модуля Node. Возможно, у вас есть файл app.js
:
// app.js module.exports = function (req, res, next) { res.end("this comes from a module"); };
И в вашем server.js
:
var connect = require("connect"), app = require("./app"); connect() .use(app) .listen(3000);
Заключение
Если вам нужна удобная для начинающих библиотека, которая упрощает создание больших веб-приложений, то Connect не является вашим решением. Connect должен быть тонким слоем поверх исходного Node API, который дает вам полный контроль над вашим серверным приложением. Если вы хотите немного больше, я рекомендую Express (тем же самым людям, кстати). В противном случае Connect - это фантастическая расширяемая библиотека для веб-приложений Node.