Permalink
Browse files

README.md

all dependencies to package.json
i-ajax-proxy and i-api-request blocks
  • Loading branch information...
1 parent c77ef62 commit da4b3d766fef66986855173b678b24b29fdbc339 @wtfil wtfil committed Mar 24, 2013
View
@@ -0,0 +1,8 @@
+Tiny framefork for single-page applycations with NodeJS and BEM
+
+Getting Started
+---------------
+
+You should have NodeJS >= 0.8 and Node Package Manager (npm) installed
+
+Checkout this test project https://github.com/wtfil/bem-node-test
@@ -0,0 +1,5 @@
+({
+ mustDeps: [
+ {block: 'i-router'}
+ ]
+})
@@ -0,0 +1,63 @@
+/**
+ * Proxy ajax calls on i-blocks
+ */
+
+BEM.blocks['i-router'].define('GET,POST', /^\/ajax\/(i[\w\-]+)\/([^_][\w]+)/, 'i-ajax-proxy');
+
+BEM.decl('i-ajax-proxy', {}, {
+
+ _blockList: [],
+
+ /**
+ * Allow to proxy blocks
+ *
+ * @param {String} blockName
+ */
+ allowBlock: function (blockName) {
+ if (blockName.match(/^i/)) {
+ this._blockList.push(blockName);
+ } else {
+ throw new Error('Only i-blocks allowed to proxy');
+ }
+ },
+
+ /**
+ * Response with json
+ *
+ * @param {Array} matchers
+ */
+ init: function (matchers) {
+ var blockName = matchers[1],
+ methodName = matchers[2],
+ data = BEM.blocks['i-router'].get('params');
+ if (
+ this._blockList.indexOf(blockName) !== -1 &&
+ BEM.blocks[blockName] &&
+ typeof BEM.blocks[blockName][methodName] === 'function' &&
+ data &&
+ data.resource
+ ) {
+ try {
+ data.params = data.params ? JSON.parse(decodeURIComponent(data.params)) : {};
+ } catch (err) {
+ console.error(err);
+ data.params = {};
+ }
+ //do not parse json
+ data.output = 'string';
+ return BEM.blocks[blockName][methodName](
+ data.resource,
+ data
+ ).then(function (json) {
+ BEM.blocks['i-response'].json(json);
+ }).fail(function (err) {
+ console.error(err);
+ BEM.blocks['i-response'].error(err);
+ });
+ } else {
+ BEM.blocks['i-response'].missing();
+ return this.__base.apply(this, arguments);
+ }
+ }
+
+});
@@ -0,0 +1,65 @@
+/**
+ * Request json api
+ */
+BEM.decl('i-api-request', null, {
+
+ /**
+ * Http error constructor
+ *
+ * @param {Number} status
+ * @param {String} message
+ * @param {String} responseBody
+ */
+ _HttpError: function (status, message, responseBody) {
+ this.name = 'E_HTTP_ERROR';
+ this.status = status;
+ this.message = message;
+ if (responseBody) {
+ this.message += ' ' + String(responseBody).replace(/\n/g, '\\n');
+ }
+ },
+
+ /**
+ * Trim slashes from resource
+ */
+ _normalizeResource: function (resource) {
+ return String(resource).replace(/^\/|\/$/, '');
+ },
+
+ /**
+ * Pass parsed json to promise resolve
+ * @param {Vow.promise}
+ * @param {Sting} result
+ * @param {String} [format]
+ */
+ _parse: function (promise, result, format) {
+ if (format === 'string') {
+ return promise.fulfill(result);
+ }
+ try {
+ promise.fulfill(JSON.parse(result));
+ } catch (err) {
+ promise.reject(err);
+ }
+
+ },
+
+ /**
+ * Check if error is Http error
+ *
+ * @param {Error} error
+ * @return {Boolean}
+ */
+ isHttpError: function (error) {
+ return error instanceof this._HttpError;
+ },
+
+ /**
+ * Http get
+ */
+ get: function (resource, data) {
+ return this._request('get', resource, data);
+ }
+
+});
+
@@ -0,0 +1,6 @@
+({
+ mustDeps: [
+ {block: 'i-ajax-proxy'},
+ {block: 'i-promise'}
+ ]
+})
@@ -0,0 +1,56 @@
+/**
+ * Request json api
+ */
+
+BEM.decl('i-api-request', null, {
+
+ /**
+ * Aborts all active requests
+ */
+ abort: function () {
+ if (this._activeXhrs) {
+ while (this._activeXhrs.length) {
+ this._activeXhrs.pop().abort();
+ }
+ }
+ },
+
+ /**
+ * Http request rest api
+ *
+ * @param {String} method Http method
+ * @param {String} resource
+ * @param {Object} data
+ * @param {Object} [data.params] Get params
+ * @return {Vow.Promise}
+ */
+ _request: function (method, resource, data) {
+ var promise = Vow.promise(),
+ _this = this;
+ data = data || {};
+ data.resource = this._normalizeResource(resource);
+ if (data.params) {
+ data.params = JSON.stringify(data.params);
+ }
+ this._activeXhrs = this._activeXhrs || [];
+
+ this._activeXhrs.push(jQuery.ajax({
+ type: data.body ? 'POST' : 'GET',
+ url: '/ajax/' + this._name + '/' + method,
+ data: data,
+ complete: function (xhr) {
+ if (xhr.status === 200) {
+ _this._parse(promise, xhr.responseText, data.output);
+ } else {
+ promise.reject(new _this._HttpError(xhr.status, xhr.statusText, xhr.responseText));
+ }
+ _this._activeXhrs = _this._activeXhrs.filter(function (xhrItem) {
+ return xhr !== xhrItem;
+ });
+ }
+ }));
+
+ return promise;
+ }
+
+});
@@ -0,0 +1,160 @@
+/**
+ * Request rest api
+ */
+(function () {
+ var request = require('request'), //@see https://github.com/mikeal/request/
+ url = require('url'),
+ dns = require('dns'),
+ apiResolveCache = {},
+ querystring = require('querystring'),
+ zlib = require('zlib');
+
+ require('http').globalAgent.maxSockets = 20;
+
+ BEM.decl('i-api-request', null, {
+
+ _apiHostConfigKey: 'api',
+
+ /**
+ * Define request headers
+ */
+ _getRequestHeaders: function (hostname) {
+ return {
+ 'Accept': 'application/json',
+ 'Content-type': 'application/json',
+ 'Accept-Encoding': 'gzip, deflate',
+ 'host': hostname, //bug with Host when capitalized in https://github.com/mikeal/request/
+ 'Connection': 'keep-alve'
+ };
+ },
+
+ // TODO setConfig
+ _config: {},
+
+ /**
+ * Resolve ip and set defaults for requests
+ */
+ _resolveApiParams: function (parse) {
+ var promise = Vow.promise(),
+ _this = this,
+ host = parse.hostname,
+ apiHost;
+
+ if (apiResolveCache[host]) {
+ promise.fulfill(apiResolveCache[host]);
+ } else {
+ dns.resolve(host, BEM.blocks['i-state'].bind(function (err, ipArr) {
+ var apiParams;
+ if (err) {
+ promise.reject(err);
+ } else {
+ apiParams = {
+ headers: _this._getRequestHeaders(host),
+ host: host,
+ timeout: _this._config.timeout || 5000,
+ uri: url.format({
+ protocol: parse.protocol,
+ hostname: ipArr[0],
+ port: parse.port || 80,
+ pathname: parse.path
+ })
+ };
+ apiResolveCache[host] = apiParams;
+ promise.fulfill(apiParams);
+ }
+ }));
+ }
+ return promise;
+ },
+
+ /**
+ * Http request rest api
+ *
+ * @param {String} method Http method
+ * @param {String} resource
+ * @param {Object} data
+ * @param {Object} [data.params] Get params
+ * @param {Object} [data.output=object] Output format
+ * @return {Vow.Promise}
+ */
+ _request: function (method, resource, data) {
+ var parse = url.parse(resource);
+
+ return this._resolveApiParams(parse).then(function (apiParams) {
+ return this._requestApi(apiParams, method, resource, data);
+ }.bind(this));
+ },
+
+ /**
+ * Decode gziped body
+ */
+ _decodeBody: function (res, body, callback) {
+ if (body && res.headers['content-encoding'] === 'gzip') {
+ zlib.gunzip(body, function (err, decodedBody) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, decodedBody.toString());
+ }
+ });
+ } else if (body) {
+ callback(null, body.toString());
+ } else {
+ callback(null, '');
+ }
+ },
+
+ _getUri: function (uri, query) {
+ return uri + (query ? ((uri.indexOf('?') !== -1 ? '&' : '?') + querystring.stringify(query)) : '');
+ },
+
+ /**
+ * Request rest api with predefined api params
+ */
+ _requestApi: function (apiParams, method, resource, data) {
+ var promise = Vow.promise(),
+ _this = this,
+ query = data && data.params,
+ // TODO _resolveApiParams()
+ requestUri = this._getUri(resource, query),
+ start = Date.now(),
+ requestOptions = {
+ uri: requestUri,
+ method: method,
+ encoding: null,
+ forever: true,
+ headers: apiParams.headers,
+ timeout: apiParams.timeout
+ };
+
+ request(requestOptions, function (err, res, encodedBody) {
+ if (err) {
+ if (err.code === 'ETIMEDOUT') {
+ console.error(['Timeout', this._timeout, requestUri].join(' '));
+ promise.reject(_this._HttpError(500, 'ETIMEDOUT'));
+ } else {
+ promise.reject(err);
+ }
+ } else {
+ _this._decodeBody(res, encodedBody, function (err, body) {
+ if (err) {
+ promise.reject(err);
+ } else {
+ if (res.statusCode !== 200) {
+ promise.reject(new _this._HttpError(
+ res.statusCode,
+ body
+ ));
+ } else if (data && data.output === 'string') {
+ promise.fulfill(body);
+ } else {
+ _this._parse(promise, body);
+ }
+ }
+ });
+ }
+ });
+ return promise;
+ }
+ });
+}());
Oops, something went wrong.

0 comments on commit da4b3d7

Please sign in to comment.