Примеры быстрой разработки api на масштабируемом...
DESCRIPTION
Примеры кода приложений и конфигурации сервера с доступом к файлам, памяти, базам данных и параллельной асинхронной обработкой различных типов API запросов с состоянием и без состояния.TRANSCRIPT
Примеры быстрой разработки API на масштабируемом сервере приложений Impress для Node.js
Тимур ШемсеминовНИИ Системных Технологий, MetaSystems Inc., ITAdapter Inc.
mailto:[email protected] 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& 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:[email protected]://habrahabr.ru/users/marcusaurelius/https://www.npmjs.org/package/impresshttps://github.com/tshemsedinov/impress