Archivos de la categoría Proyectos

Nuevo proyecto: PlayEpub, lector de ebooks para Blackberry Playbook

Ah!, que no os lo había contado, os tengo algo abandonados últimamente, pero es que he estado bastante liado, nuevo proyecto en la oficina y nuevo proyecto en casa :P. Research in Motion (más conocido como Blackberry) me ha regalado su nuevo tablet, el Playbook. ¿Adivináis por qué? ¡Sí! Por publicar Valenbisi para Playbook :).

No lo voy a negar, nunca me habría comprado un tablet si no me lo hubiesen regalado :P, y después de dos meses con él en casa, me confirmo, no le veo 500 euros de utilidad :P. El aparato en cuestión me parece muy buen cacharro, micro de doble núcleo a 1ghz, 1G de ram, salida HDMI y lo más sorprendente, sistema operativo QNX, un nuevo player en el mercado. RIM compró QNX, un sistema operativo en tiempo real, hace unos años y ahora ha aprovechado para incluirlo en su tablet. El rendimiento es espectacular, reproduciendo una película en HD, navegando y jugando simultáneamente sin notar ni un ápice de deterioro. Pero claro, todo tiene sus pegas :P. Desde mi punto de vista la mayor es la falta de aplicaciones. Aunque RIM anunció a bombo y platillo hace unos meses que se podrían ejecutar aplicaciones Android, lo cierto es que por el momento no, nos limitamos a las aplicaciones nativas. ¿Y cómo se desarrollan estas aplicaciones nativas? Pues más sorprendente aún, en Adobe AIR, por mi encantado :P, pero reconozco que tiene muchas limitaciones a la hora de poder hacer ciertas cosas, por ejemplo un cliente ssh. Aún así es más que suficiente para la mayoría de aplicaciones de usuario.

Cuando cayó en mis manos, lo primero que eché en falta fue un lector de ebooks que me permitiese abrir los epub que ya tenía y que suelo leer en el móvil o convertir a mobi para pasarlos al Kindle. Ni corto ni perezoso me dije, ¿por qué no hacerlo yo mismo? De eso hace dos meses y hoy os confirmo que ya está a la venta :).

La verdad es que inicialmente pensé que sería mucho más sencillo, de hecho, tras estudiar el formato epub y tener el sistema de descompresión y lectura preparado, creía que lo tenía prácticamente hecho, ¡cuanto me equivocaba!. La parte de paginación tipo libro, es decir, click para página siguiente, click para página anterior, se me complicó muchísimo. Rehice el sistema cuatro veces desde cero ya que no encontraba la forma correcta de que todo funcionase como debía. Finalmente di con algo que funcionaba, me limitaba para hacer otras cosas, pero al menos la navegación y lectura eran fluidas y usables :).

La última semana de desarrollo  fue la más satisfactoria a nivel personal ya que, una vez superados los problemas de la paginación (mes y medio solo para eso), me dediqué a añadirle funcionalidades más vistosas y llamativas de cara al usuario, aumentar el tamaño de fuente, modo noche que hace el fondo negro y la fuente blanca, reducir y aumentar el brillo de pantalla, añadir marcadores para volver rápidamente a ciertas páginas, porcentaje de lectura…

Creo que me quedó muy aparente :). En la imagen de abajo podéis ver la interactividad de la pantalla de lectura con 4 zonas sensibles para hacer click, cada una con una función distinta. Pero eso no es todo, si se hace un click largo, es decir, se pulsa con el dejo y de deja pulsado sin levantarlo más de un segundo, sale la opción para añadir marcadores (o eliminarlo si esa página ya lo tenía).

He de decir que la finalidad de la aplicación no es hacer negocio, la hice para leer mis libros, pero si entra algo… :P. Los primeros tres días a la venta generó casi 200 descargas a 2$. Tampoco me puedo quejar.

Y como siempre llego tarde a todas partes he de decir que me pasé estos dos meses de desarrollo controlando en la tienda de aplicaciones que no saliese otro lector de epubs. Pues bien, el mismo sábado que subí la mía apareció otra publicada. Encima tardaron una semana en validármela, con lo que la otra tuvo tiempo de copar el mercado, una lástima, pero como siempre, tarde a todas partes :P.

Más información en PlayEpub.

Valenbisi para Blackberry ya en la App World

Finalmente ya está disponible la aplicación para Blackberry de mi proyecto Valenbisi para móviles en la tienda oficial, la Blackberry App World. Lo último que os había comentado es que no iba a ser posible ya que RIM exigía un documento firmado ante notario dando fé de que tú, el futuro vendedor, eras quien decías ser y, como os imaginareis, no estaba yo dispuesto a ese gasto :P, así que la estaba distribuyendo yo mismo directamente a aquellos que entraban desde el navegador de su dispositivo. Se han hecho cerca de cien instalaciones de la aplicación de esta manera lo que me parece que no está nada mal teniendo en cuenta que es el usuario el que te encuentra a tí.

Ahora, sin embargo, todo ha cambiado. RIM ha rectificado su política de autenticidad notarial, me imagino que muchas solicitudes se habrán quedado a mitad de camino sin completar el último paso. El viernes pasado recibí un email donde me indicaban que tenía pendiente completar el último paso pero que ahora ya no era necesario un documento notarial sino que con una copia de un documento oficial, por ejemplo el DNI, sería suficiente. Dicho y hecho, 4 días es lo que costó finalizar el proceso: subir la aplicación, pasar los controles de calidad y que la publiquen. Desde hoy mismo está online. Podéis verla aquí.

Desde una BlackBerry podéis acceder desde el icono de la App World buscando “valenbisi“:

Y este es el resultado:

A ver si tiene un éxito parecido al de la versión Android que lleva ya ¡más de mil descargas!.

Modificando la infraestructura web de un servidor con Nginx para servir contenido estático y como proxy de Apache

Es la moda :), y con este comentario no quiero decir que no sea una buena idea.

La explicación es sencilla. Solemos utilizar Apache para servir cualquier tipo de contenido a través de una petición HTTP, pero en Apache cargamos habitualmente muchos módulos necesarios para que nuestras aplicaciones funcionen correctamente, empezando por el mismísimo PHP, pero que no necesitamos para servir contenidos estáticos (imágenes, archivos css o javascript, archivos comprimidos…). Si pudiésemos separar de una manera sencilla las peticiones de estáticos de las de dinámicos podríamos redirigirlas a distintos servicios y conseguiríamos que las estáticas consumiesen muchos menos recursos ya que las podríamos hacer con un servidor mucho más ligero que Apache. ¡Podemos!

Este sería el escenario tradicional de un servidor web:

Las solicitudes HTTP llegan al servidor web desde Internet y éste lee los archivos en disco necesarios para servirla. Nada nuevo.

Este es el escenario al que queremos migrar:

Todas las peticiones HTTP son recibidas en el puerto 80 por un servidor mucho más ligero que Apache (nginx, lighttpd) que se encarga de servir directamente los contenidos estáticos desde el disco y de redirigir las solicitudes dinámicas al Apache de siempre que ahora escucha en el puerto 8080, es decir, el servidor ligero actúa como proxy para las peticiones de contenidos dinámicos.

Para el caso que nos ocupa, el servidor a migrar tiene varios virtual hosts definidos de distintos sites y se pretendía dejar los que tienen poco tráfico tal y como están ahora, es decir, que Apache lo sirva todo, y cambiar sólo aquellos donde el tráfico es elevado para que Nginx sirva los estáticos.

El primer paso es, por tanto, instalar y configurar Nginx para que actúe como proxy completo. Deberemos modificar también Apache para que deje de escuchar en el puerto 80. Empecemos por esto último.

Tendremos que cambiar el fichero de configuración httpd.conf para que escuche en el nuevo puerto, en mi caso el 8080. Cambiaremos la línea correspondiente para que quede así:

Listen 8080

Pero esto no es todo. Tenemos que modificar también los virtual hosts definidos para que escuchen en el nuevo puerto. Al final del mismo archivo cambiaremos la línea correspondiente por esta (la tuya será similar, quizás en vez del asterisco tenga la ip de tu máquina):

NameVirtualHost *:8080

En mi Centos5 la configuración de los virtual hosts se guarda en /etc/httpd/httpd.d. Desde ahí lanzamos este comando que nos los actualizará todos de un tirón:

for i in `dir .`; do sed -i s/*:80/*:8080/g $i; echo  $i; done

Menos mal, sino habría que cambiarlos a mano uno por uno :P.

Ahora instalamos nginx:

yum install nginx

y hacemos la primera configuración que nos permitirá, de momento, que sea proxy completo de Apache, es decir, que lo redirija todo a Apache.

Creamos el archivo /etc/nginx/proxy.conf

resolver 127.0.0.1;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 128m;
client_body_buffer_size 256k;
proxy_connect_timeout 60;
proxy_send_timeout 60;
proxy_read_timeout 60;
proxy_buffer_size 32k;
proxy_buffers 32 256k;
proxy_busy_buffers_size 512k;
proxy_temp_file_write_size 512k;

La primera línea será la ip del servidor DNS.

Ahora añadiremos a la configuración de nginx lo siguiente dentro del “server” por defecto.

/etc/nginx/nginx.conf

server{
	listen       80;
	location / {
		proxy_pass http://$host:8080;
		include /etc/nginx/proxy.conf;
}

El primer paso debería estar terminado. Levantamos los servicios de Apache y nginx y probamos a navegar por los virtual hosts del servidor. Si todo ha ido bien debería funcionar correctamente, pero si analizamos las cabeceras que se reciben en la respuesta, por ejemplo con el plugin livehttpheaders de Firefox, veremos como añade la siguiente línea a todas las solicitudes:

Server: nginx/0.8.53

Prueba 1 superada. Vamos ahora a configurar determinados virtual hosts para que el contenido estático lo sirva nginx.

/etc/nginx/conf.d/virtual.conf

server {
	listen  80;
	server_name www.tudominio.com;
	location ~* ^.+.(jpg|js|jpeg|png|ico|gif|txt|js|css|swf|zip|rar|avi|exe|mpg|mp3|wav|mpeg|asf|wmv|flv)$ {
		root /var/httpd/www.tudominio.com;
		expires 30d;
	}
	location / {
		proxy_pass http://www.tudominio.com:8080;
		include /etc/nginx/proxy.conf;
	}
}

Con esto le decimos que todos los archivos estáticos los sirva nginx directamente y que las demás peticiones las redirija a Apache. Debemos crear un “server” por cada virtual host que queramos configurar de esta manera. Básicamente lo que hacemos es configurar la misma ruta en disco para los archivos estáticos que la que teníamos en Apache, de esta manera no tendremos que cambiar absolutamente nada. En muchos sitios proponen crear un host static.tudominio.com, pero esto implicaría modificar físicamente todas tus aplicaciones web, y eso no es lo que se pretende :P.

Sólo nos queda reiniciar nginx. ¿Cómo sabemos si funciona? Sencillo, revisando los logs de Apache y nginx. Si lo hacemos veremos cómo este último sirve los archivos estáticos y Apache todos los demás :).

Finalmente tendremos un pequeño problema. Desde Apache se verán todas las solicitudes HTTP como si vinieran de la propia máquina ya que, en efecto, vienen de nosotros mismos a través del proxy. Esto puede ser un problema para analizadores de tráfico pero también si tienes algún sistema que controla las ip’s de los usuarios. Para solucionarlo tenemos mod_rpaf para Apache (reverse proxy add forward module) que nos reemplazará las cabeceras adecuadamente de manera que la IP remota que veamos sea la de nuestro cliente. Lo descargamos, lo instalamos y lo configuramos:

/etc/httpd/conf.d/rpaf.conf

LoadModule rpaf_module modules/mod_rpaf-2.0.so
RPAFenable On
RPAFsethostname On
RPAFproxy_ips 127.0.0.1
RPAFheader X-Forwarded-For

Con esto conseguiremos que se reemplace la IP del cliente que se recibe en Apache con la que viene en la cabecera “X-Forwarded-For” que es donde nginx nos deja la original.

Eso es todo. Si reiniciamos el servidor Apache veremos que en los logs comenzará a verse la IP real del usuario y no la de nuestra máquina local.

Como siempre, un último detalle. Integremos nginx con Cacti para hacer un seguimiento :).

Las plantillas están aquí, pero a mi no me funcionaron bien, las cambié un poco por estas otras.

Añadimos en la configuración inicial de nginx las siguientes líneas:

location /nginx_status {
	stub_status on;
	access_log   off;
	allow IP_DE_TU_SERVIDOR_CACTI;
	deny all;
}

Con esto podrás acceder al estado del servidor nginx desde http://IP_DE_TU_SERVIDOR/nginx_status.

Dentro del zip hay cuatro archivos, dos scripts perl que debes copiar en el directorio scripts de tu instalación de cacti y dos xml que debes importar desde la propia herramienta. Ya está todo preparado. Desde la configuración de cacti del servidor donde has instalado nginx añades las nueves fuentes de datos y los gráficos. Al configurarlo te pedirá la url de acceso que veíamos hace un momento.

Y hasta aquí hemos llegado. En muy poco tiempo hemos conseguido modificar toda la estructura de nuestro sistema web sin pérdida de disponibilidad y, lo mejor de todo, hemos creado un sistema mucho más eficiente y robusto. En condiciones de tráfico medio no notarás mucha diferencia de rendimiento, pero tendrás un sistema mucho más preparado para hacer frente a picos y efectos “barrapunto” :).

Aplicaciones Valenbisi para smartphones Android, Symbian y BlackBerry

Finalizo con éste la serie de artículos sobre el último miniproyecto en el que me embarqué hace ahora un mes, Valenbisi para dispositivos móviles.

Tras un par de semanas con las aplicaciones para los principales sistemas operativos para smartphones a pleno rendimiento (excepto iOS), parece que hay mucha más gente con buenos cacharros de lo que yo esperaba, teniendo en cuenta además que es una aplicación enfocada a una zona geográfica muy concreta.

Os comento además que, aprovechando la infraestructura que ya tenia montada y viendo que el proveedor del servicio es el mismo, decidí replicar el sistema para las bicis urbanas de Sevilla, información del estado de las estaciones de Sevici, acompañado también de su aplicación para Android.

Aquí van las estadísticas de descarga de las aplicaciones desde Android Market:

No está nada mal, sobre todo teniendo en cuenta que la de Sevici no la anuncié en ningún sitio :). Los números de la de Valencia me parecen espectaculares, desde luego ya hay mucha gente por ahí con un Android.

Y aquí las estadísticas para la aplicación para Symbian desde Ovi Store:

Éstas son, sin duda, más complicadas, parece que en la India se usa mucho Symbian :P. Aún así se nota que hay descargas.

Finalmente nos queda Blackberry. Tengo aplicaciones para OS4 y OS5, pero esta vez sin presencia en App World ya que me solicitaban firmas de notario, así que directamente pasé de ellos :P, la distribuyo directamente desde Valenbisi.mobi y que se la descargue quien quiera. De hecho, cada vez que entras, si tu terminal es compatible con alguna de las aplicaciones disponibles, te ofrece la posibilidad de descargar la aplicación. Se han hecho aproximadamente unas 20 descargas de la aplicación para BlackBerry, teniendo en cuenta que la visibilidad así es mucho menor que desde la tienda de aplicaciones, me parece un buen número :).

Finalmente tenemos las estadísticas globales de visitas al sitio web, contando tanto los que entran desde aplicaciones como los que entran directamente.

Me parecen muy buenos datos para una aplicación completamente local. Llama la atención el pico de visitas del 8 de diciembre, pero tiene sentido ya que, además de día festivo, hizo un día espectacular con 23º de temperatura, importante para ir en bici :P. Seguro que a medida que vaya mejorando el tiempo se notará el incremento de uso. Pese a que sólo oímos hablar de iPhone’s y Android’s, fijaos en la sorprendente relación de sistemas más utilizados. Aunque Android pega fuerte, Nokia sigue siendo lo más utilizado, un dato importante a tener en cuenta en cualquier aplicación para móviles que desarrollemos.

Con esto abandono el desarrollo :P. La aplicación está terminada, funciona bien y parece ser por los comentarios que es fiable. Espero no tener que hacer mucho mantenimiento :P, me aburre bastante :).

Miniproyecto – Valenbisi.mobi, geolocalización del servicio de bicicletas de Valencia para móviles y sms

Como supongo que la mayoría no sabréis de qué hablo, empezaré por el principio.

Valenbisi es el servicio urbano de alquiler de bicicletas de Valencia a semejanza del que existe en muchas otras ciudades españolas y europeas. La idea es fomentar su utilización para desplazamientos cortos ya que el abono anual (20 euros) permite alquilar gratuitamente la bicicleta la primera media hora de cada día. Hay varias estaciones a lo largo de la ciudad y simplemente coges la bici en una y la dejas en la que mejor te venga. Valencia creo que es una de las ciudades que más se prestan a un servicio de este tipo por dos razones básicas, la climatología y la falta de cuestas. Al parecer el éxito ha sido espectacular tras el verano hasta tal punto que el servicio de abonos está saturado de solicitudes, el nuestro tardó alrededor de un mes en llegarnos.

El sábado pasado mi pareja y yo decidimos acercarnos a la playa y darnos un paseo en bici, hacía un día fenomenal y el calorcito invitaba a pasear. El plan era coger la bicicleta al principio de la playa y dejarla al final para regresar a pie. Pues bien, el problema fue que a la hora de regresar no había ningún puesto libre donde dejar las bicis en las 3 estaciones por las que pasamos, tuvimos que volver al punto de inicio donde tuvimos la suerte de encontrar los dos que necesitábamos. Dos chicas que llegaron justos detrás nuestra no tuvieron la misma suerte :P. Esto nos planteó el problema que puede ser una situación de este tipo al tener que buscar otra estación que tenga enganches libres, puedes ir a la siguiente pero tampoco sabes si habrá puestos, con lo que te tocará ir a la siguiente y así indefinidamente. Si además del tiempo perdido tenemos en cuenta el dinero que pagas por el tiempo que pasa, no cabe duda que puede ser un serio inconveniente para el servicio. ¿Qué pasará cuando llegue el verano y todos los usuarios quieran bajar con sus bicis a la playa? Pues que no habrá donde dejarlas desde primera hora de la mañana. Estos días, siguiendo el problema, he visto que ocurre algo similar con las estaciones del centro de la ciudad en horario laboral, a las 10 de la mañana están casi todas completas. ¿Cómo sabes entonces dónde dejar tu bici?

Pues aquí llego yo a medio solucionar el problema :P. Una aplicación para el móvil que te dice las estaciones más cercanas con puestos libres o bicis disponibles, ya que el problema puede ser el contrario, que vayas a por una bicicleta y estén todas ocupadas. La diferencia con otras aplicaciones por el estilo es que no me he atado a Iphone o Android como casi siempre sucede en estos casos, he hecho algo que funcionará en la mayoría de terminales de los usuarios “normales”, otra cosa es que quieran utilizarlo. Para acompañarlo, he hecho una interfaz por SMS :P, que es más sencillo para la gente, pero sin la mayoría de opciones.

Como funciona el servicio

Muy sencillo. Entras en http://valenbisi.mobi, le dices donde estás, que puede ser tu dirección o el número de estación de Valenbisi, y automáticamente te indicará el estado de las estaciones que tienes a tu alrededor, es decir, bicis y huecos libres que tienen. Además, si las que tienes cerca no tuviesen bicis o puntos libres, te permite buscar las más cercanas con disponibilidad. Todo ello acompañado de imágenes con mapas con las estaciones indicadas posicionadas y rutas para llegar.

Detalles técnicos

La implementación del sistema no me habrá llevado más de 10 horas incluidas las pruebas. Gracias a todos los que me habéis ayudado con ellas y a vuestras sugerencias :).

Para desarrollarlo he utilizado:

  • API de Valenbisi, si se puede llamar así. Aporta la información de la localización de las estaciones y el estado de las mismas.
  • Google Geocoding API para obtener las coordenadas de una dirección y viceversa.
  • Google Directions API para obtener la ruta entre dos puntos.
  • Google Static Maps API para obtener imágenes de mapas.
  • Geolocation API Specification para aquellos navegadores que lo soporten, así el usuario no tiene que indicar su dirección, el GPS del dispositivo lo hace todo. Actualmente lo soportan, que yo sepa, Iphone, Android y las últimas Blackberry.
  • Wurfl, para obtener las características de su teléfono.

No voy a explicar cómo se utilizan las API’s ya que están bien documentadas y son muy sencillas, todas basadas en peticiones HTTP que devuelven XML o json.

Cuando el usuario accede a http://valenbisi.mobi, si su navegador soporta localización, le saltará el aviso de que la página quiere usar esa información y automáticamente posicionará al usuario. Si no lo soporta, la mayoría de los casos, el usuario debe introducir su dirección o el número de la estación Valenbisi que le interesa. Con esos datos averiguamos donde está. Si es una dirección a través de Google Geodecoding API y si es un número simplemente con el API de Valenbisi. De un modo o de otro ya sabemos donde está el usuario.

Ahora simplemente debemos buscar las estaciones que tiene alrededor. Yo lo he hecho de dos formas. La primera es mostrar las estaciones que tiene a su alrededor a partir del mapa. Esta es una de las primeras cosas curiosas que he hecho. Para obtener el mapa de la posición del usuario, le pido a Google Static Maps API una imagen del mismo tamaño que la pantalla del teléfono del usuario, así verá todo lo máximo que le permita su dispositivo. Esto significa que cada usuario verá un mapa de distinto tamaño. Ahora tengo que averiguar las estaciones que aparecen dentro del mapa. Podríamos pensar en pasarle todas al API y que éste pinte las que aparecen en nuestra imagen y pase de las otras, pero la idea no funciona ya que Static Maps tiene un tamaño máximo de url, que por cierto no es muy grande, rebasado el cual la imagen da error. Así que lo que hice fue calcular cuanto espacio geográfico cabe en la imagen. Le pedí una imagen posicionando marcas cada 0,001 puntos de longitud y latitud. A partir de esa imagen calcule a cuantos píxeles correspondía cada salto. Con esta información, y manteniendo el zoom fijo, sabremos en cada momento qué coordenadas se van a ver en la imagen que le pida a Google ya que conozco el ancho y el alto que le voy a pedir, conozco las coordenadas del centro (la posición del usuario) y sé cuánto me puedo desplazar a lo largo de la imagen. No es 100% fiable pero nos sirve para lo que necesitamos. Ahora simplemente recorro todas las estaciones y si sus coordenadas están entre los límites de la imagen que voy a mostrar, añado la marca. A medida que el usuario se mueve por el mapa con las flechas, voy actualizando las estaciones que se ven en el mismo con este procedimiento.

La segunda manera de buscar estaciones que he utilizado es mostrar las estaciones más cercanas que cumplan uno de los posibles requisitos, que tengan bicis o que tengan huecos libres. Aquí el usuario no puede navegar por el mapa, simplemente le muestro a las que puede acudir. Para localizar las estaciones más cercanas, la opción más clara es consultar Google Directions Api entre la posición del usuario y todas y cada una de las estaciones y obtener las que están más cerca (uno de los parámetros que devuelve es la distancia), pero esto implicaría más de 100 llamadas al API por consulta, con lo cual no es viable. Aplicando conceptos básicos de trigonometría y coordenadas cartesianas, sabemos que, en un plano, la distancia (en línea recta) entre dos puntos es:

Simplemente con los datos de la localización de estaciones y la posición del usuario y aplicando algunas operaciones matemáticas puedo averiguar aquellas estaciones que, siempre en línea recta y cumpliendo los requisitos de disponibilidad, están más cerca del cliente. Ordeno las distancias resultantes de menor a mayor y con las cinco primeras, ahora sí, consulto Google Directions API para que me de una distancia real entre el usuario y esas estaciones. Las vuelvo a ordenar por distancia y ya tengo aquellas más cercanas al usuario. Solo queda pedirle a Google Static Maps la imagen del mapa donde aparecen estas estaciones, ahora sin zoom para que lo ajuste automáticamente. Mucho más eficiente que consultar la distancia de las más de 100 estaciones.

Solo nos queda averiguar la ruta para ir desde la localización del usuario hasta la estación seleccionada. Static Maps no hace rutas, solo líneas rectas, pero podemos indicarle todos los puntos intermedios necesarios y que nos pinte las líneas entre ellos, con lo que si supiéramos los pasos que tiene que dar el usuario, podríamos pintar la ruta. Y para esto tenemos de nuevo Google Directions API, nos dirá los puntos entre el usuario y la estación. Solo tenemos que pasar estos puntos a Static Maps y tendremos la imagen de nuestra ruta.

Eso es todo, uniendo todas las piezas entre sí adecuadamente tendremos el sistema que os presento :). El último toque fue añadir las opciones de idioma, para que los turistas puedan utilizar el servicio :P.

Finalmente vinieron las pruebas con algunos conocidos y los ajustes para los terminales más antiguos (en los nuevos se ve todo fenomenal :P) .

Aquí van algunas capturas en distintos terminales:

Nokia X6

Nokia N95

Nokia N73

Nokia N70

Iphone

BlackBerry 9000

Android

Interfaz SMS

Cuando ya lo tenía terminado se me ocurrió, ¿y por qué no hacer también una interfaz SMS para los usuarios que no tienen Internet en el móvil? Ya tenía todas las piezas y dado que profesionalmente me dedico a eso… 10 minutos 🙂

Envía BICI númeroDeEstación al 215000 25325 y te devuelvo el estado de esa estación y las más próximas con sus direcciones. Por ejemplo:

BICI 146

Est. 165:12 bicis,8 libres.OTRAS:164,8 bicis,9 libres (C. PAVIA (OPUESTO Nº 15-17) )-167,17 bic 0,35Eur Alvento Soluciones S.A.n.atn.clte:902887786 alvento.es

Todo lo que quepa en los 160 caracteres permitidos, que no es mucho. Coste del SMS 0,35 euros, igual que un SMS normal 🙂. Si usas habitualmente la misma estación puedes saber con antelación si tendrás bici o hueco. Información legal del servicio: 0,35Eur.Alvento Soluciones S.A.n.atn.clte. 902887786 alvento.es

Más no se puede pedir en tan poco tiempo. Os invito a probarlo. Se aceptan críticas, mejoras de usabilidad, nuevas funciones, etc…

Actualización:

Sintiéndolo mucho he tenido que cambiar el número corto del servicio ya que el que había dicho inicialmente sólo funciona en Vodafone. El número actual, el 25325 es el más barato de los posibles, lo siento, ya sé que así se limita mucho su utilidad… quedará como ejemplo de lo que se podría hacer 😛 a no ser que alguien quiera hacerse cargo del servicio.

Mi pequeña biblioteca técnica

Hoy voy a mostraros la pequeña colección de libros técnicos que he ido acumulando durante los últimos diez años. A tiempo pasado me he dado cuenta de que una gran mayoría de ellos no pintan nada ya en la estantería ya que son tecnologías desfasadas o superadas por nuevas versiones, tal es el caso, por ejemplo, de Visual Basic 6.0, Windows NT, Oracle 8i… Otros pueden servir aún como introducción general (Java2, SQL Server, MySQL, etc.), pero nunca serán un referente en sí mismos ya que están también “anticuados“. Bien es verdad, y considero importante puntualizarlo, que el grueso de mi biblioteca data del periodo 2000-2005, los últimos años apenas he comprado nada, Internet es, sin lugar a dudas, la biblioteca particular de todos nosotros. Mis últimas adquisiciones tratan temas más relacionados con la gestión (de proyectos, personas y negocios) que con la tecnología en sí misma, y es que estos últimos no pierden su capacidad de enseñanza, con los años siguen ahí para quien quiera consultarlos.

Estos son mis libros, agrupados, más o menos, por temática. Indico entre paréntesis el/los autor/es y la editorial, a continuación el año en que lo compré y una pequeña reseña del mismo.

  • Sistemas Operativos:
    • Guía completa de Microsoft Windows NT Server 4.0 (Russel y Crawlord, Mc Graw Hill), 2000. Con este y los dos siguientes aprendí todo lo relacionado con redes Windows, controladores de dominio, usuarios, etc. En su momento me fueron de muchísima utilidad e incluso gracias a ellos llegué a impartir formación sobre Windows NT, ha pasado mucho tiempo ya.
    • Windows NT 4.0 Server, instalación y gestión (J.M. Martínez y M. Martínez, Prentice Hall), 2000.
    • Windows NT Server 4.0 (Anaya Multimedia), 2000.
    • Linux Máxima Seguridad (Anónimo, Sams), 2002. En su momento me pareció una joya y me sirvió para profundizar y mejorar considerablemente mis conocimientos en temas de seguridad, auditoría e IDS en sistemas Linux. Aún sigo haciendo alguna consulta de vez en cuando.
    • El Libro Oficial de Red Hat Linux – Firewalls (Bill McCarty, Anaya Multimedia), 2003. Fundamental junto al anterior en mi formación sobre sistemas de red avanzados bajo Linux. También sigo echándole un ojo de vez en cuando aunque en Internet esté toda la información más que explicada.
  • Programación y lenguajes:
    • Teach yourself Java2 in 21 Days Second Edition (Lemay & Cadenhead), 2001. Un clásico. Con la primera edición aprendí Java allá por 1996. Con este libro recuperé mi formación en Java, importante en aquel momento.
    • Core Servlets and Java Server Pages (Marty Hall), 2002. Tras mejorar mis conocimientos de java con el anterior y junto al siguiente, aprendí todo lo relacionado con aplicaciones web en Java. En su momento me parecieron un par de libros fundamentales.
    • More Servlets and Java Server Pages (Marty Hall), 2002.
    • Creación de sitios web con XML y Java (Maruyama, Tamura y Uramoto, Prentice Hall), 2002. Lo compré a raíz de los anteriores, supongo que lo habría visto recomendado en algún lado, pero no recuerdo que me pareciese un libro impactante.
    • Learning Perl 3rd Edition (Schwartz & Phoenix, O’Reilly), 2002. Necesitaba algo de formación en Perl pero nunca llegué a leérmelo entero, más que nada porque nunca llegué a necesitar un nivel avanzado en Perl.
    • ActionScript con FlashMX Edición Especial (Prentice Hall), 2003. Sin duda otro libro básico en mi formación. Gracias a él aprendí todo lo relacionado con ActionScript, algo que me ayudó enormemente a dar el paso a Flex algún tiempo después.
    • Desarrollo de juegos con J2ME (Manuel J. Prieto, Ra-Ma), 2005. Un proyecto fracasado :P, en aquel momento tenía algunas ideas en la cabeza pero nunca llegué a leerme este libro.
    • Visual C#.NET (Fco. Charte Ojeda, Anaya Multimedia), 2004. No lo compré, llegó a mis manos por casualidad y tampoco hice nada con él.
    • Enciclopedia de Microsoft Visual Basic 6.0 (Fco. Javier Ceballos, Ra-Ma), 2000. Sin lugar a dudas fue básico en mi formación inicial allá por el año 2000. Hoy está completamente desfasado gracias a la tecnología .NET, pero en aquel momento era imprescindible. Creo que fue el primero de todos.
    • Programación de Active Server Pages (Hillier & Mezick, Mc Graw Hill), 2000. Gracias a este libro conseguí mi primer trabajo de programador web. Corría el año 2000 y comenzaba mi carrera profesional. Junto al anterior, fueron mis primeros libros.
    • XML (Óscar González, Anaya Multimedia), 2001. Cuando estaba aprendiendo Java, no recuerdo por qué, necesitaba conocimientos avanzados en XML y este pequeño libro de bolsillo me dio lo que buscaba.
    • WebServices (Joan Ribas Lequerica, Anaya Multimedia), 2003. Lo mismo que el anterior pero para WebServices, va al meollo del asunto sin complicaciones.
    • La Biblia ASP.Net (Mridula Parihar, Anaya Multimedia), 2009. Mi última adquisición. Por cuestiones profesionales he de ponerme las pilas con .NET, ahí lo tengo esperando :P.
    • The Pragmatic Programmer – From Journeyman to Master (Andrew Hunt & David Thomas, Addison Wesley), 2006. Fundamental para cualquier programador, todo lo que cuenta es completamente lógico y de cajón, tanto que muchas veces lo olvidamos todo.
    • The Unified Modelling LAnguage User Guide Second Edition (Booch, Rumbaugh & Jacobson, Addison Wesley), 2007. La biblia del UML.
    • UML y Patrones Segunda Edición (Craig Larman, Pearson – Prentice Hall), 2005. Empieza bien, pero me lo dejé a mitad, es bastante espeso. Espero retomarlo pronto.
    • The Zen of CSS Design (Dave Shea & Molly E. Holzschlag, New Riders), 2005. Éste y el siguiente me ayudaron considerablemente a entrar en el mundo del CSS, hay miles de recursos en Internet, pero no está de más tener algo de ayuda “a mano“.
    • Stylin’ with CSS – A Designer’s guide (Charles Wyke-Smith, New Riders), 2005.
    • Usabilidad – Diseño de sitios web (Jakob Nielsen, Prentice Hall), 2002. Completamente imprescindible. Recuerdo cómo me impactó su lectura y cómo me abrió los ojos hacia el desconocido campo de la usabilidad en un momento donde no estaba tan de moda como ahora. Todos los que trabajamos en diseño de software deberíamos leerlo, no es que aporte nada concreto, lo importante es la visión global que te hace tener sobre cómo deben pensarse las cosas que fabricas teniendo en cuenta en quién las utilizará.
  • Bases de datos:
    • Oracle 8i Guía de aprendizaje (Abbey, Corey y Abramson, Oracle Press), 2001. Este libro me permitió salir airoso de una tarea que me habían encomendado por aquella época, gracias a él conseguí aprender los conceptos básicos de Oracle.
    • Oracle 9i – Servidor de aplicaciones, red y programación (Cesa Pérez, Ra-Ma), 2003. La idea era prolongar los conocimientos que había alcanzado con el anterior, pero al final todo se quedó en nada ya que profesionalmente me aparté de Oracle.
    • MySQL  Edición Especial (Paul DuBois, Prentice Hall), 2001. La lectura de este libro fue fundamental para mi en aquel momento, con él aprendí a tunear y optimizar un sistema con una elevada carga. En 2001 no había tanta literatura al respecto en Internet como hay hoy en día.
    • Microsoft SQL Server a Fondo (Soukup, Mc Graw Hill), 2001. Mi primera toma de contacto con la base de datos de Microsoft.
  • Management y gestión de proyectos:
  • Negocios y emprendedurismo:
    • El Manifiesto Cultrain (Levine, Locke, Searls & Weinberger, Deusto), 2008. Un clásico sobre la gestión de negocios en Internet orientado hacia el trato con el cliente, la apertura de información en ambos sentidos, etc.
    • El Arte de Empezar (Guy Kawasaki, Ilustrae), 2007.  Otro clásico. Kawasaki expone su experiencia en la creación y desarrollo de startups.
    • El libro negro del emprendedor (Fernando Trías de Bes, Empresa Activa), 2008. Sería el equivalente en  español al de Guy Kawasaki. Muy recomendables los tres en todo caso.
  • Multimedia:
    • Premiere 6.5 (Antonio Paniagua Navarro, Anaya Multimedia) 2002. Con este pequeño manual dí mis primeros pasos en la edición de vídeo digital.
    • Adobe AfterEfeects 5 (Anaya Multimedia), 2002. Y con este otro profundicé un poco en ellos :).
    • Photoshop 5.5 para Windows (Miguel Angel Casanova González & Azucena González Ajenjo, Anaya Multimedia), 2001. Nunca llegué a leerlo, ni ganas de hacerlo :P.

Como conclusión, lo que os comentaba anteriormente. La mayoría de libros decentes sobre lenguajes de programación son muy caros y su vida útil es, como mucho, un par de años, a no ser que sea algo más tradicional como C ó C++. Hoy en día en Internet hay literatura de sobra para aprender cualquier cosa, la única pega que teníamos era tener que pegarte al monitor para leer un pdf o imprimirlo. Por suerte, la bajada de precios de los ebooks hace que durante los próximos meses asistamos a una migración paulatina de lectura técnica hacia los libros electrónicos, y es que así ya puedes ojear tranquilamente ese pdf de Python que tienes pendiente ;).

Nuevo proyecto en 5 horas: UsayTira.me, direcciones de correo de usar y tirar explicado paso a paso

Parece que últimamente me aburro mucho :P. Hace unos días, leyendo un artículo, se me ocurrió de nuevo explicar cómo se hacen esos sistemas de correo instantáneos que se suelen utilizar para registrarse en webs y que después no te envíen spam :P. La idea me pareció muy adecuada para poner un ejemplo práctico de algo que vimos hace tiempo sobre otras utilidades para un servidor de correo y, tal y como me ocurrió hace unos meses, lo que era un artículo se convierte en proyecto.

La idea, por tanto, es crear un sistema que, sin necesidad de ningún registro, te permita crear una cuenta de correo y recibir y leer emails en ella por espacio de una hora, al cabo de este tiempo la cuenta se autodestruye y todos los emails serán devueltos. En nuestra aplicación tendremos dos opciones para crear la cuenta, aleatoria o personalizada, creo que no hacen falta más explicaciones. Una vez usas una cuenta puedes volver a ella más tarde cuando la necesites volviendo a crear una cuenta personalizada con el mismo usuario. Esto es útil, por ejemplo, para que te recuerden la contraseña que utilizaste para registrar en aquella web de descarga de películas y de la que ya no te acuerdas ;).

Qué necesitamos

  • Servidor Linux con Qmail como MTA.
  • Apache, PHP y MySQL.
  • Pear MimeDecode: para procesar los correos entrantes con PHP.
  • Una plantilla superchula de FreeCssTemplates
  • Jquery: para todo lo que es ajax y Javascript
  • ZeroClipboard:  para copiar y pegar automáticamente
  • Jquery ScrollTo: pluggin para desplazar el scroll automáticamente.
  • Una imagen de “Cargando” para las acciones ajax que personalizas aquí.
  • Diccionarios de palabras “aleatorias”. Aquí hay unos cuantos.
  • Adodb (opcional) para el acceso a base de datos.

Eso es todo, sólo hay que juntar las piezas adecuadamente.

Preparando Qmail

Nuestro primer paso, antes de ponernos con los temas puramente web, será configurar adecuadamente el servidor de correo para nuestro propósito. Para ello necesitamos crear un usuario del sistema que reciba todo el correo dirigido a un dominio. Esto lo podemos hacer del siguiente modo:

adduser -g users -s /dev/null usaytirame
passwd usaytirame UNACLAVESUPERCOMPLICADA

Añadimos nuestro nombre de host en:

/var/qmail/control/rcpthosts

usaytira.me

Y el usuario al que dirigiremos los correos en:

/var/qmail/control/virtualdomains

usaytira.me:usaytirame

Reiniciando Qmail conseguiremos que los correos enviados a cualquier cuenta del dominio vayan al buzón del usuario indicado, es decir cualquiercosa[arroba]usaytira.me.

Pero no queremos que los correos vayan al buzón del usuario sino simplemente procesarlos. Para eso editamos el archivo:

/home/usaytirame/.qmail-default

|preline /usr/bin/php /home/usaytirame/procesa.php

Sólo con esa línea. Con este comando conseguimos redirigir los correos entrantes a un script en el que podremos recuperarlos y reutilizarlos a nuestro antojo como veremos a continuación.

Procesando los emails entrantes

Vamos a comenzar por crear una base de datos donde guardaremos los emails recibidos. Como queremos poder acceder a los emails dirigidos a cada cuenta independientemente, haremos dos tablas, una (emails) la utilizaremos como tabla maestra para las cuentas de correo que se creen y en la otra (correos) iremos almacenando los emails recibidos para cada una de esas cuentas.  La estructura sería poco más o menos la siguiente:

CREATE TABLE IF NOT EXISTS `emails` (
  `idEmail` int(11) NOT NULL auto_increment,
  `fecha` timestamp NOT NULL default CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP,
  `email` varchar(255) NOT NULL,
  `ip` varchar(15) NOT NULL,
  PRIMARY KEY  (`idEmail`),
  KEY `email` (`email`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

CREATE TABLE IF NOT EXISTS `correos` (
  `id` int(11) NOT NULL auto_increment,
  `idEmail` int(11) NOT NULL,
  `de` varchar(255) NOT NULL,
  `para` varchar(255) NOT NULL,
  `subject` varchar(255) NOT NULL,
  `fecha` varchar(255) NOT NULL,
  `body` text NOT NULL,
  `fullmail` text NOT NULL,
  PRIMARY KEY  (`id`),
  KEY `idEmail` (`idEmail`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

Creo que es suficientemente descriptiva. El campo fecha de emails nos servirá para controlar los 60 minutos de duración máxima.

Pues ya tenemos todo preparado. Veamos cómo procesamos los emails.

/home/usaytirame/procesa.php

include ("mimeDecode.php");

$email=file("php://stdin");
$email=implode("", $email);

$params['include_bodies'] = true;
$params['decode_bodies'] = true;
$params['decode_headers'] = true;
$params['input'] = $email;
$structure = Mail_mimeDecode::decode($params);

$subject = quoted_printable_decode(trim($structure->headers['subject']));
$ddate = quoted_printable_decode(trim($structure->headers['date']));
$from = quoted_printable_decode(trim($structure->headers['from']));
$to = quoted_printable_decode(trim($structure->headers['to']));

if(ereg("<(.*)>", $to, $p))
    $to=$p[1];
$to=strtolower($to);

Tenemos la primera parte del procesado preparada. De aquí debemos destacar:

$email=file("php://stdin");

Con esto leemos desde la entrada estandar el contenido del email. Recordad que no estamos en una aplicación web sino en un script ejecutado en la consola del sistema.

La otra línea importante es:

$structure = Mail_mimeDecode::decode($params);

Con ella conseguimos procesar el email y separar su estructura en un array asociativo con los distintos parámetros. Os ayudará mucho ver el contenido de ese array:

print_r($structure)

Llegados a este punto podemos hacer una prueba. Necesitamos el contenido completo de un email, podemos sacarlo de nuestro cliente de correo o del propio servidor.

cat prueba.eml | /home/usaytirame/procesa.php

Si todo ha ido bien veremos en pantalla un array con la estructura del correo.

No voy a detallar todo el proceso ya que alargaría mucho el artículo. Vamos con el siguiente paso:

$query="select idEmail from emails where email=".$conn->quote($to);
$rs=$conn->Execute($query);
if($rs->recordcount()==0){
        exit(100);
}else{
        $idEmail=$rs->fields['idEmail'];
        $content = get_content($structure);
        $query="insert into correos
            (idEmail, de, para, subject, fecha, body, fullmail)
            VALUES
           ($idEmail,
            ".$conn->Quote($from).",
            ".$conn->Quote($to).",
            ".$conn->Quote($subject).",
            ".$conn->Quote($ddate).",
            ".$conn->Quote($content).",
            ".$conn->Quote($email).")";
        $rs=$conn->Execute($query);
}

En la primera parte del script comprobamos si la cuenta a la que va destinado el email existe en nuestra base de datos, si no, muy importante, devolvemos un código 100 que indica a qmail que debe devolver el correo ya que no existe el usuario.

Si la cuenta existe recogemos el cuerpo del mensaje. Yo lo hago con la función get_content, que analiza la estructura del correo y devuelve el contenido. Esta parte os la dejo a vosotros. Básicamente consiste en comprobar las distintas partes que puede tener un correo y devolver lo que estimemos oportuno. Un detalle muy importante a tener en cuenta es la codificación tanto del email como de nuestra base de datos y la aplicación web. En mi caso las dos últimas están en UTF-8, con lo que debo convertir todos los textos del email a esta misma codificación. La estructura que teníamos inicialmente en un array tendrá parámetros que nos indican el charset en el que viene el email. Las funciones de conversión de PHP te pueden ser útiles: iconv, utf8_encode, etc.Finalmente introducimos todos los campos del email en la base de datos.

Puedes volver a probar a procesar el email de prueba tal y como hicimos antes. Recuerda que debes añadir el registro de la tabla emails para que  guarde el correo, si no la cuenta no existirá. Una vez te funcione desde la línea de comandos ya puedes probar a enviarte un correo real :).

La aplicación web

No creo que hacer la parte web propiamente dicha necesite muchas explicaciones. Tenemos ya todos los elementos preparados, sólo debemos añadir los formularios para crear la cuenta de correo (aleatoria o personalizada) y, con un poco de ajax, ir cargando los correos a medida que van llegando. No  hay más truco.

Añadiré, eso sí, algunas aclaraciones interesantes.

Para crear las cuentas aleatorias, en vez de utilizar una secuencia aleatoria de números y letras, que daría como resultado algo ininteligible, usamos los diccionarios que comentaba más arriba. Los importamos en una tabla de la base de datos y simplemente tenemos que buscar aleatoriamente una palabra que no esté utilizada todavía como cuenta de correo, sencillo y muy impactante visualmente ya que estás ofreciendo cuentas legibles y con sentido.

Como en cualquier otra aplicación accesible públicamente, hay que añadir algún tipo de mecanismo de seguridad. En mi caso lo he hecho implementando una blacklist de direcciones IP. Cada vez que se crea una cuenta actualizo en una base de datos el número de cuentas que se han creado desde esa IP, si pasa del límite que estimemos oportuno, esa IP se pasa a la tabla de lista negra y cuando intente crear una nueva cuenta no se le dejará.

Nos falta una cosa: Eliminar las cuentas que tienen más de una hora. Muy sencillo, una tarea en el CRON que ejecuta un script que lanza una consulta a la base de datos que elimina las cuentas (y sus correos asociados) que se crearon hace más de 60 minutos.

Finalmente he añadido la opción de reiniciar esos 60 minutos de tiempo, simplemente actualizando el timestamp de la base de datos y algunos efectos visuales para plegar y desplegar los mensajes usando Jquery.

No hay mucho más, en unas cinco horas tenemos la aplicación hecha y funcionando.

Conclusiones

Como conclusión, la misma que hice hace unos meses con el primer proyecto. La copio tal cual porque es igual de válida.

Bueno, y todo este rollo ¿para qué?. Pues muy sencillo, para que veais que hoy en día la tecnología está al alcance de todos, es sencillo y rápido crear un proyecto en Internet, hay de todo por todas las esquinas, la tecnología no es lo importante, lo que verdaderamente cuenta es cómo mueves ese producto tecnológico para rentabilizarlo y obtener un beneficio de él.

Ya tengo mi proyecto superchulo funcionando, sólo me ha costado unas 5 horas de trabajo. Le he puesto un poco de Adsense por aquí y por allí. ¿Y ahora qué? ¿A esperar a que la gente entre y me haga millonario? 😛 Es mucho más complicado que eso como todos sabéis, primero tienes que tener una masa de usuarios elevada que le dé movimiento al proyecto y después tienes que conseguir que la mayoría de ellos sean gente normal, no gente técnica, usuarios avanzados que no pagamos por nada ni pinchamos en publicidad 😛 .

Hoy en día, en Internet, como en cualquier negocio, las técnicas de marketing y venta son mucho más importantes que la tecnología en sí misma, es duro reconocerlo, pero es así. De nada sirve que tengas el mejor producto del mundo mundial si no consigues que la gente lo utilice y se deje dinero, así de claro. Si tienes los conocimientos adecuados para mover el negocio, no te preocupes, la tecnología te la aporta cualquier partner por un módico precio, pero poner en manos de otro toda la estrategia de ventas de tu negocio no está tan claro ¿no?.

Espero que os sirva de algo el artículo. He querido mostrar, fundamentalmente, cómo utilizando algunas librerías que puedes obtener sin coste puedes hacer algo realmente útil y funcional con muy poco esfuerzo. Seguro que sacáis alguna idea.

Perdón por el rollo 😛 , al final me ha costado mucho más escribir el artículo que implementarlo.

Podíamos haber añadido una opción que he visto por ahí que consiste en crear una cuenta automáticamente cada vez que entra un email para una cuenta que no existe, pero estaríamos creando cuentas para todo el spam que recibamos, así que prefiero no hacerlo. Si quisierais hacerlo creo que ya sabéis cómo.

Usando Amazon S3 como sistema de backup

Estoy tratando de mejorar el sistema de backups que utilizo habitualmente usando Amazon S3 como plataforma de almacenamiento. S3 (así como otros servicios Cloud de Amazon) tienen dos ventajas importantes, por un lado la disponibilidad, del orden del 99,99% y por otro el precio, altamente competitivo, cobran por espacio utilizado y por peticiones realizadas pero aún así sale por un precio espectacular.

Nuestro backup es del orden de 40gb, y, si el sistema va bien, planeo utilizarlo también para backups personales (fotos sobre todo :P).

El requerimiento básico es que la sincronización se realizará de manera automatizada desde un servidor Linux con lo que el sistema debe permitir el envío desatentido.

Buscando por ahí como hacerlo encontré FuseOverAmazon, un sistema basado en Fuse que permite montar un “bucket” de S3 como si fuese una unidad local y sobre la que posteriormente podríamos utilizar rsync. ¿Qué más se puede pedir? Dicho y hecho, vamos a probarlo. En mi caso utilizo CentOS.

yum install fuse fuse-devel curl-devel libxml2-devel
wget http://s3fs.googlecode.com/files/s3fs-r191-source.tar.gz
tar xvfz s3fs-r191-source.tar.gz
cd s3fs
make
make install

Vamos a probarlo.

/usr/bin/s3fs nombrebucket -o accessKeyId=TUACCESSKEYID -o secretAccessKey=TUSECRETKEY /mnt/s3

Si todo ha ido bien tendrás montado en /mnt/s3 tu “nombrebucket” y podrás listar los archivos, copiar, eliminar, etc., como si fuese una unidad del equipo. Hasta aquí todo ha ido bien.  Sólo nos queda sincronizar nuestro backup:

/usr/bin/rsync -avz --delete /usr1 /mnt/s3

Y aquí es donde viene el problema. En mi caso han pasado 4 días y aún no ha pasado del 10% de la sincronización, funciona todo bien pero la sincronización es extremadamente lenta, no sé si estoy haciendo algo mal, si es normal, o no, pero es imposible de utilizar así.

Como la idea no ha sido del todo buena, tenemos un plan B. Se trata de utilizar s3sync, un script en Ruby que hace el proceso muy sencillo, sólamente hay que configurarlo indicando tus datos de acceso y a funcionar:

s3sync -r /mnt/backup nombrebucket:prefijo

Donde “prefijo” puede ser nulo.

Esto enviará a nombrebucket/prefijo/ tu backup.  De momento las pruebas son mucho más satisfactorias que con s3fs, la velocidad se puede considerar más que adecuada, sobre todo comparada con el anterior.

Como decía, de momento estoy probando el rendimiento y la velocidad, pero no estoy del todo convencido, así que estoy pensando en utilizar Amazon EC2 en vez de S3, de manera que lanzando una instancia de una máquina virtual pueda hacer un rsync clásico contra un sistema de ficheros de verdad. La ventaja es que la máquina virtual puedo lanzarla sólo cuando la necesite y pararla después, con lo que con una hora diaria podría ser suficiente, recordemos que Amazon EC2 cuesta, entre otras cosas, por cada hora que utilizas la instancia. Adicionalmente se podría hacer después un volcado del backup desde EC2 a S3, pero en nuestro caso los 40gb sería una limitación que encarecería el precio considerablemente aún en el caso de utilizar rotaciones semanales.

Ya os contaré :P.

De artículo a proyecto explicado paso a paso: acorta URLs con IraUrl.me

Hace un par de semanas me encontré con la necesidad de utilizar uno de esos sistemas que hay por ahí para acortar URL‘s. Necesitaba enviar una dirección por SMS y tenía que ocupar la menor cantidad de caracteres posible por aquello de optimizar el texto del mensaje. Mientras lo utilizaba pensaba en lo ingenioso de utilizar un sistema de numeración base36 para reducir exponencialmente el número de caracteres de la redirección. Esto iba a ser, pues, un artículo sobre las ventajas de los sistemas de numeración distintos al decimal para determinados proyectos, pero se acabó convirtiendo en un proyecto completo. Cuando estaba comenzando la explicación teórica pensé, ¿por qué no hacerlo? ¿por qué no demostrar lo rápido y fácil que se puede montar algo en Internet hoy en día?

Así, tras unas 15 horas de trabajo os presento IraUrl.me, un acortador de URL’s al estilo de TinyUrl o Bit.ly. Me ha costado más escribir el artículo que hacerlo realidad, curioso ¿eh?. En realidad a medida que iba preparando la aplicación se me iban ocurriendo más cosas que sería interesante montar, por lo que las 8 horas iniciales, más o menos, se convirtieron en 15.

iraurl

La teoría

Para el que no lo sepa, un acortador de URL se basa en encontrar un dominio lo más corto posible y crear redirecciones HTTP 301 a otras URL‘s. El truco está en optimizar los parámetros que añadiremos a la URL para que sean lo más cortos posible, no queremos que éstos nos penalicen lo corto del dominio.

¿Cómo funcionan entonces estos acortadores de URL‘s? Mucho más fácil de lo que parece y seguramente como a ti se te habría ocurrido. Simplemente tenemos una base de datos donde vamos añadiendo registros a una tabla a medida que se van creando nuevas URL’s cortas.  Esta tabla tiene un campo autonumérico, la clave de la tabla, que para cada nueva URL nos devuelve un identificador único, con lo que cada dirección podría ser accesible de la manera habitual:

http://dominio.com/1

http://dominio.com/1000000

Esa es exactamente la idea, lo único que hacemos es cambiar el identificador en cuestión de base10 (la de nuestro sistema métrico decimal) a base36 o base62 en mi caso. Otros sistemas de numeración conocidos son el hexadecimal (base16) y base64.

Vale, ya has hablado en chino. ¿De qué va esto? Veamos.

Sobre bases de numeración

El sistema decimal utiliza diez dígitos (de ahí lo de decimal :P) para formar todas las combinaciones de números posibles. Lo que ya conocemos, vamos. El binario utiliza dos dígitos (0 y 1), el hexadecimal 16 (0..9ABCDE), base36, como su nombre indica, treinta y seis (0..9a..z) y base62 utiliza los 62 dígitos que comprenden los números del 0 al 9 y las letras de la A a la Z en mayúsculas y minúsculas (0..9a..zA..Z). Veamos unos ejemplos:

Binario Decimal Hexadecimal Base36 Base62
0 0 0 0 0
1 1 1 1 1
10 2 2 2 2
1010 10 A a a
1100100 100 64 2s 1c
1000000 F4240 lfls 4c92
10000000 989680 5yc1s FXsk

Se puede observar de un vistazo cómo a medida que aumenta el número, cuanto mayor sea la base que manejamos menos dígitos tendrá . Los números, a fin de cuentas, son combinaciones continuas entre todos los dígitos posibles.Así, en función de la base y del número de dígitos, el mayor número representable representable sería:

Num. dígitos
Decimal Base62
1 10 62
2 100 3844
3 1000 238328
4 10000 14776336
5 100000 916132832
6 1000000 56800235584
7 10000000 3521614606208
8 100000000 218340105584896
9 1000000000 13537086546263552

O lo que es lo mismo, base(número de dígitos), 629 contra 109.Espero que se entienda la teoría. Como curiosidad:

Decimal: 10000000000000000000000

Base62: 36aHo5IWaicak

La pregunta ahora sería, ¿Por qué Base62 y no Base64, por ejemplo, mucho más conocida? Sencillo, porque además de los 62 caracteres de Base62, Base64 utiliza dos adicionales, generalmente + y / además del =, lo que convierten la cadena en no web safe, es decir, los caracteres especiales debieran traducirse para que su transporte no diese problemas, con lo que estaríamos perdiendo las ventajas de nuestro cifrado corto. Los 62 caracteres utilizados en Base62 son totalmente seguros, sólo letras (mayúsculas y minúsculas) y números.

Sabiendo ya cómo funciona el sistema, veremos cómo crear nuestra aplicación. Obviamente no contaré todo paso a paso ya que sino tardaría mucho más en escribir el artículo que en hacer la aplicación, me meteré sólo en las cosas que considere más importantes.

Para codificar/decodificar de base10 a base62 utilizaré estas librerías:

function dec2base($dec, $base, $digits = FALSE) {
      if($base < 2 or $base > 256) {
            die("Invalid Base: .$basen");
      }
      bcscale(0);
      $value = '';
      if(!$digits) {
            $digits = digits($base);
      }
      while($dec > $base - 1) {
            $rest = bcmod($dec,$base);
            $dec = bcdiv($dec,$base);
            $value = $digits[$rest].$value;
      }
      $value=$digits[intval($dec)].$value;
      return (string) $value;
}

function base2dec($value, $base, $digits = FALSE) {
      if($base < 2 or $base > 256) {
            die("Invalid Base: .$basen");
      }
      bcscale(0);
      if($base < 37) {
            $value = strtolower($value);
      }
      if(!$digits) {
            $digits = digits($base);
      }
      $size = strlen($value);
      $dec = '0';
      for($loop=0; $loop < $size; $loop++) {
            $element = strpos($digits, $value[$loop]);
            $power = bcpow($base, $size-$loop-1);
            $dec = bcadd($dec, bcmul($element, $power));
      }
      return (string)$dec;
}

function digits($base) {
      if($base < 64) {
            return substr('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_', 0, $base);
      } else {
            return substr("x0x1x2x3x4x5x6x7x8x9xaxbxcxdxexfx10x11x12x13x14x15x16x17x18x19x1ax1bx1cx1dx1ex1f !x22#x24%&'()*+,-./0123456789:;<=>x3f@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~x7fx80x81x82x83x84x85x86x87x88x89x8ax8bx8cx8dx8ex8fx90x91x92x93x94x95x96x97x98x99x9ax9bx9cx9dx9ex9fxa0xa1xa2xa3xa4xa5xa6xa7xa8xa9xaaxabxacxadxaexafxb0xb1xb2xb3xb4xb5xb6xb7xb8xb9xbaxbbxbcxbdxbexbfxc0xc1xc2xc3xc4xc5xc6xc7xc8xc9xcaxcbxccxcdxcexcfxd0xd1xd2xd3xd4xd5xd6xd7xd8xd9xdaxdbxdcxddxdexdfxe0xe1xe2xe3xe4xe5xe6xe7xe8xe9xeaxebxecxedxeexefxf0xf1xf2xf3xf4xf5xf6xf7xf8xf9xfaxfbxfcxfdxfexff", 0, $base);
      }
}

function base_encode($value) {
      return dec2base(base2dec($value, 256), 62);
}

function base_decode($value) {
      return dec2base(base2dec($value, 62), 256);
}

Las dos últimas funciones son las que utilizaremos para las conversiones.

Paquetes y librerías utilizadas:

  • Free CSS Templates: Para tener una bonita plantilla xhtml para nuestro proyecto 🙂 .
  • Maxmind GeoLite Country: Para la geolocalización de un usuario a través de su IP.
  • Wurfl: Para identificar el navegador/terminal de un visitante por su User Agent. Yo lo complemento con Tera-Wurfl para mantener la información en una base de datos.
  • Fusion Charts Free: Para los gráficos de estadísticas.
  • Zero Clipboard: Para copiar al portapapeles la url corta generada sin que el usuario deba seleccionarla, solo con un click.
  • JqueryUI: Para el componente de navegación con pestañas.
  • Google Safebrowsing API: Para comprobar si una url es potencialmente peligrosa.
  • Adodb (opcional): Para abstraer el acceso a la base de datos. Yo suelo utilizarla en todos mis proyectos pero se pueden utilizar las funciones nativas de PHP.
  • PHPExcel: Para generar Excel y PDF.

Adicionalmente:

  • Un dominio y hosting donde alojarlo (6€).
  • PHP y MySQL (tampoco son obligatorios, puedes hacerlo con cualquier tecnología).
  • 15 horas de tu tiempo :P.

Estructura de la web

Cualquier proyecto web que se precie debe comenzarse describiendo qué queremos mostrar a nuestros visitantes, hay que recopilar todas las ideas, decidir las que interesan de verdad, estudiar cómo se van a disponer en el frontend y terminar con un mapa web que nos indique el flujo a seguir en el trabajo. Este será el nuestro:

mapaWebIraUrlCuando un usuario vaya a una de nuestras url’s cortas en realidad estaremos reenviando la petición http internamente a un script encargado de hacer todo el proceso, link.php en mi caso.

Para ver las estadísticas de una URL me ha gustado el sistema de bit.ly, así que se lo copiamos :P. Añadiendo un “+” al final de la URL corta, en vez de saltar a la dirección larga mostraremos las estadísticas. Esto lo haremos, como en el caso anterior, dirigiendo internamente a otro script, stats.php.

Si el identificador que pretendemos usar para saltar a la url larga o ver estadísticas no existe, reenviaremos a index.php para que muestre un mensaje de error tipo “La url no existe“.

El dominio

Obviamente tendremos que buscar un dominio lo más corto posible, la mayoría estarán ya ocupados, pero buscando y buscando en TLD‘s extraños puedes encontrar algo. Yo he escogido un .me porque tiene un carácter menos que un .com 🙂 y no cuesta lo que un .es :P.

La base de datos

Muy sencilla, dos tablas solamente, en una mantendremos las urls generadas y en otra las estadísticas de acceso a las mismas.

CREATE TABLE IF NOT EXISTS `urls` (
  `id` bigint(20) NOT NULL auto_increment,
  `url` varchar(500) NOT NULL,
  `titulo` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

CREATE TABLE IF NOT EXISTS `stats` (
`id` bigint(20) NOT NULL auto_increment,
`idurl` bigint(20) NOT NULL,
`codpais` varchar(255) NOT NULL,
`referer` varchar(255) NOT NULL,
`hostreferer` varchar(255) NOT NULL,
`ua` varchar(255) NOT NULL,
`hora` datetime NOT NULL,
`pais` varchar(255) NOT NULL,
`marca` varchar(255) NOT NULL,
`modelo` varchar(255) NOT NULL,
PRIMARY KEY  (`id`),
KEY `idurl` (`idurl`,`hora`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1;

La configuración de Apache

Como hemos comentado queremos que cuando alguien vaya a cualquier url de nuestro site del tipo

http://tudominio.com/prueba3

Se redirija internamente al script link.php que será el encargado de procesar la petición. De igual modo si alguien visita

http://tudominio.com/prueba3+

le mostraremos las estadísticas de esa URL (si existen). Configuramos Apache para que tenga en cuenta todas estas particularidades, mod_rewrite será nuestro amigo para conseguirlo. En mi caso he hecho que si la llamada no es un script php, ni una imagen ni un archivo javascript ni un css ni tiene el signo “+“, se vaya a link.php. Si tiene el signo “+” se irá a stats.php.

RewriteEngine on
RewriteCond %{REQUEST_URI} !^/(.*).php$
RewriteCond %{REQUEST_URI} !^/css/(.*)$
RewriteCond %{REQUEST_URI} !^/js/(.*)$
RewriteCond %{REQUEST_URI} !^/(.*)+$
RewriteCond %{REQUEST_URI} !^/images/(.*)$
RewriteRule ^(.+) /link.php?id=$1
RewriteCond %{REQUEST_URI} ^/(.*)+$
RewriteRule ^(.+) /stats.php?id=$1

Imagen y diseño

Para el diseño, o te lo haces tú mismo o si eres un negado creativo como yo te descargas una plantilla superchula de freecsstamplates.org y la adaptas a tus necesidades, no voy a entrar en más detalles.

Crear urls cortas

El primer script de nuestra aplicación. Un sencillo formulario donde el usuario introduce la URL que quiere acortar y al hacer submit… la acortamos integrando el sistema con la comprobación de malware que explicaba hace unos días ;).

$urlbase="";
if(isset($_POST['url'])){
        $url=$_POST['url'];

        try{
	        if(substr($url, 0, 4)!="http")
	        	$url="http://".$url;
			$class = new GoogleSafeBrowsing('ABQIAAAAQYvf-54bCBMuGY20SeONVxQ236Mc_IjryQBl-W_Repaw3fCykA', true);
			$nomalware=$class->lookupsFor($url);

			if($nomalware){
        			$htmltitle="";
        			$html=file_get_contents($url);
        			if($html!=""){
        				preg_match('/(.*)</title>/is', $html, $matches);
					if(is_array($matches) && count($matches>0))
						$htmltitle=trim($matches[1]);
        			}
                		$query="select * from urls where url='$url'";
				$rs=$conn->Execute($query);
				if($rs->recordcount()>0){
					$id=$rs->fields['id'];
				}else{
					$query="insert into urls (url, titulo) VALUES ('$url', '$htmltitle')";
					$rs=$conn->Execute($query);
					$id=$conn->insert_id();
				}
				$base=base_encode($id);
				$urlbase="http://iraurl.me/".$base;
			}else{
				$err=4;
			}
        }catch(exception $e){
        	$err=3;
        }
}

Hemos añadido una pequeña comprobación. Si la URL que se quiere añadir ya existe, devolvemos la misma URL corta, yo he tomado esa decisión, tú puedes hacer lo que quieras. Además obtenemos el título de la URL final para tener una referencia hacia la misma, cuestión de sencillez visual :P.

Reenvío a urls largas

Ya tenemos nuestra URL corta, vamos ahora a reenviar las solicitudes a ella a la larga. Recordemos que nuestro Apache nos va a redirigir esa petición a link.php?id=XXXX. Nuestro script actualiza, además, las estadísticas de visitas de la url.

if(isset($_GET['id'])){
	$idb=$_GET['id'];
	$id=base_decode($idb)+0;
	try{
		$query="select * from urls where id=$id";
		$rs=$conn->Execute($query);
		if($rs->recordcount()>0){
			$url=$rs->fields['url'];
			$referer=@$_SERVER['HTTP_REFERER'];
			$ua=@$_SERVER['HTTP_USER_AGENT'];
			$ip=@$_SERVER['REMOTE_ADDR'];

			$hostreferer="";
			if(preg_match('@^(?:http://)?([^/]+)@i', $referer, $matches)>0)
				$hostreferer = $matches[1];

			$terminal=getMarcaModelo($_SERVER['HTTP_USER_AGENT']);
			$marca=$terminal['marca'];
			$modelo=$terminal['modelo'];

			$temp=getGeoCodeAndPais($ip);
			$codpais=$temp['code'];
			$pais=$temp['pais'];

			$query="insert into stats (idurl, codpais, referer, ua, hora, pais, marca, modelo, hostreferer) VALUES
					($id, '$codpais', '$referer', '$ua', now(), '$pais', '$marca', '$modelo', '$hostreferer')";
			$rs2=$conn->Execute($query);

			header("HTTP/1.x 301 Moved");
			header("Location: $url");
			exit;
		}else{
			header("Location: http://iraurl.me/index.php?err=1");
			exit;
		}
	}catch(exception $e){
		header("Location: http://iraurl.me/index.php?err=2");
		exit;
	}
}
header("Location: http://iraurl.me/index.php?err=1");

Como veis, si la URL no existe redirigimos al usuario a index.php con un mensaje de error. Necesitaremos dos funciones adicionales, las que nos devuelven información del país de origen de una IP y los datos del terminal del usuario (móvi o web). No entraré en detalles sobre la instalación de Maxmind GeoLite Country o de Wurfl/Tera-Wurfl.

function getGeoCodeAndPais($ip){
	require_once(dirname(__FILE__)."/geoip/geoip.inc");
	$gi = geoip_open("/usr/share/GeoIP/GeoIP.dat",GEOIP_STANDARD);
	$codpais=geoip_country_code_by_addr($gi, $ip);
	$pais=geoip_country_name_by_addr($gi, $ip);
	geoip_close($gi);
	return array("pais"=>$pais, "code"=>$codpais);
}

function getCapabilities($ua){
	require_once(dirname(__FILE__)."/Tera-WURFL/TeraWurfl.php");
	$wurflObj = new TeraWurfl();
	$matched = $wurflObj->GetDeviceCapabilitiesFromAgent($ua);
	$movil = $wurflObj->capabilities;

	return $movil;
}

Estadísticas

La teoría es la misma. Si existe la URL cargamos los datos, si no redirigimos a la home. En nuestra caso utilizamos el componente de pestañas de JqueryUI para organizar los distintos tipos de datos que permitiremos ver y añadiremos los botones para exportar a Excel y PDF.

$idb=substr($_GET['id'], 0, strlen($_GET['id'])-1);

$id=base_decode($idb)+0;
$query="select * from urls where id=$id";
$rs=$conn->Execute($query);
if($rs->recordcount()>0){
	$urlbase="http://iraurl.me/".$idb;
	$url=$rs->fields['url'];
	$id=$rs->fields['id'];
	$htmltitulo=$rs->fields['titulo'];
	if($htmltitulo=="")
		$htmltitulo=$url;
	$query="select count(*) as nregs from stats where idurl=$id";
	$rs=$conn->Execute($query);
	$clicks=$rs->fields['nregs'];
}else{
	header("Location: http://iraurl.me/index.php?err=1");
	exit;
}

Muy sencillo.

stats

Lo complicado en este caso es mostrar las gráficas con FusionCharts. Para cada una debemos añadir algo de código html:

<div id="chartClicks"></div>
<script type="text/javascript">
 var myChart = new FusionCharts("images/Charts/FCF_Column3D.swf", "idChartClicks", "430", "400", "0", "1");
 myChart.setDataURL(escape("xml.php?t=cli&id='.$idb.'"));
 myChart.setTransparent(true);
 myChart.render("chartClicks");
</script>

El script xml.php será el que devuelva los datos en el formato adecuado para FusionCharts. Por ejemplo:

$query="select DAY(hora) as dia, MONTH(hora) as mes, YEAR(hora) as ano, count(*) as nclicks
 from stats
 where idurl=$id
 group by ano, mes, dia
 order by hora";
$rs=$conn->Execute($query);

$xml='<graph caption="Clicks" rotateNames="1" xAxisName="Día" yAxisName="Clicks" showNames="1" decimalPrecision="0" formatNumberScale="0" chartLeftMargin="5" chartRightMargin="5" chartTopMargin="0">';
while($r=$rs->fetchrow()){
 $xml.='<set name="'.$r['dia']."/".$r['mes']."/".$r['ano'].'" value="'.$r['nclicks'].'" color="#A1A1A1" />';
}
$xml.='</graph>';

Os doy sólo un ejemplo, el resto lo montáis por vuestra cuenta :).

Descifrar urls cortas

Todos los sistemas de acortar URL’s funcionan tal y como cuento en este artículo, haciendo un HTTP/301 redirect hacia la url original.

A partir de la URL corta podemos saber cual es la URL original simplemente siguiendo las redirecciones que hace. Muy sencillo con PHP y que, además nos sirve para, integrándola en nuestra API de malware, prevenir posibles problemas con la URL final.

function get_web_page( $url )
{
    $options = array( 'http' => array(
        'user_agent'    => 'spider',
        'max_redirects' => 10,
        'timeout'       => 120,
    ) );
    $context = stream_context_create( $options );
    $page    = @file_get_contents( $url, false, $context );

    $result  = array( );
    if ( $page != false )
        $result['content'] = $page;
    else if ( !isset( $http_response_header ) )
        return null;    // Bad url, timeout

    // Save the header
    $result['header'] = $http_response_header;

    // Get the *last* HTTP status code
    $nLines = count( $http_response_header );
    for ( $i = $nLines-1; $i >= 0; $i-- )
    {
        $line = $http_response_header[$i];
        if ( strncasecmp( "HTTP", $line, 4 ) == 0 )
        {
            $response = explode( ' ', $line );
            $result['http_code'] = $response[1];
            break;
        }
    }

    return $result;
}

$url="";
if(isset($_POST['url'])){
	$url=$_POST['url'];
	$datos=get_web_page( $url );
	if($datos){
		$headers=$datos['header'];
		$urls=array($url);
		foreach($headers as $head){
			$temp=explode(" ", $head);
			if(strtolower($temp[0])=="location:"){
				$urls[]=$temp[1];
			}
		}
		$htmltitle="";
	    	preg_match('/(.*)</title>/is', $datos['content'], $matches);
		if(is_array($matches) && count($matches>0))
			$htmltitle=trim($matches[1]);
	}
}

Ya está, en $urls tendremos la lista de urls que van saltando hasta llegar a la final.

descifrar

Api

Hoy en día todo tiene que tener Api. Para las estadísticas es muy sencillo, el propio XML que generamos para consumir con FusionCharts nos permite que clientes externos se alimenten del mismo. Para crear URL‘s cortas remotamente, simplemente creamos un archivo api.php:

if(isset($_GET['url'])){
	$url=urldecode($_GET['url']);
	try{
        $htmltitle="";
        if(substr($url, 0, 4)!="http")
        	$url="http://".$url;
        $html=file_get_contents($url);
        if($html!=""){
        	preg_match('/(.*)</title>/is', $html, $matches);
			if(is_array($matches) && count($matches>0))
				$htmltitle=trim($matches[1]);
        }
        $query="select * from urls where url='$url'";
        $rs=$conn->Execute($query);
        if($rs->recordcount()>0){
                $id=$rs->fields['id'];
        }else{
                $query="insert into urls (url, titulo) VALUES ('$url', '$htmltitle')";
                $rs=$conn->Execute($query);
                $id=$conn->insert_id();
        }
        $base=base_encode($id);
        $urlbase="http://iraurl.me/".$base;
        echo $urlbase;
    }catch(exception $e){
   		echo "ERROR";
    }
}

Eso es todo. No olvides integrarlo también con el sistema de malware.

Conclusiones

Bueno, y todo este rollo ¿para qué?. Pues muy sencillo, para que veais que hoy en día la tecnología está al alcance de todos, es sencillo y rápido crear un proyecto en Internet, hay de todo por todas las esquinas, la tecnología no es lo importante, lo que verdaderamente cuenta es cómo mueves ese producto tecnológico para rentabilizarlo y obtener un beneficio de él.

Ya tengo mi proyecto superchulo funcionando, sólo me ha costado unas 15 horas de trabajo. Le he puesto un poco de Adsense por aquí y por allí. ¿Y ahora qué? ¿A esperar a que la gente entre y me haga millonario? 😛 Es mucho más complicado que eso como todos sabéis, primero tienes que tener una masa de usuarios elevada que le dé movimiento al proyecto y después tienes que conseguir que la mayoría de ellos sean gente normal, no gente técnica, usuarios avanzados que no pagamos por nada ni pinchamos en publicidad :P.

Hoy en día, en Internet, como en cualquier negocio, las técnicas de marketing y venta son mucho más importantes que la tecnología en sí misma, es duro reconocerlo, pero es así. De nada sirve que tengas el mejor producto del mundo mundial si no consigues que la gente lo utilice y se deje dinero, así de claro. Si tienes los conocimientos adecuados para mover el negocio, no te preocupes, la tecnología te la aporta cualquier partner por un módico precio, pero poner en manos de otro toda la estrategia de ventas de tu negocio no está tan claro ¿no?.

Espero que os sirva de algo el artículo. He querido mostrar, fundamentalmente, cómo utilizando algunas librerías que puedes obtener sin coste puedes hacer algo realmente útil y funcional con muy poco esfuerzo. Seguro que sacáis alguna idea.

Perdón por el rollo :P, al final me ha costado mucho más escribir el artículo que implementarlo.

Google Safe Browsing API con PHP, filtra las urls potencialmente peligrosas

Preparando un pequeño nuevo proyecto del que os hablaré en mi siguiente artículo, tuve un problema que me llevó a escribir este otro. En un momento de la aplicación el usuario debe escribir una url (el resto es secreto aún :P). Pues bien, ya durante las pruebas aparecieron los típicos graciosos que utilizan url’s potencialmente peligros, de esas que no hacen cosas nada buenas. Buscando un poco dí con Google Safe Browsing API, el sistema que Google pone a nuestra disposición para comprobar contra sus sistemas si tienen una determinada url recogida dentro de su base de datos de urls peligrosas, la misma que utilizan ellos para lanzar a veces esos avisos de “¿seguro que quieres ver esto?”.

Esta API funciona de modo diferente a la mayoría de aplicaciones de Google que residen directamente en la nube y lanzamos consultas contra su API. En este caso nos provee de su base de datos de urls potencialmente peligrosas para que las guardemos localmente y hagamos las consultas directamente en nuestros sistemas, lo único que debemos hacer es actualizar periódicamente esos datos, eso sí, con algunas limitaciones que nos impone Google, como que no se pueden renovar en intervalos inferiores a 30 minutos. Actualmente esta base de datos tiene más de trescientos mil registros.

Vamos a explicar como utilizarla. Lo primero que debemos hacer es, como en todo lo que hace Google, crear una “API KEY” y una sencilla tabla en nuestro servidor MySQL donde alojaremos la información que nos envíe Google.

CREATE TABLE IF NOT EXISTS `malware` (
`malware_hash` varchar(32) NOT NULL,
PRIMARY KEY  (`malware_hash`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

Creamos ahora una tarea que se ejecutará periódicamente y que leerá, siguiendo las especificaciones adecuadas, los datos que devuelve Google y los insertará/eliminará de nuestra base de datos. Yo, como ya he comentado en otras ocasiones, utilizo Adodb para el acceso a datos.

include(dirname(dirname(__FILE__))."/includes/funciones.inc.php");

$api_key = "TU KEY";
$google_url = "http://sb.google.com/safebrowsing/update";

//open the remote URL
$target = "$google_url?client=api&apikey=$api_key&version=goog-malware-hash:1:-1,goog-black-hash:1:-1";

$handle = fopen("$target", 'r')
	or die("Couldn't open file handle " . $target);
if ($handle) {
    while (!feof($handle)) {
        $line = fgets($handle);
        if (substr($line,0,1) != '[') {
        	$operation = (substr($line,0,1)); //get the '+' or '-'
        	$hash = trim(substr($line,1)); //get the md5 hash
        	$hash = qstr($hash); //escape
        	if ($operation == '+'){
        		$sql = "insert ignore into malware (malware_hash) VALUES ('".$hash."')";
        		$conn->Execute($sql);
        	}else{
        		$sql = "delete from malware where malware_hash = '".$hash."'";
        		$conn->Execute($sql);
        	}
        }
    }
    fclose($handle);
}

Lo que hacemos simplemente es eliminar los hash que nos indica que debemos quitar y añadir los nuevos. Vamos a probar el sistema. Utilicé varias clases y sistemas que había por ahí para utilizar con PHP pero ninguno me parecía lo suficientemente completo hasta que encontré este. Su funcionamiento es muy sencillo. De nuevo está hecho usando Adodb, cámbialo según tus requerimientos.

$class = new GoogleSafeBrowsing('tu_key', true);
$safe=$class->lookupsFor("http://blog.osusnet.com");

Nos devolverá un booleano que indica si la url es segura o no. Si probamos con alguna de éstas obtendremos un bonito false :). Espero que os sirva.