Archive for Marzo, 2007

Seguridad en PHP: primeros pasos

Lunes, Marzo 26th, 2007

El primer paso por la seguridad en PHP es simplemente haber pensado un momento en que se debe gestionar la seguridad. El segundo pasa por una simple auditoría de nuestras vulnerabilidades. Pero ¿como podemos saber si nuestra plataforma PHP es vulnerable a ataques?.

Bueno, pues es algo tan simple como usar el proyecto phpsecinfo (licencia BSD), una pequeña auditoría de seguridad en formato phpinfo. Para poder usarlo no tenemos más que descargarlo de la web del proyecto, descomprimirlo y consultar en el navegador la dirección donde fue extraido.

(more…)

Como crear tu propio buscador.
Instalación y configuración de SPHINX ( II )

Viernes, Marzo 23rd, 2007

Continuando el post de instalación de sphinx vamos a ver un ejemplo práctico, que es lo que estamos desenado todos.

He descargado los RFC desde la web del IETF con un script en PHP y algo de expresiones regulares, e insertados en una tabla. En total son unos 4400 documentos con mucho texto para juguetear.

La base de datos tiene una sola tabla bastante simple, aquí está el código SQL para crearla:

SQL:

  1. CREATE TABLE `rfc_en` (
  2.   `id` int(11) NOT NULL AUTO_INCREMENT,
  3.   `titulo` varchar(255) collate latin1_spanish_ci NOT NULL,
  4.   `autor` varchar(255) collate latin1_spanish_ci NOT NULL,
  5.   `enlace` varchar(255) collate latin1_spanish_ci NOT NULL,
  6.   `contenido` mediumblob NOT NULL,
  7.   `fecha` date NOT NULL,
  8.   PRIMARY KEY  (`id`),
  9.   KEY `fecha` (`fecha`)
  10. ) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COLLATE=latin1_spanish_ci AUTO_INCREMENT=1;



El archivo de configuracion se va a mostrar por partes.

Apartado source

CODE:

  1. source rfcsrc
  2. {
  3.         type            = mysql
  4.         sql_host      = host
  5.         sql_user      = miuser
  6.         sql_pass     = mipass
  7.         sql_db         = rfc
  8.         sql_port       = 3306 
  9.  
  10.         sql_query_pre       =
  11.         sql_query              = \
  12.                SELECT id, titulo FROM rfc_en
  13.         sql_query_post     =
  14. }
  15.  
  16. source rfc2src : rfcsrc
  17. {
  18.         sql_query       =\
  19.                 SELECT id, titulo,contenido FROM rfc_en
  20. }



Se han definido dos índices, en uno se va a tener en cuenta el título de los RFC y en el otro vamos a hacer un poco el bruto indizando todo el contenido del RFC (llegan a ser 300KB o más) además del título. En el primer source se ve la información de conexión a base de datos y la sentencia sql de construcción del índice.

La sentencia se construye igual que cualquier SELECT de SQL pero con la peculiaridad de que el primer campo a recoger debe ser la clave primaria (recordar que esta DEBE ser un entero único de 32 bits). Todos los campos que aparezcan a continuación serán utilizados para el índice. De momento solo usaremos datos de texto, más adelante se detallará como introducir otro tipo de campos para búsquedas más avanzadas.

El segundo source esta definido mediante una herencia del primero, lo cual nos evita escribir 10 líneas ( los desarrolladores somos vagos y podemos estar un día escribiendo un script para evitar repetir la misma acción 2 veces :D ). Solo sobreescribe la sentencia SQL y se mantiene el resto.

Apartado index

CODE:

  1. index rfc1
  2. {
  3.         source                = rfcsrc
  4.         stopwords           =
  5.         min_word_len     = 1
  6.         charset_type       = sbcs
  7.         path                    = ruta-de-instalacion/var/data/rfc1
  8.         morphology        = none
  9. }
  10.  
  11.  
  12. index rfc2 : rfc1
  13. {
  14.         source                = rfc2src
  15.         path                    = ruta-de-instalacion/var/data/rfc2
  16. }



Cada índice hace referencia a su fuente de datos definida más arriba.


Apartado indexer: simplemente forzamos a 32 megas el máximo usado por el indizador.

CODE:

  1. indexer
  2. {
  3.         mem_limit      = 32M
  4. }


Apartado search: se ponen las rutas de los log y el descriptor de proceso. Se define el puerto asociado al demonio y otros parámetros de control. Si no se está seguro de que poner en algun sitio, dejarlo tal cual viene en el fichero esqueleto.

La directiva address es un mecanismo de seguridad, podemos forzar al demonio a que solo conteste las peticiones dirigidas a cierta ip. Si tenemos el script php en la misma máquina que sphinx se puede restringir a la direccion de loopback 127.0.0.1 y nos cubriremos las espaldas por si legan peticiones externas. Si sphinx esta instalado en una máquina diferente al origen de la consulta, pero se dispone de red local se puede asociar el demonio a la ip privada en vez de la pública.

CODE:

  1. searchd
  2. {
  3.        
  4.         # address        = 127.0.0.1
  5.         # address        = 192.168.0.1
  6.         port                  = 3312
  7.         log                   = ruta-de-instalacion/var/log/searchd.log
  8.         query_log         = ruta-de-la-instalacion/var/log/query.log
  9.         read_timeout    = 5
  10.         max_children    = 30
  11.         pid_file              =ruta-de-instalacionvar/log/searchd.pid
  12.         max_matches   = 1000
  13. }


Con esto ya tenemos fichero de configuración listo.

Así que al lio, lanzamos el indexer:

$ bin/indexer --config etc/sphinx.conf --all
indexing index 'rfc'...
collected 4398 docs, 0.2 MB
sorted 0.0 Mhits, 100.0% done
total 4398 docs, 222430 bytes
total 0.297 sec, 749621.68 bytes/sec, 14821.90 docs/sec
indexing index 'rfc2'...
collected 4398 docs, 214.1 MB
sorted 27.6 Mhits, 100.0% done
total 4398 docs, 214094137 bytes
total 22.821 sec, 9381369.37 bytes/sec, 192.72 docs/sec

En var/data estarán ahora los dos índices generados y tendrán dos tamaños bastante diferentes: 200 KB frente a 68 MB. Hay que decir que los índices tan grandes pueden ser habituales pero, al contrario que este caso, lo serán por el hecho de que las tablas indizadas tengan una cantidad de registros bastante alta en vez de tener una media de 150 KB de texto por registro.

$ ls -lh var/data/
total 70M
-rw-r--r-- 1 root root 0 2007-03-23 20:49 rfc2.spa
-rw-r--r-- 1 root root 68M 2007-03-23 20:49 rfc2.spd
-rw-r--r-- 1 root root 79 2007-03-23 20:49 rfc2.sph
-rw-r--r-- 1 root root 1,1M 2007-03-23 20:49 rfc2.spi
-rw-r--r-- 1 root root 0 2007-03-23 20:49 rfc.spa
-rw-r--r-- 1 root root 203K 2007-03-23 20:49 rfc.spd
-rw-r--r-- 1 root root 62 2007-03-23 20:49 rfc.sph
-rw-r--r-- 1 root root 23K 2007-03-23 20:49 rfc.spi

Y ahora lanzamos el demonio searchd:

$ bin/searchd --config etc/sphinx.conf
Sphinx 0.9.7-RC2
Copyright (c) 2001-2006, Andrew Aksyonoff

using config file 'etc/sphinx.conf'...

Llegados a este punto ya estamos en posicion para empezar a lanzar consultas al demonio y para ello usaremos el API PHP.
Un poquito de código:

PHP:

  1. function querySPHINX($tag,$off,$contenido)
  2. {
  3.     $port = 3312;
  4.  
  5.         if($contenido)
  6.         {
  7.             $indice = "rfc2";
  8.         }
  9.         else
  10.         {
  11.          $indice = "rfc";
  12.         }
  13.     if ($off == "")
  14.     {
  15.         $off = 0;
  16.     }
  17.    
  18.     $cl = new SphinxClient ()
  19.     $cl->SetServer ( "localhost", $port );   
  20.     $cl->SetLimits ($off, 10 )
  21.     $cl->SetMatchMode ( SPH_MATCH_ALL );   
  22.     $resultado = $cl->Query ( $tag, $indice );
  23.     $num_encontrados = $resultado['total_found'];
  24.    
  25.     if($num_encontrados == 0)
  26.     {
  27.             $cl->SetMatchMode ( SPH_MATCH_ANY );
  28.             $resultado = $cl->Query ( $tag, $indice );
  29.             $num_encontrados = $resultado['total_found'];
  30.     }
  31.     return  array_keys($resultado['matches']);   
  32. }



Con esta función se hace una consulta muy simple basada en la keyword a buscar, el offset de resultados a devolver (para paginar) y un booleano para decidir si consultar el contenido o no. Primero creamos el objeto y se le asocia el servidor y puerto del demonio. A continuación se le indica que queremos obtener 10 resultados a partir del offset $off (0 por omisión), se selecciona el modo de consulta y se lanza la misma.

Los modos de consulta son:

  • SPH_MATCH_ALL: el documento devuelto debe contener todas las palabras de la búsqueda
  • SPH_MATCH_ANY: el documento devuelto debe contener alguna de las palabras de la búsqueda
  • SPH_MATCH_PHRASE: el documento devuelto debe contener todas las palabras de la búsqueda y en el mismo orden

La variable $resultado devuelve bastante información pero de momento nos quedamos con dos elementos:

  • $resultado['total_found'] : numero de resultados encontrados para la búsqueda
  • $resultado['matches'] : array con los identificadores de documento como índice. Por eso se recuperan las claves con array_keys



Espero que esta guía haya sido de utilidad y que la gente se anime a instalarlo y cacharrear un poco.

Podeis probar la demo. Me gustaría no estar en un hosting compartido ni tener un ping tal alto para que vierais que esto es realmente rápido. Si alguien quiere el ejemplo le puedo facilitar los scripts de construcción de la base de datos, el resto está practicamente en las entradas.

Actualizacion
He cambiado la demo de sitio, porque la gente de dreamhost me tiraba el sphinx (ya decia yo que era demasiado chollo para un plan cutre de hosting compartido instalar un servicio como si nada ). Ahora está en una máquina con buenos recursos. Así que ya no hay excusa, los tiempos deberían ser bastante buenos.

Enlaces de interés

Comparativa de sistemas de búsquedas FULLTEXT

Martes, Marzo 20th, 2007

Leyendo una entrada un poco antigua de MySQL Performance Blog me encuentro con un documento bastante interesante sobre comparativas de búsquedas de texto completo en MySQL hechas con diferentes sistemas: Lucene, Sphinx, TgSearch y el propio MySQL.

El documento en cuestión no es otro que High Performance Full Text Search for Database Content presentado en la EuroOSCON 2006.

De él extraigo estos gráficos que hablan por si solos (hacer click para ver en un tamaño decente).

Tiempo de ejecución de una consulta booleana

Comparativa Boolean Search



Tiempo de construcción del índice

Index Building Time



Tamaño del índice

Tamaño del índice



Tiempo de ejecución de consulta de tipo ‘phrase’'

Tiempo de ejecución de consulta de tipo ‘phrase’



Tiempo de ejecución de consulta normal

Tiempo de ejecución de consulta normal



Como siempre sacar los gráficos de contexto es algo muy feo, esto tiene muchos matices y por eso recomiendo la lectura completa del documento. Aún así, hay una cosa en común en todos los resultados y es que SPHINX barre con mucha diferencia a cualquiera de los otros sistemas.

Ya no teneis excusa para instalar SPHINX

Cómo crear tu propio buscador.
Instalación y configuración de SPHINX ( I )

Martes, Marzo 20th, 2007

Se va a explicar como se puede hacer un buscador realmente profesional en unos pocos pasos. Hasta ahora no existía ningun recurso en español de SPHINX así que me propuse acabar con esa injusticia.

¿Cómo es un buscador por dentro?

Un buscador web tiene generalmente los siguientes componentes:

  • Crawler: componente encargado de la recuperación de lás páginas web que se quieren procesar. Comienza descargando el código HTML de una página, extrae todos sus enlaces y a continuación repite el proceso con cada uno de ellos y asi sucesivamente. Requiere de una gran capacidad de alamacenamiento y uso de ancho de banda. También se les llama arañas web.
  • Indexer: es la parte que procesa toda la información recogida por el crawler y genera uno o varios índices.
  • Searcher: componente que consulta el índice y recupera la información resultante

Cuando uno piensa en un buscador web, de alguna manera piensa directamente en un buscador de páginas web, pero existe vida mucho más allá de estos. Hay muchos casos (en realidad la gran mayoría) en los que se tiene una gran cantidad de información en una base de datos y se necesita poder realizar búsquedas en ella. Es este el caso de los blogs, de los wikis, de cualquier sitio de comercio electrónico... De hecho, está guía se va a basar en que ya se dispone de una base de datos mediana y no se realiza ningun proceso de crawling.

¿Porqué SPHINX ?

Una vez aclarado que partimos de una base de datos, se podría pensar en hacer el buscador con las herramientas propias del SGBD en concreto. Pero la realidad es otra muy distinta, el rendimiento de las búsquedas de texto completo con las herramientas propias de MySQL deja muchísimo que desear cuando la base de datos crece.

Es en este punto, donde entra SPHINX (SQL Phrase Index ). SPHINX es un indexer/searcher escrito en C/C++ para bases de datos MySQL y PostgreSQL. Sus características más importantes son:

  • Velocidad de indización muy alta
  • Velocidad de búsqueda muy alta
  • Stemmers en inglés y ruso
  • Soporte para búsquedas booleanas
  • Algoritmo de relevancia de proximidad
  • Soporte de listas de palabras vacías
  • API de búsqueda en PHP y Phyton

Descarga

La versión 0.9.7-rc2 con la que se ha realizado este artículo se puede descargar de la web oficial del proyecto sphinx.

Aunque este manual se realizó con la version 0.9.7-rc2 se puede instalar sin ningún problema siguiendo los mismos pasos la version 0.9.7 que salió hace muy poquito. Descarga de aqui

Instalación

Requisitos

  • MySQL o PostgreSQL.
  • Un compilador de c++.
  • Un sistema operativo de estos: Linux 2.4.x, 2.6.x,Windows 2000, XP,FreeBSD 4.x, 5.x, 6.x,NetBSD 1.6
  • Herramienta estilo make

En esta guía se va a usar un MySQL 4.1 y la familia de compiladores gnu, sobre una Debian GNU/Linux 2.6.8.

Compilación

Se desempaqueta el tarball:

$ tar xvzf sphinx-0.9.7-rc2.tar.gz
$ cd sphinx-0.9.7-rc2

Se compila:

$ ./configure --prefix = /ruta-de-instalacion \
--with-mysql-includes=/usr/include/mysql/ \
--with-mysql-libs=/usr/lib
$ make
$ make install

La ruta de las librerias e includes de mysql diferirá según la forma en la que se haya instalado. El caso arriba expuesto es para un mysql-4.1 instalado como paquete debian.

Configuración

Una vez completada la instalación en la ruta elegida se tienen tres directorios:

  • bin: contiene los tres binarios.
  • etc : dónde están los ficheros de configuración
  • var: aquí están los logs y los índices

Fichero sphinx.conf

Hacemos una copia del fichero que viene como esqueleto de configuración:

$ cp etc/sphinx.conf.dist etc/sphinx.conf

Si vemos este fichero nos daremos cuenta de que tenemos cuatro elementos en él: index, source, indexer y searchd. A continuación se van a explicar las directivas simples que hacen que podamos realizar un primer buscador plenamente funcional. El resto se dejan para quien quiera juguetear un poco más.

indexer

  • mem_limit: limite de memoria impuesto al binario que realiza la indización. Tocarlo con cuidado

searchd

  • port: puerto en el que estará escuchando el demonio
  • log: para los mensajes de actividad del demonio (start,stop..)
  • query_log: para las queries que se lanzan al demonio
  • pid_file: para guardar el pid del demonio

Los otros dos elementos son los que definen los índices que se quieren crear. El source representa una fuente de datos

source

  • sql_host: nombre de la máquina donde se encuentra mysql
  • sql_user: usuario de acceso a la base de datos
  • sql_pass: contraseña de acceso para el usuario indicado antes
  • sql_db: nombre de la base de datos
  • sql_port: puerto de mysql (generalmente es 3306)
  • sql_query: query de recogida de datos

La directiva sql_query funciona prácticamente como una query SQL normal y corriente con ciertas peculariedades: el primer campo de la sentencia SELECT debe ser la clave primaria (y ojo, esta debe ser un entero único de 32 bits por narices) y a continuación se ponen los campos (de texto) que queremos que se indexen.

Ejemplo de SQL serían:

SQL:

  1. SELECT id,campo_texto1,campo_texto2 FROM tabla;
  2. SELECT id,campo_texto1,campo_texto2 FROM tabla WHERE int1=1;
  3. SELECT id,campo_texto1,campo_texto2 FROM tabla WHERE int=1 AND fecha < NOW();

No hay problema alguno por usar WHERE ni funciones.

index

  • source: nombre del elemento fuente asociado
  • path: ruta al fichero índice (tipicamente bajo directorio var/data)
  • morphology: tipo de stemming (none, stem_es,stem_ru)
  • stopwords: ruta a la lista de palabras vacías
  • min_word_len: número de caracteres mínimos de una palabra para ser indizada

El stemming es el proceso por el cual se reduce una palabra a su lexema. De esta manera se consigue que todas las búsquedas que tienen la misma raíz devuelvan el mismo resultado. Es una técnica dependiente de idioma en que esté el contenido a transformar, de manera que se debe estudiar la localización de nuestra información para asociarle un stemmer u otro (o ninguno) . Vienen implementados uno para inglés y otro para ruso.

A continuación un ejemplo completo de configuración.