Примеры быстрой разработки API на масштабируемом...

Post on 26-Dec-2014

500 views 2 download

description

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

Transcript of Примеры быстрой разработки API на масштабируемом...

Примеры быстрой разработки API на масштабируемом сервере приложений Impress для Node.js

Тимур ШемсеминовНИИ Системных Технологий, MetaSystems Inc., ITAdapter Inc.

mailto:timur.shemsedinov@gmail.com https://github.com/tshemsedinov/impresshttp://habrahabr.ru/users/marcusaurelius/ https://www.npmjs.org/package/impress

Сервер приложений Impress

• Масштабирование, прозрачное для приложений• Изоляция приложений (память, конфигурация, БД)• Модификация кода без перезапуска• Быстрая отдача статики из памяти (gzip, js min.)• URL-rewriting регулярными выражениями, с внутренним

перенаправлением или внешним HTTP-вызовам• Виртуальные хосты с поддержкой *.domain.com• Кэширование кода, статики и шаблонов в памяти• Возможность запускать на одном tcp порту несколько

приложений и одно приложение на нескольких портах• Server-Sent Events и WebSocket на одном порту• Возможность иметь API без состояния (REST) и с состоянием

(RPC), прилипание по IP и Cookie• Множество других вещей: встроенный механизм сессий,

логирование, IPC и ZeroMQ, драйвера доступа к БД и т.д.

Примеры быстрой разработки API

Общие принципы:• Маршрутизация запросов на основе файловой системы• Каждый обработчик в отдельном файле• Не требуется готовить среду исполнения запроса в обработ-

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

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

• Можно ответвлять долгие обработчики (workers) в отдельные процессы

• Осуществляется глобальное (кросс-серверное) межпроцессовое взаимодействие при помощи трансляции событий

1. Простейший пример обработчика JSON

/example/app/examples/simple/jsonPost.json/post.jsmodule.exports = function(client, callback) { client.context.data = { a: 1 }; callback();}---------------------------------------------------------------HTTP POST /example/app/examples/simple/jsonPost.json{ "a": 1}

2. Простейший пример обработчика AJAX

/examples/simple/ajaxTest.ajax/get.jsmodule.exports = function(client, callback) { client.context.data = { parameterName: client.query.parameterName, }; callback();}

---------------------------------------------------------------/examples/simple/ajaxTest.ajax/html.templateAJAX Request with parameter returning back in template<br>parameterName: @parameterName@

---------------------------------------------------------------HTTP GET/examples/simple/ajaxTest.ajax?parameterName=parameterValueAJAX Request with parameter returning back in template<br>parameterName: parameterValue

3. Пример вызова обработчика

/js/init.js$.post('/examples/simple/jsonPost.json', { parameterName: "paramaterValue" }, function(res) { console.log(res.valueLength); });---------------------------------------------------------------HTTP POST /example/app/examples/simple/jsonPost.json{ "status": 1, "parameterValue": "paramaterValue", "valueLength": 14, "requestCounter": 3}---------------------------------------------------------------Console:14

4. Пример доступа к файловой системе

/examples/simple/fsAccess.json/get.jsmodule.exports = function(client, callback) { var filePath = client.hostDir+client.path+'/test.txt'; fs.readFile(filePath, 'utf8', function(error, data) { client.context.data = { fileContent: data, dataLength: data.length }; callback(); });}---------------------------------------------------------------HTTP GET /examples/simple/fsAccess.json{ "fileContent": "?Example text file", "dataLength": 18}

5. Пример HTTP-запроса из обработчика

/examples/simple/httpRequest.json/get.jsmodule.exports = function(client, callback) { var req = impress.http.request({ hostname: 'google.com', port: 80, path: '/', method: 'get' }, function(response) { var data = ''; response.on('data', function(chunk) {data=data+chunk;}); response.on('end', function() { client.context.data = data; callback(); }); } ); req.on('error', function(e) { client.context.data = "Can't get page"; callback(); }); req.end();}

6. Пример доступа к MongoDB (чтение)

/examples/mongodb/getData.json/get.jsmodule.exports = function(client, callback) { dbAlias.testCollection.find({}).toArray( function(err, nodes) { client.context.data = nodes; callback(); } );}

---------------------------------------------------------------HTTP GET mongodb/getData.json[ { "_id": "53547375894c3d3022000001" }]

7. Примеры доступа к MongoDB

/examples/mongodb/insertData.json/get.jsmodule.exports = function(client, callback) { dbAlias.testCollection.insert(client.query, function(err) { client.context.data = !err; callback(); });}---------------------------------------------------------------/examples/mongodb/getCollections.json/get.jsmodule.exports = function(client, callback) { dbImpress.connection.collections(function(err, collections) { var items = []; for (var i = 0; i < collections.length; i++) { items.push(collections[i].collectionName); } client.context.data = items; callback(); });}

8. Пример с SQL-запросом (mysql)

/examples/mysql/getCities.json/get.jsmodule.exports = function(client, callback) { dbAlias.query( 'select * from City', function(err, rows, fields) { client.context.data = { rows:rows, fields:fields }; callback(); } );}

9. Пример с разными типами ресурсов

/examples/complex/getFsMongoRequest.json/get.jsmodule.exports = function(client, callback) { impress.async.parallel({ file: function(callback) { var filePath = client.hostDir+client.path+'/test.txt'; fs.readFile(filePath, 'utf8', function(error, data) { callback(null, data); }); }, request: function(callback) { var req = impress.http.request({ hostname: 'google.com', port: 80, path: '/', method: 'get' }, function(response) { var data = ''; response.on('data', function(chunk) { data = data+chunk; }); response.on('end', function() { callback(null, data); }); } ); req.on('error', function(e) { callback(null, "Can't get page"); }); req.end(); },...

...продолжение примера

/examples/complex/getFsMongoRequest.json/get.js... mongo: function(callback) { dbAlias.testCollection.find({}).toArray(function(err, nodes) { callback(null, nodes); }); } }, function(err, results) { client.context.data = results; callback(); });}---------------------------------------------------------------------------------{ "mongo": [ { "_id": "53547375894c3d3022000001" } ], "file": "?Example text file", "request": "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\n<TITLE>302 Moved</TITLE></HEAD><BODY>\n <H1>302 Moved</H1>\nThe document has moved\n <A HREF=\"http://www.google.com.ua/?gws_rd=cr&amp; ei=OWVWU5nHOqOc4wTbjYDgBw\">here</A>.\r\n</BODY></HTML>\r\n"}

10. Пример обработчика с состоянием

/examples/memory/stateful.json/get.jsmodule.exports = function(client, callback) { application.stateTest = application.stateTest || { counter: 0, addresses: [] };

application.stateTest.counter++; application.stateTest.addresses.push( client.req.connection.remoteAddress );

client.context.data = application.stateTest; callback();}

11. Пример обработчика SSE

/examples/events/connect.sse/get.jsmodule.exports = function(client, callback) { client.sse.channel = 'TestEventStream'; callback();}

---------------------------------------------------------------/js/init.jsvar sse = new EventSource("/examples/events/connect.sse");

sse.addEventListener("TestEventStream", function(e) { console.dir({ event: e.event, data: e.data });});

12. Пример обработчика WebSocket

/examples/events/connect.ws/get.jsmodule.exports = function(client, callback) { var connection = client.res.websocket.accept(); connection.send('Hello world'); connection.on('message', function(message) { connection.send('I am here'); }); connection.on('close', function(reasonCode, description) { console.log('disconnected'); }); callback();}---------------------------------------------------------------/js/init.jsws = new WebSocket("ws://127.0.0.1:80/examples/events/connect.ws");ws.onopen = function() {};ws.onclose = function() {};ws.onmessage = function(evt) { console.log("Message from server: "+evt.data);}

Интроспекция API и файлов

Шаблоны развертывания

• Рекомендации по установке• Конфигурация сервера приложений /config/*.js

• Стратегии запуска (single, multiple, specialization, sticky)• Параметры многопоточности: cluster.js• Порты сетевые интерфейсы и порты: servers.js• Параметры песочников, плагины и зоны видимости• Параметры контроллера прикладного облака: cloud.js• Параметры логирования: log.js

• Конфигурация каждого приложения /applications/name/config/*.js• Подключения к базам данных: databases.js• Виртуалхосты: hosts.js• URL-реврайтинг и обтатный proxy: routes.js• Параметры сессий: sessions.js• Параметры отдачи статики и кеша: files.js• Любые собственные файлы конфигурации приложения

Установка node.js и impress

CentOS 6.5 (64bit) minimalcurl http://.../impress/install.sh | sh---------------------------------------------------------------#!/bin/bashyum -y updateyum -y install wgetyum -y groupinstall "Development Tools"cd /usr/srcwget http://nodejs.org/dist/v0.10.26/node-v0.10.26.tar.gztar zxf node-v0.10.26.tar.gzcd node-v0.10.26./configuremakemake installln -s /usr/local/bin/node /binln -s /usr/local/bin/npm /binmkdir /impresscd /impressnpm install impress

Управление сервисом (демоном)

Если установить в каталог /impress

Исполнится /impress/bin/install.sh• Устанавливается как сервис (демон)• Запускается при старте системы• Перезапускает свои потоки обработки при падениях

/impress/bin/uninstall.sh• Останавливает сервер• Удаляет сервис из системы• Удаляет из автоматического старта

После установи можно пользоватьсяservice impress startservice impress stopservice impress restartservice impress updateservice impress status

Конфигурация сервера приложений

/config/cloud.jsmodule.exports = { name: "PrivateCloud", type: "standalone", controller: "127.0.0.1", pubSubPort: "3000", reqResPort: "3001", health: "2s"}---------------------------------------------------------------/config/cluster.jsmodule.exports = { check: "http://127.0.0.2/", name: "C1", cookie: "node", strategy: "multiple", // single, specialization, sticky workers: os.cpus().length, gcInterval: 0}

Конфигурация сетевых интерфейсов

/config/servers.jsmodule.exports = { www: { protocol: "http", address: "127.0.0.1", port: 80, applications: ["example", "host2"], nagle: true, slowTime: "1s" }, ssl: { protocol: "https", address: "127.0.0.1", port: 443, key: "example.key", cert: "example.cer" }}

Конфигурация плагинов

/config/plugins.jsmodule.exports = [ "db", "db.schema", "db.mongodb", "db.memcached", "db.mysql", "db.mysql.schema", "impress.log", "impress.security", "impress.security.mongodb", "impress.mail", "impress.uglify", "impress.health", "impress.cloud", "impress.geoip", "impress.websocket", "impress.sse"]

Конфигурация логов и песочницы

/config/log.jsmodule.exports = { keepDays: 10, writeInterval: "3s", writeBuffer: 64*1024, fileTypes: [ "access", "error", "debug", "slow" ]}---------------------------------------------------------------/config/sandbox.js module.exports = { modules: [ 'global', 'console', 'process', 'impress', 'db', 'domain', 'crypto', 'geoip', 'os', 'Buffer', 'stream', 'nodemailer', 'net', 'http', 'https', 'dgram', 'dns', 'url', 'path', 'fs', 'util', 'events', 'iconv', 'querystring', 'zlib', 'async']}

Конфигурация хостов и URL-rewriting

/applications/applicationName/config/hosts.jsmodule.exports = [ "127.0.0.1", "mydomain.com", "*.domainname.com",]---------------------------------------------------------------/applications/applicationName/config/routes.jsmodule.exports = [ { url: "/api/(one|two)/(.*)", rewrite: "/example/[1].json?par1=[2]" }, { url: "/api/(name1|name2|name3)/(.*)", rewrite: "/api/[1]/[2]", host: "example.com", port: 80, slowTime: "1s" }]

Конфигурация подключений к БД

/applications/applicationName/config/databases.jsmodule.exports = { mongoTest: { url: "mongodb://hostName:27017/databaseName", slowTime: "2s", collections: ["collection1", "collection2"], security: true, alias: "alias1" }, system: { url: "mysql://user:password@localhost/dbName", slowTime: 1000, alias: "aliasName" }}

Конфигурация сессий и статики

/applications/applicationName/config/sessions.jsmodule.exports = { anonymous: true, cookie: "SID", characters: "ABCDEFGH...fghijkl...456789", length: 64, persist: true, database: "impress"}---------------------------------------------------------------/applications/applicationName/config/files.jsmodule.exports = { minify: false, static: [ "*/css/*", "*/images/*", "*/js/*", "*/favicon.ico", "*/favicon.png" ]}

Примеры и демо-приложение

Спасибо за внимание!Прошу задавать вопросы

Тимур ШемсеминовНИИ Системных Технологий, MetaSystems Inc., ITAdapter Inc.

mailto:timur.shemsedinov@gmail.comhttp://habrahabr.ru/users/marcusaurelius/https://www.npmjs.org/package/impresshttps://github.com/tshemsedinov/impress