Статьи

Введение в Generators & Koa.js: Часть 2

Конечный продукт
Что вы будете создавать

Добро пожаловать во вторую часть нашей серии о генераторах и коа. Если вы пропустили это, вы можете прочитать часть 1 здесь . Перед началом процесса разработки убедитесь, что вы установили Node.js 0.11.9 или выше.

В этой части мы создадим API словаря с использованием Koa.js, а также узнаем о маршрутизации, сжатии, ведении журнала, ограничении скорости и обработке ошибок в Koa.js. Мы также будем использовать Mongo в качестве хранилища данных и кратко узнаем об импорте данных в Mongo и о том, как легко выполнять запросы в Koa. Наконец, мы рассмотрим отладку приложений Koa.

Коа имеет радикальные изменения, встроенные в его капот, которые используют генератор ES6. Помимо изменения в потоке управления, Koa представляет свои собственные пользовательские объекты, такие как this , this.request и this.response , которые удобно выступать в качестве слоя синтаксического сахара, построенного поверх объектов req и res Node, давая вам доступ к различным удобным методам и методам получения / установки.

Помимо удобства, Коа также очищает промежуточное ПО, которое в Express полагалось на некрасивые хаки, которые часто модифицировали основные объекты. Это также обеспечивает лучшую обработку потока.

Промежуточное программное обеспечение — это подключаемая функция, которая добавляет или удаляет определенный фрагмент функциональности, выполняя некоторую работу с объектами запроса / ответа в Node.js.

Промежуточное ПО Koa — это, по сути, функция генератора, которая возвращает одну функцию генератора и принимает другую. Обычно в приложении имеется ряд промежуточного программного обеспечения, которое запускается для каждого запроса.

Кроме того, промежуточное программное обеспечение должно уступать следующему «нижестоящему» промежуточному программному обеспечению, если оно запускается «восходящим промежуточным программным обеспечением». Мы обсудим больше об этом в разделе обработки ошибок.

Еще одна вещь: чтобы добавить промежуточное программное обеспечение в приложение koa.use() , мы используем метод koa.use() и предоставляем функцию промежуточного программного обеспечения в качестве аргумента. Пример: app.use(koa-logger) добавляет koa-logger в список промежуточного программного обеспечения, которое использует наше приложение.

Для начала со словарем API нам нужен рабочий набор определений. Чтобы воссоздать этот реальный сценарий, мы решили использовать реальный набор данных. Мы взяли дамп определения из Википедии и загрузили его в Монго. Набор состоял из около 700 000 слов, так как мы импортировали только английскую дамп. Каждая запись (или документ) состоит из слова, его типа и значения. Вы можете прочитать больше о процессе импорта в файле import.txt в хранилище.

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

1
$ git clone https://github.com/bhanuc/dictapi.git

Мы можем начать с создания базового сервера Koa:

01
02
03
04
05
06
07
08
09
10
11
var koa = require(‘koa’);
var app = koa();
 
app.use(function *(next){
    this.type = ‘json’;
    this.status = 200;
    this.body = {‘Welcome’: ‘This is a level 2 Hello World Application!!’};
});
 
if (!module.parent) app.listen(3000);
console.log(‘Hello World is Running on http://localhost:3000/’);

В первой строке мы импортируем Koa и сохраняем экземпляр в переменной приложения. Затем мы добавляем одно промежуточное программное обеспечение в строке 5, которое является анонимной функцией генератора, которая принимает следующую переменную в качестве параметра. Здесь мы устанавливаем тип и код состояния ответа, который также определяется автоматически, но мы также можем установить их вручную. Затем, наконец, мы устанавливаем тело ответа.

Поскольку мы установили тело в нашем первом промежуточном программном обеспечении, это будет означать конец каждого цикла запросов, и никакое другое промежуточное программное обеспечение не будет задействовано. Наконец, мы запускаем сервер, вызывая его метод listen и передаем номер порта в качестве параметра.

Мы можем запустить сервер, запустив скрипт через:

1
2
$ npm install koa
$ node —harmony index.js

Вы можете напрямую достичь этой стадии, перейдя к фиксации 6858ae0 :

1
$ git checkout 6858ae0

Маршрутизация позволяет нам перенаправлять разные запросы к разным функциям на основе типа запроса и URL. Например, мы можем захотеть ответить /login не так, как при signup . Это можно сделать, добавив промежуточное программное обеспечение, которое вручную проверяет URL-адрес полученного запроса и запускает соответствующие функции. Или вместо написания этого промежуточного программного обеспечения вручную мы можем использовать промежуточное программное обеспечение, созданное сообществом, также известное как модуль промежуточного программного обеспечения.

Чтобы добавить возможность маршрутизации в наше приложение, мы будем использовать модуль сообщества с именем koa-router .

Чтобы использовать koa-router , мы изменим существующий код на код, показанный ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
var koa = require(‘koa’);
var app = koa();
var router = require(‘koa-router’);
var mount = require(‘koa-mount’);
 
var handler = function *(next){
    this.type = ‘json’;
    this.status = 200;
    this.body = {‘Welcome’: ‘This is a level 2 Hello World Application!!’};
};
 
var APIv1 = new router();
APIv1.get(‘/all’, handler);
 
app.use(mount(‘/v1’, APIv1.middleware()));
if (!module.parent) app.listen(3000);
console.log(‘Hello World is Running on http://localhost:3000/’);

Здесь мы импортировали два модуля, где router хранит koa-router и mount хранит модуль koa-mount , что позволяет нам использовать маршрутизатор в нашем приложении Koa.

В строке 6 мы определили нашу функцию- handler , которая является той же самой функцией, что и раньше, но здесь мы дали ей имя. В строке 12 мы сохраняем экземпляр маршрутизатора в APIv1 , а в строке 13 регистрируем наш обработчик для всех запросов GET по маршруту /all .

Таким образом, все запросы, за исключением случаев, когда запрос get отправляется на localhost:3000/all , возвращает «not found». Наконец, в строке 15 мы используем промежуточное ПО mount , которое предоставляет полезную функцию генератора, которая может быть app.use() в app.use() .

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

1
$ git checkout 8f0d4e8

Прежде чем запускать наше приложение, теперь нам нужно установить koa-router и koa-mount используя npm . Мы видим, что с ростом сложности нашего приложения количество модулей / зависимостей также увеличивается.

Чтобы отслеживать всю информацию о проекте и сделать эти данные доступными для npm , мы храним всю информацию в package.json включая все зависимости. Вы можете создать package.json вручную или с помощью интерактивного интерфейса командной строки, который открывается с помощью $ npm init   команда.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
{
    «name»: «koa-api-dictionary»,
    «version»: «0.0.1»,
    «description»: «koa-api-dictionary application»,
    «main»: «index»,
    «author»: {
    «name»: «Bhanu Pratap Chaudhary»,
    «email»: «[email protected]»
    },
    «repository»: {
    «type»: «git»,
    «url»: «https://github.com/bhanuc/dictapi.git»
    },
    «license»: «MIT»,
    «engines»: {
    «node»: «>= 0.11.13»
    }
}

Очень минимальный файл package.json выглядит как приведенный выше.

Когда package.json присутствует, вы можете сохранить зависимость, используя следующую команду:

1
$ npm install <package-name> —save

Например: в этом случае мы установим модули, используя следующую команду, чтобы сохранить зависимости в package.json .

1
$ npm install koa-router koa-mount —save

Теперь вы можете запустить приложение, используя $ node --harmony index.js .

Вы можете прочитать больше о package.json здесь .

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

Для удобства управления мы будем хранить все функции API в отдельной папке с именем api и в файле с именем api.js , а затем импортировать его в наш основной файл index.js .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
var monk = require(‘monk’);
var wrap = require(‘co-monk’);
var db = monk(‘localhost/mydb’);
var words = wrap(db.get(‘words’));
/**
* GET all the results.
*/
exports.all = function *(){
    if(this.request.query.word){
        var res = yield words.find({ word : this.request.query.word });
        this.body = res;
    } else {
        this.response.status = 404;
        }
    };
/**
* GET a single result.
*/
exports.single = function *(){
    if(this.request.query.word){
        var res = yield words.findOne({ word : this.request.query.word });
        this.body = res;
    } else {
        this.response.status = 404;
    }
};

Здесь мы используем co-monk , который действует как оболочка для monk , что позволяет нам очень легко запрашивать MongoDB с помощью генераторов в Koa. Здесь мы импортируем monk и co-monk и подключаемся к экземпляру MongoDB в строке 3. Мы вызываем wrap() для коллекций, чтобы сделать их дружественными для генератора.

Затем мы добавляем два метода генератора с именами all и single как свойство переменной export, чтобы их можно было импортировать в другие файлы. В каждой из функций сначала мы проверяем параметр запроса «слово». Если присутствует, мы запрашиваем результат, иначе мы отвечаем с ошибкой 404.

Мы используем ключевое слово yield для ожидания результатов, как обсуждалось в первой статье, которая приостанавливает выполнение до получения результата. В строке 12 мы используем метод find , который возвращает все совпадающие слова, которые сохраняются в res и впоследствии отправляются обратно. В строке 23 мы используем метод findOne доступный в коллекции, который возвращает первый соответствующий результат.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
var koa = require(‘koa’);
var app = koa();
var router = require(‘koa-router’);
var mount = require(‘koa-mount’);
var api = require(‘./api/api.js’);
 
var APIv1 = new router();
APIv1.get(‘/all’, api.all);
APIv1.get(‘/single’, api.single);
 
 
app.use(mount(‘/v1’, APIv1.middleware()));
if (!module.parent) app.listen(3000);
console.log(‘Dictapi is Running on http://localhost:3000/’);

Здесь мы импортируем экспортированные методы из api.js и назначаем обработчики для GET api.js /all /single и у нас есть полностью функциональный API и готовое приложение.

Чтобы запустить приложение, вам просто нужно установить модули monk и co-monk используя команду ниже. Также убедитесь, что у вас есть работающий экземпляр MongoDB, в который вы импортировали коллекцию, присутствующую в репозитории git, используя инструкции, указанные в import.txtweird .

1
$ npm install monk co-monk —save

Теперь вы можете запустить приложение, используя следующую команду:

1
$ node —harmony index.js

Вы можете открыть браузер и открыть следующие URL-адреса, чтобы проверить работу приложения. Просто замените «новое» словом, которое вы хотите запросить.

  • http://localhost:3000/v1/all?word=new
  • http://localhost:3000/v1/single?word=new

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

1
$ git checkout f1076eb

Используя каскадное промежуточное ПО, мы можем отлавливать ошибки, используя механизм try/catch , так как каждое промежуточное ПО может реагировать, одновременно уступая как нисходящему, так и восходящему. Таким образом, если мы добавим промежуточное программное обеспечение Try and Catch в начале приложения, оно будет перехватывать все ошибки, с которыми столкнулся запрос в остальном промежуточном программном обеспечении, поскольку оно будет последним промежуточным программным обеспечением во время апстриминга. Добавление следующего кода в строку 10 или ранее в index.js должно работать.

01
02
03
04
05
06
07
08
09
10
11
app.use(function *(next){
try{
    yield next;
} catch (err) { //executed only when an error occurs & no other middleware responds to the request
this.type = ‘json’;
this.status = err.status ||
this.body = { ‘error’ : ‘The application just went bonkers, hopefully NSA has all the logs 😉 ‘};
//delegate the error back to application
this.app.emit(‘error’, err, this);
    }
});

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

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

Довольно просто добавить логирование и ограничение скорости в наше приложение Koa. Мы будем использовать два модуля сообщества: koa-logger и koa-better-rate-limiting . Нам нужно добавить следующий код в наше приложение:

1
2
3
4
5
6
var logger = require(‘koa-logger’);
var limit = require(‘koa-better-ratelimit’);
//Add the lines below just under error middleware.
app.use(limit({ duration: 1000*60*3 , // 3 min
                max: 10, blacklist: []}));
app.use(logger());

Здесь мы импортировали два модуля и добавили их в качестве промежуточного программного обеспечения. Регистратор будет регистрировать каждый запрос и печатать в стандартный stdout процесса, который может быть легко сохранен в файле. А ограничение промежуточного программного обеспечения ограничивает количество запросов, которые данный пользователь может запросить за определенный период времени (здесь это максимум десять запросов за три минуты). Также вы можете добавить массив IP-адресов, которые будут внесены в черный список, и их запрос не будет обработан.

Не забудьте установить модули перед использованием кода, используя:

1
$ npm install koa-logger koa-better-ratelimit —save

Один из способов обеспечить более быструю доставку — это получить ответ, что довольно просто в Koa. Чтобы сжимать ваш трафик в Koa, вы можете использовать модуль koa-compress .

Здесь параметры могут быть пустым объектом или могут быть настроены в соответствии с требованиями.

1
2
3
4
5
6
7
8
var compress = require(‘koa-compress’);
var opts = {
    filter: function (content_type) { return /text/i.test(content_type) }, // filter requests to be compressed using regex
    threshold: 2048, //minimum size to compress
    flush: require(‘zlib’).Z_SYNC_FLUSH };
            }
//use the code below to add the middleware to the application
app.use(compress(opts));

Вы даже можете отключить сжатие в запросе, добавив следующий код в промежуточное ПО:

1
this.compress = true;

Не забудьте установить компресс с помощью npm .

1
$ npm install compress —save

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

1
git checkout 8f5b5a6

Тестирование должно быть неотъемлемой частью всего кода, и следует стремиться к максимальному охвату тестирования В этой статье мы будем писать тесты для маршрутов, доступных из нашего приложения. Мы будем использовать supertest и Mocha для создания наших тестов.

Мы будем хранить наш тест в test.js в папке api . В обоих тестах мы сначала опишем наш тест, дав ему более удобочитаемое имя. После этого мы передадим анонимную функцию, которая описывает правильное поведение теста, и примет обратный вызов, который содержит реальный тест. В каждом тесте мы импортируем наше приложение, запускаем сервер, описываем тип запроса, URL и запрос, а затем устанавливаем кодировку в gzip. Наконец, мы проверяем ответ, если он правильный.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
var request = require(‘supertest’);
var api = require(‘../index.js’);
 
describe(‘GET all’, function(){
  it(‘should respond with all the words’, function(done){
    var app = api;
    request(app.listen())
    .get(‘/v1/all’)
    .query({ word: ‘new’ })
    .set(‘Accept-Encoding’, ‘gzip’)
    .expect(‘Content-Type’, /json/)
    .expect(200)
    .end(done);
  })
})
 
describe(‘GET /v1/single’, function(){
  it(‘should respond with a single result’, function(done){
    var app = api;
 
    request(app.listen())
    .get(‘/v1/single’)
    .query({ word: ‘new’ })
    .set(‘Accept-Encoding’, ‘gzip’)
    .expect(200)
    .expect(‘Content-Type’, /json/)
    .end(function(err, res){
    if (err) throw err;
    else {
        if (!(‘_id’ in res.body)) return «missing id»;
        if (!(‘word’ in res.body)) throw new Error(«missing word»);
        done();
    }
  });
  })
})

Для запуска нашего теста мы Makefile :

1
2
3
4
5
6
7
8
9
test:
    @NODE_ENV=test ./node_modules/.bin/mocha \
        —require should \
        —reporter nyan \
        —harmony \
        —bail \
        api/test.js
 
.PHONY: test

Здесь мы настроили репортер (nyan cat) и среду тестирования (mocha). Обратите внимание, что импорт должен добавить --harmony чтобы включить режим ES6. Наконец, мы также указываем расположение всех тестов. Makefile может быть настроен для бесконечного тестирования вашего приложения.

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

1
$ make test

Просто не забудьте установить модули тестирования (mocha, should, supertest) перед тестированием, используя команду ниже:

1
$ npm install mocha should mocha —save-dev

Для запуска наших приложений в производство мы будем использовать PM2, который является полезным монитором процессов Node. Мы должны отключить приложение logger во время работы; это может быть автоматизировано с помощью переменных среды.

Чтобы установить PM2, введите в терминале следующую команду

1
$ npm install pm2 -g

И наше приложение можно запустить с помощью следующей команды:

1
$ pm2 start index.js —node-args=»—harmony»

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

Koa — это легкое и выразительное промежуточное программное обеспечение для Node.js, которое делает процесс написания веб-приложений и API более приятным.

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

Пожалуйста, не стесняйтесь оставлять любые комментарии, вопросы или другую информацию в поле ниже.