Разработка клиент-серверной инфраструктуры на javascript (часть 2 — сервер и размещение)

от автора

imageЗдравствуйте, в этой статье я расскажу о серверной части и опишу процесс размещения приложения на облачном хостинге openshift.

Разработка клиент-серверной инфраструктуры на javascript (часть 1 — клиент)

Сервер написан на nodejs с использованием swagger-node-express. Это дает преимущество автодокументации. Хочу отметить, что сервер создавался, скорее, в поддержку клиентской части, поэтому некоторые проверки и оптимизиции были намеренно опущены и оставлены на будущее.

Сервер состоит из набора модулей. В главном файле все модули инициализируются.

var express = require("express"),  swagger = require("./swagger"),  orm = require('./orm'),  auth = require('./auth'),  config = require('./config'),  static_files = require('./static');  var app = express();  app.use(express.bodyParser());  auth.init(app); orm.init(app); swagger(app); static_files(app);  app.listen(config.listen, config.ipaddr); 

Основные модули. Для автентификации используется http заголовок, его имя можно задавать в параметрах. Сесия хранится в memcached, это просто соответствие api_key -> user_id. Код проверки пользователя.

var client = new memcache.Client(cfg.memcache.port, cfg.memcache.host); ... app.use(function(req, res, next){ 	client.get( 		req.headers[cfg.header.toLowerCase()], 		function(error, result){ 			if(result){ 				req.user = result; 			} 			next(); 		} 	);  	req.memcache = client; }); 

Для работы с базой данных используется node-orm2. Отмечу, что в package.json добавляйте только тот драйвер базы данных, который будете использовать.
Соединение с базой и пример описания модели.

app.use(orm.express(config.db, {     define: function (db, models) {          db.define("users",           {             id       : Number,             email    : String,             password : String,             twitter  : Number,             facebook : Number,             google   : Number,             linkedin : String         },         {             validations: {               email: [                 orm.enforce.unique("Email already taken!"),                 orm.enforce.unique({ ignoreCase: true }),                   orm.enforce.notEmptyString()                 ],                 password: orm.enforce.notEmptyString()             },           id: "id",           autoFetch: false         }       );       var Conferences = db.define("conferences",         {             id          : Number,             title       : String,             description : String,             datetime    : Date,             place       : String,             location    : String,             site        : String,             logo        : String,             facebook    : String,             twitter     : String,             telephone   : String,             cost        : String,             file        : String         },{           id: "id",           autoFetch: false         }       );       var Decisions = db.define("decisions",         {           id            : Number,           decision      : ['go', 'not go', 'favorite'],           user          : Number,           conference_id : Number         },{           id: "id",           cache: false,           autoFetch: false         }       );        Decisions.hasOne('conference', Conferences, {reverse: 'decision'});     } })); 

Локально и на моем VPS для статики используется nginx, но в случае PaaS сам nodejs статику и отдает, поэтому для нее я создал отдельный обработчик. Нужно отдавать документацию и сам клиент.

var static_handler = express.static(__dirname + '/../static/'); app.get(/^\/static(\/.*)?$/, function(req, res, next) { 	if (req.url === '/static') { // express static barfs on root url w/o trailing slash 		res.writeHead(302, { 'Location' : req.url + '/' }); 		res.end();                 return; 	}  	req.url = req.url.substr('/static'.length); 	return static_handler(req, res, next); });  var main_handler = express.static(__dirname + '/../client/www/'); app.get(/^\/(.*)$/, function(req, res, next) { 	if(req.url == '/cordova.js'){ 		return res.send(''); 	}  	if(!req.url){ 		req.url = 'index.html'; 	} 	return main_handler(req, res, next); }); 

Файл cordova.js создается phonegap`ом, содержит функции общения с железом и прочие, платформозависимые фичи. В браузер просто отдается заглушка, таким образом клиент знает, что функции ОС он использовать не может.
Теперь нужно инициализировать сам сервер.

app.use(function(req, res, next) {     res.header('Access-Control-Allow-Origin', '*');     res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');     res.header('Access-Control-Allow-Headers',  cfg.header+', Content-Type');     res.header('Access-Control-Expose-Headers', cfg.header+', Content-Type');      if (req.method == 'OPTIONS') {         res.send(200);     }     else {         next();     } });  swagger.setAppHandler(app); swagger.addModels(models); controllers.init(swagger); swagger.configure(cfg.basePath, "0.1"); // Serve up swagger ui at /docs via static route var docs_handler = express.static(__dirname + '/../documentation/swagger/'); app.get(/^\/docs(\/.*)?$/, function(req, res, next) { 	if (req.url === '/docs') { // express static barfs on root url w/o trailing slash 		res.writeHead(302, { 'Location' : req.url + '/' }); 		res.end(); 		return; 	} 	// take off leading /docs so that connect locates file correctly 	req.url = req.url.substr('/docs'.length); 	return docs_handler(req, res, next); }); 

Чтобы из браузера можно было свободно пользоватся API отдаются cross-origin resource sharing headers. Потом инициализация свагера. Тут нужно обратить внимание на

swagger.addModels(models); controllers.init(swagger); 

Эти модели, что додаются, это, можно сказать, аналог ViewModel и нинкак не относятся к моделям, описываемых в orm.js. В controllers.js прописываются обработчики action’ов. Пример.

swagger.addGET(conferences.get); swagger.addGET(conferences.list); swagger.addPOST(conferences.decision); swagger.addDELETE(conferences.reject); 

Каким образом описывается action. Распишу пример обработчика получения конференции по id.

var get = {   'spec': {     "description" : "Get conference by id",     "path" : "/conferences.{format}/{id}",     "notes" : "Get conference",     "summary" : "Get conference",     "method": "GET",     "responseClass" : "Conference",     "nickname" : "conference"   },   'action': function (req,res) {      if (!req.params.id) {         throw swagger.errors.invalid('id'); }       var id = parseInt(req.params.id);      req.db.models.conferences.get(id, function(err, conference){       if(err){         res.send(500, JSON.stringify({code: 500, header: 'Internal Server Error', message: JSON.stringify(err)}));       }else{         if(conference){           if(conference.file){             conference.file = '/static/' + conference.file;           }           if(req.user){             conference.getDecision({user: req.user}, function(err, decision){               if(err){                 res.send(500, JSON.stringify(err));               }else{                 conference.decision = decision.pop();                 res.send(200, JSON.stringify(conference));               }             });           }else{             res.send(200, JSON.stringify(conference));           }         }else{           throw swagger.errors.notFound('conference');         }       }     });   } }; 

В «spec» в основном информация для документации, но тут находится «path», тоесть url, по которому express будет вызывать обработчик. Основная часть это — action. Проверяется наличие параметров и запрос в базу данных. Кто не любит, когда колбеки разрастаются вширь, скажу сразу, что node-orm2 позволяет строить цепочки как запроса так и обработчиков. Если к конференции прикреплены файлы, то для них генерируется путь. Если пользователь залогинен ищется его решение относительно конференции. Это делается для экономии http запросов, но скажу чесно, что не знаю, как лучше и правильнее с точки зрение REST: прикреплять has-one модель к родительской или возвращать id, а клиент уже пусть сам шлет еще один запрос, если ему нужно.
На этом, думаю, описание работы с swagger-node-express закончено. Как только проект начал обретать готовый вид я задумался, куда его разместить. Сначала он жил на моем VPS, но я там постоянно все ламаю, значит было решено выложить его на PaaS. Потому что облака это модно, а еще весело. Кстати, многие облачные хостинги предлагают бесплатные аккаунты, причем многие из них без ограничений во времени. Таким образом можно свободно хостить с десяток проектов.
Хотя я и описываю в этой статье openshift, но сам я к ним никак не отношусь и процедуры размещения на других хостингах будут очень сильно похожи(все сводится к git push). Выбрал я именно этот хостинг совершенно случайно.
Итак, сначала нужно зарегистрироватся, если вы этого еще не сделали. Потом создать проект, в их документации это подробно описано. Особенно я хочу обратить ваше внимание на некоторые вещи. Во-первых, если вы думаете хостить проект в облаке, то лучше сразу выберите где именно и заточите проект под именно этот хостинг. Таким образом вы избавитесь от переделывания проекта перед деплоем, например мне нужно было переименовать главный файл в server.js, додать поддержку статики клиентского приложения и использовать внешний memcached сервер.
Я переместил client/www в server/www и туда же добавил каталог со статикой, прикрепленной к конференциям, и свагер. Вот как выглядит новый static.js.

var static_handler = express.static(__dirname + '/www/static/'); app.get(/^\/static(\/.*)?$/, function(req, res, next) {   if (req.url === '/static') { // express static barfs on root url w/o trailing slash     res.writeHead(302, { 'Location' : req.url + '/' });     res.end();     return;   }   // take off leading /docs so that connect locates file correctly   req.url = req.url.substr('/docs'.length);   return static_handler(req, res, next); });  var main_handler = express.static(__dirname + '/www/'); app.get(/^\/(.*)$/, function(req, res, next) {    if(req.url == '/cordova.js'){     return res.send('');   }    if(!req.url){     req.url = 'index.html';   }   return main_handler(req, res, next); }); 

Во-вторых, поинтересуйтесь насчет SQL/no-SQL баз, потому что в некоторых базы отдельно, а в некоторых, openshift например, занимают целый слот, а их и так мало. Memcached я отдал другому сервису garantiadata.com, где тоже создал триальный аккаунт. У меня осталось 2 слота, кстати, вчера второй экзепляр ноды тоже запускался, что меня порадовало, так как я не был уверен что автомасштабирование пройдет успешно.
И да, сам деплой.

git add --all; git commit -m 'commit'; git push origin master 

Не забудьте перед этим добавить origin, который можно получить на странице приложения в openshift. Так же укажите вашему приложению правильный хост/порт для прослужшки.

exports.listen = process.env.OPENSHIFT_NODEJS_PORT || 8080; exports.ipaddr = process.env.OPENSHIFT_NODEJS_IP || "127.0.0.1"; 

Состояние можно мониторить, при git push вы получаете лог. А также можно зайти по ssh и посмотреть tail_all.
Через ssh также можно подключится к БД, предварительно посмотев строку подключения.

echo $OPENSHIFT_POSTGRESQL_DB_URL 

Вот и все, проект в облаке, все щасливы. Как всегда, рад критике и предложениям.

ссылка на оригинал статьи http://habrahabr.ru/post/200356/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *