Archive

Archive for the ‘NodeJS’ Category

twitter-node https fix

November 1st, 2011 No comments
nodejs twitter-node

nodejs twitter-node

twitter streaming api

twitter streaming api

Como muchos sabran, hace un tiempo el steaming api de twitter solo funciona con https.

Para nodejs existe el modulo twitter-node, que permitia consumir este streaming, pero dejo de funcionar por el tema del https.

Aca adjunto el index.js de ese modulo (el codigo js que lo hace funcionar) fixeado para que trabajo con https.

Como fix extra, también deje habilitado el debug (propiedad que existía pero no hacia nada).

 

//Fixed by Cesar Casas (lortmorris@gmail.com)
var https         = require('https'),
    query        = require('querystring'),
    Parser       = require('./parser'),
    EventEmitter = require('events').EventEmitter,
    Buffer       = require('buffer').Buffer;

// process.mixin is gone, a function for replacement
function extend(a, b) {
  Object.keys(b).forEach(function (key) {
    a[key] = b[key];
  });
  return a;
}

// Creates a streaming connection with twitter, and pushes any incoming
// statuses to a tweet event.
//
// options - optional Object that specifies custom configuration values.
//
// Valid option keys:
//
// port      - Integer of proxy port
// host      - String or ip address of the proxy server.  Defaults to 'stream.twitter.com'.
// path      - String of the base path for the request.
// action    - String part of the URL that specifies what to query for.
// track     - Array of keywords to filter.  See track()
// following - Array of userIDs to filter.  See follow()
// locations - Array of lat/long tuples.  See location()
// params    - Extra HTTP params Object to send with the request.
// user      - String Twitter login name or email.
// password  - String Twitter password.
//
// Returns TwitterNode instance.
var TwitterNode = exports.TwitterNode = function(options) {
  EventEmitter.call(this);
  if(!options) options = {};
  var self           = this;
  this.port          = options.port      || 80;
  this.host          = options.host      || 'stream.twitter.com';
  this.path          = options.path      || '/1/statuses/';
  this.action        = options.action    || 'filter';
  this.trackKeywords = options.track     || [];
  this.following     = options.follow    || [];
  this.locations     = options.locations || [];
  this.params        = options.params    || {};
  this.user          = options.user;
  this.password      = options.password;
  this.headers       = { "User-Agent": 'Twitter-Node' };
  this.debug         = options.debug || false;
  this.parser        = new Parser();
  this.parser.addListener('object', processJSONObject(this));
  this.parser.addListener('error', function (error) {
    self.emit('error', new Error('TwitterNode parser error: ' + error.message));
  });
  if (options.headers) {
    extend(this.headers, options.headers);
  }
}

TwitterNode.prototype = Object.create(EventEmitter.prototype);

// Track the following keyword.  If called multiple times, all words are sent
// as a comma-separated parameter to Twitter.
//
// See: http://apiwiki.twitter.com/Streaming-API-Documentation#track
//
// word - String word to track.
//
// Returns nothing.
TwitterNode.prototype.track = function track(word) {
  this.trackKeywords.push(word);
  return this;
};

// Follow the given twitter user (specified by their userID, not screen name)
// If called multiple times, all userIDs are sent as a comma-separated
// parameter to Twitter.
//
// See: http://apiwiki.twitter.com/Streaming-API-Documentation#follow
//
// userID - Integer userID to track.
//
// Returns nothing.
TwitterNode.prototype.follow = function follow(userId) {
  this.following.push(userId);
  return this;
};

// Match tweets in the given bounding box.
//
// See: http://apiwiki.twitter.com/Streaming-API-Documentation#locations
//
// Example: location(-122.75, 36.8, -121.75, 37.8) // SF
//
// lng1, lat1 - southwest corner of the bounding box.
// lng2, lat2 - northeast corner.
//
// Returns nothing.
TwitterNode.prototype.location = function location(lng1, lat1, lng2, lat2) {
  this.locations.push(lng1, lat1, lng2, lat2)
  return this;
};

TwitterNode.prototype.stream = function stream() {
  if (this._clientResponse && this._clientResponse.connection) {
    this._clientResponse.socket.end();
  }

  if (this.action === 'filter' && this.buildParams() === '') return;

	var headers = extend({}, this.headers),
      twit    = this,
      request;

  headers['Host'] = this.host;

  if (this.user) {
    headers['Authorization'] = basicAuth(this.user, this.password);
  }

var options = {
  host: this.host,
  port: this.port,
  path: this.requestUrl(),
  method: 'GET',
  headers : headers
};

if(this.debug) console.log(options);

var req = https.request(options, function(res) {

if(this.debug)  console.log("statusCode: ", res.statusCode);
if(this.debug)  console.log("headers: ", res.headers);

  res.on('data', function(d) {
    twit._receive(d);
    if(this.debug) process.stdout.write(d);
  });

  res.on('response', function(d) {
	    twit._clientResponse = d;
	    if(this.debug) process.stdout.write(d);
	  });

  res.on('end', function() {
      twit.emit('end', this);
      twit.emit('close', this);
    });

});

req.end();

req.on('error', function(e) {
  console.error(e);
});

  return this;
};

// UTILITY METHODS

// Passes the received data to the streaming JSON parser.
//
// chunk - String data received from the HTTP stream.
//
// Returns nothing.
TwitterNode.prototype._receive = function(chunk) {
  this.parser.receive(chunk);
  return this;
};

// Builds the URL for the streaming request.
//
// Returns a String absolute URL.
TwitterNode.prototype.requestUrl = function() {
  var url =  this.path + this.action + ".json" + this.buildParams();
	console.log('make url: ', url);

	return url;
};

// Builds the GET params for the streaming request.
//
// Returns URI encoded string: "?track=LOST"
TwitterNode.prototype.buildParams = function() {
  var options = {};
  extend(options, this.params);
  if (this.trackKeywords.length > 0) options.track = this.trackKeywords.join(",");
  if (this.following.length > 0)     options.follow = this.following.join(",");
  if (this.locations.length > 0)     options.locations = this.locations.join(",");
  if (options.track || options.follow || options.locations) {
    return "?" + query.stringify(options);
  }
  return "";
};

// Base64 encodes the given username and password.
//
// user - String Twitter screen name or email.
// pass - String password.
//
// Returns a Basic Auth header fit for HTTP.
var basicAuth = function basicAuth(user, pass) {
  return "Basic " + new Buffer(user + ":" + pass).toString('base64');
};

// Creates a callback for the object Event of the JSON Parser.
//
// twit - an instance of this TwitterNode.
//
// Returns a function to be passed to the addListener call on the parser.
var processJSONObject = function processJSONObject(twit) {
  return function(tweet) {
    if (tweet.limit) {
      twit.emit('limit', tweet.limit);
    } else if (tweet['delete']) {
      twit.emit('delete', tweet['delete']);
    } else {
      twit.emit('tweet', tweet);
    }
  };
};

Utilidades para el manejo del flujo asincrónico en NodeJS

nodejs

nodejs

Los que programan en NodeJS seguramente habrán notado lo dificil que es seguir el flujo del programa cuando tenemos varias llamadas a funciones que realizan operaciones asincronicas.
Los casos mas comunes que se suelen dar son donde tenemos que realizar 2 o mas operaciones asincrónicas en serie o en paralelo y debemos determinar cuando se han finalizado de procesar.
Para ello hay varias librerías que nos ayudan a escribir código mas legible y ahorrarnos un poco de indentación, pero la verdad es que, en mi opinión, la mayoría son demasiado complejas y tienen demasiados “features” para resolver problemas sencillos.
Basandome en algunas de estas librerías y algunos conceptos que leí por ahí, desarrollé unas utilidades simples.

1) forEachAsync: Permite realizar una operación asincrónica en cada iteración y continuar a la siguiente al finalizarla.

 

Array.prototype.forEachAsync = function(iterator, then) {
	var self = this;
	var loop = function(i) {
		if (i < self.length) {
			iterator(self[i], function() {
				loop(i+1);
			});
		}
		else {
			then();
		}
	};
	loop(0);
};

Ejemplo:

[1,2,3,4,5].forEachAsync(function(item, next) {
	console.log('item ' + item);
	setTimeout(function() {
		next(); // avanzamos al siguiente item, alias ‘continue’
	}, 500);
}, function() {
	console.log('end');
});

2) forEachAsyncParallel: Parecido a la versión anterior pero permite n cantidad de operaciones simultaneas.

Array.prototype.forEachAsyncParallel = function(iterator, max, then) {
	var self = this;
	var count = 0, index = 0;
	var loop = function() {
		if (index == self.length && count == 0) then();
		else if (index < self.length) {
			count++;
			iterator(self[index++], function() {
				count--;
				loop();
			});
		}
	};
	if (self.length == 0) then();
	for (var i = 0, len = Math.min(max, self.length); i < len && index < self.length; i++) loop();
};

Ejemplo:

[1,2,3,4,5].forEachAsyncParallel(function(item, next) {
	console.log('item ' + item);
	setTimeout(function() {
		next();
	}, 500);
}, 2, function() {
	console.log('end');
});

3) Futuros / continuaciones
Podemos aplicar el concepto de “currying” y hacer una función que nos ayude a encapsular una operación asincrónica para recuperar el resultado mas adelante.
Este concepto consiste en derivar una funcion de n parámetros en una que solo acepte un parámetro. Si aplicamos esto al callback podemos hacer lo siguiente:

function searchApi(url, callback) {
	// async op...
}
function readFile(path, callback) {
	// async op...
}

// creamos funciones especializadas
function curriedSearchApi(url) {
	return future(searchApi, arguments, 1);
}
function curriedReadFile(path) {
	return future(readFile, arguments, 1);
}

// iniciamos las operaciones...
var apiSearch = curriedSearchApi('http://google.com?q=lalala');
var fileReader = curriedReadFile('/etc/passwd');

// mas adelante...
apiSearch(function(err, result) {
	fileReader(function(err, result) {
		// terminaron ambas operaciones, hacemos algo con ambos resultados
	});
});

Y aquí está la implementación de “future”:

function future(fn, args, i) {
	var done, err, result;
	var cb = function(e, r) {
		done = true;
		err = e,
		result = r;
	};
	args = Array.prototype.slice.call(args);
	args[i || 1] = function(e, r) {
		cb(e, r);
	};
	fn.apply(this, args);
	return function(_) {
		if (done) _(err, result);
		else cb = _;
	};
}

Como se ve, lo que hacemos es llamar a la funcion fn con los argumentos recibidos. i indica el índice que ocupa el callback en el objeto arguments.
Creamos nuestro propio callback que va a almacenar el resultado y retornamos una función cuyo único parámetro es un callback.
Si al momento de llamar esta función ya teníamos el resultado, simplemente lo devolvemos, caso contrario se reemplaza el callback por el nuevo.

Hay varias formas de resolver los casos anteriores, algunas tan sencillas como usar contadores y otras algo rebuscadas pero que en definitiva ayudan a mantener legible el código.
Pueden ver la lista de módulos que ayudan a resolver este tipo de problemas aquí: https://github.com/joyent/node/wiki/modules#async-flow

Espero les haya gustado, hasta la próxima! :D

Consejos de timezone by Demian Rodriguez

June 2nd, 2011 2 comments

time zone, el gran problema de la programacion. Consejos y trucos

Timezone, consejos y trucos para php, mysql y nodejs

Demian Rodriguez nos da unos consejos para minimizar estos problemas.

1) Establecer como timezone default “UTC” tanto en el server como en la base de datos.
Ej en Debian:

#dpkg-reconfigure tzdata
#restart mysql

2) Para el caso de MySQL, las columnas de tipo DATE, DATETIME o TIME no almacenan info sobre el timezone, uno es responsable de saber en que formato esta la fecha para despues mostrarla correctamente al usuario. Para esto es recomendable guardar todas las fechas en UTC.

3) Al mostrar una fecha al usuario se debe convertir al timezone adecuado. Como?

PHP:

// mostrar
$fechaDeMysql = "2011-06-01 08:30:20";
$date = new DateTime($fechaDeMysql, new DateTimeZone("UTC"));
$date->setTimeZone(date_default_timezone_get());
$date->format(...);

// guardar
$fechaQueVieneDeNoseDonde = 'Mon, 15 Aug 2005 15:52:01 +0300'; // ya tiene timezone
$date = new DateTime($fechaQueVieneDeNoseDonde);
$date->setTimeZone(new DateTimeZone("UTC"));
// guardarla en mysql...

NodeJS:
Si aca no mostramos nada al usuario es re facil, no hay que hacer nada :)
El proceso usa el tz del SO. Pero tambien lo podemos forzar seteando la variable de entorno al principio del script:

process.env.TZ = 'UTC';

Como obtener una fecha de la db y mostrarla al usuario en el TZ correcto? No se, es una incógnita como sabe JS en que TZ está la fecha que saque de la db. Lo pregunté aca: https://github.com/felixge/node-mysql/pull/55

// guardar
/**
 * metodo re copado para formatear fechas :)
 * return formatted date as yyyy-mm-dd H:i:s using UTC
 */
Date.prototype.formatUTC = function() {
	function pad(n) {
		return n < 10 ? '0' + n : n
	}
	var d = this;
	return d.getUTCFullYear()+'-'
	  + pad(d.getUTCMonth()+1)+'-'
	  + pad(d.getUTCDate())+' '
	  + pad(d.getUTCHours())+':'
	  + pad(d.getUTCMinutes())+':'
	  + pad(d.getUTCSeconds());
};

var fechaParaInsertar = new Date('Mon, 15 Aug 2005 15:52:01 +0300').formatUTC();

NodeJS, documentacion en español

March 14th, 2011 2 comments

Muchos programadores no son muy adeptos al ingles, es por eso que buscan en forma desesperada informacion en español.

En el caso de NodeJS tenemos algunos recursos.

El primero es en si este Blog, donde vamos a ir agregando documentacion, codigos de ejemplos, etc.

Como otras alternativas tenemos nodejs.es : http://www.nodejs.es

Recomiendo seguir al usuario: @nodejs_es ya que ahi podremos estar mas “al dia” con las publicaciones del blog de nodejs.es.

En si nodejs.es es un blog, con articulos sobre lo que va saliendo de la tecnologia node, pero tambien tenemos toda la documentacion oficial traducida, los ejemplos, etc.

NodeJs documentacion en espanol

NodeJs documentacion en espanol

node-twitter, stream twitter api con nodejs

March 3rd, 2011 No comments

nodejs

nodejs

twitter api stream

twitter api stream

NodeJS sigue creciendo a pasos agigantados, y es por ello que ya tenemos soluciones casi para cualquier cosa.

En esta ocacion, vamos a ver twitter-node de technoweenie, una libreria que nos permitira conectarnos facilmente al api de stream de twitter.

Para los que no sepan, el api de twitter nos permite leer su stream directamente y “escuchar” todo lo que pasa en twitter.

El problema que puede encontrar un programador es que al ser un stream por el port 80 de “objectos”, la conexion obviamente no se corta nunca, y la forma de dividir los objetos enviados es por un salto de linea. Una solucion seria establecer un socket al port 80, haces las llamadas http correspondiente y procesar el buffer, pero es mas facil usar node.

Primero que nada, en nodejs siempre vamos a encontrar varias alternativas para solucionar un problema, yo en este caso seleccione la libreria node-twitter de technoweenie porque es perfecta para este ejemplo.

Antes que nada, tenemos que instalar la libreria.

git clone https://github.com/technoweenie/twitter-node.git

npm install twitter-node

Con eso ya tenemos la libreria instalada, ahora simplemente copiamos el ejemplo que en la misma pagina de la libreria podemos encontrar

var TwitterNode = require('twitter-node').TwitterNode
  , sys         = require('sys')

var twit = new TwitterNode({
  user: 'tuUserDeTwitter',
  password: 'TuPasswordDeTwitter',
  track: ['google', 'linux'], 

});

twit.params['count'] = 100;

twit.headers['User-Agent'] = 'Firefox';

// Capturamos los errores
twit.addListener('error', function(error) {
  console.log(error.message);
});

twit
  .addListener('tweet', function(tweet) {
    sys.puts("@" + tweet.user.screen_name + ": " + tweet.text);
  })

  .addListener('limit', function(limit) {
    sys.puts("LIMIT: " + sys.inspect(limit));
  })

  .addListener('delete', function(del) {
    sys.puts("DELETE: " + sys.inspect(del));
  })

  .addListener('end', function(resp) {
    sys.puts("wave goodbye... " + resp.statusCode);
  })

  .stream();

twit.stream();

Como veran es realmente simple. Obviamente recomiendo leer primero que nada la documentacion, y segundo el codigo fuente (se aprende mucho intentando enteder las cosas, no solo usandolas).

 

Categories: NodeJS Tags: , , , ,